Reputation: 1011
I am working on a SQL query where I have to find out Average, Max value, Max Value-DateTime, Min Value, Min Value-DateTime of each Group of any Date. For this I used following query
SELECT
Dl.TagDescID,
convert( decimal(18,2), AVG( Dl.Value ) ) AS 'TotalAvgValue',
convert(decimal(18,2), MAX( Dl.Value ) ) AS 'MaxValue',
MAX( Dl.Date ) MaxDate,
convert( decimal(18,2), MIN( Dl.Value ) ) AS 'MinValue',
Min( Dl.Date ) MinDate
FROM
tblDataLog AS Dl WITH (nolock)
INNER JOIN tblTagDescription AS TD WITH (nolock) ON Dl.TagDescID = TD.ID
WHERE
( CONVERT(date, Dl.Date, 103) = CONVERT(date, @StartDate, 103) )
AND
( TD.GroupName = @Group )
GROUP BY
Dl.TagDescID
`
TagDescID TotalAvgValue MaxValue MaxDate MinValue MinDate
1 36.59 36.59 2020-01-07 10:13:42.293 36.59 2020-01-07 10:13:32.750
2 20.49 20.49 2020-01-07 10:13:42.293 20.49 2020-01-07 10:13:32.750
In the above Data Set The MaxValue of TagDescID 1 is 36.59 and its Date as 'MaxDate' should be the exact datetime when this maxvalue Arrives in the table tblDataLog. here Max-Date of MaxValue of TagDescID 1 is '2020-01-07 10:13:42.293' but the value should have been '2020-01-07 08:13:42.293'. Same for MinValue and MinDate For each TagDescID.
How to solve it?
Upvotes: 0
Views: 310
Reputation: 16554
The issue is that you are really asking for multiple aggregate and grouping sets at the same time. What you are observing is that each column aggregate expression is returning the result of that aggregate independently of the other columns, this is by design.
In the above query
MaxDate
andMinDate
should be the Datetime when theMaxValue
orMinValue
inserted to the tabletblDataLog
.
In SQL Server we have a number of ways to query this.
One way is to query the set for the Max and Min values, then re-query the set for the Max and Min Dates around those times.
In this example I'm using a CTE instead of a simple nested query because I find the syntax more readable.
The two
CROSS APPLY
queries will evaluate once for each result in theDaily
CTE. This solution can be more efficient to using an inline lookup for theDateTime
of the corresponding log records because it doesn't have to lookup once for every row intblDataLog
, this solution will result in 2 additional lookups for every row that is returned by the group by.
;WITH Daily as
(
SELECT
Dl.TagDescID,
convert(decimal(18,2),AVG(Dl.Value)) AS [TotalAvgValue],
convert(decimal(18,2),MAX(Dl.Value)) AS [MaxValue],
convert(decimal(18,2),MIN(Dl.Value)) AS [MinValue]
FROM @tblDataLog AS Dl
INNER JOIN @tblTagDescription AS TD ON Dl.TagDescID = TD.ID
WHERE (CONVERT(date, Dl.Date, 103) = CONVERT(date, @StartDate, 103)) AND (TD.GroupName=@Group)
GROUP BY Dl.TagDescID
)
SELECT Daily.TagDescID, TotalAvgValue, MaxValue, [Max].Date MaxDate, MinValue, [Min].Date MinDate
FROM Daily
CROSS APPLY (SELECT MAX(DlMax.Date) Date FROM @tblDataLog DlMax WHERE DlMax.TagDescID = Daily.TagDescId AND (CONVERT(date, DlMax.Date, 103) = CONVERT(date, @StartDate, 103)) AND DlMax.Value = Daily.MaxValue) as [Max]
CROSS APPLY (SELECT MIN(DlMin.Date) Date FROM @tblDataLog DlMin WHERE DlMin.TagDescID = Daily.TagDescId AND (CONVERT(date, DlMin.Date, 103) = CONVERT(date, @StartDate, 103)) AND DlMin.Value = Daily.MinValue) as [Min]
Another way to do this is to group by the date, instead of filtering for an explicit date value, so this will summarize by date and tag, giving you a report across multiple dates and multiple tags if you want it:
;WITH Daily as
(
SELECT
Dl.TagDescID,
TD.GroupName,
CONVERT(date, Dl.Date, 103) as Date,
MAX(Dl.Date) as Max_Date,
MIN(Dl.Date) as Min_Date,
convert(decimal(18,2),AVG(Dl.Value)) AS [TotalAvgValue],
convert(decimal(18,2),MAX(Dl.Value)) AS [MaxValue],
convert(decimal(18,2),MIN(Dl.Value)) AS [MinValue]
FROM @tblDataLog AS Dl
INNER JOIN @tblTagDescription AS TD ON Dl.TagDescID = TD.ID
GROUP BY Dl.TagDescID, TD.GroupName, CONVERT(date, Dl.Date, 103)
)
SELECT Daily.Date, Daily.GroupName, Daily.TagDescID, TotalAvgValue, MaxValue, [Max].Date MaxDate, MinValue, [Min].Date MinDate
FROM Daily
CROSS APPLY (SELECT MAX(DlMax.Date) Date FROM @tblDataLog DlMax WHERE DlMax.TagDescID = Daily.TagDescId AND DlMax.Date >= Daily.Min_Date AND DlMax.Date <= Daily.Max_Date) [Max]
CROSS APPLY (SELECT MIN(DlMin.Date) Date FROM @tblDataLog DlMin WHERE DlMin.TagDescID = Daily.TagDescId AND DlMin.Date >= Daily.Min_Date AND DlMin.Date <= Daily.Max_Date) [Min]
-- Optional Date Filter
-- WHERE TD.GroupName=@Group AND Daily.Date = @StartDate
In my test set, this returns:
Date GroupName TagDescID TotalAvgValue MaxValue MaxDate MinValue MinDate
2020-03-11 Test 1 5.00 8.00 2020-03-11 22:00:00.000 2.00 2020-03-11 10:00:00.000
2020-03-15 Test 1 5.00 8.00 2020-03-15 13:00:00.000 2.00 2020-03-15 06:00:00.000
Un-comment the
where
clause to enable your date filter, or you could change it to a date range and you would still see results by day.
Upvotes: 2
Reputation: 24763
I think this is what you are after. The corresponding Date
when Value
is minimum and maximum. You can use a sub-query
to do this
SELECT
Dl.TagDescID,
convert( decimal(18,2), AVG( Dl.Value ) ) AS 'TotalAvgValue',
convert(decimal(18,2), MAX( Dl.Value ) ) AS 'MaxValue',
--MAX( Dl.Date ) MaxDate,
(SELECT TOP 1 x.[Date] FROM tblDataLog x WHERE x.TagDescID = Dl.TagDescID ORDER BY Value DESC) As MaxDate,
convert( decimal(18,2), MIN( Dl.Value ) ) AS 'MinValue',
--Min( Dl.Date ) MinDate
(SELECT TOP 1 x.[Date] FROM tblDataLog x WHERE x.TagDescID = Dl.TagDescID ORDER BY Value) As MinDate
FROM
tblDataLog AS Dl WITH (nolock)
INNER JOIN tblTagDescription AS TD WITH (nolock) ON Dl.TagDescID = TD.ID
WHERE
( CONVERT(date, Dl.Date, 103) = CONVERT(date, @StartDate, 103) )
AND
( TD.GroupName = @Group )
GROUP BY
Dl.TagDescID
EDIT : as others commented, avoid CONVERT(date, Dl.Date, 103) = CONVERT(date, @StartDate, 103)
You can replace that with
WHERE Dl.Date >= @StartDate
AND Dl.Date < DATEADD (DAY, 1, @StartDate)
Upvotes: 0