Select each distinct value over time without losing NULLs in between - sql

Let's assume a table has many columns and a Temporal table is logging its history. There is one field I need to know when it changes.
Number
VersionStartDate
991281
2021-11-12 08:27:11
991281
2021-11-12 08:20:11
NULL
2021-11-12 07:20:11
NULL
2021-11-12 06:20:11
771281
2021-11-11 08:26:11
NULL
2021-11-11 08:25:11
661281
2021-11-10 08:24:11
NULL
2021-11-10 08:22:11
661281
2021-11-10 08:21:11
551281
2021-11-09 08:20:11
I need to get each value, and the moment it changed. I also need to know if it's been set NULL so this query is not giving what I need.
SELECT
Number,
MIN(VersionStartDate) [Date]
FROM _TABLE_
GROUP BY
Number
ORDER BY
[Date] DESC
The result should be
Number
VersionStartDate
991281
2021-11-12 08:20:11
NULL
2021-11-12 06:20:11
771281
2021-11-11 08:26:11
NULL
2021-11-11 06:25:11
661281
2021-11-10 08:24:11
NULL
2021-11-10 08:22:11
661281
2021-11-10 08:21:11
551281
2021-11-09 08:20:11

Quite similar to JMabee's which appeared after I started working on it, but perhaps a bit simpler:
CREATE TABLE #d (Number INT, VersionStartDate DATETIME);
INSERT INTO #d(Number, VersionStartDate)
VALUES
(991281 ,'2021-11-12T08:27:11'),
(991281 ,'2021-11-12T08:20:11'),
(NULL ,'2021-11-12T07:20:11'),
(NULL ,'2021-11-12T06:20:11'),
(771281 ,'2021-11-11T08:26:11'),
(NULL ,'2021-11-11T08:25:11'),
(661281 ,'2021-11-10T08:24:11'),
(NULL ,'2021-11-10T08:22:11'),
(661281 ,'2021-11-10T08:21:11'),
(551281 ,'2021-11-09T08:20:11');
WITH cte AS
(
SELECT Number,
VersionStartDate,
LAG(Number, 1) OVER (ORDER BY VersionStartDate) AS PrevNumber
FROM #d
)
SELECT cte.Number,
cte.VersionStartDate
FROM cte
WHERE ISNULL(cte.Number, -1) <> ISNULL(cte.PrevNumber, -1)
ORDER BY cte.VersionStartDate DESC;

Well this is one way to do it, I am sure there is a more eloquent way to write it, but it gets you started:
CREATE TABLE #T(Number int, VersionStartDate datetime)
INSERT INTO #T vALUES
(991281,'2021-11-12 08:27:11'),
(991281,'2021-11-12 08:20:11'),
(NULL,'2021-11-12 07:20:11'),
(NULL,'2021-11-12 06:20:11'),
(771281,'2021-11-11 08:26:11'),
(NULL,'2021-11-11 08:25:11'),
(661281,'2021-11-10 08:24:11'),
(NULL,'2021-11-10 08:22:11'),
(661281,'2021-11-10 08:21:11'),
(551281,'2021-11-09 08:20:11')
SELECT Number, MIN(VersionStartDate) VersionStartDate
FROM
(
SELECT *, SUM(CASE WHEN ISNULL(Number,-1) <> ISNULL(LG,-1) THEN 1 ELSE 0 END) OVER(ORDER BY VersionStartDate desc) GRP
FROM
(
SELECT *, LAG(Number,1,-1) OVER(ORDER BY VersionStartDate desc) LG
FROM #T
) X
) Y
GROUP BY GRP,Number
ORDER BY VersionStartDate desc

Add VersionStartDate in your group by.
SELECT
Number,
MIN(VersionStartDate)
FROM _TABLE_
GROUP BY
Number, VersionStartDate
ORDER BY
VersionStartDate DESC

Related

How to retrieve default value even when do no exist record in database

