lapis icon indicating copy to clipboard operation
lapis copied to clipboard

Insert multiple row in a single SQL query

Open tpimh opened this issue 6 years ago • 6 comments

Is it possible to Model:create() not just a single row, but several? How do I do it?

tpimh avatar Mar 10 '18 17:03 tpimh

It's not possible to do it with model:create, the easiest way would be to manually write a query. You can also do a begin commit transaction to bundle separate inserts done with model:create together, which should speed it up significantly. Just be careful you don't leave the connection in a transaction state, since it will be reused later.

leafo avatar Mar 10 '18 18:03 leafo

Maybe it's a good idea for create to also accept array as first argument? Would a pull request with such improvement be useful?

tpimh avatar Mar 10 '18 19:03 tpimh

Maybe it's a good idea for create to also accept array as first argument? Would a pull request with such improvement be useful?

It's pretty common to override create for custom initialization, and changing the interface could have impact on that. If this was a method It would probably make sense have a separate method, especially since lua does not have separate array types. That aside, I'm not sure the additional API is worth it. Lapis' orm gives you what you need for most common queries, and I recommend people trying to do more complex things to write queries themselves, using the primitive query building functions provided in the db module.

leafo avatar Mar 15 '18 17:03 leafo

@tpimh Something along the lines of this should work:

-- model is just the class
-- directive example: "ON CONFLICT DO NOTHING"
bulk_insert = (model, tvals, directive = "", tx_size = 500) ->
  table_name = model\table_name!
  columns = [ n.column_name for n in *model\columns! ]
  columns_str = table.concat(columns, ",")

  DB.query "BEGIN"

  for n = 1, #tvals, tx_size
    batch_size = math.min(tx_size - 1, #tvals - n)
    query = "INSERT INTO #{table_name} (#{columns_str}) VALUES "
    query_tail = {}

    for n_offset = 0, batch_size
      col_vals = table.concat([ DB.escape_literal(
        tvals[n + n_offset][col]
      ) for col in *columns ], ",")
      table.insert(query_tail, "\n(#{col_vals})")

    query ..= table.concat(query_tail, ",")
    if directive
      query ..= " #{directive}"
    query ..= ";"


    DB.query query

  DB.query "COMMIT"

Of course you also need to error-check and ROLLBACK to prevent connection orphaning.

turbo avatar Aug 09 '19 11:08 turbo

@turbo Thanks! I'll give it a try.

tpimh avatar Aug 09 '19 11:08 tpimh

Don't run it blindly though ;). E.g. if your create table contains subqueries, this will double-escape them. Customize it to your needs.

turbo avatar Aug 09 '19 15:08 turbo