Getting an "Aggregate" at a GROUP BY query - sql-server

1029/5000
I have 2 tables which are linked by the serial number (DeviceID).
Table 1 (C) lists all downloaded cyclist data.
Table 2 (T) lists the data about the device and when the last download took place.
Now I want to do a group by with the average speed of the last 3 6 or 12 months (counted from today).
This goes without problems.
However, when I get the average speed of the last 3 6 or 12 months counted from the last download I am going to get:
An aggregate may not appear in the WHERE clause unless it is in a subquery contained in a HAVING clause or a select list, and the column being aggregated is an outer reference.
Code 1 that goes OK:
SELECT
C.DeviceID
, AVG(C.Speed) AS AVG_Speed
, DATEDIFF(MONTH, C.LogDateTime, GETDATE()) AS Months
FROM Compass C
JOIN Transfer T ON C.DeviceID = T.DeviceID
WHERE DATEDIFF(MONTH, C.LogDateTime, GETDATE()) <= #EvalTimeFrame
GROUP BY C.DeviceID
Code 2 that goes wrong:
SELECT
C.DeviceID
, AVG(C.Speed) AS AVG_Speed
, DATEDIFF(MONTH, C.LogDateTime, GETDATE())) - (DATEDIFF(MONTH, MAX(T.TransferDateTime), GETDATE()) AS Months
FROM Compass C
JOIN Transfer T ON C.DeviceID = T.DeviceID
WHERE (DATEDIFF(MONTH, C.LogDateTime, GETDATE())) - (DATEDIFF(MONTH, MAX(T.TransferDateTime), GETDATE())) <= #EvalTimeFrame
GROUP BY C.DeviceID
Acually what I want to have is:
GROUP BY C.DeviceID, (DATEDIFF(MONTH, C.LogDateTime, GETDATE())) - (DATEDIFF(MONTH, MAX(T.TransferDateTime), GETDATE()))
It would help me a lot - any idea?

Related

T-SQL : filter out all employees who left before > 6 months

I need to create a new employee-database that filters out all employees, who left the company longer than 6 months ago from today.
I have a table with entry date, exit date and tried something like :
WHERE [exit date] > = DATEADD(M, -6, getdate())
That didn't work because it shows only the employees who left the company longer than 6 months ago. I just want to filter them out automatically and only show the employees, who are still employed er left the company lesser than 6 months ago.
Thanks in advance for your help.
You need to check for null:
WHERE ([exit date] IS NULL OR [exit date] > = DATEADD(M, -6, getdate()))
Do not try tricks with NOT and <
WHERE NOT ([exit date] < DATEADD(M, -6, getdate()))
This doesn't work because NULL rows will just result in UNKNOWN, so those rows will not be returned.
You can do this though, you may find it performs faster or slower than the first version:
WHERE NOT EXISTS (SELECT 1
WHERE [exit date] < DATEADD(M, -6, getdate())
This works because if exit date is null then no row gets returned from the subquery.

Is there an optimal way to create logical columns from physical column using SQL query statement?

I'm writing a SQL query using a table. My requirement is that I need to generate two logical columns from one physical column with certain conditions. In SQL how to generate two logical columns in final result set?
I have so far tried using sub-queries to derive those logical columns. But that sub-query returns error when incorporate it as a column in main query.
Overall there are other tables which will be joined using SQL JOIN to derive respective columns.
Columns:
CarrierName NVARCHAR(10)
MonthDate DATETIME
Stage INT
Scenario:
In my SQL Server table there is a column called Stage of type int that contains values like 1, 2, 3, 4.
Now, I have two date criteria to apply on above column to derive two logical columns in final result set.
Criteria #1:
Get carriers from past 12 months and priors to past month end date and value of "CurrentStage" should be less than and derive "PriorStage"
Example:
Current month is: March 2019 (2019-03-25) or any given date
Past latest month end date would be: 2019-02-28
12 months prior to above past latest month would be:
From 2018-02-01 To 2019-01-31
Criteria #2:
Get Carriers from past latest month end date and derive "CurrentStage"
While writing two independent SQL SELECT statements I get my desired results.
My challenge is when I think them to integrate in one select statement.
I get this error:
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression
Code:
DECLARE #DATE DATETIME
SET #DATE = '2018-08-25';
--QUERY 1 - RECORDS WITH PREVIOUS MONTH END DATE
SELECT
T1.CarrierName AS 'Carrier_Number',
T1.Stage AS 'Monitoring_Stage–Current'
FROM
table1 T1
WHERE
T1.Stage IS NOT NULL AND
CONVERT(DATE, T1.MonthDate) = CONVERT(DATE, DATEADD(D, -(DAY(#DATE)), #DATE))
--QUERY 2 - RECORDS FROM PAST 12 MONTHS PRIOR PREVIOUS MONTH END DATE
SELECT
T2.CarrierName,
T2.Stage AS 'Monitoring_Stage–Prior'
FROM
table2 T2
WHERE
T2.Stage IS NOT NULL AND
CONVERT(DATE, T2.MonthDate) BETWEEN CONVERT(DATE, DATEADD(M, -12, DATEADD(D, -(DAY(#DATE)), #DATE)))
AND CONVERT(DATE, DATEADD(D, -(DAY(#DATE) + (DAY(DATEADD(D, -(DAY(#DATE)), #DATE)))), #DATE))
AND T2.Stage) > (SELECT DISTINCT MAX(m.Stage AS INT))
FROM table1 m
WHERE CONVERT(DATE, m.MonthDate) = CONVERT(DATE, DATEADD(D, -(DAY(#DATE)), #DATE))
AND T2.CarrierName = m.CarrierName)
My final expected result set should contain below columns.
Where CurrentStage value is less than PriorStage value.
Expected Results
CarrierName | CurrentStage | PriorStage
--------------+--------------+-------------
C11122 | 1 | 2
C32233 | 3 | 4
Actual Result
I am looking for alternatives. I.e. CTE, Union, temp table etc.
Something like:
SELECT
CarrierName,
Query 1 Result As 'CurrentStage',
Query 2 Result As 'PrioreStage'
FROM
table1
To improve this post, I am adding my response here. My resolution below for this posted question is still under evaluation hence not posting it as my final answer. But it really brought a light to my effort.
RESOLUTION:
SELECT
DISTINCT M.CarrierName, A.[CurrentStage], B.[PriorStage]
FROM
--QUERY 1 - RECORDS WITH CURRENT MONTH END DATE
(SELECT M.CarrierName, M.CarrierID
, Stage AS 'CurrentStage'
FROM table1 M
WHERE M.Stage IS NOT NULL AND
CONVERT(date, M.MonthDate) = CONVERT(date, DATEADD(D,-(DAY(#DATE)), #DATE))
)
A **inner join**
(
--QUERY 2 - RECORDS FROM PAST 12 MONTHS PRIOR CURRENT MONTH END DATE
SELECT M2.CarrierName, M2.CarrierID
, Stage AS 'PriorStage'
FROM table1 M2
WHERE M2.Stage IS NOT NULL AND
CONVERT(date, M2.MonthDate) BETWEEN CONVERT(date, DATEADD(M, -12, DATEADD(D,-(DAY(#DATE)), #DATE)))
AND CONVERT(date, DATEADD(D,-(DAY(#DATE)+(DAY(DATEADD(D,-(DAY(#DATE)), #DATE)))), #DATE))
AND M2.Stage > (SELECT DISTINCT max(m.Stage)
FROM table1 m
WHERE CONVERT(date, m.MonthDate) = CONVERT(date, DATEADD(D,-(DAY(#DATE)), #DATE)) AND
M2.CarrierName = m.CarrierName
)
) B on b.Carrier_Number = a.Carrier_Number
INNER JOIN table1 M ON A.CarrierID = M.CarrierID AND B.CarrierID = M.CarrierID

How get one line per key in left outer join

The context is a transaction table with date and UserAccount. This table contains about billion lines.
dOperationValueDate sUserAccount
------------------- ----------------------------------------------
2016-03-05 00000000001
2016-03-06 00000000002
2016-03-07 00000000003
2016-03-08 00000000004
2016-03-09 00000000005
2016-04-05 00000000002
2016-10-05 00000000001
2016-10-06 00000000001
2016-10-06 00000000005
I would like to find datas in my table with these criterias :
At least one transaction before 6 months ago (like TOP 1 *)
No transaction for 6 months
In my example, the results would be accounts 2, 3, 4.
I started with a LEFT OUTER JOIN, in order to remove all userId with transaction since 6 months. But the processing time is just horrible : for 4 hours right now.
SELECT b.sUserAccount FROM
(SELECT sUserAccount FROM T_Operations WITH (readuncommitted) WHERE dOperationValueDate < DATEADD(month, -6, DATEADD(month, DATEDIFF(month, 0, GETUTCDATE()), 0)) GROUP BY sUserAccount) b -- all operations before 6 months ago
LEFT JOIN
(SELECT sUserAccount FROM T_Operations WITH (readuncommitted) WHERE dOperationValueDate >= DATEADD(month, -6, DATEADD(month, DATEDIFF(month, 0, GETUTCDATE()), 0)) GROUP BY sUserAccount) c -- all operations since 6 months
ON b.sUserAccount = c.sUserAccount
WHERE c.sUserBankAccount IS NULL) d -- remove all customers who have operations before 6 months ago and since 6 months / keep only customers who have operations beofre 6 months ago only
I think the solution is to find only one operation in the b query, and sql stops when it find one row. The main problem is only if the user doesn't have transaction before 6 months ago but for the others, it will be fine.
On the other hand, I have to check each transaction since 6 months in order to remove customers from the scope.
I read about CROSS APPLY, but I'm not sure about how it works.
The main problem here is the processing time. I have to do a "quick" request (less than 1 hour).
I think you should be able to just use NOT EXISTS here.
SELECT b.sUserAccount
FROM T_Operations b WITH (READUNCOMMITTED)
WHERE b.dOperationValueDate < DATEADD(month,-6,DATEADD(month,DATEDIFF(month,0,GETUTCDATE()),0))
AND NOT EXISTS ( SELECT 1
FROM T_Operations WITH (READUNCOMMITTED)
WHERE sUserAccount = b.sUserAccount
AND dOperationValueDate >= DATEADD(month,-6,DATEADD(month,DATEDIFF(month,0,GETUTCDATE()),0)) )
GROUP BY b.sUserAccount -- all operations before 6 months ago
or actually, you might be able to just use GROUP BY with HAVING
SELECT sUserAccount
FROM T_Operations WITH (READUNCOMMITTED)
GROUP BY sUserAccount
HAVING MAX(dOperationValueDate) < DATEADD(month,-6,DATEADD(month,DATEDIFF(month,0,GETUTCDATE()),0))
as a side note.. DATEADD(month,-6,DATEADD(month,DATEDIFF(month,0,GETUTCDATE()),0)) would return 2016-04-01
if you want current date, minus six months you can use DATEADD(month,-6,CAST(GETUTCDATE() AS DATE)) or DATEADD(month,-6,DATEADD(day,DATEDIFF(day,0,GETUTCDATE()),0)
datatime #dt = DATEADD(month, -6, DATEADD(month, DATEDIFF(month, 0, GETUTCDATE()), 0));
SELECT sUserAccount
FROM T_Operations WITH (readuncommitted)
WHERE dOperationValueDate < #dt
EXCEPT
SELECT sUserAccount
FROM T_Operations WITH (readuncommitted)
WHERE dOperationValueDate >= #dt;
Have an index on dOperationValueDate

Adding Month-to-date and Year-to-date in a query using SQL Server 2012?

so I'm trying to make a query that includes a daily sum of the amount from the first instance the database starts collecting data to the last available instance of that date (database collects data every hour). And while I have done this, now I have to make it show a month to date and a year to date sum amount. I have tried various ways to come up with this but have had no luck. Below is the code that I believe is the closest I have gotten to achieve this. Can someone help me make my code work or suggest another way around this?
Select * from
(
SELECT Devices.DeviceDesc,
SUM(DeviceSummaryData.Amount) AS MTD,
Devices.Area,
MIN(DeviceSummaryData.StartDate) AS FirstOfStartDate,
MAX(DeviceSummaryData.EndDate) AS LastOfStartDate
FROM Devices INNER JOIN DeviceSummaryData ON Devices.DeviceID = DeviceSummaryData.DeviceID
WHERE (DeviceSummaryData.StartDate = MONTH(getdate())) AND (DeviceSummaryData.EndDate <= CAST(DATEADD(DAY, 1, GETDATE())
AS date))
GROUP BY Devices.DeviceDesc, Devices.Area, DATEPART(day, DeviceSummaryData.StartDate)
--
) q2
UNION ALL
SELECT * FROM (
SELECT Devices.DeviceDesc,
Sum(Amount) as Daily,
Devices.Area,
MIN(StartDate) as FirstDate,
MAX(DeviceSummaryData.EndDate) AS LastOfStartDate
FROM Devices INNER JOIN DeviceSummaryData ON Devices.DeviceID = DeviceSummaryData.DeviceID
WHERE (DeviceSummaryData.StartDate >= CAST(DATEADD(DAY, 0, GETDATE()) AS date)) AND (DeviceSummaryData.EndDate <= CAST(DATEADD(DAY, 1, getdate()) AS date))
GROUP BY Devices.Area,
Devices.DeviceDesc,
DATEPART(day, DeviceSummaryData.StartDate)
ORDER BY Devices.DeviceDesc
) q2
Another type of attempt I have tried would be this:
SELECT Devices.DeviceDesc,
Sum(case
when DeviceSummaryData.StartDate >= CAST(DATEADD(DAY, 0, getdate()) AS date)
THEN Amount
else 0
end) as Daily,
Sum(case
when Month(StartDate) = MONTH(getdate())
THEN Amount
else 0
end) as MTD,
Devices.Area,
MIN(StartDate) as FirstDate,
MAX(DeviceSummaryData.EndDate) AS LastOfStartDate
FROM Devices INNER JOIN DeviceSummaryData ON Devices.DeviceID = DeviceSummaryData.DeviceID
WHERE (DeviceSummaryData.StartDate >= CAST(DATEADD(DAY, 0, GETDATE()) AS date)) AND (DeviceSummaryData.EndDate <= CAST(DATEADD(DAY, 1, getdate()) AS date))
GROUP BY Devices.Area,
Devices.DeviceDesc,
DATEPART(day, DeviceSummaryData.StartDate)
ORDER BY Devices.DeviceDesc
I'm not the best with Case When's, but I saw somewhere that this is a possible way to do this. I'm not too concerned with the speed or efficiency, I just need it to generate the query to be able to get the data. Any help and Suggestions are greatly appreciated!
The second attempt is on the right track but a bit confused. In the CASE statements you are trying to compare months etc, but your WHERE clause restricts the data you're looking at to a single day. Also, your GROUP BY should not include the day anymore. If you say in English what you want, it's "For each device area and type, I want to see a total, a MTD total and a YTD total". It's that "For each" bit that should define what appears in your GROUP BY.
Just remove the WHERE clause entirely and get rid of DATEPART(day, DeviceSummaryData.StartDate) from your GROUP BY and you should get the results you want. (Well, a daily and monthly total, anyway. Yearly is achieved much the same way).
Also note that DATEADD(DAY, 0, GETDATE()) is identical to just GETDATE().

SQL Server - Get first date in a week, given the week number?

I've got a query (for use in bug tracker.net) that calculates the number of bugs by week by status. But the query returns the week number, what I really want is the first date of the week
select datepart(wk, DateAdd(day, 0, DateDiff(day, 0, bg_reported_date)))
as [week], bg_status , st_name as [status], count(*) as [count]
from bugs inner join statuses on bg_status = st_id
group by datepart(wk, DateAdd(day, 0, DateDiff(day, 0, bg_reported_date))),
bg_status, st_name
order by [week], bg_status
The part that gets the week number is
datepart(wk, DateAdd(day, 0, DateDiff(day, 0, bg_reported_date))) as [week]
It returns this output:
week bg_status status count
----------- ----------- --------------------------------------------- ------
22 1 new 1
22 5 closed 32
But it would be better to say the first date of each week, eg 01-01-2010, then 08-01-2010, etc
Question is not a duplicate of How do you get the "week start date" and "week end date" from week number in SQL Server? (answer says how to get week start from a date not from a week number)
Not a duplicate of Calculate date from week number (question asks for c#)
Not a duplicate of Get first date of week from provided date (question asks for javascript)
I did search but couldn't find this question answered for SQL Server (2010 if it matters)
If you think about it in the right way, the answer to SO 1267126 can be applied to your problem.
Each of the bug reported dates that you have in the group maps to the same week. By definition, therefore, each of those bug dates must also map to the same start of the week. So, you run the 'start of the week from given date' calculation on the bug report dates, as well as the week number calculation, and group by both (modestly ghastly) expressions, and end up with the answer you seek.
SELECT DATEPART(wk, DATEADD(day, 0, DATEDIFF(d, 0, bg_reported_date))) [week],
DATEADD(dd, -(DATEPART(dw, bg_reported_date)-1), bg_reported_date)
AS [weekstart], bg_status, st_name AS [status], COUNT(*) AS [count]
FROM bugs INNER JOIN statuses ON bg_status = st_id
GROUP BY DATEPART(wk, DATEADD(day, 0, DATEDIFF(day, 0, bg_reported_date))),
DATEADD(dd, -(DATEPART(dw, bg_reported_date)-1), bg_reported_date),
bg_status, st_name
ORDER BY [week], bg_status
Since bg_reported_date is a DATETIME (see the comment; it includes a time component), it is necessary to cast it to DATE before determining the week-start (but the week number expression doesn't need the cast, and the 'day of week' part of the week start expression doesn't need the cast either):
SELECT DATEPART(wk, DATEADD(day, 0, DATEDIFF(d, 0, bg_reported_date))) [week],
DATEADD(dd, -(DATEPART(dw, bg_reported_date)-1),
CAST(bg_reported_date AS DATE)) AS [weekstart],
bg_status, st_name AS [status], COUNT(*) AS [count]
FROM bugs INNER JOIN statuses ON bg_status = st_id
GROUP BY DATEPART(wk, DATEADD(day, 0, DATEDIFF(day, 0, bg_reported_date))),
DATEADD(dd, -(DATEPART(dw, bg_reported_date)-1),
CAST(bg_reported_date AS DATE),
bg_status, st_name
ORDER BY [week], bg_status
NB: Untested code!
I realize this is a very old thread, but "Get first date in a week, given the week number" is exactly what I wanted to do and I do NOT have an actual date to work with, so the accepted answer would not work for me. I thought I'd post my solution for posterity. Note that I suspect different culture settings MAY break this, so test before using.
My answer is built starting from this one.
Let's assume you know a week number and a year and you want to get the start and end dates for that week of that year. Here's what I have:
--These 2 "declared" variables would be passed in somehow
declare #WeekNumber int = DATEPART(wk, GETDATE())
declare #ForYear int = YEAR(GETDATE())-1
--Since we don't have a raw date to work with, I figured I could just start with
--Jan 1 of that year. I'll store that date in a cte here, but if you are doing this
--in a stored proc or function, it would make much more sense to use another #variable
;with x as
(
--this method works in SQL 2008:
SELECT CONVERT(DateTime, ('1/1/' + CONVERT(varchar, #ForYear))) as Jan1ForSelectedYear
--If you are using 2014 or higher, you can use this instead:
--DATETIME2FROMPARTS(#ForYear, 1, 1, 0,0,0,0,0)
)
--Now that we have a date to work with, we'll just add the number of weeks to that date
--That will bring us to the right week number of the given year.
--Once we have THAT date, we can get the beginning and ending of that week
--Sorry to make you scroll, but I think this is easier to see what is going on this way
SELECT CONVERT(varchar(50), DateAdd(wk, (#WeekNumber - 1), (DATEADD(dd, ##DATEFIRST - DATEPART(dw, x.Jan1ForSelectedYear) - 6, x.Jan1ForSelectedYear))), 101) as FirstDayOfWeekXForSelectedYear,
CONVERT(varchar(50), DateAdd(wk, (#WeekNumber - 1), (DATEADD(dd, ##DATEFIRST - DATEPART(dw, x.Jan1ForSelectedYear) , x.Jan1ForSelectedYear))), 101) as LastDayOfWeekXForSelectedYear
FROM x

Resources