Oracle - Sum function and flag column update - oracle

CREATE TABLE TABLE2_LINES (NAMES VARCHAR2(12), LINE_NO NUMBER, UNITS NUMBER(18,4), CODE VARCHAR2(4));
INSERT INTO TABLE2_LINES VALUES ('JOHN',1,1,'101');
INSERT INTO TABLE2_LINES VALUES ('JOHN',2,1,'202');
INSERT INTO TABLE2_LINES VALUES ('JOHN',3,1,'180');
INSERT INTO TABLE2_LINES VALUES ('JOHN',4,2,'300');
INSERT INTO TABLE2_LINES VALUES ('RYAN',1,2,'180');
INSERT INTO TABLE2_LINES VALUES ('RYAN',2,1,'180');
INSERT INTO TABLE2_LINES VALUES ('RYAN',3,1,'500');
INSERT INTO TABLE2_LINES VALUES ('OLAN',1,1,'301');
INSERT INTO TABLE2_LINES VALUES ('TONY',1,1,'201');
COMMIT;
CREATE TABLE TABLE4_CODES (CD_NM VARCHAR2(12), B_CODE VARCHAR2(4), E_CODE VARCHAR2(4));
INSERT INTO TABLE4_CODES VALUES ('CODELIST','100','101');
INSERT INTO TABLE4_CODES VALUES ('CODELIST','180','180');
INSERT INTO TABLE4_CODES VALUES ('CODELIST','200','219');
INSERT INTO TABLE4_CODES VALUES ('EXCODELIST','180','180');
COMMIT;
CREATE TABLE RESULT1 (NAMES VARCHAR2(12), TOT_UNITS NUMBER, FLAG VARCHAR2(2));
COMMIT;
Case 1: For person JOHN, We have eligible codes i.e. 101,202,180 (these codes present in TABLE4_CODES table of CD_NM CODELIST). It has other eligible codes along with 180 so SUM(UNITS) is 3 and FLAG should be populated as O
Case 2: For person RYAN, We have only 1 eligible code i.e. 180 only (this code present in TABLE4_CODES table of CD_NM EXCODELIST...Need to look into CD_NM = EXCODELIST instead of CODELIST), so SUM(UNITS) is 3 and FLAG should be populated as N
Case 3: For person OLAN, we don't have eligible codes so need to do anything.
Case 4: For person TONY, we have eligible code 201 but we don't have 180 so (this code present in TABLE4_CODES table of CD_NM = CODELIST), so SUM(UNITS) is 1 and FLAG should be populated as N
If particular person has multiple eligible codes along with 180, we need to look into TABLE4_CODES table of CD_NM CODELIST before proceeding for sum calculation. Ex: JOHN
If particular person has single/multiple eligible codes with don't have 180, we need to look into TABLE4_CODES table of CD_NM CODELIST before proceeding for sum calculation Ex: TONY
If particular person has only 1 eligible code i.e. 180, we need to look into TABLE4_CODES table of CD_NM EXCODELIST before proceeding for sum calculation. Ex: RYAN
Output to be expected in RESULT1 table using TABLE2_LINES and TABLE4_CODES:
NAMES TOT_UNITS FLAG
-----------------------------
JOHN 3 O
RYAN 3 N
TONY 1 O

