combing two rows of data in Sql - sql

So my data looks like this:
+-----------+---------+-------------+-------+-------------+--+
| time | Outlets | Meal_Period | cover | day_of_week | |
+-----------+---------+-------------+-------+-------------+--+
| 10/1/2013 | 72 | 1 | 0 | Tuesday | |
| 10/1/2013 | 72 | 2 | 31 | Tuesday | |
| 10/1/2013 | 72 | 3 | 116 | Tuesday | |
| 10/1/2013 | 72 | 6 | 32 | Tuesday | |
| 10/1/2013 | 187 | 17 | 121 | Tuesday | |
| 10/1/2013 | 187 | 18 | 214 | Tuesday | |
| 10/1/2013 | 187 | 19 | 204 | Tuesday | |
| 10/1/2013 | 101 | 2 | 0 | Tuesday | |
| 10/1/2013 | 101 | 3 | 0 | Tuesday | |
| 10/1/2013 | 101 | 4 | 0 | Tuesday | |
| 10/1/2013 | 101 | 6 | 0 | Tuesday | |
| 10/1/2013 | 282 | 1 | 17 | Tuesday | |
| 10/1/2013 | 282 | 2 | 207 | Tuesday | |
| 10/1/2013 | 282 | 3 | 340 | Tuesday | |
| 10/1/2013 | 282 | 6 | 4 | Tuesday | |
| 10/1/2013 | 103 | 1 | 0 | Tuesday | |
+-----------+---------+-------------+-------+-------------+--+
The code is:
IF OBJECT_ID('tempdb.dbo.#time') IS NOT NULL
DROP TABLE #time
SELECT DATEADD(dd, 0, DATEDIFF(DD, 0, open_dttime)) AS 'time'
,profit_center_id AS 'Outlets'
,meal_period_id AS 'Meal_Period'
,sum(num_covers) AS 'Number_Covers'
INTO #time
FROM [STOF_Infogen].[dbo].[Order_Header]
WHERE CasinoID = 'csg'
AND profit_center_id IN (
'102'
,'100'
,'283'
,'101'
,'282'
,'187'
,'280'
,'103'
,'281'
,'72'
,'183'
)
AND (
open_dttime BETWEEN '2014-02-01 06:30'
AND '2014-03-01 06:30'
)
GROUP BY profit_center_id
,open_dttime
,meal_period_id
ORDER BY profit_center_id
,meal_period_id
IF OBJECT_ID('tempdb.dbo.#time2') IS NOT NULL
DROP TABLE #time2
SELECT [TIME]
,Outlets AS 'Outlets'
,meal_period AS 'Meal_Period'
,SUM(number_covers) AS 'cover'
,DATENAME(DW, [time]) AS 'day_of_week'
INTO #time2
FROM #time
GROUP BY [TIME]
,Outlets
,Meal_Period
ORDER BY [TIME] ASC
,Outlets
,Meal_Period
SELECT *
FROM #time2
I created temporary drop tables for my date but I'm having two issues;
I will like to group where the profit centres are 187 and 282 while still keeping the other rows.
for some reason I can't tweek the date stamp because it excludes the last day of the month.
As always any help is appreciated.

Making some test data:
DECLARE #MealInfo TABLE
(
MealTime DATETIME,
Outlets VARCHAR(10),
Meal_Period int,
Cover INT
)
INSERT INTO #MealInfo
VALUES
('10/1/2013', '72', 1, 0),
('10/1/2013', '72', 2, 31),
('10/1/2013', '72', 3, 116),
('10/1/2013', '72', 6, 32),
('10/1/2013', '187', 17, 121),
('10/1/2013', '187', 18, 214),
('10/1/2013', '187', 19, 204),
('10/1/2013', '101', 2, 0),
('10/1/2013', '101', 3, 0),
('10/1/2013', '101', 4, 0),
('10/1/2013', '101', 6, 0),
('10/1/2013', '282', 1, 17),
('10/1/2013', '282', 2, 207),
('10/1/2013', '282', 3, 340),
('10/1/2013', '282', 6, 4),
('10/1/2013', '103', 1, 0);
Because you want to group 187 and 282 together, I use a case statement to lump them into one outlet and then we can group on the outlets to break out the sums:
SELECT
m.MealTime,
m.Outlets,
m.Meal_Period,
SUM(m.Cover) AS Number_Covers
FROM
(
SELECT mi.MealTime,
(CASE WHEN mi.Outlets IN ('187', '282') THEN '187+282' ELSE mi.Outlets END) Outlets,
mi.Meal_Period,
mi.Cover
FROM #MealInfo mi
) m
GROUP BY m.MealTime, m.Outlets, m.Meal_Period
Here is the output:
MealTime Outlets Meal_Period Number_Covers
2013-10-01 00:00:00.000 101 2 0
2013-10-01 00:00:00.000 101 3 0
2013-10-01 00:00:00.000 101 4 0
2013-10-01 00:00:00.000 101 6 0
2013-10-01 00:00:00.000 103 1 0
2013-10-01 00:00:00.000 187+282 1 17
2013-10-01 00:00:00.000 187+282 2 207
2013-10-01 00:00:00.000 187+282 3 340
2013-10-01 00:00:00.000 187+282 6 4
2013-10-01 00:00:00.000 187+282 17 121
2013-10-01 00:00:00.000 187+282 18 214
2013-10-01 00:00:00.000 187+282 19 204
2013-10-01 00:00:00.000 72 1 0
2013-10-01 00:00:00.000 72 2 31
2013-10-01 00:00:00.000 72 3 116
2013-10-01 00:00:00.000 72 6 32
If your data had overlapping periods for 187 and 282, the sum total would contain both parts into 1 column.

Related

How do I edit the code that calculates the value for the four weeks of the month for all months with pl/sql?

