Comparing data from two rows in a same sql table - sql

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;

Related

T-SQL calculate the percent increase or decrease between the earliest and latest for each project

I have a table like below, I am trying to run a query in T-SQL to get the earliest and latest costs for each project_id according to the date column and calculate the percent cost increase or decrease and return the data-set show in the second table (I have simplified the table in this question).
project_id date cost
-------------------------------
123 7/1/17 5000
123 8/1/17 6000
123 9/1/17 7000
123 10/1/17 8000
123 11/1/17 9000
456 7/1/17 10000
456 8/1/17 9000
456 9/1/17 8000
876 1/1/17 8000
876 6/1/17 5000
876 8/1/17 10000
876 11/1/17 8000
Result:
(Edit: Fixed the result)
project_id "cost incr/decr pct"
------------------------------------------------
123 80% which is (9000-5000)/5000
456 -20%
876 0%
Whatever query I run I get duplicates.
This is what I tried:
select distinct
p1.Proj_ID, p1.date, p2.[cost], p3.cost,
(nullif(p2.cost, 0) / nullif(p1.cost, 0)) * 100 as 'OVER UNDER'
from
[PROJECT] p1
inner join
(select
[Proj_ID], [cost], min([date]) min_date
from
[PROJECT]
group by
[Proj_ID], [cost]) p2 on p1.Proj_ID = p2.Proj_ID
inner join
(select
[Proj_ID], [cost], max([date]) max_date
from
[PROJECT]
group by
[Proj_ID], [cost]) p3 on p1.Proj_ID = p3.Proj_ID
where
p1.date in (p2.min_date, p3.max_date)
Unfortunately, SQL Server does not have a first_value() aggregation function. It does have an analytic function, though. So, you can do:
select distinct project_id,
first_value(cost) over (partition by project_id order by date asc) as first_cost,
first_value(cost) over (partition by project_id order by date desc) as last_cost,
(first_value(cost) over (partition by project_id order by date desc) /
first_value(cost) over (partition by project_id order by date asc)
) - 1 as ratio
from project;
If cost is an integer, you may need to convert to a representation with decimal places.
You can use row_number and OUTER APPLY over top 1 ... prior to SQL 2012
select
min_.projectid,
latest_.cost - min_.cost [Calculation]
from
(select
row_number() over (partition by projectid order by date) rn
,projectid
,cost
from projectable) min_ -- get the first dates per project
outer apply (
select
top 1
cost
from projectable
where
projectid = min_.projectid -- get the latest cost for each project
order by date desc
) latest_
where min_.rn = 1
This might perform a little better
;with costs as (
select *,
ROW_NUMBER() over (PARTITION BY project_id ORDER BY date) mincost,
ROW_NUMBER() over (PARTITION BY project_id ORDER BY date desc) maxcost
from table1
)
select project_id,
min(case when mincost = 1 then cost end) as cost1,
max(case when maxcost = 1 then cost end) as cost2,
(max(case when maxcost = 1 then cost end) - min(case when mincost = 1 then cost end)) * 100 / min(case when mincost = 1 then cost end) as [OVER UNDER]
from costs a
group by project_id

Return next value in Sybase based on condition

I have a table like this:
date ticker price
01/01/17 APPL 700
01/01/17 SNAP 15
01/02/17 APPL 750
01/02/17 SNAP 13
I'd want to retrieve the next price for that ticker as an additional column, like so:
date ticker price next_price
01/01/17 APPL 700 750
01/01/17 SNAP 15 13
01/02/17 APPL 750 NULL
01/02/17 SNAP 13 NULL
I think in most databases you'd be able to do something like this:
SELECT date, ticker, price, RANK() OVER (PARTITION BY ticker
ORDER BY date ASC) AS RANK
from table_name
and then do something with the rank to find the next_price. Unfortunately Sybase ASE is sadly limited and doesn't support RANK().
Any ideas on what to use instead?
Assumptions:
each unique ticker value has a max of 1 record for any given date
next_price is for the next day where 'next' could be defined as +1 day, +2 days, +1 week, +1 month, etc
Off the top of my head ... a bit of a convoluted correlated sub-query to find next_price ...
Setup table and data:
create table mytab
([date] date
,ticker varchar(10)
,price int
)
go
insert mytab values ('1/1/2017','APPL',700)
insert mytab values ('1/1/2017','SNAP',15)
insert mytab values ('1/2/2017','APPL',750)
insert mytab values ('1/2/2017','SNAP',13)
insert mytab values ('1/5/2017','APPL',800)
insert mytab values ('1/7/2017','SNAP',23)
go
One possible query:
select t1.[date],
t1.ticker,
t1.price,
(select price
from mytab t2
where t2.ticker = t1.ticker
and t2.[date] = (-- find the 'next' available day for t1.ticker
select min([date])
from mytab t3
where t3.ticker = t1.ticker
and t3.[date] > t1.[date]
)
) as next_price
from mytab t1
order by 1,2
go
date ticker price next_price
---------------- ---------- ----------- -----------
Jan 1 2017 APPL 700 750
Jan 1 2017 SNAP 15 13
Jan 2 2017 APPL 750 800
Jan 2 2017 SNAP 13 23
Jan 5 2017 APPL 800 NULL
Jan 7 2017 SNAP 23 NULL
Tested on ASE 15.7 SP138
You would not use rank() for this. You would use lead().
You can use a correlated subquery:
select t.*,
(select top 1 t2.price
from table_name t2
where t2.ticker = t.ticker and t2.date > t.date
order by t2.date asc
) as next_price
from table_name t;
If you know the date is the next calendar date, then you could use a left join instead -- that would be more efficient.

