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;
Related
I have a set of date in a Table which contains weekly date.
I want to select the following:
If the date is less than 2 months old then i want to select all the date (weekly).
If the date is more than 2 months old then i only want to select the last date of each month (monthly).
I tried the following code:
SELECT DISTINCT(Date) FROM [Table] WHERE Date IN
(CASE
WHEN Date> DATEADD(month, -2, GETDATE())
THEN Date
ELSE MAX(Date) GROUP BY Month(Date),Year(Date)
);
But without success:
Incorrect syntax near the keyword 'GROUP'.
If for instance the current Date is 13/09/2022,
13/09/2022 - 2 months = 13/07/2022
If i have the following Date in my Table:
06/05/2022
13/05/2022
20/05/2022
31/05/2022
07/06/2022
10/06/2022
17/06/2022
24/06/2022
30/06/2022
08/07/2022 (<13/07/2022)
15/07/2022 (>13/07/2022)
22/07/2022
29/07/2022
05/08/2022
12/08/2022
19/08/2022
26/08/2022
Then the final output should be:
31/05/2022
30/06/2022 (<13/07/2022)
15/07/2022 (>13/07/2022)
22/07/2022
29/07/2022
05/08/2022
12/08/2022
19/08/2022
26/08/2022
Your syntax is completely invalid, I'm not going to bother fixing it.
DISTINCT is not a function, it works over the whole set of columns.
You can't use aggregates inside a WHERE, even if they would be window functions (which they're not).
The GROUP BY is inside a CASE which makes no sense.
Instead I'm just going off your requirements
If the date is less than 2 months old then I want to select all the date (weekly).
If the date is more than 2 months old then I only want to select the last date of each month (monthly).
You can use a ROW_NUMBER strategy for this.
SELECT
t.Date
FROM (
SELECT *,
rn = ROW_NUMBER() OVER (PARTITION BY EOMONTH(t.Date) ORDER BY t.Date DESC)
FROM [Table] t
) t
WHERE (
t.Date > DATEADD(month, -2, GETDATE())
OR rn = 1
)
ORDER BY
Date;
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'm working on two queries that pull data from one table. I can get them to run how I want individually, but I'd like to combine them into one if possible. I've tried the solution in [Multiple COUNT SELECTS from the same table in one query but wasn't able to get it figured out. Below is what I'm working on:
--goback 30 days start at midnight
declare #daysgoingback as int
set #daysgoingback = 90
declare #olderdate as datetime
set #olderdate = DATEADD(Day, -#daysgoingback, DATEDIFF(Day, 0, GetDate()))
--today at 11:59pm
declare #today as datetime
set #today = dateadd(ms, -3, (dateadd(day, +1, convert(varchar, GETDATE(), 101))))
print #today
--these are the two queries I'd like to combine:
select
avg(x.LogAlerts*1.0 / #daysgoingback) as 'Avg number of log alerts'
from
(select count(*) as LogAlerts
from message_log_table
where msg_timestamp between #olderdate and #today) X
select
avg(x.MessagesDeleted*1.0 / #daysgoingback) as 'Avg number of messages deleted'
from
(select count(*) as MessagesDeleted
from message_log_table
where msg_details like '%message deleted%'
and msg_timestamp between #olderdate and #today) X
"AVG" is an aggregate function, which averages multiple rows. Your "select count(*)" subquery always returns exactly one row, so you are not really using the AVG function as it's intended.
You can combine the two like this:
select count(*) * 1.0 / #daysgoingback as 'Avg number of log alerts'
, count(case when msg_details like '%message deleted%' then 1 end) * 1.0 / #daysgoingback as 'Avg number of messages deleted'
from message_log_table
where msg_timestamp between #olderdate and #today;
Make each query into a CTE, and then CROSS JOIN them.
Both queries only return a single-cell result, so there's no need to get fancy here.
What's an alternative to getting a distinct number of dates, say all the dates for September:
9/1/2016
9/2/2016
9/3/2016
and apply each value to a query. Say something like:
Select GuitarId,GuitarBrand
From GuitarSales
Where GuitarDate = #date
I don't want to use a cursor, is there an alternative to doing this?
I tried a CTE but even then I'd have to apply the cursor for each date.
If you want all the dates for a month you can use
Select GuitarId,GuitarBrand
From GuitarSales
Where month(GuitarDate) = 9
and year(GuitarDate) = 2016;
If I understand you correctly, you need a list of all dates in September. This is a quick solution to get a gapless list of all days in September: In your query you can use this as source and LEFT JOIN your actual data.
WITH RunningNumbers AS
(
SELECT TOP(30) ROW_NUMBER() OVER(ORDER BY (SELECT NULL))-1 AS Nr
FROM sys.objects
)
SELECT {d'2016-09-01'}+Nr AS RunningDate
FROM RunningNumbers
There are many examples, how you can create a tally table on the fly. Small numbers (like 30 in this example) can be taken easily from any table with sufficient rows.
If you need this more often you might think about a Numbers-Table
a related question: https://stackoverflow.com/a/39387790/5089204
create a persitant numbers table with a lot of usefull side data: https://stackoverflow.com/a/32474751/5089204
Assuming you have an index on GuitarDate here is a way you can create a SARGable where predicate so you can still leverage the speed of using an index seek.
declare #date datetime = '2016-09-10' --just to demonstrate starting with September 10, 2016
select gs.GuitarId
, gs.GuitarBrand
From GuitarSales gs
where gs.GuitarDate >= dateadd(month, datediff(month, 0, #date), 0) --beginning of the month for #date
and gs.GuitarDate < dateadd(month, datediff(month, 0, #date) + 1, 0) --beginning of next month
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.