SQL logic to fail a check if any of the related customers has failed - sql

I have the requirement to flag the customers Y only when all the related customers have also passed the check.
below are the two tables:
relationship table :
customer_id related_customer
1 1
1 2
1 3
2 1
2 2
2 3
3 1
3 2
3 3
11 11
11 22
22 11
22 22
Check table
customer_id check_flag
1 y
2 y
3 n
11 y
22 y
I want output like below:
customer_id paas_fail_flag
1 n
2 n
3 n
11 y
22 y
output justification: since 1,2,3 are related customers and since one of them (3) has n in table 2 , so all the related customers should also have n.
11,22 are related customers and both have y in table 2.so in output both should have y.

You need to join relationship to check and use conditional aggregation:
SELECT r.customer_id,
COALESCE(MAX(CASE WHEN c.check_flag = 'n' THEN c.check_flag END), 'y') paas_fail_flag
FROM relationship r INNER JOIN "check" c
ON c.customer_id = r.related_customer
GROUP BY r.customer_id
ORDER BY r.customer_id
See the demo.

Something like this? Sample data in lines #1 - 40; query begins at line #41:
SQL> WITH
2 -- sample data
3 rel (customer_id, related_customer)
4 AS
5 (SELECT 1, 1 FROM DUAL
6 UNION ALL
7 SELECT 1, 2 FROM DUAL
8 UNION ALL
9 SELECT 1, 3 FROM DUAL
10 UNION ALL
11 SELECT 2, 1 FROM DUAL
12 UNION ALL
13 SELECT 2, 2 FROM DUAL
14 UNION ALL
15 SELECT 2, 3 FROM DUAL
16 UNION ALL
17 SELECT 3, 1 FROM DUAL
18 UNION ALL
19 SELECT 3, 2 FROM DUAL
20 UNION ALL
21 SELECT 3, 3 FROM DUAL
22 UNION ALL
23 SELECT 11, 11 FROM DUAL
24 UNION ALL
25 SELECT 11, 22 FROM DUAL
26 UNION ALL
27 SELECT 22, 11 FROM DUAL
28 UNION ALL
29 SELECT 22, 22 FROM DUAL),
30 chk (customer_id, check_flag)
31 AS
32 (SELECT 1, 'y' FROM DUAL
33 UNION ALL
34 SELECT 2, 'y' FROM DUAL
35 UNION ALL
36 SELECT 3, 'n' FROM DUAL
37 UNION ALL
38 SELECT 11, 'y' FROM DUAL
39 UNION ALL
40 SELECT 22, 'y' FROM DUAL),
41 temp
42 AS
43 -- minimum CHECK_FLAG per customer and related customer
44 ( SELECT r.customer_id, r.related_customer, MIN (c.check_flag) mcf
45 FROM rel r JOIN chk c ON c.customer_id = r.related_customer
46 GROUP BY r.customer_id, r.related_customer)
47 SELECT customer_id, MIN (mcf) flag
48 FROM temp
49 GROUP BY customer_id
50 ORDER BY customer_id;
CUSTOMER_ID FLAG
----------- ----
1 n
2 n
3 n
11 y
22 y
SQL>

Assuming that your relationship data could be sparse, for example:
CREATE TABLE relationship ( customer_id, related_customer ) AS
SELECT 2, 3 FROM DUAL UNION ALL
SELECT 3, 1 FROM DUAL UNION ALL
SELECT 3, 2 FROM DUAL UNION ALL
SELECT 11, 22 FROM DUAL;
CREATE TABLE "CHECK" ( customer_id, check_flag ) AS
SELECT 1, 'y' FROM DUAL UNION ALL
SELECT 2, 'y' FROM DUAL UNION ALL
SELECT 3, 'n' FROM DUAL UNION ALL
SELECT 11, 'y' FROM DUAL UNION ALL
SELECT 22, 'y' FROM DUAL;
(Note: The below query will also work on your dense data, where every relationship combination is enumerated.)
Then you can use a hierarchical query:
SELECT customer_id,
MIN(check_flag) AS check_flag
FROM (
SELECT CONNECT_BY_ROOT(c.customer_id) AS customer_id,
c.check_flag AS check_flag
FROM "CHECK" c
LEFT OUTER JOIN relationship r
ON (r.customer_id = c.customer_id)
WHERE CONNECT_BY_ISLEAF = 1
CONNECT BY NOCYCLE
( PRIOR r.related_customer = c.customer_id
OR PRIOR c.customer_id = r.related_customer )
AND PRIOR c.check_flag = 'y'
)
GROUP BY
customer_id
ORDER BY
customer_id
Which outputs:
CUSTOMER_ID
CHECK_FLAG
1
n
2
n
3
n
11
y
22
y
db<>fiddle here

