NULLIF of FINAL SUM Doubled when using MATCH_RECOGNIZE - oracle

When I run the following code, I would expect b1 and b2 to be equal, however, b2 is doubled. Am I doing something wrong? Is this a bug in the database? We're running Oracle 12c (12.2.0.1.0).
WITH TBL AS
(
SELECT 1 a, 1 b FROM DUAL UNION ALL
SELECT 1 a, 2 b FROM DUAL UNION ALL
SELECT 1 a, 3 b FROM DUAL UNION ALL
SELECT 1 a, 4 b FROM DUAL
)
SELECT
*
FROM
TBL
MATCH_RECOGNIZE
(
PARTITION BY
a
ORDER BY
b
MEASURES
FINAL SUM(b) b1,
NULLIF(FINAL SUM(b), 0) b2
ALL ROWS PER MATCH WITH UNMATCHED ROWS
AFTER MATCH SKIP PAST LAST ROW
PATTERN
(C*)
DEFINE
C AS B > 0
) mr
Result:
| A | B | B1 | B2 |
|---|---|----|----|
| 1 | 1 | 10 | 20 |
| 1 | 2 | 10 | 20 |
| 1 | 3 | 10 | 20 |
| 1 | 4 | 10 | 20 |

The problem seems to be with NULLIF when I converted the same into it's logical equivalent and it is working fine CASE WHEN expr1 = expr 2 THEN NULL ELSE expr1 END
WITH TBL AS
(
SELECT 1 a, 1 b FROM DUAL UNION ALL
SELECT 1 a, 2 b FROM DUAL UNION ALL
SELECT 1 a, 3 b FROM DUAL UNION ALL
SELECT 1 a, 4 b FROM DUAL
)
SELECT
*
FROM
TBL
MATCH_RECOGNIZE
(
PARTITION BY
a
ORDER BY
b
MEASURES
FINAL SUM(b) b1,
CASE WHEN FINAL SUM(b)=0 THEN NULL ELSE FINAL SUM(b) END b2
ALL ROWS PER MATCH WITH UNMATCHED ROWS
AFTER MATCH SKIP PAST LAST ROW
PATTERN
(C*)
DEFINE
C AS B > 0
) mr
Result
| A | B | B1 | B2 |
|---|---|----|----|
| 1 | 1 | 10 | 10 |
| 1 | 2 | 10 | 10 |
| 1 | 3 | 10 | 10 |
| 1 | 4 | 10 | 10 |

Related

Oracle iterates thru values on columns (Oracle 11)

So I have some data like this
NO| ID | PID | COUNT
1 | 00033 | P4 | 1
2 | 00033 | P3 | 3
3 | 00033 | P2 | 2
i want to iterate the ID and PID based on count values, like this
NO| ID | PID
1 | 00033 | P4
2 | 00033 | P3
3 | 00033 | P3
4 | 00033 | P3
5 | 00033 | P2
6 | 00033 | P2
actually its already solved using this query
SELECT row_number() OVER ( ORDER BY t."ID", t."PID" DESC ) as NO,
t."ID", t."PID"
FROM Table1 t
CROSS APPLY(
SELECT 1 FROM dual
CONNECT BY level <= t."COUNT"
)
ORDER BY t."ID", t."PID" DESC
as per this link
Oracle iterates thru values on columns
but since our DB using oracle 11. the code doesnt working anymore.
i'd like to know the same approach for oracle 11.
Regards, Rian
Cast this hierarchical query as table of numbers and then join:
with
t("NO", "ID", "PID", "COUNT") as (
select 1, '00033', 'P4', 1 from dual union all
select 2, '00033', 'P3', 3 from dual union all
select 3, '00033', 'P2', 2 from dual ),
a as (select t.*,
cast(multiset(select level lvl
from dual
connect by level <= "COUNT")
as sys.odcinumberlist) nums
from t)
select "NO", "ID", "PID" from a cross join table(nums)
Result:
NO ID PID
---------- ----- ---
1 00033 P4
2 00033 P3
2 00033 P3
2 00033 P3
3 00033 P2
3 00033 P2
6 rows selected

how can list row with max value of one column

