SQL Door access - sql

I'm pretty new to sql and have been set a task of creating a query which will give me the first and last entrance of each user and how long they were in the office for.
So far i have got the below
SELECT
CONVERT(VARCHAR(10),evts.Eventtime,101) as Date,
CONVERT(VARCHAR(10),evts.Eventtime,108) as Time,
un.UserName AS [User],
case
when p.Name = 'ACU:4321104 - 5th floor (In)' then '5th entrance'
when p.name = 'ACU:4321176 - 4th floor (In)' then '4th entrance'
when p.name = 'ACU:4321176 - 4th floor (Out)' then '4th exit'
when p.name = 'ACU:4321104 - 5th floor (Out)' then '5th exit'
when p.name = 'ACU:4321158 - 3rd Floor (In)' then '3rd entrance'
when p.name = 'ACU:4321158 - 3rd Floor (Out)' then '3rd exit'
end as [where]
FROM
Net2Events.dbo.Events AS evts LEFT OUTER JOIN
dbo.vw_UserNamesFS AS un ON evts.UserID = un.UserID LEFT OUTER JOIN
Net2Events.dbo.[Event subtype description] AS esd ON evts.EventSubType = esd.EventSubType LEFT OUTER JOIN
Net2Events.dbo.[Event description] AS ed ON evts.EventType = ed.EventType LEFT OUTER JOIN
dbo.Peripherals AS p ON evts.Address = p.Address AND evts.SubAddr = p.SubAddr
WHERE evts.EventTime >= dateadd(day,datediff(day,1,GETDATE()),0)
AND evts.EventTime < dateadd(day,datediff(day,0,GETDATE()),0)
Which gives access to every entrance and exit. Any help would be highly appreciated.
Thanks