Related

oracle moving where clause from join to outer join - not returning records properly

I have a query which as follows:
select c.* from cases c
left outer join (select case_id, case_status_id from case_status where case_id not in (SELECT case_id
FROM case_status
where (case_status_id = 16 and case_status_date < sysdate - 365))) cs ON cs.case_id = c.case_id
left outer join lkp_case_status lkp_cs
on lkp_cs.id = cs.case_status_id
where c.case_type = 'P'
and c.delete_date is null
The 2nd line used to be a join earlier but now I had to convert it to left outer join. What it does is it checks for case status and if the case status is 16 and the date is a year back don't show the record. When I change it to left outer join it picks up the record even when the dates are in 2019.
case
id name
1 AAA
2 BBB
3 CCC
4 DDD
case_status
1 16 01-NOV-19 03.42.37.420000000 PM
1 5 01-NOV-19 03.42.37.420000000 PM
2 1 18-NOV-19 12.36.11.268000000 PM
2 3 18-NOV-19 12.36.11.268000000 PM
3 5 18-NOV-21 12.36.11.268000000 PM
3 16 18-NOV-21 12.36.11.268000000 PM
The output should show as follows:
Result:
id name status
2 BBB 1,3
3 CCC 5,16
4 DDD
case id 1 has status 16 and dates back to 2019, case id 3 though has status 16 is within a year so should be picked up.
The query is really big but I am missing only this case status scenario hence posting a part of it.
Any suggestions or inputs, please.
How about not exists?
Sample data:
SQL> with
2 tcase (id, name) as
3 (select 1, 'AAA' from dual union all
4 select 2, 'BBB' from dual union all
5 select 3, 'CCC' from dual union all
6 select 4, 'DDD' from dual
7 ),
8 tcase_status (case_id, case_status_id, case_status_date) as
9 (select 1, 16, date '2019-11-01' from dual union all
10 select 1, 5, date '2019-11-01' from dual union all
11 select 2, 1, date '2019-11-18' from dual union all
12 select 2, 3, date '2019-11-18' from dual union all
13 select 3, 5, date '2021-11-18' from dual union all
14 select 3, 16, date '2021-11-18' from dual
15 )
Query:
16 select c.id,
17 c.name,
18 listagg(cs.case_status_id, ', ') within group (order by cs.case_status_id) status
19 from tcase c left join tcase_status cs on c.id = cs.case_id
20 where not exists (select null
21 from tcase_status c
22 where c.case_id = cs.case_id
23 and c.case_status_id = 16
24 and c.case_status_date < add_months(trunc(sysdate), -12)
25 )
26 group by c.id, c.name
27 order by c.id;
ID NAME STATUS
---------- ---- ----------
2 BBB 1, 3
3 CCC 5, 16
4 DDD
SQL>

select only those users whose contacts length is not 5

I have table like this:
id
name
contact
1
A
65489
1
A
1
A
45564
2
B
3
C
12345
3
C
1234
4
D
32
4
D
324
I only want users who have no contact or the contact length is not five.
If the user has two or more contacts and the length of one of them is five and the rest is not, then such users should not be included in the table.
so,If the customer has at least one contact length of five, I do not want that.
so, i want table like this:
id
name
contact
2
B
4
D
32
4
D
324
Can you halp me?
You could actually do a range check here:
SELECT id, name, contact
FROM yourTable t1
WHERE NOT EXISTS (
SELECT 1
FROM yourTable t2
WHERE t2.id = t1.id AND TO_NUMBER(t2.contact) BETWEEN 10000 AND 99999
);
Note that if contact already be a numeric column, then just remove the calls to TO_NUMBER above and compare directly.
Yet another option:
SQL> with test (id, name, contact) as
2 (select 1, 'a', 65879 from dual union all
3 select 1, 'a', null from dual union all
4 select 1, 'a', 45564 from dual union all
5 select 2, 'b', null from dual union all
6 select 3, 'c', 12345 from dual union all
7 select 3, 'c', 1234 from dual union all
8 select 4, 'd', 32 from dual union all
9 select 4, 'd', 324 from dual
10 )
11 select *
12 from test a
13 where exists (select null
14 from test b
15 where b.id = a.id
16 group by b.id
17 having nvl(max(length(b.contact)), 0) < 5
18 );
ID N CONTACT
---------- - ----------
2 b
4 d 32
4 d 324
SQL>
COUNT analytic function can also be used to get the job done.
select id, name, contact
from (
select id, name, contact
, count( decode( length(contact), 5, 1, null ) ) over( partition by id, name ) cnt
from YourTable
)
where cnt = 0
demo