Querying data as showed below there is no record on database for day 2021-10-03.
date
value
2021-10-01
100
2021-10-02
90
2021-10-04
10
2021-10-05
40
I would like to execute the query using date between as SELECT ... WHERE date BETWEEN '2021-10-01' AND '2021-10-05' and in case of do not exist data for a specific day, to retrieve zero as exemplified below:
date
value
2021-10-01
100
2021-10-02
90
2021-10-03
10
2021-10-04
10
2021-10-05
40
Is it possible? in bigQuery?
I tried the query below, but retrieved duplicated values.
WITH `project.myproject` AS (
SELECT
DATA_VENDA AS date,
CAST(SUM(VLR_VENDA_TABELA) AS FLOAT64) AS total,
FROM `project.myproject`
WHERE
(DATA_VENDA BETWEEN '2020-10-02'
AND '2020-10-07')
AND COD_CP = '0000010232'
GROUP BY
DATA_VENDA
ORDER BY
DATA_VENDA
),
dates AS (
SELECT total, date
FROM `project.myproject`, UNNEST(GENERATE_DATE_ARRAY(date('2020-10-02'), date('2020-10-07'))) AS date
)
SELECT d.date, IFNULL(t.total, 0) total
FROM dates d
LEFT JOIN `project.myproject` t
ON d.date = t.date
AND d.total = t.total
ORDER BY d.date
I found out the answers running command below. The difference from that to this is that in this new one I removed the line AND d.total = t.total, who was responsible for creating duplicated data. The final answer follow below:
WITH `project.myproject` AS (
SELECT
DATA_VENDA AS date,
CAST(SUM(VLR_VENDA_TABELA) AS FLOAT64) AS total,
FROM `project.myproject`
WHERE
(DATA_VENDA BETWEEN '2020-10-02'
AND '2020-10-07')
AND COD_CP = '0000010232'
GROUP BY
DATA_VENDA
ORDER BY
DATA_VENDA
),
dates AS (
SELECT total, date
FROM `project.myproject`, UNNEST(GENERATE_DATE_ARRAY(date('2020-10-02'), date('2020-10-07'))) AS date
)
SELECT d.date, IFNULL(t.total, 0) total
FROM dates d
LEFT JOIN `project.myproject` t
ON d.date = t.date
ORDER BY d.date
You can simply do that with the common table expression(CTE) as shown below.
DECLARE #Datatemp TABLE (
Id INT IDENTITY(1,1) NOT NULL,
CDate DATETIME,
Val INT
)
INSERT INTO #Datatemp SELECT '2021-10-01',10
INSERT INTO #Datatemp SELECT '2021-10-02',50
INSERT INTO #Datatemp SELECT '2021-10-04',24
INSERT INTO #Datatemp SELECT '2021-10-05',18
;WITH DateTemp(Date) AS (
SELECT CAST('2021-10-01' AS DATETIME)
UNION ALL
SELECT [Date]+1
FROM DateTemp
WHERE [Date] < '2021-10-05'
)
SELECT DateTemp.[Date] CDat
,ISNULL(t.Val, 0) Val
FROM DateTemp
LEFT JOIN #Datatemp t ON t.CDate = DateTemp.[Date]
ORDER BY DateTemp.[Date]
--OPTION (MAXRECURSION 0)
By default number of iterations for recursive CTE is 100. As long as this number is exceeded, the query will be interrupted and an error will be generated. If you want to remove this restriction, you can specify MAXRECURSION 0.

SQL Server cumulative SUM of a DATEDIFF into a percentage

