Oracle add sequential query rows if missing - sql

In Oracle 19c I have a data like:
with t as (
select 1 Cat, 1 id, 11 val from dual
union all
select 1, 3, 33 from dual
union all
select 2, 2, 22 from dual
union all
select 2, 4, 44 from dual)
select *
from t
In query result I want to get 4 rows per every cat with ids 1-4 and if there was no such id in that cat a val must be null:
cat
id
val
1
1
11
1
2
1
3
33
1
4
2
1
2
2
22
2
3
2
4
44

Use a PARTITIONed join with a row-generator:
SELECT t.cat,
i.id,
t.val
FROM (SELECT LEVEL AS id FROM DUAL CONNECT BY LEVEL <= 4) i
LEFT OUTER JOIN t
PARTITION BY (t.cat)
ON (i.id = t.id)
Which outputs:
CAT
ID
VAL
1
1
11
1
2
null
1
3
33
1
4
null
2
1
null
2
2
22
2
3
null
2
4
44
db<>fiddle here

Related

Oracle SQL - Create identifier for couple of values

I'm struggling with the following problem, I have the follwing data in a table:
Param ID
Param Val
Other Cols
1
15
XXX
1
15
XXX
1
16
XXX
1
16
XXX
2
21
XXX
2
21
XXX
2
22
XXX
2
22
XXX
I would like to select a new colum in order to create 4 sets of data to have all the possible combination between the values of parameter 1 and 2; so I would like to obtain something like this:
Set
Param ID
Param Val
Other Cols
1
1
15
XXX
2
1
15
XXX
3
1
16
XXX
4
1
16
XXX
1
2
21
XXX
3
2
21
XXX
2
2
22
XXX
4
2
22
XXX
So for example for the Set 1 I will have the Couple of values 15 and 21, for the set 2 the values 15 and 22 etc etc.
I tried using different analytic functions, but I was not able to have what I need.
Thanks in advance.
Despite a good hint on the MODEL clause, I guess I will go for a solution combining cross join and unpivot, maybe it is not the best, but it fit my needs.
WITH tbl AS
(
Select 1 "ID", 15 "VAL" From Dual Union All
Select 1 "ID", 16 "VAL" From Dual Union All
Select 2 "ID", 21 "VAL" From Dual Union All
Select 2 "ID", 22 "VAL" From Dual )
SELECT *
FROM (
SELECT ROWNUM AS SET_ID,
id1,
id4
FROM (
SELECT
CASE a.id
WHEN 1 THEN a.val
ELSE 0
END AS id1,
CASE a.id
WHEN 2 THEN a.val
ELSE 0
END AS id2,
CASE b.id
WHEN 1 THEN b.val
ELSE 0
END AS id3,
CASE b.id
WHEN 2 THEN b.val
ELSE 0
END AS id4
FROM tbl a,
tbl b)
WHERE id2 = 0
AND id3 = 0) UNPIVOT (VAL FOR ID IN (id1 AS '1',
id4 AS '2'))
that results in :
SET_ID
ID
VAL
1
1
15
1
2
21
2
1
15
2
2
22
3
1
16
3
2
21
4
1
16
4
2
22
tried with all the combination of source data, and it seems work :)
Sample data:
WITH
tbl AS
(
Select 1 "ID", 15 "VAL" From Dual Union All
Select 1 "ID", 15 "VAL" From Dual Union All
Select 1 "ID", 16 "VAL" From Dual Union All
Select 1 "ID", 16 "VAL" From Dual Union All
Select 2 "ID", 21 "VAL" From Dual Union All
Select 2 "ID", 21 "VAL" From Dual Union All
Select 2 "ID", 22 "VAL" From Dual Union All
Select 2 "ID", 22 "VAL" From Dual
)
UPDATED AFTER THE COMMENT
If you want the combinations then you will have to use the MODEL clause. To do that you should prepare the data (cte named grid) a bit so you could do the addressing to the particular data and manage all the combinations you want. It looks like here:
grid AS
( Select Distinct
Sum(1) OVER(Partition By ID Order By ID ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) "IDS_TOTAL_ORDER",
Sum(1) OVER(Partition By VAL Order By VAL ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) "VALS_TOTAL_ORDER",
ID "ID",
VAL "VAL"
From
tbl
Order By ID
)
Select SET_ID, ID, VAL
From ( Select 0 "SET_ID", IDS_TOTAL_ORDER, VALS_TOTAL_ORDER, ID "ID", VAL "VAL"
From grid
)
MODEL
Dimension By (ID, IDS_TOTAL_ORDER, VALS_TOTAL_ORDER)
Measures(SET_ID, VAL)
RULES
(
SET_ID[1, ANY, ANY] = CV(IDS_TOTAL_ORDER),
SET_ID[2, 1, 1] = CV(IDS_TOTAL_ORDER),
SET_ID[2, 2, 2] = CV(IDS_TOTAL_ORDER) + 1,
SET_ID[2, 3, 1] = CV(IDS_TOTAL_ORDER) - 1,
SET_ID[2, 4, 2] = CV(IDS_TOTAL_ORDER)
)
Order By ID, IDS_TOTAL_ORDER
This way you can get any combination. Here is your result:
SET_ID
ID
VAL
1
1
15
2
1
15
3
1
16
4
1
16
1
2
21
3
2
21
2
2
22
4
2
22
More about MODEL clause: https://www.oracle.com/webfolder/technetwork/tutorials/obe/db/10g/r2/prod/bidw/sqlmodel/sqlmodel_otn.htm
Sets now looks like here:
SET_ID
IDS_VALS
1
1/15, 2/21
2
1/15, 2/22
3
1/16, 2/21
4
1/16, 2/22
Regards...

