create dynamic records from time stamps - sql

I have the following table:
Id Date Time Location leadHourDiff
3 2017-01-01 2017-01-01 13:00:00.000 Boston 2
15 2017-01-01 2017-01-01 13:00:00.000 Philly 1
16 2017-01-01 2017-01-01 15:00:00.000 Philly 1
and i would like dynamically create the hour records between Time and (Time + leadHourDiff)
so the end result would be:
Date Time Location
2017-01-01 2017-01-01 13:00:00.000 Boston --main record
2017-01-01 2017-01-01 14:00:00.000 Boston --new record
2017-01-01 2017-01-01 15:00:00.000 Boston --new record
2017-01-01 2017-01-01 13:00:00.000 Philly --main record
2017-01-01 2017-01-01 14:00:00.000 Philly --new record
2017-01-01 2017-01-01 15:00:00.000 Philly --main record
2017-01-01 2017-01-01 16:00:00.000 Philly --new record

One option is to use a numbers table (This can be generated with a recursive cte) and join the leadHourDiff column on to that.
with numbers(num) as (select 0
union all
select num+1 from numbers where num < 100 --change this as needed
)
select t.*,dateadd(hour,n.num,t.datetime_col) as new_datetime
from tbl t
join numbers n on t.leadHourDiff >= n.num

A simple way is to use a recursive CTE:
with cte as (
select id, date, time, Location, leadHourDiff
from t
union all
select id, date, dateadd(hour, 1, time), location, leadHourDiff - 1
from cte
where leadHourDiff >= 0
)
select date, time, Location
from cte
order by location, date, time;

Here's how I ended up doing this. Also, forgot to mention that I only wanted the missing time values. That was an tpyo on my part. Here's the whole solution
CREATE TABLE #Orders(
Id int IDENTITY(1,1)
,[Time] datetime
,[Location] varchar(20)
,OrderAmt int
)
INSERT INTO #Orders
SELECT '2017-01-01 11:00:00', 'Boston', 23 UNION ALL
SELECT '2017-01-01 12:00:00', 'Boston', 31 UNION ALL
SELECT '2017-01-01 13:00:00', 'Boston', 45 UNION ALL
SELECT '2017-01-01 16:00:00', 'Boston', 45 UNION ALL ---15
SELECT '2017-01-01 17:00:00', 'Boston', 67 UNION ALL
SELECT '2017-01-01 18:00:00', 'Boston', 89 UNION ALL
SELECT '2017-01-01 19:00:00', 'Boston', 90 UNION ALL
SELECT '2017-01-01 20:00:00', 'Boston', 123 UNION ALL
SELECT '2017-01-01 21:00:00', 'Boston', 145 UNION ALL
SELECT '2017-01-01 22:00:00', 'Boston', 156 UNION ALL
SELECT '2017-01-01 23:00:00', 'Boston', 145 UNION ALL
SELECT '2017-01-02 00:00:00', 'Boston', 167 UNION ALL
SELECT '2017-01-01 11:00:00', 'Philly', 23 UNION ALL
SELECT '2017-01-01 12:00:00', 'Philly', 31 UNION ALL
SELECT '2017-01-01 13:00:00', 'Philly', 45 UNION ALL
SELECT '2017-01-01 15:00:00', 'Philly', 45 UNION ALL
SELECT '2017-01-01 17:00:00', 'Philly', 67 UNION ALL
SELECT '2017-01-01 18:00:00', 'Philly', 89 UNION ALL
SELECT '2017-01-01 19:00:00', 'Philly', 90 UNION ALL
SELECT '2017-01-01 20:00:00', 'Philly', 123 UNION ALL
SELECT '2017-01-01 21:00:00', 'Philly', 145 UNION ALL
SELECT '2017-01-01 22:00:00', 'Philly', 156 UNION ALL
SELECT '2017-01-01 23:00:00', 'Philly', 145 UNION ALL
SELECT '2017-01-02 00:00:00', 'Philly', 167
;WITH HourDiff AS (
SELECT *
FROM
(
SELECT
Id
,CAST([Time] AS date) AS [Date]
,[Time]
,[Location]
,COALESCE(lead(DATEPART(HOUR, [Time])) OVER(PARTITION BY [Location], CAST([Time] AS date) ORDER BY [Time] ASC ) - DATEPART(HOUR, [Time]),1)-1 AS leadHourDiff
FROM #Orders
) t1
WHERE t1.leadHourDiff <> 0
)
, CTE AS (
SELECT
Location
,DATEADD(HOUR, leadHourDiff, [Time]) AS missingTime
FROM HourDiff
UNION ALL
SELECT
Location
,DATEADD(HOUR, leadHourDiff - 1, [Time]) AS missingTime
FROM HourDiff
WHERE Time < DATEADD(HOUR, leadHourDiff - 1, [Time])
)
SELECT
Location
,CAST(missingTime AS time) AS missingTime
FROM CTE
ORDER BY Location, missingTime
DROP TABLE #Orders
Final result:
Location missingTime
Boston 14:00:00.000
Boston 15:00:00.000
Philly 14:00:00.000
Philly 16:00:00.000
UPDATE:
here's an update..the final CTE was not working properly when i add new data for new york
new data for new york:
SELECT '2017-01-01 11:00:00', 'New York', 23 UNION ALL
SELECT '2017-01-01 20:00:00', 'New York', 31 UNION ALL
new final CTE:
, CTE AS (
SELECT
Location
,DATEADD(HOUR, leadHourDiff, [Time]) AS missingTime
,[Time]
,leadHourDiff
FROM HourDiff
UNION ALL
SELECT
Location
,DATEADD(HOUR, leadHourDiff - 1 , [Time]) AS missingTime
,[Time]
,leadHourDiff - 1
FROM CTE
WHERE leadHourDiff >= 0
AND Time < DATEADD(HOUR, leadHourDiff - 1, [Time])
)
Final result:
Location missingTime
Boston 14:00:00.0000000
Boston 15:00:00.0000000
New York 12:00:00.0000000
New York 13:00:00.0000000
New York 14:00:00.0000000
New York 15:00:00.0000000
New York 16:00:00.0000000
New York 17:00:00.0000000
New York 18:00:00.0000000
New York 19:00:00.0000000
Philly 14:00:00.0000000
Philly 16:00:00.0000000

