oracle select mulitple records to 1 on conditions - sql

Table : CODES_TABLE
Serial - Code - DateTime
A123 B2 01/01/17:14:00
A124 B2 01/01/17:14:00
A123 B3 01/01/17:14:05
A123 B4 01/01/17:14:08
A124 B3 01/01/17:14:00
A128 B2 03/01/17:14:00
A129 B2 03/01/17:14:00
A129 B4 02/01/17:14:00
What Im trying to get is a list of all Serials which have generated a code B2, B3 and B4 And have generated it in a given order – i.e B2 first, then B3 then B4 So In this example – only Serial A123

Assuming, from your input data, that every code may only occure once for a serial, this could be a way:
/* test case */
with testTable(Serial,Code, DateTime) as (
select 'A123', 'B2', to_date('01/01/17:14:00', 'dd/mm/yy:hh24:mi') from dual union all
select 'A124', 'B2', to_date('01/01/17:14:00', 'dd/mm/yy:hh24:mi') from dual union all
select 'A123', 'B3', to_date('01/01/17:14:05', 'dd/mm/yy:hh24:mi') from dual union all
select 'A123', 'B4', to_date('01/01/17:14:08', 'dd/mm/yy:hh24:mi') from dual union all
select 'A124', 'B3', to_date('01/01/17:14:00', 'dd/mm/yy:hh24:mi') from dual union all
select 'A128', 'B2', to_date('03/01/17:14:00', 'dd/mm/yy:hh24:mi') from dual union all
select 'A129', 'B2', to_date('03/01/17:14:00', 'dd/mm/yy:hh24:mi') from dual union all
select 'A129', 'B4', to_date('02/01/17:14:00', 'dd/mm/yy:hh24:mi') from dual
)
/* the query */
select serial
from testTable
group by serial
having listagg( case when code in ('B2', 'B3', 'B4') then code end) within group ( order by dateTime) like '%B2B3B4%'
The idea here is to aggregate by serial, building for each serial a string that contains the codes, ordered by dateTime.
Assuming that every code can only appear once for a serial the only serials that match your condition will have strings containing 'B2B3B4'.
The CASE is used to handle the case you need to check if a serial has B2, B3, B5 where even B4 may occur.
This should better explain how this should work:
select serial, listagg( case when code in ('B2', 'B3', 'B4') then code end) within group ( order by dateTime) as string
from testTable
group by serial;
SERI STRING
---- ---------------
A123 B2B3B4
A124 B2B3
A128 B2
A129 B4B2

Related

How to get dates as column names in SQL BigQuery?

