Related
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
SELECT logcount, logUserID, maxlogtm
, DATEDIFF(day, maxlogtm, GETDATE()) AS daysdiff
FROM statslogsummary
WHERE daysdiff > 120
I get
"invalid column name daysdiff".
Maxlogtm is a datetime field. It's the little stuff that drives me crazy.
SELECT
logcount, logUserID, maxlogtm,
DATEDIFF(day, maxlogtm, GETDATE()) AS daysdiff
FROM statslogsummary
WHERE ( DATEDIFF(day, maxlogtm, GETDATE() > 120)
Normally you can't refer to field aliases in the WHERE clause. (Think of it as the entire SELECT including aliases, is applied after the WHERE clause.)
But, as mentioned in other answers, you can force SQL to treat SELECT to be handled before the WHERE clause. This is usually done with parenthesis to force logical order of operation or with a Common Table Expression (CTE):
Parenthesis/Subselect:
SELECT
*
FROM
(
SELECT
logcount, logUserID, maxlogtm,
DATEDIFF(day, maxlogtm, GETDATE()) AS daysdiff
FROM statslogsummary
) as innerTable
WHERE daysdiff > 120
Or see Adam's answer for a CTE version of the same.
If you want to use the alias in your WHERE clause, you need to wrap it in a sub select, or CTE:
WITH LogDateDiff AS
(
SELECT logcount, logUserID, maxlogtm
, DATEDIFF(day, maxlogtm, GETDATE()) AS daysdiff
FROM statslogsummary
)
SELECT logCount, logUserId, maxlogtm, daysdiff
FROM LogDateDiff
WHERE daysdiff > 120
The most effective way to do it without repeating your code is use of HAVING instead of WHERE
SELECT logcount, logUserID, maxlogtm
, DATEDIFF(day, maxlogtm, GETDATE()) AS daysdiff
FROM statslogsummary
HAVING daysdiff > 120
If you don't want to list all your columns in CTE, another way to do this would be to use outer apply:
select
s.logcount, s.logUserID, s.maxlogtm,
a.daysdiff
from statslogsummary as s
outer apply (select datediff(day, s.maxlogtm, getdate()) as daysdiff) as a
where a.daysdiff > 120
How about using a subquery(this worked for me in Mysql)?
SELECT * from (SELECT logcount, logUserID, maxlogtm
, DATEDIFF(day, maxlogtm, GETDATE()) AS daysdiff
FROM statslogsummary) as 'your_alias'
WHERE daysdiff > 120
HAVING works in MySQL
according to documentation:
The HAVING clause was added to SQL because the WHERE keyword could not
be used with aggregate functions.
You could refer to column alias but you need to define it using CROSS/OUTER APPLY:
SELECT s.logcount, s.logUserID, s.maxlogtm, c.daysdiff
FROM statslogsummary s
CROSS APPLY (SELECT DATEDIFF(day, s.maxlogtm, GETDATE()) AS daysdiff) c
WHERE c.daysdiff > 120;
DBFiddle Demo
Pros:
single definition of expression(easier to maintain/no need of copying-paste)
no need for wrapping entire query with CTE/outerquery
possibility to refer in WHERE/GROUP BY/ORDER BY
possible better performance(single execution)
For me, the simplest way to use an ALIAS in the WHERE clause is to create a sub-query and select from it instead.
Example:
WITH Q1 AS (
SELECT LENGTH(name) AS name_length,
id,
name
FROM any_table
)
SELECT id, name, name_length FROM Q1 WHERE name_length > 0
Came here looking something similar to that, but with a CASE WHEN, and ended using the where like this: WHERE (CASE WHEN COLUMN1=COLUMN2 THEN '1' ELSE '0' END) = 0 maybe you could use DATEDIFF in the WHERE directly.
Something like:
SELECT logcount, logUserID, maxlogtm
FROM statslogsummary
WHERE (DATEDIFF(day, maxlogtm, GETDATE())) > 120
I have a query where there are instances where a "phase" starts and ends on the same day - this is calculated as 1 day. If, however, another "phase" starts and ends on the same day against the same ref. no. and period no., then I'd like to calculate this as 0 days.
Example:
**Ref. Period. Phase StDt EndDt**
013 3 KAA 01/01/16 01/01/16 - This is one day
013 3 TAA 02/01/16 03/01/16 - this is 2 days
013 3 KAT 01/01/16 01/01/16 - **would like this to be counted as 0 day**
013 3 TTA 04/04/16 04/04/16 - this is one day
I would like this unique calculation to be done in the data grouped by Ref. And Period numbers. This is a tricky one....
Thanks
Try this.
I am assuming that you are using TSQl (Not sure a you have also tagged SQL.
;WITH cte_result(ID,Ref, Period,Phase,StDt,EndDt) AS
(
SELECT 1,'013' ,3,'KAA',CAST('01/01/16'AS DATETIME),CAST('01/01/16'AS DATETIME) UNION ALL
SELECT 2,'013' ,3,'TAA','01/02/16','01/03/16' UNION ALL
SELECT 3,'013' ,3,'KAT','01/01/16','01/01/16' UNION ALL
SELECT 4,'013' ,3,'TTA','04/04/16','04/04/16')
,cte_PreResult AS
(
SELECT ROW_NUMBER() OVER (PARTITION BY CAST(StDt AS DATE), CAST(EndDt AS DATE) ORDER BY ID) AS [Order],
Ref,
Period,
Phase,
StDt,
EndDt
FROM cte_result
)
SELECT Ref,
Period,
Phase,
StDt,
EndDt,
CASE
WHEN [Order] <> 1
THEN '0 Day(s)'
ELSE CAST(DATEDIFF(dd, StDt, EndDt) + 1 AS VARCHAR(10)) + ' Day(s)'
END AS Comment
FROM cte_PreResult
If there is no ID column then select some column to order by, probably Phase so replace ID with Phase as here ROW_NUMBER() OVER (PARTITION BY StDt,EndDt ORDER BY ID) AS [Order], if there is no candidate column to order by then try this
;WITH cte_result(ID,Ref, Period,Phase,StDt,EndDt) AS
(
SELECT 1,'013' ,3,'KAA',CAST('01/01/16'AS DATETIME),CAST('01/01/16'AS DATETIME) UNION ALL
SELECT 2,'013' ,3,'TAA','01/02/16','01/03/16' UNION ALL
SELECT 3,'013' ,3,'KAT','01/01/16','01/01/16' UNION ALL
SELECT 4,'013' ,3,'TTA','04/04/16','04/04/16')
,cte_PreResult AS
(
SELECT ROW_NUMBER() OVER (PARTITION BY CAST(StDt AS DATE), CAST(EndDt AS DATE) ORDER BY (SELECT NULL)) AS [Order],
Ref,
Period,
Phase,
StDt,
EndDt
FROM cte_result
)
SELECT Ref,
Period,
Phase,
StDt,
EndDt,
CASE
WHEN [Order] <> 1
THEN '0 Day(s)'
ELSE CAST(DATEDIFF(dd, StDt, EndDt) + 1 AS VARCHAR(10)) + ' Day(s)'
END AS Comment
FROM cte_PreResult
This expression should work on the SSRS side:
=IIF(Fields!StartDate.Value=Fields!EndDate.Value AND Fields!Phase.Value <> LOOKUPSET(Fields!StartDate.Value &"_" & Fields!EndDate.Value,Fields!StartDate.Value & "_" & Fields!EndDate.Value,Fields!Phase.Value,"DatasetName").GetValue(0),0,DATEDIFF("D",Fields!StartDate.Value,Fields!EndDate.Value)+1)
It will return a value of 1 for the first phase returned by the dataset. If the phase-date range combinations are not unique within the grouping, this will not work as written, but you should be able to modify accordingly.
Also, if the rows are sorted differently between SSRS and the dataset, it may not be the first row that appears that gets the 1.
The below did the trick! Basically, I'm using count aggregate to count the number of instances where phases start and end on the same day PER Ref and period. Then, for any where there are more than 1, I just use simple case statments to count the first one as 1 and any subsequent ones as 0. I'm creating the below as a subquery in the joins as a left outer join:
LEFT OUTER JOIN
(SELECT TOP (100) PERCENT Period, Ref,
CONVERT(date, PhaseStartDate) AS stdt, CONVERT(date, PhaseEndDate) AS enddt,
COUNT(*)
AS NoOfSameDayPhases,
MIN(PhaseSequence) AS FirstPhSeq
FROM Phases AS Phases_1
WHERE (CONVERT(date, PhaseStartDate) =
CONVERT(date, PhaseEndDate))
GROUP BY VoidPeriod, Ref, CONVERT(date,
PhaseStartDate), CONVERT(date, PhaseEndDate)) AS SameDayPH ON CONVERT(date,
PhaseEndDate) = SameDayPH.enddt AND CONVERT(date,
PhaseStartDate) = SameDayPH.stdt AND
VoidPeriod = SameDayPH.VoidPeriod AND SameDayPH.Ref =
VoidPhases.Ref
I have a MS Access query and I want to convert in SQL Server query, any help will be greatly appreciated.
SELECT
dbo_Employees.*,
(SELECT top 1 dbo_attendance.attend_date
FROM dbo_Attendance
WHERE dbo_attendance.ID_Employee=dbo_attendance.ID_Employee
and dbo_attendance.attend_date > dbo_attendance.attend_date
order by dbo_attendance.attend_date asc) AS NextDate,
IIf(IsNull(NextDate),Now(),Nextdate) AS next123,
Next123-dbo_attendance.attend_date AS difference,
dbo_attendance.attend_date,
IIf(dbo_attendance.attend_date+90<Next123,1,0) AS Day90Credit,
IIf(dbo_attendance.attend_date+90<Next123,dbo_attendance.attend_date+90,dbo_attendance.attend_date+365) AS CreditDate,
IIf((Day90Credit=0 And CreditDate<Now()) Or Day90Credit=1,1,0) AS TotalCredit
FROM dbo_attendance, dbo_Employees
WHERE (((dbo_Employees.Employee_ID)=[dbo_attendance].[ID_Employee]));
In sql server (and most every other RDBMS) we use CASE statements instead of iif(). The structure is pretty simple CASE WHEN <condition> THEN <value if true> ELSE <value if false> END.
Changing your iif() over to CASE will be the bulk of the switch over. The first iif() however is better represented as a COALESCE() which allows a list of fields or values. Coalesce will grab the first Non-Null value/field from the list for that record.
The other things that have to be switched is the Date logic. In SQL Server you use DATEADD() to add days (or other date parts like year and month) to a date. you use DATEDIFF() to subtract two dates to get a date part (like Days or Months or Years).
SELECT dbo_Employees.*,
(
SELECT TOP 1 dbo_attendance.attend_date
FROM dbo_Attendance
WHERE dbo_attendance.ID_Employee = dbo_attendance.ID_Employee
AND dbo_attendance.attend_date > dbo_attendance.attend_date
ORDER BY dbo_attendance.attend_date ASC
) AS NextDate,
COALESCE(NextDate, GETDATE()) AS next123,
datediff(day, dbo_attendance.attend_date, COALESCE(NextDate, GETDATE())) AS difference,
dbo_attendance.attend_date,
CASE
WHEN DATEADD(DAY, 90, dbo_attendance.attend_date) < COALESCE(NextDate, GETDATE())
THEN 1
ELSE 0
END AS Day90Credit,
CASE
WHEN DATEADD(DAY, 90, dbo_attendance.attend_date) < COALESCE(NextDate, GETDATE())
THEN dateAdd(DAY, 90, dbo_attendance.attend_date)
ELSE DATEADD(DAY, 365, dbo_attendance.attend_date)
END AS CREDITDATE,
CASE
WHEN (
Day90Credit = 0
AND CreditDate < GETDATE()
)
OR DATEADD(DAY, 90, dbo_attendance.attend_date) < COALESCE(NextDate, GETDATE())
THEN 1
ELSE 0
END AS TotalCredit
FROM dbo_attendance,
dbo_Employees
WHERE dbo_Employees.Employee_ID = [dbo_attendance].[ID_Employee];
Lastly... I can't remember how this works in SQL server since it's been a while since I was in the environment, but you might have to switch instances of dbo_ to dbo.. Your server will cry foul and let you know anyhow.
You can try CTE (Common Table Expressions) in Sql Server for complex calculations, see this link: https://technet.microsoft.com/en-us/library/ms190766(v=sql.105).aspx
I refactored part of your query as below, proceed adding your calculations under WITH block:
WITH Emp_CTE (ID_Employee, attend_date)
AS
(
SELECT emp.*,
(SELECT TOP 1 att.attend_date FROM dbo_Attendance AS att
WHERE att.ID_Employee = emp.ID_Employee
AND att.attend_date > emp.attend_date
ORDER BY att.attend_date ASC) AS [NextDate]
FROM dbo_Employees
)
SELECT ISNULL(NextDate, GETDATE()) AS [next123],
ISNULL(NextDate, GETDATE()) - att.attend_date AS [difference]
FROM Emp_CTE;
I found this post on stackoverflow, Add a summary row with totals
What I'm trying to accomplish is just that, but with the converted DURATION field I have listed in this WITH statement below. I'm trying to TOTAL up the Durations column for that day(24 hr period). I don't know if it's possible. Let me know. Thank you!
;WITH dupes AS
(
SELECT
CALLER_PHONE, DIALED_PHONE
FROM
dbo.PBXDATA
GROUP BY
CALLER_PHONE, DIALED_PHONE
)
SELECT
c.CALL_TIME, c.SALES_REP, c.CALL_TYPE, c.FLAG1,
COALESCE(NULLIF(c.FLAG3, 'NULL'),'') AS FLAG3,
ISNULL(dupes.CALLER_PHONE, '') + ISNULL(dupes.DIALED_PHONE,'') AS PHONE,
CONVERT(VARCHAR(8), c.DURATION, 108) AS DURATION
FROM
dupes
JOIN
dbo.PBXDATA c ON dupes.CALLER_PHONE = c.CALLER_PHONE
OR dupes.DIALED_PHONE = c.DIALED_PHONE
WHERE
(c.SALES_REP LIKE 'Doug%' OR
c.SALES_REP LIKE 'Nick%' OR
c.SALES_REP LIKE 'Bob%' OR
c.SALES_REP LIKE 'Joe%' OR
c.SALES_REP LIKE 'John%')
AND (c.CALL_TIME >= DATEADD(DAY, 0, DATEDIFF(DAY, 0, CURRENT_TIMESTAMP)))
AND (c.CALL_TIME < DATEADD(DAY, 1, DATEDIFF(DAY, 0, CURRENT_TIMESTAMP)))
AND DURATION = (SELECT CAST(DATEADD(S, SUM(DATEDIFF(S, '00:00:00', DURATION)), '00:00:00') AS TIME)
FROM dbo.PBXDATA)
ORDER BY
c.CALL_TIME;
If you just want an overall total for the Duration in your dupes table, you can just sum your Duration there.
;WITH dupes AS
(
SELECT CALLER_PHONE, DIALED_PHONE, convert(varchar(8), SUM(c.DURATION), 108) AS Total_Time
FROM dbo.PBXDATA
GROUP BY CALLER_PHONE, DIALED_PHONE
)
And add , Total_Time to your SELECT statement.
If you have multiple days in your query, you would need to add the date field in the dupes and add it as part of the JOIN's ON clause.