How to display Max Date amount in another column? - sql

I want to derive column MSP_ADULT and MSP_CHILD based on the LATEST date record ADULT_AMT to be in MSP_ADULT and CHILD_AMT to be in MSP_CHILD column.
I want to my out like below.
END_DATE ADULT_AMT CHILD_AMT MSP_ADULT MSP_CHILD
09/01/2017 100 50 180 80
10/01/2018 200 100 180 80
04/05/2019 300 90 180 80
08/20/2019 180 80 180 80
Here is the code I am running, but it is not working.
SELECT
AL1.END_DATE as BROCHURE_EFFECTIVE_END_DATE,
PORT_CODE,
AL7.PRODUCT_LEGACY_CODE as COMPONENT_CODE,
AL3.PRICE_GUEST_AGE_GROUP ,
MAX(CASE WHEN AL3.PRICE_GUEST_AGE_GROUP = 'ADULT' THEN AL3.PRICE_AMOUNT ELSE 0 END) ADULT_AMT,
MAX(CASE WHEN AL3.PRICE_GUEST_AGE_GROUP = 'CHILD' THEN AL3.PRICE_AMOUNT ELSE 0 END) CHILD_AMT,
MAX(CASE WHEN AL3.PRICE_GUEST_AGE_GROUP = 'ADULT' THEN AL3.PRICE_AMOUNT ELSE 0 END)
OVER (PARTITION BY PORT_CODE, AL7.PRODUCT_LEGACY_CODE --order by AL1.END_DATE desc
)AS MSP_ADULT,
MAX(CASE WHEN AL3.PRICE_GUEST_AGE_GROUP = 'CHILD' THEN AL3.PRICE_AMOUNT ELSE 0 END)
OVER (PARTITION BY PORT_CODE, AL7.PRODUCT_LEGACY_CODE order by AL1.END_DATE desc ) AS MSP_child
FROM RATE_PLAN AL1
inner join PRICE AL3
on (AL3.RATE_PLAN_SK=AL1.RATE_PLAN_SK and AL3.rate_plan_sk <>-1 )
Inner join PRODUCT_VARIANT AL7
ON (AL3.PRODUCT_CODE = AL7.PRODUCT_LEGACY_CODE and AL7.CATALOG_VERSION='Online')
INNER JOIN PRODUCT_OFFERING AL14
ON (AL14.PRODUCT_CODE = AL7.PRODUCT_LEGACY_CODE and AL7.CATALOG_VERSION='Online')
inner join PORT
on (AL7.FULFILLMENT_LOCATION = PORT_CODE)
where TO_CHAR(AL1.END_DATE,'YYYY') >= TO_CHAR(SYSDATE,'YYYY')
and AL3.use_for_pricing_flag is not null
and port_code='HKT' and AL7.product_LEGACY_code='PK83'
GROUP BY
PORT_CODE,
PRICE_GUEST_AGE_GROUP,
AL7.PRODUCT_LEGACY_CODE,
AL1.END_DATE
)

So you want the last values of those conditional MAX's.
Try FIRST_VALUE with a descending order.
...
FIRST_VALUE(ADULT_AMT)
OVER (PARTITION BY PORT_CODE, COMPONENT_CODE ORDER BY BROCHURE_EFFECTIVE_END_DATE DESC) AS MSP_ADULT,
FIRST_VALUE(CHILD_AMT)
OVER (PARTITION BY PORT_CODE, COMPONENT_CODE ORDER BY BROCHURE_EFFECTIVE_END_DATE DESC) AS MSP_CHILD
...
Note that there's also the LAST_VALUE window function, but that one can sometimes be misleading.

Related

How to get difference in value over a sliding time window?

