How to Update the Following Table with MERGE in Oracle? - sql

I have the following data set (Oracle 12):
Table X
+---------+--------+---------------+--------+
| COLN | COLM | COLK | COLP |
+---------+--------+---------------+--------+
| 1 | 500 | K1 | 777 |
+---------+--------+---------------+--------+
Table A
+---------+--------+---------------+--------+
| COL1 | COL2 | COL3 | COL4 |
+---------+--------+---------------+--------+
| 1 | K1 | 500 | B |
| 1 | K2 | 500 | NULL |
+---------+--------+---------------+--------+
Table B
+---------+--------+---------+
| COLZ | COLX | COLW |
+---------+--------+---------+
| 1 | K1 | 777 |
| 1 | K2 | 678 |
+---------+--------+---------+
The three tables have the following commonality:
X.COLN = A.COL1 = B.COLZ
X.COLk = A.COL2 = B.COLX
X.COLM = A.COL3
I need to write a query which retrieves values for the following columns in one query:
X.COLK, X.COLP, B.COLX, B.COLW
The ultimate goal is, if the following conditions are met:
If there more than one record in Table A where A.COL1's and A.COL3's are matching (and there is a corresponding record in Table X)
And one of the rows is not null, e.g. A.COL4 = B, and another one is NULL
I update Table X to replace X.COLK, X.COLP (K1 and 777) in my MERGE statement with values in Table B (B.COLX, B.COLW -- K2 and 678).
Is this possible?
MERGE INTO X FX
USING (
SELECT COLX ONGOING_X, COLW ONGOING_W
FROM B
WHERE (COLZ, COLX) IN
(SELECT COL1, COL2
FROM A
WHERE COL3 = ?
AND COL1 = ?
AND COL4 IS NULL)
) NEW_B
ON (FX.COLk = ?
AND FX.COLP = ?)
WHEN MATCHED THEN
UPDATE SET
FX.COLk = NEW_B.ONGOING_X,
FX.FOLP = NEW_B.ONGOING_W;

You may do a MERGE using ROWID.
MERGE INTO x tgt USING (
WITH c AS (
SELECT col1,
col3,
MAX(
CASE
WHEN col4 IS NULL THEN col2
END
) AS col2 --Ongoing col2 as indicated from col4
FROM a
GROUP BY col1,
col3
HAVING COUNT(
CASE
WHEN col4 IS NULL THEN 1
END
) = 1 AND COUNT(col4) = 1 --Contains one and exactly one NULL and one NON NULL
) SELECT x.rowid AS rid,
b.*
FROM x
JOIN c ON c.col1 = x.coln AND c.col3 = x.colm
JOIN b ON b.colz = c.col1 AND b.colx = c.col2 --Join with ongoing value from c( a.k.a table A )
)
src ON ( tgt.rowid = src.rid ) --ROWID match
WHEN MATCHED THEN UPDATE SET tgt.colk = src.colx,
tgt.colp = src.colw;
Demo

Related

Make a select query with stored procedure from multiple independent tables

