SQL Time Conversion - sql

I am having trouble with a conversion of time.
I have a table called 'TotalTime' which is set to INT and holds the time in seconds only. I want to convert these seconds to days, hours, minutes, seconds e.g. 01d 09:26:43.
Now I will show you the code I am using:
SELECT [BuildID],[Product],[Program],
SUM(CASE WHEN [State] = 'Running' THEN cast(TotalTime as INT) ELSE 0 END) AS [Running],
SUM(CASE WHEN [State] = 'Break' THEN cast(TotalTime as INT) ELSE 0 END) AS [Break]
FROM [line_log].[dbo].[Line1Log]
GROUP BY [BuildID], [Product], [Program]
So as you can see I am grouping the [State] column and would like to display the results of 'TotalTime' in the format I mentioned above.
Now I have tried this code but it will not work as I cannot convert INT to VARCHAR
SELECT [BuildID],[Product],[Program],
SUM(CASE WHEN [State] = 'Running' THEN CAST(FLOOR(TotalTime / 86400) AS VARCHAR(10))+'d ' + CONVERT(VARCHAR(5), DATEADD(SECOND, TotalTime, '19000101'), 8) ELSE 0 END) AS [Running]
FROM [line_log].[dbo].[Line1Log]
GROUP BY [BuildID], [Product], [Program]
The above would not display it in the exact format I wanted either.
Just wondering if someone would be willing to help me on this one?
Thanks for taking the time to read this :)

You should convert calculated seconds after summing and grouping:
And use VARCHAR(8) instead of VARCHAR(5).
SELECT [BuildID],[Product],[Program],
CAST(FLOOR([Running] / 86400) AS VARCHAR(10))+'d ' + CONVERT(VARCHAR(8), DATEADD(SECOND, [Running], '19000101'), 8) AS [Running],
CAST(FLOOR([Break] / 86400) AS VARCHAR(10))+'d ' + CONVERT(VARCHAR(8), DATEADD(SECOND, [Break], '19000101'), 8) AS [Break]
FROM (
SELECT [BuildID],[Product],[Program],
SUM(CASE WHEN [State] = 'Running' THEN cast(TotalTime as INT) ELSE 0 END) AS [Running],
SUM(CASE WHEN [State] = 'Break' THEN cast(TotalTime as INT) ELSE 0 END) AS [Break]
FROM [line_log].[dbo].[Line1Log]
GROUP BY [BuildID], [Product], [Program]
) T

Related

simplify a SQL case statement in a case expression