I divided the month into four weeks and printed the amount for each week. How do I set this up with a loop for 12 months?
declare
cursor c is
select varis_tar, tutar
from muhasebe.doviz_takip
where trunc(varis_tar) BETWEEN TO_DATE('01/10/2021', 'DD/MM/YYYY') AND
TO_DATE('31/10/2021', 'DD/MM/YYYY')
group by varis_tar,tutar;
tutar1 number(13,2):=0;
tutar2 number(13,2):=0;
tutar3 number(13,2):=0;
tutar4 number(13,2):=0;
begin
for r in c loop
if r.varis_tar between TO_DATE('01/10/2021', 'DD/MM/YYYY') AND
TO_DATE('07/10/2021', 'DD/MM/YYYY') then
tutar1:=(r.tutar)+tutar1;
--message(r.tutar);
elsif r.varis_tar between TO_DATE('07/10/2021', 'DD/MM/YYYY') AND
TO_DATE('14/10/2021', 'DD/MM/YYYY') then
tutar2:=(r.tutar)+tutar2;
--message(r.tutar);
elsif r.varis_tar between TO_DATE('14/10/2021', 'DD/MM/YYYY') AND
TO_DATE('21/10/2021', 'DD/MM/YYYY') then
tutar3:=(r.tutar)+tutar3;
--message(r.tutar);
elsif r.varis_tar between TO_DATE('21/10/2021', 'DD/MM/YYYY') AND
TO_DATE('31/10/2021', 'DD/MM/YYYY') then
tutar4:=(r.tutar)+tutar4;
--message(r.tutar);
end if;
end loop;
I tried to get the dates the same way for all the months. I tried that, but it worked wrong.
where trunc(varis_tar) BETWEEN TO_DATE('1', 'DD') AND
TO_DATE('31', 'DD')
if r.varis_tar between TO_DATE('1', 'DD') AND
TO_DATE('07', 'DD') then
elsif r.varis_tar between TO_DATE('7', 'DD') AND
TO_DATE('14', 'DD') then
elsif r.varis_tar between TO_DATE('14', 'DD') AND
TO_DATE('21', 'DD') then
elsif r.varis_tar between TO_DATE('21', 'DD') AND
TO_DATE('31', 'DD') then
I don't know if I'am understanding it correctly but:
try if extract(day from varis_tar) between 1 and 7
or more complex
l_week := to_char(varis_tar,'W'); --week number
if l_week = 1 then --first week
elsif l_week = 2 etc...
Your code has several issues:
date in Oracle is actually a datetime, so between will not count any time after the midnight of the upper boundary.
you count the midnight of the week's end twice: in current week and in the next week (between includes both boundaries).
you do not need any PL/SQL and especially a cursor loop, because it occupy resources during calculation outside of SQL context.
Use datetime format to calculate weeks, because it is easy to read and understand. Then group by corresponding components.
with a as (
select
date '2021-01-01' - 1 + level as dt
, level as val
from dual
connect by level < 400
)
, b as (
select
dt
, val
/*Map 29, 30 and 31 to 28*/
, to_char(
least(dt, trunc(dt, 'mm') + 27)
, 'yyyymmw'
) as w
from a
)
select
substr(w, 1, 4) as y
, substr(w, 5, 2) as m
, substr(w, -1) as w
, sum(val) as val
, min(dt) as dt_from
, max(dt) as dt_to
from b
group by
w
Y | M | W | VAL | DT_FROM | DT_TO
:--- | :- | :- | ---: | :--------- | :---------
2021 | 01 | 1 | 28 | 2021-01-01 | 2021-01-07
2021 | 01 | 2 | 77 | 2021-01-08 | 2021-01-14
2021 | 01 | 3 | 126 | 2021-01-15 | 2021-01-21
2021 | 01 | 4 | 265 | 2021-01-22 | 2021-01-31
2021 | 02 | 1 | 245 | 2021-02-01 | 2021-02-07
2021 | 02 | 2 | 294 | 2021-02-08 | 2021-02-14
2021 | 02 | 3 | 343 | 2021-02-15 | 2021-02-21
2021 | 02 | 4 | 392 | 2021-02-22 | 2021-02-28
2021 | 03 | 1 | 441 | 2021-03-01 | 2021-03-07
2021 | 03 | 2 | 490 | 2021-03-08 | 2021-03-14
2021 | 03 | 3 | 539 | 2021-03-15 | 2021-03-21
2021 | 03 | 4 | 855 | 2021-03-22 | 2021-03-31
2021 | 04 | 1 | 658 | 2021-04-01 | 2021-04-07
2021 | 04 | 2 | 707 | 2021-04-08 | 2021-04-14
2021 | 04 | 3 | 756 | 2021-04-15 | 2021-04-21
2021 | 04 | 4 | 1044 | 2021-04-22 | 2021-04-30
2021 | 05 | 1 | 868 | 2021-05-01 | 2021-05-07
2021 | 05 | 2 | 917 | 2021-05-08 | 2021-05-14
2021 | 05 | 3 | 966 | 2021-05-15 | 2021-05-21
2021 | 05 | 4 | 1465 | 2021-05-22 | 2021-05-31
2021 | 06 | 1 | 1085 | 2021-06-01 | 2021-06-07
2021 | 06 | 2 | 1134 | 2021-06-08 | 2021-06-14
2021 | 06 | 3 | 1183 | 2021-06-15 | 2021-06-21
2021 | 06 | 4 | 1593 | 2021-06-22 | 2021-06-30
2021 | 07 | 1 | 1295 | 2021-07-01 | 2021-07-07
2021 | 07 | 2 | 1344 | 2021-07-08 | 2021-07-14
2021 | 07 | 3 | 1393 | 2021-07-15 | 2021-07-21
2021 | 07 | 4 | 2075 | 2021-07-22 | 2021-07-31
2021 | 08 | 1 | 1512 | 2021-08-01 | 2021-08-07
2021 | 08 | 2 | 1561 | 2021-08-08 | 2021-08-14
2021 | 08 | 3 | 1610 | 2021-08-15 | 2021-08-21
2021 | 08 | 4 | 2385 | 2021-08-22 | 2021-08-31
2021 | 09 | 1 | 1729 | 2021-09-01 | 2021-09-07
2021 | 09 | 2 | 1778 | 2021-09-08 | 2021-09-14
2021 | 09 | 3 | 1827 | 2021-09-15 | 2021-09-21
2021 | 09 | 4 | 2421 | 2021-09-22 | 2021-09-30
2021 | 10 | 1 | 1939 | 2021-10-01 | 2021-10-07
2021 | 10 | 2 | 1988 | 2021-10-08 | 2021-10-14
2021 | 10 | 3 | 2037 | 2021-10-15 | 2021-10-21
2021 | 10 | 4 | 2995 | 2021-10-22 | 2021-10-31
2021 | 11 | 1 | 2156 | 2021-11-01 | 2021-11-07
2021 | 11 | 2 | 2205 | 2021-11-08 | 2021-11-14
2021 | 11 | 3 | 2254 | 2021-11-15 | 2021-11-21
2021 | 11 | 4 | 2970 | 2021-11-22 | 2021-11-30
2021 | 12 | 1 | 2366 | 2021-12-01 | 2021-12-07
2021 | 12 | 2 | 2415 | 2021-12-08 | 2021-12-14
2021 | 12 | 3 | 2464 | 2021-12-15 | 2021-12-21
2021 | 12 | 4 | 3605 | 2021-12-22 | 2021-12-31
2022 | 01 | 1 | 2583 | 2022-01-01 | 2022-01-07
2022 | 01 | 2 | 2632 | 2022-01-08 | 2022-01-14
2022 | 01 | 3 | 2681 | 2022-01-15 | 2022-01-21
2022 | 01 | 4 | 3915 | 2022-01-22 | 2022-01-31
2022 | 02 | 1 | 1194 | 2022-02-01 | 2022-02-03
db<>fiddle here
Or the same in columns:
with a as (
select
date '2021-01-01' - 1 + level as dt
, level as val
from dual
connect by level < 400
)
, b as (
select
val
/*Map 29, 30 and 31 to 28*/
, to_char(dt, 'yyyymm') as m
, to_char(
least(dt, trunc(dt, 'mm') + 27)
, 'w'
) as w
from a
)
select
substr(m, 1, 4) as y
, substr(m, 5, 2) as m
, tutar1
, tutar2
, tutar3
, tutar4
from b
pivot(
sum(val)
for w in (
1 as tutar1, 2 as tutar2
, 3 as tutar3, 4 as tutar4
)
)
Y | M | TUTAR1 | TUTAR2 | TUTAR3 | TUTAR4
:--- | :- | -----: | -----: | -----: | -----:
2021 | 01 | 28 | 77 | 126 | 265
2021 | 02 | 245 | 294 | 343 | 392
2021 | 03 | 441 | 490 | 539 | 855
2021 | 04 | 658 | 707 | 756 | 1044
2021 | 05 | 868 | 917 | 966 | 1465
2021 | 06 | 1085 | 1134 | 1183 | 1593
2021 | 07 | 1295 | 1344 | 1393 | 2075
2021 | 08 | 1512 | 1561 | 1610 | 2385
2021 | 09 | 1729 | 1778 | 1827 | 2421
2021 | 10 | 1939 | 1988 | 2037 | 2995
2021 | 11 | 2156 | 2205 | 2254 | 2970
2021 | 12 | 2366 | 2415 | 2464 | 3605
2022 | 01 | 2583 | 2632 | 2681 | 3915
2022 | 02 | 1194 | null | null | null
db<>fiddle here

