How to get count of CTE result? - sql

I am new to sql, I have created a CTE now I want to get the count of rows from CTE result set
DECLARE #start_date date,#end_date DATE ;
select #start_date= min(ETA) from [dbo].[testTable]
select #end_date=max(ETA) from [dbo].[testTable];
;WITH AllDays
AS (
SELECT #start_date AS [Date]
--, 1 AS [level]
UNION ALL
SELECT DATEADD(DAY, 1, [Date])
--, [level] + 1
FROM AllDays
WHERE [Date] < #end_date )
--Insert into #tempETA (CallETA)
SELECT [Date]--, [level]
FROM AllDays OPTION (MAXRECURSION 0)
select count(a.Date),a.Date from AllDays a
I am getting error here:
(1048 row(s) affected)
Msg 208, Level 16, State 1, Line 20
Invalid object name 'AllDays'.

A CTE can hold temporary result set that is defined within the execution scope of a single SELECT, INSERT, UPDATE, DELETE. You cannot have another SELECT outside the scope of CTE
Use ##ROWCOUNT to get the count of CTE. Considering you want the CTE result and its count.
SELECT [Date]--, [level]
FROM AllDays OPTION (MAXRECURSION 0)
select ##ROWCOUNT
If you want to count to be part of your result then use COUNT() OVER()
SELECT [Date],count(1)over() as Total_count
FROM AllDays OPTION (MAXRECURSION 0)

Check out this below code and tell.is this what you want to get.?
DECLARE #start_date date,#end_date DATE ;
select #start_date= min(ETA) from [dbo].[testTable]
select #end_date=max(ETA) from [dbo].[testTable];
;WITH AllDays
AS (
SELECT #start_date AS [Date]
--, 1 AS [level]
UNION ALL
SELECT DATEADD(DAY, 1, #start_date)
--, [level] + 1
FROM [testTable]
WHERE #start_date < #end_date )
--Insert into #tempETA (CallETA)
--SELECT [Date]--, [level]
--FROM AllDays OPTION (MAXRECURSION 0)
select count(a.Date) from AllDays a

Related

Based on day fetch all dates - sql

I have start date, end date and name of days. How can fetch all dates between those two dates of that specific days in sql?
example data:
start_date:4/11/2018
end_date: 5/11/2018
days: monday, thursday
expected output: all dates between start and end date which comes on monday and thursday and store them in table
updated
my present code(not working)
; WITH CTE(dt)
AS
(
SELECT #P_FROM_DATE
UNION ALL
SELECT DATEADD(dw, 1, dt) FROM CTE
WHERE dt < #P_TO_DATE
)
INSERT INTO Table_name
(
ID
,DATE_TIME
,STATUS
,CREATED_DATE
,CREATED_BY
)
SELECT #P_ID
,(SELECT dt FROM CTE WHERE DATENAME(dw, dt) In ('tuesday','friday',null))
,'NOT SENT'
,CAST(GETDATE() AS DATE)
,#USER_ID
Another approach for generating dates between ranges can be like following query. This will be faster compared to CTE or WHILE loop.
DECLARE #StartDate DATETIME = '2018-04-11'
DECLARE #EndDate DATETIME = '2018-05-15'
SELECT #StartDate + RN AS DATE FROM
(
SELECT (ROW_NUMBER() OVER (ORDER BY (SELECT NULL)))-1 RN
FROM master..[spt_values] T1
) T
WHERE RN <= DATEDIFF(DAY,#StartDate,#EndDate)
AND DATENAME(dw,#StartDate + RN) IN('Monday','Thursday')
Note:
If the row count present in master..[spt_values] is not sufficient for the provided range, you can make a cross join with the same to get a bigger range like following.
SELECT (ROW_NUMBER() OVER (ORDER BY (SELECT NULL)))-1 RN
FROM master..[spt_values] T1
CROSS JOIN master..[spt_values] T2
By this you will be able to generate date between a range with gap of 6436369 days.
You can use a recursive common table expression (CTE) to generate a list of days. With datepart(dw, ...) you can filter for specific days of the week.
An example that creates a list of Mondays and Thursdays between March 1st and today:
create table ListOfDates (dt date);
with cte as
(
select cast('2018-03-01' as date) as dt -- First day of interval
union all
select dateadd(day, 1, dt)
from cte
where dt < getdate() -- Last day of interval
)
insert into ListOfDates
(dt)
select dt
from cte
where datepart(dw, dt) in (2, 5) -- 2=Monday and 5=Thursday
option (maxrecursion 0)
See it working at SQL Fiddle.
This will work for you:
DECLARE #table TABLE(
ID INT IDENTITY(1,1),
Date DATETIME,
Day VARCHAR(50)
)
DECLARE #Days TABLE(
ID INT IDENTITY(1,1),
Day VARCHAR(50)
)
INSERT INTO #Days VALUES ('Monday')
INSERT INTO #Days VALUES ('Thursday')
DECLARE #StartDate DATETIME='2018-01-01';
DECLARE #EndDate DATETIME=GETDATE();
DECLARE #Day VARCHAR(50)='Friday';
DECLARE #TempDate DATETIME=#StartDate;
WHILE CAST(#TempDate AS DATE)<=CAST(#EndDate AS DATE)
BEGIN
IF EXISTS (SELECT 1 FROM #Days WHERE DAY IN (DATENAME(dw,#TempDate)))
BEGIN
INSERT INTO #table
VALUES (
#TempDate, -- Date - datetime
DATENAME(dw,#TempDate) -- Day - varchar(50)
)
END
SET #TempDate=DATEADD(DAY,1,#TempDate)
END
SELECT * FROM #table
INSERT INTO TargetTab(dateCOL)
SELECT dateCOL
FROM tab
WHERE dateCOL >= startdate AND dateCOL <= enddate
AND (DATENAME(dw,dateCOL) ='Thursday' OR DATENAME(dw,dateCOL) = 'Monday')
Try this query to get your result.
Use a recursive CTE to generate your dates, then filter by week day.
SET DATEFIRST 1 -- 1: Monday, 7 Sunday
DECLARE #StartDate DATE = '2018-04-11'
DECLARE #EndDate DATE = '2018-05-15'
DECLARE #WeekDays TABLE (WeekDayNumber INT)
INSERT INTO #WeekDays (
WeekDayNumber)
VALUES
(1), -- Monday
(4) -- Thursday
;WITH GeneratingDates AS
(
SELECT
GeneratedDate = #StartDate,
WeekDay = DATEPART(WEEKDAY, #StartDate)
UNION ALL
SELECT
GeneratedDate = DATEADD(DAY, 1, G.GeneratedDate),
WeekDay = DATEPART(WEEKDAY, DATEADD(DAY, 1, G.GeneratedDate))
FROM
GeneratingDates AS G -- Notice that we are referencing a CTE that we are also declaring
WHERE
G.GeneratedDate < #EndDate
)
SELECT
G.GeneratedDate
FROM
GeneratingDates AS G
INNER JOIN #WeekDays AS W ON G.WeekDay = W.WeekDayNumber
OPTION
(MAXRECURSION 30000)
Try this:
declare #start date = '04-11-2018'
declare #end date = '05-11-2018'
declare #P_ID int = 1
declare #USER_ID int = 11
;with cte as(
select #start [date]
union all
select dateadd(DAY, 1, [date]) from cte
where [date] < #end
)
--if MY_TABLE doesn't exist
select #P_ID,
[date],
'NOT SENT',
cast(getdate() as date),
#USER_ID
into MY_TABLE
from cte
--here you can specify days: 1 - Sunday, 2 - Monday, etc.
where DATEPART(dw,[date]) in (2, 5)
option (maxrecursion 0)
--if MY_TABLE does exist
--insert into MY_TABLE
--select #P_ID,
-- [date],
-- 'NOT SENT',
-- cast(getdate() as date),
-- #USER_ID
--from cte
--where DATEPART(dw,[date]) in (2, 5)
--option (maxrecursion 0)

On MS SQL, how to create a table that only consists of dates and days of the week?

I am need to create a table on MS SQL 2014 showing a date and a weekday column. It need to start at 2014-01-01 and the last day should be today. It should look like the one bellow:
days_date weekday
2014-01-01 Wednesday
2014-01-02 Thursday
2014-01-03 Friday
... ...
2018-03-06 Tuesday
My relevant script is here:
CREATE TABLE [dbo].[new_table](
[days_date] [date] NOT NULL,
[weekday] [nvarchar](50) NULL
) ON [PRIMARY]
GO
WITH CTE (DT) AS
(
SELECT CAST('2014-01-01' AS DATE) DT
UNION ALL
SELECT DATEADD(DAY, 1, DT)
FROM CTE
WHERE DATEADD(DAY, 1, DT) < '2018-03-06'
)
INSERT INTO [dbo].[new_table]
([days_date]
,[weekday])
VALUES
(select * from CTE,
,select DATENAME(CTE,GETDATE()))
GO
Getting some errors here:
Msg 156, Level 15, State 1, Line 13
Incorrect syntax near the keyword 'select'.
Msg 156, Level 15, State 1, Line 14
Incorrect syntax near the keyword 'SELECT'.
Msg 155, Level 15, State 1, Line 14
'CTE' is not a recognized datename option.
How should I fix the script?
you had some syntax errors in your insert query.
More, the datename you was trying to insert was related to getdate() which (i guess) is not your target. You should refer to your CTE date alias: DT
try this:
CREATE TABLE [dbo].[new_table](
[days_dates] [date] NOT NULL,
[weekday] [nvarchar](50) NULL
) ON [PRIMARY]
GO
WITH CTE (DT) AS
(
SELECT CAST('2014-01-01' AS DATE) DT
UNION ALL
SELECT DATEADD(DAY, 1, DT)
FROM CTE
WHERE DATEADD(DAY, 1, DT) < '2018-03-06'
)
INSERT INTO [dbo].[new_table]
([days_dates]
,[weekday])
(select * , DATENAME(WEEKDAY,dt) from CTE)
OPTION (MaxRecursion 0)
GO
You Can't use a SELECT statement inside the Values, But instead, you replace values using the select.
Change your insert Query as below
;WITH CTE
(
DT
) AS
(
SELECT CAST('2014-01-01' AS DATE) DT
UNION ALL
SELECT
DATEADD(DAY, 1, DT)
FROM CTE
WHERE DATEADD(DAY, 1, DT) < '2018-03-06'
)
INSERT INTO [dbo].[new_table]
(
[days_date],
[weekday]
)
select
DT,
DATENAME(DW,DT)
from CTE
OPTION(maxrecursion 0)
-- The following error message Will encountered:
Msg 530, Level 16, State 1, Line 1
The statement terminated.
The maximum recursion 100 has been exhausted before statement completion.
To overcome this error message, the MAXRECURSION query hint can be specified to increase the maximum number of recursion from the default value of 100 to a maximum of 1000.
CREATE TABLE [dbo].[new_table](
[days_date] [date] NOT NULL,
[weekday] [nvarchar](50) NULL
) ON [PRIMARY]
; WITH CTE (DT) AS
(
SELECT CAST('2014-01-01' AS DATE) DT
UNION ALL
SELECT DATEADD(DAY, 1, DT)
FROM CTE
WHERE DATEADD(DAY, 1, DT) < =GETDATE()
)
INSERT INTO [dbo].[new_table]
([days_date]
,[weekday])
SELECT DT,DATENAME(DW,DT)
FROM CTE OPTION (maxrecursion 0)
GO
SELECT * FROM new_table
-- To Over come Such Issue Use OPTION (maxrecursion 0)

Tsql union in while loop

I am trying to union the same table together with itself while changing some value in the where clause. The problem i have is with the union between the loops. I can not use a table variable since the schema is too complicated to write by hand each time. Temp tables seem to be the way to go but I do not know how to get it to work and the correct syntax.
psuedo code of what I am trying to achieve:
DECLARE #var int, #tempTable
SET #var = someValue
WHILE expressionIncludingVar
#tempTable = SELECT *
FROM someTable
WHERE column = #var
UNION ALL #tempTable
SET #var = someChangeToVar
RETRUN #tempTable
The result of the query should be #tempTable hence the weird "RETURN #tempTable".
Thank you in advance.
EDIT:
Another hardcoded example:
I am trying to unhardcode something like this:
SELECT someAggregateColumns
FROM table
WHERE someDateColumn > #date and < someDateColumn < DATEADD(month, 2, #date)
GROUP BY someColumn
UNION ALL
SELECT someAggregateColumns
FROM table
WHERE someDateColumn > DATEADD(month, 1, #date) and and < someDateColumn < DATEADD(month, 1, DATEADD(month, 3, #date))
GROUP BY someColumn
SELECT someAggregateColumns
FROM table
WHERE someDateColumn = DATEADD(month, 2, #date) DATEADD(month, 1, DATEADD(month, 4, #date))
GROUP BY someColumn
UNION ALL
....etc
Maybe Recursive CTE works for you.
You can try this.
DECLARE #MyTable TABLE(ID INT, ColumnA VARCHAR(10), ColumnB VARCHAR(10))
INSERT INTO #MyTable VALUES
(1,'A', '10'),
(2,'B', '11'),
(3,'C', '12'),
(4,'D', '13'),
(5,'E', '14'),
(6,'F', '15'),
(7,'H', '16')
DECLARE #var INT = 4
;WITH CTE AS (
SELECT * FROM #MyTable WHERE ID = #var
UNION ALL
SELECT T.* FROM CTE INNER JOIN #MyTable T ON CTE.ID - 1 = T.ID
)
SELECT * INTO #tempTable FROM CTE
SELECT * FROM #tempTable
DROP TABLE #tempTable
If the only thing what is different in each cycle is a counter, then why aren't you just write one query including all data?
Instead of WHERE column = #var use WHERE column >= 0 AND column <= #maxVarValue.
If your conditions are more complex, you should consider to have a small (temp) table which contains the columns to be filtered, then just join that table to your source to get the desired result.
According to the comments, you can use a tally table (or numbers table).
Example:
DECLARE #Tally (Number INT);
INSERT INTO #Tally (Number) VALUES (0),(1),(2),(3),(4),(5);
SELECT
someAggregateColumns
FROM
table AGG
INNER JOIN #Tally T
ON AGG.someDateColumn = DATEADD(month, T.Number, #date)
WHERE
T.Number >= 0
AND T.Number <= 3
;
The above query will return the results for the current and the next 3 months.
You can persist a numbers table and re-use it. I usually have one called util.Number.
based in second example what you want is
SELECT someAggregateColumns
FROM table
WHERE someDateColumn IN (
#date,
DATEADD(month, 1, #date),
DATEADD(month, 2, #date),
DATEADD(month, 3, #date)
)
GROUP BY someColumn
Now if you want a range is even easier:
WHERE someDateColumn BETWEEN #date
AND DATEADD(month, 3, #date)
Using a TALLY table and a CROSS APPLY you can generate the Dates to check:
DECLARE #Var INT = 4
DECLARE #Date Date = '2017-01-01'
;WITH Tally
AS
(
SELECT ROW_NUMBER() OVER (ORDER BY Object_Id) -1 AS Num
FROM sys.columns
)
SELECT *
FROM MyTable
CROSS APPLY Tally
WHERE Num < #var AND
MyDate = DATEADD(month, num, #date)

Simplest way to fill working day table

I have a table working_days with one column date of type date
I need to fill it with working days in USA.
Can you suggest how can I so this?
Manually it is too long.
You can use a recursive CTE to accomplish this. This only excludes the weekends. Using DATEFIRST you can figure out what day is a weekend. This query should work no matter what day of the week is set to DATEFIRST.
;WITH DatesCTE
AS (
SELECT CAST('2016-01-01' AS DATE) AS [workingDays]
UNION ALL
SELECT DATEADD(DAY, 1, workingdays)
FROM DatesCTE
WHERE DATEADD(DAY, 1, workingdays) < '2017-01-01'
)
SELECT *
FROM DatesCTE
WHERE ((DATEPART(dw, workingDays) + ##DATEFIRST) % 7) NOT IN (0, 1)
OPTION (MAXRECURSION 366)
At first fill your table with all dates for year (for example 2016):
DECLARE #date_start date = '2016-01-01',
#date_end date = '2016-12-31';
WITH cte as (
SELECT #date_start as [d], 0 as Level
UNION ALL
SELECT DATEADD(day,1,[d]), [level] + 1 as [level]
from cte
WHERE [level] < DATEDIFF(day,#date_start,#date_end)
),
holidays as ( --table with holidays
SELECT * FROM (VALUES
('2016-01-01'),
('2016-01-18'),
('2016-02-15'),
('2016-05-30'),
('2016-07-04'),
('2016-09-05'),
('2016-10-10'),
('2016-11-11'),
('2016-11-24'),
('2016-12-26')) as t(d)
)
SELECT c.d
FROM cte c
LEFT JOIN holidays h on c.d=h.d
WHERE DATEPART(WEEKDAY,d) NOT IN (1,7) --will show only monday-friday
AND AND h.d is NULL
OPTION (MAXRECURSION 1000); --if you need more than 3 years get MAXRECURSION up
A simple loop will do:
declare #d date = '20160101';
while #d <= '20161231'
begin
if datepart(weekday, #d) not in (1, 7) and <#d not a holiday>
insert into working_days ("date") values (#d);
set #d = dateadd(day, 1, #d);
end

Calculation of date in function

1 28/11/2011 ...
How do I write a function in SQL to implement the above pattern?
You could do something like this in SQL Server:
DECLARE #BaseDate DATE = '20111107';
DECLARE #EndDate DATE = GETDATE(); --Or the "end of dates in the database"
WITH RecursiveCTE AS (
SELECT
1 AS [Counter],
#BaseDate AS [MyDate]
UNION ALL
SELECT
[Counter] + 1,
DATEADD(DAY, 7, MyDate)
FROM
RecursiveCTE
WHERE
MyDate < #EndDate)
SELECT * FROM RecursiveCTE OPTION (MAXRECURSION 0);
To handle dates that aren't exact and make this into a function you would do this:
--Function definition
CREATE FUNCTION SuperDuperDataCalculator (
#BaseDate DATE = '20131016',
#EndDate DATE = '20131020')
RETURNS #Results TABLE (
[Counter] INT,
[Date] DATE)
AS
BEGIN
WITH RecursiveCTE AS (
SELECT
1 AS [Counter],
#BaseDate AS [MyDate]
UNION ALL
SELECT
[Counter] + 1,
CASE WHEN DATEADD(DAY, 7, MyDate) > #EndDate THEN #EndDate ELSE DATEADD(DAY, 7, MyDate) END
FROM
RecursiveCTE
WHERE
MyDate < #EndDate)
INSERT INTO
#Results
SELECT * FROM RecursiveCTE OPTION (MAXRECURSION 0);
RETURN;
END;
GO
--Usage
SELECT * FROM SuperDuperDataCalculator('20131016', '20131020');
--Results
Counter Date
1 2013-10-16
2 2013-10-20
Note that we have to use a multi-statement table-valued function as there is a bug in SQL Server where it won't let you use OPTIONs in a simple table-valued function. The alternative would be to remove the OPTION (MAXRECURSION 0) from the function and remember to use this every time you reference it (i.e. a pretty poor alternative).
...and finally, if you wanted to just return the maximum counter value you could rewrite this as a scalar-valued function, i.e.:
--Function definition
CREATE FUNCTION SuperDuperDataCalculator (
#BaseDate DATE = '20131016',
#EndDate DATE = '20131020')
RETURNS INT
AS
BEGIN
DECLARE #Results TABLE (
[Counter] INT,
[Date] DATE);
DECLARE #ReturnValue INT;
WITH RecursiveCTE AS (
SELECT
1 AS [Counter],
#BaseDate AS [MyDate]
UNION ALL
SELECT
[Counter] + 1,
CASE WHEN DATEADD(DAY, 7, MyDate) > #EndDate THEN #EndDate ELSE DATEADD(DAY, 7, MyDate) END
FROM
RecursiveCTE
WHERE
MyDate < #EndDate)
INSERT INTO
#Results
SELECT * FROM RecursiveCTE OPTION (MAXRECURSION 0);
SELECT #ReturnValue = MAX([Counter]) FROM #Results;
RETURN #ReturnValue;
END;
GO
SELECT dbo.SuperDuperDataCalculator('20131016', '20131020');
Try this - It will get all the weeks and assign a rownumber in the subquery. Then only select the records where row number = 1 because there might be more results for that week. So hence RowNo = 1
SELECT ROW_NUMBER() OVER(ORDER BY RowNo) AS IncrementalWeek,dte
FROM
(
SELECT DISTINCT DATEPART(ww,CONVERT(VARCHAR(20),createdDate,111)) AS [week],
CONVERT(VARCHAR(20),createdDate,111) AS dte,
ROW_NUMBER() OVER(PARTITION BY DATEPART(ww,Convert(VARCHAR(20),createdDate,111)) ORDER BY DATEPART(ww,CONVERT(VARCHAR(20),createdDate,111))) AS RowNo
FROM YourTable
) AS tble
WHERE RowNo = 1
ORDER BY [week]