Merge table in Oracle with delete condition refering to source table - sql

For the following question it is said that the answer should be C. But I think the correct answer is Answer D as NOT MATCHED block inserts all unmatching records to target table. Can anybody explain this?
Thank you.
Q)View the Exhibit and examine the data in ORDERS_MASTER and MONTHLY_ORDERS tables.
Evaluate the following MERGE statement:
MERGE INTO orders_master o
USING monthly_orders m
ON (o.order_id = m.order_id)
WHEN MATCHED THEN
UPDATE SET o.order_total = m.order_total
DELETE WHERE (m.order_total IS NULL)
WHEN NOT MATCHED THEN
INSERT VALUES (m.order_id, m.order_total);
What would be the outcome of the above statement?
A. The ORDERS_MASTER table would contain the ORDER_IDs 1 and 2.
B. The ORDERS_MASTER table would contain the ORDER_IDs 1,2 and 3.
C. The ORDERS_MASTER table would contain the ORDER_IDs 1,2 and 4.
D. The ORDERS_MASTER table would contain the ORDER IDs 1,2,3 and 4.
Answer: C

The correct answer is indeed C, this is because the source of the merge operation is the monthly_orders table, which only contains two records with order_id 2 and 3 respectively.
Think about what will happen for each of these records:
For order_id = 2, because this id exists in the order_master table, we'll execute the MATCHED part of the merge statement, updating the order_total to 2500. Since the quantity for this record is not NULL, the DELETE won't do anything.
For order_id = 3, again, the id exists in the order_master table, so we execute the MATCHED part of the merge statement, updating the order_total to NULL and then issuing a DELETE on order_master for the row we just updated because the quantity on monthly_order is NULL.
This leaves us with order_id 1, 2 and 4, which matches answer C.
Code
CREATE TABLE orders_master (
order_id NUMBER(1) NOT NULL
,order_total NUMBER(10) NULL
)
/
CREATE TABLE monthly_orders (
order_id NUMBER(1) NOT NULL
,order_total NUMBER(10) NULL
)
/
INSERT INTO orders_master (order_id, order_total) VALUES (1, 1000)
/
INSERT INTO orders_master (order_id, order_total) VALUES (2, 2000)
/
INSERT INTO orders_master (order_id, order_total) VALUES (3, 3000)
/
INSERT INTO orders_master (order_id, order_total) VALUES (4, NULL)
/
INSERT INTO monthly_orders (order_id, order_total) VALUES (2, 2500)
/
INSERT INTO monthly_orders (order_id, order_total) VALUES (3, NULL)
/
MERGE INTO orders_master o
USING monthly_orders m
ON (o.order_id = m.order_id)
WHEN MATCHED THEN
UPDATE SET o.order_total = m.order_total
DELETE WHERE m.order_total IS NULL
WHEN NOT MATCHED THEN
INSERT VALUES (m.order_id, m.order_total)
/
COMMIT
/
SQL> select * from orders_master
2 /
ORDER_ID ORDER_TOTAL
---------- -----------
1 1000
2 2500
4

Related

How to correct my Snowflake Unique Constraint SQL statement?

I have a table that looks like:
ID|CREATED |VALUE
1 |1649122158|200
1 |1649122158|200
1 |1649122158|200
That I'd like to look like:
ID|CREATED |VALUE
1 |1649122158|200
And I run the following query:
DELETE FROM MY_TABLE T USING (SELECT ID,CREATED,ROW_NUMBER() OVER (PARTITION BY ID ORDER BY CREATED DESC) AS RANK_IN_KEY FROM MY_TABLE T) X WHERE X.RANK_IN_KEY <> 1 AND T.ID = X.ID AND T.CREATED = X.CREATED
But it removes everything from MY_TABLE and not just other rows with the same value. This is more than just selecting distinct records, I'd like to enforce a unique constraint to get the latest value of ID and keep just one record for it, even if there were duplicates.
So
ID|CREATED |VALUE
1 |1649122158|200
1 |1649122159|300
2 |1649122158|200
2 |1649122158|200
3 |1649122170|500
3 |1649122160|200
Would become (using the same final unique constraint statement):
ID|CREATED |VALUE
1 |1649122159|300
2 |1649122158|200
3 |1649122170|500
How can I improve my logic to properly handle these unique constraint modifications?
Check out this post: https://community.snowflake.com/s/question/0D50Z00008EJgemSAD/how-to-delete-duplicate-records-
If all columns make up a unique records, the recommended solution is the insert all the records into a new table with SELECT DISTINCT * and do a swap. You could also do a INSERT OVERWRITE INTO the same table.
Something like INSERT OVERWRITE INTO tableA SELECT DISTINCT * FROM tableA;
The following setup should leave rows with id of 1 and 3. And not delete all rows as you say.
Schema
create table t (
id int,
created int ,
value int
);
insert into t values(1, 1649122158, 200);
insert into t values(1 ,1649122159, 300);
insert into t values(2 ,1649122158, 200);
insert into t values(2 ,1649122158, 200);
insert into t values(3 ,1649122170, 500);
insert into t values(3 ,1649122160, 200);
Delete statement
with x as (
SELECT
id, created,
row_number() over(partition by id) as r
FROM t
)
delete from t
using x
where x.id = t.id and x.r <> 1 and x.created = t.created
;
Output
select * from t;
1 1649122158 200
3 1649122170 500
The logic is such, that the table in the using clause is joined with the operated on table. Following the join logic, it just matches by some key. In your case, you have key as {id,created}. This key is duplicated for rows with id of 2. So the whole group is deleted.
I'm no savvy in database schemas. But as a thought, you may add a row with a rank to existing table. And after that you can proceed with deletion. This way you do not need to create other table and insert values to that. Be warned that data may become fragmented(physically, on disks). So you will need to run some kind of tune up later.
Update
You may find this almost one-liner interesting:
SO answer
I will duplicate code here, as it is so small and well written.
WITH
u AS (SELECT DISTINCT * FROM your_table),
x AS (DELETE FROM your_table)
INSERT INTO your_table SELECT * FROM u;