I'm attempting to write a SQL query which returns every product where the most recent price on an order within the last 30 days is different than the most recent price in the previous 30 days, and that calculated variance. I'm currently using PostgreSQL 11.
Data Model
Right now, the data is structured into three tables: orders, products, and a pivot table, order_product. Here is the simplified version of the table structure:
Orders
id
order_date
1
2022-01-15
2
2022-02-15
3
2022-03-08
Products
id
name
1
Some product
2
Another product
3
Yet another product
Order_Product
order_id
product_id
unit_price
1
1
10
1
2
20
1
3
10
2
1
12
2
2
20
2
3
5
3
1
15
Desired Output
The desired output would be something like the following:
id
name
order_date
latest_unit_price
previous_unit_price
variance
1
Some product
2022-03-08
15
10
5
3
Yet another product
2022-02-15
5
10
-5
What I've done so far
I've been able to write a join that combines the Orders and Products via the order_product table, within the 60-day window, which is seemingly the easy part:
SELECT
"products"."id",
"products"."name",
"order_product"."unit_price",
"orders"."order_date"
FROM
products
JOIN order_product ON products.id = order_product.product_id
JOIN orders ON order_product.order_id = orders.id
WHERE
order_date BETWEEN now() - INTERVAL '60 days'
AND now()
I've been trying to work with RANK() and LAG(); however, where I'm getting stuck is being able to find the rank the rows within the 30-day time windows, and then calculate the variance between the two windows.
Any help would be much appreciated!
Update: Added solution
Building off of the answer by D-Shih, I had to tweak this to work based on the time window starting from the current date:
WITH CTE AS (
SELECT
"products"."id",
"products"."name",
"order_product"."unit_price",
"orders"."order_date"
FROM
products
JOIN order_product ON products.id = order_product.product_id
JOIN orders ON order_product.order_id = orders.id
WHERE
order_date BETWEEN now() - INTERVAL '60 days' AND now()
),
CTE2 AS (
SELECT
*,
EXTRACT(DAYS FROM now() - order_date :: timestamp) gap_days
FROM
CTE
),
CTE3 AS (
SELECT
*,
(CASE WHEN gap_days < 30 THEN 1 ELSE 0 END) grp
FROM
CTE2
)
SELECT
id,
name,
MAX(CASE WHEN grp = 1 THEN order_date END) order_date,
MAX(CASE WHEN grp = 1 THEN unit_price END) latest_unit_price,
MAX(CASE WHEN grp = 0 THEN unit_price END) previous_unit_price,
SUM(CASE WHEN grp = 1 THEN unit_price ELSE - unit_price END) variance
FROM
(
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY ID, grp ORDER BY order_date DESC) rn
FROM
CTE3
) t1
WHERE
rn = 1
GROUP BY
id,
name
HAVING
MAX(CASE WHEN grp = 1 THEN unit_price END) <> MAX(CASE WHEN grp = 0 THEN unit_price END)
sqlfiddle
You can try to use EXTRACT with LAG window function to get days difference from order_date and previous order_date each productId.
Then use SUM aggregate condition window function to calculate the group
grp = 0 within the last 30 days
grp = 1 most recent price in the previous 30 days,
the query would be look like as below.
WITH CTE AS (
SELECT "products"."id",
"products"."name",
"order_product"."unit_price",
"orders"."order_date"
FROM
products
JOIN order_product ON products.id = order_product.product_id
JOIN orders ON order_product.order_id = orders.id
WHERE
order_date BETWEEN now() - INTERVAL '60 days'
AND now()
), CTE2 AS (
SELECT *,EXTRACT(DAYS FROM order_date - LAG(order_date,1,order_date) OVER(PARTITION BY id ORDER BY order_date)) gap_seconds
FROM CTE
), CTE3 AS (
SELECT *,(CASE WHEN SUM(gap_seconds) OVER(PARTITION BY id ORDER BY order_date) > 30 THEN 1 ELSE 0 END) grp
FROM CTE2
)
SELECT id,
name,
MAX(CASE WHEN grp = 1 THEN order_date END) order_date,
MAX(CASE WHEN grp = 1 THEN unit_price END) latest_unit_price,
MAX(CASE WHEN grp = 0 THEN unit_price END) previous_unit_price,
SUM(CASE WHEN grp = 1 THEN unit_price ELSE - unit_price END) variance
FROM (
SELECT *,ROW_NUMBER() OVER(PARTITION BY ID,grp ORDER BY order_date DESC) rn
FROM CTE3
) t1
WHERE rn = 1
GROUP BY id,
name
HAVING MAX(CASE WHEN grp = 1 THEN unit_price END) <> MAX(CASE WHEN grp = 0 THEN unit_price END)
sqlfiddle

Select data where sum for last 7 from max-date is greater than x

