Postgres UPDATE..FROM query with multiple updates on the same row - sql

I am attempting to optimise a bulk UPDATE statement in Postgres using the UPDATE..FROM syntax to update from a list of values. It works except when the same row might be updated more than once in the same query.
For example say I have a table "test" with columns "key" and "value".
update test as t set value = v.value from (values
('key1', 'update1'),
('key1', 'update2') )
as v (key, value)
where t.key = v.key;
My desired behavior is for the row with key 'key1' to be updated twice, finishing with value set to 'update2'. In practice sometimes the value is updated to update1 and sometimes to update2. Also an update trigger function on the table is only invoked once.
The documentation (http://www.postgresql.org/docs/9.1/static/sql-update.html) explains why:
When a FROM clause is present, what essentially happens is that the target table is joined to the tables mentioned in the from_list, and each output row of the join represents an update operation for the target table. When using FROM you should ensure that the join produces at most one output row for each row to be modified. In other words, a target row shouldn't join to more than one row from the other table(s). If it does, then only one of the join rows will be used to update the target row, but which one will be used is not readily predictable.
Because of this indeterminacy, referencing other tables only within sub-selects is safer, though often harder to read and slower than using a join.
Is there any way to reformulate this query to achieve the behavior I'm looking for? Does the reference to sub-selects in the documentation give a hint?

Example (assuming id is a PK in the target table, and {id, date_modified} is a PK in the source table)
UPDATE target dst
Set a = src.a , b = src.b
FROM source src
WHERE src.id = dst.id
AND NOT EXISTS (
SELECT *
FROM source nx
WHERE nx.id = src.id
-- use an extra key field AS tie-breaker
AND nx.date_modified > src.date_modified
);
(in fact, this is deduplication of the source table -> forcing the source table to the same PK as the target table)

Related

Update multiple rows in the target table from one row in the source table using a natural key

I need to update two columns in a FACT table using data from a dimension table. the challenge is that I don't have a primary key that match both tables, so I have to use a natural key, two columns to create a unique value. besides the source have a single record and the target has multiples records. if I do a merge I get
ora-30926 unable to get a stable set of rows
and if I do an update I get another error. please I need help.
I try this update statement:
UPDATE dw.target_table obc
SET
( obc.sail_key,
obc.durations ) = (
SELECT
sd.sail_key,
sd.durations
FROM
dw.source_table sd
WHERE
obc.code_1 = sd.code_2
AND obc.date_1 = sd.date_2
)
WHERE
obc.item NOT IN (
30,
40
)
AND obc.sail_key = 0
and OBC.load_date between to_date('01-12-2018','DD-MM-YYYY')
AND to_date ('31-12-2018','DD-MM-YYYY');
and I try this merge statement:
MERGE INTO dw.target_table obc
USING ( SELECT distinct
code_2,date_2,durations,sail_key
FROM dw.source_table
) tb_dim
ON ( obc.code_1 = tb_dim.code_2
AND obc.date_1 = tb_dim.date_2 )
WHEN MATCHED THEN UPDATE SET obc.durations = tb_dim.durations,
obc.sail_key = tb_dim.sail_key
WHERE
obc.sail_key = 0
AND obc. NOT IN (
30,
40
)
AND obc.loaddate BETWEEN TO_DATE('01-01-2012','DD-MM-YYYY')
AND TO_DATE ('31-01-2012','DD-MM-YYYY');
ora-30926 unable to get a stable set of rows
This means (code_2,date_2) is not a unique key of tb_dim. Consequently your USING subquery does not produce a set which matches just one row to any row in obc. Consequently the MERGE fails, because Oracle cannot determine which row from the USING subquery should be applied to the target. The DISTINCT does not help because it is applied to the whole projection and it seems you have multiple different values of durations,sail_key for each permutation of code_2,date_2.
You don't say which error you get when you run your UPDATE but presumably it's ORA-01779 or ORA-01427. Something indicating the subquery isn't returning a set of joining keys.
So how do you fix the situation? We cannot give you the correct solution because this is a failure of your data model or your specification. Solving requires an understanding of your business that we do not have. But generally you need to find an extra rule which reduces the USING subquery to a set. That is:
add a third key column which allows the ON clause to map one row in tb_dim to one row in obc; or
use row_number() analytic function in the subquery to fake such a column, preferably ordering by a meaningful column such as date; or
add a criterion WHERE clause of the subquery to remove duplicate values of code_2,date_2.
Alternatively, if you don't care which particular values of durations,sail_key get applied you can use an aggregate:
USING (SELECT code_2
,date_2
,max(durations) as durations
,max(sail_key) as sail_key
FROM dw.source_table
group by code_2,date_2 ) tb_dim
Use whatever function makes sense to you.

Is Oracle MERGE NOT MATCHED THEN UPDATE possible?

We'd like to set the IS_DEL bit = 1 when a record exists in a Target table that doesn't exist in the Source table.
Is it possible to use a MERGE statement using the WHEN NOT MATCHED clause, but make it perform an UPDATE?
When attempting to do so, I'm getting a "ORA-00905: missing keyword" message.
MERGE
INTO AMEPSA.ENTERPRISE_LOCATION trg
USING (
SELECT C.LOCATION_KEY as LOCATION_KEY
FROM AMEPSA.ENTERPRISE_LOCATION C
INNER JOIN AMESTAGE.VW_LOCATION L ON C.REC_SRC_KEY_CD = L.LOCATION_ID
WHERE C.CURR_REC_IND = 'Y'
) src
ON (trg.LOCATION_KEY = src.LOCATION_KEY)
WHEN NOT MATCHED THEN UPDATE
SET trg.IS_DEL = 1
Does the "WHEN NOT MATCH" clause only support "THEN INSERT"?
From the documentation:
Use the MERGE statement to select rows from one or more sources for update or insertion into a table or view. You can specify conditions to determine whether to update or insert into the target table or view.
The syntax looks for rows in the source table (src) which do or do not have matching rows in the target table (trg). If there is a matching target row then it updates that; if there is not a matching row then it inserts a new row in the target table.
It does not, and cannot, look for rows in the target table that are not matched in the source table - which is what you are trying to identify and update.
The syntax diagrams for WHEN MATCHED and WHEN NOT MATCHED also make it clear that you cannot do WHEN NOT MATCHED THEN UPDATE.
Yes you can only insert when not match. See exact options in oracle merge.
The condition can refer to either the data source or the target table. If the condition is not true, then the database skips the update operation when merging the row into the table.

SQL Update statement that deletes rows if key clash

I have a table that has two columns Hotel_Guest_ID and Guest_ID that links Guest records to the Hotel details of that guest. The table has the constraint that each pair needs to be unique.
I now have a second table of Prime_ID and Duplicate_ID that was generated after cleaning up the Guest table of duplicates. I would like to go through the Booking table and if the Hotel.Guest_ID is found as a Duplicate_ID, to then replace it with Prime_ID.
update b
set h.Guest_ID = gd.Prime_ID
from Hotel as h
join Guest_Duplicates as gd
on h.Guest_ID = gd.Duplicate_ID
However this fails as often a Prime_ID already has a record with a specific Guest, at which point I want to just delete this row instead of updating it.
Is there a nice way to do this in a single pass or would I have to delete potential clash rows first, then update in a second query?
What you are looking for is a MERGE statement. You can insert, update and delete using a single statement. Here's an example -
MERGE Table1 AS t1
USING Table2 AS t2
ON t1.GuestID = t2.DuplicateID
WHEN MATCHED AND (any condition)
THEN DELETE
WHEN MATCHED
THEN UPDATE SET (assign statement)
WHEN NOT MATCHED
THEN
INSERT(column names)
VALUES(values to be inserted);

DB2 SQL Statement for Inserting new data and updating existing data

I've found a lot of near misses on this question. So many similar, but not quite right, scenarios. No doubt my ignorance will shine here.
Using DB2 and a shred of knowledge, my scenario is as follows:
On a table, insert a row of data if a given value is not present in a given column, or update the corresponding row if the value is present.
I have a table
id, bigint, not nullable
ref,varchar, nullable
I am not sure if a MERGE is the correct path here as most examples and thorough discussions all seem to revolve around merging one table into another. I'm simply gathering user input and either adding it or updating it. It seems like it should be really simple.
I'm using jdbc and prepared statements to get this done.
Is MERGE the correct way to do this?
When testing my query in DB2 Control Center, I run up against
"No row was found for FETCH, UPDATE or DELETE; or the result of a
query is an empty table"
or a variety of other errors depending on how I structure my MERGE. Here's what I have presently.
merge into table1 as t1
using (select id from table1 group by id) as t2
on t1.id = t2.id
when matched then update set t1.ref = 'abc'
when not matched then insert (t1.id, t1.ref) values (123, 'abc');
If I were to instead compose an update followed by an insert; for new data the insert runs and the update fails, and for existing data they both succeed resulting in bad data in the table, e.g. two identical rows.
The desired result is if on initial use with the values:
id = 1
ref = a
a new row is added. On subsequent use if the values change to:
id = 1
ref = b
the row with id = 1 is updated. Subsequent uses would follow the same rules.
Please let me know how I can phrase this question better.
Update id is not an automatic incrementing key. It's an external key that will be unique but not every thing we are referencing will need a related row in the table I'm attempting to update. This table is rather unstructured on its own but is part of a larger data model.
I'm a bit puzzled by your query. Reading the text makes me suspect that you want something like this:
merge into table1 as t1
using ( values (123, 'abc') ) as t2 (id, ref)
on t1.id = t2.id
when matched then update
set t1.ref = t2.ref
when not matched then
insert (id, ref) values (t2.id, t2.ref);
Is that correct?

Updating Records in Table A based on Records in Table B

I have to write a statement which fills a table (customers) with synthetically generated values. There is an addtional constraint that I should only fill those attributes (columns) with a special property (i.e. formally do a projection on them and then operate on them exclusively). These properties are stored in a second table, attributes.
My first draft consists of the following two statements:
-- Get the attributes (columns) we are interested in only
SELECT attributeID from attributes
WHERE tableID = 'customers'
-- Iterate over each row of customers, filling only those attributes (columns)
-- obtained by the above SELECT statement
UPDATE customers
SET (use the records from above select statement...)
Now my problem is how to put them together. I know there is the possibility of appending a WHERE clause to the SET clause, but that would select rows, not columns, as I need. I also read about PIVOT, but so far only inside one single table, not two, as is the case here. I would be very thankful for any hint, since I have no idea how to do this.
is not it you're looking for?
SQL Update Multiple Fields FROM via a SELECT Statement
UPDATE
Table
SET
Table.col1 = other_table.col1,
Table.col2 = other_table.col2
FROM
Table
INNER JOIN
other_table
ON
Table.id = other_table.id
Standard SQL-92 requires a scalar subquery:
UPDATE customers
SET attributeID = (
SELECT A1.attributeID
FROM attributes AS A1
WHERE A1.tableID = 'customers'
);
However, UPDATE customers...WHERE A1.tableID = 'customers' "smells" like you may be mixing data with metadata.