Suppose I have a table with data as below:
SELECT *
FROM TestTable
ORDER BY deliver_date
deliver_date quantity
2015-10-01 5.00
2015-10-02 3.00
2015-10-05 10.00
2015-10-07 8.00
2015-10-08 6.00
I know how to do the cumulative as below:
SELECT t1.deliver_date, SUM(t2.quantity) AS cumQTY
FROM TestTable t1
INNER JOIN TestTable t2 ON t2.deliver_date <= t1.deliver_date
GROUP BY t1.deliver_date
ORDER BY t1.deliver_date
result:
deliver_date cumQTY
2015-10-01 5.00
2015-10-02 8.00
2015-10-05 18.00
2015-10-07 26.00
2015-10-08 32.00
But, is it possible for me to get the result as below?
deliver_date cumQTY
2015-10-01 5.00
2015-10-02 8.00
2015-10-03 8.00
2015-10-04 8.00
2015-10-05 18.00
2015-10-06 18.00
2015-10-07 26.00
2015-10-08 32.00
Means, the date must follow continuously.
For example: I do not have 2015-10-03 data in my TestTable table, but the cumulative table must show the date 2015-10-03
Appreciate if someone can help on this.
Thank you.
You can do this using a Tally Table:
SQL Fiddle
DECLARE #startDate DATE,
#endDate DATE
SELECT
#startDate = MIN(deliver_date),
#endDate = MAX(deliver_date)
FROM TestTable
;WITH E1(N) AS(
SELECT 1 FROM(VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))t(N)
),
E2(N) AS(SELECT 1 FROM E1 a CROSS JOIN E1 b),
E4(N) AS(SELECT 1 FROM E2 a CROSS JOIN E2 b),
Tally(N) AS(
SELECT TOP(DATEDIFF(DAY, #startDate, #endDate) + 1)
ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
FROM E4
),
CteAllDates AS(
SELECT
deliver_date = DATEADD(DAY, t.N-1, #startDate),
quantity = ISNULL(tt.quantity, 0)
FROM Tally t
LEFT JOIN TestTable tt
ON DATEADD(DAY, N-1, #startDate) = tt.deliver_date
)
SELECT
deliver_date,
cumQty = SUM(quantity) OVER(ORDER BY deliver_date)
FROM CteAllDates
First, you want to generate all dates starting from the MIN(deliver_date) up to MAX(deliver_date). This is done using a tally table, the CTEs from E1(N) up to Tally(N).
Now that you have all the dates, do a LEFT JOIN on the original table, TestTable, to get the corresponding quantity, assigning 0 if there is no matching dates.
Lastly, to get the cumulative sum, you can use SUM(quantity) OVER(ORDER BY deliver_date).
For more explanation on tally table, see my answer here.
Related
Is there a T-SQL query that would allow me to see products that have had no changes in quantity for the past 4 days?
Product
Date
Quantity
Coke
2022-04-06
0
Coke
2022-04-07
0
Coke
2022-04-08
0
Coke
2022-04-09
0
Pepsi
2022-04-06
0
Pepsi
2022-04-07
1
Pepsi
2022-04-08
1
Pepsi
2022-04-09
1
Sprite
2022-04-06
1
Sprite
2022-04-07
0
Sprite
2022-04-08
0
Sprite
2022-04-09
1
Tango
2022-04-05
2
Tango
2022-04-06
1
Tango
2022-04-07
1
Tango
2022-04-08
1
Tango
2022-04-09
1
Result
Product
Quantity
Coke
0
Edit: this is how I started off my code
DECLARE #CurrentDate date = GETDATE();
DECLARE #PreviousDate date = DATEADD (Day, -4, #CurrentDate)
DECLARE #Quantity AS Decimal(8,5)
DECLARE #Count AS int
SELECT
[Date], [Product], [Quantity]
FROM
Table1
WHERE
[Date] = #PreviousDate
AND [Product] IN (SELECT [Product]
FROM Table1
WHERE [DATE] BETWEEN #PreviousDate AND #CurrentDate)
If I understand correctly you could use first_value() analytic function to partition the products accordingly and check the first and last quantities match,
select distinct product, quantity from (
select *,
First_Value(quantity) over(partition by product order by date) f,
First_Value(quantity) over(partition by product order by date desc) l
from t
where date >= DateAdd(day,-4,GetDate())
)t
where f = l;
Demo Fiddle
Edit
Seems I didn't quite understand but do now (I hope) so how about an approach using not exists?
with cv as (
select *,
First_Value(quantity) over(partition by product order by date desc) currentValue,
Count(*) over(partition by product) qty
from t
where date >= DateAdd(day,-4,GetDate())
)
select distinct product, quantity
from cv t
where qty=4 and not exists (
select * from cv t2
where t2.product = t.product
and t2.quantity != currentValue
);
Demo Fiddle (2)
You select all records from #PreviousDate (WHERE [Date] = #PreviousDate) and which have modifications (AND [Product] IN (...))
SELECT
t1.[Date], t1.[Product], t1.[Quantity], t2.Quantity "PreviousQuantity"
FROM
Table1 t1
LEFT JOIN Table1 t2 On t1.Product = t2.Product and t2.[Date] = #PreviousDate
WHERE t1.[Date] = #CurrentDate
and t1.Quantity = t2.Quantity
But this will show nothing because #PreviousDate has the value 2022-04-05 (when #CurrentDate=2022-04-09)
So, you might need to change #PreviousDate to: DECLARE #PreviousDate date = DATEADD (Day, -3, #CurrentDate)
see: DBFIDDLE
This is for SQL Server 2008/2012.
I have the following dataset with the claim start date and end date. I want to calculate the number of days when there are back to back claims where the claim start date of the next date is one day after the claim end date of the previous date, making it a continuous service.
If there is a break in service, like for member id 1002 where the claim end of 05/15 and next one starts on 05/18, the count should restart.
MemberID Claim Start Claim End Claim_ID
1001 2016-04-01 2016-04-15 ABC11111
1001 2016-04-16 2016-04-30 ABC65465
1001 2016-05-01 2016-05-15 ABC51651
1001 2016-05-16 2016-06-15 ABC76320
1002 2016-04-01 2016-04-15 ABC74563
1002 2016-04-16 2016-04-30 ABC02123
1002 2016-05-01 2016-05-15 ABC02223
1002 2016-05-18 2016-06-15 ABC66632
1002 2016-06-16 2016-06-30 ABC77447
1002 2016-07-10 2016-07-31 ABC33221
1002 2016-08-01 2016-08-10 ABC88877
So effectively, I want the following output. Min of the very first claim start date, max of the claim end date when there is no gap in coverage between multiple claims. If there is a gap in coverage, the count starts over and the min of the start date of the 1st claim and the max of the claim end date until there is no gap in coverage between multiple claims.
MemberID Claim_Start Claim_End Continuous_Service_Days
1001 2016-04-01 2016-06-15 76
1002 2016-04-01 2016-05-15 45
1002 2016-05-18 2016-06-30 44
1002 2016-07-10 2016-08-10 32
I have tried while loops, CTE's and I have also tried the following table to first get all the dates between the claims. But I am having problems with counting the days between consecutive dates and to reset the count if there is a break in coverage.
Master.dbo.spt_values
Any help is appreciated. Thanks!
You need to find the gaps first.
This solution uses a Tally Table to generate the dates first from ClaimStart to ClaimEnd. Then using the generated dates, get the gaps using this method.
Now that you have the gaps, you can now use GROUP BY to ge the MIN(ClaimStart) and MAX(ClaimStart):
WITH E1(N) AS( -- 10 ^ 1 = 10 rows
SELECT 1 FROM(VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))t(N)
),
E2(N) AS(SELECT 1 FROM E1 a CROSS JOIN E1 b), -- 10 ^ 2 = 100 rows
E4(N) AS(SELECT 1 FROM E2 a CROSS JOIN E2 b), -- 10 ^ 4 = 10,000 rows
CteTally(N) AS(
SELECT TOP(SELECT MAX(DATEDIFF(DAY, ClaimStart, ClaimEnd) + 1) FROM tbl)
ROW_NUMBER() OVER(ORDER BY(SELECT NULL))
FROM E4
),
CteDates AS( -- Generate the dates from ClaimStart to ClaimEnd
SELECT
t.MemberID,
dt = DATEADD(DAY, ct.N - 1, t.ClaimStart)
FROM tbl t
INNER JOIN CteTally ct
ON DATEADD(DAY, ct.N - 1, t.ClaimStart) <= t.ClaimEnd
),
CteGrp AS( -- Find gaps and continuous dates
SELECT *,
rn = DATEADD(DAY, - ROW_NUMBER() OVER(PARTITION BY MemberID ORDER BY dt), dt)
FROM CteDates
)
SELECT
MemberID,
ClaimStart = MIN(dt),
ClaimEnd = MAX(dt),
Diff = DATEDIFF(DAY, MIN(dt), MAX(dt)) + 1
FROM CteGrp
GROUP BY MemberID, rn
ORDER BY MemberID, ClaimStart;
ONLINE DEMO
Declare #YourTable table (MemberID int,[Claim Start] date,[Claim End] date,[Claim_ID] varchar(25))
Insert Into #YourTable values
(1001,'2016-04-01','2016-04-15','ABC11111'),
(1001,'2016-04-16','2016-04-30','ABC65465'),
(1001,'2016-05-01','2016-05-15','ABC51651'),
(1001,'2016-05-16','2016-06-15','ABC76320'),
(1002,'2016-04-01','2016-04-15','ABC74563'),
(1002,'2016-04-16','2016-04-30','ABC02123'),
(1002,'2016-05-01','2016-05-15','ABC02223'),
(1002,'2016-05-18','2016-06-15','ABC66632'),
(1002,'2016-06-16','2016-06-30','ABC77447'),
(1002,'2016-07-10','2016-07-31','ABC33221'),
(1002,'2016-08-01','2016-08-10','ABC88877')
;with cte0(N) as (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N))
,cte1(R,D) as (Select Row_Number() over (Order By (Select Null))
,DateAdd(DD,-1+Row_Number() over (Order By (Select Null)),(Select MinDate=min([Claim Start]) From #YourTable))
From cte0 N1, cte0 N2, cte0 N3, cte0 N4)
Select MemberID
,[Claim Start] = Min([Claim Start])
,[Claim End] = Max([Claim End])
,Continuous_Service_Days = count(*)
From (
Select *,Island = R - Row_Number() over (Partition By MemberID Order by [Claim Start])
From #YourTable A
Join cte1 B on D Between [Claim Start] and [Claim End]
) A
Group By MemberID,Island
Order By 1,2
Returns
MemberID Claim Start Claim End Continuous_Service_Days
1001 2016-04-01 2016-06-15 76
1002 2016-04-01 2016-05-15 45
1002 2016-05-18 2016-06-30 44
1002 2016-07-10 2016-08-10 32
I'm trying to do this query, in sql server, but something is wrong. Need some help...
I have a table with item movements and another one with other movements (buy) where I find the cost of each item in each date when I buy it. So, I just need first table with last cost based on the date of movement finding the cost on second table on the last date.
In other words, only must search the records from the second table with date lower than the first table date for that item and return the cost of the most recent date.
Examples:
First Table
REF DATE
1 2015-10-15
1 2015-08-30
2 2015-09-11
3 2015-05-22
2 2015-03-08
2 2015-07-15
3 2015-11-14
1 2015-11-20
Second Table (Buy)
REF DATE COST
1 2015-08-20 150
1 2015-10-12 120
2 2015-04-04 270
2 2015-06-15 280
3 2015-03-01 75
3 2015-10-17 80
I need this result:
REF DATE Cost
1 2015-10-15 120
1 2015-08-30 150
2 2015-09-11 280
3 2015-05-22 75
2 2015-03-08 -
2 2015-07-15 280
3 2015-11-14 80
1 2015-11-20 120
Any help appreciated.
You can do it using OUTER APPLY:
SELECT [REF], [DATE], [COST]
FROM Table1 AS t1
OUTER APPLY (
SELECT TOP 1 COST
FROM Table2 AS t2
WHERE t1.REF = t2.REF AND t1.DATE >= t2.DATE
ORDER BY t2.DATE DESC) AS t3
Demo here
;WITH cte AS (
SELECT ft.*,
st.[Cost],
ROW_NUMBER() OVER (PARTITION BY ft.[Ref],ft.[Date] ORDER BY st.[Date] DESC) RN
FROM FirstTable ft
LEFT JOIN SecondTable st ON ft.[Ref] = st.[Ref]
AND ft.[Date] >= st.[Date]
)
SELECT Ref,
[Date],
[Cost]
FROM cte
WHERE RN = 1
or if you dont want to use a cte.
SELECT
Ref,
[Date],
[Cost]
FROM
(SELECT
ft.*,
st.[Cost],
ROW_NUMBER() OVER (PARTITION BY ft.[Ref],ft.[Date] ORDER BY st.[Date] DESC) RN
FROM
FirstTable ft
LEFT JOIN SecondTable st ON ft.[Ref] = st.[Ref]
AND ft.[Date] >= st.[Date]
) t
WHERE
t.RN = 1
I have a table [tbl] with money values
id mon
1 10.17
2 36.00
I need to split these values into rows by a set of specific ranges [1.00,10.00,25.00]. The sum of the new values grouped by id will equal the original value.
id mon sum
1 1.00 1.00
1 9.17 10.17
2 1.00 1.00
2 10.00 11.00
2 25.00 36.00
Is there any way to do this without using a cursor?
Here's one way to do it:
;with CTE as (select t2.value, t1.id, sum(t2.value)
over (partition by t1.id order by t2.value asc) as total
from table1 t1 join table2 t2 on t1.mon >= t2.limit
)
select id, value, total from CTE
union all
select t1.id, t1.mon - c.total, t1.mon
from table1 t1
outer apply (select top 1 id, total from CTE c
where c.id = t1.id order by c.value desc) c
where t1.mon > c.total
order by 1,3
This uses additional table that has the limits stored to join with the original data and then uses running total in a CTE and joins that to the original table to get the remaining amounts
You can test the example in SQL Fiddle
Here is my attempt using window functions and CROSS APPLY:
;WITH Cte(s) AS(
SELECT CAST(1 AS MONEY) UNION ALL
SELECT 10 UNION ALL
SELECT 25
)
,CteRange AS(
SELECT
s,
e = SUM(s) OVER(ORDER BY s)
FROM Cte
)
SELECT
t.id,
mon = CASE WHEN t.mon > x.e THEN x.s ELSE mon - LAG(x.e) OVER(PARTITION BY t.id ORDER BY x.s) END,
[sum] = CASE WHEN t.mon < x.e THEN t.mon ELSE x.e END
FROM tbl t
CROSS APPLY(
SELECT * FROM CteRange
)x
WHERE t.mon > x.s
UNION ALL
SELECT
t.id,
mon = t.mon - x.e,
[sum] = t.mon
FROM tbl t
CROSS APPLY(
SELECT TOP 1 e
FROM CteRange
ORDER BY e DESC
)x(e)
WHERE t.mon > e
ORDER BY t.id, mon
SQL Fiddle
This works for your given example data, you just need to predefine ranges all by yourself (I've used CROSS JOIN VALUES, but this can be done however you want/prefer). I think that's not an issue. I've used running SUM and analytic functions to achieve that.
DECLARE #tbl TABLE
(
id INT IDENTITY (1, 1)
, mon MONEY
);
INSERT INTO #tbl (mon)
VALUES (10.17), (36.00);
SELECT id
, [sum] - SUM(lagRange) OVER (PARTITION BY ID ORDER BY rangeId) AS mon
, [sum]
FROM (
SELECT id, rangeId
, LAG(rangeValue, 1, 0) OVER (PARTITION BY ID ORDER BY rangeId) AS lagRange
, CASE
WHEN SUM(rangeValue) OVER (PARTITION BY ID ORDER BY rangeId) > mon THEN mon
ELSE SUM(rangeValue) OVER (PARTITION BY ID ORDER BY rangeId)
END AS [sum]
FROM #tbl
CROSS JOIN (VALUES ((1), (1.00)), ((2), (10.00)), ((3), (25.00))) AS T(rangeId, rangeValue)
WHERE rangeValue <= mon
) AS T;
Results:
id mon sum
-----------------
1 1.00 1.00
1 9.17 10.17
2 1.00 1.00
2 10.00 11.00
2 25.00 36.00
I have two tables
1) Fund details
ID Symbol
-------------------
1 ABC
2 XYZ
2) Fund Price data
Fund_id date Price
-------------------------------------------
1 2014-07-01 00:00:00.000 25.25
1 2014-07-02 00:00:00.000 25.45
......
2 2014-07-01 00:00:00.000 75.25
2 2014-07-02 00:00:00.000 75.42
.......
Now what I want to achieve is:
Here I am fetching the monthly data of a particular Fund as below:
SELECT YEAR(date) [Year], MONTH(date) [Month],
DATENAME(MONTH,date) [Month Name], COUNT(1) [Sales Count], F.Symbol
FROM FundData FD inner join FundDetails F on F.ID = FD.Fund_ID
where F.Symbol = 'ABC'
GROUP BY YEAR(date), MONTH(date), DATENAME(MONTH, date), F.Symbol
Output:
Year Month Month Name Sales Count Symbol
-------------------------------------------
2014 4 April 21 ABC
2014 5 May 21 ABC
2014 6 June 21 ABC
2014 7 July 3 ABC
.......
Total Rows: 301
So here this is only for only particular fund which has returned 301 rows.
Now I want to get all the funds from the Fund details table which has rows less than given count ex 216 which I will pass as a parameter
Use Following query:
Declare #YourParameter int = 10
SELECT YEAR(date) [Year],
MONTH(date) [Month],
DATENAME(MONTH,date) [Month Name],
COUNT(1) [Sales Count],
F.Symbol
FROM FundData FD
INNER JOIN FundDetails F on FD.ID = F.Fund_ID
Where FD.ID IN (SELECT z.Fund_ID
FROM FundDetails z
WHERE z.Fund_ID=FD.ID
GROUP BY z.Fund_ID, YEAR(z.date), MONTH(z.date)
HAVING COUNT(*) <= #YourParameter
)
GROUP BY YEAR(date), MONTH(date), DATENAME(MONTH, date), F.Symbol
I have fixed it:
Declare #YourParameter int = 110
WITH CTE AS
(
SELECT YEAR(date) [Year], MONTH(date) [Month],
DATENAME(MONTH,date) [Month Name], COUNT(1) [Sales Count], F.Symbol
FROM FundData FD inner join FundDetails F on F.ID = FD.Fund_ID
where F.ID
IN (SELECT z.ID FROM FundDetails z)
GROUP BY F.Symbol, YEAR(date), MONTH(date), DATENAME(MONTH, date)
)
SELECT Symbol, COUNT(*) as cnt FROM CTE
GROUP BY Symbol
having COUNT(*) >= #YourParameter