H2 SQL Query column not found in scope - sql

In relation with : SQL: Find unavailability per minute of a ressource in an appointment
((click above link for schema + info))
I'm trying to run this query in an H2 SQL database. I'm a little unfamiliar with H2 syntax. I noted the column num in the WHERE clause that causes the issue.
Error:
Column "NUM" not found; SQL statement:
CREATE FORCE VIEW (
SELECT
"A"."APPOINTMENT_ID",
"A"."APPOINTMENT_START_TIME",
"A"."APPOINTMENT_END_TIME",
"C"."COMPONENT_HOST_NAME",
'unavailable' AS "STATE"
FROM "PUBLIC"."APPOINTMENT" "A"
LEFT OUTER JOIN "PUBLIC"."APPOINTMENT_COMPONENT" "AC"
ON "A"."APPOINTMENT_ID" = "AC"."APPOINTMENT_ID"
INNER JOIN "PUBLIC"."COMPONENT" "C"
ON "C"."COMPONENT_ID" = "AC"."COMPONENT_ID"
WHERE ((CONVERT("A"."APPOINTMENT_START_TIME", TIME) <= DATEADD('minute', "NUM", CAST('00:00:00' AS TIME)))
AND (CONVERT("A"."APPOINTMENT_END_TIME", TIME) >= DATEADD('minute', "NUM", CAST('00:00:00' AS TIME))))
AND ("C"."COMPONENT_ID" IN(1))
FETCH FIRST ROW ONLY
) AS
SELECT
"A"."APPOINTMENT_ID",
"A"."APPOINTMENT_START_TIME",
"A"."APPOINTMENT_END_TIME",
"C"."COMPONENT_HOST_NAME",
'unavailable' AS "STATE"
FROM "PUBLIC"."APPOINTMENT" "A"
LEFT OUTER JOIN "PUBLIC"."APPOINTMENT_COMPONENT" "AC"
ON "A"."APPOINTMENT_ID" = "AC"."APPOINTMENT_ID"
INNER JOIN "PUBLIC"."COMPONENT" "C"
ON "C"."COMPONENT_ID" = "AC"."COMPONENT_ID"
WHERE ((CONVERT("A"."APPOINTMENT_START_TIME", TIME) <= DATEADD('minute', "NUM", CAST('00:00:00' AS TIME)))
AND (CONVERT("A"."APPOINTMENT_END_TIME", TIME) >= DATEADD('minute', "NUM", CAST('00:00:00' AS TIME))))
AND ("C"."COMPONENT_ID" IN(1))
FETCH FIRST ROW ONLY [42122-200] 42S22/42122 (Help)
My code:
with times(num) as
(
select 30 as num
union all select (num + 30)
from times where num < (24*60)
)
select dateadd('minute', num, cast('00:00:00' as time)) as datetimeinterval, unavailabilities.state from times
outer join(
select top 1 a.appointment_id, a.appointment_start_time, a.appointment_end_time, c.component_host_name, 'unavailable' as state
from appointment a
left join appointment_component ac on a.appointment_id = ac.appointment_id
inner join component c on c.component_id = ac.component_id
where
dateadd('minute', -->num<--, cast('00:00:00' as time)) between convert(a.appointment_start_time, time) and convert(a.appointment_end_time, time)
and
c.component_id in (1)
) unavailabilities
TLDR: Trying to get unavailabilities of a list of components by the minute or by a range of minutes (30 minutes here). Num should return a multiple of 30 in this case, depending on the time frame selected for which it will check if the components are taken or not.
N.B. I changed machine=component and appmach=appointment_component (cross table) from the link above

I am not sure about the syntax. I am not very much familiar with H2. But isn't num should be used in on clause instead of subquery ? Please check:
with recursive times(num) as
(
select 30 as num
union all select (num + 30)
from times where num < (24*60)
)
select dateadd('minute', num, cast('00:00:00' as time)) as datetimeinterval, unavailabilities.state from times
outer join(
select top 1 a.appointment_id, a.appointment_start_time, a.appointment_end_time, c.component_host_name, 'unavailable' as state
from appointment a
left join appointment_component ac on a.appointment_id = ac.appointment_id
inner join component c on c.component_id = ac.component_id
where
dateadd('minute', -->num<--, cast('00:00:00' as time)) between convert(a.appointment_start_time, time) and convert(a.appointment_end_time, time)
and
c.component_id in (1)
) unavailabilities
on dateadd('minute', num, cast('00:00:00' as time)) between convert(appointment_start_time, time) and convert(appointment_end_time, time)