I have some database like example below.
WITH TB AS(
SELECT 'A' C1, 'B' AS C2, 1 AS N1, 1 AS N2 FROM DUAL UNION ALL
SELECT 'A' C1, 'B' AS C2, 2 AS N1, 2 AS N2 FROM DUAL UNION ALL
SELECT 'A1' C1, 'B1' AS C2, 1 AS N1, 3 AS N2 FROM DUAL UNION ALL
SELECT 'A1' C1, 'B1' AS C2, 2 AS N1, 4 AS N2 FROM DUAL UNION ALL
SELECT 'A1' C1, 'B1' AS C2, 3 AS N1, 1 AS N2 FROM DUAL UNION ALL
SELECT 'A2' C1, 'B2' AS C2, 1 AS N1, 6 AS N2 FROM DUAL
)
SELECT * FROM TB
How can I list all row with Max(N1), and group by C1, C2 like Image below?
The description of the requirements in the question is unclear, I am guesing that max(n1) should be calculated for each group of C1+c2.
If this is a case, then you can use MAX() OVER () analytic function in this way:
SELECT tb.*,
max( n1 ) over (partition by c1, c2 ) xxxx
FROM TB;
| C1 | C2 | N1 | N2 | XXXX |
|----|----|----|----|------|
| A | B | 1 | 1 | 2 |
| A | B | 2 | 2 | 2 |
| A1 | B1 | 1 | 3 | 3 |
| A1 | B1 | 2 | 4 | 3 |
| A1 | B1 | 3 | 1 | 3 |
| A2 | B2 | 1 | 6 | 1 |
and then wrap the above query as a subquery, and filter out unwanted rows:
SELECT c1,c2,n1,n2 FROM (
SELECT tb.*,
max( n1 ) over (partition by c1, c2 ) xxxx
FROM TB
)
WHERE n1 = xxxx
| C1 | C2 | N1 | N2 |
|----|----|----|----|
| A | B | 2 | 2 |
| A1 | B1 | 3 | 1 |
| A2 | B2 | 1 | 6 |
Demo: http://sqlfiddle.com/#!4/d2fb9/4
One way is using KEEP .. DENSE_RANK
SQL Fiddle
Query 1:
WITH TB AS(
SELECT 'A' C1, 'B' AS C2, 1 AS N1, 1 AS N2 FROM DUAL UNION ALL
SELECT 'A' C1, 'B' AS C2, 2 AS N1, 2 AS N2 FROM DUAL UNION ALL
SELECT 'A1' C1, 'B1' AS C2, 1 AS N1, 3 AS N2 FROM DUAL UNION ALL
SELECT 'A1' C1, 'B1' AS C2, 2 AS N1, 4 AS N2 FROM DUAL UNION ALL
SELECT 'A1' C1, 'B1' AS C2, 3 AS N1, 1 AS N2 FROM DUAL UNION ALL
SELECT 'A2' C1, 'B2' AS C2, 1 AS N1, 6 AS N2 FROM DUAL
)
SELECT C1
,C2
,MAX(N1) AS N1
,MAX(N2) KEEP (
DENSE_RANK FIRST ORDER BY N1 DESC
) AS N2
FROM TB
GROUP BY C1
,C2
Results:
| C1 | C2 | N1 | N2 |
|----|----|----|----|
| A | B | 2 | 2 |
| A1 | B1 | 3 | 1 |
| A2 | B2 | 1 | 6 |

Use count(decode...) across multiple tables

