I have a table that looks like this:-
tblMeterReadings
id meter date total
1 1 03/01/2014 100.1
1 1 04/01/2014 184.1
1 1 05/01/2014 134.1
1 1 06/01/2014 132.1
1 1 07/01/2014 126.1
1 1 08/01/2014 190.1
This is an 8 day "contiguous block" from '2014-01-03' to '2014-01-08'.
In the real table there are "contiguous blocks" of years in length.
I need to select the MOST RESCENT CONTINUOUS 365 DAY BLOCK (filtered by meter column). If 365 cannot be found, then it should select next largest continuous block.
When I say CONTINUOUS I mean there must be no days missing.
This is beyond me, so if someone can solve... I will be very impressed.
using distinct to not count days with 2 sets of data
declare #gapdays int = 2 -- replace this with 365 in your case
;with x as
(
select datediff(d, '2014-01-01', [date])-dense_rank()over(order by [date]) grp
,[date]
from #t
)
select top 1 max([date]) last_date, min([date]) first_date, count(distinct [date]) days_in_a_row
from x
group by grp
having count(distinct [date]) >= #gapdays
order by max([date]) desc
There you go:
declare #tblMeterReadings table (id int, meter int, [date] date, total money)
insert into #tblMeterReadings ( id, meter, date, total )
values
(1, 1, '03/01/2014', 100.1),
(1, 1, '04/01/2014', 184.1),
(1, 1, '05/01/2014', 134.1),
(1, 1, '06/01/2014', 132.1),
(1, 1, '07/01/2014', 126.1),
(1, 1, '08/01/2014', 190.1),
(1, 1, '10/01/2014', 200.1),
(1, 1, '12/01/2014', 202.1),
(1, 1, '13/01/2014', 204.1)
;with data as (
select i = datediff(day, '2014', [date]), *
from #tblMeterReadings l
)
, islands as (
select island = l.i - row_number() over (order by i), l.*
from data l
)
, spans as (
select l = min(i), r = max(i)
from islands i
group by island
)
select *
from spans s
left join data l on s.l = l.i
left join data r on s.r = r.i
Most recent continuous block not exceeding 365 days in length will be as follows:
select top 1 *
from spans s
left join data l on s.l = l.i
left join data r on s.r = r.i
where s.l - s.r < 365
order by s.l - s.r desc, s.r desc
With recursive CTE and datepart(dayofyear, date):
with cte as
(
select id, meter, date, datepart(dayofyear, date) as x, cast(1 as int) as level, t1.date as startDate from tblMeterReadings t1
where meter = 1
and not exists(select * from tblMeterReadings t2 where (datepart(dayofyear, t1.date) - 1) = datepart(dayofyear, t2.date))
union all
select t1.id, t1.meter, t1.date, datepart(dayofyear, t1.date) as x, t2.level + 1, t2.startDate from tblMeterReadings t1
inner join cte t2 ON (datepart(dayofyear, t1.date)) = (datepart(dayofyear, t2.date) + 1)
)
select TOP 365 * from cte
where cte.startDate = (select top 1 startdate
from cte
--where Level <= 365
order by Level desc, startDate desc)
order by Level desc
OPTION ( MAXRECURSION 365 )
SQL Fiddle example
Related
SQL-SERVER 2017
SQL-FIDDLE
DB-CODE:
CREATE TABLE [TABLE_1] (
PLAN_NR decimal(28,6) NULL,
START_DATE datetime NULL);
INSERT INTO TABLE_1
(PLAN_NR,START_DATE)
VALUES
(1,'2020-05-01'),
(2,'2020-08-01');
My question is about the Syntax of the following Select:
WITH Dates AS(
SELECT T1.PLAN_NR,
V.I+1 AS PERIOD_NR,
DATEADD(DAY, 7*V.I, T1.START_DATE) AS START_DATE,
LEAD(DATEADD(DAY, 7*V.I, T1.START_DATE)) OVER (PARTITION BY PLAN_NR ORDER BY V.I) AS END_DATE
FROM dbo.TABLE_1 T1
------------------------------------------------
--> JOIN (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) V(I) ON 1000 >= V.I) <-- THIS LINE
------------------------------------------------
SELECT D.PLAN_NR,
D.PERIOD_NR,
V.START_DATE
FROM Dates D
CROSS APPLY (VALUES(START_DATE),(CASE WHEN MONTH(START_DATE) != MONTH(END_DATE) THEN
DATEADD(MONTH,DATEDIFF(MONTH,0,END_DATE),0) END)) V(START_DATE)
WHERE V.START_DATE IS NOT NULL
What is the logic to extend the values here to e.g. 1000?
Do I have to write out VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10)......(1000)?
Edit:
I got the code mentioned here from Larnu answer to another question:
here.
I would create an extended tally:
WITH N AS(
SELECT N
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL))N(N)),
Tally AS
(SELECT 0 AS I
UNION ALL
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS I
FROM N N1,
N N2,
N N3),
Dates AS
(SELECT T1.PLAN_NR,
T.I + 1 AS PERIOD_NR,
DATEADD(DAY, 7 * T.I, T1.START_DATE) AS START_DATE,
LEAD(DATEADD(DAY, 7 * T.I, T1.START_DATE)) OVER (PARTITION BY PLAN_NR ORDER BY T.I) AS END_DATE
FROM dbo.TABLE_1 T1
CROSS JOIN Tally T)
SELECT D.PLAN_NR,
D.PERIOD_NR,
V.START_DATE
FROM Dates D
CROSS APPLY (VALUES (START_DATE),
(CASE
WHEN MONTH(START_DATE) != MONTH(END_DATE) THEN DATEADD(MONTH, DATEDIFF(MONTH, 0, END_DATE), 0)
END)) V (START_DATE)
WHERE V.START_DATE IS NOT NULL;
You don't need, you can have a CTE to generate the number and join in like following.
WITH cteNum
AS (
SELECT 1 AS I
UNION ALL
SELECT I + 1
FROM cteNum
WHERE I + 1 <= 1000
)
Your final query will look like
with cteNum as
(
select 1 as I
union all
select I +1 from cteNum
where I + 1<=1000
), Dates AS(
SELECT T1.PLAN_NR,
V.I+1 AS PERIOD_NR,
DATEADD(DAY, 7*V.I, T1.START_DATE) AS START_DATE,
LEAD(DATEADD(DAY, 7*V.I, T1.START_DATE)) OVER (PARTITION BY PLAN_NR ORDER BY V.I) AS END_DATE
FROM dbo.TABLE_1 T1
JOIN cteNum V ON 1000 >= V.I)
SELECT D.PLAN_NR,
D.PERIOD_NR,
V.START_DATE
FROM Dates D
CROSS APPLY (VALUES(START_DATE),(CASE WHEN MONTH(START_DATE) != MONTH(END_DATE) THEN DATEADD(MONTH,DATEDIFF(MONTH,0,END_DATE),0) END)) V(START_DATE)
WHERE V.START_DATE IS NOT NULL
option (maxrecursion 0)
SQL-FIDDLE
I am trying to get the records grouped by the minute they were running in. In example below, I have 2 events a01 and a02.
I would like to get the following
min 10:34 - a01
min 10:35 - a01
min 10:36 - a01
min 10:36 - a02
...
min 10:38 - a01
min 10:38 - a02
min 10:39 - a02
So, I am currently using a minute as the time interval. Can you please point me to some examples for this.
Create SQL below:
CREATE TABLE test_t1 (
t1 VARCHAR(150)
,StartTime DATETIME NULL
,EndTime DATETIME NULL
);
INSERT INTO test_t1 (
t1
,StartTime
,EndTime
)
VALUES (
'a01'
,convert(DATETIME, '20180101 10:34:09.630')
,convert(DATETIME, '20180101 10:38:09.630')
);
INSERT INTO test_t1 (
t1
,StartTime
,EndTime
)
VALUES (
'a02'
,convert(DATETIME, '20180101 10:36:09.630')
,convert(DATETIME, '20180101 10:39:09.630')
);
Recursive CTE can be used to solve this kind of problems
with cte as (
select
t1, StartTime = DATEADD(MINUTE, DATEDIFF(MINUTE, 0, StartTime), 0)
, EndTime = DATEADD(MINUTE, DATEDIFF(MINUTE, 0, EndTime), 0)
from
test_t1
)
, rcte as (
select
t1, StartTime, EndTime, convert(char(5), StartTime, 108) res
from
cte
union all
select
t1, dateadd(mi, 1, StartTime), EndTime, convert(char(5), dateadd(mi, 1, StartTime), 108)
from
rcte
where
StartTime < EndTime
)
select
t1, res
from
rcte
order by t1
option (maxrecursion 0)
You need a Tally Table for this.
DECLARE
#minDateTime AS DATETIME,
#maxDateTime AS DATETIME;
SELECT
#minDateTime = MIN(StartTime),
#maxDateTime = MAX(EndTime)
FROM test_t1;
DECLARE #Range AS INT = DATEDIFF(MINUTE, #minDateTime, #maxDateTime);
;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
E8(N) AS(SELECT 1 FROM E4 a CROSS JOIN E4 b), -- 10 ^ 8 = 10,000,000 rows
CteTally(N) AS(
SELECT TOP(#Range) ROW_NUMBER() OVER(ORDER BY(SELECT NULL))
FROM E8
)
SELECT
tt.t1,
MinInterval = DATEADD(MINUTE, ct.N - 1, tt.StartTime)
FROM test_t1 tt
INNER JOIN CteTally ct
ON DATEADD(MINUTE, ct.N - 1, tt.StartTime) <= tt.EndTime
ORDER BY
tt.t1, MinInterval;
Brief explanation of the Tally Table query taken from the article:
Selecting N rows in SQL Server
ONLINE DEMO
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
For example there is some table with dates:
2015-01-01
2015-01-02
2015-01-03
2015-01-06
2015-01-07
2015-01-11
I have to write ms sql query, which will return count of consecutive dates starting from every date in the table. So the result will be like:
2015-01-01 1
2015-01-02 2
2015-01-03 3
2015-01-06 1
2015-01-07 2
2015-01-11 1
It seems to me that I should use LAG and LEAD functions, but now I even can not imagine the way of thinking.
CREATE TABLE #T ( MyDate DATE) ;
INSERT #T VALUES ('2015-01-01'),('2015-01-02'),('2015-01-03'),('2015-01-06'),('2015-01-07'),('2015-01-11')
SELECT
RW=ROW_NUMBER() OVER( PARTITION BY GRP ORDER BY MyDate) ,MyDate
FROM
(
SELECT
MyDate, DATEDIFF(Day, '1900-01-01' , MyDate)- ROW_NUMBER() OVER( ORDER BY MyDate ) AS GRP
FROM #T
) A
DROP TABLE #T;
You can use this CTE:
;WITH CTE AS (
SELECT [Date],
ROW_NUMBER() OVER(ORDER BY [Date]) AS rn,
CASE WHEN DATEDIFF(Day, PrevDate, [Date]) IS NULL THEN 0
WHEN DATEDIFF(Day, PrevDate, [Date]) > 1 THEN 0
ELSE 1
END AS flag
FROM (
SELECT [Date], LAG([Date]) OVER (ORDER BY [Date]) AS PrevDate
FROM #Dates ) d
)
to produce the following result:
Date rn flag
===================
2015-01-01 1 0
2015-01-02 2 1
2015-01-03 3 1
2015-01-06 4 0
2015-01-07 5 1
2015-01-11 6 0
All you have to do now is to calculate a running total of flag up to the first occurrence of a preceding zero value:
;WITH CTE AS (
... cte statements here ...
)
SELECT [Date], b.cnt + 1
FROM CTE AS c
OUTER APPLY (
SELECT TOP 1 COALESCE(rn, 1) AS rn
FROM CTE
WHERE flag = 0 AND rn < c.rn
ORDER BY rn DESC
) a
CROSS APPLY (
SELECT COUNT(*) AS cnt
FROM CTE
WHERE c.flag <> 0 AND rn < c.rn AND rn >= a.rn
) b
OUTER APPLY calculates the rn value of the first zero-valued flag that comes before the current row. CROSS APPLY calculates the number of records preceding the current record up to the first occurrence of a preceding zero valued flag.
I'm assuming this table:
SELECT *
INTO #Dates
FROM (VALUES
(CAST('2015-01-01' AS DATE)),
(CAST('2015-01-02' AS DATE)),
(CAST('2015-01-03' AS DATE)),
(CAST('2015-01-06' AS DATE)),
(CAST('2015-01-07' AS DATE)),
(CAST('2015-01-11' AS DATE))) dates(d);
Here's a recursive solution with explanations:
WITH
dates AS (
SELECT
d,
-- This checks if the current row is the start of a new group by using LAG()
-- to see if the previous date is adjacent
CASE datediff(day, d, LAG(d, 1) OVER(ORDER BY d))
WHEN -1 THEN 0
ELSE 1 END new_group,
-- This will be used for recursion
row_number() OVER(ORDER BY d) rn
FROM #Dates
),
-- Here, the recursion happens
groups AS (
-- We initiate recursion with rows that start new groups, and calculate "GRP"
-- numbers
SELECT d, new_group, rn, row_number() OVER(ORDER BY d) grp
FROM dates
WHERE new_group = 1
UNION ALL
-- We then recurse by the previously calculated "RN" until we hit the next group
SELECT dates.d, dates.new_group, dates.rn, groups.grp
FROM dates JOIN groups ON dates.rn = groups.rn + 1
WHERE dates.new_group != 1
)
-- Finally, we enumerate rows within each group
SELECT d, row_number() OVER (PARTITION BY grp ORDER BY d)
FROM groups
ORDER BY d
SQLFiddle
I have my database table ABC as shown below :
ItemId Month Year Sales
1 1 2013 333
1 2 2013 454
2 1 2013 434
and so on .
I would like to write a query to find the top 3 items that have had the highest increase in sales from last month to this month , so that I see somethinglike this in the output.
Output :
ItemId IncreaseInSales
1 +121
9 +33
6 +16
I came up to here :
select
(select Sum(Sales) from ABC where [MONTH] = 11 )
-
(select Sum(Sales) from ABC where [MONTH] = 10)
I cannot use a group by as it is giving an error . Can anyone point me how I can
proceed further ?
Assuming that you want the increase for a given month, you can also do this with an aggregation query:
select top 3 a.ItemId,
((sum(case when year = #YEAR and month = #MONTH then 1.0*sales end) /
sum(case when year = #YEAR and month = #MONTH - 1 or
year = #YEAR - 1 and #Month = 1 and month = 12
then sales end)
) - 1
) * 100 as pct_increase
from ABC a
group by a.ItemId
order by pct_increase desc;
You would put the year/month combination you care about in the variables #YEAR and #MONTH.
EDIT:
If you just want the increase, then do a difference:
select top 3 a.ItemId,
(sum(case when year = #YEAR and month = #MONTH then 1.0*sales end) -
sum(case when year = #YEAR and month = #MONTH - 1 or
year = #YEAR - 1 and #Month = 1 and month = 12
then sales
end)
) as difference
from ABC a
group by a.ItemId
order by difference desc;
Here is the SQL Fiddle that demonstrates the below query:
SELECT TOP(3) NewMonth.ItemId,
NewMonth.Month11Sales - OldMonth.Month10Sales AS IncreaseInSales
FROM
(
SELECT s1.ItemId, Sum(s1.Sales) AS Month11Sales
FROM ABC AS s1
WHERE s1.MONTH = 11
AND s1.YEAR = 2013
GROUP BY s1.ItemId
) AS NewMonth
INNER JOIN
(
SELECT s2.ItemId, Sum(s2.Sales) AS Month10Sales
FROM ABC AS s2
WHERE s2.MONTH = 10
AND s2.YEAR = 2013
GROUP BY s2.ItemId
) AS OldMonth
ON NewMonth.ItemId = OldMonth.ItemId
ORDER BY NewMonth.Month11Sales - OldMonth.Month10Sales DESC
You never mentioned if you could have more than one record for an ItemId with the same Month, so I made the query to handle it either way. Obviously you were lacking the year = 2013 in your query. Once you get past this year you will need that.
Another option could be something on these lines:
SELECT top 3 a.itemid, asales-bsales increase FROM
(
(select itemid, month, sum(sales) over(partition by itemid) asales from ABC where month=2
and year=2013) a
INNER JOIN
(select itemid, month, sum(sales) over(partition by itemid) bsales from ABC where month=1
and year=2013) b
ON a.itemid=b.itemid
)
ORDER BY increase desc
if you need to cater for months without sales then you can do a FULL JOIN and calculate increase as isnull(asales,0) - isnull(bsales,0)
You could adapt this solution based on PIVOT operator:
SET NOCOUNT ON;
DECLARE #Sales TABLE
(
ItemID INT NOT NULL,
SalesDate DATE NOT NULL,
Amount MONEY NOT NULL
);
INSERT #Sales (ItemID, SalesDate, Amount)
VALUES
(1, '2013-01-15', 333), (1, '2013-01-14', 111), (1, '2012-12-13', 100), (1, '2012-11-12', 150),
(2, '2013-01-11', 200), (2, '2012-12-10', 150), (3, '2013-01-09', 900);
-- Parameters (current year & month)
DECLARE #pYear SMALLINT = 2013,
#pMonth TINYINT = 1;
DECLARE #FirstDayOfCurrentMonth DATE = CONVERT(DATE, CONVERT(CHAR(4), #pYear) + '-' + CONVERT(CHAR(2), #pMonth) + '-01');
DECLARE #StartDate DATE = DATEADD(MONTH, -1, #FirstDayOfCurrentMonth), -- Begining of the previous month
#EndDate DATE = DATEADD(DAY, -1, DATEADD(MONTH, 1, #FirstDayOfCurrentMonth)) -- End of the current month
SELECT TOP(3) t.ItemID,
t.[2]-t.[1] AS IncreaseAmount
FROM
(
SELECT y.ItemID, y.Amount,
DENSE_RANK() OVER(ORDER BY y.FirstDayOfSalesMonth ASC) AS MonthNum -- 1=Previous Month, 2=Current Month
FROM
(
SELECT x.ItemID, x.Amount,
DATEADD(MONTH, DATEDIFF(MONTH, 0, x.SalesDate), 0) AS FirstDayOfSalesMonth
FROM #Sales x
WHERE x.SalesDate BETWEEN #StartDate AND #EndDate
) y
) z
PIVOT( SUM(z.Amount) FOR z.MonthNum IN ([1], [2]) ) t
ORDER BY IncreaseAmount DESC;
SQLFiddle demo
Your sample data seems to be incomplete, however, here is my try. I assume that you want to know the three items with the greatest sales-difference from one month to the next:
WITH Increases AS
(
SELECT a1.itemid,
a1.sales - (SELECT a2.sales
FROM dbo.abc a2
WHERE a1.itemid = a2.itemid
AND ( ( a1.year = a2.year
AND a1.month > 1
AND a1.month = a2.month + 1 )
OR ( a1.year = a2.year + 1
AND a1.month = 1
AND a2.month = 12 ) ))AS IncreaseInSales
FROM dbo.abc a1
)
SELECT TOP 3 ItemID, MAX(IncreaseInSales) AS IncreaseInSales
FROM Increases
GROUP BY ItemID
ORDER BY MAX(IncreaseInSales) DESC
Demo
SELECT
cur.[ItemId]
MAX(nxt.[Sales] - cur.[Sales]) AS [IncreaseInSales]
FROM ABC cur
INNER JOIN ABC nxt ON (
nxt.[Year] = cur.[Year] + cur.[month]/12 AND
nxt.[Month] = cur.[Month]%12 + 1
)
GROUP BY cur.[ItemId]
I'd do this this way. It should work in all the tagged versions of SQL Server:
SELECT TOP 3 [ItemId],
MAX(CASE WHEN [Month] = 2 THEN [Sales] END) -
MAX(CASE WHEN [Month] = 1 THEN [Sales] END) [Diff]
FROM t
WHERE [Month] IN (1, 2) AND [Year] = 2013
GROUP BY [ItemId]
HAVING COUNT(*) = 2
ORDER BY [Diff] DESC
Fiddle here.
The reason why I'm adding the HAVING clause is that if any item is added in only one of the months then the numbers will be all wrong. So I'm only comparing items that are only present in both months.
The reason of the WHERE clause would be to filter in advance only the needed months and improve the efficiency of the query.
An SQL Server 2012 solution could also be:
SELECT TOP 3 [ItemId], [Diff] FROM (
SELECT [ItemId],
LEAD([Sales]) OVER (PARTITION BY [ItemId] ORDER BY [Month]) - [Sales] Diff
FROM t
WHERE [Month] IN (1, 2) AND [Year] = 2013
) s
WHERE [Diff] IS NOT NULL
ORDER BY [Diff] DESC