I am trying to get a cumulative SUM of a DATEDIFF into a percentage from some basic data I have, here is a small snapshot:
ID IIn IOut
AB123 2015-11-06 15:24:44.057 2015-11-14 01:00:00.000
QA565 2015-10-27 20:12:19.753 2015-11-06 03:00:00.000
UN555 2015-12-29 06:29:23.417 2016-01-03 08:00:00.000
LG602 2015-08-07 16:52:13.573 2015-08-11 03:00:00.000
ETC ETC
I then use DATEDIFF to get a number of days:
SELECT ID, DATEDIFF(hour, IIn, IOut)/24.0 IDays
FROM TimeTable
Which gives me:
ID IDays
AB123 7.416666
QA565 9.291666
UN555 5.083333
LG602 3.458333
What I want is a count of ID'S split by their IDay's (rounded down) with a cumulative % from lowest IDay's to highest like so:
ID IDays IDaysPer
LG602 3 12.5
UN555 5 33.33
AB123 7 62.49
QA565 9 100
You can do this using a couple of windowed aggregates, placing your original query in a CTE for convenience (A subquery would also work):
declare #timeTable table (ID char(5) not null, IIn datetime not null,
IOut datetime not null)
insert into #timeTable(ID,IIn,IOut) values
('AB123','2015-11-06T15:24:44.057','2015-11-14T01:00:00.000'),
('QA565','2015-10-27T20:12:19.753','2015-11-06T03:00:00.000'),
('UN555','2015-12-29T06:29:23.417','2016-01-03T08:00:00.000'),
('LG602','2015-08-07T16:52:13.573','2015-08-11T03:00:00.000')
;With Diffs as (
SELECT ID, DATEDIFF(hour, IIn, IOut)/24.0 IDays
FROM #timeTable
)
select
*,
(
SUM(IDays) OVER (ORDER BY IDays, ID)
/
SUM(IDays) OVER ()
) * 100 as IDaysPer
from
Diffs
order by IDays
Note that I couldn't quite make sense of your "rounded down" requirement but you should be able to use any common rounding technique wrapped around the appropriate calculation. So my outputs don't quite match yours:
ID IDays IDaysPer
----- --------------------------------------- ---------------------------------------
LG602 3.458333 13.696300
UN555 5.083333 33.828300
AB123 7.416666 63.201300
QA565 9.291666 100.000000
Consider TimeTable has already the data
WITH t1 (ID, IDays)
AS (
SELECT ID, DATEDIFF(hour, IIn, IOut) / 24.0 AS IDays
FROM TimeTable
)
SELECT
ID, FLOOR(IDays),
(FLOOR(IDays) / (SELECT SUM(FLOOR(IDays)) FROM t1 t2 WHERE t1.IDays <= t2.IDays)) * 100.0 AS IDaysPer
FROM t1
ORDER BY 2 ASC
Here you go : Output matches with yours...
create table #TEMp
(ID VARCHAR(100)
,IIn datetime
,IOut datetime
)
insert into #temp(ID,IIn,IOut) values
('AB123','2015-11-06T15:24:44.057','2015-11-14T01:00:00.000'),
('QA565','2015-10-27T20:12:19.753','2015-11-06T03:00:00.000'),
('UN555','2015-12-29T06:29:23.417','2016-01-03T08:00:00.000'),
('LG602','2015-08-07T16:52:13.573','2015-08-11T03:00:00.000')
select ID,IDays AS Idays,ROUND(CAST(SUM(IDays) OVER(ORDER BY IDays) AS FLOAT)/CAST(SUM(IDays)OVER() AS FLOAT) * 100,2) AS IdaysPer
from
(
select *,ROUND(DATEDIFF(hour, IIn, IOut)/24,0) IDays
from #TEMP
)T

SQL Server: query to get the data between two values from same columns and calculate time difference

