sqorn/sqorn

Allow multiple updates

Closed this issue · 3 comments

First of all thank you for this great library !
I would like to be able to build the following kind of query.
I think it isn't possible at the moment or maybe I just could not find how.

        update trad.bcas as t
        set quantity = v.quantity::integer
        from (values
            ($1, $2),
            ($3, $4),
            ($5, $6)
        ) as v(code, quantity)
        where t.store_id = $7
        and t.schedule_id = $8
        and t.product_code = v.code
        returning *

I did it using the following, code

    const bcas = [{
        quantity: 1,
        code: '2310'
    }, {
        quantity: 2,
        code: '2730'
    }, {
        quantity: 3,
        code: '3511'
    }];
    
    let qb = sq.l`
        update trad.bcas
        set quantity = v.quantity::integer
        from (values
    `;

    const last = bcas.length - 1;
    bcas.map((b, index) => {
        qb = qb.l`(${ b.code}, ${ b.quantity })`;
        if (index !== last) qb = qb.l`,`;
    });

    qb = qb.l`) as v(code, quantity)
        where store_id = ${ storeId }
        and schedule_id = ${ scheduleId }
        and product_code = v.code
    `;

is there a better way atm ?
Thanks :)

You're right. Right now there is no better way to write it because there's no mechanism for adding an additional from clause to an update query.

According to Postgres's grammar, there can only be a single update table. I think I'll change how update queries are compiled so that the first table passed to .from is used as the update table, and subsequent tables are used in the from clause.

I've also been thinking about adding an overload to .from that accepts an object, and this seems like the perfect use case. Object keys will be table names (to the right of as), and object values will the source tables (either names or an array of values).

You'd then be able to write your query as:

const bcas = [{
  quantity: 1,
  code: '2310'
}, {
  quantity: 2,
  code: '2730'
}, {
  quantity: 3,
  code: '3511'
}];

sq.from({
    t: 'trad.bcas',
    v: bcas
  })
  .set`quantity = v.quantity::integer`
  .where({
    't.storeId': storeId,
    't.scheduleId': scheduleId,
     equalProductCode: sq.l`t.product_code = v.code`
  })
  .return`*`

That would be great ! Feel free to use my case for your needs.
I'd be glad to try out some wip if you want me to.
Thank you for your quick answer :)

This is now possible using sqorn-pg v0.0.27. Your query can be written as:

sq.from({ t: 'trad.bcas' }) // first .from call used for UPDATE CLAUSE
  .from({ v: bcas }) // subsequent .from calls used for FROM CLAUSE
  .set`quantity = v.quantity::integer`
  .where({
    't.storeId': storeId,
    't.scheduleId': scheduleId,
    join: sq.l`t.product_code = v.code`
  })
  .return`*`

or as

const values = sq .extend(...bcas.map(b => sq.l`(${b.quantity}, ${b.code})`)).join(', ')

sq.l`update trad.bcas as t`.l`set quantity = v.quantity::integer`
  .l`from (values ${values}) as v(quantity, code)`
  .l`where (t.store_id = ${storeId} and t.schedule_id = ${scheduleId} and t.product_code = v.code)`
  .l`returning *`

Thanks for the feedback. Let me know if you have any other suggestions.