classify interval Day and Night SQL server - sql

I have a time frame :
FROMTIME:06:01:00 - TOTIME:23:59:00 = DAY
FROMTIME:23:59:01 - TOTIME:06:00:00 = Night
I have a TIME_IN and TIME_OUT by a Car .
How to classify interval time by car is DAY or Night or DayAndNight
. I use code but it's execute very long time :
Declare #From_Time_Day time
,#To_Time_Day time
,#From_Time_Night time
,#To_Time_Night time
,#Midnight time
set #From_Time_Day = (select FROM_TIME from DAY_STATUS where DAY_CHECK=1)
set #To_Time_Day = (select TO_TIME from DAY_STATUS where DAY_CHECK=1)
set #From_Time_Night = (select FROM_TIME from DAY_STATUS where DAY_CHECK=2)
set #To_Time_Night = (select TO_TIME from DAY_STATUS where DAY_CHECK=2)
set #Midnight = '00:00:00'
select * from (
select
(case when (
cast((select top 1 IO_TIME from IO_INFO where IO_STATUS= 'IN' and CA_ID = Data.CA_ID and IO_ID < Data.IO_ID order by IO_ID desc) as date)
= cast(Data.IO_TIME as date)
)
then
(
case when (
cast((select top 1 IO_TIME from IO_INFO where IO_STATUS= 'IN' and CA_ID = Data.CA_ID and IO_ID < Data.IO_ID order by IO_ID desc) as time) >= #From_Time_Day
and cast(Data.IO_TIME as time) <=#To_Time_Day
) then 'DAY'
when (
cast((select top 1 IO_TIME from IO_INFO where IO_STATUS= 'IN' and CA_ID = Data.CA_ID and IO_ID < Data.IO_ID order by IO_ID desc) as time) >= #From_Time_Night
and cast(Data.IO_TIME as time) < #Midnight
) then 'Night'
when (
cast((select top 1 IO_TIME from IO_INFO where IO_STATUS= 'IN' and CA_ID = Data.CA_ID and IO_ID < Data.IO_ID order by IO_ID desc) as time) >= #Midnight
and cast(Data.IO_TIME as time) <= #To_Time_Night
) then 'Night'
else 'DayAndNight' end
)
when (
cast((select top 1 IO_TIME from IO_INFO where IO_STATUS= 'IN' and CA_ID = Data.CA_ID and IO_ID < Data.IO_ID order by IO_ID desc) as date)
<> cast(Data.IO_TIME as date)
)
then (
case when(
(cast((select top 1 IO_TIME from IO_INFO where IO_STATUS= 'IN' and CA_ID = Data.CA_ID and IO_ID < Data.IO_ID order by IO_ID desc) as time) >= #From_Time_Night and
cast((select top 1 IO_TIME from IO_INFO where IO_STATUS= 'IN' and CA_ID = Data.CA_ID and IO_ID < Data.IO_ID order by IO_ID desc) as time) < #Midnight)
and (cast(Data.IO_TIME as time)>=#Midnight and cast(Data.IO_TIME as time)<=#To_Time_Night)
) then 'Night'
else 'DayAndNight' end
)
end
) as INTERVAL
from IO_INFO as Data where IO_STATUS = 'OUT'

Okay, seeing that this is a Tricky type of query I've lost some time to find a solution, I've Created a Table in a test database with 4 fields, ID, Car, TimeOut, TimeIn, I've inserted some rows in this table to have samples and then I've made a Query that returns the state of the period for each travel of my cars.
Here is the Query:
SELECT [ID]
,[Car]
,[TimeOut]
,[TimeIn]
, MultiDay
,OutDAyNight
,InDAyNight
, CASE WHEN Multiday = 1 THEN 'Day/Night'
WHEN OutDAyNight+InDAyNight = 1 THEN 'Day/Night'
WHEN OutDAyNight+InDAyNight = 2 THEN 'Day'
WHEN OutDAyNight+InDAyNight = 0 THEN 'Night' END AS Periods
FROM
(SELECT [ID]
,[Car]
,[TimeOut]
,[TimeIn]
, CASE WHEN DATEDIFF(hour, [TimeOut],[TimeIn]) > 18
THEN 1 ELSE 0 END AS MultiDay
, CASE WHEN DATEPART(hour, [TimeOut]) > 6 AND
DATEPART(hour, [TimeOut]) <=23 THEN 1
WHEN DATEPART(hour, [TimeOut]) = 6 AND
DATEPART(minute, [TimeOut]) > 0 THEN 1
ELSE 0 END AS OutDAyNight
, CASE WHEN DATEPART(hour, [TimeIn]) > 6 AND
DATEPART(hour, [TimeIn]) <=23 THEN 1
WHEN DATEPART(hour, [TimeIn]) = 6 AND
DATEPART(minute, [TimeIn]) > 0 THEN 1
ELSE 0 END AS InDAyNight
FROM [dbo].[TbCarInOut] ) Times
Using this double query, In the first select (the lower one in the script) I Used the Datediff function to see if the time between exit and return of the car is more than 18 hours, this surely means there is a day and night period. Then I use the Datepart function to understand if the out time is Night or Day setting a 0 for Night and 1 for day and I do the same for the time In, this way, in the second query, I can use a simple case and a sum to decide if the period is Day/Night, Day or Night.
HTH

Related

Select Case in SQL Statement

I have the below SQL query that returns the following output for employee time ATTENDANCE.
Note when InOut = '0' it means in, and if InOut = '1' it means out.
Query:
SELECT
DATEPART(mi, LogTime) AS [InMin],
OutletName as [InOutletName]
FROM
[dbo].[AccessLog]
INNER JOIN
dbo.Outlets ON dbo.Outlets.OutletCode = dbo.AccessLog.TerminalID
WHERE
DATEPART(HOUR, LogTime) = '15'
AND InOut = '0'
AND CAST(LogDate AS date) = '2016-12-01'
Output:
InMin InOutletName
--------------------
47 GJ-SH1
The output I am looking for in to get OutMin, OutOutletName and this can be applied when InOut = '1'
Desired output:
InMin InOutletName OutMin OutOutletName
-----------------------------------------
47 GJ-SH1 10 GJ-SH1
I didn't get a chance to try it out in my SSMS, but required query should be something like this -
SELECT
Case When InOut = '0' Then DATEPART(mi, LogTime) End AS [InMin],
Case When InOut = '0' Then OutletName End as [InOutletName],
Case When InOut = '1' Then DATEPART(mi, LogTime) End AS [OutMin],
Case When InOut = '1' Then OutletName End as [OutOutletName]
FROM
[dbo].[AccessLog]
INNER JOIN
dbo.Outlets ON dbo.Outlets.OutletCode = dbo.AccessLog.TerminalID
WHERE
DATEPART(HOUR, LogTime) = '15'
AND CAST(LogDate AS date) = '2016-12-01';
A simple solution would be to join the AccessLog table twice:
SELECT
DATEPART(mi, ali.LogTime) AS [InMin],
DATEPART(mi, alo.LogTime) AS [OutMin],
OutletName as [InOutletName]
FROM dbo.Outlets
INNER JOIN [dbo].[AccessLog] ali
ON dbo.Outlets.OutletCode = ali.TerminalID
LEFT JOIN [dbo].[AccessLog] alo
ON dbo.Outlets.OutletCode = al0.TerminalID AND CAST(ali.LogDate AS date) = CAST(alo.LogDate AS date) AND alo.InOut = '1'
WHERE DATEPART(HOUR, LogTime) = '15'
AND ali.InOut = '0'
AND CAST(ali.LogDate AS date) = '2016-12-01'
Note that for the AccessLog representing the out date I've used a left join, so that you will also get Outlet names that hasn't logged out yet.
I just wrote it on the fly, not sure about the result but you can try it. It may help:
;with InQuery as (SELECT
DATEPART(mi, LogTime) AS [InMin],
OutletName as [InOutletName]
FROM [dbo].[AccessLog]
INNER JOIN dbo.Outlets
ON dbo.Outlets.OutletCode = dbo.AccessLog.TerminalID
WHERE DATEPART(HOUR, LogTime) = '15'
AND InOut = '0'
AND CAST(LogDate AS date) = '2016-12-01'),
OutQuery As (SELECT
DATEPART(mi, LogTime) AS [OutMin],
OutletName as [OutOutletName]
FROM [dbo].[AccessLog]
INNER JOIN dbo.Outlets
ON dbo.Outlets.OutletCode = dbo.AccessLog.TerminalID
WHERE DATEPART(HOUR, LogTime) = '15'
AND InOut = '1'
AND CAST(LogDate AS date) = '2016-12-01')
select iq.*, oq.* from InQuery iq, OutQuery oq

sql join and group by generated date range

I have Table1 and I need a query to populate Table2:
Problem here is with Date column. I want to know the process of location/partner combination per day. Main issue here is that I can't pick DateCreated and make it as default date since it doesn't necessarily cover whole date range, like in this example where it doesn't have 2015-01-07 and 2015-01-09. Same case with other dates.
So, my idea is to first select dates from some table which contains needed date range and then perform calculation for each day/location/partner combination from cte but in that case I can't figure out how to make a join for LocationId and PartnerId.
Columns:
Date - CreatedItems - number of created items where Table1.DateCreated = Table2.Date
DeliveredItems - number of delivered items where Table1.DateDateOut = Table2.Date
CycleTime - number of days delivered item was in the location (DateOut - DateIn + 1)
I started with something like this but it's very like that I completely missed the point with it:
with d as
(
select date from DimDate
where date between DATEADD(DAY, -365, getdate()) and getdate()
),
cr as -- created items
(
select
DateCreated,
LocationId,
PartnerId,
CreatedItems = count(*)
from Table1
where DateCreated is not null
group by DateCreated,
LocationId,
PartnerId
),
del as -- delivered items
(
select
DateOut,
LocationId,
ParnerId,
DeliveredItems = count(*),
CycleTime = DATEDIFF(Day, DateOut, DateIn)
from Table1
where DateOut is not null
and Datein is not null
group by DateOut,
LocationId,
PartnerId
)
select
d.Date
from d
LEFT OUTER JOIN cr on cr.DateCreated = d.Date -- MISSING JOIN PER LocationId and PartnerId
LEFT OUTER JOIN del on del.DateCompleted = d.Date -- MISSING JOIN PER LocationId and PartnerId
with range(days) as (
select 0 union all select 1 union all select 2 union all
select 3 union all select 4 union all select 5 union all
select 6 /* extend as necessary */
)
select dateadd(day, r.days, t.DateCreated) as "Date", locationId, PartnerId,
sum(
case
when dateadd(day, r.days, t.DateCreated) = t.DateCreated
then 1 else 0
end) as CreatedItems,
sum(
case
when dateadd(day, r.days, t.DateCreated) = t.Dateout
then 1 else 0
end) as DeliveredItems,
sum(
case
when dateadd(day, r.days, t.DateCreated) = t.Dateout
then datediff(days, t.DateIn, t.DateOut) + 1 else 0
end) as CycleTime
from
<yourtable> as t
inner join range as r
on r.days between 0 and datediff(day, t.DateCreated, t.DateOut)
group by dateadd(day, r.days, t.DateCreated), LocationId, PartnerId;
If you only want the end dates (rather than all the dates in between) this is probably a better approach:
with range(dt) as (
select distinct DateCreated from T union
select distinct DateOut from T
)
select r.dt as "Date", locationId, PartnerId,
sum(
case
when r.dt = t.DateCreated
then 1 else 0
end) as CreatedItems,
sum(
case
when r.dt = t.Dateout
then 1 else 0
end) as DeliveredItems,
sum(
case
when r.dt = t.Dateout
then datediff(days, t.DateIn, t.DateOut) + 1 else 0
end) as CycleTime
from
<yourtable> as t
inner join range as r
on r.dt in (t.DateCreated, t.DateOut)
group by r.dt, LocationId, PartnerId;
If to specify WHERE clause? Something Like that:
WHERE cr.LocationId = del.LocationId AND
cr.PartnerId = del.PartnerId

SQL query: select all where if x=1 then y<z else if x=2 then y<w

I want to select all columns however if fuel = 1 then start date > '1 oct 2013' else if fuel = 0 then start date > '1 september'.
Using SQL server 2005.
I tried this..
SELECT * FROM EntitlementEpisode
WHERE
IF EnFuel = 1
THEN
IF
BEGIN
[EnEpisodeStart] >
(SELECT TOP 1 [BeYearStart]
FROM [BenefitYear] ORDER BY [BeId] desc)
END
ELSE IF EnFuel = 0
THEN
IF
BEGIN
[EnEpisodeStart] >
CONVERT(VARCHAR(30), '1 OCT' + YEAR(GETDATE()))
END
Is this the right way of going about it?
Use the strength of the binary operators...!
SELECT *
FROM EntitlementEpisode
WHERE
(EnFuel=1 AND EnEpisodeStart] >
(SELECT TOP 1 [BeYearStart]
FROM [BenefitYear] ORDER BY [BeId] desc))
OR
(EnFuel=0 AND [EnEpisodeStart] >
CONVERT(VARCHAR(30), '1 OCT' + YEAR(GETDATE())))
How about something like
SELECT *
FROM EntitlementEpisode
WHERE (EnFuel = 1 AND [EnEpisodeStart] > (SELECT TOP 1 [BeYearStart] FROM [BenefitYear] ORDER BY [BeId] desc)
OR (EnFuel = 0 AND [EnEpisodeStart] > CONVERT(VARCHAR(30), '1 OCT' + YEAR(GETDATE())))

SubQuery accessing parent value

Hello all I am having a issue with this:
Select Customer_Tool_Lookup.ID,
Customer.CustomerName,
(select count(ID) as perDay
FROM CustomerData
WHERE DatetimeInserted >= '2013-04-29 00:00:00.000'
AND DatetimeInserted <= '2013-04-29 11:59:59.599'
AND Customer_ID = Customer_Tool_Lookup.Customer_ID) as DCount,
(select count(ID) as perMonth
FROM CustomerData
WHERE DatetimeInserted >= '2013-04-01 00:00:00.000'
AND DatetimeInserted <= '2013-04-30 11:59:59.599'
AND Customer_ID = Customer_Tool_Lookup.Customer_ID) as mCount,
(select count(ID) as perYear
FROM CustomerData
WHERE DatetimeInserted >= '2013-01-01 00:00:00.000'
AND DatetimeInserted <= '2013-04-30 11:59:59.599'
AND Customer_ID = Customer_Tool_Lookup.Customer_ID) as yCount,
Customer_tool_Lookup.PricePerClick,
Customer_Tool_lookup.MinimumPerMonth,
case
when ClicksPerMonth > (select count(ID) as perMonth
FROM CustomerData
WHERE DatetimeInserted >= '2013-04-01 00:00:00.000'
AND DatetimeInserted <= '2013-04-30 11:59:59.599'
AND Customer_ID = Customer_Tool_Lookup.Customer_ID)
then ClicksPerMonth
else ((select count(ID) as perMonth
FROM CustomerData
WHERE DatetimeInserted >= '2013-04-01 00:00:00.000'
AND DatetimeInserted <= '2013-04-30 11:59:59.599'
AND Customer_ID = Customer_Tool_Lookup.Customer_ID) - Customer_tool_lookup.MinimumPerMonth) * PricePerClick END as TDMonth
FROM Customer_tool_Lookup Left join Customer on Customer.ID = Customer_Tool_Lookup.Customer_ID
I am getting an error:
Invalid objectName 'Customer_tool_Lookup'
It started when I added the and statement at the end of the subquery:
AND Customer_ID = Customer_Tool_Lookup.Customer_ID <--
one each of them.
I normally don't ask SQL questions I have done subqueries before but for some reason I have trouble using parent data.
Thanks!
My suggestion would be to convert those correlated subqueries into a single subquery that you join to. If you use this subquery, then you can access the alias inside of your TDMonth CASE expression:
Select ctl.ID,
c.CustomerName,
cd.DCount,
cd.mCount,
cd.yCount
ctl.PricePerClick,
ctl.MinimumPerMonth,
case
when ClicksPerMonth > cd.mCount
then ClicksPerMonth
else (cd.mCount - ctl.MinimumPerMonth) * PricePerClick
END as TDMonth
from Customer_tool_Lookup ctl
left join Customer c
on c.ID = ctl.Customer_ID
left join
(
select Customer_ID,
COUNT(case
when DatetimeInserted >= '2013-04-29 00:00:00.000'
and DatetimeInserted <= '2013-04-29 11:59:59.599'
then ID end) as DCount,
COUNT(case
when DatetimeInserted >= '2013-04-01 00:00:00.000'
and DatetimeInserted <= '2013-04-30 11:59:59.599'
then ID end) as mCount,
COUNT(case
when DatetimeInserted >= '2013-01-01 00:00:00.000'
and DatetimeInserted <= '2013-04-30 11:59:59.599'
then ID end) as yCount
from CustomerData
group by Customer_ID
) cd
on ctl.Customer_ID = cd.Customer_ID

More efficient way of grouping rows by hour (using a timestamp)

I'm trying to show a log of daily transactions that take place. My current method is embarrassingly inefficient and I'm sure there is a much better solution. Here is my current query:
select ReaderMACAddress,
count(typeid) as 'Total Transactions',
SUM(CASE WHEN CAST("Timestamp" as TIME) between '05:00:00' and '11:59:59' THEN 1 ELSE 0 END) as 'Morning(5am-12pm)',
SUM(CASE WHEN CAST("Timestamp" as TIME) between '12:00:00' and '17:59:59' THEN 1 ELSE 0 END) as 'AfternoonActivity(12pm-6pm)',
SUM(CASE WHEN CAST("Timestamp" as TIME) between '18:00:00' and '23:59:59' THEN 1 ELSE 0 END) as 'EveningActivity(6pm-12am)',
SUM(CASE WHEN CAST("Timestamp" as TIME) between '00:00:00' and '04:59:59' THEN 1 ELSE 0 END) as 'OtherActivity(12am-5am)'
from Transactions
where ReaderMACAddress = '0014f54033f5'
Group by ReaderMACAddress;
which returns the results:
ReaderMACAddress Total Transactions Morning(5am-12pm) AfternoonActivity(12pm-6pm) EveningActivity(6pm-12am) OtherActivity(12am-5am)
0014f54033f5 932 269 431 232 0
(sorry for any alignment issues here)
At the moment I only want to look at a single Reader that I specify (through the where clause). Ideally, it would be easier to read if the time sections were in a single column and the results, i.e. a count function were in a second column yielding results such as:
Total Transactions 932
Morning(5am-12pm) 269
AfternoonActivity(12pm-6pm) 431
EveningActivity(6pm-12am) 232
OtherActivity(12am-5am) 0
Thanks for any help :)
I would first consider a computed column, but I believe from a previous post you don't have the ability to change the schema. So how about a view?
CREATE VIEW dbo.GroupedReaderView
AS
SELECT ReaderMACAddress,
Slot = CASE WHEN t >= '05:00' AND t < '12:00' THEN 1
WHEN t >= '12:00' AND t < '18:00' THEN 2
WHEN t >= '18:00' THEN 3 ELSE 4 END
FROM
(
SELECT ReaderMACAddress, t = CONVERT(TIME, [Timestamp])
FROM dbo.Transactions
) AS x;
Now your per-MAC address query is much, much simpler:
SELECT Slot, COUNT(*)
FROM dbo.GroupedReaderView
WHERE ReaderMACAddress = '00...'
GROUP BY Slot;
This will provide a result like:
1 269
2 431
3 232
4 0
You can also add WITH ROLLUP which will provide a grand total with the Slot column being NULL:
SELECT Slot, COUNT(*)
FROM dbo.GroupedReaderView
WHERE ReaderMACAddress = '00...'
GROUP BY Slot
WITH ROLLUP;
Should yield:
1 269
2 431
3 232
4 0
NULL 932
And you can pivot that if you need to, add labels per slot, etc. in your presentation tier.
You could also do it this way, it just makes the view a lot more verbose and pulls a lot of extra data when you query it directly; it's also slightly less efficient to group by strings.
CREATE VIEW dbo.GroupedReaderView
AS
SELECT ReaderMACAddress,
Slot = CASE WHEN t >= '05:00' AND t < '12:00' THEN
'Morning(5am-12pm)'
WHEN t >= '12:00' AND t < '18:00' THEN
'Afternoon(12pm-6pm)'
WHEN t >= '18:00' THEN
'Evening(6pm-12am)'
ELSE
'Other(12am-5am)'
END
FROM
(
SELECT ReaderMACAddress, t = CONVERT(TIME, [Timestamp])
FROM dbo.Transactions
) AS x;
These aren't necessarily more efficient than what you've got, but they're less repetitive and easier on the eyes. :-)
Also if you don't want to (or can't) create a view, you can just put that into a subquery, e.g.
SELECT Slot, COUNT(*)
FROM
(
SELECT ReaderMACAddress,
Slot = CASE WHEN t >= '05:00' AND t < '12:00' THEN
'Morning(5am-12pm)'
WHEN t >= '12:00' AND t < '18:00' THEN
'Afternoon(12pm-6pm)'
WHEN t >= '18:00' THEN
'Evening(6pm-12am)'
ELSE
'Other(12am-5am)'
END
FROM
(
SELECT ReaderMACAddress, t = CONVERT(TIME, [Timestamp])
FROM dbo.Transactions
) AS x
) AS y
WHERE ReaderMACAddress = '00...'
GROUP BY Slot
WITH ROLLUP;
Just an alternative that still lets you use BETWEEN and may be even a little less verbose:
SELECT Slot, COUNT(*)
FROM
(
SELECT ReaderMACAddress,
Slot = CASE WHEN h BETWEEN 5 AND 11 THEN 'Morning(5am-12pm)'
WHEN h BETWEEN 12 AND 17 THEN 'Afternoon(12pm-6pm)'
WHEN h >= 18 THEN 'Evening(6pm-12am)'
ELSE 'Other(12am-5am)'
END
FROM
(
SELECT ReaderMACAddress, h = DATEPART(HOUR, [Timestamp])
FROM dbo.Transactions
) AS x
) AS y
WHERE ReaderMACAddress = '00...'
GROUP BY Slot
WITH ROLLUP;
UPDATE
To always include each slot even if there are no results for that slot:
;WITH slots(s, label, h1, h2) AS
(
SELECT 1, 'Morning(5am-12pm)' , 5, 11
UNION ALL SELECT 2, 'Afternoon(12pm-6pm)' , 12, 17
UNION ALL SELECT 3, 'Evening(6pm-12am)' , 18, 23
UNION ALL SELECT 4, 'Other(12am-5am)' , 0, 4
)
SELECT s.label, c = COALESCE(COUNT(y.ReaderMACAddress), 0)
FROM slots AS s
LEFT OUTER JOIN
(
SELECT ReaderMACAddress, h = DATEPART(HOUR, [Timestamp])
FROM dbo.Transactions
WHERE ReaderMACAddress = '00...'
) AS y
ON y.h BETWEEN s.h1 AND s.h2
GROUP BY s.label
WITH ROLLUP;
The key in all of these cases is to simplify and not repeat yourself. Even if SQL Server only performs it once, why convert to time 4+ times?