Aggregation per Date - sql

I have thousands of companies listed but for illustration; I cited 2 companies. I need to produce the column TotalSales in which values are the sum of sales per company , a year prior to its corresponding actual year & quarter.
Company Sales Quarter Year TotalSales QtrYr_Included
ABC Inc. 10,000 1 2010 null Q12009 - Q42009
ABC Inc. 50,000 2 2010 10,000 Q22009 - Q12010
ABC Inc. 35,000 3 2010 60,000 Q32009 - Q22010
ABC Inc. 15,000 4 2010 95,000 Q42009 - Q32010
ABC Inc. 5,000 1 2011 110,000 Q12010 - Q42010
ABC Inc. 10,000 2 2011 105,000 Q22010 - Q12011
SoKor Group 50,000 1 2009 null Q12008 - Q42008
SoKor Group 10,000 2 2009 50,000 Q22008 - Q12009
SoKor Group 10,000 3 2009 60,000 Q32008 - Q22009
SoKor Group 5,000 4 2009 70,000 Q42008 - Q32009
SoKor Group 15,000 1 2010 . Q12009 - Q42009
SoKor Group 20,000 3 2010 . Q22009 - Q12010
Thank you so much.

Here is one way to do it using Sum Over window aggregate
SELECT *,
Sum(sales)
OVER(
partition BY Company
ORDER BY [Year], [Quarter] ROWS BETWEEN 4 PRECEDING AND 1 PRECEDING)
FROM Yourtable
for Older versions
;WITH cte
AS (SELECT Row_number()OVER(partition BY Company ORDER BY [Year], [Quarter]) rn,*
FROM Yourtable a)
SELECT *
FROM cte a
CROSS apply (SELECT Sum (sales) Total_sales
FROM (SELECT TOP 4 sales
FROM cte b
WHERE a.Company = b.Company
AND b.rn < a.rn
ORDER BY [Year] DESC,
[Quarter] DESC)a) cs

#Prdp's solution is valid. However, it would show incorrect results when there are quarters missing for a given company as it will consider whatever row was available before the missing row. A way to avoid such situation is using derived tables to generate all combinations of year,quarter and company. Left joining the original table on to this result would generate 0 sales for the missing quarters. Then use the sum window function to get the sum of sales for the last 4 quarters for each row.
SELECT *
FROM
(SELECT C.COMPANY,
Y.[YEAR],
Q.[QUARTER],
T.SALES,
SUM(COALESCE(T.SALES,0)) OVER(PARTITION BY C.COMPANY
ORDER BY Y.[YEAR], Q.[QUARTER]
ROWS BETWEEN 4 PRECEDING AND 1 PRECEDING) AS PREV_4QTRS_TOTAL
FROM
(SELECT 2008 AS [YEAR]
UNION ALL SELECT 2009
UNION ALL SELECT 2010
UNION ALL SELECT 2011
UNION ALL SELECT 2012
UNION ALL SELECT 2013) Y --Add more years as required or generate them using a recursive cte or a tally table
CROSS JOIN
(SELECT 1 AS [QUARTER]
UNION ALL SELECT 2
UNION ALL SELECT 3
UNION ALL SELECT 4) Q
CROSS JOIN
(SELECT DISTINCT COMPANY
FROM T) C
LEFT JOIN T ON Y.[YEAR]=T.[YEAR]
AND Q.[QUARTER]=T.[QUARTER]
AND C.COMPANY=T.COMPANY
) X
WHERE SALES IS NOT NULL --to filter the result to include only rows from the original table
ORDER BY 1,2,3
Sample Demo

Related

What to use in place of union in above query i wrote or more optimize query then my given query without union and union all