Oracle PL/SQL SUM OVER( ) starting from certain row

I have a table who looks like this:
Pam_A Week Value_1
A 1 10
A 2 13
B 3 15
B 4 10
B 5 11
B 6 10
I want to achieve the following:
Pam_A Week Value_1 Value_2
A 1 10
A 2 13
B 3 15 28
B 4 10 38
B 5 11 49
B 6 10 59
When Pam_A=B, sum the current Value_1 and its preceding row value and keep that value increasing accordding the next value in Value_1
Any ideas for achieve this cumulative sum?
First of all you need to mark all rows that you want to count. You can do it like this:
with t(Pam_A, Week, Value_1) as (
select 'A', 1, 10 from dual union all
select 'A', 2, 13 from dual union all
select 'B', 3, 15 from dual union all
select 'B', 4, 10 from dual union all
select 'B', 5, 11 from dual union all
select 'B', 6, 10 from dual
)
select
Pam_A, Week, Value_1
,case
when Pam_A='B' or lead(Pam_A)over(order by week) = 'B'
then 'Y'
else 'N'
end as flag
from t;
Results:
PAM_A WEEK VALUE_1 FLAG
----- ---------- ---------- ----
A 1 10 N
A 2 13 Y
B 3 15 Y
B 4 10 Y
B 5 11 Y
B 6 10 Y
6 rows selected.
Then you can aggregate only rows that have flag='Y':
with t(Pam_A, Week, Value_1) as (
select 'A', 1, 10 from dual union all
select 'A', 2, 13 from dual union all
select 'B', 3, 15 from dual union all
select 'B', 4, 10 from dual union all
select 'B', 5, 11 from dual union all
select 'B', 6, 10 from dual
)
select
v.*
,case
when flag='Y' and Pam_a='B'
then sum(Value_1)over(partition by flag order by Week)
end as sums
from (
select
Pam_A, Week, Value_1
,case
when Pam_A='B' or lead(Pam_A)over(order by week) = 'B'
then 'Y'
else 'N'
end as flag
from t
) v;
Results:
PAM_A WEEK VALUE_1 FLAG SUMS
----- ---------- ---------- ---- ----------
A 1 10 N
A 2 13 Y
B 3 15 Y 28
B 4 10 Y 38
B 5 11 Y 49
B 6 10 Y 59
6 rows selected.
Using a combination of LEAD and SUM analytic functions, you can determine which rows have the next PAM_A as a B, then only SUM if the next row is a B or the current row is a B.
Query
WITH
d (pam_a, week, value_1)
AS
(SELECT 'A', 1, 10 FROM DUAL
UNION ALL
SELECT 'A', 2, 13 FROM DUAL
UNION ALL
SELECT 'B', 3, 15 FROM DUAL
UNION ALL
SELECT 'B', 4, 10 FROM DUAL
UNION ALL
SELECT 'B', 5, 11 FROM DUAL
UNION ALL
SELECT 'B', 6, 10 FROM DUAL)
SELECT pam_a,
week,
value_1,
CASE
WHEN pam_a = 'B'
THEN
SUM (CASE WHEN next_pam_a = 'B' OR pam_a = 'B' THEN value_1 ELSE 0 END)
OVER (ORDER BY week)
ELSE
NULL
END value_2
FROM (SELECT pam_a, week, value_1, LEAD (pam_a) OVER (ORDER BY week) AS next_pam_a FROM d);
Result
PAM_A WEEK VALUE_1 VALUE_2
________ _______ __________ __________
A 1 10
A 2 13
B 3 15 28
B 4 10 38
B 5 11 49
B 6 10 59
If I understand, you want a cumulative sum but with conditionality:
select t.*,
(case when pam_A = 'B' then sum(value_1) over (order by week) end) as value_2
from t;

Oracle SQL query to fetch manyToMany record in a single column