I would like to get dates (monthYear,weekYear,etc.) as columns, and having the value as count activities by users, but i'm not reaching it :(
Some example:
My table
userid
activityId
activityStatus
activityDate
A1
z1
finished
2022-08-01T15:00:00
A2
z2
finished
2022-08-07T20:00:00
A2
z3
finished
2022-08-08T10:00:00
A1
z4
finished
2022-09-17T16:00:00
A1
z5
finished
2022-09-20T17:00:00
A3
z6
finished
2022-08-19T13:00:00
What I'im trying to do, something like this (but I know now that's not working):
SELECT
userid,
COUNT(activityId) as doneActivities,
CONCAT(EXTRACT(YEAR from activityDate),'-',EXTRACT(WEEK from activityDate))
And the result of this attemp is like:
userid
doneActivities
weekYear
A1
1
31-2022
A1
2
33-2022
A2
1
31-2022
A2
1
32-2022
A3
1
33-2022
Expected result would be something like this:
userid
31-2022
32-2022
33-2022
A1
1
0
2
A2
1
1
0
A3
0
0
1
I know how to do it on Power BI, but I want to automate this for futures queries.
If it's not clear, please let me know guys, and I'll try to explain again. There is some time that I don't practice english.
Thanks in advance!
Since BigQuery doesn't support dynamic pivoting (using variables as column names) at this point (maybe due to security issues), some way around is needed.
However, the basic idea of approaching the problem would be first using (static) PIVOT. And then changing the query to string and do EXECUTE IMMEDIATE.
PIVOT
WITH
dataset AS (
SELECT 'A1' as userid, 'z1' as activityId, 'finished' as activityStatus, DATETIME('2022-08-01T15:00:00') as activityDate,
UNION ALL SELECT 'A2', 'z2', 'finished', '2022-08-07T20:00:00'
UNION ALL SELECT 'A2', 'z3', 'finished', '2022-08-08T10:00:00'
UNION ALL SELECT 'A1', 'z4', 'finished', '2022-09-17T16:00:00'
UNION ALL SELECT 'A1', 'z5', 'finished', '2022-09-20T17:00:00'
UNION ALL SELECT 'A3', 'z6', 'finished', '2022-08-19T13:00:00'
),
preprocess_dataset AS (
WITH
_dataset AS (
SELECT *, CONCAT('_', EXTRACT(YEAR from activityDate), '_', EXTRACT(WEEK from activityDate)) as year_week,
FROM dataset
)
SELECT userid, year_week, COUNT(DISTINCT activityId) as done_activities,
FROM _dataset
GROUP BY userid, year_week
)
SELECT *,
FROM preprocess_dataset
PIVOT (
SUM(done_activities) FOR year_week IN (
'_2022_31', '_2022_32', '_2022_33', '_2022_37', '_2022_38'
)
)
ORDER BY userid
;
Results
Just to add on to the great answer of #JihoChoi, if you would like to implement dynamic pivoting. See approach below:
create temp table my_table (userid string,doneActivities int64,weekYear string);
insert into my_table
select
userid,
count(distinct activityId) as doneActivities,
weekYear
from
(
select
*,
concat('_',extract(YEAR from activityDate),'_',extract(WEEK from activityDate)) as weekYear
from `project-id.dataset_id.table_id`
)
group by userid,weekYear;
execute immediate (
select '''
select * from my_table
pivot(sum(doneActivities) for weekYear in ("''' || string_agg(weekYear,'", "') || '''"))
'''
from (select * from my_table order by weekYear)
)
Output:

Columns data into rows in oracle

