Calculate the percentage change with respect to the previous year, quarter and category - sql

Using ORACLE SQL, I have a query that gives me the output in the following table(posting an image). However, I need to figure out a way to get the percentage change between year, quarter and metric in an additional column.
Example: Year 2022, Q1, apple against Year 2021, Q1, apple.
I'm relatively new to SQL so I'm not sure if I need to sort the output differently to use the function LEAD, or if there is a better way to do it in general.
My current query with my attempt at the percent change with lead (that didn't work) is like this:
`
SELECT s.YEAR
, t.quarter
, CASE WHEN fruits IN ('tangerine','lemon') THEN 'orange'
ELSE fruits
END metric
, COUNT(DISTINCT s.ID) AS COUNT
-- , ROUND((COUNT(UNIQUE s.ID) - LEAD(COUNT(UNIQUE s.ID)) OVER (ORDER BY t.quarter))/COUNT(UNIQUE s.ID)*100,2) pct_change
FROM s
JOIN sc
ON S.KEY = SC.KEY
JOIN c
ON SC.KEY = C.KEY
JOIN t
ON s.quarter = t.quarter
WHERE S.YEAR BETWEEN '2021' AND '2022'
AND s.quarter IN ('1','2','3')
GROUP BY S.YEAR
, t.quarter
,CASE WHEN fruits IN ('tangerine','lemon') THEN 'orange'
ELSE fruits
END
`

With my query as (
...your original query as above goes here...
)
Select curr.year, curr.quarter, curr.metric
, curr.count as current_count
, Case When prior.count is null then prior.count else if prior.count = 0 then null else (curr.count - prior.count)/ prior.count*100 as pct_chg
From my_query curr Left Outer Join my_query prior
On curr.year-1 = prior.year and
curr.qtr=prior.qtr and
curr.metric=prior.metric

Related

SUM with left outer join gets inflated result

The following query gives me the MRR (monthly recurring revenue) for my customer:
with dims as (
select distinct subscription_id, country_name, product_name from revenue
where site_id = '18XLsHIVSJg' and subscription_id is not null
)
select to_date('2022-07-01') as occurred_date,
count(distinct srm.subscription_id) as subscriptions,
count(distinct srm.receiver_contact) as subscribers,
sum(srm.baseline_mrr) as mrr_srm
from subscription_revenue_mart srm
join dims d on d.subscription_id = srm.subscription_id
where srm.site_id = '18XLsHIVSJg'
-- MRR as of the day before ie June 30th
and to_date(srm.creation_date) < '2022-07-01'
-- Counting the subscriptions active after July 1st
and ((srm.subscription_status = 'SUBL.A') or
-- Counting the subscriptions canceled/deactivated after July 1st
(srm.subscription_status = 'SUBL.C' and (srm.deactivation_date >= '2022-07-01') or (srm.canceled_date >= '2022-07-01')) ) group by 1;
I get a total of $5922.15 but I need to add data from another table to capture upgrades/downgrades a customer makes on a product subscription. Using the same approach as above, I can query my "change" table thusly:
select subscription_id, sum(mrr_change_amount) mrr_change_amount,max(subscription_event_date) subscription_event_date from subscription_revenue_mart_change srmc
where site_id = '18XLsHIVSJg'
and to_date(srmc.creation_date) < '2022-07-01'
and ((srmc.subscription_status = 'SUBL.A')
or (srmc.subscription_status = 'SUBL.C' and (srmc.deactivation_date >= '2022-07-01') or (srmc.canceled_date >= '2022-07-01')))
group by 1;
I get a total of $3635.47
When I combine both queries into one, I get an inflated result:
with dims as (
select distinct subscription_id, country_name, product_name from revenue
where site_id = '18XLsHIVSJg' and subscription_id is not null
),
change as (
select subscription_id, sum(mrr_change_amount) mrr_change_amount,
-- there can be multiple changes per subscription
max(subscription_event_date) subscription_event_date from subscription_revenue_mart_change srmc
where site_id = '18XLsHIVSJg'
and to_date(srmc.creation_date) < '2022-07-01'
and ((srmc.subscription_status = 'SUBL.A')
or (srmc.subscription_status = 'SUBL.C' and (srmc.deactivation_date >= '2022-07-01') or (srmc.canceled_date >= '2022-07-01')))
group by 1
)
select to_date('2022-07-01') as occurred_date,
count(distinct srm.subscription_id) as subscriptions,
count(distinct srm.receiver_contact) as subscribers,
-- See comment RE: LEFT OUTER join
sum(coalesce(c.mrr_change_amount,srm.baseline_mrr)) as mrr
from subscription_revenue_mart srm
join dims d
on d.subscription_id = srm.subscription_id
-- LEFT OUTER join required for customers that never made a change
left outer join change c
on srm.subscription_id = c.subscription_id
where srm.site_id = '18XLsHIVSJg'
and to_date(srm.creation_date) < '2022-07-01'
and ((srm.subscription_status = 'SUBL.A')
or (srm.subscription_status = 'SUBL.C' and (srm.deactivation_date >= '2022-07-01') or (srm.canceled_date >= '2022-07-01'))) group by 1;
It should be $9557.62 ie (5922.15 + $3635.47) but the query outputs $16116.91, which is wrong.
I think the explode-implode syndrome may cause this.
I had designed my "change" CTE to prevent this by aggregating all the relevant fields but it's not working.
Can someone provide pointers on the best way to work around this issue?
It would help if you gave us sample data too, but I see a problem here:
sum(coalesce(c.mrr_change_amount,srm.baseline_mrr)) as mrr
Why COALESCE? That will give you one of the 2 numbers, but I guess what you want is:
sum(ifnull(c.mrr_change_amount, 0) + srm.baseline_mrr) as mrr
That's the best I can offer with what you've given us.

T-SQL Using CTE to aggregate totals for matching and non-matching periods

Matched Sales are provided by the join, It's getting the unmatched that is eluding me.
CTE
With PriorSalesCTE
(
Item
Variant,
Sum(sales)
Date between 7/1/2020 and 7/5/2020
),
CurrentSalesCTE
(
Item
Variant,
Sum(sales)
Date between 7/1/2021 and 7/5/2021
)
Select
SUM(cs.Sales) ‘MatchedSales’
FROM PriorSalesCTE ps join CurrentSalesCTE ps
ON cs.Item = ps.Item
And cs.Variant = ps.Variant
Now I need the empty spaces on both sides
I need the sales for items sold in 2020 but not sold in 2021 – Lost Sales
Conversely, sales for 2021 that did not sell in 2020 – New Sales.
I tried adding these in the CTE as separate sections of the CTE, but the join doesn’t give me what I need.
Any suggestions? Is the CTE simply preventing me for getting everything and maybe add a UNION ALL query to get the unmatched values?
For your actual query, you could use a FULL JOIN, which will give you the results from either side also.
But I think there is another solution: you don't need to join separate queries for this, you can just use conditional aggregation
WITH SalesByItem AS (
SELECT
t.Item,
t.Variant
Sales2020 = SUM(CASE WHEN Date BETWEEN '20200701' and '20200705' THEN t.Sales END),
Sales2021 = SUM(CASE WHEN Date BETWEEN '20210701' and '20210705' THEN t.Sales END)
FROM YourTable t
WHERE (Date BETWEEN '20200701' and '20200705'
OR Date BETWEEN '20210701' and '20210705')
GROUP BY
t.Item,
t.Variant
)
SELECT
NewSales = SUM(CASE WHEN Sales2020 IS NULL THEN Sales2021 END),
MatchedSales = SUM(CASE WHEN Sales2020 IS NOT NULL AND Sales2021 IS NOT NULL THEN Sales2021 END),
LostSales = SUM(CASE WHEN Sales2021 IS NULL THEN Sales2020 END)
FROM SalesByItem s;

SUM and Grouping by date and material

Still learning SQL forgive me.
I have 3 tables. a material table, a material_req table and a material_trans table. I want to group by material and then group columns by year.
so it would be [material, 2019, 2018, 2017, 2016, total (total being the total qty used for each material.
I have tried to place the date in the select statement, and grouped by the date also. but then the returned result is a lot of the same material with a lot of dates. I only need the year. maybe try the same and return just the year?
SELECT material_req.Material
-- , Material_Trans_Date
, SUM(-1 * material_trans.Quantity) AS 'TOTAL'
,Standard_Cost
FROM
Material_Req inner join Material_Trans
ON
Material_Req.Material_Req = Material_Trans.Material_Req
LEFt JOIN Material
ON
Material.Material = Material_Req.Material
WHERE
material_trans.Material_Trans_Date between '20180101' AND GETDATE()
-- Material_Trans_Date between '20180101' AND '20181231'
-- Material_Trans_Date between '20170101' AND '20171231'
-- Material_Trans_Date between '20160101' AND '20161231'
GROUP BY
material_req.Material ,Standard_Cost
ORDER BY
Material_Req.Material, Standard_Cost
expected results should by grouped by material, 2019, 2018, 2017,2016, Standard_Cost. the years column will have the sum of qty for each material for that year.
results look like this current_results
If you are using SQL Server then you might try this:
SELECT material_req.Material
, SUM(CASE WHEN DATEPART(YEAR, Material_Trans_Date) = '2019' THEN material_trans.Quantity ELSE 0 END) [2019 TOTAL]
, SUM(CASE WHEN DATEPART(YEAR, Material_Trans_Date) = '2018' THEN material_trans.Quantity ELSE 0 END) [2018 TOTAL]
,Standard_Cost
FROM
Material_Req inner join Material_Trans
ON
Material_Req.Material_Req = Material_Trans.Material_Req
LEFt JOIN Material
ON
Material.Material = Material_Req.Material
WHERE
material_trans.Material_Trans_Date between '20180101' AND GETDATE()
GROUP BY
material_req.Material ,Standard_Cost
ORDER BY
Material_Req.Material, Standard_Cost

Bring through a newly created calculated column in another query

I have 2 separate queries below which run correctly.Now I've created a calculated column to provide a count of working days by YMs and would like to bring this through to query1(the join would be query1.Period = query2.Yms)
please see the query and outputs below.
SELECT Client, ClientGroup, Type, Value, Period, PeriodName, PeriodNumber, ClientName
FROM metrics.dbo.vw_KPI_001_Invoice
select YMs,sum(case when IsWorkDay = 'X' then 1 else 0 end) from IESAONLINE.Dbo.DS_Dates
where Year > '2013'
group by YMs
Query 1
Client ClientGroup Type Value Period PeriodName PeriodNumber ClientName
0LG0 KarroFoods Stock 5691.68 201506 Week 06 2015 35 Karro Foods Scunthorpe
Query 2
YMs (No column name)
201401 23
Would the following work:
SELECT Client, ClientGroup, Type, Value, Period, PeriodName, PeriodNumber, ClientName, cnt
FROM metrics.dbo.vw_KPI_001_Invoice q1
INNER JOIN (select YMs,sum(case when IsWorkDay = 'X' then 1 else 0 end) as cnt from IESAONLINE.Dbo.DS_Dates
where Year > '2013'
group by YMs ) q2 ON q1.Period = q2.YMs
If a value isn't always available then you might consider changing the INNER JOIN to an OUTER JOIN.

Cannot group SQL results correctly

Hi i have a query which i need to show the number of transactions a user made,per day with the EUR equivalent of each transaction.
The query below does do that (find the eur equivalent by getting an average rate) but because the currencies are different i get the results by currency instead and not by total. what the query returns is:
Numb Transactions,Date, userid,transaction_type,total value (per currency),eur_equiv
1 12/12, 2, test 5 10
2 12/12,2, test 2 2
whereas i want it to return
Numb Transactions,Date, userid,transaction_type,total value (per currency),eur_equiv
1 12/12, 2, test 7 12
the query is shown below
SELECT COUNT(DISTINCT(ot.ID)) AS 'TRANSACTION COUNTER'
,CONVERT(VARCHAR(10) ,ot.CREATED_ON ,103) AS [DD/MM/YYYY]
,lad.ci
,ot.TRA_TYPE
,c.C_CODE
,CASE
WHEN op.CURRENCY_ID='CURRENCY-002' THEN SUM(CAST(op.IT_AMOUNT AS MONEY))
/(
SELECT AVG(CAST(cr.B_RATE AS MONEY)) AS AVG_RATE
FROM C_RATE cr
WHERE cr.CURRENCY_ID = 'CURRENCY-002'
)
WHEN op.CURRENCY_ID='-CURRENCY-005' THEN SUM(CAST(op.IT_AMOUNT AS MONEY))
/(
SELECT AVG(CAST(cr.B_RATE AS MONEY)) AS AVG_RATE
FROM C_RATE cr
WHERE cr.CURRENCY_ID = 'CURRENCY-005'
)
WHEN op.CURRENCY_ID='CURRENCY-006' THEN SUM(CAST(op.IT_AMOUNT AS MONEY))
/(
SELECT AVG(CAST(cr.B_RATE AS MONEY)) AS AVG_RATE
FROM C_RATE cr
WHERE cr.CURRENCY_ID = 'CURRENCY-006'
)
ELSE '0'
END AS EUR_EQUIVAL
FROM TRANSACTION ot
INNER JOIN PAYMENT op
ON op.ID = ot.ID
INNER JOIN CURRENCY c
ON op.CURRENCY_ID = c.ID
INNER JOIN ACCOUNT a
ON a.ID = ot.ACCOUNT_ID
INNER JOIN ACCOUNT_DETAIL lad
ON lad.A_NUMBER = a.A_NUMBER
INNER JOIN CUST cus
ON lad.CI = cus.CI
WHERE ot.TRA_TYPE_ID IN ('INBANK-TYPE'
,'IN-AC-TYPE'
,'DOM-TRANS-TYPE')
AND ot.STATUS_ID = 'COMPLETED'
AND cus.BRANCH IN ('123'
,'456'
,'789'
,'789')
GROUP BY
lad.CI
,CONVERT(VARCHAR(10) ,ot.CREATED_ON ,103)
,c.C_CODE
,op.CURRENCY_ID
,ot.TRAN_TYPE_ID
HAVING SUM(CAST(op.IT_AMOUNT AS MONEY))>'250000.00'
ORDER BY
CONVERT(VARCHAR(10) ,ot.CREATED_ON ,103) ASC
SELECT MIN([Numb Transactions]
, Date
, UserID
, Transaction_type
, SUM([Total Value]
, SUM([Eur Equiv]
FROM (
... -- Your current select (without order by)
) q
GROUP BY
Date
, UserId
, Transaction_type
The problem resides most likely in a double row in your joins. What I do, is Select * first, and see what columns generate double rows. You might need to adjust a JOIN relationship for the double rows to disappear.
Without any resultsets, it's very hard to reproduce the error you are getting.
Check for the following things:
Select * returns only double rows on the data i will merge with an aggregate function. If the answer here is "NO", you will need to alter a JOIN relationship with a Subselect. I am thinking of that Account and Account detail table.
certain joins can create duplicate rows if the join cannot be unique enough. Maybe you will have to join on multiple things here, for example JOIN table1 ON table1.ID = table2.EXT_ID and table1.Contact = table2.Contact
Couple of things:
1) Consider using a function for:
SELECT AVG(CAST(cr.B_RATE AS MONEY)) AS AVG_RATE
FROM C_RATE cr
WHERE cr.CURRENCY_ID = 'CURRENCY-002' with the currencyID as a parameter
2) Would grouping sets work here?
3) was the sum on the case or on the individual whens?
sum(CASE ..) vs sum(cast(op.IT_Amount as money)