I have a requirement to get the number of hours between two values, say 20 and 25 or above (this will be user input values and not fixed). Below is the table with sample data.
Consider in the table on 01-09-2016 08:40 value_ID is 25 and it reaches back to 20 on 02-09-2016 13:20, I need to consider the number of hours between these two range ie 12 hours and 40 min it is .. Similarly 04-09-2016 13:20 it reached 26.3 (which is above 25 ) and '06-09-2016 16:20' reached 19.3 (below 20) and number of hours is 45 hours. I tried creating a function, however it's not working..
CODE TO CREATE TABLE:
CREATE TABLE [dbo].[NumOfHrs](
[ID] [float] NULL,
[Date] [datetime] NULL,
[Value_ID] [float] NULL
) ON [PRIMARY]
CODE to insert data :
INSERT INTO [dbo].[NumOfHrs]
([ID]
,[Date]
,[Value_ID])
VALUES
(112233,'8-31-2016 08:20:00',19.2),
(112233,'9-01-2016 08:30:00',24),
(112233,'9-01-2016 08:40:00',25),
(112233,'9-01-2016 09:20:00',26),
(112233,'9-02-2016 10:20:00',27),
(112233,'9-02-2016 10:20:00',24),
(112233,'9-02-2016 10:20:00',23),
(112233,'9-02-2016 11:20:00',22),
(112233,'9-02-2016 12:20:00',21),
(112233,'9-02-2016 13:20:00',20),
(112233,'9-03-2016 13:20:00',19.8),
(112233,'9-04-2016 13:20:00',21),
(112233,'9-04-2016 14:20:00',24),
(112233,'9-04-2016 16:20:00',24.6),
(112233,'9-04-2016 19:20:00',26.3),
(112233,'9-04-2016 23:20:00',27),
(112233,'9-05-2016 00:20:00',22),
(112233,'9-06-2016 16:20:00',19.3),
(112233,'9-07-2016 00:20:00',22),
(112233,'9-08-2016 00:20:00',21),
(112233,'9-09-2016 00:20:00',23),
(445566,'9-10-2016 00:20:00',24),
(445566,'9-11-2016 00:20:00',25),
(445566,'9-12-2016 00:20:00',26),
(445566,'9-13-2016 00:20:00',24),
(445566,'9-14-2016 00:20:00',23),
(445566,'9-15-2016 00:20:00',24),
(445566,'9-16-2016 00:20:00',21),
(445566,'9-17-2016 00:20:00',20),
(445566,'9-18-2016 00:20:00',18.5),
(445566,'9-19-2016 00:20:00',17)
image of the table:
Well, I couldn't think of anything simpler. Here's my try to solve the problem:
;with NumOfHrs_rn as (
select id, [Date], Value_ID,
row_number() over (partition by id order by [date]) AS rn
from [dbo].[NumOfHrs]
), NumOfHrs_lag as (
select t1.id, t1.[date],
t2.Value_ID as prev_value,
t1.Value_ID as curr_value
from NumOfHrs_rn as t1
-- get previous value (lag)
join NumOfHrs_rn as t2 on t1.id = t2.id and t1.rn = t2.rn + 1
), NumOfHrs_flag as (
select id, [Date], prev_value, curr_value,
case
when curr_value >= 25 and prev_value < 25 then 'start'
when curr_value <= 20 and prev_value > 20 then 'stop'
else 'ignore'
end as flag
from NumOfHrs_lag
), NumOfHrs_grp as (
select id, [Date], curr_value, flag,
row_number() over (partition by id order by [Date]) -
case flag
when 'start' then 0
when 'stop' then 1
end as grp
from NumOfHrs_flag
where flag in ('start', 'stop')
)
select min([Date]) AS 'start', max([Date]) as 'stop'
from NumOfHrs_grp
group by id, grp
order by min([Date])
Output:
start stop
------------------------------------------------
2016-09-01 08:40:00.000 2016-09-02 13:20:00.000
2016-09-04 19:20:00.000 2016-09-06 16:20:00.000
2016-09-11 00:20:00.000 2016-09-17 00:20:00.000
You can manipulate the above query in order to get the time difference expressed in hours/minutes/seconds format.
Demo here

SQL failing to add value from previous row into the next

I am trying to add the value of the previous row to the current row into the column cumulative
Select
Ddate as Date, etype, Reference, linkacc as ContraAcc,
Description,
sum(case when amount > 0 then amount else 0 end) as Debits,
sum(case when amount < 0 then amount else 0 end) as Credits,
sum(amount) as Cumulative
from
dbo.vw_LT
where
accnumber ='8400000'
and [DDate] between '2016-04-01 00:00:00' and '2016-04-30 00:00:00'
and [DataSource] = 'PAS11CEDCRE17'
group by
Ddate, etype, Reference, linkacc, Description, Amount
Output(what i am getting):
Date Reference ContraAcc Description Debits Credits Cumulative
--------------------------------------------------------------------------
2016-04-01 CC007 8000000 D/CC007 0 -39.19 -39.19
2016-04-01 CC007 8000000 D/CC007 1117.09 0 1117.09
2016-04-01 CC009 8000000 CC009 2600 0 2600
in the cumulative column should like below(what i need):
Date Reference ContraAcc Description Debits Credits Cumulative
--------------------------------------------------------------------------
2016-04-01 CC007 8000000 D/CC007 0 -39.19 -39.19
2016-04-01 CC007 8000000 D/CC007 1117.09 0 1077.9
2016-04-01 CC009 8000000 CC009 2600 0 3677.9
Before we delve into the solution, let me tell you that if you are using SQL Server version more than 2012, there are LAG and LEAD, which can help you to solve this.
I am not giving you an exact query to solve your problem (as we dont know what your primary key for that table is), but you can get the idea by seeing the below example
DECLARE #t TABLE
(
accountNumber VARCHAR(50)
,dt DATETIME
,TransactedAmt BIGINT
)
INSERT INTO #t VALUES ('0001','7/20/2016',1000)
INSERT INTO #t VALUES ('0001','7/21/2016',-1000)
INSERT INTO #t VALUES ('0001','7/22/2016',2000)
INSERT INTO #t VALUES ('0002','7/20/2016',500)
INSERT INTO #t VALUES ('0002','7/21/2016',-500)
INSERT INTO #t VALUES ('0002','7/22/2016',2000)
;WITH CTE AS
(
SELECT ROW_NUMBER() OVER(Partition by accountNumber order by dt) as RN, *
FROM #t
),CTE1 AS
(
SELECT *,TransactedAmt As TotalBalance
FROM CTE WHERE rn = 1
UNION
SELECT T1.*,T1.TransactedAmt + T0.TransactedAmt as TotalBalance
FROM CTE T1
JOIN CTE T0
ON T1.accountNumber = T0.accountNumber
AND T1.RN = T0.RN+1
AND T1.RN > 1
)
select * from CTE1 order by AccountNumber

