1

Suppose there is a table with data:

+----+-------+
| id | value |
+----+-------+
|  1 |     0 |
|  2 |     0 |
+----+-------+

I need to do a bulk update. And use COPY FROM STDIN for fast insert to temp table without constraints and so it can contains duplicate values in id column

Temp table to update from:

+----+-------+
| id | value |
+----+-------+
|  1 |     1 |
|  2 |     1 |
|  1 |     2 |
|  2 |     2 |
+----+-------+

If I simply run a query like with:

UPDATE test target SET value = source.value FROM tmp_test source WHERE target.id = source.id;

I got wrong results:

+----+-------+
| id | value |
+----+-------+
|  1 |     1 |
|  2 |     1 |
+----+-------+

I need the target table to contain the values that appeared last in the temporary table.

What is the most effective way to do this, given that the target table may contain millions of records, and the temporary table may contain tens of thousands?**

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
Alew
  • 316
  • 4
  • 12

1 Answers1

1

Assuming you want to take the value from the row that was inserted last into the temp table, physically, you can (ab-)use the system column ctid, signifying the physical location:

UPDATE test AS target
SET    value = source.value
FROM  (
   SELECT DISTINCT ON (id)
          id, value
   FROM   tmp_test
   ORDER  BY id, ctid DESC
   ) source
WHERE  target.id = source.id
AND    target.value <> source.value;  -- skip empty updates

About DISTINCT ON:

This builds on a implementation detail, and is not backed up by the SQL standard. If some insert method should not write rows in sequence (like future "parallel" INSERT), it breaks. Currently, it should work. About ctid:

If you want a safe way, you need to add some user column to signify the order of rows, like a serial column. But do your really care? Your tiebreaker seems rather arbitrary. See:

AND target.value <> source.value

skips empty updates - assuming both columns are NOT NULL. Else, use:

AND target.value IS DISTINCT FROM source.value

See:

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228