Related

Oracle Sort of gaps and island query

Instead of writing long sentences and paragraphs let me show the data and what I want to achieve :
create table ssb_price (itm_no varchar2(10), price number, price_code varchar2(10), valid_from_dt date, valid_to_dt date);
insert into ssb_price values ('A001', 83, 'AB', '01-JAN-21', '05-JAN-21');
insert into ssb_price values ('A001', 83, 'AB', '06-JAN-21', '12-JAN-21');
insert into ssb_price values ('A001', 98, 'SPQ', '13-JAN-21', '17-JAN-21');
insert into ssb_price values ('A001', 83, 'AB', '19-JAN-21', '24-JAN-21');
insert into ssb_price values ('A001', 83, 'DE', '25-JAN-21', '30-JAN-21');
insert into ssb_price values ('A001', 83, 'DE', '31-JAN-21', '04-FEB-21');
insert into ssb_price values ('A001', 77, 'XY', '07-FEB-21', '12-FEB-21');
insert into ssb_price values ('A001', 77, 'XY', '15-FEB-21', '20-FEB-21');
insert into ssb_price values ('A001', 62, 'SD', '23-FEB-21', '26-FEB-21');
insert into ssb_price values ('A001', 59, 'SD', '26-FEB-21', '03-MAR-21');
For particular itm_no and price if the from and to dates are continuous then I should get that value. For price 77 there is a gap of 2 days (13th and 14th) between to date and the next from date so its not continuous. Lemme paste what the desired output should look like :(taken the snip from excel)
I have asked this question clubbed with another post. But that post was old and haven't got any feedback so creating this. Please let me know if I should merge this post with the previous one.
This is basically a gaps-and-islands problem. But instead of aggregating to reduce the number of rows, you want to use window functions at the last step.
In your data, the time frames neatly tile. That suggests using lag() and a cumulative sum to define the groups:
select p.*,
min(valid_from_dt) over (partition by itm_no, price, price_code, grp) as new_valid_from_dt,
max(valid_to_dt) over (partition by itm_no, price, price_code, grp) as new_valid_to_dt
from (select p.*,
sum(case when valid_from_dt = prev_valid_to_dt + interval '1' day then 0 else 1 end) over
(partition by itm_no, price, price_code order by valid_from_dt) as grp
from (select p.*,
lag(valid_to_dt) over (partition by itm_no, price, price_code order by valid_from_dt) as prev_valid_to_dt
from ssb_price p
) p
) p
order by itm_no, valid_from_dt;
Here is a db<>fiddle.
From Oracle 12, you can use MATCH_RECOGNIZE:
SELECT itm_no,
price,
price_code,
valid_from_dt,
valid_to_dt,
MIN( valid_from_dt ) OVER ( PARTITION BY itm_no, mnum ) AS new_valid_from_dt,
MAX( valid_to_dt ) OVER ( PARTITION BY itm_no, mnum ) AS new_valid_to_dt
FROM ssb_price
MATCH_RECOGNIZE(
PARTITION BY itm_no
ORDER BY valid_from_dt, valid_to_dt
MEASURES
MATCH_NUMBER() AS mnum
ALL ROWS PER MATCH
PATTERN ( start_range continued_range* )
DEFINE
continued_range AS (
valid_from_dt = PREV( valid_to_dt ) + 1
AND price = PREV( price )
)
)
and, from Oracle 10g, you can use the MODEL clause:
SELECT itm_no,
price,
price_code,
valid_from_dt,
valid_to_dt,
mn,
MIN( valid_from_dt ) OVER ( PARTITION BY itm_no, mn ) AS new_valid_from_dt,
MAX( valid_to_dt ) OVER ( PARTITION BY itm_no, mn ) AS new_valid_to_dt
FROM (
SELECT *
FROM (
SELECT s.*,
ROW_NUMBER() OVER ( PARTITION BY itm_no ORDER BY valid_from_dt ) AS rn
FROM ssb_price s
)
MODEL
PARTITION BY ( itm_no )
DIMENSION BY ( rn )
MEASURES ( price, price_code, valid_from_dt, valid_to_dt, 1 AS mn )
RULES (
mn[rn>1] = mn[cv(rn)-1]
+
CASE
WHEN valid_from_dt[cv(rn)] = valid_to_dt[cv(rn)-1] + 1
AND price[cv(rn)] = price[cv(rn) - 1]
THEN 0
ELSE 1
END
)
)
Which, for the sample data:
create table ssb_price (itm_no, price, price_code, valid_from_dt, valid_to_dt) AS
SELECT 'A001', 83, 'AB', DATE '2021-01-01', DATE '2021-01-05' FROM DUAL UNION ALL
SELECT 'A001', 83, 'AB', DATE '2021-01-06', DATE '2021-01-12' FROM DUAL UNION ALL
SELECT 'A001', 98, 'SPQ', DATE '2021-01-13', DATE '2021-01-17' FROM DUAL UNION ALL
SELECT 'A001', 83, 'AB', DATE '2021-01-19', DATE '2021-01-24' FROM DUAL UNION ALL
SELECT 'A001', 83, 'DE', DATE '2021-01-25', DATE '2021-01-30' FROM DUAL UNION ALL
SELECT 'A001', 83, 'DE', DATE '2021-01-31', DATE '2021-02-04' FROM DUAL UNION ALL
SELECT 'A001', 77, 'XY', DATE '2021-02-07', DATE '2021-02-12' FROM DUAL UNION ALL
SELECT 'A001', 77, 'XY', DATE '2021-02-15', DATE '2021-02-20' FROM DUAL UNION ALL
SELECT 'A001', 62, 'SD', DATE '2021-02-23', DATE '2021-02-26' FROM DUAL UNION ALL
SELECT 'A001', 59, 'SD', DATE '2021-02-26', DATE '2021-03-03' FROM DUAL;
Outputs:
ITM_NO
PRICE
PRICE_CODE
VALID_FROM_DT
VALID_TO_DT
NEW_VALID_FROM_DT
NEW_VALID_TO_DT
A001
83
AB
2021-01-01 00:00:00
2021-01-05 00:00:00
2021-01-01 00:00:00
2021-01-12 00:00:00
A001
83
AB
2021-01-06 00:00:00
2021-01-12 00:00:00
2021-01-01 00:00:00
2021-01-12 00:00:00
A001
98
SPQ
2021-01-13 00:00:00
2021-01-17 00:00:00
2021-01-13 00:00:00
2021-01-17 00:00:00
A001
83
AB
2021-01-19 00:00:00
2021-01-24 00:00:00
2021-01-19 00:00:00
2021-02-04 00:00:00
A001
83
DE
2021-01-25 00:00:00
2021-01-30 00:00:00
2021-01-19 00:00:00
2021-02-04 00:00:00
A001
83
DE
2021-01-31 00:00:00
2021-02-04 00:00:00
2021-01-19 00:00:00
2021-02-04 00:00:00
A001
77
XY
2021-02-07 00:00:00
2021-02-12 00:00:00
2021-02-07 00:00:00
2021-02-12 00:00:00
A001
77
XY
2021-02-15 00:00:00
2021-02-20 00:00:00
2021-02-15 00:00:00
2021-02-20 00:00:00
A001
62
SD
2021-02-23 00:00:00
2021-02-26 00:00:00
2021-02-23 00:00:00
2021-02-26 00:00:00
A001
59
SD
2021-02-26 00:00:00
2021-03-03 00:00:00
2021-02-26 00:00:00
2021-03-03 00:00:00
db<>fiddle here