Data is present in the screenshot as table format. I want to convert into desired format as mentioned below.
Table describe :
Table A
(branch_code, branch_name, branch_state, hol1, hol2, hol3....hol100)
Expected Output
TAMF046 14/01/2021
TAMF046 15/01/2021
TAMF046 26/01/2021
KERF047 26/01/2021
KERF047 11/03/2021
KERF047 02/04/2021
This is exactly what UNPIVOT is for
with t(id, c1, c2, c3) as (
select 1, 'a', 'b', 'c' from dual union all
select 2, 'aa', 'bb', 'cc' from dual
)
select *
from t
unpivot (
val
for col in (
c1 as 'A1',
c2 as 'A2',
c3 as 'A3'
)
)
val is the new column that will contain values from columns c1 c2 c3.
col is the new column that will contain the name of the column from where the val comes from.
A1 A2 A3 are the values you want to fill in the col for each unpivoted column (these aliases can be omitted if you are ok with the original column names).
Use union
Eg:
select Branch_Code, Hol1 from MyTable
union
select Branch_Code, Hol2 from MyTable
etc
WITH HOLIDAY
AS (
SELECT BRANCH_CODE,HOL1,HOL2,HOL3,HOL4,HOL5,HOL6,HOL7,HOL8,HOL9,HOL10,HOL11,HOL12,HOL13,HOL14,HOL15,HOL16,HOL17,HOL18,HOL19,HOL20,HOL21,HOL22,HOL23,HOL24,HOL25,HOL26,HOL27,HOL28,HOL29,HOL30,HOL31,HOL32,HOL33,HOL34,HOL35,HOL36,HOL37,HOL38,HOL39,HOL40,HOL41,HOL42,HOL43,HOL44,HOL45,HOL46,HOL47,HOL48,HOL49,HOL50,HOL51,HOL52,HOL53,HOL54,HOL55,HOL56,HOL57,HOL58,HOL59,HOL60,HOL61,HOL62,HOL63,HOL64,HOL65,HOL66,HOL67,HOL68,HOL69,HOL70,HOL71,HOL72,HOL73,HOL74,HOL75,HOL76,HOL77,HOL78,HOL79,HOL80,HOL81,HOL82,HOL83,HOL84,HOL85,HOL86,HOL87,HOL88,HOL89,HOL90,HOL91,HOL92,HOL93,HOL94,HOL95,HOL96,HOL97,HOL98,HOL99,HOL100
FROM CUST_HOLIDAY_MASTER
WHERE BRANCH_CODE = I.BRANCH_CODE
)
SELECT BRANCH_CODE , COLVALUE FROM abc
unpivot
( colvalue for col in (HOL1,HOL2,HOL3,HOL4,HOL5,HOL6,HOL7,HOL8,HOL9,HOL10,HOL11,HOL12,HOL13,HOL14,HOL15,HOL16,HOL17,HOL18,HOL19,HOL20,HOL21,HOL22,HOL23,HOL24,HOL25,HOL26,HOL27,HOL28,HOL29,HOL30,HOL31,HOL32,HOL33,HOL34,HOL35,HOL36,HOL37,HOL38,HOL39,HOL40,HOL41,HOL42,HOL43,HOL44,HOL45,HOL46,HOL47,HOL48,HOL49,HOL50,HOL51,HOL52,HOL53,HOL54,HOL55,HOL56,HOL57,HOL58,HOL59,HOL60,HOL61,HOL62,HOL63,HOL64,HOL65,HOL66,HOL67,HOL68,HOL69,HOL70,HOL71,HOL72,HOL73,HOL74,HOL75,HOL76,HOL77,HOL78,HOL79,HOL80,HOL81,HOL82,HOL83,HOL84,HOL85,HOL86,HOL87,HOL88,HOL89,HOL90,HOL91,HOL92,HOL93,HOL94,HOL95,HOL96,HOL97,HOL98,HOL99,HOL100)
)
WHERE COLVALUE IS NOT NULL;
This is the way which i try to this but if anyone has any other way using dynamic query. Please share your views,

Set flag column based on a regular expression