Aggregation per Date

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

sql completion of a result where it miss some results in the sequence

I have a table with sales information
like this: |product | sales | date|
Most of the time the date are consecutive from 201601 to 201652.
but some times there is a gap ex : no line for 201602 for productA
How can I make an SQL query that will return a result for this gap like this :
productA,4,201601
**productA,0,201602**
productA,5,201603
productA,8,201604
(...)
instead of :
productA,4,201601
productA,5,201603
productA,8,201604
(...)
Of course it will also be some product B,C,...
You do this by using cross join to get all the rows and then left join to pull in the values.
Assuming you have some data for each week:
select p.product, d.date, coalesce(s.sales, 0) as sales
from (select distinct product from sales) p cross join
(select distinct date from sales) d left join
sales s
on s.product = p.product and s.date = d.date;
If you have tables of products and dates, you can use those instead of the subqueries.
Starting from oracle 10g you can use partition outer join to produce desired result:
-- sample of data
with sales(product, sales, dt) as(
select 'product A', 4, 201601 from dual union all
select 'product A', 5, 201603 from dual union all
select 'product A', 8, 201604 from dual
),
-- here we generate months for the year 2016
mnth(dt) as(
select 201600 + level
from dual
connect by level <= 12
)
-- actual query
select s.product
, nvl(s.sales, 0) as sales
, m.dt as date1
from sales s
partition by(s.product)
right join mnth m
on (m.dt = s.dt)
order by s.product, m.dt
Result:
PRODUCT SALES DATE1
--------- ---------- ----------
product A 4 201601
product A 0 201602
product A 5 201603
product A 8 201604
product A 0 201605
product A 0 201606
product A 0 201607
product A 0 201608
product A 0 201609
product A 0 201610
product A 0 201611
product A 0 201612
12 rows selected
based on Gordon's response, I edited so date does not depend on Sales table. Here assumption is that tab will have atleast 52 row, if not please use appropriate data-dictionary table from oracle.
select p.product, d.date, coalesce(s.sales, 0) as sales
from (select distinct product from sales) p cross join
(select 2016 || rownum rn from tab where rownum<=52) d left join
sales s
on s.product = p.product and s.date = d.date;

Adding each row with aprevious row in sql query

Lets say I have a table with a values below:
Date sales
===== =====
Jan 100
Feb 150
Mar 500
and so on
How can I query this table with the results below:
Date Sales Total
==== ===== ======
Jan 100 100
Feb 150 250 (Jan + Feb)
Mar 500 750 (Jan + Feb + mar)
I know it can be done in SP looping through but is there a simple query?
your help is appreciated.
Thanks,
J
A windowed SUM should work on several DBMS. A SQL Server example is below (please let us know which one you are using otherwise):
DECLARE #T TABLE ([Date] DATE, [Sales] INT)
INSERT #T VALUES ('1/1/2015', 100), ('2/1/2015', 150), ('3/1/2015', 500)
SELECT
[Date],
[Sales],
SUM([Sales]) OVER (ORDER BY [Date]) AS [Total]
FROM #T
ORDER BY
[Date]
This generates the following output:
Date Sales Total
---------- ----------- -----------
2015-01-01 100 100
2015-02-01 150 250
2015-03-01 500 750
Since SQL Server 2008 doesn't support the ORDER BY in a windowed aggregate (only since 2012), here's a method to do same thing. It's very inefficient - there's just not a very efficient way to do this otherwise I've seen unfortunately.
;WITH CTE AS (
SELECT
ROW_NUMBER() OVER (ORDER BY [Date]) AS RowId,
[Date],
[Sales]
FROM #T
)
SELECT
A.[Date],
A.[Sales],
SUM(B.[Sales]) AS [Total]
FROM CTE A
INNER JOIN CTE B
ON B.RowId <= A.RowId
GROUP BY
A.[Date],
A.[Sales]
ORDER BY
A.[Date]