Return next value in Sybase based on condition - sql

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.

Related

get max date when sum of a field equals a value

I have a problem with writing a query.
Row data is as follow :
DATE CUSTOMER_ID AMOUNT
20170101 1 150
20170201 1 50
20170203 1 200
20170204 1 250
20170101 2 300
20170201 2 70
I want to know when(which date) the sum of amount for each customer_id becomes more than 350,
How can I write this query to have such a result ?
CUSTOMER_ID MAX_DATE
1 20170203
2 20170201
Thanks,
Simply use ANSI/ISO standard window functions to calculate the running sum:
select t.*
from (select t.*,
sum(t.amount) over (partition by t.customer_id order by t.date) as running_amount
from t
) t
where running_amount - amount < 350 and
running_amount >= 350;
If for some reason, your database doesn't support this functionality, you can use a correlated subquery:
select t.*
from (select t.*,
(select sum(t2.amount)
from t t2
where t2.customer_id = t.customer_id and
t2.date <= t.date
) as running_amount
from t
) t
where running_amount - amount < 350 and
running_amount >= 350;
ANSI SQL
Used for the test: TSQL and MS SQL Server 2012
select
"CUSTOMER_ID",
min("DATE")
FROM
(
select
"CUSTOMER_ID",
"DATE",
(
SELECT
sum(T02."AMOUNT") AMOUNT
FROM "TABLE01" T02
WHERE
T01."CUSTOMER_ID" = T02."CUSTOMER_ID"
AND T02."DATE" <= T01."DATE"
) "AMOUNT"
from "TABLE01" T01
) T03
where
T03."AMOUNT" > 350
group by
"CUSTOMER_ID"
GO
CUSTOMER_ID | (No column name)
----------: | :------------------
1 | 03/02/2017 00:00:00
2 | 01/02/2017 00:00:00
db<>fiddle here
DB-Fiddle
SELECT
tmp.`CUSTOMER_ID`,
MIN(tmp.`DATE`) as MAX_DATE
FROM
(
SELECT
`DATE`,
`CUSTOMER_ID`,
`AMOUNT`,
(
SELECT SUM(`AMOUNT`) FROM tbl t2 WHERE t2.`DATE` <= t1.`DATE` AND `CUSTOMER_ID` = t1.`CUSTOMER_ID`
) AS SUM_UP
FROM
`tbl` t1
ORDER BY
`DATE` ASC
) tmp
WHERE
tmp.`SUM_UP` > 350
GROUP BY
tmp.`CUSTOMER_ID`
Explaination:
First I select all rows and subselect all rows with SUM and ID where the current row DATE is smaller or same as all rows for the customer. From this tabe i select the MIN date, which has a current sum of >350
I think it is not an easy calculation and you have to calculate something. I know It could be seen a little mixed but i want to calculate step by step. As fist step if we can get success for your scenario, I believe it can be made better about performance. If anybody can make better my query please edit my post;
Unfortunately the solution that i cannot try on computer is below, I guess it will give you expected result;
-- Get the start date of customers
SELECT MIN(DATE) AS DATE
,CUSTOMER_ID
INTO #table
FROM TABLE t1
-- Calculate all possible date and where is sum of amount greater than 350
SELECT t1.CUSTOMER_ID
,SUM(SELECT Amount FROM TABLE t3 WHERE t3.DATE BETWEEN t1.DATE
AND t2.DATE) AS total
,t2.DATE AS DATE
INTO #tableCalculated
FROM #table t1
INNER JOIN TABLE t2 ON t.ID = t2.ID
AND t1.DATE != t2.DATE
WHERE total > 350
-- SELECT Min amount and date for per Customer_ID
SELECT CUSTOMER_ID, MIN(DATE) AS DATE
FROM #tableCalculated
GROUP BY ID
SELECT CUSTOMER_ID, MIN(DATE) AS GOALDATE
FROM ( SELECT cd1.*, (SELECT SUM(AMOUNT)
FROM CustData cd2
WHERE cd2.CUSTOMER_ID = cd1.CUSTOMER_ID
AND cd2.DATE <= cd1.DATE) AS RUNNINGTOTAL
FROM CustData cd1) AS custdata2
WHERE RUNNINGTOTAL >= 350
GROUP BY CUSTOMER_ID
DB Fiddle

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;

Syntax to get sum(sales) group by brand but different date