Sqlite update not affecting all rows when using subquery

Based on this answer I am trying to update a column in table b based on values from table a, where table b has a foreign key to table a. My query executes successfully and works as expected on the rows it affects. However, it does not affect all rows, and I do not understand why. It seems to only affect three rows no matter how large the database.
Here is a reproducible example:
CREATE TABLE a ( qty1 INTEGER, qty2 INTEGER, aID INTEGER PRIMARY KEY)WITHOUT ROWID;
CREATE TABLE b ( bID INTEGER, aID INTEGER,FOREIGN KEY(aID) REFERENCES a(aID) ON DELETE CASCADE);
INSERT INTO a (qty1, qty2, aID) VALUES (0, 100, 1906250435150010797);
INSERT INTO a (qty1, qty2, aID) VALUES (100, 100, 1906250435150010798);
INSERT INTO a (qty1, qty2, aID) VALUES (0, 100, 1906250435150010799);
INSERT INTO b (bID, aID) VALUES (0, 1906250435150010797);
INSERT INTO b (bID, aID) VALUES (0, 1906250435150010798);
INSERT INTO b (bID, aID) VALUES (1, 1906250435150010798);
INSERT INTO b (bID, aID) VALUES (0, 1906250435150010799);
ALTER TABLE b ADD COLUMN SameQty INTEGER;
UPDATE b SET SameQty = 0 WHERE aID = (SELECT aID from a where qty1 != qty2);
UPDATE b SET SameQty = 1 WHERE aID = (SELECT aID from a where qty1 = qty2);
Expected result:
Actual result:
Tested on sqlite 3.26.0
Your update statement:
UPDATE b SET SameQty = 0 WHERE aID = (SELECT aID from a where qty1 != qty2);
has a subquery that can return multiple rows, but is used in a context where only a single row is expected. I feel like that should cause an error, but if you're not seeing one, maybe it's just picking a single arbitrary row from the results?
Anyways, you want IN:
UPDATE b SET SameQty = 0 WHERE aID IN (SELECT aID from a where qty1 != qty2);
Your using a subquery that can and does return more than one row in a scalar context when doing "WHERE aID = (SELECT aID from a". Use IN instead of =.
UPDATE b SET SameQty = 0 WHERE aID IN (SELECT aID from a where qty1 != qty2);
UPDATE b SET SameQty = 1 WHERE aID IN (SELECT aID from a where qty1 = qty2);
db<>fiddle

Update Table: Copy result from one row to another in oracle

i am facing the following problem when updating data in a table.
First the structure of the database.
I have 2 tables.
Table 1 :
Votings: Columns: PKID, RESULT, USAGE, GROUP
For each usage a group has to vote and the result is saved in this table.
Table 2:
Usages: Columns: PKID, STATUS, SOURCE
Each usage has a status, some usages have a source usage, but it can be null.
Now i need to migrate my data. I want to copy certain results from some votings to some other votings in the votings table.
And here i'm stuck...
The use case is the following:
Every voting with a usage in status 4 (i call it active) and group b should get the result of the voting of group b which is bound to the usage whose source is the usage in status 4 (the active usage).
These are the the results i want to insert:
select v2.result from votings v2
join usage us on us.pkid = v2.usage
where us.status=10 and us.source in (
select v3.usage from votings v3
join usage us2 on us2.pkid = v3.usage
where us2.status = 4 and v3.group = v2.group)
But now i'm not able to link these results to the correct row...
Here an example:
example merge
If I understood everything correctly You need merge similiar to this:
merge into votings v
using (select u2.pkid usid, vt.grp, vt.result
from votings vt
join usage u1 on vt.usage = u1.pkid and u1.status = 10
join usage u2 on u2.source = u1.pkid and u2.status = 4
) s
on (v.usage = s.usid and v.grp = s.grp)
when matched then update set result = s.result
Test data:
create table votings(PKID varchar2(3), RESULT int, USAGE varchar2(3), GRP varchar2(2));
insert into votings values ('V01', 23, 'U01', 'B');
insert into votings values ('V02', 17, 'U02', 'B');
insert into votings values ('V03', 9, 'U04', 'C');
insert into votings values ('V04', 3, 'U04', 'B');
create table usage (PKID varchar2(3), STATUS int, SOURCE varchar2(3));
insert into usage values ('U01', 10, null);
insert into usage values ('U02', 10, null);
insert into usage values ('U04', 4, 'U01');
After update value 3 for key V04 was replaced with 23 (key V01).

