How to combine two tables in Oracle SQL on a quantitative base - sql

beacause of a really old db design I need some help. This might be quite simple I'm just not seeing the wood for the trees at the moment.
TABLE A:
ID
1
2
3
4
5
TABLE B:
ID
VALUE B
1
10
1
20
2
10
2
20
3
10
3
20
3
30
4
10
TABLE C:
ID
VALUE C
1
11
1
21
2
11
2
21
2
31
3
11
5
11
Expected result:
where ID = 1
ID
VALUE B
VALUE C
1
10
11
1
20
21
where ID = 2
ID
VALUE B
VALUE C
2
10
11
2
20
21
2
null
31
where ID = 3
ID
VALUE B
VALUE C
3
10
11
3
20
null
3
30
null
where ID = 4
ID
VALUE B
VALUE C
4
10
null
where ID = 5
ID
VALUE B
VALUE C
5
null
11
The entries in table B and C are optional and could be unlimited, the ID from table A is the connection.
B and C are not directly connected. I need a quantitative comparision to find gaps in the database. The number of entries of table B and C should be the same (but not the value), usually entries are missing in either B or C.
I tried it with outer joins but I'm getting two much rows, because I need B or C join only one time per single row.
I hope anybody understand my problem and can help me.

It looks like, for each distinct ID, you want the nth row (ordered by VALUE) from TABLE_A to match with the nth row from TABLE_B. And if one table - A or B - has more values, you want those to match to null.
Your solution will have two parts. First, use row_number() over ( partition by id order by value) to order the rows in both tables. Then, use FULL OUTER JOIN to join on (id, rownumber).
Here is a full example:
-- WITH clauses are just test data...+
with table_a (id) as (
SELECT 1 FROM DUAL UNION ALL
SELECT 2 FROM DUAL UNION ALL
SELECT 3 FROM DUAL UNION ALL
SELECT 4 FROM DUAL UNION ALL
SELECT 5 FROM DUAL ),
table_b (id, value) as (
SELECT 1,10 FROM DUAL UNION ALL
SELECT 1,20 FROM DUAL UNION ALL
SELECT 2,10 FROM DUAL UNION ALL
SELECT 2,20 FROM DUAL UNION ALL
SELECT 3,10 FROM DUAL UNION ALL
SELECT 3,20 FROM DUAL UNION ALL
SELECT 3,30 FROM DUAL UNION ALL
SELECT 4,10 FROM DUAL ),
table_c (id, value) as (
SELECT 1,11 FROM DUAL UNION ALL
SELECT 1,21 FROM DUAL UNION ALL
SELECT 2,11 FROM DUAL UNION ALL
SELECT 2,21 FROM DUAL UNION ALL
SELECT 2,31 FROM DUAL UNION ALL
SELECT 3,11 FROM DUAL UNION ALL
SELECT 5,11 FROM DUAL )
-- Solution begins here
SELECT id, b.value b_value, c.value c_value
FROM ( SELECT b.*,
row_number() OVER ( PARTITION BY b.id ORDER BY b.value ) rn
FROM table_b b ) b
FULL OUTER JOIN ( SELECT c.*,
row_number() OVER ( PARTITION BY c.id ORDER BY c.value ) rn
FROM table_c c ) c USING (id, rn)
ORDER BY id, b_value, c_value;
+----+---------+---------+
| ID | B_VALUE | C_VALUE |
+----+---------+---------+
| 1 | 10 | 11 |
| 1 | 20 | 21 |
| 2 | 10 | 11 |
| 2 | 20 | 21 |
| 2 | | 31 |
| 3 | 10 | 11 |
| 3 | 20 | |
| 3 | 30 | |
| 4 | 10 | |
| 5 | | 11 |
+----+---------+---------+

Related

Counting Rows under a Specific Header Row

