Update Oracle SQL - table with values from duplicates - sql

i hope that somebody could help. I need to update a table from a select with duplicates.
ID;CLASS;VALUE;NEW
1;a;a3;
1;b;s6;
1;c;b99;
2;a;s3;
2;b;r6;
2;c;b99;
3;a;s5;
4;a;r6;
4;b;a3;
Look at my example table, there is a colum NEW which i have to update. In the example the column NEW was filled manually.
Here is the goal (as shown in table col NEW):
1.find duplicates via ID (HAVING COUNT(*) >1 or something like that)
UPDATE TABLE SET NEW=
CLASS || '_' || VALUE
WHERE CLASS='a' or 'b'
Easy for you?
Thx in advance

The logic behind is not completely clear; this could be a way.
setup:
create table yourTable(id, class, value, new) as
(
select 1, 'a', 'a3', cast (null as varchar2(10)) from dual union all
select 1, 'b', 's6', null from dual union all
select 1, 'c', 'b99', null from dual union all
select 2, 'a', 's3', null from dual union all
select 2, 'b', 'r6', null from dual union all
select 2, 'c', 'b99', null from dual union all
select 3, 'a', 's5', null from dual union all
select 4, 'a', 'r6', null from dual union all
select 4, 'b', 'a3', null from dual
)
query:
merge into yourTable t1
using (
select listagg(value, '_') within group (order by class) as new,
id
from yourTable
where class in ('a', 'b')
group by id
having count(distinct class) = 2
) t2
on ( t1.id = t2.id
and t1.class in ('a', 'b')
)
when matched then
update set t1.new = t2.new
result:
SQL> select *
2 from yourTable;
ID C VAL NEW
---------- - --- ----------
1 a a3 a3_s6
1 b s6 a3_s6
1 c b99
2 a s3 s3_r6
2 b r6 s3_r6
2 c b99
3 a s5
4 a r6 r6_a3
4 b a3 r6_a3
9 rows selected.

Related

Is there a way to find rows of a certain value based on the previous row?

I'm trying to search in a table rows that have a certain value but the previous row has to include specific values as well. Ex.
ID
column1
column2
1
S
Date
1
T
Date
1
J
Date
1
C
Date
2
D
Date
2
Q
Date
2
L
Date
2
J
Date
2
C
Date
3
L
Date
3
T
Date
3
T
Date
3
C
Date
I would just want to select on IDs that have 'C' status in column 2 when the status immediately before is 'T'.
Is it possible to do this?
From Oracle 12, you can use MATCH_RECOGNIZE:
SELECT id, column1, column2
FROM table_name
MATCH_RECOGNIZE (
PARTITION BY id
ORDER BY something_that_orders_the_rows
ALL ROWS PER MATCH
PATTERN ( {- status_t -} status_c)
DEFINE
status_t AS column1 = 'T',
status_c AS column1 = 'C'
);
or, in earlier versions, you can use the LAG analytic function:
SELECT id, column1, column2
FROM (
SELECT id, column1, column2,
LAG(column1) OVER (PARTITION BY id ORDER BY something_that_orders_the_rows) AS prev_c1
FROM table_name t
)
WHERE column1 = 'C'
AND prev_c1 = 'T';
Which, for the sample data:
CREATE TABLE table_name (ID, column1, column2, something_that_orders_the_rows) AS
SELECT 1, 'S', 'Date', 1 FROM DUAL UNION ALL
SELECT 1, 'T', 'Date', 2 FROM DUAL UNION ALL
SELECT 1, 'J', 'Date', 3 FROM DUAL UNION ALL
SELECT 1, 'C', 'Date', 4 FROM DUAL UNION ALL
SELECT 2, 'D', 'Date', 1 FROM DUAL UNION ALL
SELECT 2, 'Q', 'Date', 2 FROM DUAL UNION ALL
SELECT 2, 'L', 'Date', 3 FROM DUAL UNION ALL
SELECT 2, 'J', 'Date', 4 FROM DUAL UNION ALL
SELECT 2, 'C', 'Date', 5 FROM DUAL UNION ALL
SELECT 3, 'L', 'Date', 1 FROM DUAL UNION ALL
SELECT 3, 'T', 'Date', 2 FROM DUAL UNION ALL
SELECT 3, 'T', 'Date', 3 FROM DUAL UNION ALL
SELECT 3, 'C', 'Date', 4 FROM DUAL;
Both output:
ID
COLUMN1
COLUMN2
3
C
Date
fiddle

