Ecto.Repo.insert_all

You're seeing just the callback insert_all, go back to Ecto.Repo module for more information.
Link to this callback

insert_all(schema_or_source, entries_or_query, opts)

View Source (optional)

Specs

insert_all(
  schema_or_source :: binary() | {binary(), module()} | module(),
  entries_or_query ::
    [map() | [{atom(), term() | Ecto.Query.t()}]] | Ecto.Query.t(),
  opts :: Keyword.t()
) :: {integer(), nil | [term()]}

Inserts all entries into the repository.

It expects a schema module (MyApp.User) or a source ("users") or both ({"users", MyApp.User}) as the first argument. The second argument is a list of entries to be inserted, either as keyword lists or as maps. The keys of the entries are the field names as atoms and the value should be the respective value for the field type or, optionally, an Ecto.Query that returns a single entry with a single value.

It returns a tuple containing the number of entries and any returned result as second element. If the database does not support RETURNING in INSERT statements or no return result was selected, the second element will be nil.

When a schema module is given, the entries given will be properly dumped before being sent to the database. If the schema primary key has type :id or :binary_id, it will be handled either at the adapter or the storage layer. However any other primary key type or autogenerated value, like Ecto.UUID and timestamps, won't be autogenerated when using insert_all/3. You must set those fields explicitly. This is by design as this function aims to be a more direct way to insert data into the database without the conveniences of insert/2. This is also consistent with update_all/3 that does not handle auto generated values as well.

It is also not possible to use insert_all to insert across multiple tables, therefore associations are not supported.

If a source is given, without a schema module, the given fields are passed as is to the adapter.

Options

  • :returning - selects which fields to return. When true, returns all fields in the given schema. May be a list of fields, where a struct is still returned but only with the given fields. Or false, where nothing is returned (the default). This option is not supported by all databases.

  • :prefix - The prefix to run the query on (such as the schema path in Postgres or the database in MySQL). This overrides the prefix set in the query and any @schema_prefix set in the schema.

  • :on_conflict - It may be one of :raise (the default), :nothing, :replace_all, {:replace_all_except, fields}, {:replace, fields}, a keyword list of update instructions or an Ecto.Query query for updates. See the "Upserts" section for more information.

  • :conflict_target - A list of column names to verify for conflicts. It is expected those columns to have unique indexes on them that may conflict. If none is specified, the conflict target is left up to the database. It may also be {:unsafe_fragment, binary_fragment} to pass any expression to the database without any sanitization, this is useful for partial index or index with expressions, such as ON CONFLICT (coalesce(firstname, ""), coalesce(lastname, "")).

  • :placeholders - A map with placeholders. This feature is not supported by all databases. See the "Placeholders" section for more information.

See the "Shared options" section at the module documentation for remaining options.

Source query

A query can be given instead of a list with entries. This query needs to select into a map containing only keys that are available as writeable columns in the schema.

Examples

MyRepo.insert_all(Post, [[title: "My first post"], [title: "My second post"]])

MyRepo.insert_all(Post, [%{title: "My first post"}, %{title: "My second post"}])

query = from p in Post,
  join: c in assoc(p, :comments),
  select: %{
    author_id: p.author_id,
    posts: count(p.id, :distinct),
    interactions: sum(p.likes) + count(c.id)
  },
  group_by: p.author_id
MyRepo.insert_all(AuthorStats, query)

Upserts

insert_all/3 provides upserts (update or inserts) via the :on_conflict option. The :on_conflict option supports the following values:

  • :raise - raises if there is a conflicting primary key or unique index

  • :nothing - ignores the error in case of conflicts

  • :replace_all - replace all values on the existing row with the values in the schema/changeset, including fields not explicitly set in the changeset, such as IDs and autogenerated timestamps (inserted_at and updated_at). Do not use this option if you have auto-incrementing primary keys, as they will also be replaced. You most likely want to use {:replace_all_except, [:id]} or {:replace, fields} explicitly instead. This option requires a schema

  • {:replace_all_except, fields} - same as above except the given fields are not replaced. This option requires a schema

  • {:replace, fields} - replace only specific columns. This option requires :conflict_target

  • a keyword list of update instructions - such as the one given to update_all/3, for example: [set: [title: "new title"]]

  • an Ecto.Query that will act as an UPDATE statement, such as the one given to update_all/3

Upserts map to "ON CONFLICT" on databases like Postgres and "ON DUPLICATE KEY" on databases such as MySQL.

Return values

By default, both Postgres and MySQL will return the number of entries inserted on insert_all/3. However, when the :on_conflict option is specified, Postgres and MySQL will return different results.

Postgres will only count a row if it was affected and will return 0 if no new entry was added.

MySQL will return, at a minimum, the number of entries attempted. For example, if :on_conflict is set to :nothing, MySQL will return the number of entries attempted to be inserted, even when no entry was added.

Also note that if :on_conflict is a query, MySQL will return the number of attempted entries plus the number of entries modified by the UPDATE query.

Placeholders

Passing in a map for the :placeholders allows you to send less data over the wire when you have many entries with the same value for a field. To use a placeholder, replace its value in each of your entries with {:placeholder, key}, where key is the key you are using in the :placeholders option map. For example:

placeholders = %{blob: large_blob_of_text(...)}

entries = [
  %{title: "v1", body: {:placeholder, :blob}},
  %{title: "v2", body: {:placeholder, :blob}}
]

Repo.insert_all(entries, placeholders: placeholders)

Keep in mind that:

  • placeholders cannot be nested in other values. For example, you cannot put a placeholder inside an array. Instead, the whole array has to be the placeholder

  • a placeholder key can only be used with columns of the same type

  • placeholders require a database that supports index parameters, so they are not currently compatible with MySQL