I have a data set as such:
Date Value Type
2020-06-01 103 B
2020-06-01 100 A
2020-06-01 133 A
2020-06-11 150 A
2020-07-01 1000 A
2020-07-21 104 A
2020-07-25 140 A
2020-07-28 1600 A
2020-08-01 100 A
Like this:
Type ISHIGH
A 1
B 0
Here's the query i tried,
select type, case when sum(value) > 10 then 1 else 0 end as total_usage
from table_a
where (select sum(value) as usage from tableA where date = max(date)-7)
group by type, date
This is clearly not right. What is a simple way to do this?
It is a simply group by except that you need to be able to access max date before grouping:
select type
, max(date) as last_usage_date
, sum(value) as total_usage
, case when sum(case when date >= cutoff_date then value end) >= 1000 then 'y' end as [is high!]
from t
cross apply (
select dateadd(day, -6, max(date))
from t as x
where x.type = t.type
) as ca(cutoff_date)
group by type, cutoff_date
If you want just those two columns then a simpler approach is:
select t.type, case when sum(value) >= 1000 then 'y' end as [is high!]
from t
left join (
select type, dateadd(day, -6, max(date)) as cutoff_date
from t
group by type
) as a on t.type = a.type and t.date >= a.cutoff_date
group by t.type
Find the max date by type. Then used it to find last 7 days and sum() the value.
with
cte as
(
select [type], max([Date]) as MaxDate
from tableA
group by [type]
)
select c.[type], sum(a.Value),
case when SUM(a.Value) > 1000 then 1 else 0 end as ISHIGH
from cte c
inner join tableA a on a.[type] = c.[type]
and a.[Date] >= DATEADD(DAY, -7, c.MaxDate)
group by c.[type]
This can be done through a cumulative total as follows:
;With CTE As (
Select [type], [date],
SUM([value]) Over (Partition by [type] Order by [date] Desc) As Total,
Row_Number() Over (Partition by [type] Order by [date] Desc) As Row_Num
From Tbl)
Select Distinct CTE.[type], Case When C.[type] Is Not Null Then 1 Else 0 End As ISHIGH
From CTE Left Join CTE As C On (CTE.[type]=C.[type]
And DateDiff(dd,CTE.[date],C.[date])<=7
And C.Total>1000)
Where CTE.Row_Num=1
I think you are quite close with you initial attempt to solve this. Just a tiny edit:
select type, case when sum(value) > 1000 then 1 else 0 end as total_usage
from tableA
where date > (select max(date)-7 from tableA)
group by type

Aging bucket in SQL query

I have a table which has member ID along with LM_Conversion_date and retired_date. I have managed to get the difference between two date but now i would like to have the aging bucket and reflect those membership number which falls under those bucket. Here is my table example and how i want to see the data,
Member_no LM_Conversion_date Retired_date Date_difference
100026 08/12/2017 31/12/2017 23
100114 31/08/2017 31/08/2017 0
100620 15/09/2017 30/09/2017 15
100726 10/01/2017 31/12/2016 -10
I want the output to be
All negative 0-15 15-30 >30
100726 100114 100026
100620
Any Help will be much appreciated
You can do this using conditional aggregation:
select max(case when grp = '<0' then member_no end) as all_negative,
max(case when grp = '<=15' then member_no end) as [0-15],
max(case when grp = '<=30' then member_no end) as [15-30],
max(case when grp = '>30' then member_no end) as [>30]
from (select t.*, v.grp,
row_number() over (partition by grp order by member_no) as seqnum
from t cross apply
(values (case when date_difference <= 0 then '<0'
when date_difference <= 15 then '<=15'
when date_difference <= 30 then '<=30'
else '>30'
end)
) v(grp)
) t
group by seqnum
order by seqnum;
The subquery basically enumerates the members in each group. These are aggregated into separate rows by the aggregation.

SQL Sum Group by Custom Group