I am counting the birthdays , sales , order in all 12 months from customers table in SQL server like these
In Customers table birth_date ,sale_date, order_date are columns of the table
select 1 as ranking,'Birthdays' as Type,[MONTH],TOTAL
from ( select DATENAME(month, birth_date) AS [MONTH],count(*) TOTAL
from customers
group by DATENAME(month, birth_date)
)x
union
select 2 as ranking,'sales' as Type,[MONTH],TOTAL
from ( select DATENAME(month, sale_date) AS [MONTH],count(*) TOTAL
from customers
group by DATENAME(month, sale_date)
)x
union
select 3 as ranking,'Orders' as Type,[MONTH],TOTAL
from ( select DATENAME(month, order_date) AS [MONTH],count(*) TOTAL
from customers
group by DATENAME(month, order_date)
)x
And the output is like these(just dummy data)
ranking
Type
MONTH
TOTAL
1
Birthdays
January
12
1
Birthdays
April
6
1
Birthdays
May
10
2
Sales
Febrary
8
2
Sales
April
14
2
Sales
May
10
3
Orders
June
4
3
Orders
July
3
3
Orders
October
6
3
Orders
December
17
I want to find count of these all these three types without using UNION and UNION ALL, means I want these data by single query statement (or more optimize version of these query)
Another approach is to create a CTE with all available ranking values ​​and use CROSS APPLY for it, as shown below.
WITH ranks(ranking) AS (
SELECT * FROM (VALUES (1), (2), (3)) v(r)
)
SELECT
r.ranking,
CASE WHEN r.ranking = 1 THEN 'Birthdays'
WHEN r.ranking = 2 THEN 'Sales'
WHEN r.ranking = 3 THEN 'Orders'
END AS Type,
DATENAME(month, CASE WHEN r.ranking = 1 THEN c.birth_date
WHEN r.ranking = 2 THEN c.sale_date
WHEN r.ranking = 3 THEN c.order_date
END) AS MONTH,
COUNT(*) AS TOTAL
FROM customers c
CROSS APPLY ranks r
GROUP BY r.ranking,
DATENAME(month, CASE WHEN r.ranking = 1 THEN c.birth_date
WHEN r.ranking = 2 THEN c.sale_date
WHEN r.ranking = 3 THEN c.order_date
END)
ORDER BY r.ranking, MONTH

Comparing data from two rows in a same sql table

