0

I am trying out SQLite and encountered a problem. There are 3 Tables A, B, and C. I want to update Table A using the sum of B and C.

Table A. James null.

Table B. James 5.

Table C James 2

so with the update, I want table A to have

James 3. (5-2)

Thank You

Kolay.Ne
  • 1,345
  • 1
  • 8
  • 23
user9052801
  • 53
  • 1
  • 6

3 Answers3

1

SQLite does not support joins in an UPDATE statement so you can do it by accessing directly the corresponding rows of the tables A and B like this:

update A 
set value = 
  (select value from B where name = A.name) -
  (select value from C where name = A.name)

If you want to update only the row with name = 'James' then add:

where name = 'James'

See the demo

forpas
  • 160,666
  • 10
  • 38
  • 76
  • You can't do that, `select value from B` is a row set, not a scalar... need to use aggregate like SUM, or MAX, to convert to scalar) – Dinu Mar 24 '19 at 12:16
  • 1
    If there is only 1 row returned as is the case here it is totally valid. There is no 1 to many relationship between A and B or C. – forpas Mar 24 '19 at 12:18
  • It doesn't matter, the type is still rowset, SQL can't add rowsets. Try it if you don't believe me, you will get an error. – Dinu Mar 24 '19 at 12:19
  • I will post a demo in a few mins. – forpas Mar 24 '19 at 12:20
  • 1
    SQL needs to infer from the query, not the schema, that the subquery is exactly 1x1 to convert to scalar. – Dinu Mar 24 '19 at 12:23
  • Let me ask you something: can't you see the facts? I posted a demo. You see it's working. Why all these comments? Can't you see that you are wrong? This is a case of 1 to 1. Funny though, there is another post with the same answer but you comment only here. Why? – forpas Mar 24 '19 at 12:27
  • The other post also uses aggregates :) I didn't know this runs in SQLite, but it shouldn't, because the query parser should run before the optimizer, and the query parser should not know about constraints, which can be more complicated on the matter of unique'ness. It looks like SQLite https://www.sqlite.org/eqp.html#subqueries is silently discarding possible secondary rows without any warning. So, kudos to you for knowing this works, but my comment is still correct: you should not write the query like this: it's not portable and prone to silent failures by discarding data, – Dinu Mar 24 '19 at 12:36
  • It also runs for mysql: https://www.db-fiddle.com/f/tFGdXSM7Xfq7CqPvufZEDC/1 and postgresql: https://www.db-fiddle.com/f/tFGdXSM7Xfq7CqPvufZEDC/2 and sqlserver: https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=6db1c7a527b0615afb21c2e6a371418f. Anything else? Did you learn anything today? – forpas Mar 24 '19 at 12:46
  • There are a lot of things in this world that work for the wrong reasons... until they don't :) IMHO, good practice and logic should not be circumvented because something "just works". But to each his own. Did you learn anything today? – Dinu Mar 24 '19 at 12:57
  • What does it take for you to see that you are wrong? Even the facts don't help. This is a case of 1x1 do you get it? As long as there is only 1 row returned, the statement is perfectly valid and this is the case where **only 1 row returned**. – forpas Mar 24 '19 at 13:00
  • https://stackoverflow.com/questions/40357613/what-does-correlated-scalar-subqueries-must-be-aggregated-mean This is one notable example where it doesn't. For one reason, because it's unstable, it's non-deterministic. If picking a random first row, the same query can return different results based on anything (such as running OPTIMIZE). A simple runtime check of none-or-one hides the design problem until it crashes. It's especially problematic on distributed systems w/ transaction replication, where one instance can have different data (not-yet-synced), trashing the whole array. – Dinu Mar 24 '19 at 23:01
0

Works in every DB:

UPDATE 
  "A" 
SET
  "x" = 
    (
      SELECT
        SUM("x")
      FROM "B"
        WHERE "B"."id"="A"."id"
    ) +
    (
      SELECT
        SUM("x")
      FROM "C"
        WHERE "C"."id"="A"."id"
    )
Dinu
  • 1,374
  • 8
  • 21
0

I believe the following demonstrates that Yes you can:-

DROP TABLE IF EXISTS ta;
DROP TABLE IF EXISTS tb;
DROP TABLE IF EXISTS tc;
CREATE TABLE IF NOT EXISTS ta (name TEXT, numb INTEGER);
CREATE TABLE IF NOT EXISTS tb (name TEXT, numb INTEGER);
CREATE TABLE IF NOT EXISTS tc (name TEXT, numb INTEGER);
INSERT INTO ta VALUES ('JAMES',null),('Mary',100);
INSERT INTO tb VALUES ('JAMES',5),('Sue',33);
INSERT INTO tc VALUES ('JAMES',2),('Anne',45);

UPDATE ta SET numb = 
    (SELECT sum(numb) FROM tb WHERE name = 'JAMES') 
    - 
    (SELECT sum(numb) FROM tc WHERE name = 'JAMES') 
WHERE name = 'JAMES';

SELECT * FROM ta;
SELECT * FROM tb;
SELECT * FROM tc;

This :-

  • Drops the tables if they exist allowing it to be rerun (simplifies modifications if need be).
    • column names name and numb have been assumed as they weren't given.
  • Creates the 3 tables (note table names used for the demo are ta, tb and tc)
  • Adds some data (note that additional rows have been added to show how to distinguish (at least to a fashion))
  • Updates column numb of table A (ta) where the name column has a value of JAMES according to the sum of the numb column from all rows with the same name (JAMES) from table tb minus the sum of the numb column from all rows with the same name (JAMES) from table tc
    • This may not be exactly what you want so it assumes that you want to sum all rows with the same name per table (ta and tc)
  • Queries all the tables (first is shown below as that is the table that has been updated.)

The first result showing that the row has been updated from null to 3 (5 - 2) and that the row for Mary has remained as it was :-

enter image description here

The following change to the UPDATE gets the name (rather than hard-coding 'JAMES' multiple times, as per the row(s) extract from the ta table, the use of hard-coded names perhaps making it easier to understand the working of the SQL).

UPDATE ta SET numb = (SELECT sum(numb) FROM tb WHERE name = ta.name) - (SELECT sum(numb) FROM tc WHERE name = ta.name) WHERE name = 'JAMES';

Note that should there not be an associated row (i.e. with the same name) in either tb or tc then the result will be null (whether or not sum is used).

MikeT
  • 51,415
  • 16
  • 49
  • 68