I've got a table valued function created in MSSQL that takes in 2 paramateres
1. End Date
2. Number of weeks to go back
to generate a table of dates with the week start date.
The PERIOD table simply is a table with 1 column (called pPeriod) with all the dates between '19971229' and '20201231'
CREATE FUNCTION [dbo].[Get_Week_Rank]
(
-- Add the parameters for the function here
#PERIOD_END DATETIME,
#NUM_WEEKS INT
)
RETURNS TABLE
AS
RETURN
(
SELECT A.PPERIOD TY_PPERIOD, B.PPERIOD TY_PWKSTART, DATEADD(YY, -1, A.PPERIOD) LY_PPERIOD, DATEADD(YY, -1, B.PPERIOD) LY_PWKSTART, B.CRNK WEEK_RANK FROM (
SELECT PPERIOD, ROW_NUMBER() OVER (ORDER BY PPERIOD) CRNK FROM PERIODS
WHERE PPERIOD BETWEEN DATEADD(WW, #NUM_WEEKS - 1, #PERIOD_END) + 1 AND #PERIOD_END
) AS A
JOIN (
SELECT PPERIOD, ROW_NUMBER() OVER (ORDER BY CRNK % 7) CRNK FROM (
SELECT PPERIOD, ROW_NUMBER() OVER (ORDER BY PPERIOD) CRNK FROM PERIODS
WHERE PPERIOD BETWEEN DATEADD(WW, #NUM_WEEKS, #PERIOD_END) + 1 AND #PERIOD_END
) AS A
WHERE CRNK % 7 = 1
) AS B ON (A.CRNK - 1)/7 = B.CRNK
)
I noticed then when #NUM_WEEKS is between -1 and -130, the results are correct when running this query:
SELECT * INTO #WEEKS FROM GET_WEEK_RANK('20160401', -104)
SELECT * FROM #WEEKS ORDER BY 1
However, any number below -130 (eg -156, -208), the returned results are all wrong.
Wrong results
You can see that TY_PWKSTART is all jumbled up and not in sync with TY_PPERIOD.
If I run the query directly, the results return fine:
SELECT * FROM GET_WEEK_RANK('20160401', -140)
What could be the issue? I am using Microsoft SQL Server 2014
EDIT: Posting images of results
As you can see, both queries essentially do the same thing, but the results returned are different. The order of pWkStart in the first query when using SELECT INTO is wrong.
Wrong results:
SELECT * INTO #WEEK_WRONG FROM GET_WEEK_RANK('20160410', -140)
SELECT * FROM #WEEK_WRONG ORDER BY 1
Correct results:
CREATE TABLE #WEEK_CORRECT (TY_PPERIOD DATETIME, TY_PWKSTART DATETIME, LY_PPERIOD DATETIME, LY_PWKTART DATETIME, WEEK_RANK INT)
INSERT INTO #WEEK_CORRECT
SELECT * FROM GET_WEEK_RANK('20160410', -140)
SELECT * FROM #WEEK_CORRECT ORDER BY 1
EDIT2:
Turns out that my initial query was producing unexpected results. I've fixed my query and managed to get consistent results from SELECT INTO and INSERT INTO.
Just sharing the code here:
CREATE FUNCTION [dbo].[Get_Week_Rank]
(
#PERIOD_END DATETIME,
#NUM_WEEKS INT
)
RETURNS TABLE
AS
RETURN
(
SELECT A.PPERIOD TY_PPERIOD, B.PPERIOD TY_PWKSTART, DATEADD(YY, -1, A.PPERIOD) LY_PPERIOD, DATEADD(YY, -1, B.PPERIOD) LY_PWKSTART, B.CRNK + 1 WEEK_RANK FROM (
SELECT PPERIOD, (ROW_NUMBER() OVER (ORDER BY PPERIOD)-1)/7 CRNK FROM PERIODS
WHERE PPERIOD BETWEEN DATEADD(WW, #NUM_WEEKS, #PERIOD_END) + 1 AND #PERIOD_END
) AS A
JOIN (
SELECT PPERIOD, ROW_NUMBER() OVER (ORDER BY PPERIOD)-1 CRNK FROM (
SELECT PPERIOD , ROW_NUMBER() OVER (PARTITION BY CRNK ORDER BY CRNK) CRNK FROM (
SELECT PPERIOD, (ROW_NUMBER() OVER (ORDER BY PPERIOD)-1)/7 CRNK FROM PERIODS
WHERE PPERIOD BETWEEN DATEADD(WW, #NUM_WEEKS, #PERIOD_END) + 1 AND #PERIOD_END
) AS A
) AS A
WHERE CRNK = 1
) AS B ON A.CRNK = B.CRNK
)
This part of your query is broken:
SELECT PPERIOD, ROW_NUMBER() OVER (ORDER BY CRNK % 7) CRNK FROM (
...
) AS A
WHERE CRNK % 7 = 1
Since the where clause establishes that CRNK % 7 is equal to 1, the ROW_NUMBER() expression is free to assign row numbers in any order1. I would guess that you still would want to assign row numbers in the order in which PPERIOD or CRNK values work, and so the expression should instead by:
SELECT PPERIOD, ROW_NUMBER() OVER (ORDER BY CRNK) CRNK FROM (
...
) AS A
WHERE CRNK % 7 = 1
1Since you haven't provided enough expressions in the ORDER BY for row numbers to be assigned unambiguously, there's no guarantee on the values assigned to each row.
Related
I have a SQL Server table like this:
How can I change reading column into 2 columns based on rownumber?
I have tried like this:
WITH pivot_data AS
(
SELECT
date, CurrentMeterSNID,
1 + ((ROW_NUMBER() OVER (PARTITION BY CurrentMeterSNID ORDER BY date desc) - 1) % 2) rownum,
Reading
FROM
INF_Facility_ElectricalRecord
)
SELECT
date, CurrentMeterSNID, [1], [2]
FROM
pivot_data
PIVOT
(MAX(Reading) FOR rownum IN ([1], [2])) AS p;
but the result that I get is:
I get Null record; how can I replace that null value with record from a day after the date?
actually you are not doing PIVOT. You just want to conditionally display the value on different column. For this you use the CASE statement.
For the second requirement : for the NULL value, showing subsequent day value, you can use LEAD() or LAG() window function. This is the else part of the case
select date, CurrentMeterSNID,
[1] = case when rownum2 = 1
then reading
else lead(reading) over(partition by CurrentMeterSNID order by date)
end,
[2] = case when rownum2 = 2
then reading
else lead(reading) over(partition by CurrentMeterSNID order by date)
end
from INF_Facility_ElectricalRecord
As long as you are displaying every date in that query, you can't have what you want.
So you have to pick the max(date) in other words where rownumber will be 1.
WITH pivot_data AS(
SELECT date,CurrentMeterSNID,
1 + ((row_number() over(partition by CurrentMeterSNID ORDER by date desc) - 1) % 2) rownum,
Reading
FROM dbo.Table_1 )
, T2 AS
(
SELECT CurrentMeterSNID, date, [1], [2]
FROM pivot_data PIVOT (max(Reading) FOR rownum IN ([1],[2])) AS p
)
SELECT CurrentMeterSNID, Max(date), MAX([1]), Max([2])
FROM T2
GROUP BY CurrentMeterSNID
I want to get the last Saturday from today + 21 days.
In order to achieve this, I have written this script shown below. But the problem is that I can't get success to return the value from the result.
I want to create this function in SQL Server and will get this value in a stored procedure where I want.
DECLARE #StartDate DATETIME
DECLARE #EndDate DATETIME
DECLARE #NumOfDays INT
DECLARE #resultDate smalldatetime
SET #StartDate = GETDATE()
SET #EndDate = GETDATE()+21
SET #NumOfDays = DATEDIFF(DD,#StartDate , #EndDate) + 1 ;
WITH Tens AS
(
SELECT 1 N UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1
),
HUNDREDS AS
(
SELECT T1.N FROM TENS T1 CROSS JOIN TENS T2
),
THOUSANDS AS
(
SELECT T1.N FROM HUNDREDS T1 CROSS JOIN HUNDREDS T2
),
Numbers AS
(
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT 0)) RN FROM THOUSANDS
)
SELECT TOP 1
DATEADD(DD, (RN - 1), #StartDate) SaturdayDates
FROM
Numbers
WHERE
RN <= #NumOfDays
AND DATENAME (WEEKDAY, (DATEADD(DD, (RN - 1), #StartDate))) = 'Saturday'
ORDER BY
SaturdayDates DESC
Can you please guide me to achieve my goal? Thanks
Just rewrite it like this table-valued function:
CREATE FUNCTION dbo.Get_NextSaturdayDay()
RETURNS TABLE
AS
RETURN
(
-- Add the SELECT statement with parameter references here
WITH Tens AS
(
SELECT 1 N UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1
),
HUNDREDS AS
(
SELECT T1.N FROM TENS T1 CROSS JOIN TENS T2
),
THOUSANDS AS
(
SELECT T1.N FROM HUNDREDS T1 CROSS JOIN HUNDREDS T2
),
Numbers AS
(
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT 0)) RN FROM THOUSANDS
)
SELECT TOP 1 DATEADD( DD,(RN - 1) , GETDATE() ) as SaturdayDates
FROM
Numbers
WHERE
RN <= (DATEDIFF(DD,GETDATE() , DATEADD(day,21,GETDATE()) ) + 1) AND DATENAME ( WEEKDAY, (DATEADD( DD,(RN - 1) , GETDATE() )) ) = 'Saturday'
ORDER BY SaturdayDates DESC
)
GO
Than do:
SELECT *
FROM dbo.Get_NextSaturdayDay()
Output:
SaturdayDates
2016-10-15 11:02:33.570
If you need scalar-valued function:
CREATE FUNCTION dbo.Get_NextSaturdayDay ()
RETURNS datetime
AS
BEGIN
DECLARE #datetime datetime
;WITH Tens AS
(
SELECT 1 N UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1
),
HUNDREDS AS
(
SELECT T1.N FROM TENS T1 CROSS JOIN TENS T2
),
THOUSANDS AS
(
SELECT T1.N FROM HUNDREDS T1 CROSS JOIN HUNDREDS T2
),
Numbers AS
(
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT 0)) RN FROM THOUSANDS
)
SELECT TOP 1 #datetime = DATEADD( DD,(RN - 1) , GETDATE() )
FROM
Numbers
WHERE
RN <= (DATEDIFF(DD,GETDATE() , DATEADD(day,21,GETDATE()) ) + 1) AND DATENAME ( WEEKDAY, (DATEADD( DD,(RN - 1) , GETDATE() )) ) = 'Saturday'
ORDER BY DATEADD( DD,(RN - 1) , GETDATE() ) DESC
-- Return the result of the function
RETURN #datetime
END
GO
Then run:
SELECT dbo.Get_NextSaturdayDay()
Output:
2016-10-15 11:02:33.570
A table 'readings' has a list of dates
[Date] [Value]
2015-03-19 00:30:00 1.2
2015-03-19 00:40:00 1.2
2015-03-19 00:50:00 0.1
2015-03-19 01:00:00 0.1
2015-03-19 01:10:00 2
2015-03-19 01:20:00 0.5
2015-03-19 01:30:00 0.5
I need to get the most recent instance where the value is below a set point (in this case the value 1.0), but I only want the start (earliest datetime) where the value was below 1 for consecutive times.
So with the above data I want to return 2015-03-19 01:20:00, as the most recent block of times where value < 1, but I want the start of that block.
This SQL just returns the most recent date, rather than the first date whilst the value has been low (so returns 2015-03-19 01:30:00 )
select top 1 *
from readings where value <=1
order by [date] desc
I can't work out how to group the consecutive dates, to therefore only get the first ones
It is SQL Server, the real data isn't at exactly ten min intervals, and the readings table is about 70,000 rows- so fairly large!
Thanks, Charli
Demo
SELECT * FROM (
SELECT [Date]
,Value
,ROW_NUMBER() OVER (PARTITION BY cast([Date] AS DATE) ORDER BY [Date] ASC) AS RN FROM #table WHERE value <= 1
) t WHERE t.RN = 1
Select Max( [date] )
From [dbo].[readings]
Where ( [value] <= 1 )
You seem to want the minimum date for each set of consecutive records having a value that is less than 1. The query below returns exactly these dates:
SELECT MIN([Date])
FROM (
SELECT [Date], [Value],
ROW_NUMBER() OVER (ORDER BY [Date]) -
COUNT(CASE WHEN [Value] < 1 THEN 1 END) OVER (ORDER BY [Date]) AS grp
FROM mytable) AS t
WHERE Value < 1
GROUP BY grp
grp calculated field identifies consecutive records having Value<1.
Note: The above query will work for SQL Server 2012+.
Demo here
Edit:
To get the date value of the last group you can modify the above query to:
SELECT TOP 1 MIN([Date])
FROM (
SELECT [Date], [Value],
ROW_NUMBER() OVER (ORDER BY [Date]) -
COUNT(CASE WHEN [Value] < 1 THEN 1 END) OVER (ORDER BY [Date]) AS grp
FROM mytable) AS t
WHERE Value < 1
GROUP BY grp
ORDER BY grp DESC
Demo here
I am trying to create a dynamic calendar where I can ignore weekends and skip ahead to 45 days from now
Declare #StartDate Date = '12/01/2015'
;With NumberList AS
(
Select *
From
(
Select Rank() Over(Order By S1.Id, S2.Id) Number
From master.dbo.SysObjects S1, master.dbo.SysObjects S2
) XX
Where Number < 1000
)
,FullCalendar as
(
select Cast (dateadd(day, number, DATEADD(yy, DATEDIFF(yy,0,getdate()), 0)) as Date) CalendarDate
from Numberlist
)
,CalendarWithNumbers As
(
Select CalendarDate, Row_Number() Over (Order By CalendarDate) DayNumber
From FullCalendar
Where ((DATEPART(dw, CalendarDate) + ##DATEFIRST) % 7) NOT IN (0, 1)
)
,StartingDate as
(
Select *
From CalendarWithNumbers
Where 1=1
And CalendarDate = #StartDate
)
Select #StartDate StartDate, CalendarDate NDaysFromNow, DayNumber
From CalendarWithNumbers
Where DayNumber = (Select DayNumber + 45 from StartingDate)
However, I get an an Overflow Error
StartDate NDaysFromNow DayNumber
---------- ------------ --------------------
Msg 517, Level 16, State 1, Line 3
Adding a value to a 'datetime' column caused an overflow.
However, I am only choosing the first 1000 numbers in my first CTE, and my starting date is the first day of the year. 1/1/2015 + 1000 days only goes up to 9/26/2017.
Why I am getting an Overflow Error?
EDIT - Comments added to explain code
I added a comments to the code to make it easier to understand.
Declare #StartDate Date = '12/01/2015'
;With NumberList AS
(
/* Get a list of 1000 Numbers*/
Select *
From
(
/*
Get a sequence of Numbers (I get 2071*2071 = 4289041)
I may need to go up to 50 years in the future so I have to do a cartesian product (same as Cross Join)
However, this is where it fails
*/
Select Rank() Over(Order By S1.Id, S2.Id) Number
From master.dbo.SysObjects S1, master.dbo.SysObjects S2
/*
comment out the Select ABOVE
Uncomment the Select BELOW
It works!!
Probably because now the sequencing only goes up to 2071
*/
--Select Rank() Over(Order By S1.Id) Number
--From master.dbo.SysObjects S1
) XX
Where Number < 1000
)
--Select * From NumberList
--UnComment just the line above, and comment everything below to see the result just up to there
,FullCalendar as
(
/* Take the first day of the year, and add 1 to 1000 days to it
It starts from 2015-01-02 (bug needs to be fixed so it starts from 2015-01-01)
It ends at 2017-09-26
*/
select Cast (dateadd(day, number, DATEADD(yy, DATEDIFF(yy,0,getdate()), 0)) as Date) CalendarDate
from Numberlist
)
--Select * From FullCalendar
--UnComment just the line above, and comment everything below to see the result just up to there
,CalendarWithNumbers As
(
/*
WHERE CLAUSE exclude the weekend days
ROW_NUMBER now number each day sequentially and save it in the DayNumber column
*/
Select CalendarDate, Row_Number() Over (Order By CalendarDate) DayNumber
From FullCalendar
Where ((DATEPART(dw, CalendarDate) + ##DATEFIRST) % 7) NOT IN (0, 1)
)
--Select * From CalendarWithNumbers
--UnComment just the line above, and comment everything below to see the result just up to there
,StartingDate as
(
/* Get the DayNumber (from the query above) for the starting day*/
Select *
From CalendarWithNumbers
Where 1=1
And CalendarDate = #StartDate
)
--Select * From StartingDate
--UnComment just the line above, and comment everything below to see the result just up to there
/*
Now calculate 45 days from the daynumber in starting date
that gives you the value for NDays from StartDate
*/
Select
SD.CalendarDate,
Sd.DayNumber,
CWN.CalendarDate NDaysFromNow,
CWN.DayNumber
From CalendarWithNumbers CWN
Inner Join StartingDate SD
On CWN.DayNumber = SD.DayNumber + 345
My guess is that SQL Server decides to do things in different order than you think, and actually this:
;With NumberList AS
(
Select *
From
(
Select Rank() Over(Order By S1.Id, S2.Id) Number
From master.dbo.SysObjects S1, master.dbo.SysObjects S2
) XX
Where Number < 1000
)
Doesn't get filtered to 999 rows before this happens;
select Cast (dateadd(day, number, DATEADD(yy, DATEDIFF(yy,0,getdate()), 0)) as Date) CalendarDate
Even though that's what you expect to happen. If you change it to this, the error will go away:
;With NumberList AS
(
Select top 999 row_number() Over(Order By (select null)) Number
From master.dbo.SysObjects S1 cross join master.dbo.SysObjects S2
)
Using top is usually a lot better option, because that gives the optimizer a better understanding about how many rows will be actually coming from there, instead of having to decide when to filter out rows with Number >= 1000 (or what the distribution of the numbers might be)
This works ,,,, just replaced the query that generates the NumberList
Declare #StartDate Date = '12/01/2015' --<-- I would use '20151201'
;With NumberList AS
(
Select *
From
(
Select ROW_NUMBER() Over(Order By (SELECT NULL)) Number
From master..spt_values S1
) XX
Where Number < 1000
)
,FullCalendar as
(
select Cast (dateadd(day, number, DATEADD(yy, DATEDIFF(yy,0,getdate()), 0)) as Date) CalendarDate
from Numberlist
)
,CalendarWithNumbers As
(
Select CalendarDate, Row_Number() Over (Order By CalendarDate) DayNumber
From FullCalendar
Where ((DATEPART(dw, CalendarDate) + ##DATEFIRST) % 7) NOT IN (0, 1)
)
,StartingDate as
(
Select *
From CalendarWithNumbers
Where 1=1
And CalendarDate = #StartDate
)
Select #StartDate StartDate, CalendarDate NDaysFromNow, DayNumber
From CalendarWithNumbers
Where DayNumber = (Select DayNumber + 45 from StartingDate)
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