This is how I understood it. Read comments within code.
SQL> with
2 t180 as
3 -- does NAME have code = 180? If so, return 1; else, return 0
4 (select t2.names,
5 max(case when t2.code = 180 then 1 else 0 end) as f180
6 from table2_lines t2
7 group by t2.names
8 ),
9 eligible as
10 -- number of eligible rows per NAME
11 (select t2.names,
12 count(distinct t2.code) cnt_eligible
13 from table2_lines t2 join table4_codes t4 on t2.code
14 between t4.b_code and t4.e_code
15 group by t2.names
16 ),
17 total_rows as
18 -- gotal number of rows per NAME
19 (select t2.names,
20 count(*) cnt_total
21 from table2_lines t2
22 group by t2.names
23 )
24 -- apply rules (within the CASE expression) to return desired result
25 select distinct
26 t2.names,
27 case when e.cnt_eligible = 1 and f.f180 = 1 then t.cnt_total -- Ryan, N
28 when e.cnt_eligible >= 1 and f.f180 = 0 then e.cnt_eligible -- Tony, O
29 when e.cnt_eligible >= 1 and f.f180 = 1 then e.cnt_eligible -- John, O
30 end tot_units,
31 --
32 case when e.cnt_eligible = 1 and f.f180 = 1 then 'N' -- Ryan, N
33 when e.cnt_eligible >= 1 and f.f180 = 0 then 'O' -- Tony, O
34 when e.cnt_eligible >= 1 and f.f180 = 1 then 'O' -- John, O
35 end flag
36 from table2_lines t2 join t180 f on f.names = t2.names
37 join eligible e on e.names = t2.names
38 join total_rows t on t.names = t2.names;
Result:
NAMES TOT_UNITS FLAG
------------ ---------- ----
JOHN 3 O
RYAN 3 N
TONY 1 O
SQL>

Related

Insert record from one table to another table - Oracle

I have a table TABLE1 which has 5 columns (ROLL_NO, NAME, UNITS, CODE, AMOUNT);
CREATE TABLE TABLE1 (ROLL_NO VARCHAR2(3), NAME VARCHAR2(4), UNITS NUMBER, AMOUNT NUMBER, CODE VARCHAR2(3));
------------------------------------------------------------------------------------------
INSERT INTO TABLE1 VALUES ('101', 'JOHN', 1, 6, 'ABC');
INSERT INTO TABLE1 VALUES ('101', 'JOHN', 2, 6, 'ABC');
INSERT INTO TABLE1 VALUES ('102', 'TOMS', 1, 7, 'ABC');
INSERT INTO TABLE1 VALUES ('102', 'TOMS', 6, 7, 'ABC');
INSERT INTO TABLE1 VALUES ('103', 'FINN', 1, 1, 'BCD');
ROLL_NO NAME UNITS AMOUNT CODE
-------------------------------------------------------
101 JOHN 1 6 ABC
101 JOHN 2 6 ABC
-------------------------------------------
102 TOMS 1 7 ABC
102 TOMS 6 7 ABC
103 FINN 1 1 BCD
There is second table TABLE2 where we need to insert data from TABLE1
CREATE TABLE TABLE2 (ROLL_NO VARCHAR2(3), NAME VARCHAR2(4), RESULT VARCHAR2(3));
There are three conditions to insert data into TABLE2
1st case : If CODE is 'ABC' and SUM(UNITS) of particular ROLL_NO is equal to AMOUNT then don't insert data into TABLE2
2nd case : If CODE is 'ABC' and SUM(UNITS) of particular ROLL_NO is not equal to AMOUNT then insert data with RESULT column value as 'YES'
3rd case : If CODE is not 'ABC' then RESULT column will be 'YES'.
Note: NAME, CODE and AMOUNT will be same for particular ROLL_NO though ROLL_NO has multiple UNITS.
Example :
ROLL_NO 102 CODE 'ABC' and two lines with SUM(UNITS) as 7 and its equal to AMOUNT i.e. 7 and (1st case)
ROLL_NO 101 has CODE 'ABC' and two lines with SUM(UNITS) as 3 and its not equal to AMOUNT i.e. 6 (2nd case)
ROLL_NO 103 has CODE 'BCD' which is not equal to 'ABC'(3rd case)
At the end TABLE2 should have
ROLL_NO NAME RESULT
-----------------------------
101 JOHN YES
103 FINN YES
I have tried this oracle query but it is inserting data related to 102 ROLL_NO which I don't need
SELECT T1.ROLL_NO, T1.NAME,
CASE
WHEN T1.CODE <> 'ABC' THEN 'YES'
WHEN T1.CODE = 'ABC' AND T2.TOT_UNITS <> T1.AMOUNT THEN 'YES'
END RESULT
FROM (SELECT DISTINCT ROLL_NO, NAME, AMOUNT, CODE
FROM TABLE1 ) T1
JOIN (SELECT ROLL_NO, SUM(UNITS) AS TOT_UNITS
FROM TABLE1
GROUP BY ROLL_NO) T2 ON T1.ROLL_NO = T2.ROLL_NO
I am not able to figure out how to not insert ROLL_NO 102 record into TABLE2..Can anyone provide better query than this if possible? Thank you
A "better" option is to scan table1 only once.
SQL> insert into table2 (roll_no, name, result)
2 with temp as
3 (select roll_no, name, sum(units) sum_units, amount, code,
4 case when code = 'ABC' and sum(units) = amount then 'NO'
5 when code = 'ABC' and sum(units) <> amount then 'YES'
6 else 'YES'
7 end as result
8 from table1
9 group by roll_no, name, amount, code
10 )
11 select roll_no, name, result
12 from temp
13 where result = 'YES';
2 rows created.
SQL> select * from table2;
ROL NAME RES
--- ---- ---
101 JOHN YES
103 FINN YES
SQL>