I am trying to find out differences between two rows in a same table. Having trouble to find right query. For example, I have
Year Item Qty Amount
------------------------------
2014 Shoes 500 2500
2014 Ties 300 900
2014 Pants 200 4000
2015 Shoes 600 3000
2015 Ties 200 600
I am trying to find out what was the increased (or decreased) from previous year to this year. I will always have only two years to compare. The query result should look like following:
Items Qty Diff Amount Diff
------------------------------
Shoes 100 500
Ties (-100) (-300)
Pants Null Null
What should be the query look like?
If you want to include everything, then you can use FULL OUTER JOIN, if just the one with the earlier year, LEFT OUTER JOIN, if you want the one with both earlier and subsequent year, then INNER JOIN.
SELECT
T1.Item
, (T2.QTY-T1.QTY) AS [QTY Diff]
, (T2.Amount - T1.Amount) AS [Amount Diff]
FROM
<<Table>> T1
LEFT OUTER JOIN <<Table>> T2
ON T1.Item=T2.Item
AND T1.YEAR=(T2.YEAR-1);
1. Use LAG or LEAD
WITH tb(Year,Item,Qty,Amount) AS (
SELECT 2014,'Shoes',500,2500 UNION
SELECT 2014,'Ties',300,900 UNION
SELECT 2014,'Pants',200,4000 UNION
SELECT 2015,'Shoes',600,3000 UNION
SELECT 2015,'Ties',200,600
)
SELECT *,Qty-LAG(qty)OVER(PARTITION BY Item ORDER BY year) AS QtyDiff ,Amount-LAG(Amount)OVER(PARTITION BY Item ORDER BY year) AS AmountDiff
FROM tb
Year Item Qty Amount QtyDiff AmountDiff
----------- ----- ----------- ----------- ----------- -----------
2014 Pants 200 4000 NULL NULL
2014 Shoes 500 2500 NULL NULL
2015 Shoes 600 3000 100 500
2014 Ties 300 900 NULL NULL
2015 Ties 200 600 -100 -300
2.Cross or Outer Apply
WITH tb(Year,Item,Qty,Amount) AS (
SELECT 2014,'Shoes',500,2500 UNION
SELECT 2014,'Ties',300,900 UNION
SELECT 2014,'Pants',200,4000 UNION
SELECT 2015,'Shoes',600,3000 UNION
SELECT 2015,'Ties',200,600
)
SELECT t1.Year,t1.Item,t1.Qty- t2.qty AS DiffQty,t1.Amount-t2.Amount AS DiffAmount
FROM tb AS t1
OUTER APPLY (SELECT TOP 1 tt.qty,tt.Amount FROM tb AS tt WHERE tt.Year<t1.Year AND t1.Item=tt.Item ORDER BY tt.Year desc) AS t2
ORDER BY t1.Item,t1.Year
Using the lag function is the best approach to this.
SELECT [Year], [Item], [Qty], [Amount],
[Qty] - LAG([Qty]) OVER (PARTITION BY [Item] ORDER BY [Year]) [QtyDiff],
[Amount] - LAG([Amount]) OVER (PARTITION BY [Item] ORDER BY [Year]) [AmountDiff]
FROM [ItemTable] it
order BY [Year] DESC, [Item];
Hope this helps.
Here is the required query:
SET #YEAR1 = '2014';
SET #YEAR2 = '2015';
SELECT
Item,
if(count(*)>1,sum(if(Year=#YEAR2,Qty,-Qty)),NULL) as 'Qty Diff',
if(count(*)>1,sum(if(Year=#YEAR2,Amount,-Amount)),NULL) as 'Amount Diff'
FROM
table
WHERE
Year IN (#YEAR1,#YEAR2)
group by Item;

Running Total in Oracle SQL - insert missing rows

Let's assume I have following set of data in Oracle SQL database:
Product Year Month Revenue
A 2016 1 7
A 2016 5 15
After creating running totals with following code
select Product, Year, Month, Revenue,
sum(Revenue) over (partition by Product, Year order by Month) Revenue_Running
from exemplary_table
I receive following result:
Product Year Month Revenue Revenue_Running
A 2016 1 7 7
A 2016 5 15 22
Is there any way that I can get this:
Product Year Month Revenue Revenue_Running
A 2016 1 7 7
A 2016 2 (null) 7
A 2016 2 (null) 7
A 2016 4 (null) 7
A 2016 5 15 22
You need a calendar table and Left join with your exemplary_table
SELECT p.product,
c.year,
c.month,
COALESCE(revenue, 0),
Sum(revenue)OVER (partition BY p.product, c.year ORDER BY c.month) Revenue_Running
FROM calendar_table c
CROSS JOIN (SELECT DISTINCT product
FROM exemplary_table) p
LEFT JOIN exemplary_table e
ON c.year = e.year
AND e.month = c.month
WHERE c.dates >= --your start date
AND c.dates <= --your end date

Oracle PARTITION BY GROUPING_ID with SUM

I'm trying to implement a simple data warehouse analytic query, dealing with 'YEAR_VALUE', 'MONTH_VALUE' and a 'INVOICE_COST'
SELECT YEAR_VALUE, MONTH_VALUE, SUM (INVOICE_VALUE) AS TOTAL_INVOICE,
RANK () OVER (PARTITION BY GROUPING_ID (YEAR_VALUE, MONTH_VALUE) ORDER BY SUM (INVOICE_VALUE) DESC) AS YEAR_RANK,
RANK () OVER (PARTITION BY YEAR_VALUE, GROUPING_ID (MONTH_VALUE) ORDER BY SUM (INVOICE_VALUE) DESC) AS MONTH_RANK
FROM FACT_WH
JOIN TIME_WH ON TIME_WH.TIME_ID = FACT_WH.TIME_ID
GROUP BY (YEAR_VALUE, MONTH_VALUE);
The output is :
Output
'YEAR_RANK' should express year's total invoice value compared to other years, 2016 has a YEAR_RANK=1 and 2015 has a YEAR_RANK=2
The problem is that 'YEAR_RANK' has the values 1,2,3,4,5 it should be 1,1,2,2,1
I can't find the problem in my code, It's maybe in line #2, I tried everything and wasted much time already.
Thanks in advance.
A good approach, especially in case the query is complex and/or delivers confusing results is to divide the whole query in subqueries each solving a particular task.
In your case I'd recommend to first attack the join of the fact and dimension table and group by on YEAR and month to calculate the total_invoice
You get results such as
YEAR_VALUE MONTH_VALUE TOTAL_INVIOCE
---------- ----------- -------------
2016 3 29960
2016 1 10700
2015 11 5100
2015 8 1680
2016 2 800
Note that you don't need any GROUP BY extension such as GROUPING_ID, you'll solve everything using analytic functions
In the next step (using the previous result as a factored subquery) you calculate the year and months totals - using analytic version of SUM.
In the last step you calculate the RANK. Note that for the year you need
a DENSE_RANK, while otherwise you get 'skipped' ranks such as 1,3 (due to repeated records for one year).
The year_rank is not partitioned at all, the month_rankis partitioned on YEAR as you order the months within a year.
with data as (
-- perform join and group by in this subquery
select 2016 year_value, 3 month_value, 29960 total_invioce from dual union all
select 2016 year_value, 1 month_value, 10700 total_invioce from dual union all
select 2015 year_value, 11 month_value, 5100 total_invioce from dual union all
select 2015 year_value, 8 month_value, 1680 total_invioce from dual union all
select 2016 year_value, 2 month_value, 800 total_invioce from dual),
year_month as (
-- perform year and month summary here
select
year_value, month_value, total_invioce,
sum(total_invioce) over (partition by year_value) total_invoice_year,
sum(total_invioce) over (partition by month_value) total_invoice_month
from data
)
-- perform ranking here
select year_value, month_value, total_invioce,
dense_rank() OVER (ORDER BY total_invoice_year DESC) year_rank,
rank() OVER (partition by year_value ORDER BY total_invoice_month DESC) month_rank
from year_month
order by total_invioce desc;
YEAR_VALUE MONTH_VALUE TOTAL_INVIOCE YEAR_RANK MONTH_RANK
---------- ----------- ------------- ---------- ----------
2016 3 29960 1 1
2016 1 10700 1 2
2015 11 5100 2 1
2015 8 1680 2 2
2016 2 800 1 3

Count records that span multiple date range

ACCOUNT Amount DATE
1 50 01-2010
1 100 03-2010
1 100 02-2011
2 100 01-2011
2 50 05-2011
2 50 09-2011
3 100 03-2012
3 100 03-2013
Is there a query structure that will allow me to count distinct accounts that has spanned current and past year? For example, account 1 has amounts in 2011 and 2010 so it should be counted once under 2011. Account 2 only has amounts in 2011 so it doesn't get counted while account 3 has amounts in 2013 and 2012, so it gets counted as 1 under 2013:
2010 2011 2012 2013
0 1 0 1
First, you need to know the years where you have data for an account:
select account, year(date) as yr
from t
group by account, year(date)
Next, you need to see if two years are in sequence. You can do this in 2012 with lag/lead. Instead, we'll just use a self join:
with ay as (
select account, year(date) as yr
from t
group by account, year(date)
)
select ay.account, ay.yr
from ay join
ay ayprev
on ay.account = ayprev.account and
ay.yr = ayprev.yr + 1
Next, if you want to count the number of accounts by year, just put this into an aggregation:
with ay as (
select account, year(date) as yr
from t
group by account, year(date)
)
select yr, count(*) as numaccounts
from (select ay.account, ay.yr
from ay join
ay ayprev
on ay.account = ayprev.account and
ay.yr = ayprev.yr + 1
) ayy
group by yr
Assuming you have a record id (call this ID)
SELECT COUNT(*),Year FROM Table t3
INNER JOIN (
SELECT record_id, Year(t1.Date) as Year FROM Table t1
INNER JOIN Table t2
WHERE Year(t1.Date)-1=Year(t2.Date) AND t1.Account == t2.Account
) x ON x.record_id = t3.record_id
GROUP BY Year
Use Below Query :
SELECT YEAR(T1.Date) AS D, COUNT(*) AS C
FROM YourTable AS T1
INNER JOIN YourTable T2 ON T2.Account = T1.Account AND YEAR(T2)=YEAR(T1)+1
GROUP BY T1.Account, YEAR(T1.Date)