I have three 3 tables (having the same key) in the following structure:
Input Table t1:
file_in| f_in_state|
--------------------
F01 | 1 |
F02 | 2 |
F21 | 1 |
F41 | 2 |
Input Table t2:
line_in| file_in| l_in_state |
-----------------------------
L001 | F01 | 1 |
L002 | F01 | 2 |
L003 | F01 | 2 |
L004 | F01 | 2 |
L005 | F21 | 1 |
L006 | F21 | 1 |
L007 | F21 | 1 |
L008 | F21 | 1 |
Input Table t3:
line_out|line_in| file_in| l_out_state|
---------------------------------------
D001 |L001 | F01 | 1 |
D002 |L002 | F01 | 1 |
D003 |L003 | F01 | 1 |
and I need to count the number of occurrences of the columns refering to the different states across my three tables for each "file in id" and then combine them to get an output like this:
file_in_id|file_in_state| A | B | C | D | E |
---------------------------------------------
F01 | 1 | 1 | 3 | 0 | 0 | 3 |
F02 | 2 | 2 | 0 | 0 | 0 | 0 |
F21 | 1 | 1 | 4 | 0 | 0 | 0 |
F41 | 2 | 2 | 0 | 0 | 0 | 0 |
with:
A refers to the number of input lines ("line_in") having the state = '1'
B refers to the number of input lines ("line_in") having the state = '2'
C refers to the number of input lines ("line_in") having the state = '3' (in my case, there is no line with this state, but it is possible to happen)
D refers to the number of output lines ("line_out") having the state = '1'
E refers to the number of output lines ("line_out") having the state = '2'
So, I tried to use the decode function in my query but I didn't get the wished result.
SELECT
t1.file_in AS file_in_id,
t1.f_in_state AS file_in_state,
COUNT(DECODE(t2.f_in_state, '1', 1, null)) AS A,
COUNT(DECODE(t2.f_in_state, '2', 1, null)) AS B,
COUNT(DECODE(t2.f_in_state, '3', 1, null)) AS C,
COUNT(DECODE(t3.f_out_state, '1', 1, null)) AS D,
COUNT(DECODE(t3.f_out_state, '2', 1, null)) AS E
FROM table1 t1,
table2 t2,
table3 t3
WHERE t1.file_in = t2.file_in (+)
AND t2.file_in = t3.file_in (+)
GROUP BY t1.file_in, t1.f_in_state
ORDER BY t1.file_in
But, this is what I get :
file_in_id|file_in_state|A |B |C |D |E |
----------------------------------------
F01 |1 |1 |3 |9 |0 |12|
F02 |2 |2 |0 |0 |0 |0 |
F21 |1 |1 |4 |0 |0 |0 |
F41 |2 |2 |0 |0 |0 |0 |
Could somebody tells me what is wrong with this query and how can I fix it to get what I would like to have as a result.
It's very important, this is how the input table 3 should be :
Input Table t3:
line_out|*file_out*| file_in| l_out_state|
---------------------------------------
D001 |W01 | F01 | 1 |
D002 |W01 | F01 | 1 |
D003 |W01 | F01 | 1 |
This query gives desired result:
select file_in, f_in_state,
count(case l_in_state when '1' then 1 end) a,
count(case l_in_state when '2' then 1 end) b,
count(case l_in_state when '3' then 1 end) c,
count(case l_out_state when '1' then 1 end) d,
count(case l_out_state when '2' then 1 end) e
from t1
left join t2 using (file_in)
left join t3 using (file_in, line_in)
group by file_in, f_in_state
order by file_in
You could also use pivot if you have Oracle 11g or above.
Test:
with t1(file_in, f_in_state) as (
select 'F01', '1' from dual union all
select 'F02', '2' from dual union all
select 'F21', '1' from dual union all
select 'F41', '2' from dual ),
t2(line_in, file_in, l_in_state) as (
select 'L001', 'F01', '1' from dual union all
select 'L002', 'F01', '2' from dual union all
select 'L003', 'F01', '2' from dual union all
select 'L004', 'F01', '2' from dual union all
select 'L005', 'F21', '1' from dual union all
select 'L006', 'F21', '1' from dual union all
select 'L007', 'F21', '1' from dual union all
select 'L008', 'F21', '1' from dual ),
t3(line_out, line_in, file_in, l_out_state) as (
select 'D001', 'L001', 'F01', '1' from dual union all
select 'D002', 'L002', 'F01', '1' from dual union all
select 'D003', 'L003', 'F01', '1' from dual )
select file_in, f_in_state,
count(case l_in_state when '1' then 1 end) a,
count(case l_in_state when '2' then 1 end) b,
count(case l_in_state when '3' then 1 end) c,
count(case l_out_state when '1' then 1 end) d,
count(case l_out_state when '2' then 1 end) e
from t1
left join t2 using (file_in)
left join t3 using (file_in, line_in)
group by file_in, f_in_state
order by file_in
Output:
FILE_IN F_IN_STATE A B C D E
------- ---------- ---------- ---------- ---------- ---------- ----------
F01 1 1 3 0 3 0
F02 2 0 0 0 0 0
F21 1 4 0 0 0 0
F41 2 0 0 0 0 0
Variant with SUM
Select a.file_in, a.f_in_state,
Sum(Case When b.l_in_state=1 Then 1 Else 0 End) A,
Sum(Case When b.l_in_state=2 Then 1 Else 0 End) B,
Sum(Case When b.l_in_state=3 Then 1 Else 0 End) C,
Sum(Case When c.l_out_state=1 Then 1 Else 0 End) D,
Sum(Case When c.l_out_state=2 Then 1 Else 0 End) E
From T1 a
Left join T2 b on a.file_in=b.file_in
Left join T3 c on a.file_in=c.file_in and b.line_in=c.line_in
GROUP BY a.file_in, a.f_in_state
ORDER BY a.file_in