Calculate Average time spend based on a change in location zone

I have a table similar to
create table LOCHIST
(
RES_ID VARCHAR(10) NOT NULL,
LOC_DATE TIMESTAMP NOT NULL,
LOC_ZONE VARCHAR(10)
)
with values such as
insert into LOCHIST values(0911,2015-09-23 12:27:00.000000,SYLVSYLGA);
insert into LOCHIST values(5468,2013-02-15 13:13:24.000000,30726);
insert into LOCHIST values(23894,2013-02-15 13:12:13.000000,BECTFOUNC);
insert into LOCHIST values(24119,2013-02-15 13:12:09.000000,30363);
insert into LOCHIST values(7101,2013-02-15 13:11:37.000000,37711);
insert into LOCHIST values(26083,2013-02-15 13:11:36.000000,SHAWANDAL);
insert into LOCHIST values(24978,2013-02-15 13:11:36.000000,38132);
insert into LOCHIST values(26696,2013-02-15 13:11:27.000000,29583);
insert into LOCHIST values(5468,2013-02-15 13:11:00.000000,37760);
insert into LOCHIST values(5552,2013-02-15 13:10:55.000000,30090);
insert into LOCHIST values(24932,2013-02-15 13:10:48.000000,JBTTLITGA);
insert into LOCHIST values(23894,2013-02-15 13:10:42.000000,47263);
insert into LOCHIST values(26803,2013-02-15 13:10:25.000000,32534);
insert into LOCHIST values(24434,2013-02-15 13:10:03.000000,PLANSUFVA);
insert into LOCHIST values(26696,2013-02-15 13:10:00.000000,GEORALBGA);
insert into LOCHIST values(5468,2013-02-15 13:09:54.000000,19507);
insert into LOCHIST values(23894,2013-02-15 13:09:48.000000,37725);
This table literally goes on for millions of records.
Each RES_ID represents the ID of a trailer who pings their location to a LOC_ZONE which is then stored at the time in LOC_DATE.
What I am trying to find, is the average amount of time spent for all trailers in a specific location zone. For example, if trailer x spent 4 hours in in loc zone PLANSUFVA, and trailer y spent 6 hours in loc zone PLANSUFVA I would want to return
Loc Zone Avg Time
PLANSUFVA 5
Is there anyway to do this without cursors?
I really appreciate your help.
This needs SQL 2012:
with data
as (
select *, (case when LOC_ZONE != PREVIOUS_LOC_ZONE or PREVIOUS_LOC_ZONE is null then ROW_ID else null end) as STAY_START, (case when LOC_ZONE != NEXT_LOC_ZONE or NEXT_LOC_ZONE is null then ROW_ID else null end) as STAY_END
from (
select RES_ID, LOC_ZONE, LOC_DATE, lead(LOC_DATE, 1) over (partition by RES_ID, LOC_ZONE order by LOC_DATE) as NEXT_LOC_DATE, lag(LOC_ZONE, 1) over (partition by RES_ID order by LOC_DATE) as PREVIOUS_LOC_ZONE, lead(LOC_ZONE, 1) over (partition by RES_ID order by LOC_DATE) as NEXT_LOC_ZONE, ROW_NUMBER() over (order by RES_ID, LOC_ZONE, LOC_DATE) as ROW_ID
from LOCHIST
) t
), stays as (
select * from (
select RES_ID, LOC_ZONE, STAY_START, lead(STAY_END, 1) over (order by ROWID) as STAY_END
from (
select RES_ID, LOC_ZONE, STAY_START, STAY_END, ROW_NUMBER() over (order by RES_ID, LOC_ZONE, STAY_START desc) as ROWID
from data
where STAY_START is not null or STAY_END is not null
) t
) t
where STAY_START is not null and STAY_END is not null
)
select s.LOC_ZONE, avg(datediff(second, LOC_DATE, NEXT_LOC_DATE)) / 60 / 60 as AVG_IN_HOURS
from data d
inner join stays s on d.RES_ID = s.RES_ID and d.LOC_ZONE = s.LOC_ZONE and d.ROW_ID >= s.STAY_START and d.ROW_ID < s.STAY_END
group by s.LOC_ZONE
To solve this problem, you need the amount of time spent at each location.
One way to do this is with a correlated subquery. You need to group adjacent values. The idea is to find the next value in the sequence:
select resid, min(loc_zone) as loc_zone, min(loc_date) as StartTime,
max(loc_date) as EndTime,
nextdate as NextStartTime
from (select lh.*,
(select min(loc_date) from lochist lh2
where lh2.res_id = lh.res_id and lh2.loc_zone <> lh.loc_zone and
lh2.loc_date > lh.loc_date
) as nextdate
from lochist lh
) lh
group by lh.res_id, nextdate
With this data, you can then get the average that you want.
I am not clear if the time should be based on EndTime - StartTime (last recorded time at the location minus the first recorded time) or NextStartTime - startTime (first time at next location minus first time at this location).
Also, this returns NULL for the last location for each res_id. You don't say what to do about the last in the sequence.
If you build an index on res_id, loc_date, loc_zone, it might run faster.
If you had Oracle or SQL Server 2012, the right query is:
select lh.*,
lead(loc_date) over (partition by res_id order by loc_date) as nextdate
from (select lh.*,
lag(loc_zone) over (partition by res_id order by loc_date) as prevzone
from lochist lh
) lh
where prevzone is null or prevzone <> loc_zone
Now you have one row per stay and nextdate is the date at the next zone.
This should get you each zone ordered by the average number of minutes spent in it. The CROSS APPLY returns the next ping in a different zone.
SELECT
loc.LOC_ZONE
,AVG(DATEDIFF(mi,loc.LOC_DATE,nextPing.LOC_DATE)) AS avgMinutes
FROM LOCHIST loc
CROSS APPLY(
SELECT TOP 1 loc2.LOC_DATE
FROM LOCHIST loc2
WHERE loc2.RES_ID = loc.RES_ID
AND loc2.LOC_DATE > loc.LOC_DATE
AND loc2.LOC_ZONE <> loc.LOC_ZONE
ORDER BY loc2.LOC_DATE ASC
) AS nextPing
GROUP BY loc.LOC_ZONE
ORDER BY avgMinutes DESC
My variation of the solution:
select LOC_ZONE, avg(TOTAL_TIME) AVG_TIME from (
select RES_ID, LOC_ZONE, sum(TIME_SPENT) TOTAL_TIME
from (
select RES_ID, LOC_ZONE, datediff(mi, lag(LOC_DATE, 1) over (
partition by RES_ID order by LOC_DATE), LOC_DATE) TIME_SPENT
from LOCHIST
) t
where TIME_SPENT is not null
group by RES_ID, LOC_ZONE) f
group by LOC_ZONE
This accounts for multiple stays at the same location. The choice between lag or lead depends if a stay should start or end with the ping (ie, if one trailer sends a ping from A and then x hours later from B, does that count for A or B).
To do this without using either a cursor or a correlated subquery, try:
with rl as
(select l.*, rank() over (partition by res_id order by loc_date) rn
from lochist l),
fdr as
(select rc.*, coalesce(rn.loc_date, getdate()) next_date
from rl rc
left join rl rn on rc.res_id = rn.res_id and rc.rn + 1 = rn.rn)
select loc_zone, avg(datediff(second, loc_date, next_date))/3600 avg_time
from fdr
group by loc_zone
SQLFiddle here.
(Because of the way that SQLServer calculates time differences, it's probably better to calculate the average time in seconds and then divide by 60*60. With the exception of the getdate() and datediff clauses - which can be replaced by sysdate and next_date - loc_date - this should work in both SQLServer 2005 onwards and Oracle 10g onwards.)