How to write SQL Query with group by and having [duplicate] - sql

This question already has answers here:
Fetch the rows which have the Max value for a column for each distinct value of another column
(35 answers)
Closed 12 months ago.
I have this table which has three columns: number, status and unix_time
number,status,Unix_time
01,Y,112
02,Y,112
01,Y,114
03,,114
01,,115
02,Y,115
04,Y,115
04,Y,119
05,Y,119
06,Y,120
05,Y,120
06,,121
05,Y,121
NOTE: actual value of the column unix_time is in millisec since 1970
OUTPUT: all numbers, whose last appearance (based on timestamp), have status 'Y'
number
02
04
05
Kindly help me, how can I write an oracle SQL query

One option is to use row_number (or perhaps rank? Depends on whether there can be duplicates as far as time is concerned) analytic function (to find which row is the last per each num), and then - in main query - return only those rows whose status is Y.
Sample data:
SQL> WITH
2 test (num, status, time)
3 AS
4 (SELECT '01', 'Y', 112 FROM DUAL
5 UNION ALL
6 SELECT '02', 'Y', 112 FROM DUAL
7 UNION ALL
8 SELECT '01', 'Y', 114 FROM DUAL
9 UNION ALL
10 SELECT '03', '', 114 FROM DUAL
11 UNION ALL
12 SELECT '01', '', 115 FROM DUAL
13 UNION ALL
14 SELECT '02', 'Y', 115 FROM DUAL
15 UNION ALL
16 SELECT '04', 'Y', 115 FROM DUAL
17 UNION ALL
18 SELECT '04', 'Y', 119 FROM DUAL
19 UNION ALL
20 SELECT '05', 'Y', 119 FROM DUAL
21 UNION ALL
22 SELECT '06', 'Y', 120 FROM DUAL
23 UNION ALL
24 SELECT '05', 'Y', 120 FROM DUAL
25 UNION ALL
26 SELECT '06', '', 121 FROM DUAL
27 UNION ALL
28 SELECT '05', 'Y', 121 FROM DUAL),
Query begins here:
29 temp
30 AS
31 (SELECT num,
32 status,
33 time,
34 ROW_NUMBER () OVER (PARTITION BY num ORDER BY time DESC) rn
35 FROM test)
36 SELECT num
37 FROM temp
38 WHERE rn = 1
39 AND status = 'Y';
NU
--
02
04
05
SQL>

Related

SQL Implementing Forward Fill logic