I have developed the following query but it does not work as expected:
WITH TABLE1 AS
(
SELECT 613414473 as ID, 1706014200964 as P_NUM, 119539 as d_id, 'F20.0' AS CODE FROM DUAL UNION ALL
SELECT 613414473 as ID, 1706014200964 as P_NUM, 119539 as d_id, 'F22.0' AS CODE FROM DUAL UNION ALL
SELECT 613415801 as ID, 1707045167741 as P_NUM, 115182 as d_id, 'A94.0' AS CODE FROM DUAL UNION ALL
SELECT 613415801 as ID, 1707045167741 as P_NUM, 115182 as d_id, NULL AS CODE FROM DUAL UNION ALL
SELECT 613417084 as ID, 1702038456441 as P_NUM, 6541 as d_id, 'E79' AS CODE FROM DUAL UNION ALL
SELECT 613417084 as ID, 1702038456421 as P_NUM, 6541 as d_id, 'I10' AS CODE FROM DUAL UNION ALL
SELECT 613418372 as ID, 1706226211517 as P_NUM, 25727 as d_id, 'F32.9' AS CODE FROM DUAL )
SELECT T1.*
, CASE when regexp_like( CODE, 'C0[5-9]|' ||
'A0[0-9]|A1[0-9]|A2[0-9]|A3[0-9]|A4[0-9]|A5[0-9]|A6[0-9]|A7[0-9]|A8[0-9]|A9[0-7]|' )
THEN 1
ELSE 0 END AS FOUND_CODE
FROM TABLE1 T1;
I want codes which are like C0[5-9]% or A0[0-97] to be flagged with value 1, and then for the same p_num, if at least one code was found to set all the flags for that p_num to 1.
Example output of the above:
| 613414473|1706014200964|119539|F20.0|0|
| 613414473|1706014200964|119539|F22.0|0|
| 613415801|1707045167741|115182|A94.0|1|
| 613415801|1707045167741|115182|NULL |1|
| 613417084|1702038456441|6541 |E79 |0|
| 613417084|1702038456421|6541 |I10 |0|
| 613418372|1706226211517|25727 |F32.9|0|
How can I modify my query to get that output? And is there a better regular expression I can use?
Based on your description, the regular expression pattern should be
'^(C0[5-9]|A[0-8][0-9]|A9[0-7])'
The ^ anchors to the start of the value, and the parentheses allow any of the pipe-separated patterns to match; and the patterns are simplified, as A00 to A89 can be handles in one go.
That flags the same single row as your original query. The next stage is to move that into a subquery, and then use an analytic function partitioned by the p_num which you want to be common:
max(found_code) over (partition by p_num)
So together that becomes (with additional rows to match a different rule):
with table1 (id, p_num, d_id, code) as
(
select 613414470, 1706014200960, 119530, 'D99' from dual union all
select 613414471, 1706014200960, 119531, 'C05' from dual union all
--
select 613414473, 1706014200964, 119539, 'F20.0' from dual union all
select 613414473, 1706014200964, 119539, 'F22.0' from dual union all
select 613415801, 1707045167741, 115182, 'A94.0' from dual union all
select 613415801, 1707045167741, 115182, null from dual union all
select 613417084, 1702038456441, 6541 , 'E79' from dual union all
select 613417084, 1702038456421, 6541 , 'I10' from dual union all
select 613418372, 1706226211517, 25727 , 'F32.9' from dual
)
select id, p_num, d_id, code, max(found_code) over (partition by p_num) as found_code
from (
select t1.*
, case when regexp_like( code, '^(C0[5-9]|A[0-8][0-9]|A9[0-7])' )
then 1
else 0
end as found_code
from table1 t1
);
ID P_NUM D_ID CODE FOUND_CODE
------------- ------------- ------------- ----- -------------
613414470 1706014200960 119530 D99 1
613414471 1706014200960 119531 C05 1
613414473 1706014200964 119539 F20.0 0
613414473 1706014200964 119539 F22.0 0
613415801 1707045167741 115182 A94.0 1
613415801 1707045167741 115182 1
613417084 1702038456441 6541 E79 0
613417084 1702038456421 6541 I10 0
613418372 1706226211517 25727 F32.9 0

Oracle SQL How can I write this Join with less code?