oracle sql - merge table with nested table as column

I have 2 tables and one nested table:
1.stores data about products which include following columns:
ITEM - product id(key)
STORE - store id(key)
PRICE
NORMAL_PRICE
DISCOUNTS - nested table with info about discounts include columns:
PromotionId(key)
PromotionDescription
PromotionEndDate
MinQty
DiscountedPrice
DiscountedPricePerMida
2- temp table with new discounts include columns:
PROMOTIONID(key)
PRODUCTID(key)
PROMOTIONDESCRIPTION
PROMOTIONENDDATE
MINQTY
DISCOUNTEDPRICE
DISCOUNTEDPRICEPERMIDA
What i need to do is merge table 2 into table 1 - if no match insert else ignore
(when match is: product id matching in table 1 and 2 and for this product sub table PROMOTIONID match PROMOTIONID from table 2)
This is where I got so far and I have difficulty with nested part - ON clause and Insert clause
MERGE INTO PRICES P
USING(SELECT * FROM TMP_PROMO)T
ON(P.ITEM=T.PRODUCTID AND P.STORE=50 AND P.DISCOUNTS.PROMOTIONID=T.PROMOTIONID)
WHEN NOT MATCHED THEN INSERT (P.DISCOUNTS)
VALUES(T.PROMOTIONID,
T.PROMOTIONDESCRIPTION,
T.PROMOTIONENDDATE,
T.MINQTY,
T.DISCOUNTEDPRICE,
T.DISCOUNTEDPRICEPERMIDA);
I know that this is wrong but I can't find anywhere how to do it
example:
Prices table:
row1(1,50,...,nested_table[(11,...),(12,...)])
row2(2,50,...,nested_table[(10,...),(12,...)])
new promo table:
(15,1,...)
(11,1,...)
new promo with id 15 will be added to row1 and row2
and promo with id 11 will not be added
Please help,
thanks
What you intend to do is not realy a MERGE. You are adding a new promotion in each record that doesn't contain it.
Below is an answer how yu would approach it if you would use not a nested table but a conventional child table.
Setup (simplified to a minimum)
create table ITEM
(ITEM_ID NUMBER PRIMARY KEY);
create table ITEM_PROMO
(ITEM_ID NUMBER REFERENCES ITEM(ITEM_ID),
PROMO_ID NUMBER);
create table TMP_PROMO
(PROMO_ID NUMBER);
insert into ITEM values (1);
insert into ITEM values (2);
insert into ITEM_PROMO values (1,11);
insert into ITEM_PROMO values (1,12);
insert into ITEM_PROMO values (2,10);
insert into ITEM_PROMO values (2,12);
insert into TMP_PROMO values (15);
insert into TMP_PROMO values (11);
commit;
The first thing you need to find is which promotions are missing for an item.
Use a cross join to get all combination and constrain those promotions that EXISTS for a particular ITEM_ID:
select ITEM.ITEM_ID, TMP_PROMO.PROMO_ID
from ITEM cross join TMP_PROMO
where NOT EXISTS (select NULL from ITEM_PROMO where ITEM_ID = ITEM.ITEM_ID and PROMO_ID = TMP_PROMO.PROMO_ID)
;
This gives as expected
ITEM_ID PROMO_ID
---------- ----------
2 11
1 15
2 15
Now simple add those new promotions
INSERT INTO ITEM_PROMO
select ITEM.ITEM_ID, TMP_PROMO.PROMO_ID
from ITEM cross join TMP_PROMO
where NOT EXISTS (select NULL from ITEM_PROMO where ITEM_ID = ITEM.ITEM_ID and PROMO_ID = TMP_PROMO.PROMO_ID)
;
This should give you a hint how to approach while using nested tables (or how to change the DB design:)

Join all rows in table with first matched row in other table [duplicate]

This question already has answers here:
SQL join to correlated subquery where tables are related by overlapping ranges
(2 answers)
Closed 8 years ago.
I have 2 tables, and i want table1 left join table2, idea is to show all table1's rows and for each table1's row, i search records in all table2 until the 1st matched value.
So results' row number = talbe1's row number, just add table2's 1st matched value, but here i get results' row number > talbe1's row number
It depends on your table structures and how you join them. I can think of an example that the row number of the result > table 1 row number.
create table staff (
staff_id int,
name varchar(100)
)
;
create table stationery (
s_id int,
name varchar(100),
staff_id int
)
;
insert into staff values (1, 'Peter');
insert into staff values (2, 'Sally');
insert into stationery values (1, 'ruler', 1);
insert into stationery values (2, 'pencil', 1);
insert into stationery values (3, 'pencil', 2);
select *
from staff s1
left join stationery s2 on s1.staff_id = s2.staff_id ;
STAFF_ID NAME S_ID
1 Peter 11 Peter 22 Sally 3