How to create a SQL query for the below scenario

I am using Snowflake SQL, but I guess this can be solved by any sql. So I have data like this:
RA_MEMBER_ID YEAR QUARTER MONTH Monthly_TOTAL_PURCHASE CATEGORY
1000 2020 1 1 105 CAT10
1000 2020 1 1 57 CAT13
1000 2020 1 2 107 CAT10
1000 2020 1 2 59 CAT13
1000 2020 1 3 109 CAT11
1000 2020 1 3 61 CAT14
1000 2020 2 4 111 CAT11
1000 2020 2 4 63 CAT14
1000 2020 2 5 113 CAT12
1000 2020 2 5 65 CAT15
1000 2020 2 6 115 CAT12
1000 2020 2 6 67 CAT15
And I need data like this:
RA_MEMBER_ID YEAR QUARTER MONTH Monthly_TOTAL_PURCHASE CATEGORY Monthly_rank Quarterly_Total_purchase Quarter_category Quarter_rank Yearly_Total_purchase Yearly_category Yearly_rank
1000 2020 1 1 105 CAT10 1 105 CAT10 1 105 CAT10 1
1000 2020 1 1 57 CAT13 2 57 CAT13 2 57 CAT13 2
1000 2020 1 2 107 CAT10 1 212 CAT10 1 212 CAT10 1
1000 2020 1 2 59 CAT13 2 116 CAT13 2 116 CAT13 2
1000 2020 1 3 109 CAT11 1 212 CAT10 1 212 CAT10 1
1000 2020 1 3 61 CAT14 2 116 CAT13 2 116 CAT13 2
1000 2020 2 4 111 CAT11 1 111 CAT11 1 212 CAT10 1
1000 2020 2 4 63 CAT14 2 63 CAT14 2 124 CAT14 2
1000 2020 2 5 113 CAT12 1 113 CAT12 1 212 CAT10 1
1000 2020 2 5 65 CAT15 2 65 CAT15 2 124 CAT14 2
1000 2020 2 6 115 CAT12 1 228 CAT12 1 228 CAT12 1
1000 2020 2 6 67 CAT15 2 132 CAT15 2 132 CAT15 2
So basically, I have the top two categories by purchase amount for the first 6 months. I need the same for quarterly based on which month of the quarter it is. So let's say it is February, then the top 2 categories and amounts should be calculated based on both January and February. For March we have to get the quarter data by taking all three months. From April it will be the same as monthly rank, for May again calculate based on April and May. Similarly for Yearly also.
I have tried a lot of things but nothing seems to give me what I want.
The solution should be generic enough because there can be many other months and years.
I really need help in this.
Not sure if below is what you are after. I assume that everything is category based:
create or replace table test (
ra_member_id int,
year int,
quarter int,
month int,
monthly_purchase int,
category varchar
);
insert into test values
(1000, 2020, 1,1, 105, 'cat10'),
(1000, 2020, 1,1, 57, 'cat13'),
(1000, 2020, 1,2, 107, 'cat10'),
(1000, 2020, 1,2, 59, 'cat13'),
(1000, 2020, 1,3, 109, 'cat11'),
(1000, 2020, 1,3, 61, 'cat14'),
(1000, 2020, 2,4, 111, 'cat11'),
(1000, 2020, 2,4, 63, 'cat14'),
(1000, 2020, 2,5, 113, 'cat12'),
(1000, 2020, 2,5, 65, 'cat15'),
(1000, 2020, 2,6, 115, 'cat12'),
(1000, 2020, 2,6, 67, 'cat15');
WITH BASE as (
select
RA_MEMBER_ID,
YEAR,
QUARTER,
MONTH,
CATEGORY,
MONTHLY_PURCHASE,
LAG(MONTHLY_PURCHASE) OVER (PARTITION BY QUARTER, CATEGORY ORDER BY MONTH) AS QUARTERLY_PURCHASE_LAG,
IFNULL(QUARTERLY_PURCHASE_LAG, 0) + MONTHLY_PURCHASE AS QUARTERLY_PURCHASE,
LAG(MONTHLY_PURCHASE) OVER (PARTITION BY YEAR, CATEGORY ORDER BY MONTH) AS YEARLY_PURCHASE_LAG,
IFNULL(YEARLY_PURCHASE_LAG, 0) + MONTHLY_PURCHASE AS YEARLY_PURCHASE
FROM
TEST
),
BASE_RANK AS (
SELECT
RA_MEMBER_ID,
YEAR,
QUARTER,
MONTH,
CATEGORY,
MONTHLY_PURCHASE,
RANK() OVER (PARTITION BY MONTH ORDER BY MONTHLY_PURCHASE DESC) as MONTHLY_RANK,
QUARTERLY_PURCHASE,
RANK() OVER (PARTITION BY QUARTER ORDER BY QUARTERLY_PURCHASE DESC) as QUARTERLY_RANK,
YEARLY_PURCHASE,
RANK() OVER (PARTITION BY YEAR ORDER BY YEARLY_PURCHASE DESC) as YEARLY_RANK
FROM BASE
),
MAIN AS (
SELECT
RA_MEMBER_ID,
YEAR,
QUARTER,
MONTH,
CATEGORY,
MONTHLY_PURCHASE,
MONTHLY_RANK,
QUARTERLY_PURCHASE,
QUARTERLY_RANK,
YEARLY_PURCHASE,
YEARLY_RANK
FROM BASE_RANK
)
SELECT * FROM MAIN
ORDER BY YEAR, QUARTER, MONTH
;
Result:
+--------------+------+---------+-------+----------+------------------+--------------+--------------------+----------------+-----------------+-------------+
| RA_MEMBER_ID | YEAR | QUARTER | MONTH | CATEGORY | MONTHLY_PURCHASE | MONTHLY_RANK | QUARTERLY_PURCHASE | QUARTERLY_RANK | YEARLY_PURCHASE | YEARLY_RANK |
|--------------+------+---------+-------+----------+------------------+--------------+--------------------+----------------+-----------------+-------------|
| 1000 | 2020 | 1 | 1 | cat10 | 105 | 1 | 105 | 4 | 105 | 9 |
| 1000 | 2020 | 1 | 1 | cat13 | 57 | 2 | 57 | 6 | 57 | 12 |
| 1000 | 2020 | 1 | 2 | cat10 | 107 | 1 | 212 | 1 | 212 | 3 |
| 1000 | 2020 | 1 | 2 | cat13 | 59 | 2 | 116 | 2 | 116 | 6 |
| 1000 | 2020 | 1 | 3 | cat11 | 109 | 1 | 109 | 3 | 109 | 8 |
| 1000 | 2020 | 1 | 3 | cat14 | 61 | 2 | 61 | 5 | 61 | 11 |
| 1000 | 2020 | 2 | 4 | cat11 | 111 | 1 | 111 | 4 | 220 | 2 |
| 1000 | 2020 | 2 | 4 | cat14 | 63 | 2 | 63 | 6 | 124 | 5 |
| 1000 | 2020 | 2 | 5 | cat12 | 113 | 1 | 113 | 3 | 113 | 7 |
| 1000 | 2020 | 2 | 5 | cat15 | 65 | 2 | 65 | 5 | 65 | 10 |
| 1000 | 2020 | 2 | 6 | cat12 | 115 | 1 | 228 | 1 | 228 | 1 |
| 1000 | 2020 | 2 | 6 | cat15 | 67 | 2 | 132 | 2 | 132 | 4 |
+--------------+------+---------+-------+----------+------------------+--------------+--------------------+----------------+-----------------+-------------+