You will almost certainly need to adjust your where clause if this is more than a one off report, but for all EventTimes between those two dates, this query will give you the first and last EventTime by each user and the time between those in minutes.
If you need to further break this down by specific flors, you can add your where field back in (Though I would recommend against that name, as it is a SQL keyword) to both the select and the group by:
SELECT un.UserName AS [User]
,MIN(evts.Eventtime) as FirstEvent
,MAX(evts.Eventtime) as LastEvent
,DATEDIFF(minute,MIN(evts.Eventtime),MAX(evts.Eventtime)) as TimeDifference
FROM Net2Events.dbo.Events AS evts
LEFT OUTER JOIN dbo.vw_UserNamesFS AS un
ON evts.UserID = un.UserID
LEFT OUTER JOIN Net2Events.dbo.[Event subtype description] AS esd
ON evts.EventSubType = esd.EventSubType
LEFT OUTER JOIN Net2Events.dbo.[Event description] AS ed
ON evts.EventType = ed.EventType
LEFT OUTER JOIN dbo.Peripherals AS p
ON evts.Address = p.Address
AND evts.SubAddr = p.SubAddr
WHERE evts.EventTime >= dateadd(day,datediff(day,1,GETDATE()),0)
AND evts.EventTime < dateadd(day,datediff(day,0,GETDATE()),0)
GROUP BY un.UserName
I think you will need to do more than just the first and last event however, as I don't think there is a production database in the world with 100% correct data. This means you will need to find the earliest In event and the latest Out event and then check that these make sense.
You can 'exclude' certain rows from aggregates like min and max with a case statement that substitutes an impossible value way beyond the normal range which you can handle if needs be later on:
min(case when <criteria to include> then EventTime else '29990101' end)
Edit to add in response to comment
To format your Time Difference into various date elements, you need to start with your largest date part - ie: Days? Hours? - and then work down from there. Start with working out how many full days you have, which for a value in minutes means doing integer division by 60 to get the number of full hours and then by 24 to get the number of full days:
#difference / 60 / 24
We then need the number of full hours, minus all the hours taken up in the full days. To do this we can do the above calculation by use modulo instead of division to find the remainder of the hours that don't create a full day:
#difference / 60 % 24
And then to find the number of minutes left over, we simply need to find the remainder of a division by 60:
#difference % 60
Do some cast/converting to get the right formatting and you have your difference in Days, Hours and Minutes:
declare #start datetime = getdate() - 400.255;
declare #end datetime = getdate();
declare #difference int = datediff(minute,#start,#end);
select cast(#difference/60/24 as nvarchar(5)) + case when #difference/60/24 = 1 then ' Day, ' else ' Days, ' end
+ cast(#difference/60%24 as nvarchar(5)) + case when #difference/60%24 = 1 then ' Hour, ' else ' Hours, ' end
+ cast(#difference%60 as nvarchar(5)) + case when #difference%60 = 1 then ' Minute' else ' Minutes' end;
which returns: 400 Days, 6 Hours, 7 Minutes
Of course, if you only ever want Hours and Minutes in hh:mm format and are super confident this time difference will never go over 23 hours, 59 minutes and 59.9999999 seconds, you can simply convert the difference to a time value by adding it to an arbitrary date, and formatting accordingly in your front end application:
cast(dateadd(minute,#difference,0) as time)
Which returns: 06:07:00.0000000 for the parameters above - Note the missing 400 days.

Related

Get Decimal Places for 10/7

I am trying to make a Calculation where I want to update ideal hours based on number of Assistant and Head Coaches based on whether a restaurant is 5 day or 7 day a week. When I make a calculation where I use 10/7 or 20/7, it takes it as 1 and 2 respectively. I am currently using trunc but I have tried using cast :: decimal(10,6) etc and it doesn't work.
select
a.entity,
a.store_name,
a.order_date,
a.daily_ideal_hours,
a.daily_ideal_hours - (case when b.days_open like '%Weekdays%' then ((c.total_acs*(20/5) + c.total_hcs*(10/5)))
when b.days_open like '%All%' then ((c.total_acs * trunc(20/7,10)) + (c.total_hcs * trunc(10/7,10))) end) as updated_value
from scorecards_ideal_labor_hours as a
left join days_store_open as b
on a.entity = b.entity
left join hc_ac_data as c
on a.entity = c.entity_id
and a.order_date = c.report_date
where a.order_date between '2021-12-06' and '2021-12-12'
and a.entity = 66
order by a.order_date desc;
How do I fix this?
You don't need to use trunc function here.
Just typecast both numerator and denominator to float.
This will give the final answer with decimal places, without any rounding:
select (10::float)/(7::float);
This gives answer as:
1.4285714285714286
If you need to round the final answer to few digits only:
select trunc((10::float)/(7::float),2);

YTD for the below query

I want to add add the Year to date component to this code. I have tried some other ways but I am not getting what I would like to see. Can someone please help me revised this to include the YTD in addition to the Month to date that is already there?
SELECT
COST__DESC,
ST.AD_SRV_MTN AS MONTH_OF_AD,
COUNT(DISTINCT CM.CM_NBR) AS CMS,
MEM_MO AS MBR_MTH,
CMS/MBR_MTH*1000 AS CMS_PER_1000
FROM XTR.FT_CM AS CM
JOIN XTR.FT_ST AS ST ON ST.CM_NBR = CM.CM_NBR
JOIN XTR.DIM_MED_CST AS MC ON ST.CST_CK = MCC.CST_CK
JOIN XTR.DIM_AF AS AFF ON ST.PRO_CK = AFF.AFF_CK
JOIN XTR.DIM_ADJDCTN_STAT AS A_S ON ST.ADJDCTN_STAT_CK = A_S.ADJDCTN_STAT_CK
JOIN XTR.DIM_ADJ_OT AS OT ON ST.ADJ_CK = OT.ADJ_CK
LEFT JOIN
(SELECT
CALENDAR_YEAR_MONTH as YEAR_MO,
SUM(MBR.COUNT_NBR) as MEM_MO
FROM XTR.FT_MBR_MONTHS MBR
INNER JOIN DIM_MBR_C ON MBR.DB_MBR_CK = DIM_MBR_C.DB_MBR_CK
AND MBR.DATE_CK BETWEEN DIM_MBR_C.DB_eff_date_ck
AND DIM_MBR_C.DB_END_DATE_CK
INNER JOIN DIM_DATE DT ON ELI_DATE_CK = DT.DATE_CK
WHERE MBR.F_C_CK = 500058321 AND YEAR_MO >= 201701
GROUP BY 1) MM ON ST.AD_SRV_MTN = MM.YEAR_MO
WHERE ST.F_C_CK = 500058321 AND ST.ST_START_DATE_CK >= 20200101
AND ST.AD_SRV_MTN > 201912 AND MC.MED_DESC IN ('Er', 'IP')
AND ST.AD_SRV_MTN < ((EXTRACT (YEAR FROM CURRENT_DATE) *100) +
EXTRACT (MONTH FROM CURRENT_DATE))
GROUP BY 1,2,4
ORDER BY 1,2
Honestly I don't really get your SQL and what is counted, but: Your can play with dates quite easy in Teradata, as Dates are stored (and can be used) internally as INTEGER. Just keep in mind year 1900 as year 0 and format YYYYMMDD.
So e.g. 16-Apr-2020 is in Format YYYYMMDD 20200416 and if you take 1900 as 0 you'll end up with 1200416 which is the internal format. Just try SELECT CURRENT_DATE (INT); - So if you want compare YearNumers you just have to divide by 10000.
With this your can implement YTD as SUM (CASE WHEN CURRENT_DATE/10000 = <YourDateField>/10000 THEN <YourKPI> else 0 END) as YourKPI_YTD. Counting can be done by SUM...THEN 1 ELSE 0 END....

Joining Tables on Time, IF NULL edit time by 1 minute

I have two tables.
Table 1 = My Trades
Table 2 = Market Trades
I want query the market trade 1 minute prior to my trade. If there is no market trade in Table 2 that is 1 minute apart from mine then I want to look back 2 minutes and so on till I have a match.
Right now my query gets me 1 minute apart but I cant figure out how to get 2 minutes apart if NULL or 3 minutes apart if NULL (up to 30 minutes). I think it would best using a variable but im not sure the best way to approach this.
Select
A.Ticker
,a.date_time
,CONVERT(CHAR(16),a.date_time - '00:01',120) AS '1MINCHANGE'
,A.Price
,B.Date_time
,B.Price
FROM
Trade..MyTrade as A
LEFT JOIN Trade..Market as B
on (a.ticker = b.ticker)
and (CONVERT(CHAR(16),a.date_time - '00:01',120) = b.Date_time)
There is no great way to do this in MySQL. But, because your code looks like SQL Server, I'll show that solution here, using APPLY:
select t.Ticker ,
convert(CHAR(16), t.date_time - '00:01', 120) AS '1MINCHANGE',
t.Price,
m.Date_time,
m.Price
from Trade..MyTrade as t outer apply
(select top 1 m.*
from Trade..Market m
where a.ticker = b.ticker and
convert(CHAR(16), t.date_time - '00:01', 120) >= b.Date_time)
order by m.DateTime desc
) m;

SQL Server : remove duplicates from count()

I'm creating a report in a SQL Server database. I will show it's code first and then describe what it does and where is problem.
SELECT
COUNT(e.flowid) AS [count],
t.name AS [process],
CAST(DATEPART(YEAR, e.dtcr) AS VARCHAR) + '-' + CAST(RIGHT('0' + RTRIM(DATEPART(MONTH, e.dtcr)), 2) AS VARCHAR) + '-' + CAST(RIGHT('0' + RTRIM(DATEPART(DAY, e.dtcr)), 2) AS VARCHAR) AS [day]
FROM
dbo.[Event] e
JOIN
dbo.Flow f ON e.flowid = f.id
JOIN
dbo.WorkOrder o ON f.workorderno = o.number
AND o.treenodeid IN (26067, 26152, 2469, 1815, 1913) -- only from requested processes
JOIN
dbo.TreeNode t ON o.treenodeid = t.id -- for process name in select statement
JOIN
dbo.Product p ON f.productid = p.id
AND p.materialid NOT IN (26094, 27262, 27515, 27264, 28192, 28195, 26090, 26092, 26093, 27065, 26969, 27471, 28351, 28353, 28356, 28976, 27486, 29345, 29346, 27069, 28653, 28654, 26735, 26745, 28686) -- exclude unwanted family codes
WHERE
e.pass = 1 -- only passed units
AND e.treenodeid IN (9036, 9037, 9038, 9039, 12594, 26330) -- only from requested events
AND e.dtcr BETWEEN '2015-12-01 00:00:00.000' AND '2016-05-31 23:59:59.999' -- only from requested time interval
GROUP BY
DATEPART(YEAR, e.dtcr), DATEPART(MONTH, e.dtcr), DATEPART(DAY, e.dtcr), t.name
ORDER BY
[day]
What query does is count units that passed specific events in a time periods (with some filters).
Important tables are:
Event - basically log for units passing specific events.
Product - list of units.
Output is something like this:
COUNT PROCESS DAY
71 Process-1 2015-12-01
1067 Process-2 2015-12-01
8 Process-3 2015-12-01
3 Process-4 2015-12-01
15 Process-1 2015-12-02
276 Process-2 2015-12-02
47 Process-3 2015-12-02
54 Process-4 2015-12-02
It does well but there is an issue. In some specific cases unit can pass same event several times and this query counts every such passing. I need to count every unit only once.
"Duplicated" records are in Event table. They have different dates and ids. Same for all records I need to count only once is flowid. Is there any simple way to achieve this?
Thank you for your time and answers!
To count each flowid only once, do count(distinct flowid), i.e.
SELECT
COUNT(distinct e.flowid) AS [count],
t.name AS [process],
CAST(DATEPART(YEAR, e.dtcr) AS VARCHAR) + '-' + CAST(RIGHT('0' + RTRIM(DATEPART(MONTH, e.dtcr)), 2) AS VARCHAR) + '-' + CAST(RIGHT('0' + RTRIM(DATEPART(DAY, e.dtcr)), 2) AS VARCHAR) AS [day]
FROM
...
It sounds like you need the first time that something passes the threshold. You can get the first time using row_number(). This can be tricky with the additional conditions on the query. This modification might work for you:
select sum(case when seqnum = 1 then 1 else 0 end) as cnt,
. . .
from (select e.*,
row_number() over (partition by eventid order by e.dtcr) as seqnum
from event e
where e.pass = 1 and -- only passed units
e.treenodeid IN (9036, 9037, 9038, 9039, 12594, 26330) and
e.dtcr >= '2015-12-01' AND e.dtcr < '2016-06-01'
) e join
. . .
You don't specify how the same event is identified for the duplicates. The above uses eventid for this purpose.

Concatenation of max_times within 15 minute time intervals SQL

I am trying to write a sql query that pulls an ID and concatenates the max times within 15 minute time intervals starting from the first time.
i.e. for one log_id data might be
101 01:01
101 01:08
101 01:23
101 02:01
101 02:10
101 02:16
we would want to display
101 01:01, 01:08, 01:23, 2:01, 2:16
any ideas?
here is the starting query we are using:
select
ol.log_id,
ifm.meas_value,
ifm.measure_id,
ifm.recorded_time
from meas ifm
inner join rec ifr on ifm.fsd_id = ifr.fsd_id
inner join pe on ifr.data_id = pe.data_id
inner join record_summary f on pe.n_id = f.n_id
inner join pe2 on pe.t_id = pe2.t_id and pe.date = pe2.date and pe2.type = 51
inner join log l on pe2.ata_id = l.data_id and l.date = pe2.date
where ifm.measure_id in ('891')
and ol.date >= trunc(sysdate - 3)
for each log_id there will be multiple recorded times we want to pull the first time, and the maximum time in every 15 minute interval until the last time. These times will be concatenated to a list.
We tried a listagg with all the values, but there are too many times so the end-user wants to only see one value for every 15 minutes.
Basically you have 3 questions in one:
How to round date field to 15 min?
https://community.oracle.com/thread/1042863
ceil(to_number(to_char(test_timestamp, 'yyyymmddhh24mi'))/15) * 15
How to get the row from a group?
That is a very popular question, you will find something here:
https://stackoverflow.com/questions/tagged/oracle+greatest-n-per-group?sort=votes&pageSize=30
How to aggregate over string values to concatenate within a group:
LISTAGG(convertedTimeField, ' ')
Try to combine all three pieces in one query to get required result.
To simplify working with the following table:
CREATE TABLE Test ( id NUMBER, log DATE );
(Note: DATE in oracle is actually dateTime. String concatenation is done with ||.)
Your given list seems to indicate, that when the max and the min is the same you only display it once, this can be done by grouping twice:
SELECT
O.id,
O.logDate,
LISTAGG(O.logTimes, ', ') WITHIN GROUP (ORDER BY O.logTimes) logTimes
FROM (
SELECT
O.id,
O.logDate,
O.G G,
CASE
WHEN TO_CHAR(MIN(O.log), 'HH24:MI') <> TO_CHAR(MAX(O.log), 'HH24:MI') THEN
TO_CHAR(MIN(O.log), 'HH24:MI') || ', ' || TO_CHAR(MAX(O.log), 'HH24:MI')
ELSE
TO_CHAR(MIN(O.log), 'HH24:MI')
END logTimes
FROM (
SELECT
T.id,
T.log,
TRUNC(T.log) logDate,
TO_CHAR(T.log, 'HH24') || FLOOR(TO_NUMBER(TO_CHAR(T.log, 'MI')) / 15) G
FROM
Test T
) O
GROUP BY
O.id, O.logDate, O.G
) O
GROUP BY
O.id, O.logDate
;
See SQL fiddle.