Hello Oracle experts I have a question on how to join two tables correctly.
My first table describes an leave category, the minimum years of service time required for new max vacation leave rollover amount and the max rollover numbers.
PTRLVAC_LCAT_CODE PTRLVAC_YEAR PTRLVAC_ROLL_MAX_HRS
C1 0 80
C1 2 88
C1 5 128
P3 0 120
P3 2 128
P3 5 168
The next table details the employee id, hire date, and leave category
PEBEMPL_PIDM PEBEMPL_HIRE_DATE PEBEMPL_LCAT_CODE
1234 01/09/2017 P3
3214 02/01/2014 C1
The join that I have right now relies on a CTE and I'm not sure if it's the easiest solution.
**I've included the tables here as CTEs
with ptrlvac as(
select 'C1' ptrlvac_lcat_code
,0 ptrlvac_year
,80 ptrlvac_roll_max_hrs
from dual union all
select 'C1', 2, 88 from dual union all
select 'C1', 5, 128 from dual union all
select 'P3', 0, 120 from dual union all
select 'P3', 5, 128 from dual union all
select 'P3', 2, 168 from dual
) , pebempl as(
select 1234 pebempl_pidm
,to_date('09-JAN-2017', 'DD-MON-YYYY') pebempl_hire_date
,'P3' pebempl_lcat_code
from dual
UNION ALL
select 3214, to_date('01-FEB-2014','DD-MON-YYYY'), 'C1' from dual
) ,leave as(
select a.ptrlvac_lcat_code
,a.ptrlvac_year
,a.ptrlvac_roll_max_hrs
,row_number()
over(partition by a.ptrlvac_lcat_code
order by a.ptrlvac_year) rn
from ptrlvac a
)
,leave_rules as(
select a.ptrlvac_lcat_code
,a.ptrlvac_year year_start
,nvl(b.ptrlvac_year, 100)-1 year_end
,a.ptrlvac_roll_max_hrs
from leave a
left join leave b
on a.ptrlvac_lcat_code = b.ptrlvac_lcat_code
and a.rn = b.rn - 1
)
select distinct pebempl_pidm
,pebempl_hire_date
,floor(months_between(to_date(:seldate, 'DD-MON-YYYY'), pebempl_hire_date) / 12) as service_years
,pebempl_lcat_code as lcat
,b.ptrlvac_roll_max_hrs
from pebempl a
inner join leave_rules b
on a.pebempl_lcat_code = b.ptrlvac_lcat_code
and floor(months_between(to_date(:seldate, 'DD-MON-YYYY'), pebempl_hire_date) / 12) between b.year_start and b.year_end
Any help to save some keystrokes would be greatly appreciated.
Thanks in advance.
I'm not sure if this does what you want:
select
t2.PEBEMPL_PIDM,
t1.PTRLVAC_ROLL_MAX_HRS
from test1 t1, test2 t2
where
t1.PTRLVAC_LCAT_CODE = t2.PEBEMPL_LCAT_CODE and
t1.PTRLVAC_YEAR =
(select max(t1s.PTRLVAC_YEAR) from test1 t1s
where t1s.PTRLVAC_LCAT_CODE = t2.PEBEMPL_LCAT_CODE
and (sysdate-PEBEMPL_HIRE_DATE)/365 >= t1s.PTRLVAC_YEAR);
Here are the results I got based on your test data:
PEBEMPL_PIDM PTRLVAC_ROLL_MAX_HRS
------------ --------------------
3214 88
1234 120
Bobby
Had this thought over lunch, further reducing #BobbyDurret's answer:
select
t2.PEBEMPL_PIDM,
max(t1.PTRLVAC_ROLL_MAX_HRS)
from ptrlvac t1, pebempl t2
where
t1.PTRLVAC_LCAT_CODE = t2.PEBEMPL_LCAT_CODE and
(sysdate-PEBEMPL_HIRE_DATE)/365 >= t1.PTRLVAC_YEAR
group by t2.PEBEMPL_PIDM
Assumes the Max_Hrs always increases for more years of service.
Instead of using row_number and filtering in a separate CTE, use lead:
with leave_rules as
(
select a.ptrlvac_lcat_code
,a.ptrlvac_year as year_start
,a.ptrlvac_roll_max_hrs
,lead(ptrlvac_year,1,10000) over (partition by ptrlvac_lcat_code
order by ptrlvac_year)
as year_end
from ptrlvac a
)
select distinct pebempl_pidm
,pebempl_hire_date
,floor(months_between(sysdate, pebempl_hire_date) / 12) as service_years
,pebempl_lcat_code as lcat
,b.ptrlvac_roll_max_hrs
from pebempl a
inner join leave_rules b
on a.pebempl_lcat_code = b.ptrlvac_lcat_code
and floor(months_between(sysdate, pebempl_hire_date) / 12) between year_start and year_end
Combines your 2 CTE's to compute "leave_rules" into 1. I just put sysdate for the date variable so I could test easily - you probably want to use the bind variables as you originally had.

sql-to fetch reciprocal data