My data is like so
item date country sales
----------------------------------------
motorola 2015-01-01 US 10
motorola 2015-01-01 UK 20
motorola 2015-01-02 US 40
motorola 2015-01-02 UK 80
motorola 2015-01-03 US 120
motorola 2015-01-03 UK 150
motorola 2015-01-04 US 170
motorola 2015-01-04 US 180
I want to get the daily sales delta of motorola from 2 jan 2015 until 4 jan 2015.
So for example
total sales for 1 jan 2015 is 10 (US) + 20(UK) = 30
total sales for 2 jan 2015 is 120 so daily sales delta (sales on date minus D-1) is 90
total sales for 3 jan 2015 is 270 so daily delta is 150
total sales for 4 jan 2015 is 350 so daily delta is 80
I'm expecting the result tuple :
date dailyDelta
2015-01-02 90
2015-01-03 150
2015-01-04 80
What is the syntax to get this? I'm using SQL Server 2012.
Thanks
This is it, the query logic is as simple as it gets, the performance better than inner joins:
select date, sum(sales) - coalesce(lag(sum(sales), 1) over (order by date), 0)
from my_sales
group by date
order by date
Use window function lag. Play with it: http://sqlfiddle.com/#!6/bebab/8 and read about it: https://msdn.microsoft.com/en-us/library/hh231256.aspx
briefly, lag(sum(sales), 1) over (order by date) means "get sum(sales) column of previous record of this query, ordered by date", coalesce(XXX, 0) means "when XXX is null, let's pretend it was a zero"
I don't really see much of a way around a self join. Here is how it would work:
select a.date
, a.item
, sum(a.sales) - sum(b.sales) as DailyDelta
from table a
join table b on a.product = b.product
and b.date = dateadd(day, -1, a.date)
group by a.date
, a.item
Not great performance wise but it will get the job done.
The following query is based on MySQL, but you can tweak it to make it work for SQL server. I hope SQL Server will also support the same
select o.date, t.tsales - sum(sales) delta from test o, (select date, sum(sales) tsales from test group by date) t
where o.date = t.date -1 group by o.date
For the above query I got the following result
"date" "delta"
"2015-01-01" "90"
"2015-01-02" "150"
"2015-01-03" "80"
use a self join
declare #t table (item varchar(10), [date] date,country char(2), sales int)
insert into #t (item, [date],country, sales) values
('motorola','2015-01-01','US',10),
('motorola','2015-01-01','UK',20),
('motorola','2015-01-02','US',40),
('motorola','2015-01-02','UK',80),
('motorola','2015-01-03','US',120),
('motorola','2015-01-03','UK',150),
('motorola','2015-01-04','US',170),
('motorola','2015-01-04','US',180)
;with a as (select row_number() over (order by [date]) r,[date],sum(sales) n from #t group by [date])
select a.[date],a.n-isnull(a1.n,0) dailyDelta from a join a a1 on a.r =a1.r+1
Try this..
SELECT date,
( sum(sales) - LAG(sum(sales),1,0) over (order by sales) ) as dailyDelta
FROM Table
group by date
order by 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]

How to ignore the first zeros in the result of a query

I would like to ignore if there are any zero values in the first days of production.
SELECT D_DATE, PRODUCE FROM PRODUCTION
Dataset
Date Produce
1/1/2015 0
1/2/2015 0
1/3/2015 0
1/4/2015 6
1/5/2015 5
1/6/2015 2
1/7/2015 0
1/8/2015 1
1/9/2015 1
The first three days are zeros which I would like to ignore in my result but the 7th day should not be ignored
Desired Result
Date Produce
1/4/2015 6
1/5/2015 5
1/6/2015 2
1/7/2015 0
1/8/2015 1
1/9/2015 1
For simplicity I assume that there is at least one day with produce > 0.
SELECT d_date, produce
FROM production
WHERE
d_date >= (
SELECT MIN(d_date)
FROM production
WHERE
produce != 0
)
;
You can use SUM as analytical function to calculate the cumulative sum of produce and filter those greater than zero.
select d_date, produce
from (
select
d_date,
produce,
sum(produce) over (order by d_date) cuml_produce
from production
)
where cuml_produce > 0
order by d_date;
Try this
SELECT date,
produce
FROM
(
SELECT date,
produce,
row_number() over (order by date) r1,
row_number() over (order by produce, date) r2
FROM production
) A
WHERE r1 != r2
Use correlated subquery.
SELECT Date, Produce,
ROW_NUMBER() OVER (ORDER BY DATE) RN
INTO #Temp
FROM tbl
SELECT t.Date, t.Produce FROM #Temp t
WHERE
EXISTS(
SELECT 1 FROM #Temp t1
WHERE t1.rn < t.rn
AND t1.PRODUCE !=0)
OR T.Produce != 0
Fiddle here
Assuming your date is in DATETIME, will this help you?
SELECT D_DATE, PRODUCE
FROM PRODUCTION
where Date >= (select TOP 1 Date from PRODUCTION where PRODUCE > 0 Order by DATE)