Hiding whole objects when generating JSON form ORACLE tables

I would like to know is there any "elegant" way to hide whole generated object when one of columns is null?
For example if "tab_2.col_c" is null (because join doesn't return any values for "tab_2"), whole "tab_2" object is hidden, but when "tab_2.col_c" contains data then object "tab_2" is displayed.
tab_1:
|id|col_a|col_b|
|--|-----|-----|
|1 | a1 | b1 |
|2 | a2 | b2 |
tab_2:
|id|fk_id|col_c|col_d|
|--|-----|-----|-----|
|11| 2 | c1 | d1 |
|22| 2 | c2 | d2 |
Query:
select
json_object(
'tab_1_a' VALUE tab_1.col_a,
'tab_2_b' VALUE tab_1.col_b,
'tab_2' value json_object('c' value tab2.col_c,
'd' value tab2.col_d
)
)
from tab_1, tab2
where tab_1.id = tab_2.fk_id(+)
group by tab_1.col_a, tab_2.col_b
Output for "tab_1.id" = 1
{
"tab_1_a": "a1",
"tab_1_b": "b1",
"tab_2": {
"c": null,
"d": null
}
}
Desired output:
{
"tab_1_a": "a1",
"tab_1_b": "b1"
}
If you do not want the keys to be shown, you can use the ABSENT ON NULL clause of JSON_OBJECT_T. This will cause {} to still be returned though even if all of the keys do not contain a value.
WITH
tab_1 (id, col_a, col_b)
AS
(SELECT 1, 'a1', 'b1' FROM DUAL
UNION ALL
SELECT 2, 'a2', 'b2' FROM DUAL),
tab_2 (id,
fk_id,
col_c,
col_d)
AS
(SELECT 11, 2, 'c1', 'd1' FROM DUAL
UNION ALL
SELECT 22, 2, 'c2', 'd2' FROM DUAL)
SELECT tab_1.id,
json_object (
'tab_1_a' VALUE tab_1.col_a,
'tab_2_b' VALUE tab_1.col_b,
'tab_2' VALUE
json_object ('c' VALUE tab_2.col_c,
'd' VALUE tab_2.col_d
ABSENT ON NULL)) AS formatted_json
FROM tab_1 LEFT JOIN tab_2 ON tab_1.id = tab_2.fk_id
WHERE tab_1.id = 1;
ID FORMATTED_JSON
_____ _____________________________________________
1 {"tab_1_a":"a1","tab_2_b":"b1","tab_2":{}}
If you do not want {} returned, you can use a subquery. If no results are found then it will return null, but you can hide that as well using the ABSENT ON NULL clause again.
WITH
tab_1 (id, col_a, col_b)
AS
(SELECT 1, 'a1', 'b1' FROM DUAL
UNION ALL
SELECT 2, 'a2', 'b2' FROM DUAL),
tab_2 (id,
fk_id,
col_c,
col_d)
AS
(SELECT 11, 2, 'c1', 'd1' FROM DUAL
UNION ALL
SELECT 22, 2, 'c2', 'd2' FROM DUAL)
SELECT tab_1.id,
json_object (
'tab_1_a' VALUE tab_1.col_a,
'tab_2_b' VALUE tab_1.col_b,
'tab_2' VALUE
(SELECT json_object ('c' VALUE tab_2.col_c,
'd' VALUE tab_2.col_d
ABSENT ON NULL)
FROM tab_2
WHERE tab_2.fk_id = tab_1.id)
ABSENT ON NULL) AS formatted_json
FROM tab_1
WHERE tab_1.id = 1;
ID FORMATTED_JSON
_____ __________________________________
1 {"tab_1_a":"a1","tab_2_b":"b1"}

Rank and partition query in SQL

I have a table in AS400 as below
Type Values Status
A 1 Y
A 2 N
A 3 Y
A 4 Y
A 5 N
B 2 Y
B 7 N
C 3 Y
C 5 N
C 4 Y
C 6 Y
C 7 Y
C 1 Y
D 3 Y
D 5 Y
E 7 N
E 4 N
E 3 Y
E 6 N
E 7 Y
E 8 N
What I need is Top 2 of each type that have a status Y. I.e. the result should be something like A 1 , A 3, B 2 , C3, C4, D3, D5, E3, E7.
The query I have used is this
SELECT type,
REFERENCES
FROM (
SELECT type,
REFERENCES,
STATUS,
rank() OVER (PARTITION BY Type ORDER BY REFERENCES DESC) AS Rank
FROM Tables
) a
WHERE rank <= 2
AND Type IN (A,B,C,D,E)
AND STATUS = Y;
The issue here is it doesn't filter out the status beforehand. It picks up the top 2, then filters out with Y. So the result looks something like A1 instead of A1 and A3, because it has first picked A1 and A2 , and then filtered out A2 .
Where do I insert the Status=y to get a more accurate result.
I am a novice in SQL so if theres a better way to write the above query as well, I am fine with that.
This outta do it. Use the with clause to feed the filtered result into a new query which then you can rank.
with testtable (type, value, status) as (
select 'A', 1, 'Y' from dual union all
select 'A', 2, 'N' from dual union all
select 'A', 3, 'Y' from dual union all
select 'A', 4, 'Y' from dual union all
select 'A', 5, 'N' from dual union all
select 'B', 2, 'Y' from dual union all
select 'B', 7, 'N' from dual union all
select 'C', 3, 'Y' from dual union all
select 'C', 5, 'N' from dual union all
select 'C', 4, 'Y' from dual union all
select 'C', 6, 'Y' from dual union all
select 'C', 7, 'Y' from dual union all
select 'C', 1, 'Y' from dual union all
select 'D', 3, 'Y' from dual union all
select 'D', 5, 'Y' from dual union all
select 'E', 7, 'N' from dual union all
select 'E', 4, 'N' from dual union all
select 'E', 3, 'Y' from dual union all
select 'E', 6, 'N' from dual union all
select 'E', 7, 'Y' from dual union all
select 'E', 8, 'N' from dual
)
, ys as (
select
*
from testtable
where STATUS = 'Y'
)
, yrank as (
select
type,
value,
status,
rank() over(partition by type order by value) Y_RANK
from ys
)
select
*
from yrank
where Y_RANK <= 2

SQL How to align ranges of data points in rows?

Suppose having the data set:
with
data_table(title, x) as (
select 'a', 1 from dual union all
select 'a', 3 from dual union all
select 'a', 4 from dual union all
select 'a', 5 from dual union all
select 'b', 1 from dual union all
select 'b', 2 from dual union all
select 'b', 3 from dual union all
select 'b', 6 from dual
)
select * from data_table;
TITLE | X
-----------
a 1
a 3
a 4
a 5
b 1
b 2
b 3
b 6
Wee see that points related to a and b are different.
How to align values in X column so both groups have the same points, filling the gaps with NULL?
Expected result is:
TITLE | X
-----------
a 1
a NULL
a 3
a 4
a 5
a NULL
b 1
b 2
b 3
b NULL
b NULL
b 6
Straightforward solution I got is
with
data_table(title, x) as (
select 'a', 1 from dual union all
select 'a', 3 from dual union all
select 'a', 4 from dual union all
select 'a', 5 from dual union all
select 'b', 1 from dual union all
select 'b', 2 from dual union all
select 'b', 3 from dual union all
select 'b', 6 from dual
),
all_points(x) AS (
select distinct x from data_table
),
all_titles(title) AS (
select distinct title from data_table
),
aligned_data(title, x) as (
select t.title, p.x from all_points p cross join all_titles t
)
select ad.title, dt.x
from aligned_data ad
left join data_table dt on dt.title = ad.title and dt.x = ad.x
order by ad.title, ad.x;
As wee see cross join in aligned_data definition is bottleneck and can kill performance on valuable data sets.
I wonder if this task could be solved more elegantly. Maybe a trick with window functions can be proposed.

Oracle SQL query using case when, compacting null fields

I have a table like this:
Items
id group old_new object
1 A O pen
2 A N house
3 B O dog
4 B O cat
5 C N mars
6 C O sun
7 C N moon
8 C o earth
I would like the select return:
Items
group new_object old_object
A house pen
B null dog
B null cat
C mars sun
C moon earth
If I try:
select id,
case when old_new = 'N' then object end as new_object,
case when old_new = 'O' then object end as old_object
from the_table
order by id;
I have 8 row with many field as null
es: last rows:
group new_object old_object
C mars null
c null sun
C moon null
c null earth
But of group C I want only 2 rows...
is not like the other query 'Oracle sql join same table ecc...' because here don't want null result
I'm going to make the assumption that Old and New records are paired in the order they appear based on the ID value. With that assumption the following query:
WITH DTA(ID, GRP, OLD_NEW, OBJECT) AS (
select 1, 'A', 'O', 'pen' from dual union all
select 2, 'A', 'N', 'house' from dual union all
select 3, 'B', 'O', 'dog' from dual union all
select 4, 'B', 'O', 'cat' from dual union all
select 5, 'C', 'N', 'mars' from dual union all
select 6, 'C', 'O', 'sun' from dual union all
select 7, 'C', 'N', 'moon' from dual union all
select 8, 'C', 'O', 'earth' from dual
), dta2 as (
select dta.*
, row_number() over (partition by GRP, old_new order by id) rn
from dta
)
select coalesce(n.grp, o.grp) grp
, n.object new_object
, o.object old_object
from (select * from dta2 where old_new = 'N') n
full join (select * from dta2 where old_new = 'O') o
on n.grp = o.grp
and n.rn = o.rn;
Aside from the sample data section (with dta) this script first uses the analytic function ROW_NUMBER() to add a sequential number partitioned by the group and old_new columns. It then performs a full outer join on two inline views of the dta2 subfactored query, one for thr old objects and one for the new objects. The result, at least for this data set is:
GRP NEW_OBJECT OLD_OBJECT
--- ---------- ----------
A house pen
B dog
B cat
C mars sun
C moon earth
In the first step assign an index (IDX) of the chnage withing your group. I'm using order by ID, but this is upon you. The important thing is that the old and new valuea are unique connected with GRP and IDX.
In next step let PIVOT work for you (I'm using the data from #Sentinel, thx!)
WITH DTA(ID, GRP, OLD_NEW, OBJECT) AS (
select 1, 'A', 'O', 'pen' from dual union all
select 2, 'A', 'N', 'house' from dual union all
select 3, 'B', 'O', 'dog' from dual union all
select 4, 'B', 'O', 'cat' from dual union all
select 5, 'C', 'N', 'mars' from dual union all
select 6, 'C', 'O', 'sun' from dual union all
select 7, 'C', 'N', 'moon' from dual union all
select 8, 'C', 'O', 'earth' from dual
), DTA2 as (
SELECT
ROW_NUMBER() OVER (PARTITION BY GRP,OLD_NEW order by ID) as IDX,
GRP, OLD_NEW, OBJECT
from DTA
)
select * from DTA2
PIVOT (max(OBJECT) OBJECT for (OLD_NEW) in
('N' as "NEW",
'O' as "OLD"
))
order by GRP;
result
IDX, GRP, NEW_OBJECT, OLD_OBJECT
1 A house pen
1 B dog
2 B cat
2 C moon earth
1 C mars sun
Here's an alternative using PIVOT to get the results:
with items as (select 1 id, 'A' grp, 'O' old_new, 'pen' obj from dual union all
select 2 id, 'A' grp, 'N' old_new, 'house' obj from dual union all
select 3 id, 'B' grp, 'O' old_new, 'dog' obj from dual union all
select 4 id, 'B' grp, 'O' old_new, 'cat' obj from dual union all
select 5 id, 'C' grp, 'N' old_new, 'mars' obj from dual union all
select 6 id, 'C' grp, 'O' old_new, 'sun' obj from dual union all
select 7 id, 'C' grp, 'N' old_new, 'moon' obj from dual union all
select 8 id, 'C' grp, 'O' old_new, 'earth' obj from dual)
-- end of mimicking your items table with data in it. See SQL below:
select grp,
new_object,
old_object
from (select grp,
old_new,
obj,
row_number() over (partition by grp, old_new order by id) rn
from items)
pivot (max(obj)
for old_new in ('N' new_object,
'O' old_object))
order by grp,
rn;
GRP NEW_OBJECT OLD_OBJECT
--- ---------- ----------
A house pen
B dog
B cat
C mars sun
C moon earth
Provided that
there's at most one new object for each old,
there's no new object without old object, and
there's at most one old object for any group (this is not true for your sample data, but in comments you indicate you're interested in such solution as well)
a simpler query may be used than for the general case:
select
old.group as group, new.object as new_object, old.object as old_object
from
(select group, object from my_table where old_new = 'O') old
left join
(select group, object from my_table where old_new = 'N') new
on (old.group = new.group)