I have a table in Oracle db, which contains data like this, a+b, b+a, c+d, d+c.
My question is, when a+b = b+a, how to fetch distinct records.
PK CODE
1000 87DIA4+BAJI204
1001 87DIA4+BIJI939
1002 87DIA4+C3IDI02
1003 87DIA4+C3IZI419
1004 BAJI204+87DIA4
1005 BIJI939+87DIA4
1006 C3IDI02+87DIA4
1007 C3IZI419+87DIA4
Write a sub-query which splits out the two elements from the CODE column. Then use a self-join to find the reciprocal pairs:
SQL> with cte as ( select pk
2 , substr(code, 1, instr(code, '+')-1) as str1
3 , substr(code, instr(code, '+')+1) as str2
4 from your_table)
5 select a.pk as a_pk
6 , b.pk as b_pk
7 , a.str1
8 , a.str2
9 from cte a
10 join cte b
11 on a.str1 = b.str2
12 and a.str2 = b.str1
13 where a.pk < b.pk
14 order by a_pk;
A_PK B_PK STR1 STR2
---------- ---------- ------------ ------------
1000 1004 87DIA4 BAJI204
1001 1005 87DIA4 BIJI939
1002 1006 87DIA4 C3IDI02
1003 1007 87DIA4 C3IZI419
4 rows selected.
SQL>
The WHERE clause ensures the result set contains only one instance of each reciprocal pair.
If I understand your question, you may need something like this:
with yourTable(PK, CODE) as (
select '1000', '87DIA4+BAJI204' from dual union all
select '1001', '87DIA4+BIJI939' from dual union all
select '1002', '87DIA4+C3IDI02' from dual union all
select '1003', '87DIA4+C3IZI419' from dual union all
select '1004', 'BAJI204+87DIA4' from dual union all
select '1005', 'BIJI939+87DIA4' from dual union all
select '1006', 'C3IDI02+87DIA4' from dual union all
select '1007', 'C3IZI419+87DIA4' from dual
)
select least( substr(code, 1, instr(code, '+') -1), substr(code, instr(code, '+') +1)) as A,
greatest( substr(code, 1, instr(code, '+') -1), substr(code, instr(code, '+') +1)) as B
from yourTable
group by least( substr(code, 1, instr(code, '+') -1), substr(code, instr(code, '+') +1)),
greatest( substr(code, 1, instr(code, '+') -1), substr(code, instr(code, '+') +1))
that gives:
A B
--------------- ---------------
87DIA4 BAJI204
87DIA4 BIJI939
87DIA4 C3IDI02
87DIA4 C3IZI419
The idea is to separate the values in each row and, for each row, evaluate the "smallest" and the "greatest" value. This way, you can use these results to group and get only one row A+B if you have A+B and B+A.
As Aleksej and APC both pointed out, you need to split the code into components. And I am pleased to see they both offered the "right" solution (rather than the "lazy" solution) - to split code, use standard string functions substr() and instr(), not regular expressions.
The solution below also splits the code, in the same way. I use that for a GROUP BY clause, to group together the rows you consider "duplicates". Then from each group I just pick the one with the lowest PK value. NOTE: your problem statement is incomplete, you didn't state WHICH row should be kept from each pair. (Or, more generally, what to do with them... if the requirement is different, the solution below can be adapted.)
In each group I select MIN(PK) - that is self-explanatory. Then, to pick up the CODE value that belongs to that PK, I use the FIRST function; see the documentation if you are not familiar with it. (Some developers aren't.)
This solution reads the base data just once, and there are no joins.
with
test_data ( pk, code ) as (
select 1000, '87DIA4+BAJI204' from dual
union all select 1001, '87DIA4+BIJI939' from dual
union all select 1002, '87DIA4+C3IDI02' from dual
union all select 1003, '87DIA4+C3IZI419' from dual
union all select 1004, 'BAJI204+87DIA4' from dual
union all select 1005, 'BIJI939+87DIA4' from dual
union all select 1006, 'C3IDI02+87DIA4' from dual
union all select 1007, 'C3IZI419+87DIA4' from dual
)
-- End of test data (not part of the solution!) SQL query begins BELOW THIS LINE.
select min(pk) as pk, min(code) keep (dense_rank first order by pk) as code
from test_data
group by least(substr(code, 1, instr(code, '+')-1), substr(code, instr(code, '+')+1)),
greatest(substr(code, 1, instr(code, '+')-1), substr(code, instr(code, '+')+1))
;
PK CODE
---- ---------------
1000 87DIA4+BAJI204
1001 87DIA4+BIJI939
1002 87DIA4+C3IDI02
1003 87DIA4+C3IZI419