Related

Showing list of all 24 hours in sql server if there is no data also

I have a query where I need to show 24 hour calls for each day.
But I am getting the hours which I have calls only.
My requirement is I need to get all the hours split and 0 if there are no calls.
Please suggest
Below is my code.
select #TrendStartDate
,isd.Name
,isd.Call_ID
,isd.callType
,DATEPART(HOUR,isd.ArrivalTime)
from [PHONE_CALLS] ISD WITH (NOLOCK)
WHERE CallType = 'Incoming'
and Name not in ('DefaultQueue')
and CAST(ArrivalTime as DATe) between #TrendStartDate and #TrendEndDate
The basic idea is that you use a table containing numbers from 0 to 23, and left join that to your data table:
WITH CTE AS
(
SELECT TOP 24 ROW_NUMBER() OVER(ORDER BY ##SPID) - 1 As TheHour
FROM sys.objects
)
SELECT #TrendStartDate
,isd.Name
,isd.Call_ID
,isd.callType
,TheHour
FROM CTE
LEFT JOIN [PHONE_CALLS] ISD WITH (NOLOCK)
ON DATEPART(HOUR,isd.ArrivalTime) = TheHour
AND CallType = 'Incoming'
AND Name NOT IN ('DefaultQueue')
AND CAST(ArrivalTime as DATe) BETWEEN #TrendStartDate AND #TrendEndDate
If you have a tally table, you should use that. If not, the cte will provide you with numbers from 0 to 23.
If you have a numbers table you can use a query like the following:
SELECT d.Date,
h.Hour,
Calls = COUNT(pc.Call_ID)
FROM ( SELECT [Hour] = Number
FROM dbo.Numbers
WHERE Number >= 0
AND Number < 24
) AS h
CROSS JOIN
( SELECT Date = DATEADD(DAY, Number, #TrendStartDate)
FROM dbo.Numbers
WHERE Number <= DATEDIFF(DAY, #TrendStartDate, #TrendEndDate)
) AS d
LEFT JOIN [PHONE_CALLS] AS pc
ON pc.CallType = 'Incoming'
AND pc.Name NOT IN ('DefaultQueue')
AND CAST(pc.ArrivalTime AS DATE) = d.Date
AND DATEPART(HOUR, pc.ArrivalTime) = h.Hour
GROUP BY d.Date, h.Hour
ORDER BY d.Date, h.Hour;
The key is to get all the hours you need:
SELECT [Hour] = Number
FROM dbo.Numbers
WHERE Number >= 0
AND Number < 24
And all the days that you need in your range:
SELECT Date = DATEADD(DAY, Number, #TrendStartDate)
FROM dbo.Numbers
WHERE Number < DATEDIFF(DAY, #TrendStartDate, #TrendEndDate)
Then cross join the two, so that you are guaranteed to have all 24 hours for each day you want. Finally, you can left join to your call table to get the count of calls.
Example on DB<>Fiddle
You can use SQL SERVER recursivity with CTE to generate the hours between 0 and 23 and then a left outer join with the call table
You also use any other Method mentioned in this link to generate numbers from 0 to 23
Link to SQLFiddle
set dateformat ymd
declare #calls as table(date date,hour int,calls int)
insert into #calls values('2020-01-02',0,66),('2020-01-02',1,888),
('2020-01-02',2,5),('2020-01-02',3,8),
('2020-01-02',4,9),('2020-01-02',5,55),('2020-01-02',6,44),('2020-01-02',7,87),('2020-01-02',8,90),
('2020-01-02',9,34),('2020-01-02',10,22),('2020-01-02',11,65),('2020-01-02',12,54),('2020-01-02',13,78),
('2020-01-02',23,99);
with cte as (select 0 n,date from #calls union all select 1+n,date from cte where 1+n <24)
select distinct(cte.date),cte.n [Hour],isnull(ca.calls,0) calls from cte left outer join #calls ca on cte.n=ca.hour and cte.date=ca.date

Better SQL to link records within 1 hour sliding window

I try to create a new table with (user1, user2, count), denoting the number of time both users share same value in one column within 1 hour interval.
WITH d1 AS (SELECT * FROM user_access_tab
WHERE last_access >= 1544630400 AND last_access <= 1545601214)
SELECT d1.userid, d2.userid, COUNT(*) as count
FROM d1
INNER JOIN d1 AS d2
ON d1.item = d2.item AND d1.userid != d2.userid
WHERE d1.last_access < d2.last_access AND
(d2.last_access - d1.last_access) <= 3600
GROUP BY d1.userid, d2.userid
However, such queries are very slow even with just 1 hour interval limit. I need to query about 6 months data, which accumulated to billions rows of records. How can I improve the SQL ?
The user_access_tab looks like below
The new table looks like below. Two userids are linked as long as their last_access are within 1 hour window, and counter increases by 1.
IMHO, the problem with your query is that you join way too many records with each others.
Take the minimal example below where I inserted a CTE:
WITH user_access_tab(item, userid, last_access) AS (
SELECT UNNEST(ARRAY['A', 'A', 'A', 'A', 'A', 'A']),
UNNEST(ARRAY[11383575,11383575,52539489,52539489,24830131,24830131]),
UNNEST(ARRAY[1545645324,1545645325,1545647895,1545647896,1545646895,1545646896])
/*UNION ALL
SELECT UNNEST(ARRAY['A', 'A', 'A', 'A', 'A', 'A']),
UNNEST(ARRAY[11383575,11383575,52539489,52539489,24830131,24830131]),
UNNEST(ARRAY[1545645326,1545645327,1545647897,1545647898,1545646897,1545646898])*/
),
d1 AS (SELECT * FROM user_access_tab
WHERE last_access >= 1544630400 AND last_access <= 1545661214
)
SELECT d1.userid, d2.userid, COUNT(*) as count
FROM d1
INNER JOIN d1 AS d2
ON d1.item = d2.item AND d1.userid != d2.userid
WHERE d1.last_access < d2.last_access AND
(d2.last_access - d1.last_access) <= 3600
GROUP BY d1.userid, d2.userid
The CTE has 6 records and the query returns 3 records with a count equal to 4 for each.
Now uncomment the second half of the CTE and what you get is 3x16. That is more than the number of records in the CTE and it only gets worse with more users and events.
I would suggest you do something WAY more restrictive on 1 side of your JOIN. Example below:
WITH d1 AS (SELECT * FROM user_access_tab
WHERE last_access >= 1544630400 AND last_access <= 1545661214),
d2 AS (
SELECT *
FROM d1 d
WHERE NOT EXISTS (SELECT FROM d1 WHERE item = d.item AND userid = d.userid AND d.last_access BETWEEN last_access+1 AND d.last_access + 3600))
SELECT d2.item, d2.userid, d1.userid, COUNT(*)
FROM d2
LEFT OUTER JOIN d1 ON d2.item = d1.item AND d2.userid = d1.userid AND d1.last_access BETWEEN d2.last_access and d2.last_access + 3600
GROUP BY d2.item, d2.userid, d1.userid
Obviously, this will change the result in the COUNT(*) column (on top of being faster) but since it did not seem to make much sense before, I would say it is for the best.

SQL coding for group and sum with multiple tables and where statement

I am fairly new to SQL. Here is my end goal:
sum of hours worked by calendar day by caregiver after 12/31/15
Here is the code I have now
select br_ID, car_ID, car_FirstName, car_LastName, car_SkillCd, car_Role, car_EmpSubCod_UD, schst_Name, svcc_Name, dbo.ConvertTimeFromUtc(sch_StartTime , tz_Data), dbo.ConvertTimeFromUtc(sch_endtime , tz_Data), sch_Units, sch_PayDate, sch_PayUnits, cli_LastName, cli_FirstName
from T_Schedules
inner join T_Branches on br_ID = sch_BranchID
inner join T_TimeZones on tz_ID = br_TimeZoneID
Full outer join T_Caregiver ON T_schedules.sch_CaregiverID = T_Caregiver.car_ID
full outer join T_ServiceCode on T_Schedules.sch_ServiceCodeID = T_ServiceCode.svcc_ID
full outer join T_ScheduleStatus on T_schedules.sch_ScheduleStatusID = T_ScheduleStatus.schst_ID
full outer join T_Client on T_Schedules.sch_ClientID = T_Client.cli_ID
where sch_ScheduleStatusID = 3 and dbo.ConvertTimeFromUtc(sch_StartTime , tz_Data) >= '2016-01-01'
Anyone know how to group the hours by caregiver. I can't just sum the scheduled units because if it's an overnight shift, those hours could be split between 2 calendar days.
The overnight shift is a bit of a problem and most likely will skew your results.
Unless you go back and assoicate 1/2 the shift to one day and 1/2 to the other day there is nothing SQL can do for you. (I think) How I would approach this is a simple group by and see if that is good enough:
SELECT dbo.ConvertTimeFromUtc(sch_StartTime , tz_Data),
br_ID, car_ID, car_FirstName,
car_LastName, car_SkillCd, car_Role,
car_EmpSubCod_UD, schst_Name, svcc_Name,
dbo.ConvertTimeFromUtc(sch_endtime , tz_Data),
SUM(sch_Units),
sch_PayDate, sch_PayUnits, cli_LastName, cli_FirstName
FROM T_Schedules
INNER JOIN T_Branches on br_ID = sch_BranchID
INNER JOIN T_TimeZones on tz_ID = br_TimeZoneID
FULL OUTER JOIN T_Caregiver ON T_schedules.sch_CaregiverID = T_Caregiver.car_ID
FULL OUTER JOIN T_ServiceCode on T_Schedules.sch_ServiceCodeID = T_ServiceCode.svcc_ID
FULL OUTER JOIN T_ScheduleStatus on T_schedules.sch_ScheduleStatusID = T_ScheduleStatus.schst_ID
FULL OUTER JOIN T_Client on T_Schedules.sch_ClientID = T_Client.cli_ID
WHERE sch_ScheduleStatusID = 3 and dbo.ConvertTimeFromUtc(sch_StartTime , tz_Data) >= '2016-01-01'
GROUP BY [sch_StartTime]
The problem: A shift from sch_StartTime to sch_endtime can be over midnight, so some hours were worked on one day and some on the next.
We write two queries: one for the first day and one for the next and then glue both sets together with UNION ALL. Here is the query built step by step with WITH clauses:
with worked as
(
select
sch_caregiverid as caregiverid,
dbo.ConvertTimeFromUtc(sch_StartTime, tz_Data) as starttime,
dbo.ConvertTimeFromUtc(sch_endtime, tz_Data) as endtime
from T_Schedules
where sch_ScheduleStatusID = 3 and dbo.ConvertTimeFromUtc(sch_StartTime , tz_Data) >= '2016-01-01'
)
, day1 as
(
select
caregiverid,
starttime,
case when day(starttime) <> day(endtime) then
datetimefromparts(year(starttime), month(starttime), day(starttime), 23, 59, 59, 999)
else
endtime
end as endtime
from worked
)
, day2 as
(
select
caregiverid,
datetimefromparts(year(endtime), month(endtime), day(endtime), 0, 0, 0, 0) as starttime,
endtime
from worked
where day(starttime) <> day(endtime)
)
, bothdays as
(
select caregiverid, starttime, endtime from day1
union all
select caregiverid, starttime, endtime from day2
)
, hours_worked as
(
select
caregiverid,
convert(date, starttime) as whichday,
sum(datediff(hour, starttime, endtime)) as total
from bothdays
group by caregiverid, convert(date, starttime)
)
select *
from t_caregiver
join hours_worked on hours_worked.caregiverid = t_caregiver.car_id;

Join data from hourly and 30 minute tables

I have a table with data every hour:
and a table with data every 30 minutes:
I would like to join these two tables (by date) to get batVolt and TA in the same table and repeat the values for batVolt for the 30 minutes between the hour.
SELECT *
FROM HourTable t
INNER JOIN HalfHourTable ht
ON CAST(t.repDate AS Date) = CAST(ht.repDate AS Date)
AND DATEPART(HOUR, t.repDate) = DATEPART(HOUR, ht.repDate)
Edit
Your query should be
SELECT n.repDate
, n.TA
, a.batVolt
FROM [DAP].[dbo].[ARRMet] AS n
FULL JOIN [DAP].[dbo].[array3] AS a
ON DATEPART(HOUR, n.repDate) = DATEPART(HOUR, a.repDate)
AND CAST(n.repDate AS DATE) = CAST(a.repDate AS DATE)
WHERE CAST(n.repDate AS DATE) = '20150831'
ORDER BY n.repDate DESC
I would do this slightly differently than M.Ali. This uses fewer functions and seems a bit simpler to me.
SELECT *
FROM HourTable t
INNER JOIN HalfHourTable ht on
t.repDate = dateadd(hour, datediff(hour, 0, ht.repDate), 0)
You could use DATEPART()
select ta.repDate, ta.code, ta.TA, bat.batVolt
from table2 ta
join table 1 bat
on (DATEPART(yyyymmddhh,bat.repDate) = DATEPART(yyyymmddhh, ta.repDate)
I don't remember exact syntax for datepart but should be something similar to this
You can try following
SELECT x.*, y.*
FROM BatVoltDetails x
INNER JOIN TADetails y
ON LEFT(CONVERT(VARCHAR, x.repDate, 120), 13) = LEFT(CONVERT(VARCHAR, y.repDate, 120), 13)

SQL Match employee intime (punch time) with employee shift

I have a Stored Procedure that retrieves employee daily summary intime - outtime:
SELECT ads.attendancesumid,
ads.employeeid,
ads.date,
ads.day, -- month day number
ads.intime,
ads.outtime
--employee shift intime and outtime
ss.intime,
ss.outtime
FROM employee_attendance_daily_summary ads
JOIN employee emp
ON emp.employeeid = ads.employeeid
JOIN setup_shift ss
ON ss.shiftcode = emp.shiftcode
AND DATEPART(dw, ads.date) = ss.day
WHERE ads.employeeid = 4 -- just to filter one employee
The result of the query is something like this:
Each day is repeated 3 times because table setup_shift (employee shifts) has:
Monday to Sunday for 3 different shift types: DAY, AFTERNOON and NIGHT.
Here is the same info but with the shift type column:
What I need is to ONLY get 1 row per day but with the closest employee shift depending on the intime and outtime.
So the desire result should looks like this:
Any clue on how to do this? Appreciate it in advance.
I have also these case where intime is 00:00:00 but outtime has a value:
UPDATE:
HERE IS THE SQL FIDDLE
http://sqlfiddle.com/#!6/791cb/7
select ads.attendancesumid,
ads.employeeid,
ads.date,
ads.day,
ads.intime,
ads.outtime,
ss.intime,
ss.outtime
from employee_attendance_daily_summary ads
join employee emp
on emp.employeeid = ads.employeeid
join setup_shift ss
on ss.shiftcode = emp.shiftcode
and datepart(dw, ads.date) = ss.day
where ads.employeeid = 4
and ((abs(datediff(hh,
cast(ads.intime as datetime),
cast(ss.intime as datetime))) between 0 and 2) or
(ads.intime = '00:00:00' and
ss.intime =
(select min(x.intime)
from setup_shift x
where x.shiftcode = ss.shiftcode
and x.intime > (select min(y.intime)
from setup_shift y
where y.shiftcode = x.shiftcode))))
This would be much easier if the times were in seconds after midnight, rather than in a time, datetime, or string format. You can convert them using the formula:
select datepart(hour, intime) * 3600 + datepart(minute, intime) * 60 + datepart(second, intime)
(Part of this is just my own discomfort with all the nested functions needed to handle other data types.)
So, let me assume that you have a series of similar columns measured in seconds. You can then approach this problem by taking the overlap with each shift and choosing the shift with the largest overlap.
with t as (
<your query here>
),
ts as (
select t.*,
(datepart(hour, ads.intime) * 3600 + datepart(minute, ads.intime) * 60 +
datepart(second, ads.intime)
) as e_intimes,
. . .
from t
),
tss as (
select ts.*,
(case when e_intimes >= s_outtimes then 0
when e_outtimes <= s_inttimes then 0
else (case when e_outtimes < s_outtimes then e_outtimes else s_outtimes end) -
(case when e_intimes > s_intimes then e_intimes else s_intimes end)
end) as overlap
from ts
)
select ts.*
from (select ts.*,
row_number() over (partition by employeeid, date
order by overlap desc
) as seqnum
from ts
) ts
where seqnum = 1;
Try this man,I just take the minimum time difference of the each set datediff(mi,intime,shift_intime)
Select * from
(select
row_number() over(partition by employeeid
order by datediff(mi,intime,shift_intime) asc) as id,
attendance,employeeid,date,day,intime,outime,shiftintime,shiftoutime from table
)
where id=1