Row count by group

I am attempting to write the following query to get a row count by group.
select
a.employee, a.cov_option,
count(a.cov_option) over (partition by a.cov_option order by a.employee) as row_num
from wilson.benefit a
inner join wilson.bncategory b
ON a.plan_type = b.plan_type and a.plan_option = b.plan_option
inner join wilson.bncovopt c
ON a.company = c.company and a.plan_code = c.plan_code and a.cov_option = c.coverage_opt
where
a.plan_type = 'HL' and
to_char(a.stop_date, 'yyyy-mm-dd') = '1700-01-01'
order by a.employee, a.cov_option
The result set returned is:
employee | cov_option |row_num
-------------|--------------|--------------
429 | 1 | 1
429 | 3 | 2
429 | 3 | 2
1420 | 1 | 2
1420 | 3 | 4
1420 | 3 | 4
1537 | 2 | 2
1537 | 2 | 2
The result set I am attempting to return is:
429 | 1 | 1
429 | 3 | 2
429 | 3 | 2
1420 | 1 | 1
1420 | 3 | 2
1420 | 3 | 2
1537 | 2 | 1
1537 | 2 | 1
What you seem to want is dense_rank() rather than count(). Indeed, "count" means simply determining how many rows are in each group, it is not "counting" in the way we learn as children (first, second, third). That kind of counting is called "ranking".
dense_rank() over (partition by a.employee order by a.cov_option) as row_num
should do what you need.
There is also rank() - the difference is that if two rows are tied for first, with dense_rank() the third row gets rank 2; with simple rank() it gets rank 3 (rank 2 is "used up" by the first two rows).

Oracle select two (or more) adjacent rows having the same value for a given column

How do I do the following in Oracle:
I have a (simplified) table:
+-----+-----+-----+
| a | b | ... |
+-----+-----+-----+
| 1 | 7 | ... |
| 2 | 5 | ... |
| 1 | 7 | ... |
+-----+-----+-----+
Where a functions as a unique identifier for a person, and b is the field I am interested in matching across rows. How do I construct a query that basically says "give me the person-ID's where the person has multiple b values (i.e., duplicates)"?
So far I have tried:
SELECT a FROM mytable GROUP BY a HAVING COUNT(DISTINCT b) > 1;
This feels close except it just gives me the user IDs where the user has multiple unique b's, which I suspect is coming from the DISTINCT part, but I'm not sure how to change the query to achieve what I want.
Try
group by a,b having count(b) > 1
Yours would count 7,5,7 as 2 (one 7, one 5). This one one will count total Bs in any grouping, so you'll get 1,7 - > 2 and 1,5 -> 1
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE mytable ( a, b ) AS
SELECT LEVEL, LEVEL FROM DUAL CONNECT BY LEVEL <= 2000
UNION ALL
SELECT LEVEL *2, LEVEL * 2 FROM DUAL CONNECT BY LEVEL <= 1000;
Query 1:
WITH data AS (
SELECT a
FROM mytable
GROUP BY a
HAVING COUNT(b) > COUNT( DISTINCT b )
ORDER BY a
),
numbered AS (
SELECT a,
ROWNUM AS rn
FROM data
)
SELECT a
FROM numbered
WHERE rn <= 20
Results:
| A |
|----|
| 2 |
| 4 |
| 6 |
| 8 |
| 10 |
| 12 |
| 14 |
| 16 |
| 18 |
| 20 |
| 22 |
| 24 |
| 26 |
| 28 |
| 30 |
| 32 |
| 34 |
| 36 |
| 38 |
| 40 |

Resources