insert records into a table - column as rows based on conditions

I have a table - Base_table
create table base_table (ID number,FACTOR_1 number,FACTOR_1 number,FACTOR_3 number,FACTOR_4 number,TOTAL number, J_CODE varchar2(10))
insert into base_table values (1,10,52,5,32,140,'M1');
insert into base_table values (2,null,32,24,12,311,'M2');
insert into base_table values (3,12,null,53,null,110,'M3');
insert into base_table values (4,43,45,42,3,133,'M1');
insert into base_table values (5,432,24,null,68,581,'M2');
insert into base_table values (6,null,7,98,null,196,'M1');
ID
FACTOR_1
FACTOR_2
FACTOR_3
FACTOR_4
TOTAL
J_CODE
1
10
52
5
32
140
M1
2
null
32
24
12
311
M2
3
12
null
53
null
110
M3
4
43
45
42
3
133
M1
5
432
24
null
68
581
M2
6
null
7
98
null
196
M1
I need to insert this data into another table (FCT_T) based on certain criterias.
Also, I am trying to avaoid usage of unpivot as there are several other columns that I need to insert and manage as part of insert.
create table fct_t (id number, p_code varchar2(21), p_value number);
Logic to use -
Below values are not part of table, but needs to be used (hard-coded) in logic/criteria (perhaps CASE statements) -
M_VAL
FACT_1_CODE
FACT_2_CODE
FACT_3_CODE
FACT_4_CODE
M1
R1
R2
R3
R4
M2
R21
R65
R6
R245
M3
R1
R01
R212
R365
What I need is something similar (or any better approach available) -
insert into FCT_T values
select id,
case when FACTOR_1>0 and J_CODE = 'M1' then 'R1' end ,
factor_1
from base_table;
So far not able to figure out, how I can move factor column as rows, given an ID can have any number of rows from 1 to 4 based on criteria.
Appreciate help here.
Partial final/expected output (FCT_T) -
ID
P_CODE
P_VALUE
1
R1
10
1
R2
52
1
R3
5
1
R4
32
2
R65
32
2
R6
24
2
R245
12
You can join the table to your codes and then UNPIVOT to convert columns into rows:
INSERT INTO fct_t (id, p_code, p_value)
WITH codes (M_VAL, FACT_1_CODE, FACT_2_CODE, FACT_3_CODE, FACT_4_CODE) AS (
SELECT 'M1', 'R1', 'R2', 'R3', 'R4' FROM DUAL UNION ALL
SELECT 'M2', 'R21', 'R65', 'R6', 'R245' FROM DUAL UNION ALL
SELECT 'M3', 'R1', 'R01', 'R212', 'R365' FROM DUAL
)
SELECT id, p_code, p_value
FROM base_table b
INNER JOIN codes c
ON (b.j_code = c.m_val)
UNPIVOT (
(p_code, p_value)
FOR factor IN (
(fact_1_code, factor_1) AS 1,
(fact_2_code, factor_2) AS 2,
(fact_3_code, factor_3) AS 3,
(fact_4_code, factor_4) AS 4
)
)
WHERE p_value IS NOT NULL;
db<>fiddle here