Table A
id | food_id | price
1 | 3 | 5
2 | 7 | 9
3 | 3 | 8
Table B
id | drink_id | price | type_id
1 | 8 | 8 | 3
2 | 6 | 9 | 3
3 | 6 | 10 | 1
Table C
id(food_id) | Name
3 | Banana
7 | Strawberry
I have 3 tables like this. I want the result of the query written with the stored procedure to be as follows.
column 1
13 (select sum(price) from tableA where food_id = 3)
column 2
2 (Select count(*) from tableB where drink_id = 6)
column 3
9 (Select sum(price) from tableB where drink_id = 6 and type_id = 3)
column 4
Banana (Select Name from tableA a left join tableC c On a.id = c.id) where a.id = 1)
Suppose there are millions of rows of data in these tables. How to write the best stored procedure?
NOTE: Not validated as I don't have your tables, but this format should work once you put it into an SP.
-- put this into an SP
-- delcare varaibles, probably should change them to match results (could be decimal or int depending on what your SUM does)
DECLARE #SumPriceFood AS VARCHAR(50)
DECLARE #CountDrink AS VARCHAR(50)
DECLARE #SumPriceDrink AS VARCHAR(50)
DECLARE #Name AS VARCHAR(50)
-- get your data (not tested these as they are your tables)
SELECT #SumPriceFood = SUM(price) from tableA where food_id = 3
SELECT #CountDrink = COUNT(*) from tableB where drink_id = 6
SELECT #SumPriceDrink = sum(price) from tableB where drink_id = 6 and type_id = 3
SELECT #Name = Name from tableA a left join tableC c On a.id = c.id where a.id = 1
-- now just select the variable values you populated above for your results
SELECT #SumPriceDrink AS SumPriceDrink, #CountDrink AS CountDrink, #SumPriceDrink AS SumPriceDrink, #Name AS Name
you can use your queries as subqueries.
For an Stored procedure it isn't enough but who knows what ypu need it for
DECLARE #food_id INTEGER = 3;
DECLARE #drink_id int = 6;
DECLARE #type_id INTEGER = 3;
DEClARE #a_id int = 1;
SELECT
(select sum(price)from tableA where food_id=#food_id) col1,
(Select count(*) from tableB where drink_id=#drink_id) col2,
(Select sum(price) from tableB where drink_id=#drink_id and type_id=3) col3,
(Select Name from tableA a left join tableC c On a.id = c.id where a.id = #a_id) col4
col1 | col2 | col3 | col4
---: | ---: | ---: | :-----
13 | 2 | 9 | Banana
CREATE PROCEDURE Getdata
#food_id AS INTEGER,#drink_id int,#type_id INTEGER ,#a_id int
AS
SELECT
(select sum(price)from tableA where food_id=#food_id) col1,
(Select count(*) from tableB where drink_id=#drink_id) col2,
(Select sum(price) from tableB where drink_id=#drink_id and type_id=3) col3,
(Select Name from tableA a left join tableC c On a.id = c.id where a.id = #a_id) col4
EXEC Getdata #food_id = 3,#drink_id = 6,#type_id = 3,#a_id = 1;
col1 | col2 | col3 | col4
---: | ---: | ---: | :-----
13 | 2 | 9 | Banana
db<>fiddle here

PSQL select either a or b in order but not both a and b if both a and b exists

I have a PSQL table
+--------+------+------+------+
| Col1 | Col2 | Col3 | Col4 |
+--------+------+------+------+
| 001 | 00A | 00B | 001 |
| 001001 | 00A | 00B | 001 |
| 002 | 00X | 00Y | 002 |
| 002002 | 00X | 00Y | 002 |
+--------+------+------+------+
I have the following PSQL query:
select *
from my_table
where (Col1 = '001' or Col4 = '001')
and Col2 = '00A'
order by Col3 asc;
I get the first two rows.
Here what happens is that it matches both conditions for OR condition. I need to match only one of the or conditions. That is if first condition (Col1='001001') is true then do not evaluate the next condition.
I need to select only the 2nd row (| 001001 | 00A | 00B | 001 |)
I have build another query using EXCEPT
select *
from my_table
where (Col1 = '001' or Col4 = '001')
and Col2 = '00A'
except (select *
from my_table
where Col1 != '001'
and Col2 = '00A')
order by Col3 asc
limit 1;
I would like to know if there is any other elegant queries for this job?
Your explanation is confusing as you say col1 = '001001' in one place and use 001 in the query. But I presume you want to use a hierarchy of comparison and return the one with the highest per each group ( col2,col3,col4) . Use DISTINCT ON. Change the condition in whichever way you like to return the appropriate row.
SELECT DISTINCT ON (col2, col3, col4) *
FROM my_table WHERE col2 = '00A'
ORDER BY col2,
col3,
col4,
CASE
WHEN col1 = '001001' THEN 1
WHEN col4 = '001' THEN 2
END;
DEMO
Does this give you what you want?
select *
from my_table
where (Col1 = '001' and Col2 != '00A')
or ((Col1 is null or Col1 = '') and Col4 = '001' and Col2 = '00A')
order by Col3 asc;

SQL help on count in certain ID

Do you know how to display only the lines in table for same ID where col3 is not 'X'?
e.g., in the following table, it should display only ID 2 (as all the col2 are null)
ID | col1 | col2 | col3
---+------+------+-----
1 | 0 | 0 | X
1 | D | C | null
1 | D | C | null
2 | 0 | 0 | null
2 | D | C | null
2 | D | C | null
It should work for all ID with some many line by ID and only the same ID with all line having null.
If you are looking to get records where ID does not have at least one X in col 3 for other records:
SELECT Y.*
FROM Your_Table Y
WHERE Y.ID NOT IN (SELECT X.ID FROM YOUR_TABLE X WHERE X.ID=Y.ID AND X.COL3='X')
Most DBMS support 3 valued logic - True, False, and Undefined. NULL <> 3 is undefined, since NULL is an unknown value. You need to handle NULLs explicitly.
SELECT *
FROM Your_Table
WHERE col3 <> X
OR col3 IS NULL;
select * from table
where (col1 = col2) and (col3 <> 'X')
Use window functions or not exists:
select t.*
from t
where not exists (select 1 from t t2 where t2.id = t.id and t2.col3 = 'X');

Iterating through every row efficient?

Suppose I have the following table T1:
| col1 | col2 |
|------|------|
| 0 | 0 | // A++
| 3 | 123 | // B++
| 0 | 5 | // C++
| 8 | 432 | // A++
| 0 | 4 | // B++
I now need to create a trigger (on INSERT), that analyses every row, increases a counter (see below), populates the table T2 with the values of the counter:
IF col1 = 0 AND col2 = 0
A++
ELSE IF col1 = 0 col2 > 0
B++
ELSE IF col1 > 0
C++
In this case, T2 would look like:
| id | A | B | C |
|----|---|---|---|
| 1 | 1 | 2 | 2 |
My question is more about the design: Should I really iterate through each row, as described HERE, or is there a more efficient way?
Try something like this in trigger
;with data as
(
SELECT Sum(CASE WHEN col1 = 0 AND col2 = 0 THEN 1 END) AS a,
Sum(CASE WHEN col1 = 0 AND col2 > 0 THEN 1 END) AS b,
Sum(CASE WHEN col1 > 0 THEN 1 END) AS c
FROM (VALUES (0, 0 ),
(3, 123 ),
(0, 5 ),
(8, 432 ),
(0, 4 ) ) tc ( col1, col2 )
)
UPDATE yt
SET a = dt.a,
b = dt.b,
c = dt.c
FROM yourtable yt
JOIN data dt
ON a.id = b.id
This does not require row by row iteration. Replace the table valued constructor with Inserted table
This is something you should not write into a table (unless there aren't millions of rows and you need this for performance...). You should rather get this information on-the-fly like this:
DECLARE #T1 TABLE(col1 INT,col2 INT);
INSERT INTO #T1(col1,col2) VALUES
(0,0)
,(3,123)
,(0,5)
,(8,432)
,(0,4);
SELECT p.*
FROM
(
SELECT CASE WHEN col1=0 AND col2=0 THEN 'A'
WHEN col1=0 AND col2>0 THEN 'B'
WHEN col1>0 THEN 'C' END AS Category
FROM #T1 AS t
) AS tbl
PIVOT
(
COUNT(Category) FOR Category IN(A,B,C)
) AS p
The result
A B C
1 2 2
And I would suggest you to add another option (with ELSE) to catch invalid data (e.g. negativ values).

Update unique rows in SQL

I have a table
id | col1 | col3| col4
1 | x | r |
2 | y | m |
3 | z | p |
4 | x | r |
i have to update all unique rows of this table
i.e
id | col1 | col3| col4
1 | x | r | 1
2 | y | m | 1
3 | z | p | 1
4 | x | r | 0
i can fetch unique rows by
select distinct col1,col2 from table
.But how can i identify these rows in order to update them.Please help.
You can use the group by to pick unique result:
SELECT MIN(ID) AS ID FROM TABLE GROUP BY COL1, COL3;
id | col1 | col3
1 | x | r
2 | y | m
3 | z | p
Then
UPDATE TABLE SET col4 = 1 WHERE ID IN (SELECT MIN(ID) FROM TABLE GROUP BY COL1, COL3);
Restriction is that the id column should be unique.
If it is a small enough table, here is what you can do
Step 1: Update everything to 1
Update Table Set Col4 = 1
Step 2: Update all dups to 0 (OTTOMH)
Update Table
Set Col4 = 0
From
(
Select Col1, Min (Id) FirstId
From Table
Group By Col1
Having Count (*) > 1
) Duplicates
Where Table.Col1 = Duplicates.Col1
And Table.Id <> Duplicates.FirstId
You can also try:
UPDATE test
SET col4 = 1
WHERE id IN
(
SELECT t1.id
FROM table_name t1
LEFT JOIN table_name t2
ON t2.id < t1.id
AND t2.col1 = t1.col1
AND t2.col3 = t1.col3
WHERE t2.id IS NULL
)
One more slightly convoluted option, to set both 0 and 1 values in one hit:
update my_table mt
set col4 = (
select case when rn = 1 then 1 else 0 end
from (
select id,
row_number() over (partition by col1, col3 order by id) as rn
from my_table) tt
where tt.id = mt.id);
4 rows updated.
select * from my_table order by id;
ID COL1 COL3 COL4
---------- ---- ---- ----------
1 x r 1
2 y m 1
3 z p 1
4 x r 0
This is just using row_number() to decide which of the unique combinations is first, arbitrarily using the lowest id, assigning that the value of one, and everything else zero.