Index on view with CTE sql server - sql-server

I create a view like this. Can I add an clustered index on the column "Start" and will it have any effect?
create view v_Weeks with SchemaBinding as
with Periodes AS
(
SELECT start = CONVERT(Date,'2013-08-05')
union ALL
SELECT DateAdd(day,7,start) from Periodes where start < dateadd(year,2,GETDATE())
)
select Start,DATEADD(DAY,7,Start) as [End], datepart(ISO_WEEK,start) as week, DATEPART(YEAR,Start) as Year from Periodes

You need a calendar table. This is exactly what you are trying to do without technically doing it.
If you just need one day per week and only for two years than you could use something like this:
declare #fromdate date = '20130805';
declare #thrudate date = '20150805';
create table v_Weeks (
[Start] date not null primary key
, iso_week tinyint not null
, [year] smallint not null
);
;with n as (select n from (values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(n))
, dates as (
select top (datediff(week,#fromdate,#thrudate)+1)
[Date]=convert(date
,dateadd(week, row_number() over (order by (select 1)) -1, #fromdate)
)
from n as deka cross join n as hecto cross join n as kilo cross join n as tenK
order by [Date]
)
insert into v_weeks
select
Start = [Date]
, iso_week = datepart(iso_week,[date])
, [year] = datepart(year,[date])
from dates ;
Calendar and Numbers table references:
Calendar Tables - Why You Need One - David Stein
Generate a set or sequence without loops - 1 - Aaron Bertrand
Generate a set or sequence without loops - 3 - Aaron Bertrand
Creating a Date Table/Dimension in SQL Server 2008 - David Stein
Creating a date dimension or calendar table in SQL Server - Aaron Bertrand
TSQL Function to Determine Holidays in SQL Server - Tim Cullen
The "Numbers" or "Tally" Table: What it is and how it replaces a loop - Jeff Moden

Related

How can I get, and re-use, the next 6 *dates* from today in SQL Server?

I need to create a CTE I can re-use that will hold seven dates. That is today and the next six days.
So, output for today (4/22/2022) should be:
2022-04-22
2022-04-23
2022-04-24
2022-04-25
2022-04-26
2022-04-27
2022-04-28
So far, I have this:
WITH seq AS
(
SELECT 0 AS [idx]
UNION ALL
SELECT [idx] + 1
FROM seq
WHERE [idx] < 6
)
SELECT DATEADD(dd, [idx], CONVERT(date, GETDATE()))
FROM seq;
The problem is my SELECT is outside the WITH, so I would need to wrap this whole thing with another WITH to re-use it, for example to JOIN on it as a list of dates, and I'm not having luck getting that nested WITH to work. How else could I accomplish this?
To be clear: I'm not trying to find records in a specific table full of dates that are from the next seven days. There are plenty of easy solutions for that. I need a list of dates for today and the next six days, that I can re-use in other queries as a CTE.
You're close. Here's an example:
with cte as (
select
1 as n
,GETDATE() as dt
union all
select
n+1
,DATEADD(dd,n,GETDATE()) as dt
from cte
where n <= 6
)
select * from cte
Fiddle here
You can create a view for reusability and simply query the view rather than using the same CTE over and over again.
You can do this by adding a second column for the date to the CTE:
WITH seq AS (
SELECT 0 AS [idx], cast(current_timestamp as date) as date
UNION ALL
SELECT [idx] + 1, dateadd(dd, idx+1, cast(current_timestamp as date))
FROM seq
WHERE [idx] < 6
)
SELECT *
FROM seq;
See it here:
https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=208ecd76be2071529078f38b1735b0cd
Another option is you can "stack" CTEs, rather than nest, to avoid the second column:
WITH seq0 AS (
SELECT 0 AS [idx]
UNION ALL
SELECT [idx] + 1
FROM seq0
WHERE [idx] < 6
),
seq As (
SELECT dateadd(dd, idx, cast(current_timestamp as date)) as idx
FROM seq0
)
SELECT *
FROM seq;
Note how the final query only needed to reference the 2nd CTE.
See it here:
https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=22315438e4710792f368009cc6ff6451
Never recommend using recursion if you don't have a need for it. It's more complex and slower. I'd just use a hardcoded list of numbers, could encapsulate it in TVF if you wanted to reuse it across different stored procedures/functions. If you need to reuse it in 1 stored proc in multiple places, I'd just throw it in a temp table.
CTE Version without Recursion
WITH cte_7days AS (
SELECT theDate = CAST(DATEADD(dd,num,GETDATE()) AS DATE)
FROM (VALUES (0),(1),(2),(3),(4),(5),(6)) AS A(num)
)
SELECT *
FROM cte_7days
CROSS APPLY Version to Remove Need for CTE
Could use something like this as your base query, and then just add more joins below table depending on your query
SELECT theDate
FROM (VALUES (0),(1),(2),(3),(4),(5),(6)) AS A(num)
CROSS APPLY (SELECT theDate = CAST(DATEADD(DAY,num,GETDATE()) AS DATE)) AS B
TVF Version
CREATE FUNCTION dbo.uf_7days()
RETURNS TABLE AS
RETURN
(
SELECT theDate
FROM (VALUES (0),(1),(2),(3),(4),(5),(6)) AS A(num)
CROSS APPLY (SELECT theDate = CAST(DATEADD(DAY,num,GETDATE()) AS DATE)) AS B
)
GO

dynamic SQL - Allocate count of midnights to correct month

This is a fairly common issue that I've seen a lot of people duck tape together solutions for, but it's never quite right. Hoping this forum can get it ironed out. I have a table:
create table temp
( PatientID varchar(12),
AdmitDate datetime,
DischargeDate datetime
)
insert into temp values ('Patient1','1/30/2020 13:23:44', '2/2/2020 15:12:52')
What I'd like to count is the number of midnights the patient was admitted in the correct month. So in the example above the patient would be admitted at midnight on 1/31, 2/1, and 2/2 dates. So my output in sql should look something like:
01-2020 02-2020
------- --------
1 2
I know it has to be dynamic SQL, because the columns need to be created with respect to the date range queried. Although, I'm pretty stumped as to next steps.
create table #temp
( PatientID varchar(12),
AdmitDate datetime,
DischargeDate datetime
)
insert into #temp values ('Patient1','1/30/2020 13:23:44', '2/2/2020 15:12:52')
--Virtually creates a dates table
;with dates(thedate) as (
select dateadd(yy,years.number,0)+days.number
from master..spt_values years
join master..spt_values days
on days.type='P' and days.number < datepart(dy,dateadd(yy,years.number+1,0)-1)
where years.type='P' and years.number between 100 and 150
-- note: 100-150 creates dates in the year range 2000-2050
-- adjust as required
)
select dateadd(m,datediff(m, 0, d.thedate),0) [Month], count(1) PatientDays
from dates d
join #temp t on d.thedate between t.[AdmitDate] and t.[DischargeDate]
group by datediff(m, 0, d.thedate)
order by [Month];

Month Difference Without additional table

I am new to SQL query world and got stuck into one requirement.
In my Query i have toDate and fromdate input parameter, based on business logic it will return result like below.
Result:-
Month
Dec-16
Dec-16
Dec-16
Feb-17
Feb-17
Mar-17
Mar-17
now query should need to return the data for each month , if we dont have data for perticular month(in image which is Jan) then it should insert data and return data for that month too, in image we can see for Jan we dont have any data.
You can use a calendar or dates table for this sort of thing.
Without a calendar table, you can generate an adhoc set of months using a common table expression with just this:
declare #fromdate date = '20161201';
declare #todate date = '20170301';
;with n as (select n from (values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(n))
, Months as (
select top (datediff(month, #fromdate, #todate)+1)
[Month]=convert(date,dateadd(month,row_number() over(order by (select 1))-1,#fromdate))
from n as deka cross join n as hecto cross join n as kilo
order by [Month]
)
/* your query here: */
select
d.[Month]
, sum_col = sum(t.col)
from Months
left join tbl t
on d.[Month] = t.[Month]
group by d.[Month]
Number and Calendar table reference:
Generate a set or sequence without loops - 2 - Aaron Bertrand
The "Numbers" or "Tally" Table: What it is and how it replaces a loop - Jeff Moden
Creating a Date Table/Dimension in sql Server 2008 - David Stein
Calendar Tables - Why You Need One - David Stein
Creating a date dimension or calendar table in sql Server - Aaron Bertrand
Solved Query:-
Declare #customDate DATETIME
declare #datafound integer
set #customDate = #fromDate
WHILE #customDate < #toDate
BEGIN
select #datafound = count(1) from #temp where datepart(month, MonthDate) = datepart(month, #customDate)
if #datafound = 0
select Format(#customDate,'MMM-yy') as Month
SET #customDate = DATEADD(month, 1,#customDate)
END;

SQL Server 2008 : create data where there is none?

I am struggling to create data where there is no data, I have a query that returns similar data to this:
This table shows that for client 123 we had payments in June, July and December only.
The only notable item in the query is a DATEDIFF between the month opened and MonthPayment (this gives the mnth column).
Now where I’m falling over is I need to create the above columns but for all the months that have passed regardless like below
Ignore the month payment field for the none paying months, this shouldn't return anything!
What you need to do is connect this table to a list of all values you might want, as in the question pointed to by Zohar Peled. Your case is slightly complicated, since presumably you need to be able to return multiple clients at a time and only want to see data that pertains to that client's start and end range. I've adapted code from a similar answer I posted some time ago, which should show you how this is done.
-- set up some sample data
DECLARE #MyTable TABLE
(
ClientNo INT,
Collected NUMERIC(5,2),
MonthPayment DATETIME,
MonthOpened DATETIME,
CurrentMonth DATETIME
)
INSERT INTO #MyTable
(
ClientNo,
Collected,
MonthPayment,
MonthOpened,
CurrentMonth
) -- note: I'm in the US, so I'm using the US equivalent of the months you asked for
SELECT 123, 147.25, '7/1/2014', '12/1/2013', '4/1/2015'
UNION
SELECT 123, 40, '12/1/2014', '12/1/2013', '4/1/2015'
UNION
SELECT 123, 50, '6/1/2014', '12/1/2013', '4/1/2015'
-- create a recursive CTE that contains a list of all months that you could wish to see
--define start and end limits
Declare #todate datetime, #fromdate datetime
Select #fromdate=(SELECT MIN(MonthOpened) FROM #MyTable), #todate=DATEADD(MONTH, 1, GETDATE())
-- define CTE
;With DateSequence( DateValue ) as
(
Select #fromdate as DateValue
union all
Select dateadd(MONTH, 1, DateValue)
from DateSequence
where DateValue < #todate
)
--select result
SELECT
ClientStartEnd.ClientNo,
ISNULL(MyTable.Collected, 0.00) AS Collected,
DateSequence.DateValue AS MonthPayment,
ClientStartEnd.MonthOpened,
DATEDIFF(MONTH, ClientStartEnd.MonthOpened, DateSequence.DateValue) + 1 AS Mnth,
ClientStartEnd.CurrentMonth
FROM
DateSequence
INNER JOIN
(
SELECT DISTINCT
ClientNo,
MonthOpened,
CurrentMonth
FROM #MyTable
) ClientStartEnd ON
DateSequence.DateValue BETWEEN
ClientStartEnd.MonthOpened AND
ClientStartEnd.CurrentMonth
LEFT JOIN
#MyTable MyTable ON
ClientStartEnd.ClientNo = MyTable.ClientNo AND
DateSequence.DateValue = MyTable.MonthPayment
OPTION (MaxRecursion 0)

How can I order by count with pagination?

I have to migrate some SQL from PostgreSQL to SQL Server (2005+). On PostgreSQL i had:
select count(id) as count, date
from table
group by date
order by count
limit 10 offset 25
Now i need the same SQL but for SQL Server. I did it like below, but get error: Invalid column name 'count'. How to solve it ?
select * from (
select row_number() over (order by count) as row, count(id) as count, date
from table
group by date
) a where a.row >= 25 and a.row < 35
You can't reference an alias by name, at the same scope, except in an ending ORDER BY (it is an invalid reference inside of a windowing function at the same scope).
To get the exact same results, it may need to be extended to (nesting scope for clarity):
SELECT c, d FROM
(
SELECT c, d, ROW_NUMBER() OVER (ORDER BY c) AS row FROM
(
SELECT d = [date], c = COUNT(id) FROM dbo.table GROUP BY [date]
) AS x
) AS y WHERE row >= 25 AND row < 35;
This can be shortened a little bit as per mohan's answer.
SELECT c, d FROM
(
SELECT COUNT(id), [date], ROW_NUMBER() OVER (ORDER BY COUNT(id))
FROM dbo.table GROUP BY [date]
) AS y(c, d, row)
WHERE row >= 25 AND row < 35;
In SQL Server 2012, it's much easier with OFFSET / FETCH - closer to the syntax you're used to, but actually using ANSI-compatible syntax rather than proprietary voodoo.
SELECT c = COUNT(id), d = [date]
FROM dbo.table GROUP BY [date]
ORDER BY COUNT(id)
OFFSET 25 ROWS FETCH NEXT 10 ROWS ONLY;
I blogged about this functionality in 2010 (lots of good comments there too) and should probably invest some time doing some serious performance tests.
And I agree with #ajon - I hope your real tables, columns and queries don't abuse reserved words like this.
It works
DECLARE #startrow int=0,#endrow int=0
;with CTE AS (
select row_number() over ( order by count(id)) as row,count(id) AS count, date
from table
group by date
)
SELECT * FROM CTE
WHERE row between #startrow and #endrow
I think this will do it
select * from (
select row_number() over (order by id) as row, count(id) as count, date
from table
group by date
) a where a.row >= 25 and a.row < 35
Also, I don't know what version of SQL Server you are using but SQL Server 2012 has a new Paging feature

Resources