Retrieving based on specific condition

I have a little complex requirement on couple of tables which I am finding hard to crack.
There are 2 tables. TableA and TableB
TableA has a structure like:
-------------------------------------
ID COL1 COL2 CAT
-------------------------------------
1 RecAA RecAB 3
2 RecBA RecBB 3
3 RecCA RecCB 2
4 RecDA RecDB 2
5 RecEA RecEB 1
-------------------------------------
TableB has a structure like:
-----------------
COL3 TYPE
-----------------
RecAA 10
RecAA 11
RecAA 12
RecAB 10
RecAB 11
RecAB 12
RecAB 13
RecAB 14
RecBA 10
RecBA 11
RecBA 14
RecBA 15
RecBB 10
-----------------
Requirements:
Records in TableA should have CAT = 3.
Either COL1 or COL2 of TableA should be available in COL3 of TableB.
COL3 should definitely have TYPE in 10,11,12 and should have only that TYPE.
i.e As per the above requirements,
Of the records available in TableA, records with ID 1 and 2 have CAT = 3 in TableA
Both the records have atleast only value in COL3 of TableB. (Record with ID 1 in TableA has both COL1 and COL2 in TableB and record with ID 2 in TableA has COL1 in TableB)
RecAA record has Type 10,11,12 and only 10,11,12. So doesnt matter if RecAB has 10,11,12 or not. But RecBA and RecBB both does not have 10,11,12 types.
Therefore the result should be:
-------------------------------------
ID COL1 COL2 CAT
-------------------------------------
1 RecAA RecAB 3
-------------------------------------
What I tried:
WITH TEMP AS (SELECT COL3 FROM TableB GROUP BY COL3 HAVING SUM(CASE WHEN TYPE IN ('10','11','12') THEN 1 ELSE 0 END) = 0)
SELECT S.ID, S.COL1, S.COL2, S.CAT FROM TableA S
INNER JOIN TEMP T ON S.COL1 = T.COL3
WHERE S.CAT = 3;
Can someone please help on achieving this?
I think you're almost there, it's just your row selection in the CTE that seems problematic, and I think you need an OR:
WITH TEMP AS (
SELECT COL3
FROM TableB
GROUP BY COL3
HAVING SUM(POWER(2, TYPE - 10)) = 7 AND COUNT(*) = 3
)
SELECT
S.ID, S.COL1, S.COL2, S.CAT
FROM
TableA S
INNER JOIN TEMP T ON S.COL1 = T.COL3 OR S.COL2 = T.COL3
WHERE
S.CAT = 3;
I've subtracted 10 from each of your TYPEs to turn your 10,11,12 into 0,1,2 and then used POWER to turn them into 1, 2 and 4 which uniquely sum to 7 - (in other words your 10,11,12 became 2^(10-10), 2^(11-10) and 2^(12-10) which are 1, 2 and 4.. Which must then sum to 7).
I also mandate that there be a count of 3; the only way to get to 7 with three numbers that are powers of 2 is to have 1+2+4 which guarantees that 10,11,12 are present initially. If anything was missing, extra or repeated it wouldn't be 3 numbers that sum to 7
I think RecAB is excluded because even though it has 10,11,12 it also has 13,14 which cause it to be excluded..
You also seemed to be saying that COL3 should be present in either COL1 or COL2 of table A
You can use listagg analytic version to turn TYPE column into type_in_list column like below :
With temp_TableB (COL3, type_in_list) as (
SELECT distinct COL3, listagg(TYPE, ',') within group (order by TYPE)over(partition by COL3)
FROM TableB
)
select tA.*
--, tb.*
from tableA tA
INNER JOIN temp_TableB tB on (tA.COL1 = tB.COL3 or tA.COL2 = tB.COL3)
Where tA.CAT = 3
AND tB.TYPE_IN_LIST = '10,11,12'
;

Select where number range does not overlap