I have generated the following query, and I want to sum the patient counts by the new IMS_CUST_ID_GRP I created (roll it up to this a remove the CUST_ID) in my query. How can i modify my query to return my desired result? I tried using an analytic function but I get an error.
SELECT (CASE WHEN SUM(NEW_PAT_CNT) = 1 THEN '1'
WHEN SUM(NEW_PAT_CNT) >=2 AND SUM(NEW_PAT_CNT) <=12 THEN '2-12'
WHEN SUM(NEW_PAT_CNT) >=13 AND SUM(NEW_PAT_CNT)<=24 THEN '13-24'
WHEN SUM(NEW_PAT_CNT) >=24 AND SUM(NEW_PAT_CNT) <=48 THEN '25-48'
WHEN SUM(NEW_PAT_CNT) >48 THEN '>48'
END) IMS_CUST_ID_GRP, SUM(NEW_PAT_CNT), CUST_ID
FROM DEXODS.OPUB_ONE_IMS_IDS_FACT fct,
DEXWHS.D_DATE dt,
DEXWHS.D_ACCOUNT_VEEVA ac
WHERE fct.DATE_DIM_KEY = dt.DATE_ID
AND fct.ACCOUNT_DIM_KEY = ac.ACCOUNT_DIM_KEY
AND NEW_PAT_CNT >0
AND dt.year in n'2016'
GROUP BY CUST_ID
Analytic function which returns an error
SELECT (CASE WHEN SUM(NEW_PAT_CNT) OVER (PARTITION BY CUST_ID) = 1 THEN '1'
WHEN SUM(NEW_PAT_CNT) OVER (PARTITION BY CUST_ID) >=2 AND SUM(NEW_PAT_CNT)OVER (PARTITION BY CUST_ID) <=12 THEN '2-12'
WHEN SUM(NEW_PAT_CNT) OVER (PARTITION BY CUST_ID) >=13 AND SUM(NEW_PAT_CNT)OVER (PARTITION BY CUST_ID) <=24 THEN '13-24'
WHEN SUM(NEW_PAT_CNT) OVER (PARTITION BY CUST_ID) >=24 AND SUM(NEW_PAT_CNT)OVER (PARTITION BY CUST_ID) <=48 THEN '25-48'
WHEN SUM(NEW_PAT_CNT) OVER (PARTITION BY CUST_ID) >48 THEN '>48'
END) IMS_CUST_ID_GRP, CUST_ID, SUM(NEW_PAT_CNT)
FROM DEXODS.OPUB_ONE_IMS_IDS_FACT fct,
DEXWHS.D_DATE dt,
DEXWHS.D_ACCOUNT_VEEVA ac
WHERE fct.DATE_DIM_KEY = dt.DATE_ID
AND fct.ACCOUNT_DIM_KEY = ac.ACCOUNT_DIM_KEY
AND NEW_PAT_CNT >0
GROUP BY CUST_ID
Dataset with CUST_ID included
IMS_CUST_ID_GRP SUM(NEW_PAT_CNT) CUST_ID
1 1 55671832
1 1 56097728
2-12 4 56106239
2-12 5 56728330
2-12 9 57590869
2-12 2 55609391
2-12 9 55880657
2-12 10 56339375
2-12 3 57371546
25-48 39 55891493
13-24 21 55714333
13-24 22 56542678
Desired Dataset rolled up to IMS_CUST_ID_GRP
IMS_CUST_ID_GRP SUM(NEW_PAT_CNT)
1 2
2-12 42
13-24 43
25-48 39
If you are doing a pivot query, using GROUP BY, then you don't need to use SUM() as an analytic function. Just use it normally:
SELECT CUST_ID,
SUM(CASE WHEN CUST_ID = 1 THEN NEW_PAT_CNT ELSE 0 END) AS '1',
SUM(CASE WHEN CUST_ID >= 2 AND CUST_ID <= 12 THEN NEW_PAT_CNT ELSE 0 END) AS '2-12',
SUM(CASE WHEN CUST_ID >= 13 AND CUST_ID <= 24 THEN NEW_PAT_CNT ELSE 0 END) AS '13-24',
SUM(CASE WHEN CUST_ID >= 24 AND CUST_ID <= 48 THEN NEW_PAT_CNT ELSE 0 END) AS '25-48',
SUM(CASE WHEN CUST_ID > 48 THEN NEW_PAT_CNT ELSE 0 END) AS '>48'
FROM DEXODS.OPUB_ONE_IMS_IDS_FACT fct
INNER JOIN DEXWHS.D_DATE dt
ON fct.DATE_DIM_KEY = dt.DATE_ID
INNER JOIN DEXWHS.D_ACCOUNT_VEEVA ac
ON fct.ACCOUNT_DIM_KEY = ac.ACCOUNT_DIM_KEY
WHERE NEW_PAT_CNT > 0 -- should not be necessary assuming count no less than zero
GROUP BY CUST_ID
Note that I have replaced your implicit joins with explicit INNER JOINs. As you may have heard, you should try to avoid putting commas in the FROM clause.

Oracle opening and closing balance - SQL or PL/SQL needed?