ORACLE SQL, I don't know how to use SUM() here

Table TRANSACTION:
TRANS_VALUE
USER ID
TRANS_TYPE_ID
10
1
2
5
2
1
15
1
1
20
2
2
10
1
2
5
1
2
15
3
1
20
3
1
I need to get to this:
USER
SUM(TRANS_TYPE_1)
SUM(TRANS_TYPE_2)
1
15
25
2
5
20
3
35
NULL
Can someone help me?
I tried this but sadness
SELECT
user_id AS "USER
SUM(trans_value)
FROM
TRANSACTION
WHERE
trans_value = 1
GROUP BY
user_id
ORDER BY 1;
I need to get to this
USER
SUM(TRANS_TYPE_1)
SUM(TRANS_TYPE_2)
1
15
25
2
5
20
3
35
NULL
Use conditional aggregation:
SELECT user_id,
SUM(CASE trans_type_id WHEN 1 THEN trans_value END) AS sum_trans_type_1,
SUM(CASE trans_type_id WHEN 2 THEN trans_value END) AS sum_trans_type_2
FROM transaction
GROUP BY user_id
or PIVOT:
SELECT *
FROM transaction
PIVOT (
SUM(trans_value)
FOR trans_type_id IN (
1 AS sum_trans_type_1,
2 AS sum_trans_type_2
)
)
Which, for the sample data:
CREATE TABLE transaction (TRANS_VALUE, USER_ID, TRANS_TYPE_ID) AS
SELECT 10, 1, 2 FROM DUAL UNION ALL
SELECT 5, 2, 1 FROM DUAL UNION ALL
SELECT 15, 1, 1 FROM DUAL UNION ALL
SELECT 20, 2, 2 FROM DUAL UNION ALL
SELECT 10, 1, 2 FROM DUAL UNION ALL
SELECT 5, 1, 2 FROM DUAL UNION ALL
SELECT 15, 3, 1 FROM DUAL UNION ALL
SELECT 20, 3, 1 FROM DUAL;
Both output:
USER_ID
SUM_TRANS_TYPE_1
SUM_TRANS_TYPE_2
1
15
25
2
5
20
3
35
null
fiddle

Filtering SQL using a oracle database