I have two tables that contain records about road construction activites:
table_a is the master list.
table_b is a legacy list.
For each road, in each year, I want to select the records from table_b that do not already exist in table_a.
Also, the records should not overlap spatially along the road. More specifically, the from_m and to_m of the records in table_b should not overlap the from_m and to_m in table_a.
How can I do this? I do not have Oracle Spatial.
The data in Excel (for easy viewing):
Here is what the data looks like in Excel:
The records in green should be selected by the query; the records in red should not.
The DDL:
Table A:
create table table_a
(
id number(4,0),
road_id number(4,0),
year number(4,0),
from_m number(4,0),
to_m number(4,0)
);
insert into table_a (id,road_id,year,from_m,to_m) values (1,1,2000,0,100);
insert into table_a (id,road_id,year,from_m,to_m) values (2,1,2005,0,25);
insert into table_a (id,road_id,year,from_m,to_m) values (3,1,2005,50,75);
insert into table_a (id,road_id,year,from_m,to_m) values (4,1,2005,75,100);
insert into table_a (id,road_id,year,from_m,to_m) values (5,1,2010,10,50);
insert into table_a (id,road_id,year,from_m,to_m) values (6,1,2010,50,90);
insert into table_a (id,road_id,year,from_m,to_m) values (7,1,2015,40,100);
insert into table_a (id,road_id,year,from_m,to_m) values (8,2,2020,0,40);
insert into table_a (id,road_id,year,from_m,to_m) values (9,2,2020,0,40);
insert into table_a (id,road_id,year,from_m,to_m) values (10,3,2025,90,150);
commit;
select * from table_a;
ID ROAD_ID YEAR FROM_M TO_M
---------- ---------- ---------- ---------- ----------
1 1 2000 0 100
2 1 2005 0 25
3 1 2005 50 75
4 1 2005 75 100
5 1 2010 10 50
6 1 2010 50 90
7 1 2015 40 100
8 2 2020 0 40
9 2 2020 0 40
10 3 2025 90 150
Table B:
create table table_b
(
id number(4,0),
road_id number(4,0),
year number(4,0),
from_m number(4,0),
to_m number(4,0)
);
insert into table_b (id,road_id,year,from_m,to_m) values (1,1,1995,0,100);
insert into table_b (id,road_id,year,from_m,to_m) values (2,1,2001,0,50);
insert into table_b (id,road_id,year,from_m,to_m) values (3,1,2005,20,80);
insert into table_b (id,road_id,year,from_m,to_m) values (4,1,2005,0,100);
insert into table_b (id,road_id,year,from_m,to_m) values (5,1,2010,0,10);
insert into table_b (id,road_id,year,from_m,to_m) values (6,1,2010,90,100);
insert into table_b (id,road_id,year,from_m,to_m) values (7,1,2010,5,85);
insert into table_b (id,road_id,year,from_m,to_m) values (8,1,2015,40,100);
insert into table_b (id,road_id,year,from_m,to_m) values (9,1,2015,0,40);
insert into table_b (id,road_id,year,from_m,to_m) values (10,2,2020,0,41);
insert into table_b (id,road_id,year,from_m,to_m) values (11,3,2025,155,200);
insert into table_b (id,road_id,year,from_m,to_m) values (12,3,2025,199,300);
insert into table_b (id,road_id,year,from_m,to_m) values (13,4,2024,5,355);
commit;
select * from table_b;
ID ROAD_ID YEAR FROM_M TO_M
---------- ---------- ---------- ---------- ----------
1 1 1995 0 100
2 1 2001 0 50
3 1 2005 20 80
4 1 2005 0 100
5 1 2010 0 10
6 1 2010 90 100
7 1 2010 5 85
8 1 2015 40 100
9 1 2015 0 40
10 2 2020 0 41
11 3 2025 155 200
12 3 2025 199 300
13 4 2024 5 355
A NOT EXISTS sub-select can help here
SELECT *
FROM table_b b
WHERE
NOT EXISTS (SELECT *
FROM table_a a
WHERE
a.road_id = b.road_id AND
a.year = b.year AND
a.to_m > b.from_m AND
a.from_m < b.to_m)
Let's look at overlapping ranges (f=from, t=to)
a -------------------f=======================t-----------------
b1a -----f=============t-----------------------------------------
b1b --f=============t--------------------------------------------
b2a -------------------------------------------f======t----------
b2b -----------------------------------------------f======t------
b3 ---------------f=========t-----------------------------------
b4 ------------------------f===========t------------------------
b5 ---------------------------------------f===========t---------
The ranges b3, b4 and b5 overlap. for all of them the following is true
a.to > b.from && a.from < b.to
For b1a, b1b and b2a, b2b that don't overlap this condition is false. For b1a a.from == b.to, for b1b a.from > b.to therefore the condition a.from < b.to is false.
For b2a a.to == b.from, for b2b a.to < b.from therefore the condition a.to > b.from is false.
The trick is to compare the from of one range with the to of the other one and vice-versa.
See: http://sqlfiddle.com/#!4/85883/3/0
this will work :
SELECT * FROM table_b b
WHERE
EXISTS (SELECT *
FROM table_a a
WHERE
a.year=b.year and a.from_m<>b.from_m and a.to_m <>b.to_m );