select year,
month ,
d.PROD_ID,
T.CUSTOMER_ID,
SUM(CASE WHEN D.OP_TYPE = 1 THEN d.qty END) EARNED,
SUM(CASE WHEN D.OP_TYPE = 2 THEN d.qty END) SPEND
FROM TXN_HEADER T ,
TXN_DETAIL d ,
CUSTOMER A,
PRODUCT e
WHERE T.AMOUNT > 0
AND A.TYPE = 0
AND T.CUSTOMER_ID = A.CUSTOMER_ID
AND T.TXN_PK = D.TXN_PK
and d.PROD_ID = e.PROD_ID
and e.unit = 0
group by year, month ,d.PROD_ID, T.CUSTOMER_ID
ORDER BY 1,2,3,4
Output is as follows (here opening and closing not generated by query, but I required that has to be from the query)
YEAR MONTH PROD CUSTOMER OPENING EARNED SPEND CLOSING
---- ----- ---- -------- ------- ------ ----- -------
2012 8 548 12033 0 8 2 6
2012 9 509 12033 0 24 0 24
2012 9 509 12047 0 14 0 14
2012 9 548 12033 6 1 0 7
2012 9 548 12047 0 1 0 1
I required to generate the output as above. Here PROD_ID,CUSTOMER_ID wise dynamically the prev closing balance to be populated as opening and it shoulde calculate closing balance (opening+earned-spend) monthwise,customer wise ,product wise. is it possible to write in SQL or need to go PL/SQL?
I'd use analytics, with PROD_ID and CUSTOMER_ID in the partition clause to avoid mixing products and customers.
WITH
MONTHLY_BALANCE AS
(
SELECT
YEAR,
MONTH,
D.PROD_ID,
T.CUSTOMER_ID,
SUM(CASE WHEN D.OP_TYPE = 1 THEN D.QTY ELSE NULL END) EARNED,
SUM(CASE WHEN D.OP_TYPE = 2 THEN D.QTY ELSE NULL END) SPEND,
FROM TXN_HEADER T
JOIN CUSTOMER A
ON T.CUSTOMER_ID = A.CUSTOMER_ID
JOIN TXN_DETAIL D
ON T.TXN_PK = D.TXN_PK
JOIN PRODUCT E
ON D.PROD_ID = E.PROD_ID
WHERE T.AMOUNT > 0
AND A.TYPE = 0
AND E.UNIT = 0
GROUP BY YEAR, MONTH, D.PROD_ID, T.CUSTOMER_ID
)
SELECT
YEAR,
MONTH,
PROD_ID,
CUSTOMER_ID,
SUM(NVL(EARNED, 0) - NVL(SPEND, 0)) OVER(PARTITION BY PROD_ID, CUSTOMER_ID ORDER BY YEAR, MONTH ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) OPENING,
EARNED,
SPEND,
SUM(NVL(EARNED, 0) - NVL(SPEND, 0)) OVER(PARTITION BY PROD_ID, CUSTOMER_ID ORDER BY YEAR, MONTH ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT_ROW) CLOSING
FROM MONTHLY_BALANCE
ORDER BY 1, 2, 3, 4
Your CASE needs an ELSE
CASE WHEN D.OP_TYPE = 1 THEN d.qty ELSE 0 END
Without the else the CASE will return NULL when D.OP_TYPE is not equal to 1, and anything+NULL=NULL. When your WHEN is not satisfied it returns NULL and that is why you do not see anything for those columns.
To get OPENING and CLOSING calculated as you may want to use analytic functions like LEAD and LAG.
Select year,month,prod_id,customer_id,
LAG(closing,1,0) OVER (order by year,month,prod_id,customer_id) as opening,
earned,spend
,(LAG(closing,1,0) OVER (order by year,month,prod_id,customer_id)+closing) as closing
from (WITH temp AS (select year,
month ,
d.PROD_ID,
T.CUSTOMER_ID,
0 OPEN,
SUM(CASE WHEN D.OP_TYPE = 1 THEN d.qty END) EARNED,
SUM(CASE WHEN D.OP_TYPE = 2 THEN d.qty END) SPEND,
0 CLOSE
FROM TXN_HEADER T ,
TXN_DETAIL d ,
CUSTOMER A,
PRODUCT e
WHERE T.AMOUNT > 0
AND A.TYPE = 0
AND T.CUSTOMER_ID = A.CUSTOMER_ID
AND T.TXN_PK = D.TXN_PK
and d.PROD_ID = e.PROD_ID
and e.unit = 0
group by year, month ,d.PROD_ID, T.CUSTOMER_ID
ORDER BY 1,2,3,4)
SELECT year,month,prod_id,customer_id,open,earned,spend,(open+earned-spend) as closing
from temp);