Dense_rank query in sql(4 different columns) in

I have a table as follows:
Sn no. t_time Value rate
ABC 17-MAY-18 08:00:00 100.00 3
ABC 17-MAY-18 22:00:00 200.00 1
ABC 16-MAY-18 08:00:00 100.00 1
XYZ 14-MAY-18 01:00:00 700.00 1
XYZ 15-MAY-18 10:00:00 500.00 2
XYZ 15-MAY-18 13:00:00 100.00 2
And I want to generate the output as follows:
Sn no. New_value
ABC 150
XYZ 450
It is grouped by the Sn no. The New_value is the latest time of each date value multiplied by rate, and then averaged together.
For example ABC new_value is
Average of:[(100*1) and (200*1)]
Its a large dataset. How do I write a query for the above in the most efficient way. Please help.
You can use analytical function(row_number()) to achieve the result
SQL> WITH cte_table(Snno, t_time, Value, rate) AS (
2 SELECT 'ABC', to_date('2018-05-17 08:00:00', 'YYYY-MM-DD HH24:MI:SS'), 100.00, 3 FROM DUAL UNION ALL
3 SELECT 'ABC', to_date('2018-05-17 22:00:00', 'YYYY-MM-DD HH24:MI:SS'), 200.00, 1 FROM DUAL UNION ALL
4 SELECT 'ABC', to_date('2018-05-16 08:00:00', 'YYYY-MM-DD HH24:MI:SS'), 100.00, 1 FROM DUAL UNION ALL
5 SELECT 'XYZ', to_date('2018-05-14 01:00:00', 'YYYY-MM-DD HH24:MI:SS'), 700.00, 1 FROM DUAL UNION ALL
6 SELECT 'XYZ', to_date('2018-05-15 10:00:00', 'YYYY-MM-DD HH24:MI:SS'), 500.00, 2 FROM DUAL UNION ALL
7 SELECT 'XYZ', to_date('2018-05-15 13:00:00', 'YYYY-MM-DD HH24:MI:SS'), 100.00, 2 FROM DUAL),
8 --------------------------------
9 -- End of data preparation
10 --------------------------------
11 rn_table AS (
12 SELECT t.*, row_number() OVER (PARTITION BY TRUNC(t_time) ORDER BY t_time DESC) AS rn
13 FROM cte_table t)
14 SELECT snno,
15 AVG(VALUE * rate) new_value
16 FROM rn_table
17 WHERE rn = 1
18 GROUP BY snno;
Output:
SNNO NEW_VALUE
---- ----------
ABC 150
XYZ 450
Use the ROW_NUMBER (or RANK/DENSE_RANK if it is more appropriate) analytic function in a sub-query and then aggregate in the outer query:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE table_name ( Snno, t_time, Value, rate ) AS
SELECT 'ABC', TIMESTAMP '2018-05-17 08:00:00', 100.00, 3 FROM DUAL UNION ALL
SELECT 'ABC', TIMESTAMP '2018-05-17 22:00:00', 200.00, 1 FROM DUAL UNION ALL
SELECT 'ABC', TIMESTAMP '2018-05-16 08:00:00', 100.00, 1 FROM DUAL UNION ALL
SELECT 'XYZ', TIMESTAMP '2018-05-14 01:00:00', 700.00, 1 FROM DUAL UNION ALL
SELECT 'XYZ', TIMESTAMP '2018-05-15 10:00:00', 500.00, 2 FROM DUAL UNION ALL
SELECT 'XYZ', TIMESTAMP '2018-05-15 13:00:00', 100.00, 2 FROM DUAL;
Query 1:
SELECT snno,
AVG( value * rate ) As new_value
FROM (
SELECT t.*,
ROW_NUMBER() OVER (
PARTITION BY snno, value
ORDER BY t_time DESC
) AS rn
FROM table_name t
)
WHERE rn = 1
GROUP BY snno
Results:
| SNNO | NEW_VALUE |
|------|-------------------|
| ABC | 250 |
| XYZ | 633.3333333333334 |