I have the following three tables, my requirement is to fetch manytoMany joined tabled as a column. Can someone pls help me in writing the query.
IPtype can only be of two types public and private, so the result should have two more columns as mentioned below. If there is multiple public or private ip mapped to single asset then is should be displayed as comma separated.
Thanks
Looks like outer join (to return assets that don't have any IP addresses) with listagg (to aggregate IP addresses per asset) problem.
SQL> with
2 -- Sample data
3 asset (assetid) as
4 (select 1 from dual union all
5 select 2 from dual union all
6 select 3 from dual union all
7 select 4 from dual
8 ),
9 ip (ipid, ipnumber, iptype) as
10 (select 1, '1.2.3.4' , 'Public' from dual union all
11 select 2, '99.22.3.4', 'Private' from dual union all
12 select 3, '11.22.3.4', 'Public' from dual union all
13 select 4, '55.22.3.4', 'Public' from dual union all
14 select 5, '66.22.3.4', 'Private' from dual union all
15 select 6, '77.22.3.4', 'Private' from dual
16 ),
17 asset_ip_map (assetid, ipid) as
18 (select 1, 1 from dual union all
19 select 1, 2 from dual union all
20 select 2, 3 from dual union all
21 select 2, 4 from dual union all
22 select 3, 5 from dual union all
23 select 3, 6 from dual
24 )
25 -- Query you need
26 select a.assetid,
27 listagg(case when iptype = 'Public' then i.ipnumber end, ', ') within group (order by null) public_ip,
28 listagg(case when iptype = 'Private' then i.ipnumber end, ', ') within group (order by null) private_ip
29 from asset a left join asset_ip_map m on m.assetid = a.assetid
30 left join ip i on i.ipid = m.ipid
31 group by a.assetid
32 order by a.assetid;
ASSETID PUBLIC_IP PRIVATE_IP
---------- ------------------------------ ------------------------------
1 1.2.3.4 99.22.3.4
2 11.22.3.4, 55.22.3.4
3 66.22.3.4, 77.22.3.4
4
SQL>

Oracle - How to use avg(count(*)) on having clause or where clause?

I have data like this:
CONCERT_ID EVENT_ID ATTENDANCE AVG_ATTENDANCE_EACH_CONCERT
---------- ---------- ---------- ---------------------------
1 1 1 1,5
1 2 2 1,5
3 5 2 2
3 6 2 2
5 11 4 2
5 12 1 2
5 13 1 2
Thats from this query:
select concert_id, event_id, count(customer_id) attendance,
avg(count(*)) over (partition by concert_id) avg_attendance_each_concert
from booking
group by concert_id, event_id
order by event_id;
How to make a limitation on that query. What i want to make is
If the attendance is below average attendance show result
I already tried avg(count(*)) over (partition by concert_id) to having clause but gave me an error group function is too deep
It's easy to get the desired results by applying just one nesting :
select * from
(
select concert_id, event_id, count(customer_id) attendance,
avg(count(*)) over (partition by concert_id) avg_attendance_each_concert
from booking
group by concert_id, event_id
order by event_id
)
where attendance < avg_attendance_each_concert
D e M o
Include an "intermediate" table, a query which returned the correct result in your last question. Then select values - which satisfy a new condition - from it.
SQL> with booking (concert_id, event_id, customer_id) as
2 (select 1, 1, 10 from dual union
3 select 1, 2, 10 from dual union
4 select 1, 2, 20 from dual union
5 --
6 select 3, 5, 10 from dual union
7 select 3, 5, 20 from dual union
8 select 3, 6, 30 from dual union
9 select 3, 6, 40 from dual union
10 --
11 select 5, 11, 10 from dual union
12 select 5, 11, 20 from dual union
13 select 5, 11, 30 from dual union
14 select 5, 11, 40 from dual union
15 select 5, 12, 50 from dual union
16 select 5, 13, 60 from dual
17 ),
18 inter as
19 (select concert_id, event_id, count(customer_id) attendance,
20 avg(count(*)) over (partition by concert_id) avg_attendance_each_concert
21 from booking
22 group by concert_id, event_id
23 )
24 select concert_id, event_id, attendance, avg_attendance_each_concert
25 from inter
26 where attendance < avg_attendance_Each_concert
27 order by event_id;
CONCERT_ID EVENT_ID ATTENDANCE AVG_ATTENDANCE_EACH_CONCERT
---------- ---------- ---------- ---------------------------
1 1 1 1,5
5 12 1 2
5 13 1 2
SQL>