How would I simplify this case statement in T-SQL? It provides the desired result, but it's very unwieldy and hard to read. I have to use the inner case statement to convert a Julian date (aka 6 digit number) into a regular date format.
Basically i'm doing a datediff( getdate(), case statement). Getdate() just returns the time now (ie. 2/27/2020) and the case statement converts a julian date (ie. 123456) into a normal date (ie, 1/1/2020).
Here's the expect output if the query was ran today on Feb 27.
Select CASE
WHEN Datediff(day, Getdate(), CASE
WHEN a.wadpl = 0
THEN NULL
ELSE Dateadd(d, Substring(Cast(wadpl AS VARCHAR(6)), 4, 3) - 1, CONVERT(DATETIME, CASE
WHEN LEFT(Cast(wadpl AS VARCHAR(6)), 1) = '1'
THEN '20'
ELSE '21'
END + Substring(Cast(wadpl AS VARCHAR(6)), 2, 2) + '-01-01'))
END) < 0
THEN 'Overdue Now'
WHEN Datediff(day, Getdate(), CASE
WHEN a.wadpl = 0
THEN NULL
ELSE Dateadd(d, Substring(Cast(wadpl AS VARCHAR(6)), 4, 3) - 1, CONVERT(DATETIME, CASE
WHEN LEFT(Cast(wadpl AS VARCHAR(6)), 1) = '1'
THEN '20'
ELSE '21'
END + Substring(Cast(wadpl AS VARCHAR(6)), 2, 2) + '-01-01'))
END) <= 30
THEN 'Coming due in 01-30 days'
ELSE 'Not Overdue'
END [Overdue Status]
FROM Table_X
Here is a easy one to understand, assuming a.wadpl is an integer:
SELECT CASE
WHEN DATEDIFF(DAY, GETDATE(), DATEADD(DAY, a.wadpl % 1000, DATEADD(YEAR,a.wadpl / 1000,'1899-12-31'))) <0 THEN 'Overdue now'
WHEN DATEDIFF(DAY, GETDATE(), DATEADD(DAY, a.wadpl % 1000, DATEADD(YEAR,a.wadpl / 1000,'1899-12-31'))) <= 30 THEN 'Coming due in 01-30 days'
ELSE 'Not Overdue'
END [Overdue Status]
FROM Table_X
or you can simplify by using a subquery (or you can use a WITH):
SELECT CASE
WHEN Age <0 THEN 'Overdue now'
WHEN Age <= 30 THEN 'Coming due in 01-30 days'
ELSE 'Not Overdue'
END [Overdue Status]
FROM (
SELECT DATEDIFF(DAY,GETDATE(),
DATEADD(DAY,wadpl%1000,DATEADD(YEAR,wadpl/1000,'1899-12-31'))) Age, *
FROM Table_X) a
This will of course cause you to do this arithmetic for each row, and you can't easily use any indexes. If you were asking about aggregates, then I would suggest doing the opposite, and pre-calculating the dates and use those in your query instead. You might also want to consider putting a persisted computed column on table_x:
ALTER TABLE TABLE_X
ADD wadpl_dt AS
(DATEADD(DAY,wadpl%1000,DATEADD(YEAR,wadpl/1000,'1899-12-31'))) PERSISTED;
Now you can just refer to table_x.wadpl_dt whenever you want the datetime, and your query would become:
SELECT CASE
WHEN Age <0 THEN 'Overdue now'
WHEN Age <= 30 THEN 'Coming due in 01-30 days'
ELSE 'Not Overdue'
END [Overdue Status]
FROM (
SELECT DATEDIFF(DAY,GETDATE(), wadpl_dt) Age, *
FROM Table_X) a
Here is the easy way to convert a date to what you refer to as the julian date:
SELECT (DATEPART(YEAR,GETDATE())-1900) * 1000 + DATEPART(DAYOFYEAR, GETDATE())
And this is how you can use it:
DECLARE #overdue int;
DECLARE #next30 int;
SET #overdue = (SELECT (DATEPART(YEAR,GETDATE())-1900) * 1000 + DATEPART(DAYOFYEAR, GETDATE()));
SET #next30 = (SELECT (DATEPART(YEAR,GETDATE()+30)-1900) * 1000 + DATEPART(DAYOFYEAR, GETDATE()+30));
SELECT CASE
WHEN wadpl < #overdue THEN 'Overdue now'
WHEN wadpl <= #next30 THEN 'Coming due in 01-30 days'
ELSE 'Not Overdue'
END [Overdue Status]
FROM Table_X

sum (case when else end) statement