group by on date

I have two tables, Sales and Calls as follows:
**Sales**
CUST_ID INT primary key,
CUST_NM Varchar(40),
Sale_date Datetime2,
SALES Money);
CUST_ID CUST_NM Sale_date SALES
1 Dom 2015-01-01 15:00:02.3000000 10.00
2 Brian 2015-01-02 15:00:02.3000000 12.00
3 Stu 2015-01-03 15:00:02.3000000 21.00
4 John 2015-01-04 15:00:02.3000000 41.00
5 Jack 2015-01-05 15:00:02.3000000 51.00
6 Jill 2015-01-05 15:00:02.3000000 61.00
7 Steve 2015-01-04 15:00:02.3000000 16.00
8 Stacey 2015-01-03 15:00:02.3000000 19.00
9 Lacey 2015-01-03 15:00:02.3000000 30.00
Calls
NAME Varchar(40),
CALL_DATE Date,
TOTAL_CALLS INT
NAME CALL_DATE TOTAL_CALLS
Dom 2015-01-01 2
Brian 2015-01-02 4
Stu 2015-01-03 3
John 2015-01-04 5
Jack 2015-01-05 6
Jill 2015-01-05 10
Steve 2015-01-04 8
Stacey 2015-01-03 7
Lacey 2015-01-03 9
I want to write a select statement that brings back the date, gross sales, and the total calls from both Sales and Calls, joined on date.
Here is what I wrote, and I think it should be right, but somehow I am not getting the right output.
select Calls.CALL_DATE, sum(Sales.SALES) as gross_sale, sum(Calls.TOTAL_CALLS) as gross_total_calls
from Sales
join
Calls
on convert (date,sales.Sale_date)=calls.CALL_DATE
group by Calls.CALL_DATE
order by Calls.CALL_DATE
The output I am getting is
CALL_DATE gross_sale gross_total_calls
2015-01-01 10.00 2
2015-01-02 12.00 4
2015-01-03 210.00 57
2015-01-04 114.00 26
2015-01-05 224.00 32
Where am I going wrong??
You are generating a Cartesian product for each day. You need to aggregate before the join. Or, you can do this with a union all and aggregation:
select dte, sum(sales) as sales, sum(calls) as total_calls
from ((select cast(s.sale_date as date) as dte, sales, 0 as calls
from sales s
) union all
(select call_date, 0, total_calls as calls
from calls c
)
) sc
group by dte
order by dte;
The alternative way as suggested by Gordon using table variables to create a runnable test script.
Note the two extra rows of data and the FULL OUTER JOIN which allows all data to be returned.
declare #Sales table (CUST_ID INT primary key, CUST_NM Varchar(40), Sale_date Datetime2,SALES Money);
insert into #Sales (CUST_ID, CUST_NM, Sale_date, SALES)
select 1, 'Dom', '2015-01-01 15:00:02.3000000', 10.00 union
select 2, 'Brian', '2015-01-02 15:00:02.3000000', 12.00 union
select 3, 'Stu', '2015-01-03 15:00:02.3000000', 21.00 union
select 4, 'John', '2015-01-04 15:00:02.3000000', 41.00 union
select 5, 'Jack', '2015-01-05 15:00:02.3000000', 51.00 union
select 6, 'Jill', '2015-01-05 15:00:02.3000000', 61.00 union
select 7, 'Steve', '2015-01-04 15:00:02.3000000', 16.00 union
select 8, 'Stacey', '2015-01-03 15:00:02.3000000', 19.00 union
select 9, 'Lacey', '2015-01-03 15:00:02.3000000', 30.00 union
select 10, 'Tom', '2015-01-07 15:00:02.3000000', 1.00
declare #Calls table (NAME Varchar(40), CALL_DATE Date, TOTAL_CALLS INT)
insert into #Calls (NAME, CALL_DATE, TOTAL_CALLS)
select 'Dom', '2015-01-01', 2 union
select 'Brian', '2015-01-02', 4 union
select 'Stu', '2015-01-03', 3 union
select 'John', '2015-01-04', 5 union
select 'Jack', '2015-01-05', 6 union
select 'Jill', '2015-01-05', 10 union
select 'Steve', '2015-01-04', 8 union
select 'Stacey', '2015-01-03', 7 union
select 'Lacey', '2015-01-03', 9 union
select 'Tom', '2015-01-06', 1
select * from #Sales
select * from #Calls
select ISNULL (a.CALL_DATE, b.CALL_DATE) as CALL_DATE, gross_sale, TOTAL_CALLS
from
( select convert(date, Sale_date) as CALL_DATE, sum(SALES) as gross_sale
from #Sales
group by convert(date, Sale_date)
) a
full outer join
( select CALL_DATE, SUM(TOTAL_CALLS) as TOTAL_CALLS
from #Calls
group by CALL_DATE
) b on a.CALL_DATE = b.CALL_DATE
order by a.CALL_DATE