I am trying to count the number of rows under specific "header rows" - for example, I have a table that looks like this:
Row # | Description | Repair_Code | Data Type
1 | FRONT LAMP | (null) | Header
2 | left head lamp | 1235 | Database
3 | right head lamp | 1236 | Database
4 | ROOF | (null) | Header
5 | headliner | 1567 | Database
6 | WHEELS | (null) | Header
7 | right wheel | 1145 | Database
Rows 1, 4 and 6 are header rows (categories) and the others are descriptors under each of those categories. The Data Type column denotes if the row is a header or not.
I want to be able to count the number of rows under the header rows to return something that looks like:
Header | Occurrences
FRONT LAMP | 2
ROOF | 1
WHEELS | 1
Thank you for the help!
Data model looks wrong. If that's some kind of a hierarchy, table should have yet another column which represents a "parent row#".
The way it is now, it's kind of questionable whether you can - or can not - do what you wanted. The only thing you can rely on is row#, which is sequential in your example. If that's not the case, then you have a problem.
So: if you use a lead analytic function for all header rows, then you could do something like this (sample data in rows #1 - 7; query that might help begins at line #8):
SQL> with test (rn, description, code) as
2 (select 1, 'front lamp' , null from dual union all
3 select 2, 'left head lamp' , 1235 from dual union all
4 select 3, 'right head lamp', 1236 from dual union all
5 select 4, 'roof' , null from dual union all
6 select 5, 'headliner' , 1567 from dual
7 ),
8 hdr as
9 -- header rows
10 (select rn,
11 description,
12 lead(rn) over (order by rn) next_rn
13 from test
14 where code is null
15 )
16 select h.description,
17 count(*)
18 from hdr h join test t on t.rn > h.rn
19 and (t.rn < h.next_rn or h.next_rn is null)
20 group by h.description;
DESCRIPTION COUNT(*)
--------------- ----------
front lamp 2
roof 1
SQL>
If data model was different (note parent_rn column), then you wouldn't depend on sequential row# values, but
SQL> with test (rn, description, code, parent_rn) as
2 (select 0, 'items' , null, null from dual union all
3 select 1, 'front lamp' , null, 0 from dual union all
4 select 2, 'left head lamp' , 1235, 1 from dual union all
5 select 3, 'right head lamp', 1236, 1 from dual union all
6 select 4, 'roof' , null, 0 from dual union all
7 select 5, 'headliner' , 1567, 4 from dual
8 ),
9 calc as
10 (select parent_rn,
11 sum(case when code is null then 0 else 1 end) cnt
12 from test
13 connect by prior rn = parent_rn
14 start with parent_rn is null
15 group by parent_rn
16 )
17 select t.description,
18 c.cnt
19 from test t join calc c on c.parent_rn = t.rn
20 where nvl(c.parent_rn, 0) <> 0;
DESCRIPTION CNT
--------------- ----------
front lamp 2
roof 1
SQL>
I would approach this using window functions. Assign a group to each header by doing a cumulative count of the NULL values of repair_code. Then aggregate:
select max(case when repair_code is null then description end) as description,
count(repair_code) as cnt
from (select t.*,
sum(case when repair_code is null then 1 else 0 end) over (order by row#) as grp
from t
) t
group by grp
order by min(row#);
Here is a db<>fiddle.

oracle insert 1 row multiple columns into multiple rows one column

I have a table t1 that contains the following : (millions of rows)
ID ID_1 ID_2 ID_3 ID_4
---------------------------------
1 10 11 12 13
2 14 15 16 17
3 18 19 20 21
I need to insert these data in another table t2 as following:
ID ID_X
------------
1 10
1 11
1 12
1 13
2 14
2 15
2 16
2 17
3 18
3 19
3 20
3 21
I already did what I need using LOOPs and cursors , but I need to do it with a single insert for better performance.
any ideas ?
EDIT:
I achieved what I needed using UNPIVOT thanks to replies.
Now I need to go further by adding an increment column to t2 table as follows:
ID ID_X ID_Y
----------------------
1 10 0
1 11 200
1 12 400
1 13 600
2 14 0
2 15 200
2 16 400
2 17 600
3 18 0
3 19 200
3 20 400
3 21 600
Use UNPIVOT.
Oracle Setup:
CREATE TABLE t1 ( ID, ID_1, ID_2, ID_3, ID_4 ) AS
SELECT 1, 10, 11, 12, 13 FROM DUAL UNION ALL
SELECT 2, 14, 15, 16, 17 FROM DUAL UNION ALL
SELECT 3, 18, 19, 20, 21 FROM DUAL;
CREATE TABLE t2 ( ID NUMBER, ID_X NUMBER );
Insert Statement:
INSERT INTO t2 (id, id_x )
SELECT id, value
FROM t1
UNPIVOT ( value FOR name IN ( ID_1, ID_2, ID_3, ID_4 ) );
Output:
SELECT * FROM t2;
ID | ID_X
-: | ---:
1 | 10
1 | 11
1 | 12
1 | 13
2 | 14
2 | 15
2 | 16
2 | 17
3 | 18
3 | 19
3 | 20
3 | 21
db<>fiddle here
Using UNION this can be achievable. The following query will work :
INSERT INTO Newtable (ID, ID_X)
SELECT ID, ID_X FROM (
SELECT ID, ID_1 AS ID_X FROM TableName UNION
SELECT ID, ID_2 AS ID_X FROM TableName UNION
SELECT ID, ID_3 AS ID_X FROM TableName UNION
SELECT ID, ID_4 AS ID_X FROM TableName
) A
ORDER BY ID, ID_X
select ID, ID_X from test
unpivot(ID_X for value in (ID_1 as 'A', ID_2 as 'B',ID_3 as 'C',ID_4 as 'D'));

SQL recursive id nodes

I have a table structure like so
Id Desc Node
---------------------
1 A
2 Aa 1
3 Ab 1
4 B
5 Bb 4
6 Bb1 5
these Desc values are presented in a listview to the user, if the user chooses Bb, I want the ID 5 and also the ID 4 becuase thats the root node of that entry, simular to that if the user chooses Bb1, I need ID 6, 5 and 4
I am only able to query one level up, but there could be n levels, so my query at the moment looks like this
SELECT Id
FROM tbl
WHERE Desc = 'Bb1'
OR Id = (SELECT Node FROM tbl WHERE Desc = 'Bb1');
You can do this with Recursive CTE like below
Schema:
CREATE TABLE #TAB (ID INT, DESCS VARCHAR(10), NODE INT)
INSERT INTO #TAB
SELECT 1 AS ID, 'A' DESCS, NULL NODE
UNION ALL
SELECT 2 , 'AA', 1
UNION ALL
SELECT 3, 'AB', 1
UNION ALL
SELECT 4, 'B', NULL
UNION ALL
SELECT 5, 'BB', 4
UNION ALL
SELECT 6, 'BB1', 5
Now do recursive CTE for picking node value and apply it again on #TAB with a Join.
;WITH CTE AS(
SELECT ID, DESCS, NODE FROM #TAB WHERE ID=6
UNION ALL
SELECT T.ID, T.DESCS, T.NODE FROM #TAB T
INNER JOIN CTE C ON T.ID = C.NODE
)
SELECT * FROM CTE
When you pass 6 to the first query in CTE, the result will be
+----+-------+------+
| ID | DESCS | NODE |
+----+-------+------+
| 6 | BB1 | 5 |
| 5 | BB | 4 |
| 4 | B | NULL |
+----+-------+------+

oracle query to obtain all major version segmented message values

I need to crete one sql Oracle query to obtain the major versions of each segmented message values.
I have the next tables with their relationships already filled with example registers:
*MESSAGE_TABLE*
ID NAME
1 hello
2 bye
*SEGMENT_TABLE*
ID VALUE
1 development
2 production
*MESSAGE_VALUE_TABLE*
ID ID_MESSAGE ID_SEGMENT VERSION VALUE
1 1 1 2 hello
2 1 1 1 hi
3 1 2 1 hi
4 1 null 3 hi
5 1 null 4 hello
6 2 1 1 bye
7 2 1 2 good bye
MESSAGE_VALUE_TABLE UNIQUE_CONSTRAINT is (ID_MESSAGE, ID_SEGMENT, VERSION)
ID_SEGMENT is nullable because null segment indicates default values.
VERSION is a simple number field.
The query has to obtain the major versions of each segmented message values (query results must include the segment value):
Selected result rows from MESSAGE_VALUE_TABLE are:
ID ID_MESSAGE ID_SEGMENT VERSION VALUE
1 1 1 2 hello
3 1 2 1 hi
5 1 null 4 hello
7 2 1 2 good bye
Query return values should be (same order as the previous selected rows list):
NAME(MESSAGE_TABLE) VALUE (SEGMENT_TABLE) VALUE (MESSAGE_VALUE_TABLE)
hello development hello
hello production hi
hello null / empty hello
bye development good bye
The solution is here, thanks to San that did the hard work:
WITH tab AS (SELECT ID,
id_message,
id_segment,
CASE WHEN lead(nvl(id_segment, -1)) over (partition by id_message ORDER BY id_segmento, id_version) IS NULL
THEN 1
WHEN (nvl(id_segment, -1) != lead(nvl(id_segment, -1)) over (partition by id_message ORDER BY id_segmento, id_version))
THEN 1
ELSE 0
END change_ind,
version,
VALUE
FROM MESSAGE_VALUE_TABLE)
SELECT b.NAME, nvl(c.VALUE, 'null/empty'), a.VALUE
FROM tab a
JOIN MESSAGE_TABLE b ON (b.ID=a.id_message)
LEFT OUTER JOIN SEGMENT_TABLE c ON (c.ID=a.id_segment)
WHERE change_ind = 1
As per my understanding, you want something like
you can get first part as
WITH MESSAGE_VALUE_TABLE(ID,ID_MESSAGE,ID_SEGMENT,VERSION,VALUE) as
(select 1,1,1,1,'hi' from dual union all
select 2,1,1,2,'hello' from dual union all
select 3,1,2,1,'hi' from dual union all
select 4,1,null,3,'hi' from dual union all
select 5,1,null,4,'hello' from dual union all
select 6,1,1,1,'hi' from dual union all
select 7,1,1,2,'hello' from dual union all
select 8,1,2,3,'hi' from dual union all
select 9,1,null,1,'hi' from dual union all
select 10,1,null,2,'hello' from dual union all
select 11,2,1,1,'bye' from dual union all
select 12,2,1,2,'good bye' from dual)
------
---End of data
------
SELECT id, id_message, id_segment, version, VALUE
from (
SELECT ID,
id_message,
id_segment,
CASE WHEN lead(nvl(id_segment, -1)) over (partition by id_message ORDER BY ID) IS NULL
THEN 1
WHEN (nvl(id_segment, -1) != lead(nvl(id_segment, -1)) over (partition by id_message ORDER BY ID))
THEN 1
ELSE 0
END change_ind,
version,
VALUE
FROM MESSAGE_VALUE_TABLE)
where change_ind = 1;
Output:
| ID | ID_MESSAGE | ID_SEGMENT | VERSION | VALUE |
|----|------------|------------|---------|----------|
| 2 | 1 | 1 | 2 | hello |
| 3 | 1 | 2 | 1 | hi |
| 5 | 1 | (null) | 4 | hello |
| 7 | 1 | 1 | 2 | hello |
| 8 | 1 | 2 | 3 | hi |
| 10 | 1 | (null) | 2 | hello |
| 12 | 2 | 1 | 2 | good bye |
And second Part as
WITH MESSAGE_VALUE_TABLE(ID,ID_MESSAGE,ID_SEGMENT,VERSION,VALUE) as
(select 1,1,1,1,'hi' from dual union all
select 2,1,1,2,'hello' from dual union all
select 3,1,2,1,'hi' from dual union all
select 4,1,null,3,'hi' from dual union all
select 5,1,null,4,'hello' from dual union all
select 6,1,1,1,'hi' from dual union all
select 7,1,1,2,'hello' from dual union all
select 8,1,2,3,'hi' from dual union all
select 9,1,null,1,'hi' from dual union all
select 10,1,null,2,'hello' from dual union all
select 11,2,1,1,'bye' from dual union all
select 12,2,1,2,'good bye' from dual),
MESSAGE_TABLE(ID,NAME) AS
(SELECT 1 , 'hello' FROM dual UNION ALL
SELECT 2, 'bye' FROM dual),
SEGMENT_TABLE(ID,VALUE) AS
(SELECT 1,'development' FROM dual UNION ALL
SELECT 2,'production' FROM dual),
------
---End of data
------
tab AS (SELECT ID,
id_message,
id_segment,
CASE WHEN lead(nvl(id_segment, -1)) over (partition by id_message ORDER BY ID) IS NULL
THEN 1
WHEN (nvl(id_segment, -1) != lead(nvl(id_segment, -1)) over (partition by id_message ORDER BY ID))
THEN 1
ELSE 0
END change_ind,
version,
VALUE
FROM MESSAGE_VALUE_TABLE)
SELECT b.NAME, nvl(c.VALUE, 'null/empty') C_VALUE, a.VALUE
FROM tab a
JOIN MESSAGE_TABLE b ON (b.ID=a.id_message)
LEFT OUTER JOIN SEGMENT_TABLE c ON (c.ID=a.id_segment)
WHERE change_ind = 1
ORDER BY a.ID
Output:
| NAME | C_VALUE | VALUE |
|-------|-------------|----------|
| hello | development | hello |
| hello | production | hi |
| hello | null/empty | hello |
| hello | development | hello |
| hello | production | hi |
| hello | null/empty | hello |
| bye | development | good bye |
So your final query will be :
WITH tab AS (SELECT ID,
id_message,
id_segment,
CASE WHEN lead(nvl(id_segment, -1)) over (partition by id_message ORDER BY ID) IS NULL
THEN 1
WHEN (nvl(id_segment, -1) != lead(nvl(id_segment, -1)) over (partition by id_message ORDER BY ID))
THEN 1
ELSE 0
END change_ind,
version,
VALUE
FROM MESSAGE_VALUE_TABLE)
SELECT b.NAME, nvl(c.VALUE, 'null/empty'), a.VALUE
FROM tab a
JOIN MESSAGE_TABLE b ON (b.ID=a.id_message)
LEFT OUTER JOIN SEGMENT_TABLE c ON (c.ID=a.id_segment)
WHERE change_ind = 1
ORDER BY a.ID
Not fully sure what you are trying to achieve. Try analytical query.
select message_name, segment_value, message_value from (
select m.name message_name, s.value segment_value, v.value message_value,
dense_rank() over (partition by v.id_message, v.id_segment order by version desc nulls last) version_rank
from message_value_table v
inner join message_table m on v.id_message = m.id
left outer join segment_table s on v.id_segment = s.id
) where version_rank = 1
;
http://sqlfiddle.com/#!4/35aa7/10

Oracle advanced union

Is there any advanced Oracle SQL methods to solve this kind of situation?
Simplified:
Two queries returns primary_key_value and other_value.
Both queries always return primary_key_value but other_value might be null.
So how I can union those two queries so that it returns always those rows which has other_value, but if both queries are having other_value = null with same primary key, then only one row should be returned.
I know this is so stupid case. But specifications were like this :)
Example:
First query:
A | B
=======
1 | X
2 |
3 |
4 | Z
Second query:
A | B
=======
1 | Y
2 |
3 | Z
4 |
So result need to be like this:
A | B
=======
1 | X
1 | Y
2 |
3 | Z
4 | Z
You could use analytics:
SQL> WITH q1 AS (
2 SELECT 1 a, 'X' b FROM DUAL UNION ALL
3 SELECT 2 a, '' b FROM DUAL UNION ALL
4 SELECT 3 a, '' b FROM DUAL UNION ALL
5 SELECT 4 a, 'Z' b FROM DUAL
6 ), q2 AS (
7 SELECT 1 a, 'Y' b FROM DUAL UNION ALL
8 SELECT 2 a, '' b FROM DUAL UNION ALL
9 SELECT 3 a, 'Z' b FROM DUAL UNION ALL
10 SELECT 4 a, '' b FROM DUAL
11 )
12 SELECT a, b
13 FROM (SELECT a, b,
14 rank() over(PARTITION BY a
15 ORDER BY decode(b, NULL, 2, 1)) rnk
16 FROM (SELECT * FROM q1
17 UNION
18 SELECT * FROM q2))
19 WHERE rnk = 1;
A B
---------- -
1 X
1 Y
2
3 Z
4 Z
If you want use something really advanced, use model clause http://rwijk.blogspot.com/2007/10/sql-model-clause-tutorial-part-one.html
But, in real life, using such things usually means bad-designed data model
Another way to look at is that you want all possible values from the union of column A then left outer outer join these with the non-null values from column B, thus only showing null in B when there is no non-null value to display.
roughly:
WITH q1 as (whatever),
q2 as (whatever)
SELECT All_A.A, BVals.B
FROM (SELECT DISTINCT A FROM (SELECT A FROM q1 UNION SELECT A FROM q2)) All_A,
(SELECT A,B FROM q1 WHERE B IS NOT NULL
UNION
SELECT A,B FROM q2 WHERE B IS NOT NULL) BVals
WHERE All_A.A = BVals.A (+)
Also pruning the unwanted nulls explicitly could do the same job:
WITH q3 AS (q1_SELECT UNION q2_SELECT)
SELECT A,B
FROM q3 main
WHERE NOT ( B IS NULL AND
EXISTS (SELECT 1 FROM q3 x WHERE main.A = x.A and x.B IS NOT NULL) )