I have a SQL Server SQL statement that works but it is seemingly messy and inefficient. It checks each row of data for its data and groups it into Yesterday, WeektoDate, MonthtoDate, and YeartoDate and sums each field by the group. Is there a better way to do this?
Below is a sample of what I have, there are actually 15 or so fields in the actual query, which makes for a long SELECT query, Is there a better way to do this?
SELECT
sum(CASE when row_date BETWEEN #YesterdayMorning and #EndOfYesterday THEN Total_Calls ELSE 0 END) AS Calls_Yesterday
,sum(CASE when row_date BETWEEN #YesterdayMorning AND #EndOfYesterday THEN Total_Time ELSE 0 END) AS Time_Yesterday
,sum(CASE when row_date BETWEEN #WTDMorning and #EndOfYesterday THEN Total_Calls ELSE 0 END) AS Calls_WTD
,sum(CASE when row_date BETWEEN #WTDMorning AND #EndOfYesterday THEN Total_Time ELSE 0 END) AS Time_WTD
,sum(CASE when row_date BETWEEN #MTDMorning and #EndOfYesterday THEN Total_Calls ELSE 0 END) AS Calls_MTD
,sum(CASE when row_date BETWEEN #MTDMorning AND #EndOfYesterday THEN Total_Time ELSE 0 END) AS Time_MTD
,sum(CASE when row_date BETWEEN #YTDMorning and #EndOfYesterday THEN Total_Calls ELSE 0 END) AS Calls_YTD
,sum(CASE when row_date BETWEEN #YTDMorning AND #EndOfYesterday THEN Total_Time ELSE 0 END) AS Time_YTD
FROM TableName
One option is to get rid of the CASE, break them into multiple SELECT queries with WHERE conditions and then union them:
SELECT
'Yesterday' AS Range
SUM(Total_Calls) AS Calls,
SUM(Total_Time) AS Time
FROM TableName
WHERE row_date BETWEEN #YesterdayMorning AND #EndOfYesterday
UNION
SELECT
'WTD' AS Range,
SUM(Total_Calls) AS Calls,
SUM(Total_Time) AS Time
FROM TableName
WHERE row_date BETWEEN #WTDMorning AND #EndOfYesterday
UNION
SELECT
'MTD' AS Range,
SUM(Total_Calls) AS Calls,
SUM(Total_Time) AS Time
FROM TableName
WHERE row_date BETWEEN #MTDMorning AND #EndOfYesterday
UNION
SELECT
'YTD' AS Range,
SUM(Total_Calls) AS Calls,
SUM(Total_Time) AS Time
FROM TableName
WHERE row_date BETWEEN #YTDMorning AND #EndOfYesterday
I do not think your query is that inefficient, the query plan looks ok, but I agree the code is a bit messy. I would consider some thing like this to avoid the duplicated code:
Add all morning dates to a table
Join dates to the call table
SUM calls and call time
Query:
DECLARE #day VARCHAR(2) = FORMAT(DAY(GETDATE()), '00'), #month VARCHAR(2) = FORMAT(MONTH(GETDATE()), '00'), #year INT = YEAR(GETDATE());
DECLARE #weekday INT = DATEPART(dw, GETDATE());
DECLARE #morningTime TIME = '08:00';
DECLARE #EndOfYesterday DATETIME2 = DATEADD(dd, -1, CONVERT(DATETIME2, CONCAT(YEAR(GETDATE()), MONTH(GETDATE()), DAY(GETDATE()), ' 18:00')));
DECLARE #timeTable TABLE(ID INT IDENTITY(1,1) PRIMARY KEY, Name VARCHAR(255), FromDate DATETIME2);
INSERT INTO #timeTable
SELECT 'Yesterday', DATEADD(dd, -1, CONVERT(DATETIME2, CONCAT(#year, #month, #day, ' ', #morningTime)))
UNION ALL SELECT 'WTDMorning', DATEADD(dd, -(#weekday-1), CONVERT(DATETIME2, CONCAT(#year, #month, #day, ' ', #morningTime)))
UNION ALL SELECT 'MTDMorning', CONVERT(DATETIME2, CONCAT(#year, #month, '01', ' ', #morningTime))
UNION ALL SELECT 'YTDMorning', CONVERT(DATETIME2, CONCAT(#year, '01', '01', ' ', #morningTime))
SELECT t.Name, Total_Calls = SUM(c.Total_Calls), Total_Time = SUM(c.Total_Time)
FROM #timeTable t
LEFT JOIN TableName c ON c.row_date BETWEEN t.FromDate AND #EndOfYesterday
GROUP BY ID, t.Name
ORDER BY ID
On my test data your query actually have a 15% lower query cost that my query, but with an index on the "call table" my query is 20% cheaper then yours.
Index:
CREATE NONCLUSTERED INDEX idxRowDate
ON [dbo].[TableName] ([row_date])
INCLUDE ([Total_Calls],[Total_Time])
I am aware that this do not match your output 100%, but I do believe that rows are easier to work with than 15+ columns. This could also be converted to one row if needed.
One row support:
If it is needed, you could convert the rows to one line with dynamic sql. But it gets a bit more complex:
DECLARE #onRowconverter VARCHAR(MAX) = '';
SELECT #onRowconverter = #onRowconverter + CASE WHEN #onRowconverter > '' THEN ', ' ELSE 'SELECT ' END + CONCAT('[Calls_' + t.Name + '] = ', SUM(c.Total_Calls), ', [Time_' + t.Name + '] = ', SUM(c.Total_Time))
FROM #timeTable t
LEFT JOIN TableName c ON c.row_date BETWEEN t.FromDate AND #EndOfYesterday
GROUP BY ID, t.Name
ORDER BY ID
EXEC(#onRowconverter);

Improve SQL Server query

I have to improve this query, that works very well.
DECLARE #timTimeout int,
#iniDate varchar(20),
#endDate varchar(20)
SET #iniDate = '2014-07-20 00:00:00'
SET #endDate = '2014-11-24 23:59:59'
SET #timTimeout = 4000
SET ANSI_WARNINGS OFF
SELECT
'Approved (0200)' = ISNULL(SUM(CASE CodMsgIncome WHEN '0200' THEN 1 END), 0),
'Approved Off (0220)' = ISNULL(SUM(CASE CodMsgIncome WHEN '0220' THEN 1 END), 0),
'Cancel (0400)' = ISNULL(SUM(CASE CodMsgIncome WHEN '0400' THEN 1 END), 0),
'Regret (0420)' = ISNULL(SUM(CASE CodMsgIncome WHEN '0420' THEN 1 END), 0),
'TOTAL' = COUNT(*),
'Time-outs' = ISNULL(SUM(CASE WHEN DATEDIFF(ms, DateMsgIncome, DateMsgSent) > #timTimeout THEN 1 END), 0),
'Disponibility (%)' = (1 - CAST(ISNULL(SUM(CASE WHEN DATEDIFF(ms, DateMsgIncome, DateMsgSent) > #timTimeout THEN 1 END), 0) as money) / COUNT(*)) * 100
FROM Message (NOLOCK)
WHERE DateMsgIncome BETWEEN #iniDate AND #endDate
AND CodMsgIncome IN ('0200', '0220', '0400', '0420', '0800', '0900', '9080', '9085')
AND DescMsgIncome <> '0220'
Now, I have to prepare a report with Total data organized by month.
The output disered seems like this:
Approved (0200) | Approved Off (0220) | Cancel | Total | Time-outs | Disponibility (%)
July | 35 15 12 62 0 100.00
.
.
.
EDIT:
It is only one table on my query.
Table Message:
DateMsgIncome date,
DateMsgSent date,
CodMsgIncome varchar(4),
DescMsgIncome varchar(4),
CodMsgAnswer int.
Any suggestion is welcome.
Thanks in advance.
I ran your query through a code formatter to help clean it up. I also change the variable declaration since you didn't seem to understand what I was saying. For the record, the way you had it coded you might have missed some rows in the last few milliseconds of the day.
I changed the DATEDIFF function to use the datepart name spelled out because it is just too easy use the wrong abbreviation and get it wrong. I also simplified the calculation for the last column. The cast to money was not needed if you change the 1 - to 1.0 -. You should avoid using reserved words for object names and avoid spaces in column names. Let the front end do this kind of pretty formatting.
I also added the soon the be required WITH keyword when using table hints. (I would recommend understand what NOLOCK really means before using it).
DECLARE #timTimeout int
, #iniDate date
, #endDate date
SET #iniDate = '2014-07-20'
SET #endDate = '2014-11-25'
SET #timTimeout = 4000
SELECT MONTH(DateMsgIncome) as MyMonthColumn
, 'Approved (0200)' = ISNULL(SUM(CASE CodMsgIncome WHEN '0200' THEN 1 END), 0)
, 'Approved Off (0220)' = ISNULL(SUM(CASE CodMsgIncome WHEN '0220' THEN 1 END), 0)
, 'Cancel (0400)' = ISNULL(SUM(CASE CodMsgIncome WHEN '0400' THEN 1 END), 0)
, 'Regret (0420)' = ISNULL(SUM(CASE CodMsgIncome WHEN '0420' THEN 1 END), 0)
, 'TOTAL' = COUNT(*)
, 'Time-outs' = ISNULL(SUM(CASE WHEN DATEDIFF(MILLISECOND, DateMsgIncome, DateMsgSent) > #timTimeout THEN 1 END), 0)
, 'Disponibility (%)' = (1.0 - ISNULL(SUM(CASE WHEN DATEDIFF(MILLISECOND, DateMsgIncome, DateMsgSent) > #timTimeout THEN 1 END), 0) / COUNT(*)) * 100
FROM [Message] WITH (NOLOCK) --Ack!!! I wouldn't let this fly on my system due to inconsistencies with this hint unless accuracy is not important (like
WHERE DateMsgIncome >= #iniDate
AND DateMsgIncome < #endDate
AND CodMsgIncome IN
(
'0200'
, '0220'
, '0400'
, '0420'
, '0800'
, '0900'
, '9080'
, '9085'
)
AND DescMsgIncome <> '0220'
GROUP BY MONTH(DateMsgIncome)

SQL SUM() function ignoring WHERE clause and CASE statement

SELECT u.FirstName + ' ' + u.LastName as 'User',
COUNT(*) as 'Number of Calls',
CONVERT(varchar(4), SUM(CASE WHEN c.FromTime BETWEEN #FromDate AND #ToDate
THEN DATEDIFF(mi, c.FromTime, c.ToTime) ELSE 0 END) / 60) + ':' +
CASE WHEN SUM(CASE WHEN c.FromTime BETWEEN #FromDate AND #ToDate
THEN DATEDIFF(mi, c.FromTime, c.ToTime) ELSE 0 END) % 60 < 10 THEN '0' ELSE '' END +
CONVERT(varchar(2), SUM(DATEDIFF(mi, c.FromTime, c.ToTime)) % 60) as 'Total Time Spent',
CONVERT(varchar(4), AVG(DATEDIFF(mi, c.FromTime, c.ToTime)) / 60) + ':' +
CASE WHEN AVG(DATEDIFF(mi, c.FromTime, c.ToTime)) % 60 < 10 THEN '0' ELSE '' END +
CONVERT(varchar(2), AVG(DATEDIFF(mi, c.FromTime, c.ToTime)) % 60) as 'Average Call Time'
FROM Calls c
JOIN Users u ON u.UserID = c.TakenBy
WHERE c.FromTime BETWEEN #FromDate AND #ToDate
GROUP BY u.UserID, u.FirstName, u.LastName
ORDER BY u.FirstName + ' ' + u.LastName
The preceding SQL query returns the correct "Number of Calls" but the "Total Time" and "Average Time" are always the same regardless of the # of calls (which is obviously wrong).
I've read and tried to implement using the CASE WHEN __ Then value ELSE 0 inside SUM but it still returns an incorrect value.
The only way I can get this query to return correct results is if I completely strip out all other info, e.g.
SELECT SUM(DATEDIFF(mi, FromTime, ToTime)) FROM Calls WHERE c.FromTime BETWEEN...
How can I still use my JOIN and GROUP BY and get the aggregate functions to give me the results I want?
Thanks for any and all help!
You're probably better off with a subquery, e.g. in the SELECT part, add something like (SELECT SUM(...) FROM ... WHERE ...) AS total_sum.

How to improve this sql query

Here I get the "ProcessTime" in hours and minutes, but in case of seconds only I check for the opposite condition (see code below)
Is there any way to improve that?
select convert(varchar(10),ScanDate,101) as [Date], tmb.WO, tc.WOCategory, count(IdSingle) QtyProd, tb.Brand, tm.Model,
(case when (DateDiff(mi, Min(convert(varchar(10),ScanDate,8)), Max(convert(varchar(10),ScanDate,8)))/60%24 > 0) then
convert(varchar(5), DateDiff(mi, Min(convert(varchar(10),ScanDate,8)), Max(convert(varchar(10),ScanDate,8)))/60%24) + 'h '
else
''
end) +
(case when (DateDiff(mi, Min(convert(varchar(10),ScanDate,8)), Max(convert(varchar(10),ScanDate,8)))%60 > 0) then
convert(varchar(5), DateDiff(mi, Min(convert(varchar(10),ScanDate,8)), Max(convert(varchar(10),ScanDate,8)))%60) + 'm'
else
''
end) +
(case when
(DateDiff(mi, Min(convert(varchar(10),ScanDate,8)), Max(convert(varchar(10),ScanDate,8)))/60%24 <= 0) and
(DateDiff(mi, Min(convert(varchar(10),ScanDate,8)), Max(convert(varchar(10),ScanDate,8)))%60 <= 0) then
'< 1 min'
else
''
end) ProcessTime
With SQL Server 2005 you can use a CTE to improve this query.
Something like this:
;WITH minmax AS
(
SELECT someKey,
DateDiff(mi, Min(convert(varchar(10),ScanDate,8)), Max(convert(varchar(10),ScanDate,8))) as mdiff
FROM tablename
GROUP BY fieldName
)
Select convert(varchar(10),ScanDate,101) as [Date], tmb.WO, tc.WOCategory, count(IdSingle) QtyProd, tb.Brand, tm.Model,
(case when (mdiff/60%24 > 0) then
convert(varchar(5), mdiff/60%24) + 'h '
else
''
end) +
(case when (mdiff%60 > 0) then
convert(varchar(5), mdiff%60) + 'm'
else
''
end) +
(case when
(DateDiff(mi, mdiff/60%24 <= 0) and (DateDiff(mi, mdiff%60 <= 0) then
'< 1 min'
else
''
end) ProcessTime
from tablename
join minmax on tablename.somekey = minmax.somekey