Hiding whole objects when generating JSON form ORACLE tables - sql

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"}

Related

Remove duplicates with least row ids from Oracle

I have a database table which looks like
ID
Book_no
Book_name
Book_category
ID1
1
B1
CB1
ID1
2
B1
CB1
ID1
3
B2
CB1
ID1
4
B2
CB1
ID1
5
B3
CB1
ID2
1
B1
CB2
ID2
2
B1
CB2
ID2
3
B2
CB2
And the expected result is like
ID
Book_No
Book_name
Book_category
ID1
2
B1
CB1
ID1
4
B2
CB1
ID1
5
B3
CB1
ID2
2
B1
CB2
ID2
3
B2
CB2
I want to delete duplicate records from table on the basis of ID, Book_name and Book_category. Below query deletes the duplicate records, but the result is not expected one. As I want to delete all the duplicate records except the highest Book_no. Want to maintain the highest Book_no and delete all other duplicates.
You can DELETE correlating on the ROWID pseudo-column:
DELETE FROM table_name
WHERE ROWID IN (
SELECT rid
FROM (
SELECT ROWID AS rid,
ROW_NUMBER() OVER (
PARTITION BY id, book_name, book_category
ORDER BY book_no DESC
) AS rn
FROM table_name
)
WHERE rn > 1
);
Which, for the sample data:
CREATE TABLE table_name (id, book_no, book_name, book_category) AS
SELECT 'ID1', 1, 'B1', 'CB1' FROM DUAL UNION ALL
SELECT 'ID1', 2, 'B1', 'CB1' FROM DUAL UNION ALL
SELECT 'ID1', 3, 'B2', 'CB1' FROM DUAL UNION ALL
SELECT 'ID1', 4, 'B2', 'CB1' FROM DUAL UNION ALL
SELECT 'ID1', 5, 'B3', 'CB1' FROM DUAL UNION ALL
SELECT 'ID2', 1, 'B1', 'CB2' FROM DUAL UNION ALL
SELECT 'ID2', 2, 'B1', 'CB2' FROM DUAL UNION ALL
SELECT 'ID2', 3, 'B2', 'CB2' FROM DUAL;
Then the remaining rows are:
SELECT * FROM table_name;
ID
BOOK_NO
BOOK_NAME
BOOK_CATEGORY
ID1
2
B1
CB1
ID1
4
B2
CB1
ID1
5
B3
CB1
ID2
2
B1
CB2
ID2
3
B2
CB2
sqlfiddle here
You can use lead() and filter where the name changes:
select t.*
from (select t.*,
lead(book_name) over (partition by id, book_category order by book_no) as next_book_name
from t
) t
where next_book_name is null or next_book_name <> book_name;
Assuming your table looks like this:
create table books (id, book_no, book_name, book_category) as
select 'ID1', 1, 'B1', 'CB1' from dual union all
select 'ID1', 2, 'B1', 'CB1' from dual union all
select 'ID1', 3, 'B2', 'CB1' from dual union all
select 'ID1', 4, 'B2', 'CB1' from dual union all
select 'ID1', 5, 'B3', 'CB1' from dual union all
select 'ID2', 1, 'B1', 'CB2' from dual union all
select 'ID2', 2, 'B1', 'CB2' from dual union all
select 'ID2', 3, 'B2', 'CB2' from dual
;
You can use a delete statement in which you compare each row to the ones you want to keep. The ones you want to keep have max(book_no) when grouped by the other columns. So:
delete from books
where (id, book_no, book_name, book_category) not in
(
select id, max(book_no), book_name, book_category
from books
group by id, book_name, book_category
)
;
This assumes that the columns are non-null; if you may have null in the table, you will need to rewrite this a little more carefully, with a not exists condition instead of not in, but the idea is the same.
You can use first_value for finding the required id and can remove others using delete.
SELECT DISTINCT a.*, FIRST_VALUE(a.book_name)
OVER (ORDER BY a.book_name DESC
RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
AS "HIGHEST"
FROM ( select * from books group by id, Book_name, book_category ) a;
SELECT DISTINCT FIRST_VALUE(column_name)
OVER (ORDER BY column_name DESC
RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
AS "HIGHEST"
FROM (select * from tables group by "column_names_to_be_grouped");

How to add columnname to value in bigquery

I have a bigquery table that contains 3 "code" fields. some of these fields are used to look up against a code table. Assume the table looks like this:
data table:
id code1 code2 code3 data1
1 Y 3 A IA
2 Y 2 B IB
3 N 5 C IC
in order to perform the lookup, I have to concat the field_name to the value, delimited by a colon. I cannot hardcode the column name. using big query, is there a way to use the table object to infer the column name within the select statement?
for example:
select * from code_table join data_table where code1 = code.code_values
the value of code1 coming out is 'code1:Y' not 'Y'.
I'm wondering if there's a way to inject the column_name dynamically in the code1 value as it's going out to the code_table.
UPDATE 1:
Here's an example output from data_table to join against code_table:
1, code1:Y, code2:3, code3:A, IA
2, code1:Y, code2:2, code3:B, IB
3, code1:N, code2:5, code3:C, IC
Thanks
Does using the TO_JSON_STRING function give the desired output? Here is an example using your data:
WITH `project.dataset.table` AS (
SELECT 1 AS id, 'Y' AS code1, 3 AS code2, 'A' AS code3, 'IA' AS data1 UNION ALL
SELECT 2, 'Y', 2, 'B', 'IB' UNION ALL
SELECT 3, 'N', 5, 'C', 'IC'
)
SELECT TO_JSON_STRING(t) AS json
FROM `project.dataset.table` AS t;
+---------------------------------------------------------+
| json |
+---------------------------------------------------------+
| {"id":1,"code1":"Y","code2":3,"code3":"A","data1":"IA"} |
| {"id":2,"code1":"Y","code2":2,"code3":"B","data1":"IB"} |
| {"id":3,"code1":"N","code2":5,"code3":"C","data1":"IC"} |
+---------------------------------------------------------+
If you want to strip out the quotes, you can do that too:
WITH `project.dataset.table` AS (
SELECT 1 AS id, 'Y' AS code1, 3 AS code2, 'A' AS code3, 'IA' AS data1 UNION ALL
SELECT 2, 'Y', 2, 'B', 'IB' UNION ALL
SELECT 3, 'N', 5, 'C', 'IC'
)
SELECT REPLACE(TO_JSON_STRING(t), '"', '') AS json
FROM `project.dataset.table` AS t;
+-----------------------------------------+
| json |
+-----------------------------------------+
| {id:1,code1:Y,code2:3,code3:A,data1:IA} |
| {id:2,code1:Y,code2:2,code3:B,data1:IB} |
| {id:3,code1:N,code2:5,code3:C,data1:IC} |
+-----------------------------------------+
Edit: this gives the exact desired output. I'm assuming that you are okay with referencing id and data by name since it sounds like you don't want to format them in the same way.
WITH `project.dataset.table` AS (
SELECT 1 AS id, 'Y' AS code1, 3 AS code2, 'A' AS code3, 'IA' AS data1 UNION ALL
SELECT 2, 'Y', 2, 'B', 'IB' UNION ALL
SELECT 3, 'N', 5, 'C', 'IC'
)
SELECT
REGEXP_REPLACE(
FORMAT(
'%d %s %s',
id,
REGEXP_REPLACE(
TO_JSON_STRING(
(SELECT AS STRUCT t.* EXCEPT (id, data1))
),
'["{}]', ''),
data1
),
r'[ ,]', ', '
) AS output
FROM `project.dataset.table` AS t;
+----------------------------------+
| output |
+----------------------------------+
| 1, code1:Y, code2:3, code3:A, IA |
| 2, code1:Y, code2:2, code3:B, IB |
| 3, code1:N, code2:5, code3:C, IC |
+----------------------------------+
#standardSQL
WITH `project.dataset.table` AS (
SELECT 1 AS id, 'Y' AS code1, 3 AS code2, 'A' AS code3, 'IA' AS data1 UNION ALL
SELECT 2, 'Y', 2, 'B', 'IB' UNION ALL
SELECT 3, 'N', 5, 'C', 'IC'
)
SELECT
id,
MAX(IF(col = 1, val, NULL)) AS col1,
MAX(IF(col = 2, val, NULL)) AS col2,
MAX(IF(col = 3, val, NULL)) AS col3,
data1
FROM `project.dataset.table` AS t, UNNEST(SPLIT(REGEXP_REPLACE(TO_JSON_STRING(t), r'"|{|}', ''))) AS val WITH OFFSET col
WHERE col BETWEEN 1 AND 3
GROUP BY id, data1
ORDER BY id
output as below
id col1 col2 col3 data1
1 code1:Y code2:3 code3:A IA
2 code1:Y code2:2 code3:B IB
3 code1:N code2:5 code3:C IC
with above query you just need to know number of code columns so if it is 5 (for example) you need to add two more in SELECT and change BETWEEN 1 AND 3 to BETWEEN 1 AND 5

Fetch rows which has the same value in two columns for all the input values [duplicate]

This question already has answers here:
Oracle query to match all values in the list among all rows in table
(3 answers)
simple Oracle select statement syntax
(4 answers)
Closed 5 years ago.
I have a supplier table which has columns Item, suppliername and status. For the given items, I have to fetch the rows which has the same value in suppliername and status column only if the same values exists for all the given items.
For example, if the below is the table
Item Suppliername Status
A S1 Created
A S1 Approved
B S1 Approved
B S2 Created
C S1 Created
C S1 Approved
Input given are Items 'A', 'B', 'C'
The output should be as below.
Suppliername Status
S1 Approved
A few options:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TYPE CharList IS TABLE OF CHAR(1)
/
CREATE TABLE table_name ( Item, Suppliername, Status ) AS
SELECT 'A', 'S1', 'Created' FROM DUAL UNION ALL
SELECT 'A', 'S1', 'Approved' FROM DUAL UNION ALL
SELECT 'B', 'S1', 'Approved' FROM DUAL UNION ALL
SELECT 'B', 'S2', 'Created' FROM DUAL UNION ALL
SELECT 'C', 'S1', 'Created' FROM DUAL UNION ALL
SELECT 'C', 'S1', 'Approved' FROM DUAL
/
Query 1:
SELECT Suppliername, Status
FROM table_name
GROUP BY Suppliername, Status
HAVING CharList( 'A', 'B', 'C' )
SUBMULTISET OF CAST( COLLECT( Item ) AS CharList )
Results:
| SUPPLIERNAME | STATUS |
|--------------|----------|
| S1 | Approved |
Query 2:
SELECT Suppliername, Status
FROM table_name
WHERE Item IN ( 'A', 'B', 'C' )
GROUP BY Suppliername, Status
HAVING COUNT( DISTINCT item ) = 3
Results:
| SUPPLIERNAME | STATUS |
|--------------|----------|
| S1 | Approved |
Query 3:
SELECT Suppliername, Status
FROM table_name
WHERE Item MEMBER OF CharList( 'A', 'B', 'C' )
GROUP BY Suppliername, Status
HAVING COUNT( DISTINCT item ) = CARDINALITY( CharList( 'A', 'B', 'C' ) )
Results:
| SUPPLIERNAME | STATUS |
|--------------|----------|
| S1 | Approved |
This is how I would do it:
WITH picklist(Item) AS
(
VALUES ('A'),
('B'),
('C')
), grouping AS
(
SELECT T.Suppliername, T.Status, COUNT(*) AS C
FROM TABLE T
JOIN picklist ON T.Item = picklist.Item
GROUP BY T.Suppliername, T.Status
)
SELECT Suppliername, Status
FROM grouping
WHERE C = (SELECT COUNT(*) FROM picklist)
If Values does not work on your platform you can use for SQL Server
SELECT 'A' as Item
UNION ALL
SELECT 'B' as Item
UNION ALL
SELECT 'C' as Item
and For Oracle (according to MT0)
SELECT 'A' as Item FROM DUAL
UNION ALL
SELECT 'B' as Item FROM DUAL
UNION ALL
SELECT 'C' as Item FROM DUAL

How to return specific Parent/Child results with SQL Oracle database

this is my first question so apologies for any etiquette errors!
I have three relevant tables...
ID - this table provides the visible ID for all records
PARENT_CHILD_RELATIONSHIP - this links the parent records with their child records
DETAILS - provides details of all records
Sample data
Table ID PARENT_CHILD_RELATIONSHIP DETAILS
Columns KEY|PROPER_ID PARENT_KEY|CHILD_KEY KEY|FIELD
1|P1 1|NULL 5|A
2|P2 2|5 5|C
3|P3 2|6 6|A
4|P4 3|7 6|D
5|C1 4|8 7|B
6|C2 4|9 7|C
7|C3 8|A
8|C4 8|C
9|C5 9|B
9|C
Here is a simplified example of my query:
SELECT DISTINCT I.PROPER_ID, CHILD_ID_A.PROPER_ID, CHILD_ID_B.PROPER_ID
FROM ID I
LEFT OUTER JOIN PARENT_CHILD_RELATIONSHIP PCR_A ON PCR_A.PARENT_KEY = I.KEY
LEFT OUTER JOIN DETAILS D_A ON D_A.KEY = PCR_A.CHILD_KEY
AND D_A.FIELD = ('A')
LEFT OUTER JOIN ID CHILD_ID_A ON CHILD_ID_A.KEY = DETAILS_A.KEY
LEFT OUTER JOIN PARENT_CHILD_RELATIONSHIP PCR_B ON PCR_B.PARENT_KEY = I.KEY
LEFT OUTER JOIN DETAILS D_B ON D_B.KEY = PCR_B.CHILD_KEY
AND D_B.FIELD = ('B')
LEFT OUTER JOIN ID CHILD_ID_B ON CHILD_ID_B.KEY = DETAILS_B.KEY
WHERE I.PROPER_ID IN('1', '2', '3', '4')
What I want is to return all records where the (parent) PROPER_ID is either 1,2,3,4 in column 1. Then, in column 2, I want to return the proper ID of the child record(s) if that record has an 'A' in the Details table. For column 3, I want to do the same as column 2 but where there is a child record(s) with a 'B' in the DETAILS table.
There are 4 possible scenarios I can see:
1.There are 0 child records with 'A' or 'B' in the details table - in this case I want to return a single line with just the Parent Proper_ID and blanks in the subsequent 2 columns.
2.There is 1 or more child records with 'A' in the details table - in this case, I want to return as many lines as there are child records.
3. as above, but with only 'B' in the details table.
4.There are both 'A' and 'B' child records - in this case, if there is one of each, I want to retrun all data on the same line.
Current Output
I.PROPER_ID | CHILD_ID_A.PROPER_ID | CHILD_ID_B.PROPER_ID
P1 | NULL | NULL
P2 | C1 | NULL
P2 | C2 | NULL
P2 | NULL | NULL
P3 | NULL | C3
P3 | NULL | NULL
P4 | C4 | C5
P4 | C4 | NULL
P4 | NULL | C5
P4 | NULL | NULL
Output I want:
I.PROPER_ID | CHILD_ID_A.PROPER_ID | CHILD_ID_B.PROPER_ID
P1 | NULL | NULL
P2 | C1 | NULL
P2 | C2 | NULL
P3 | NULL | C3
P4 | C4 | C5
Is this even possible? I have tried many different variants and tried searching for other example but haven't come accross a solution.
Any help would be greatly appreciated!
I think you can do what you want with a pivot (11g or higher; a manual version is possible in earlier releases), something like:
select * from (
select p.proper_id as parent_id, c.proper_id as child_id, d.field,
row_number() over (partition by p.key, d.field order by c.key) as rn
from id p
join parent_child_relationship pcr on pcr.parent_key = p.key
left join id c on c.key = pcr.child_key
left join details d on d.key = c.key and d.field in ('A', 'B')
where p.proper_id in ('P1', 'P2', 'P3', 'P4')
)
pivot (max(child_id) as child for (field) in ('A' as a, 'B' as b))
The row_number() in the inner query lets it keep the two separate rows for P2. If a parent ever has more than A and B records, they'll be paired up, which may or may not be what you want, but it isn't clear if that can ever happen anyway.
With your sample data as CTEs:
with ID (KEY, PROPER_ID) as (
select 1, 'P1' from dual
union all select 2, 'P2' from dual
union all select 3, 'P3' from dual
union all select 4, 'P4' from dual
union all select 5, 'C1' from dual
union all select 6, 'C2' from dual
union all select 7, 'C3' from dual
union all select 8, 'C4' from dual
union all select 9, 'C5' from dual
), PARENT_CHILD_RELATIONSHIP (PARENT_KEY, CHILD_KEY) as (
select 1, null from dual
union all select 2, 5 from dual
union all select 2, 6 from dual
union all select 3, 7 from dual
union all select 4, 8 from dual
union all select 4, 9 from dual
), DETAILS (KEY, FIELD) as (
select 5, 'A' from dual
union all select 6, 'A' from dual
union all select 7, 'B' from dual
union all select 8, 'A' from dual
union all select 9, 'B' from dual
)
select parent_id, a_child as child_a_id, b_child as child_b_id
from (
select p.proper_id as parent_id, c.proper_id as child_id, d.field,
row_number() over (partition by p.key, d.field order by c.key) as rn
from id p
join parent_child_relationship pcr on pcr.parent_key = p.key
left join id c on c.key = pcr.child_key
left join details d on d.key = c.key and d.field in ('A', 'B')
where p.proper_id in ('P1', 'P2', 'P3', 'P4')
)
pivot (max(child_id) as child for (field) in ('A' as a, 'B' as b))
order by 1, 2, 3;
that produces:
PARENT_ID CHILD_A_ID CHILD_B_ID
--------- ---------- ----------
P1
P2 C1
P2 C2
P3 C3
P4 C4 C5
If you're on an earlier release that doesn't support pivot you can do the same thing more explicitly:
select parent_id,
max(case when field = 'A' then child_id end) as child_a_id,
max(case when field = 'B' then child_id end) as child_b_id
from (
select p.proper_id as parent_id, c.proper_id as child_id, d.field,
row_number() over (partition by p.key, d.field order by c.key) as rn
from id p
join parent_child_relationship pcr on pcr.parent_key = p.key
left join id c on c.key = pcr.child_key
left join details d on d.key = c.key and d.field in ('A', 'B')
where p.proper_id in ('P1', 'P2', 'P3', 'P4')
)
group by parent_id, rn
order by 1, 2, 3;

Update Oracle SQL - table with values from duplicates

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.