How to calcute future datetime after few workinghours

I am trying to calculate what date will be after 2 or more working hours from now even if I'll start calculating on weekend or after workhours it should be like:
working hours are from 8am to 4pm
I start calculating at Friday at 3pm so if I'll start calculating result should be Monday 9am
if(#data_przyj>#WorkStart AND DATEPART(DATEADD(MINUTE,#ileNaZapytanie,#data_przyj)<#WorkFinish)
BEGIN
while (DATEPART(dw, #CurrentDate)!=1 AND DATEPART(dw, #CurrentDate)!=7))
BEGIN
SET #CurrentDate = DATEADD(day, 1, #CurrentDate)
SET #czyBylPrzeskok =1
END
if (#czyBylPrzeskok =1)
BEGIN
SET #LastDay = #CurrentDate
SET #LastDay = DATEADD(MINUTE, datediff(MINUTE,DATEADD(dd, 0, DATEDIFF(MINUTE, 0, #data_przyj)),#WorkStart), #LastDay)
SET #LastDay = DATEADD(HOUR, datediff(MINUTE,DATEADD(dd, 0, DATEDIFF(HOUR, 0, #data_przyj)),#WorkStart), #LastDay)
END
ELSE
BEGIN
SET #LastDay = DATEADD(MINUTE,#ileNaZapytanie,#data_przyj)
END
SET #IsCalculated = 1
END
else if(#data_przyj>#WorkStart AND DATEADD(MINUTE,#ileNaZapytanie,#data_przyj)>#WorkFinish)
BEGIN
SET #LastDay =DateADD(DD,3,GETDATE());
SET #IsCalculated = 1
END
else if(#data_przyj<#WorkStart )
BEGIN
SET #LastDay =GETDATE();
SET #IsCalculated = 1
END
END
EDIT:
for example working hours:8:00 - 16:00 i have Date '2019-09-06 15:00' so after adding 2 working hours should be '2019-09-09 09:00', for date '2019-09-06 13:00' should be '2019-09-06 15:00' etc
The following solution uses a calendar table with working hours, then use a rolling sum to accumulate each day's business hours and find which day you need to end with.
Using a calendar table will give you the flexibility of having different business time periods and very easily adding or removing holidays.
Setup (calendar table):
IF OBJECT_ID('tempdb..#WorkingCalendar') IS NOT NULL
DROP TABLE #WorkingCalendar
CREATE TABLE #WorkingCalendar (
Date DATE PRIMARY KEY,
IsWorkingDay BIT,
WorkingStartTime DATETIME,
WorkingEndTime DATETIME)
SET DATEFIRST 1 -- 1: Monday, 7: Sunday
DECLARE #StartDate DATE = '2019-01-01'
DECLARE #EndDate DATE = '2030-01-01'
;WITH RecursiveDates AS
(
SELECT
GeneratedDate = #StartDate
UNION ALL
SELECT
GeneratedDate = DATEADD(DAY, 1, R.GeneratedDate)
FROM
RecursiveDates AS R
WHERE
R.GeneratedDate < #EndDate
)
INSERT INTO #WorkingCalendar (
Date,
IsWorkingDay,
WorkingStartTime,
WorkingEndTime)
SELECT
Date = R.GeneratedDate,
IsWorkingDay = CASE
WHEN DATEPART(WEEKDAY, R.GeneratedDate) BETWEEN 1 AND 5 THEN 1 -- From Monday to Friday
ELSE 0 END,
WorkingStartTime = CASE
WHEN DATEPART(WEEKDAY, R.GeneratedDate) BETWEEN 1 AND 5
THEN CONVERT(DATETIME, R.GeneratedDate) + CONVERT(DATETIME, '08:00:00') END,
WorkingEndTime = CASE
WHEN DATEPART(WEEKDAY, R.GeneratedDate) BETWEEN 1 AND 5
THEN CONVERT(DATETIME, R.GeneratedDate) + CONVERT(DATETIME, '16:00:00') END
FROM
RecursiveDates AS R
OPTION
(MAXRECURSION 0)
Generates a table like the following:
+------------+--------------+-------------------------+-------------------------+
| Date | IsWorkingDay | WorkingStartTime | WorkingEndTime |
+------------+--------------+-------------------------+-------------------------+
| 2019-01-01 | 1 | 2019-01-01 08:00:00.000 | 2019-01-01 16:00:00.000 |
| 2019-01-02 | 1 | 2019-01-02 08:00:00.000 | 2019-01-02 16:00:00.000 |
| 2019-01-03 | 1 | 2019-01-03 08:00:00.000 | 2019-01-03 16:00:00.000 |
| 2019-01-04 | 1 | 2019-01-04 08:00:00.000 | 2019-01-04 16:00:00.000 |
| 2019-01-05 | 0 | NULL | NULL |
| 2019-01-06 | 0 | NULL | NULL |
| 2019-01-07 | 1 | 2019-01-07 08:00:00.000 | 2019-01-07 16:00:00.000 |
| 2019-01-08 | 1 | 2019-01-08 08:00:00.000 | 2019-01-08 16:00:00.000 |
| 2019-01-09 | 1 | 2019-01-09 08:00:00.000 | 2019-01-09 16:00:00.000 |
| 2019-01-10 | 1 | 2019-01-10 08:00:00.000 | 2019-01-10 16:00:00.000 |
| 2019-01-11 | 1 | 2019-01-11 08:00:00.000 | 2019-01-11 16:00:00.000 |
| 2019-01-12 | 0 | NULL | NULL |
| 2019-01-13 | 0 | NULL | NULL |
| 2019-01-14 | 1 | 2019-01-14 08:00:00.000 | 2019-01-14 16:00:00.000 |
| 2019-01-15 | 1 | 2019-01-15 08:00:00.000 | 2019-01-15 16:00:00.000 |
| 2019-01-16 | 1 | 2019-01-16 08:00:00.000 | 2019-01-16 16:00:00.000 |
| 2019-01-17 | 1 | 2019-01-17 08:00:00.000 | 2019-01-17 16:00:00.000 |
+------------+--------------+-------------------------+-------------------------+
Proposed Solution:
DECLARE #v_BusinessHoursToAdd INT = 2
DECLARE #v_CurrentDateTimeHour DATETIME = '2019-09-06 15:00'
;WITH CalendarFromNow AS
(
SELECT
T.Date,
WorkingStartTime = CASE
WHEN #v_CurrentDateTimeHour BETWEEN T.WorkingStartTime AND T.WorkingEndTime THEN #v_CurrentDateTimeHour
ELSE T.WorkingStartTime END,
WorkingEndTime = T.WorkingEndTime
FROM
#WorkingCalendar AS T
WHERE
T.Date >= CONVERT(DATE, #v_CurrentDateTimeHour) AND
T.IsWorkingDay = 1
),
RollingBusinessSum AS
(
SELECT
C.Date,
C.WorkingStartTime,
C.WorkingEndTime,
AmountBusinessHours = DATEDIFF(HOUR, C.WorkingStartTime, C.WorkingEndTime),
RollingBusinessHoursSum = SUM(DATEDIFF(HOUR, C.WorkingStartTime, C.WorkingEndTime)) OVER (ORDER BY C.Date),
PendingHours = #v_BusinessHoursToAdd - SUM(DATEDIFF(HOUR, C.WorkingStartTime, C.WorkingEndTime)) OVER (ORDER BY C.Date)
FROM
CalendarFromNow AS C
)
SELECT TOP 1
EndingHour = DATEADD(
HOUR,
R.PendingHours,
R.WorkingEndTime)
FROM
RollingBusinessSum AS R
WHERE
R.PendingHours < 0
ORDER BY
R.Date
Explanation:
The first CTE CalendarFromNow is simply filtering the calendar dates from the current hour's date and reducing the starting working datetime to the current hour, since this is gonna be the starting point to count hours from.
+------------+-------------------------+-------------------------+
| Date | WorkingStartTime | WorkingEndTime |
+------------+-------------------------+-------------------------+
| 2019-09-06 | 2019-09-06 15:00:00.000 | 2019-09-06 16:00:00.000 |
| 2019-09-09 | 2019-09-09 08:00:00.000 | 2019-09-09 16:00:00.000 |
| 2019-09-10 | 2019-09-10 08:00:00.000 | 2019-09-10 16:00:00.000 |
| 2019-09-11 | 2019-09-11 08:00:00.000 | 2019-09-11 16:00:00.000 |
| 2019-09-12 | 2019-09-12 08:00:00.000 | 2019-09-12 16:00:00.000 |
| 2019-09-13 | 2019-09-13 08:00:00.000 | 2019-09-13 16:00:00.000 |
| 2019-09-16 | 2019-09-16 08:00:00.000 | 2019-09-16 16:00:00.000 |
+------------+-------------------------+-------------------------+
The second CTE RollingBusinessSum is calculating the amount of business hours on each day and accumulating them over the days. The last column PendingHours is the result of the amount of hours we need to add from now subtracted by the sum of business hours over the days.
+------------+-------------------------+-------------------------+---------------------+-------------------------+--------------+
| Date | WorkingStartTime | WorkingEndTime | AmountBusinessHours | RollingBusinessHoursSum | PendingHours |
+------------+-------------------------+-------------------------+---------------------+-------------------------+--------------+
| 2019-09-06 | 2019-09-06 15:00:00.000 | 2019-09-06 16:00:00.000 | 1 | 1 | 1 |
| 2019-09-09 | 2019-09-09 08:00:00.000 | 2019-09-09 16:00:00.000 | 8 | 9 | -7 |
| 2019-09-10 | 2019-09-10 08:00:00.000 | 2019-09-10 16:00:00.000 | 8 | 17 | -15 |
| 2019-09-11 | 2019-09-11 08:00:00.000 | 2019-09-11 16:00:00.000 | 8 | 25 | -23 |
| 2019-09-12 | 2019-09-12 08:00:00.000 | 2019-09-12 16:00:00.000 | 8 | 33 | -31 |
| 2019-09-13 | 2019-09-13 08:00:00.000 | 2019-09-13 16:00:00.000 | 8 | 41 | -39 |
+------------+-------------------------+-------------------------+---------------------+-------------------------+--------------+
Finally the first day that the PendingHours column is negative is the day that we arrived at the amount of hours we wanted to add. This is the TOP 1 with ORDER BY. To get the final datetime, we just subtract the pending hours to the end time for that particular day.
+------------+-------------------------+-------------------------+---------------------+-------------------------+--------------+-------------------------+
| Date | WorkingStartTime | WorkingEndTime | AmountBusinessHours | RollingBusinessHoursSum | PendingHours | EndingHour |
+------------+-------------------------+-------------------------+---------------------+-------------------------+--------------+-------------------------+
| 2019-09-09 | 2019-09-09 08:00:00.000 | 2019-09-09 16:00:00.000 | 8 | 9 | -7 | 2019-09-09 09:00:00.000 |
+------------+-------------------------+-------------------------+---------------------+-------------------------+--------------+-------------------------+
You might have to tweak performance and do boundary tests but this might give you a flexible idea of how to work with working hours at holidays or different time periods.

GROUP BY INTERVALS

I want to take the average of multiple rows of table and insert them into a view:
My table:
Date(datetime)
Period(int) -- Half-hour
Price(decimal(18,2)
Sample data:
Date | Period | Price
---------+--------+---------
07-12-17 | 47 | 10
07-12-17 | 48 | 20
07-12-17 | 1 | 30
07-12-17 | 2 | 40
07-12-17 | 3 | 50
07-12-17 | 4 | 60
07-12-17 | 5 | 70
07-12-17 | 6 | 80
07-12-17 | 7 | 10
07-12-17 | 8 | 10
07-12-17 | 9 | 10
07-12-17 | 10 | 10
07-12-17 | 11 | 20
07-12-17 | 12 | 20
07-12-17 | 13 | 20
07-12-17 | 14 | 20
My periods are half hours go from 1-48 and I want to take the average price of intervals of 8 periods and assign that price to all 8 periods in the set. Because of the time difference, I start with half-hour 47 instead of 1:
The intervals:
[48-6], [7-14], [15-22], [23-30], [31-38], [39-46]
I want the resulting view to look like:
Date | Period | Price
---------+--------+---------
07-12-17 | 47 | 45
07-12-17 | 48 | 45
07-12-17 | 1 | 45
07-12-17 | 2 | 45
07-12-17 | 3 | 45
07-12-17 | 4 | 45
07-12-17 | 5 | 45
07-12-17 | 6 | 45
07-12-17 | 7 | 15
07-12-17 | 8 | 15
07-12-17 | 9 | 15
07-12-17 | 10 | 15
07-12-17 | 11 | 15
07-12-17 | 12 | 15
07-12-17 | 13 | 15
07-12-17 | 14 | 15
I have not been able to come up with a complete query, but I am thinking it has to be GROUP BY and maybe with a HAVING Statement.
Hope you can help!
It can be done very easily and efficiently using window functions.
Sample data
DECLARE #T TABLE (dt datetime2(0), Period int, Price money);
INSERT INTO #T VALUES
('2017-12-06', 39, 90),
('2017-12-06', 40, 90),
('2017-12-06', 41, 90),
('2017-12-06', 42, 90),
('2017-12-06', 43, 90),
('2017-12-06', 44, 90),
('2017-12-06', 45, 90),
('2017-12-06', 46, 90),
('2017-12-06', 47, 10),
('2017-12-06', 48, 20),
('2017-12-07', 1, 30),
('2017-12-07', 2, 40),
('2017-12-07', 3, 50),
('2017-12-07', 4, 60),
('2017-12-07', 5, 70),
('2017-12-07', 6, 80),
('2017-12-07', 7, 10),
('2017-12-07', 8, 10),
('2017-12-07', 9, 10),
('2017-12-07', 10, 10),
('2017-12-07', 11, 20),
('2017-12-07', 12, 20),
('2017-12-07', 13, 20),
('2017-12-07', 14, 20),
('2017-12-07', 15, 40),
('2017-12-07', 16, 40),
('2017-12-07', 17, 40),
('2017-12-07', 18, 40),
('2017-12-07', 19, 30),
('2017-12-07', 20, 30),
('2017-12-07', 21, 30),
('2017-12-07', 22, 30);
Query
SELECT
dt
,Period
,Price
,DATEADD(minute, (Period-1)*30 + 60, dt) as d2
,DATEDIFF(hour, '2001-01-01', DATEADD(minute, (Period-1)*30 + 60, dt)) / 4 as d3
,AVG(Price) OVER (PARTITION BY
DATEDIFF(hour, '2001-01-01', DATEADD(minute, (Period-1)*30 + 60, dt)) / 4) AS AvgPrice
FROM #T AS MyTable
ORDER BY dt, Period;
I included intermediary results d2 and d3 in the output to help understand the formula in PARTITION BY.
Result
+---------------------+--------+-------+---------------------+-------+----------+
| dt | Period | Price | d2 | d3 | AvgPrice |
+---------------------+--------+-------+---------------------+-------+----------+
| 2017-12-06 00:00:00 | 39 | 90.00 | 2017-12-06 20:00:00 | 37103 | 90.00 |
| 2017-12-06 00:00:00 | 40 | 90.00 | 2017-12-06 20:30:00 | 37103 | 90.00 |
| 2017-12-06 00:00:00 | 41 | 90.00 | 2017-12-06 21:00:00 | 37103 | 90.00 |
| 2017-12-06 00:00:00 | 42 | 90.00 | 2017-12-06 21:30:00 | 37103 | 90.00 |
| 2017-12-06 00:00:00 | 43 | 90.00 | 2017-12-06 22:00:00 | 37103 | 90.00 |
| 2017-12-06 00:00:00 | 44 | 90.00 | 2017-12-06 22:30:00 | 37103 | 90.00 |
| 2017-12-06 00:00:00 | 45 | 90.00 | 2017-12-06 23:00:00 | 37103 | 90.00 |
| 2017-12-06 00:00:00 | 46 | 90.00 | 2017-12-06 23:30:00 | 37103 | 90.00 |
| 2017-12-06 00:00:00 | 47 | 10.00 | 2017-12-07 00:00:00 | 37104 | 45.00 |
| 2017-12-06 00:00:00 | 48 | 20.00 | 2017-12-07 00:30:00 | 37104 | 45.00 |
| 2017-12-07 00:00:00 | 1 | 30.00 | 2017-12-07 01:00:00 | 37104 | 45.00 |
| 2017-12-07 00:00:00 | 2 | 40.00 | 2017-12-07 01:30:00 | 37104 | 45.00 |
| 2017-12-07 00:00:00 | 3 | 50.00 | 2017-12-07 02:00:00 | 37104 | 45.00 |
| 2017-12-07 00:00:00 | 4 | 60.00 | 2017-12-07 02:30:00 | 37104 | 45.00 |
| 2017-12-07 00:00:00 | 5 | 70.00 | 2017-12-07 03:00:00 | 37104 | 45.00 |
| 2017-12-07 00:00:00 | 6 | 80.00 | 2017-12-07 03:30:00 | 37104 | 45.00 |
| 2017-12-07 00:00:00 | 7 | 10.00 | 2017-12-07 04:00:00 | 37105 | 15.00 |
| 2017-12-07 00:00:00 | 8 | 10.00 | 2017-12-07 04:30:00 | 37105 | 15.00 |
| 2017-12-07 00:00:00 | 9 | 10.00 | 2017-12-07 05:00:00 | 37105 | 15.00 |
| 2017-12-07 00:00:00 | 10 | 10.00 | 2017-12-07 05:30:00 | 37105 | 15.00 |
| 2017-12-07 00:00:00 | 11 | 20.00 | 2017-12-07 06:00:00 | 37105 | 15.00 |
| 2017-12-07 00:00:00 | 12 | 20.00 | 2017-12-07 06:30:00 | 37105 | 15.00 |
| 2017-12-07 00:00:00 | 13 | 20.00 | 2017-12-07 07:00:00 | 37105 | 15.00 |
| 2017-12-07 00:00:00 | 14 | 20.00 | 2017-12-07 07:30:00 | 37105 | 15.00 |
| 2017-12-07 00:00:00 | 15 | 40.00 | 2017-12-07 08:00:00 | 37106 | 35.00 |
| 2017-12-07 00:00:00 | 16 | 40.00 | 2017-12-07 08:30:00 | 37106 | 35.00 |
| 2017-12-07 00:00:00 | 17 | 40.00 | 2017-12-07 09:00:00 | 37106 | 35.00 |
| 2017-12-07 00:00:00 | 18 | 40.00 | 2017-12-07 09:30:00 | 37106 | 35.00 |
| 2017-12-07 00:00:00 | 19 | 30.00 | 2017-12-07 10:00:00 | 37106 | 35.00 |
| 2017-12-07 00:00:00 | 20 | 30.00 | 2017-12-07 10:30:00 | 37106 | 35.00 |
| 2017-12-07 00:00:00 | 21 | 30.00 | 2017-12-07 11:00:00 | 37106 | 35.00 |
| 2017-12-07 00:00:00 | 22 | 30.00 | 2017-12-07 11:30:00 | 37106 | 35.00 |
+---------------------+--------+-------+---------------------+-------+----------+
The code sample below should get you close to where you need to be. I was not sure from your question how you planned on setting up your groups. You will definitely want to use a cross apply if you are using a view. This can be done even more simply with a stored procedure. For more info on Cross Apply, see section L, here https://learn.microsoft.com/en-us/sql/t-sql/queries/from-transact-sql.
CREATE VIEW MyTableAveragePrices
AS
SELECT mt.Date, mt.Period, mtAverageGroup.AveragePrice,
CASE
WHEN mtAvg.Period IN () THEN 'Period Group 1'
WHEN mtAvg.Period IN () THEN 'Period Group 2'
END AS PeriodGroup
FROM MyTable mt
CROSS APPLY
(
SELECT
AVERAGE(mtAvg.Price) AveragePrice,
CASE
WHEN mtAvg.Period IN () THEN 'Period Group 1'
WHEN mtAvg.Period IN () THEN 'Period Group 2'
END AS PeriodGroup
FROM MyTable mtAvg
WHERE
mt.PeriodGroup = PeriodGroup
GROUP BY
CASE
WHEN mtAvg.Period IN () THEN 'Period Group 1'
WHEN mtAvg.Period IN () THEN 'Period Group 2'
END
) mtAverageGroup
GO

Sql data transformation

This is my source table
Reference ModifiedDate Teachers Students SchoolID ETC
-------------------------------------------------------------------------
1023175 2017-03-03 16:02:01.723 10 25 5
1023175 2017-03-07 07:59:49.283 15 50 15
1023175 2017-03-12 11:14:40.230 25 6 5
1023176 2017-03-04 16:02:01.723 11 35 8
1023176 2017-03-08 07:59:49.283 16 60 25
1023177 2017-03-15 11:14:40.230 15 7 2
I need the following output
Reference StartDate EndDate
---------------------------------------------
1023175 2017-03-03 16:02:01.723 2017-03-07 07:59:49.283
1023175 2017-03-07 07:59:49.283 2017-03-12 11:14:40.230
1023175 2017-03-12 11:14:40.230 9999-12-31 00:00:00.000
1023176 2017-03-04 16:02:01.723 2017-03-08 07:59:49.283
1023176 2017-03-08 07:59:49.283 9999-12-31 00:00:00.000
1023177 2017-03-15 11:14:40.230 9999-12-31 00:00:00.000 (last record should have this value)
Teachers Students SchoolID
10 25 5
15 50 15
25 6 5
11 35 8
16 60 25
15 7 2
All other columns like Teachers,Students and SchoolId etc also have to be in the output along with each record.
Any suggestions on how this can be achieved?
Using Sql Server 2008
using outer apply():
select
Reference
, StartDate = t.ModifiedDate
, EndDate = coalesce(x.ModifiedDate, convert(datetime,'9999-12-31 00:00:00.000'))
, Teachers
, Students
, SchoolID
from t
outer apply (
select top 1 i.ModifiedDate
from t as i
where i.Reference = t.Reference
and i.ModifiedDate > t.ModifiedDate
order by i.ModifiedDate asc
) x
rextester demo: http://rextester.com/RFTD32624
returns:
+-----------+-------------------------+-------------------------+----------+----------+----------+
| Reference | StartDate | EndDate | Teachers | Students | SchoolID |
+-----------+-------------------------+-------------------------+----------+----------+----------+
| 1023175 | 2017-03-03 16:02:01.723 | 2017-03-07 07:59:49.283 | 10 | 25 | 5 |
| 1023175 | 2017-03-07 07:59:49.283 | 2017-03-12 11:14:40.230 | 15 | 50 | 15 |
| 1023175 | 2017-03-12 11:14:40.230 | 9999-12-31 00:00:00.000 | 25 | 6 | 5 |
| 1023176 | 2017-03-04 16:02:01.723 | 2017-03-08 07:59:49.283 | 11 | 35 | 8 |
| 1023176 | 2017-03-08 07:59:49.283 | 9999-12-31 00:00:00.000 | 16 | 60 | 25 |
| 1023177 | 2017-03-15 11:14:40.230 | 9999-12-31 00:00:00.000 | 15 | 7 | 2 |
+-----------+-------------------------+-------------------------+----------+----------+----------+
Reference:
apply() - msdn
The power of T-SQL's APPLY operator - Rob Farley
APPLY: It Slices! It Dices! It Does It All! - Brad Shulz