Inactivate duplicate record and re-point child records to active one

There are two table as below
Table1
ID Name Age Active PID
-----------------------------
1 A 2 Y 100
2 A 2 Y 100
3 A 2 Y 100
4 B 3 Y 200
5 B 3 Y 200
Table2
T2ID CID
---------
10 1
20 1
30 1
40 2
50 2
60 3
70 3
80 3
90 4
100 5
110 5
I am trying to inactivate the duplicate record of table 1 and reassign the table2 record to activated rows of table 1,The result for table1 and table2 should be as below
ID Name Age Active PID
-----------------------------
1 A 2 Y 100
2 A 2 N 100
3 A 2 N 100
4 B 3 N 200
5 B 3 Y 200
T2ID CID
---------
10 1
20 1
30 1
40 1
50 1
60 1
70 1
80 1
90 5
100 5
110 5
please help for oracle query to update
You can do this by using two merge statements, like so:
Update table2:
MERGE INTO table2 tgt
USING (WITH t1 AS (SELECT ID,
NAME,
age,
active,
pid,
MIN(ID) OVER (PARTITION BY pid) min_id,
CASE WHEN COUNT(CASE WHEN active = 'Y' THEN 1 END) OVER (PARTITION BY pid) > 1 THEN 'Y' ELSE 'N' END multi_active_rows
FROM table1)
SELECT t2.t2id,
t2.cid old_cid,
t1.min_id new_cid
FROM t1
INNER JOIN table2 t2 ON t1.id = t2.cid
WHERE t1.multi_active_rows = 'Y') src
ON (tgt.t2id = src.t2id)
WHEN MATCHED THEN
UPDATE SET tgt.cid = src.new_cid;
Update table1:
MERGE INTO table1 tgt
USING (WITH t1 AS (SELECT ID,
NAME,
age,
active,
pid,
MIN(ID) OVER (PARTITION BY pid) min_id,
CASE WHEN COUNT(CASE WHEN active = 'Y' THEN 1 END) OVER (PARTITION BY pid) > 1 THEN 'Y' ELSE 'N' END multi_active_rows
FROM table1)
SELECT ID
FROM t1
WHERE multi_active_rows = 'Y'
AND ID != min_id) src
ON (tgt.id = src.id)
WHEN MATCHED THEN
UPDATE SET active = 'N';
Since we want to derive the results to update both table1 and table2 from the original dataset in table1, it's easier to update table2 first before updating table1.
This works by finding the lowest id across each set of pids in table1, plus checking to see if there is more than one active row for each pid (there's no need to do any updates if we have at most one active row available).
Once we have that information, we can use that to decide which rows to update in each table, and we can use the min_id to update table2 with, and we can update any rows in table1 where the id doesn't match the min_id to be not active.
N.B. If you could have a mix of Ys and Ns in your data, you may need to skip the and id != min_id check in the second merge statement and amend the update part to update the row to Y if the id is the min_id, otherwise set it to N.

Resources