I have a dataset within a date range which has three columns, Product_type, date and metric. For a given product_type, data is not available for all days. For the missing rows, we would like to do a forward date fill for next n days using the last value of the metric.
Product_type
date
metric
A
2019-10-01
10
A
2019-10-02
12
A
2019-10-03
15
A
2019-10-04
5
A
2019-10-05
5
A
2019-10-06
5
A
2019-10-16
12
A
2019-10-17
23
A
2019-10-18
34
Here, the data from 2019-10-04 to 2019-10-06, has been forward filled. There might be bigger gaps in the dates, but we only want to fill the first n days.
Here, n=2, so rows 5 and 6 has been forward filled.
I am not sure how to implement this logic in SQL.
Here's one option. Read comments within code.
Sample data:
SQL> WITH
2 test (product_type, datum, metric)
3 AS
4 (SELECT 'A', DATE '2019-10-01', 10 FROM DUAL
5 UNION ALL
6 SELECT 'A', DATE '2019-10-02', 12 FROM DUAL
7 UNION ALL
8 SELECT 'A', DATE '2019-10-03', 15 FROM DUAL
9 UNION ALL
10 SELECT 'A', DATE '2019-10-04', 5 FROM DUAL
11 UNION ALL
12 SELECT 'A', DATE '2019-10-16', 12 FROM DUAL
13 UNION ALL
14 SELECT 'A', DATE '2019-10-18', 23 FROM DUAL),
Query begins here:
15 temp
16 AS
17 -- CB_FWD_FILL = 1 if difference between two consecutive dates is larger than 1 day
18 -- (i.e. that's the gap to be forward filled)
19 (SELECT product_type,
20 datum,
21 metric,
22 LEAD (datum) OVER (PARTITION BY product_type ORDER BY datum)
23 next_datum,
24 CASE
25 WHEN LEAD (datum)
26 OVER (PARTITION BY product_type ORDER BY datum)
27 - datum >
28 1
29 THEN
30 1
31 ELSE
32 0
33 END
34 cb_fwd_fill
35 FROM test)
36 -- original data from the table
37 SELECT product_type, datum, metric FROM test
38 UNION ALL
39 -- DATUM is the last date which is OK; add LEVEL pseudocolumn to it to fill the gap
40 -- with PAR_N number of rows
41 SELECT product_type, datum + LEVEL, metric
42 FROM (SELECT product_type, datum, metric
43 FROM (-- RN = 1 means that that's the first gap in data set - that's the one
44 -- that has to be forward filled
45 SELECT product_type,
46 datum,
47 metric,
48 ROW_NUMBER ()
49 OVER (PARTITION BY product_type ORDER BY datum) rn
50 FROM temp
51 WHERE cb_fwd_fill = 1)
52 WHERE rn = 1)
53 CONNECT BY LEVEL <= &par_n
54 ORDER BY datum;
Result:
Enter value for par_n: 2
PRODUCT_TYPE DATUM METRIC
--------------- ---------- ----------
A 2019-10-01 10
A 2019-10-02 12
A 2019-10-03 15
A 2019-10-04 5
A 2019-10-05 5 --> newly added
A 2019-10-06 5 --> rows
A 2019-10-16 12
A 2019-10-18 23
8 rows selected.
SQL>
Another solution:
WITH test (product_type, datum, metric) AS
(
SELECT 'A', DATE '2019-10-01', 10 FROM DUAL
UNION ALL
SELECT 'A', DATE '2019-10-02', 12 FROM DUAL
UNION ALL
SELECT 'A', DATE '2019-10-03', 15 FROM DUAL
UNION ALL
SELECT 'A', DATE '2019-10-04', 5 FROM DUAL
UNION ALL
SELECT 'A', DATE '2019-10-16', 12 FROM DUAL
UNION ALL
SELECT 'A', DATE '2019-10-18', 23 FROM DUAL
),
minmax(mindatum, maxdatum) AS (
SELECT MIN(datum), max(datum) from test
),
alldates (datum, product_type) AS
(
SELECT mindatum + level - 1, t.product_type FROM minmax,
(select distinct product_type from test) t
connect by mindatum + level <= (select maxdatum from minmax)
),
grouped as (
select a.datum, a.product_type, t.metric,
count(t.product_type) over(partition by a.product_type order by a.datum) as grp
from alldates a
left join test t on t.datum = a.datum
),
final_table as (
select g.datum, g.product_type, g.grp, g.rn,
last_value(g.metric ignore nulls) over(partition by g.product_type order by g.datum) as metric
from (
select g.*, row_number() over(partition by product_type, grp order by datum) - 1 as rn
from grouped g
) g
)
select datum, product_type, metric
from final_table
where rn <= &par_n
order by datum
;

Split date range into weeks in sql

Given a table called Project, I need the list of team_id's who won at least an award every week in last 3 months
launch_date team_id project_name
2019-01-01 123 A
2019-01-01 345 B
2019-01-01 357 C
2019-01-09 123 D
2019-01-08 345 E
2019-01-21 123 F
project_name award
A Y
B N
C Y
D Y
E N
F Y
last 3 months can be achieved with below where condition but how do i split the launch_date into weekly intervals
where launch_date >= sysdate - 90
With the given data, answer should be team id 123
In your sample data, You have only given 21 days of data instead of 3 months.
You can find out the total number of weeks and their week starting date which can then be compared with your table data to check if an award is won by the team for each week as follows:
SQL> --SAMPLE DATA
SQL> with teams (launch_date, team_id, project_name)
2 as
3 (SELECT DATE'2019-01-01', 123, 'A' FROM DUAL UNION ALL
4 SELECT DATE'2019-01-01', 345, 'B' FROM DUAL UNION ALL
5 SELECT DATE'2019-01-01', 357, 'C' FROM DUAL UNION ALL
6 SELECT DATE'2019-01-09', 123, 'D' FROM DUAL UNION ALL
7 SELECT DATE'2019-01-08', 345, 'E' FROM DUAL UNION ALL
8 SELECT DATE'2019-01-21', 123, 'F' FROM DUAL),
9 AWARDS(project_name, award)
10 AS
11 (SELECT 'A','Y' FROM DUAL UNION ALL
12 SELECT 'B','N' FROM DUAL UNION ALL
13 SELECT 'C','Y' FROM DUAL UNION ALL
14 SELECT 'D','Y' FROM DUAL UNION ALL
15 SELECT 'E','N' FROM DUAL UNION ALL
16 SELECT 'F','Y' FROM DUAL),
17 -- YOUR QUERY START FROM HERE
18 -- WITH
19 WKS(DT) AS
20 (SELECT DISTINCT TRUNC(DATE '2019-01-21' - LEVEL + 1, 'W')
21 FROM DUAL CONNECT BY LEVEL <= 21
22 )
23 SELECT T.TEAM_ID
24 FROM WKS W
25 LEFT JOIN TEAMS T ON W.DT = TRUNC(T.LAUNCH_DATE, 'W')
26 LEFT JOIN AWARDS A ON A.PROJECT_NAME = T.PROJECT_NAME
27 WHERE A.AWARD = 'Y'
28 GROUP BY T.TEAM_ID
29 HAVING COUNT(1) = ( SELECT COUNT(1) FROM WKS);
TEAM_ID
----------
123
SQL>
In WKS cte for 3 months data, You need to replace the
WKS(DT) AS
(SELECT DISTINCT TRUNC(DATE '2019-01-21' - LEVEL + 1, 'W')
FROM DUAL CONNECT BY LEVEL <= 21
)
with
WKS(DT) AS
( SELECT DISTINCT TRUNC(sysdate - LEVEL + 1, 'W')
FROM DUAL CONNECT BY LEVEL <= trunc(sysdate) - add_months(trunc(sysdate), -3
)

Cumulatively adding up sales from last years closing balance to current years opening balances

I am trying to cumulatively add sales from last years closing balance bringing it forward to first month and continuing like this from month to month. For instance if we have the following data:
select 4 id, 'A' name, 'group1' group, '2015' year, '10' month, 20 sales from dual union all
select 5 id, 'C' name,'group2' group, '2015' year, '12' month, 89 sales from dual union all
select 13 id,'B' name, 'group2' group, '2016' year, '01' month, 10 sales from dual union all
select 14 id,'A' name, 'group3' group, '2016' year, '02' month, 8 sales from dual union all
select 15 id,'B' name, 'group1' group, '2016' year, '02' month, 16 sales from dual union all
select 16 id,'D' name,'group2' group, '2016' year, '04' month, 15 sales from dual union all
select 17 id,'D' name,'group4' group, '2016' year, '05' month, 23 sales from dual union all
select 18 id,'D' name,'group3' group, '2016' year, '06' month, 39 sales from dual union all
select 19 id,'D' name,'group3' group, '2016' year, '07' month, 45 sales from dual union all
select 20 id,'D' name,'group3' group, '2016' year, '08' month, 12 sales from dual union all
select 21 id,'D' name,'group4' group, '2016' year, '09' month, 20 sales from dual union all
select 22 id,'D' name,'group3' group, '2016' year, '10' month, 4 sales from dual union all
select 23 id,'D' name,'group3' group, '2016' year, '11' month, 98 sales from dual union all
select 24 id,'D' name,'group4' group, '2016' year, '12' month, 70 sales from dual
Note, for Year=2015 the closing balance for that year is month=12 balance which in this case is 89 for group2 and 20 for group1. If we are in 2016, I want the cumulative query to return something like this:
year, month, group, opening_balance, closing_balance
2016 01 group2 89 99 (89+10)
2016 02 group3 0 8 (0+8)
2016 02 group1 20 36 (20 + 16)
2016 04 group2 99 114 (99 + 15)
2016 05 group4 0 23 (0 + 23 - note group4 has no prior balances)
2016 06 group3 8 47 (8 + 39)
2016 07 group3 47 92 (47 + 45)
and so on
This looks like it needs involving the analytical function sum() over (partition by .... order by ... )
But I haven't figured out the correct way to do this.
Thanks in advance.
Oracle Setup:
CREATE TABLE sales ( id, name, grp, year, month, sales ) AS
SELECT 4, 'A', 'group1', '2015', '10', 20 FROM DUAL UNION ALL
SELECT 5, 'C', 'group2', '2015', '12', 89 FROM DUAL UNION ALL
SELECT 13, 'B', 'group2', '2016', '01', 10 FROM DUAL UNION ALL
SELECT 14, 'A', 'group3', '2016', '02', 8 FROM DUAL UNION ALL
SELECT 15, 'B', 'group1', '2016', '02', 16 FROM DUAL UNION ALL
SELECT 16, 'D', 'group2', '2016', '04', 15 FROM DUAL UNION ALL
SELECT 17, 'D', 'group4', '2016', '05', 23 FROM DUAL UNION ALL
SELECT 18, 'D', 'group3', '2016', '06', 39 FROM DUAL UNION ALL
SELECT 19, 'D', 'group3', '2016', '07', 45 FROM DUAL UNION ALL
SELECT 20, 'D', 'group3', '2016', '08', 12 FROM DUAL UNION ALL
SELECT 21, 'D', 'group4', '2016', '09', 20 FROM DUAL UNION ALL
SELECT 22, 'D', 'group3', '2016', '10', 4 FROM DUAL UNION ALL
SELECT 23, 'D', 'group3', '2016', '11', 98 FROM DUAL UNION ALL
SELECT 24, 'D', 'group4', '2016', '12', 70 FROM DUAL;
Query:
SELECT *
FROM (
SELECT year,
month,
grp,
COALESCE(
SUM( sales ) OVER ( PARTITION BY grp
ORDER BY dt
RANGE BETWEEN UNBOUNDED PRECEDING
AND INTERVAL '1' MONTH PRECEDING
),
0
) AS opening_balance,
SUM( sales ) OVER ( PARTITION BY grp
ORDER BY dt
RANGE BETWEEN UNBOUNDED PRECEDING
AND CURRENT ROW
) AS closing_balance
FROM (
SELECT s.*,
TO_DATE( year || month, 'YYYYMM' ) AS dt
FROM sales s
)
)
WHERE year = 2016
ORDER BY year, month, grp;
Query - Alternative without RANGE BETWEEN:
SELECT *
FROM (
SELECT year,
month,
grp,
SUM( sales ) OVER ( PARTITION BY grp
ORDER BY year, month )
- sales AS opening_balance,
SUM( sales ) OVER ( PARTITION BY grp
ORDER BY year, month
) AS closing_balance
FROM sales
)
WHERE year = 2016
ORDER BY year, month, grp;
Output:
YEAR MO GRP OPENING_BALANCE CLOSING_BALANCE
---- -- ------ --------------- ---------------
2016 01 group2 89 99
2016 02 group1 20 36
2016 02 group3 0 8
2016 04 group2 99 114
2016 05 group4 0 23
2016 06 group3 8 47
2016 07 group3 47 92
2016 08 group3 92 104
2016 09 group4 23 43
2016 10 group3 104 108
2016 11 group3 108 206
2016 12 group4 43 113

Oracle: Identifying peak values in a time series

I have following values in a column of table. there are two columns in table. The other column is having distinct dates in descending order.
3
4
3
21
4
4
-1
3
21
-1
4
4
8
3
3
-1
21
-1
4
The graph will be
I need only peaks higlighted in graph with circles in output
4
21
21
8
21
4
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE TEST ( datetime, value ) AS
SELECT DATE '2015-01-01', 3 FROM DUAL
UNION ALL SELECT DATE '2015-01-02', 4 FROM DUAL
UNION ALL SELECT DATE '2015-01-03', 3 FROM DUAL
UNION ALL SELECT DATE '2015-01-04', 21 FROM DUAL
UNION ALL SELECT DATE '2015-01-05', 4 FROM DUAL
UNION ALL SELECT DATE '2015-01-06', 4 FROM DUAL
UNION ALL SELECT DATE '2015-01-07', -1 FROM DUAL
UNION ALL SELECT DATE '2015-01-08', 3 FROM DUAL
UNION ALL SELECT DATE '2015-01-09', 21 FROM DUAL
UNION ALL SELECT DATE '2015-01-10', -1 FROM DUAL
UNION ALL SELECT DATE '2015-01-11', 4 FROM DUAL
UNION ALL SELECT DATE '2015-01-12', 4 FROM DUAL
UNION ALL SELECT DATE '2015-01-13', 8 FROM DUAL
UNION ALL SELECT DATE '2015-01-14', 3 FROM DUAL
UNION ALL SELECT DATE '2015-01-15', 3 FROM DUAL
UNION ALL SELECT DATE '2015-01-16', -1 FROM DUAL
UNION ALL SELECT DATE '2015-01-17', 21 FROM DUAL
UNION ALL SELECT DATE '2015-01-18', -1 FROM DUAL
UNION ALL SELECT DATE '2015-01-19', 4 FROM DUAL
Query 1:
SELECT datetime, value
FROM (
SELECT datetime,
LAG( value ) OVER ( ORDER BY datetime ) AS prv,
value,
LEAD( value ) OVER ( ORDER BY datetime ) AS nxt
FROM test
)
WHERE ( prv IS NULL OR prv < value )
AND ( nxt IS NULL OR nxt < value )
Results:
| DATETIME | VALUE |
|---------------------------|-------|
| January, 02 2015 00:00:00 | 4 |
| January, 04 2015 00:00:00 | 21 |
| January, 09 2015 00:00:00 | 21 |
| January, 13 2015 00:00:00 | 8 |
| January, 17 2015 00:00:00 | 21 |
| January, 19 2015 00:00:00 | 4 |
So the peak is defined as the previous value and next value being less than the current value, and you can retrieve the previous an next using LAG() and LEAD() functions.
You really need some other column (e.g. my_date) to define the order of the rows, then you can:
select my_date,
value
from (select value,
lag(value ) over (order by my_date) lag_value,
lead(value) over (order by my_date) lead_value
from my_table)
where value > coalesce(lag_value , value - 1) and
value > coalesce(lead_value, value - 1);
This would not allow for a "double-peak" such as:
1,
15,
15,
4
... for which much more complex logic would be needed.
Just for completeness the row pattern matching example:
WITH source_data(datetime, value) AS (
SELECT DATE '2015-01-01', 3 FROM DUAL UNION ALL
SELECT DATE '2015-01-02', 4 FROM DUAL UNION ALL
SELECT DATE '2015-01-03', 3 FROM DUAL UNION ALL
SELECT DATE '2015-01-04', 21 FROM DUAL UNION ALL
SELECT DATE '2015-01-05', 4 FROM DUAL UNION ALL
SELECT DATE '2015-01-06', 4 FROM DUAL UNION ALL
SELECT DATE '2015-01-07', -1 FROM DUAL UNION ALL
SELECT DATE '2015-01-08', 3 FROM DUAL UNION ALL
SELECT DATE '2015-01-09', 21 FROM DUAL UNION ALL
SELECT DATE '2015-01-10', -1 FROM DUAL UNION ALL
SELECT DATE '2015-01-11', 4 FROM DUAL UNION ALL
SELECT DATE '2015-01-12', 4 FROM DUAL UNION ALL
SELECT DATE '2015-01-13', 8 FROM DUAL UNION ALL
SELECT DATE '2015-01-14', 3 FROM DUAL UNION ALL
SELECT DATE '2015-01-15', 3 FROM DUAL UNION ALL
SELECT DATE '2015-01-16', -1 FROM DUAL UNION ALL
SELECT DATE '2015-01-17', 21 FROM DUAL UNION ALL
SELECT DATE '2015-01-18', -1 FROM DUAL UNION ALL
SELECT DATE '2015-01-19', 4 FROM DUAL
)
SELECT *
FROM
source_data MATCH_RECOGNIZE (
ORDER BY datetime
MEASURES
LAST(UP.datetime) AS datetime,
LAST(UP.value) AS value
ONE ROW PER MATCH
PATTERN ((UP DOWN) | UP$)
DEFINE
DOWN AS DOWN.value < PREV(DOWN.value),
UP AS UP.value > PREV(UP.value)
)
ORDER BY
datetime
There is a much more sophisticated method available in Oracle 12c, which is to use pattern matching SQL.
http://docs.oracle.com/database/121/DWHSG/pattern.htm#DWHSG8966
It would be overkill for a situation like this, but if you needed more complex patterns matched, such as W shaped patterns, then it would be worth investigating.
Using LAG function you can compare values from different rows. I assume the resultset you showed is ordered by another column named position.
select value
from
(select value,
lag(value,-1) over (order by position) prev,
lag(value,1) over (order by position) next
from table)
where value > prev
and value > next

Store multiple results from one field in new columns in SQL

Current query returns 3 lines for one invoice if there are 3 denial codes and more if there are multiple denial codes and multiple denial dates. I am trying to create a column for each denial code so all results can be on one line. The requirements from the client is for each denial to be in its own column so I am unable to use the listagg function.
The results should looks like the below:
office, invoice, denial date, denial code 1, denial code 2, denial code 3, denial date2, denial code 1...etc
Oracle database. Current code:
SELECT
A.OFFICE_NBR,
A.INV_NBR,
TO_DATE(A.CRTD_DT,'MM/DD/YYYY') AS CARC_DT,
A.CLM_ID,
A.CLM_LN_ID,
A.RSN_CD
FROM DENIALS A
WHERE A.OFFICE_NBR = '1234'
AND A.INV_NBR = '123456'
I took a guess at some test data but this should get you going. Please add some before and after data, that helps a lot to understand what you are trying to do, and allows for building a more realistic solution.
Anyway the caveat is you have to define ahead of time a DENIAL_CD_X column for each possible denial code. I will be interested to see if someone can come up with a dynamic solution for the denial_cd columns.
SQL> with DENIALS(OFFICE_NBR, INV_NBR, CRTD_DT, RSN_CD) as (
2 select '11', '1111', '07/31/2015', '1' from dual
3 union
4 select '11', '1111', '07/31/2015', '99' from dual
5 union
6 select '11', '1111', '07/31/2015', '50' from dual
7 union
8 select '11', '1113', '06/01/2014', '34' from dual
9 union
10 select '11', '1113', '06/01/2014', '71' from dual
11 union
12 select '32', '3232', '06/21/2015', '34' from dual
13 union
14 select '32', '3232', '07/31/2015', '99' from dual
15 )
16 select OFFICE_NBR, INV_NBR, TO_DATE(CRTD_DT,'MM/DD/YYYY') AS CARC_DT,
17 DENIAL_CD_1, DENIAL_CD_2, DENIAL_CD_3
18 from
19 (
20 select OFFICE_NBR, INV_NBR, CRTD_DT, rsn_cd,
21 row_number() over(partition by OFFICE_NBR, INV_NBR, CRTD_DT order by OFFICE_NBR, INV_NBR, CRTD_DT, rsn_cd) rn
22 from DENIALS
23 )
24 pivot
25 (
26 max(rsn_cd)
27 for rn in ('1' as DENIAL_CD_1, '2' as DENIAL_CD_2, '3' as DENIAL_CD_3)
28 )
29 order by OFFICE_NBR, INV_NBR, CRTD_DT;
OFFICE_NBR INV_NBR CARC_DT DENIAL_CD_1 DENIAL_CD_2 DENIAL_CD_3
---------- ------- ---------- ----------- ----------- -----------
11 1111 31-JUL-15 1 50 99
11 1113 01-JUN-14 34 71
32 3232 21-JUN-15 34
32 3232 31-JUL-15 99
SQL>