I would like to know if the following is possible
For example I have a shoe factory. In this factory I have a production line, Every step in this production line is recorded into the oracle database.
if the shoe has completed a production step the result is = 1
example table
Shoe_nr production step result
1 1 1
1 2 1
1 3
2 1 1
2 2 1
2 3
3 1
3 2
3 3
Now the question, is it possible to filter out production step 3 where only the shoes have passed production step 2 which is equal to 1 in result.
I know if it can be done it's probably very easy but if you dont know i found out it's a little bit tricky.
Thanks,
Chris
Yes, you can do it with IN and a Subselect
select *
from shoes
where shoe.id in (
select shoe.id
from shoes
where production_step = 2
and result = 1
)
and production_step = 3
This might be one option; see comments within code (lines #1 - 12 represent sample data; you already have that and don't type it. Query you might be interested in begins at line #13).
SQL> with shoes (shoe_nr, production_step, result) as
2 -- sample data
3 (select 1, 1, 1 from dual union all
4 select 1, 2, 1 from dual union all
5 select 1, 3, null from dual union all
6 select 2, 1, 1 from dual union all
7 select 2, 2, 1 from dual union all
8 select 2, 3, null from dual union all
9 select 3, 1, null from dual union all
10 select 3, 2, null from dual union all
11 select 3, 3, null from dual
12 ),
13 -- which shoes' production step #3 should be skipped?
14 skip as
15 (select shoe_nr
16 from shoes
17 where production_step = 2
18 and result = 1
19 )
20 -- finally:
21 select a.shoe_nr, a.production_step, a.result
22 from shoes a
23 where (a.shoe_nr, a.production_step) not in (select b.shoe_nr, 3
24 from skip b
25 )
26 order by a.shoe_nr, a.production_step;
SHOE_NR PRODUCTION_STEP RESULT
---------- --------------- ----------
1 1 1
1 2 1
2 1 1
2 2 1
3 1
3 2
3 3
7 rows selected.
SQL>
If you just want the shoe_nr that satisfy the condition, you can use aggregation and a having clause:
select shoe_nr
from mytable
group by shoe_nr
having
max(case when production_step = 2 then result end) = 0
and max(case when production_step = 3 then 1 end) = 1
If you want the entire row corresponding to this shoe_nr at step 3, use window functions instead:
select 1
from (
select
t.*,
max(case when production_step = 2 then result end)
over(partition by shoe_nr) as has_completed_step_2
from mytable t
) t
where production_step = 3 and has_completed_step_2 = 0

Store bitmap to appropriate column - Oracle

I have a table:
table1
id col val rec_pos
1 Test 1 10 1
1 Test 2 20 2
1 Test 3 30 3
1 Test 2 20 4
1 Empty 0101 5
1 Invalid 1011 6
2 Test 4 30 2
2 Test 5 30 3
2 Test 6 30 4
2 Test 5 30 5
2 Empty 11101 6
2 Invalid 10101 7
3 Test 7 30 5
3 Test 8 30 6
3 Test 8 30 7
3 Test 8 30 8
3 Empty 11110101 9
3 Invalid 10010101 10
4 Test 9 30 3
4 Empty 1101 5
4 Invalid 1011 6
For each unique id, I need to grab val for col = 'Empty' and then using rec_pos, add a new column called empty and add the appropriate values.
Same for invalid, for each unique id, I need to grab val for col = 'Invalid' and then using rec_pos, add a new column called invalid and add the appropriate values.
Example:
For id = 1, col = Empty, val = 0101
rec_pos = 1, add value in empty column = 0
rec_pos = 2, add value in empty column = 1
rec_pos = 3, add value in empty column = 0
rec_pos = 4, add value in empty column = 1
For id = 4, col = Empty, val = 1101
rec_pos = 1, add value in empty column = 1
rec_pos = 2, add value in empty column = 1
rec_pos = 3, add value in empty column = 0
rec_pos = 4, add value in empty column = 1
Output would be:
id col val rec_pos empty invalid
1 Test 1 10 1 0 1
1 Test 2 20 2 1 0
1 Test 3 30 3 0 1
1 Test 2 20 4 1 1
1 Empty 0101 5 0 0
1 Invalid 1011 6 0 0
2 Test 4 30 2 1 0
2 Test 5 30 3 1 1
2 Test 6 30 4 0 0
2 Test 5 30 5 1 1
2 Empty 11101 6 0 0
2 Invalid 10101 7 0 0
3 Test 7 30 5 0 0
3 Test 8 30 6 1 1
3 Test 8 30 7 0 0
3 Test 8 30 8 1 1
3 Empty 11110101 9 0 0
3 Invalid 10010101 10 0 0
4 Test 9 30 3 0 1
4 Empty 1101 5 0 0
4 Invalid 1011 6 0 0
How can I read the values and then appropriately assign it to respective rec_pos for the same id?
If position is determined by rec_pos then you can use simply susbtr combined with analytical max:
select t.*,
nvl(substr(max(case col when 'Empty' then val end)
over (partition by id), rec_pos, 1), 0) empty,
nvl(substr(max(case col when 'Invalid' then val end)
over (partition by id), rec_pos, 1), 0) invalid
from table1 t
or with correlated subqueries:
select t.*,
nvl(substr((select val
from table1 e
where e.id = t.id and col= 'Empty'), rec_pos, 1), 0) empty,
nvl(substr((select val
from table1 i
where i.id = t.id and col= 'Invalid'), rec_pos, 1), 0) invalid
from table1 t
dbfiddle demo
If empty and invald are real columns in your table then use simple merge with any of the above queries to update them. But it's better to make view when a column results from calculations from others.
Test case (only for IDs 1 and 4; didn't feel like typing that much):
SQL> create table test
2 (id number,
3 col varchar2(10),
4 val varchar2(10),
5 rec_pos number,
6 empty number,
7 invalid number);
Table created.
SQL> insert into test (id, col, val, rec_pos)
2 (select 1, 'test 1', '10' , 1 from dual union all
3 select 1, 'test 2' , '20' , 2 from dual union all
4 select 1, 'test 3' , '30' , 3 from dual union all
5 select 1, 'test 2' , '20' , 4 from dual union all
6 select 1, 'empty' , '0101', 5 from dual union all
7 select 1, 'invalid', '1011', 6 from dual union all
8 --
9 select 4, 'test 9' , '30' , 3 from dual union all
10 select 4, 'empty' , '1101', 5 from dual union all
11 select 4, 'invalid', '1011', 6 from dual
12 );
9 rows created.
SQL> select * From test;
ID COL VAL REC_POS EMPTY INVALID
---------- ---------- ---------- ---------- ---------- ----------
1 test 1 10 1
1 test 2 20 2
1 test 3 30 3
1 test 2 20 4
1 empty 0101 5
1 invalid 1011 6
4 test 9 30 3
4 empty 1101 5
4 invalid 1011 6
9 rows selected.
SQL>
Update statement:
SQL> update test e set
2 e.empty = (select nvl(x.digit, 0)
3 from ( select t.id,
4 substr(t.val, column_value, 1) digit,
5 column_value rec_pos
6 from test t join
7 table(cast(multiset(select level from dual
8 connect by level <= (select max(t1.rec_pos)
9 from test t1
10 where t1.id = t.id
11 )
12 ) as sys.odcinumberlist ))
13 on 1 = 1
14 where t.col = 'empty'
15 ) x
16 where x.id = e.id
17 and x.rec_pos = e.rec_pos
18 ),
19 --
20 e.invalid = (select nvl(x.digit, 0)
21 from ( select t.id,
22 substr(t.val, column_value, 1) digit,
23 column_value rec_pos
24 from test t join
25 table(cast(multiset(select level from dual
26 connect by level <= (select max(t1.rec_pos)
27 from test t1
28 where t1.id = t.id
29 )
30 ) as sys.odcinumberlist ))
31 on 1 = 1
32 where t.col = 'invalid'
33 ) x
34 where x.id = e.id
35 and x.rec_pos = e.rec_pos
36 );
9 rows updated.
SQL>
What does it do?
takes VAL and splits it into rows
position (for SUBSTR) is determined by the maximum REC_POS for that ID, so that you could use NVL later on, while updating EMPTY (or INVALID) column
basically, both updates are equal, they differ only in what you're updating (lines #14 and #32)
Result:
SQL> select * from test;
ID COL VAL REC_POS EMPTY INVALID
---------- ---------- ---------- ---------- ---------- ----------
1 test 1 10 1 0 1
1 test 2 20 2 1 0
1 test 3 30 3 0 1
1 test 2 20 4 1 1
1 empty 0101 5 0 0
1 invalid 1011 6 0 0
4 test 9 30 3 0 1
4 empty 1101 5 0 0
4 invalid 1011 6 0 0
9 rows selected.
SQL>
I think that this gives you the output that you asked for.
SELECT t.id,
t.col,
t.val,
t.rec_pos,
NVL(e.empty, 0) AS empty,
NVL(i.invalid, 0) AS invalid
FROM
table1 t
LEFT
JOIN (SELECT DISTINCT t.id, levels.column_value AS rec_pos, SUBSTR(val, levels.column_value, 1) AS empty
FROM table1 t, TABLE(CAST(MULTISET(SELECT level FROM dual CONNECT BY level <= LENGTH(val)) AS sys.OdciNumberList)) levels
WHERE t.col = 'Empty' ORDER BY id) e ON e.id = t.id AND e.rec_pos = t.rec_pos
LEFT
JOIN (SELECT DISTINCT t.id, levels.column_value AS rec_pos, SUBSTR(val, levels.column_value, 1) AS invalid
FROM table1 t, TABLE(CAST(MULTISET(SELECT level FROM dual CONNECT BY level <= LENGTH(val)) AS sys.OdciNumberList)) levels
WHERE t.col = 'Invalid' ORDER BY id) i ON i.id = t.id AND i.rec_pos = t.rec_pos
ORDER
BY t.id, t.rec_pos;

SQL pad query result for missing groups

Assume the following table:
TableA:
ID GroupName SomeValue
1 C 1
2 C 1
2 B 1
2 A 1
I need to construct a query that selects the following result:
ID GroupName SomeValue
1 C 1
1 B 0
1 A 0
2 C 1
2 B 1
2 A 1
The GroupName is actually derived from TableA column's CASE expression and can take only 3 values: A, B, C.
Are the analytic functions the way to go?
EDIT
Sorry, for not mentioning it, but the ID could consist of multiple columns. Consider this example:
ID1 ID2 GroupName SomeValue
1 1 C 1
1 2 C 1
2 2 C 1
2 2 B 1
2 2 A 1
I need to pad SomeValue with 0 for each unique combination ID1+ID2. So the result should be like this:
ID1 ID2 GroupName SomeValue
1 1 C 1
1 1 B 0
1 1 A 0
1 2 C 1
1 2 B 0
1 2 A 0
2 2 C 1
2 2 B 1
2 2 A 1
EDIT2
Seems like solution, proposed by #Laurence should work even for multiple-column 'ID'. I couldn't rewrite the query proposed by #Nicholas Krasnov to conform to this requirement. But could somebody compare these solutions performance-wise? Will the analytic function work faster than 'cross join + left outer join'?
To fill in gaps, you could write a similar query using partition by clause of outer join:
SQL> with t1(ID,GroupName,SomeValue) as
2 (
3 select 1, 'C', 1 from dual union all
4 select 2, 'C', 1 from dual union all
5 select 2, 'B', 1 from dual union all
6 select 2, 'A', 1 from dual
7 ),
8 groups(group_name) as(
9 select 'A' from dual union all
10 select 'B' from dual union all
11 select 'C' from dual
12 )
13 select t1.ID
14 , g.group_name
15 , nvl(SomeValue, 0) SomeValue
16 from t1
17 partition by (t1.Id)
18 right outer join groups g
19 on (t1.GroupName = g.group_name)
20 order by t1.ID asc, g.group_name desc
21 ;
ID GROUP_NAME SOMEVALUE
---------- ---------- ----------
1 C 1
1 B 0
1 A 0
2 C 1
2 B 1
2 A 1
6 rows selected
UPDATE: Response to the comment.
Specify ID2 column in the partition by clause as well:
SQL> with t1(ID1, ID2, GroupName,SomeValue) as
2 (
3 select 1, 1, 'C', 1 from dual union all
4 select 1, 2, 'C', 1 from dual union all
5 select 2, 2, 'C', 1 from dual union all
6 select 2, 2, 'B', 1 from dual union all
7 select 2, 2, 'A', 1 from dual
8 ),
9 groups(group_name) as(
10 select 'A' from dual union all
11 select 'B' from dual union all
12 select 'C' from dual
13 )
14 select t1.ID1
15 , t1.ID2
16 , g.group_name
17 , nvl(SomeValue, 0) SomeValue
18 from t1
19 partition by (t1.Id1, t1.Id2)
20 right outer join groups g
21 on (t1.GroupName = g.group_name)
22 order by t1.ID1, t1.ID2 asc , g.group_name desc
23 ;
ID1 ID2 GROUP_NAME SOMEVALUE
---------- ---------- ---------- ----------
1 1 C 1
1 1 B 0
1 1 A 0
1 2 C 1
1 2 B 0
1 2 A 0
2 2 C 1
2 2 B 1
2 2 A 1
9 rows selected
Select
i.Id1,
i.Id2,
g.GroupName,
Coalesce(a.SomeValue, 0) As SomeValue
From
(select distinct ID1, ID2 from TableA) as i
cross join
(select distinct GroupName from TableA) as g
left outer join
tableA a
on i.ID = a.ID and g.GroupName = a.GroupName
Order By
1,
2,
3 Desc