Return records with counts for the last 24 hours

I am trying to get records from the last 24 hours, grouped by hour with counts in SQL Server?
I have sample data like:
ID Dat
1 2015-01-19 10:29:00.000
2 2015-01-19 11:29:00.000
3 2015-01-19 11:29:00.000
4 2015-01-19 11:29:00.000
5 2015-01-19 12:29:00.000
6 2015-01-19 12:29:00.000
7 2015-01-19 12:29:00.000
8 2015-01-19 12:29:00.000
9 2015-01-17 13:29:00.000
10 2015-01-17 13:29:00.000
11 2015-01-17 13:29:00.000
12 2015-01-17 13:29:00.000
13 2015-01-17 13:29:00.000
14 2015-01-17 13:29:00.000
15 2015-01-17 14:29:00.000
17 2015-01-17 15:29:00.000
18 2015-01-17 15:29:00.000
19 2015-01-17 16:29:00.000
20 2015-01-17 16:29:00.000
21 2015-01-15 16:29:00.000
22 2015-01-15 17:29:00.000
23 2015-01-15 18:29:00.000
24 2015-01-15 18:29:00.000
25 2015-01-15 18:29:00.000
26 2015-01-15 18:29:00.000
27 2015-01-15 18:29:00.000
28 2015-01-15 18:29:00.000
29 2015-01-15 19:29:00.000
30 2015-01-10 20:29:00.000
Now suppose current date time is 2015-01-19 12:30:00.000, my desired output would be:
Date Count
2015-01-19 12:00:00.000 4
2015-01-19 11:00:00.000 3
2015-01-19 10:00:00.000 1
2015-01-19 09:00:00.000 0
2015-01-19 08:00:00.000 0
2015-01-19 07:00:00.000 0
2015-01-19 06:00:00.000 0
2015-01-19 05:00:00.000 4
and so on...
So the count is based on number of records that fall in to each hour.
You can round your values to the nearest hour and then simply GROUP and COUNT:
SQL Fiddle Demo
MS SQL Server Schema Setup:
CREATE TABLE DateTable
([ID] int, [Date] datetime)
;
INSERT INTO DateTable
([ID], [Date])
VALUES
(1, '2015-01-19 10:29:00'),
(2, '2015-01-19 11:29:00'),
(3, '2015-01-19 11:29:00'),
(4, '2015-01-19 11:29:00'),
(5, '2015-01-19 12:29:00'),
(6, '2015-01-19 12:29:00'),
(7, '2015-01-19 12:29:00'),
(8, '2015-01-19 12:29:00'),
(9, '2015-01-17 13:29:00'),
(10, '2015-01-17 13:29:00'),
(11, '2015-01-17 13:29:00'),
(12, '2015-01-17 13:29:00'),
(13, '2015-01-17 13:29:00'),
(14, '2015-01-17 13:29:00'),
(15, '2015-01-17 14:29:00'),
(17, '2015-01-17 15:29:00'),
(18, '2015-01-17 15:29:00'),
(19, '2015-01-17 16:29:00'),
(20, '2015-01-17 16:29:00'),
(21, '2015-01-15 16:29:00'),
(22, '2015-01-15 17:29:00'),
(23, '2015-01-15 18:29:00'),
(24, '2015-01-15 18:29:00'),
(25, '2015-01-15 18:29:00'),
(26, '2015-01-15 18:29:00'),
(27, '2015-01-15 18:29:00'),
(28, '2015-01-15 18:29:00'),
(29, '2015-01-15 19:29:00'),
(30, '2015-01-10 20:29:00')
;
Query to return aggregated data:
SELECT DATEADD(HOUR, DATEDIFF(HOUR, 0, [DATE]), 0) As [DateValue],
COUNT(1) AS [COUNT]
FROM DateTable
WHERE [DATE] >= DATEADD(day, -1, GETDATE())
GROUP BY DATEADD(HOUR, DATEDIFF(HOUR, 0, [DATE]), 0)
ORDER BY 1
Results:
| DATEVALUE | COUNT |
|--------------------------------|-------|
| January, 19 2015 10:00:00+0000 | 1 |
| January, 19 2015 11:00:00+0000 | 3 |
| January, 19 2015 12:00:00+0000 | 4 |
This is using GETDATE() to return the current date time value and taking the last 24 hours from the point. The query above uses the value returned from the below for the WHERE clause:
SELECT DATEADD(day, -1, GETDATE())
You can replace the filter value in the WHERE clause with a variable if required.
Try this, it will also count the hours without data:
DECLARE #t table(ID int, Date datetime)
INSERT #t values
(1,'2015-01-19 10:29:00.000'), (2,'2015-01-19 11:29:00.000'),
(3,'2015-01-19 11:29:00.000'), (4,'2015-01-19 11:29:00.000'),
(5,'2015-01-19 12:29:00.000'), (6,'2015-01-19 12:29:00.000'),
(7,'2015-01-19 12:29:00.000'), (8,'2015-01-19 12:29:00.000'),
(9,'2015-01-17 13:29:00.000'), (10,'2015-01-17 13:29:00.000'),
(11,'2015-01-17 13:29:00.000'),(12,'2015-01-17 13:29:00.000'),
(13,'2015-01-17 13:29:00.000'),(14,'2015-01-17 13:29:00.000'),
(15,'2015-01-17 14:29:00.000'),(17,'2015-01-17 15:29:00.000'),
(18,'2015-01-17 15:29:00.000'),(19,'2015-01-17 16:29:00.000'),
(20,'2015-01-17 16:29:00.000'),(21,'2015-01-15 16:29:00.000'),
(22,'2015-01-15 17:29:00.000'),(23,'2015-01-15 18:29:00.000'),
(24,'2015-01-15 18:29:00.000'),(25,'2015-01-15 18:29:00.000'),
(26,'2015-01-15 18:29:00.000'),(27,'2015-01-15 18:29:00.000'),
(28,'2015-01-15 18:29:00.000'),(29,'2015-01-15 19:29:00.000'),
(30,'2015-01-10 20:29:00.000')
DECLARE #yourdate datetime = '2015-01-19T12:30:00.000'
;WITH CTE AS
(
SELECT dateadd(hh, datediff(hh, 0, #yourdate), 0) Date
UNION ALL
SELECT dateadd(hh, -1, Date)
FROM CTE
WHERE Date + 1 > #yourdate
)
SELECT CTE.Date, count(t.id) count
FROM CTE
LEFT JOIN #t t
ON CTE.Date <= t.Date
and dateadd(hh, 1, CTE.Date) > t.Date
GROUP BY CTE.Date
ORDER BY CTE.Date DESC
Result:
Date Count
2015-01-19 12:00:00.000 4
2015-01-19 11:00:00.000 3
2015-01-19 10:00:00.000 1
2015-01-19 09:00:00.000 0
2015-01-19 08:00:00.000 0
.....
create table #temptable
(
ID int ,
Date datetime
)
go
insert into #temptable (id, Date)
select 1 , '2014-01-19 10:29:00.000' union all
select 2 , '2014-01-19 11:29:00.000' union all
select 3 , '2014-01-19 11:29:00.000' union all
select 4 , '2014-01-19 11:29:00.000' union all
select 5 , '2014-01-19 09:29:00.000' union all
select 6 , '2014-01-19 08:29:00.000' union all
select 7 , '2014-01-19 03:29:00.000'
SELECT CAST(Date as date) AS ForDate,
DATEPART(hour,Date) AS OnHour,
COUNT(*) AS Totals
FROM #temptable
GROUP BY CAST(Date as date),
DATEPART(hour,Date)
SQL Server Group by Count of DateTime Per Hour?
It may helps you
declare #t table(t datetime)
insert into #t values(getdate()),(getdate())
SELECT cast(cast(getdate() as date) as datetime)+cast(datepart(hour,getdate()) as float)/24, count(*)
from #t
group by cast(cast(getdate() as date) as datetime)+cast(datepart(hour,getdate()) as float)/24
try this
For SQL SERVER
select CAST( MyDateColumn as DATE) ,DATEPART(HOUR, MyDateColumn),COUNT(*)
from MyTable
GROUP BY CAST( MyDateColumn as DATE) ,DATEPART(HOUR, MyDateColumn)

splitting overlapping dates in SQL

I'm on SQLServer 2008 R2
I'm trying to create a report and chart for a a manufacturing resource's activity for a give period (typically 30-90 days)
Jobs are created for the length of the run (e.g. 4 days). If the weekend is not worked and the above jobs starts on a Friday, the resource's activity needs to show 1 day running, 2 days down, 3 days running without the production scheduler having to make it two jobs. I have the jobs' schedules in one table and the downtimes in another (so think of DT like some sort of calendar table). Unusually, the end time is supplied with the downtime factored in.
So I need the query to create 3 datetime ranges for this job: Fri running, Sat,Sun down, Mon,Tues,Wed Running. Note: a single job can have multiple downtime events.
Been going round in circles on this for a while. i'm sure there's an elegant way to do it: I just can't find it. I've found several similar post, but can't apply any to my case (or at least can;t get them to work)
Below is some sample date and expected results. I hope the explanation and example data is clear.
-- Create tables to work with / Source and Destination
CREATE TABLE #Jobs
(
ResourceID int
,JobNo VARCHAR(10)
,startdate SMALLDATETIME
,enddate SMALLDATETIME
)
CREATE TABLE #Downtime
(
ResourceID INT
,Reason VARCHAR(10)
,startdate SMALLDATETIME
,enddate SMALLDATETIME
)
CREATE TABLE #Results
(
ResourceID INT
,Activity VARCHAR(10)
,startdate SMALLDATETIME
,enddate SMALLDATETIME
,ActivityType varchar(1)
)
-- Job Schedule
INSERT INTO [#Jobs]
(
[ResourceID],
[JobNo],
startdate
,enddate
)
SELECT 1, 'J1', '2014-04-01 08:00' ,'2014-04-01 17:00'
UNION ALL
SELECT 1, 'J2', '2014-04-01 17:00' , '2014-04-01 23:00'
UNION ALL
SELECT 2, 'J3', '2014-04-01 08:00' ,'2014-04-01 23:00'
UNION ALL
SELECT 3, 'J4', '2014-04-01 08:00' ,'2014-04-01 09:00'
SELECT * FROM #jobs
-- Downtime Scehdule
INSERT INTO [#Downtime]
(
[ResourceID],
Reason,
startdate
,enddate
)
SELECT 1, 'DOWN', '2014-04-01 10:00' ,'2014-04-01 11:00'
UNION ALL
SELECT 1, 'DOWN', '2014-04-01 21:00' , '2014-04-01 22:00'
UNION ALL
SELECT 2, 'DOWN', '2014-04-01 10:00' ,'2014-04-01 11:00'
UNION ALL
SELECT 2, 'DOWN', '2014-04-01 21:00' , '2014-04-01 22:00'
UNION ALL
SELECT 3, 'DOWN', '2014-04-01 10:00' ,'2014-04-01 11:00'
UNION ALL
SELECT 3, 'DOWN', '2014-04-01 21:00' , '2014-04-01 22:00'
SELECT * FROM #Downtime
-- Expected Results
INSERT INTO [#Results]
(
Activity,
[ResourceID],
startdate
,enddate
,[ActivityType]
)
SELECT 'J1', 1, '2014-04-01 08:00' ,'2014-04-01 10:00', 'P'
UNION ALL
SELECT 'DOWN', 1, '2014-04-01 10:00' , '2014-04-01 11:00', 'D'
UNION ALL
SELECT 'J1', 1, '2014-04-01 11:00' ,'2014-04-01 17:00', 'P'
UNION ALL
SELECT 'J2', 1, '2014-04-01 17:00' , '2014-04-01 21:00', 'P'
UNION ALL
SELECT 'DOWN', 1, '2014-04-01 21:00' , '2014-04-01 22:00', 'D'
UNION ALL
SELECT 'J2', 1, '2014-04-01 22:00' ,'2014-04-01 23:00', 'P'
UNION ALL
SELECT 'J3', 2, '2014-04-01 08:00' ,'2014-04-01 10:00', 'P'
UNION ALL
SELECT 'DOWN', 2, '2014-04-01 10:00' , '2014-04-01 11:00', 'D'
UNION ALL
SELECT 'J3', 2, '2014-04-01 11:00' ,'2014-04-01 21:00', 'P'
UNION ALL
SELECT 'DOWN', 2, '2014-04-01 21:00' , '2014-04-01 22:00', 'D'
UNION ALL
SELECT 'J3', 2, '2014-04-01 22:00' ,'2014-04-01 23:00', 'P'
UNION ALL
SELECT 'J4', 3, '2014-04-01 08:00' ,'2014-04-01 09:00', 'P'
UNION ALL
SELECT 'DOWN', 3, '2014-04-01 10:00' , '2014-04-01 11:00', 'D'
UNION ALL
SELECT 'DOWN', 3, '2014-04-01 21:00' , '2014-04-01 22:00', 'D'
SELECT * FROM #Results
ORDER BY [ResourceID], Startdate
DELETE FROM #Results
|--------------------------J1------------------------------------| running
|----D1-----| |-------D2-------| down
|--J1--|----D1-----|-------J1------|-------D2-------|-----J1-----| result
|-----------------------------J1-----------| running
|----D1-------| down
|-----------------J1-----------------------| |----D1-------| result
Can someone point me in the right direction?
This is the closest I've got. Works great when there is an overlap, but fails on J4 where job ends before downtime
WITH cte
AS ( SELECT
ROW_NUMBER() OVER ( ORDER BY ResourceID, dt ) AS Rno
,x.ResourceID
,x.Activity
,Dt
,xdt.ActivityType
FROM
(
SELECT
ResourceID
,JobNo AS Activity
,startdate
,enddate
,'P' AS ActivityType
FROM #Jobs
UNION ALL
SELECT
ResourceID
,Reason AS Activity
,startdate
,enddate
,'D' AS ActivityType
FROM #Downtime
) AS x
CROSS APPLY
(
VALUES ( x.startdate, x.ActivityType),
( x.enddate, x.ActivityType) ) AS xdt
( Dt, ActivityType )
)
SELECT
x.ResourceID
,CASE WHEN x.Activity > x1.Activity THEN x.Activity
ELSE x1.Activity
END AS Activity
,x.dt AS StartDate
,x1.Dt AS EndDate
,CASE WHEN x.ActivityType > x1.ActivityType THEN x.ActivityType
ELSE x1.ActivityType
END AS activitytype
FROM
cte AS x
LEFT OUTER JOIN cte AS x1 ON x.ResourceID = x1.ResourceID
AND x.Rno = x1.Rno - 1
WHERE
x1.Dt IS NOT NULL
AND x1.Dt <> x.Dt;
Thanks
Mark
You were actually pretty close - rather than doing everything in the initial CTE, you actually want to join back to the original data later. Essentially, you're performing a variant on the answer supplied here.
The following query should get you what you need:
WITH AllDates AS (SELECT a.*, ROW_NUMBER() OVER(PARTITION BY resourceId ORDER BY rangeDate) AS rn
FROM (SELECT resourceId, startDate
FROM Jobs
UNION ALL
SELECT resourceId, endDate
FROM Jobs
UNION ALL
SELECT resourceId, startDate
FROM Downtime
UNION ALL
SELECT resourceId, endDate
FROM DownTime) a(resourceId, rangeDate)),
Range AS (SELECT startRange.resourceId,
startRange.rangeDate AS startDate, endRange.rangeDate AS endDate
FROM AllDates startRange
JOIN AllDates endRange
ON endRange.resourceId = startRange.resourceId
AND endRange.rn = startRange.rn + 1
AND endRange.rangeDate <> startRange.rangeDate)
SELECT Range.resourceId, Range.startDate, Range.endDate,
COALESCE(Downtime.reason, Jobs.jobNo) as activity
FROM Range
LEFT JOIN Jobs
ON Jobs.resourceId = Range.resourceId
AND Jobs.startDate <= Range.startDate
AND Jobs.endDate >= Range.endDate
LEFT JOIN Downtime
ON Downtime.resourceId = Range.resourceId
AND Downtime.startDate <= Range.startDate
AND Downtime.endDate >= Range.endDate
WHERE Jobs.jobNo IS NOT NULL
OR Downtime.reason IS NOT NULL
(And working fiddle. This should actually be ANSI-standard SQL)
...which yields the expected:
RESOURCEID STARTDATE ENDDATE ACTIVITY
----------------------------------------------------------------------------
1 2014-04-01 08:00:00 2014-04-01 10:00:00 J1
1 2014-04-01 10:00:00 2014-04-01 11:00:00 DOWN
1 2014-04-01 11:00:00 2014-04-01 17:00:00 J1
1 2014-04-01 17:00:00 2014-04-01 21:00:00 J2
1 2014-04-01 21:00:00 2014-04-01 22:00:00 DOWN
1 2014-04-01 22:00:00 2014-04-01 23:00:00 J2
2 2014-04-01 08:00:00 2014-04-01 10:00:00 J3
2 2014-04-01 10:00:00 2014-04-01 11:00:00 DOWN
2 2014-04-01 11:00:00 2014-04-01 21:00:00 J3
2 2014-04-01 21:00:00 2014-04-01 22:00:00 DOWN
2 2014-04-01 22:00:00 2014-04-01 23:00:00 J3
3 2014-04-01 08:00:00 2014-04-01 09:00:00 J4
3 2014-04-01 10:00:00 2014-04-01 11:00:00 DOWN
3 2014-04-01 21:00:00 2014-04-01 22:00:00 DOWN