Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 1 year ago.
Improve this question
Hello I have such a situation I basically need to write a SQL code for such a statement
select *,
case when 'Issue' IN ('Overforecasted', 'Underforecasted') AND 'Start Date' DISTINCT 3 dates THEN 'Issue exists for 3 weeks in a row'
FROM Merged;
I know this is not a proper SQL format but does someone know how it can be edited?
Per one DMDUNIT check if it has 3 issues in column "Issues" and later check if it has 3 different start dates. If it has 3 issues ('Overforecasted', "Underforecasted") and 3 different dates for the same DMDUNIT I need to return it in a new column (end as "3InARow")
The current edited draft
SET ARITHABORT OFF
SET ANSI_WARNINGS OFF;
;WITH Forecast AS (
SELECT LOC, DMDUNIT, STARTDATE, TOTFCST
FROM SCPOMGR.FCSTPERFSTATIC
WHERE STARTDATE >= '2021-11-24'
), Actuals AS (
SELECT LOC, DMDUNIT, DMDPostDate, HistoryQuantity
FROM SCPOMGR.HISTWIDE_CHAIN
WHERE DMDPostDate >= '2021-11-24'
), Merged as (
select
COALESCE(f.LOC, a.LOC) AS LOC,
COALESCE(f.DMDUNIT, a.DMDUNIT) AS DMDUNIT,
COALESCE(f.STARTDATE, a.DMDPostDate) AS "Start Date",
SUM(F.TOTFCST) AS "Forecast",
SUM(a.HistoryQuantity) AS "Actuals",
SUM(ABS(a.HistoryQuantity) - f.TOTFCST) AS "Abs Error",
(1 - HistoryQuantity - TOTFCST) / HistoryQuantity as "FA%",
SUM(a.HistoryQuantity) / SUM(f.TOTFCST) AS "Bias",
CASE
WHEN TOTFCST > HistoryQuantity THEN 'Overforecasted'
WHEN TOTFCST < HistoryQuantity THEN 'Underforecasted'
WHEN HistoryQuantity IS NULL AND TOTFCST > 0 THEN 'Overforecasted'
WHEN TOTFCST IS NULL AND HistoryQuantity > 0 THEN 'Underforecasted'
WHEN TOTFCST = 0.000 AND HistoryQuantity IS NULL THEN 'No issue'
END AS Issue
FROM Forecast f FULL OUTER JOIN Actuals a
ON f.LOC = a.LOC AND f.DMDUNIT = a.DMDUNIT AND f.STARTDATE = a.DMDPostDate
GROUP BY
COALESCE(f.LOC, a.LOC),
COALESCE(f.DMDUNIT, a.DMDUNIT),
COALESCE(f.STARTDATE, a.DMDPostDate),
a.HistoryQuantity, F.TOTFCST),
Transitions as (
select *,
case when indicator <> lag(indicator)
over (partition by DMDUNIT order by "Start Date")
then 1 end as tripped
from Merged cross apply (
select case when Issue in ('Overforecasted', 'Underforecasted')
then 1 else 0 end as indicator) v
), Bundles as (
select *, count(tripped) over (partition by DMDUNIT order by "Start Date") as grp
from Transitions
), Streaks as (
select *, count(*) over (partition by DMDUNIT, grp) as cnt
from Bundles
)
select *, case when indicator = 1 and cnt >= 3 then 'Yes' else 'No' end as InIssueStreak, cnt as StreakLength
from Streaks;
WITH Forecast AS (
SELECT LOC, DMDUNIT, STARTDATE, TOTFCST
FROM SCPOMGR.FCSTPERFSTATIC
WHERE STARTDATE >= '2021-11-24'
), Actuals AS (
SELECT LOC, DMDUNIT, DMDPostDate, HistoryQuantity
FROM SCPOMGR.HISTWIDE_CHAIN
WHERE DMDPostDate >= '2021-11-24'
), Merged AS (
SELECT
COALESCE(f.LOC, a.LOC) AS LOC,
COALESCE(f.DMDUNIT, a.DMDUNIT) AS DMDUNIT,
COALESCE(f.STARTDATE, a.DMDPostDate) AS "Start Date",
SUM(F.TOTFCST) AS "Forecast",
SUM(a.HistoryQuantity) AS "Actuals",
SUM(ABS(a.HistoryQuantity) - f.TOTFCST) AS "Abs Error"
(1 - SUM(a.HistoryQuantity - SUM(f.TOTFCST)) / SUM(a.HistoryQuantity) as "FA%",
SUM(a.HistoryQuantity) / SUM(f.TOTFCST) AS "Bias",
CASE
WHEN SUM(f.TOTFCST) > SUM(a.HistoryQuantity) THEN 'Overforecasted'
WHEN SUM(f.TOTFCST) < SUM(a.HistoryQuantity) THEN 'Underforecasted'
WHEN SUM(a.HistoryQuantity) IS NULL AND SUM(f.TOTFCST) > 0 THEN 'Overforecasted'
WHEN SUM(f.TOTFCST) IS NULL AND SUM(a.HistoryQuantity) > 0 THEN 'Underforecasted'
WHEN SUM(f.TOTFCST) = 0.000 AND SUM(a.HistoryQuantity) IS NULL THEN 'No issue'
END AS Issue
FROM Forecast f FULL OUTER JOIN Actuals a
ON f.LOC = a.LOC AND f.DMDUNIT = a.DMDUNIT AND f.STARTDATE = a.DMDPostDate
GROUP BY
COALESCE(f.LOC, a.LOC),
COALESCE(f.DMDUNIT, a.DMDUNIT),
COALESCE(f.STARTDATE, a.DMDPostDate)
ORDER BY
COALESCE(f.LOC, a.LOC),
COALESCE(f.DMDUNIT, a.DMDUNIT),
COALESCE(f.STARTDATE, a.DMDPostDate)
)
select *,
case when
min(Issue) over (
partition by DMDUNIT order by "Start Date"
rows between 2 preceding and current row) =
max(Issue) over (
partition by DMDUNIT order by "Start Date"
rows between 2 preceding and current row) and
count(Issue) over (
partition by DMDUNIT order by "Start Date"
rows between 2 preceding and current row) = 3
then 'Yes' else 'No' end as "3InARow"
from Merged;
If that doesn't work then try gaps and islands:
with (<copied from above...>), Transitions as (
select *,
case when indicator <> lag(indicator)
over (partition by DMDINIT order by "Start Date")
then 1 end as tripped
from Merged cross apply (
select case when Issue in ('Overforecasted', 'Underforecasted')
then 1 else 0 end as indicator) v
), Bundles as (
select *, sum(tripped) over (partition by DMDUNIT order by "Start Date") as grp
from Transitions
), Streaks as (
select *, count(*) over (partition by DMDUNIT, grp) as cnt
from Bundles
)
select *, case when cnt >= 3 then 'Yes' else 'No' end as InStreak, cnt as StreakLength
from Streaks;
https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=9fcbab1d93b7297aebc340111aa3a448
The fiddle
I've created a working test case which demonstrates how to identify consecutive weeks of over or under forecast items.
All the detail about how a weekly value is calculated or how you identify items is immaterial to the basic question. I've ignored issues related to missing data for a week. That can be easily included and just obscures the fundamental (and only) question asked.
For this example, just assume we have only the weeks of interest and all weeks for all items exist.
The SQL, which also contains the data:
WITH forecast (item, prediction) AS (
SELECT 1, 1000 UNION
SELECT 2, 500
)
, actuals (weekno, item, actual) AS (
SELECT 1, 1, 500 UNION
SELECT 2, 1, 600 UNION
SELECT 3, 1, 1600 UNION
SELECT 4, 1, 1600 UNION
SELECT 5, 1, 600 UNION
SELECT 6, 1, 1100 UNION
SELECT 7, 1, 1200 UNION
SELECT 8, 1, 1150 UNION
SELECT 9, 1, 601 UNION
SELECT 10, 1, 602 UNION
SELECT 11, 1, 603 UNION
SELECT 1, 2, 1500 UNION
SELECT 2, 2, 600 UNION
SELECT 3, 2, 550 UNION
SELECT 4, 2, 500 UNION
SELECT 5, 2, 600 UNION
SELECT 6, 2, 491 UNION
SELECT 7, 2, 492 UNION
SELECT 8, 2, 493 UNION
SELECT 9, 2, 494 UNION
SELECT 10, 2, 620
)
, step1 AS (
SELECT a.*
, f.prediction
, CASE WHEN actual < prediction THEN -1
WHEN actual > prediction THEN +1
ELSE 0
END AS side
FROM forecast AS f
JOIN actuals AS a
ON f.item = a.item
)
, step2 AS (
SELECT *
, CASE WHEN side = LAG(side) OVER (PARTITION BY item ORDER BY weekno) THEN 0 ELSE 1 END AS edge
FROM step1
)
, step3 AS (
SELECT *
, SUM(edge) OVER (PARTITION BY item ORDER BY weekno) AS xgroup
FROM step2
)
, step4 AS (
SELECT *
, COUNT(*) OVER (PARTITION BY item, xgroup) AS xcount
FROM step3
)
SELECT *
FROM step4
WHERE xcount >= 3
ORDER BY item, weekno
;
The result:
weekno
item
actual
prediction
side
edge
xgroup
xcount
6
1
1100
1000
1
1
4
3
7
1
1200
1000
1
0
4
3
8
1
1150
1000
1
0
4
3
9
1
601
1000
-1
1
5
3
10
1
602
1000
-1
0
5
3
11
1
603
1000
-1
0
5
3
1
2
1500
500
1
1
1
3
2
2
600
500
1
0
1
3
3
2
550
500
1
0
1
3
6
2
491
500
-1
1
4
4
7
2
492
500
-1
0
4
4
8
2
493
500
-1
0
4
4
9
2
494
500
-1
0
4
4
Related
I have a table that stores the start-date and number of the hours. I have also another time table as reference to working days. My main goal is the divide this hours to the working days.
For examle:
ID Date Hour
1 20210504 40
I want it to be structured as
ID Date Hour
1 20210504 8
1 20210505 8
1 20210506 8
1 20210507 8
1 20210510 8
I manage to divide the hours with the given code but couldn't manage to make it in working days.
WITH cte1 AS
(
select 1 AS ID, 20210504 AS Date, 40 AS Hours --just a test case
), working_days AS
(
select date from dateTable
),
cte2 AS
(
select ID, Date, Hours, IIF(Hours<=8, Hours, 8) AS dailyHours FROM cte1
UNION ALL
SELECT
cte2.ID,
cte2.Date + 1
,cte2.Hours - 8
,IIF(Hours<=8, Hours, 8)
FROM cte2
JOIN cte1 t ON cte2.ID = t.ID
WHERE cte2.HOURS > 8 AND cte2.Date + 1 IN (select * from working_days)
When I use it like this it only gives me this output with one day missing
ID Date Hour
1 20210504 8
1 20210505 8
1 20210506 8
1 20210507 8
To solve your problem you need to build your calendar in the right way,
adding also to working_days a ROW_NUMBER to get correct progression.
declare #date_start date = '2021-05-01'
;WITH
cte1 AS (
SELECT * FROM
(VALUES
(1, '20210504', 40),
(2, '20210505', 55),
(3, '20210503', 44)
) X (ID, Date, Hour)
),
numbers as (
SELECT ROW_NUMBER() over (order by o.object_id) N
FROM sys.objects o
),
cal as (
SELECT cast(DATEADD(day, n, #date_start) as date) d, n-1 n
FROM numbers n
where n.n<32
),
working_days as (
select d, ROW_NUMBER() over (order by n) dn
from cal
where DATEPART(weekday, d) < 6 /* monday to friday in italy (country dependent) */
),
base as (
SELECT t.ID, t.Hour, w.d, w.dn
from cte1 t
join working_days w on w.d = t.date
)
SELECT t.ID, w.d, iif((8*n)<=Hour, 8, 8 + Hour - (8*n) ) h
FROM base t
join numbers m on m.n <= (t.Hour / 8.0) + 0.5
join working_days w on w.dn = t.dn + N -1
order by 1,2
You can use a recursive CTE. This should do the trick:
with cte as (
select id, date, 8 as hour, hour as total_hour
from t
union all
select id, dateadd(day, 1, date),
(case when total_hour < 8 then total_hour else 8 end),
total_hour - 8
from cte
where total_hour > 0
)
select *
from cte;
Note: This assumes that total_hour is at least 8, just to avoid a case expression in the anchor part of the CTE. That can trivially be added.
Also, if there might be more than 100 days, you will need option (maxrecursion 0).
I have to calculate the number of cars a day - of every month
I tried to do this using the Datediff formula
But I can't add the segmentation of each month either.
Attached script table:
create table TABLE_A(Code FLOAT,DateIn datetime,dateOut datetime,Garage varchar(30)
)
insert into Table_A (Code,DateIn,dateOut,Garage) values
('1','2018-06-07 00:00:00.000','2018-12-19 00:00:00.000','X'),
('2','2018-05-30 00:00:00.000','2018-12-19 00:00:00.000','Y'),
('3','2018-08-08 00:00:00.000','2018-11-18 00:00:00.000','Z'),
('4','2018-12-30 00:00:00.000','2018-12-30 00:00:00.000','Y'),
('5','2018-09-16 00:00:00.000','2018-10-19 00:00:00.000','Y'),
('6','2018-05-08 00:00:00.000','2018-08-28 00:00:00.000','Z'),
('7','2018-01-29 00:00:00.000','2018-07-31 00:00:00.000','Z'),
('8','2018-05-24 00:00:00.000','2018-09-10 00:00:00.000','X'),
('9','2018-05-02 00:00:00.000','2018-06-30 00:00:00.000','Y'),
('10','2018-07-05 00:00:00.000','2018-12-09 00:00:00.00','Z')
And this is the structure of the query result that should be:(Columns:Year,month,Garage-Number of vehicles per day by month)
Year month X Y Z
2018 1
2018 2
2018 3
2018 4
2018 5
2018 6
2018 7
2018 8
2018 9
2018 10
2018 11
2018 12
Thanks for the help.
You can first generate the list of months and year and then can left join your table to that -
WITH MONTHS AS (SELECT 1 MNTHS
UNION ALL
SELECT 2
UNION ALL
SELECT 3
UNION ALL
SELECT 4
UNION ALL
SELECT 5
UNION ALL
SELECT 6
UNION ALL
SELECT 7
UNION ALL
SELECT 8
UNION ALL
SELECT 9
UNION ALL
SELECT 10
UNION ALL
SELECT 11
UNION ALL
SELECT 12),
YEAR AS (SELECT 2018 YEAR)
SELECT YEAR,
MNTHS,
SUM(CASE WHEN Garage = 'X' THEN 1 ELSE 0 END) AS X,
SUM(CASE WHEN Garage = 'Y' THEN 1 ELSE 0 END) AS Y,
SUM(CASE WHEN Garage = 'Z' THEN 1 ELSE 0 END) AS Z
FROM (SELECT * FROM MONTHS M CROSS JOIN YEAR Y) YEARS
LEFT JOIN TABLE_A T ON YEARS.MNTHS = MONTH(T.DateIn)
AND YEARS.YEAR = YEAR(T.DateIn)
GROUP BY YEAR(DateIn),
MONTH(DateIn),
MNTHS,
YEAR
ORDER BY YEAR,
MNTHS
Here is the fiddle
The following query should do what you want, the Recursive CTE part is used to figure out dates between each DayIn and DayOut. Once we have the complete list of dates, in the main query we do a conditional aggregation to find out the DISTINCT number of cars in the garage each month,
CREATE TABLE TABLE_A (Code FLOAT,DateIn DATETIME,dateOut DATETIME,Garage VARCHAR(30))
INSERT INTO Table_A (Code,DateIn,dateOut,Garage) VALUES
('1','2018-06-07 00:00:00.000','2018-12-19 00:00:00.000','X'),
('2','2018-05-30 00:00:00.000','2018-12-19 00:00:00.000','Y'),
('3','2018-08-08 00:00:00.000','2018-11-18 00:00:00.000','Z'),
('4','2018-12-30 00:00:00.000','2018-12-30 00:00:00.000','Y'),
('5','2018-09-16 00:00:00.000','2018-10-19 00:00:00.000','Y'),
('6','2018-05-08 00:00:00.000','2018-08-28 00:00:00.000','Z'),
('7','2018-01-29 00:00:00.000','2018-07-31 00:00:00.000','Z'),
('8','2018-05-24 00:00:00.000','2018-09-10 00:00:00.000','X'),
('9','2018-05-02 00:00:00.000','2018-06-30 00:00:00.000','Y'),
('10','2018-07-05 00:00:00.000','2018-12-09 00:00:00.00','Z')
/** Main Query Starts Here **/
;WITH CTE ([Code],[DateIn],[DateOut],[Garage]) AS (
SELECT [Code], [DateIn], [DateOut], [Garage]
FROM TABLE_A WHERE [DateIn] <= [DateOut]
UNION ALL
SELECT [Code], DATEADD(DAY, 1, [DateIn]), [DateOut], [Garage]
FROM CTE
WHERE [DateIn] < [DateOut])
SELECT
YEAR([DateIn]) AS [Year]
,MONTH([DateIn]) AS [Month]
,COUNT( DISTINCT CASE WHEN [Garage] = 'X' THEN T.t1 ELSE NULL END) AS X
,COUNT( DISTINCT CASE WHEN [Garage] = 'Y' THEN T.t1 ELSE NULL END) AS Y
,COUNT( DISTINCT CASE WHEN [Garage] = 'Z' THEN T.t1 ELSE NULL END) AS Z
FROM CTE
CROSS APPLY (VALUES (CONVERT(VARCHAR(4),YEAR([DateIn])) + CONVERT(VARCHAR(2),MONTH([DateIn])) + CONVERT(VARCHAR(20),[Code]))) AS T(t1)
GROUP BY YEAR([DateIn]), MONTH([DateIn])
ORDER BY [Year], [Month]
OPTION (MAXRECURSION 0)
The result is as below,
Year Month X Y Z
2018 1 0 0 1
2018 2 0 0 1
2018 3 0 0 1
2018 4 0 0 1
2018 5 1 2 2
2018 6 2 2 2
2018 7 2 1 3
2018 8 2 1 3
2018 9 2 2 2
2018 10 1 2 2
2018 11 1 1 2
2018 12 1 2 1
You can generate the dates using a recursive subquery.
Then you have multiple ways to calculate the summary data. One simple method uses apply:
with dates as (
select convert(date, '2018-01-01') as dte, 1 as lev
union all
select dateadd(month, 1, dte), lev + 1
from dates
where lev < 12
)
select year(d.dte), month(d.dte), s.*
from dates d outer apply
(select sum(case when a.Garage = 'X' then 1 else 0 end) as x,
sum(case when a.Garage = 'Y' then 1 else 0 end) as y,
sum(case when a.Garage = 'Z' then 1 else 0 end) as z
from table_a a
where a.datein <= d.dte and a.dateout >= d.dte
) s;
Your question is a little vague on the exact calculation. This calculates the number of cars in each garage on the first of each month.
Here is a db<>fiddle.
EDIT:
Your revised question is more complicated, but since I started answering:
with dates as (
select convert(date, '2018-01-01') as dte, 1 as lev
union all
select dateadd(month, 1, dte), lev + 1
from dates
where lev < 12
)
select year(d.dte), month(d.dte),
sum(case when a.Garage = 'X'
then datediff(day,
(case when a.datein < d.dte then d.dte else datein end),
(case when a.dateout >= dateadd(month, 1, d.dte) then eomonth(d.dte) else dateout end)
) + 1
else 0
end) as x_cardays,
sum(case when a.Garage = 'Y'
then datediff(day,
(case when a.datein < d.dte then d.dte else datein end),
(case when a.dateout >= dateadd(month, 1, d.dte) then eomonth(d.dte) else dateout end)
) + 1
else 0
end) as y_cardays,
sum(case when a.Garage = 'Z'
then datediff(day,
(case when a.datein < d.dte then d.dte else datein end),
(case when a.dateout >= dateadd(month, 1, d.dte) then eomonth(d.dte) else dateout end)
) + 1
else 0
end) as z_cardays
from (select d.*, day(eomonth(dte)) as days_in_month
from dates d
) d left join
table_a a
on a.datein < dateadd(month, 1, d.dte) and a.dateout >= d.dte
group by d.dte
order by d.dte;
Overlaps with dates are a bit tricky, but you definitely want to do this with dates.
Note this doesn't calculate the average per day. You can divide by d.days_in_month if you want the average.
Here is a revised db<>fiddle.
This below script will only work if all your DateIn and DateOut for a particular record is in a single year like 2018.
Leap year Year is not considered but can be implemented.
WITH Month_Wise_Day AS
(
-- Listing/selecting 12 month manually here
-- With number of day for that month
SELECT 1 M, 31 ND UNION ALL
SELECT 2 M, 28 UNION ALL SELECT 3 M,31 UNION ALL SELECT 4 M,30 UNION ALL SELECT 5 M,31 UNION ALL
SELECT 6 M,30 UNION ALL SELECT 7 M,31 UNION ALL SELECT 8 M,31 UNION ALL SELECT 9 M,30 UNION ALL
SELECT 10 M,31 UNION ALL SELECT 11 M,30 UNION ALL SELECT 12 M,31
)
SELECT A.Year, A.Month,
SUM(CASE WHEN A.Garage = 'X' THEN A.No_of_days ELSE 0 END) AS X,
SUM(CASE WHEN A.Garage = 'Y' THEN A.No_of_days ELSE 0 END) AS Y,
SUM(CASE WHEN A.Garage = 'Z' THEN A.No_of_days ELSE 0 END) AS Z
FROM
(
SELECT A.*,
B.M AS Month,
YEAR(A.DateIn) AS Year,
CASE
WHEN MONTH(A.DateIn) = MONTH(A.DateOut) THEN DATEDIFF(DD,DateIn,DateOut) +1
WHEN B.M = MONTH(DateIn) THEN B.ND - DAY(DateIn)+1
WHEN B.M = MONTH(DateOut) THEN DAY(DateOut)
ELSE ND
END No_of_days
FROM TABLE_A A
INNER JOIN Month_Wise_Day B ON B.M BETWEEN MONTH(DateIn) AND MONTH (DateOut)
)A
GROUP BY A.Year, A.Month
I was trying to implement a median from this solution (among others, but this seemed the simplest Median code): Function to Calculate Median in Sql Server
However, I'm having difficulty in its application. This is my current SQL query. My goal is to find the Median TotalTimeOnCall for CallerComplaintTypeID on a given Week, Month, and Department. I think my biggest issue is that I'm just fundamentally not understanding how to apply this Median function to achieve my results.
For example, if I needed an Average, instead, I could just change that ORDER BY to a GROUP BY and then slap an AVG(TotalTimeOnCall) instead. How do I accomplish this idea with this Median solution, instead?
This is the "raw data" query:
WITH rawData as (
SELECT
DepartmentName
,MONTH(PlacedOnLocal) AS MonthNumber
,CASE
WHEN Datepart(day, PlacedOnLocal) < 8 THEN '1'
WHEN Datepart(day, PlacedOnLocal) < 15 THEN '2'
WHEN Datepart(day, PlacedOnLocal) < 22 THEN '3'
WHEN Datepart(day, PlacedOnLocal) < 29 THEN '4'
ELSE '5'
END AS WeekNumber
,CallerComplaintTypeID
,TotalTimeOnCall
FROM [THE_RELEVANT_TABLE]
WHERE PlacedOnLocal BETWEEN '2014-09-01' AND '2014-12-31'
AND CallerComplaintTypeID IN (5,89,9,31,203)
AND TotalTimeOnCall IS NOT NULL
)
SELECT
DepartmentName,
MonthNumber,
WeekNumber,
CallerComplaintTypeID,
TotalTimeOnCall
FROM
rawData
ORDER BY DepartmentName, MonthNumber, WeekNumber, CallerComplaintTypeID
with this sample output:
DepartmentName MonthNumber WeekNumber CallerComplaintTypeID TotalTimeOnCall
Dept_01 9 1 5 654
Dept_01 9 1 5 156
Dept_01 9 1 5 21
Dept_01 9 1 5 67
Dept_01 9 1 5 13
Dept_01 9 1 5 97
Dept_01 9 1 5 87
Dept_01 9 1 5 16
this is the Median solution from above:
SELECT
(
(
SELECT MAX(TotalTimeOnCall)
FROM
(
SELECT TOP 50 PERCENT TotalTimeOnCall
FROM rawData
WHERE TotalTimeOnCall IS NOT NULL
ORDER BY TotalTimeOnCall
) AS BottomHalf
)
+
(
SELECT MIN(TotalTimeOnCall)
FROM
(
SELECT TOP 50 PERCENT TotalTimeOnCall
FROM rawData
WHERE TotalTimeOnCall IS NOT NULL
ORDER BY TotalTimeOnCall DESC
) AS TopHalf
)
) / 2 AS Median
Here is a simple median solution that allows you to get a median per group.
-- Example of how to get median from a set of data
;with cte_my_query as (
-- this cte simulates the query that would return your data
select '2016-01-01' as dt, 1 as val
union
select '2016-01-01' as dt, 10 as val
union
select '2016-01-01' as dt, 7 as val
union
select '2016-01-01' as dt, 16 as val
union
select '2016-01-01' as dt, 11 as val
union
select '2016-01-01' as dt, 2 as val
union
select '2016-01-01' as dt, 5 as val
union
select '2016-01-02' as dt, 6 as val
union
select '2016-01-02' as dt, 13 as val
union
select '2016-01-02' as dt, 7 as val
union
select '2016-01-02' as dt, 9 as val
union
select '2016-01-02' as dt, 18 as val
)
,cte_dates as (
-- get the distinct key we want to get median for
select distinct dt from cte_my_query
)
select dt, median.val
from cte_dates
cross apply (
-- of the top 50% (below), take the top 1, desc, which is the median value
select top 1 val from (
-- for each date, get the top 50% of the values
select top 50 percent val
from cte_my_query
where cte_dates.dt = cte_my_query.dt
order by dt
) as inner_median
order by inner_median.val desc
) median
Following is the data.
select * from (
select to_date('20140601','YYYYMMDD') log_date, null weight from dual
union
select to_date('20140601','YYYYMMDD')+1 log_date, 0 weight from dual
union
select to_date('20140601','YYYYMMDD')+2 log_date, 4 weight from dual
union
select to_date('20140601','YYYYMMDD')+3 log_date, 4 weight from dual
union
select to_date('20140601','YYYYMMDD')+4 log_date, null weight from dual
union
select to_date('20140601','YYYYMMDD')+5 log_date, 8 weight from dual);
Log_date weight avg_weight
----------------------------------
6/1/2014 NULL 0 (0/1) Since no previous data, I consider it as 0
6/2/2014 0 0 ((0+0)/2)
6/3/2014 4 4/3 ((0+0+4)/3)
6/4/2014 4 2 (0+0+4+4)/4
6/5/2014 NULL 2 (0+0+4+4+2)/5 Since it is NULL I want to take previous day avg = 2
6/6/2014 8 3 (0+0+4+4+2+8)/6 =3
So the average for the above data should be 3.
How can I achieve this in SQL instead of PLSQL. Appreciate any help on this.
I just learned how to use recursive CTEs today, really excited! Hope this helps...
; WITH RawData (log_Date, Weight) AS (
select cast('2014-06-01' as SMALLDATETIME)+0, null
UNION ALL select cast('2014-06-01' as SMALLDATETIME)+1, 0
UNION ALL select cast('2014-06-01' as SMALLDATETIME)+2, 4
UNION ALL select cast('2014-06-01' as SMALLDATETIME)+3, 4
UNION ALL select cast('2014-06-01' as SMALLDATETIME)+4, null
UNION ALL select cast('2014-06-01' as SMALLDATETIME)+5, 8
)
, IndexedData (Id, log_Date, Weight) AS (
SELECT ROW_NUMBER() OVER (ORDER BY log_Date)
, log_Date
, Weight
FROM RawData
)
, ResultData (Id, log_Date, Weight, total, avg_weight) AS (
SELECT Id
, log_Date
, Weight
, CAST(CASE WHEN Weight IS NULL THEN 0 ELSE Weight END AS FLOAT)
, CAST(CASE WHEN Weight IS NULL THEN 0 ELSE Weight END AS FLOAT)
FROM IndexedData
WHERE Id = 1
UNION ALL
SELECT i.Id
, i.log_Date
, i.Weight
, CAST(r.total + CASE WHEN i.Weight IS NULL THEN r.avg_weight ELSE i.Weight END AS FLOAT)
, CAST(r.total + CASE WHEN i.Weight IS NULL THEN r.avg_weight ELSE i.Weight END AS FLOAT) / i.Id
FROM ResultData r
JOIN IndexedData i ON i.Id = r.Id + 1
)
SELECT Log_Date, Weight, avg_weight FROM ResultData
OPTION (MAXRECURSION 0)
This gives the output:
Log_Date Weight avg_weight
----------------------- ----------- ----------------------
2014-06-01 00:00:00 NULL 0
2014-06-02 00:00:00 0 0
2014-06-03 00:00:00 4 1.33333333333333
2014-06-04 00:00:00 4 2
2014-06-05 00:00:00 NULL 2
2014-06-06 00:00:00 8 3
Note that in my answer, I modified the "Data" section of your question as it didn't compile for me. It's still the same data though, hope it helps.
Edit: By default, MAXRECURSION is set to 100. This means that the query will not work for more than 101 rows of Raw Data. By adding the OPTION (MAXRECURSION 0), I have removed this limit so that the query works for all input data. However, this can be dangerous if the query isn't tested thoroughly because it might lead to infinite recursion.
I have a table sales with columns
Month SalesAmount
--------------------------
4 50000
5 60000
6 70000
7 50000
8 60000
9 40000
I want result like this
From Month To Month Result
-----------------------------------------------
4 6 Increasing
6 7 Decreasing
7 8 Increasing
8 9 Decreasing
without using a cursor
Try this. Basically, you need to join the table to itself by the month (+1), then pull the data you want/perform any calcs.
Select
M1.Month as [From],
M2.Month as [To],
Case
When M2.SalesAmount > M1.SalesAmount Then 'Increasing'
When M2.SalesAmount < M1.SalesAmount Then 'Decreasing'
Else 'Holding Steady'
End
From sales M1
Inner Join sales M2 on M2.Month = M1.Month + 1
This works if you want the breakdown month by month. However, your example data set compresses months 4-6. Without more details on how you determine what to compress, I'm going to make the following assumptions:
You want detailed data for the last 3 periods, and a compressed summary of all other periods.
You wish only the overall trend between the first month and the last month inside the compressed period. i.e. you want to know the difference between the first, and the last month values.
To do that, the query starts to get more complicated. I've done it with two Unioned queries:
With
compressed_range as
( select min([Month]) as min_month, max([Month]) - 3 as max_month from sales )
Select
M1.[Month] as [From],
M2.[Month] as [To],
Case
When M2.SalesAmount > M1.SalesAmount Then 'Increasing'
When M2.SalesAmount < M1.SalesAmount Then 'Decreasing'
Else 'Holding Steady'
End
From sales M1
Inner Join sales M2 on M2.[Month] = ( select max_month from compressed_range )
Where M1.Month = ( select min_month from compressed_range )
Union All
Select
M1.Month as [From],
M2.Month as [To],
Case
When M2.SalesAmount > M1.SalesAmount Then 'Increasing'
When M2.SalesAmount < M1.SalesAmount Then 'Decreasing'
Else 'Holding Steady'
End
From sales M1
Inner Join sales M2 on M2.Month = M1.Month + 1
Where M2.Month >= (Select max_month + 1 from compressed_range)
This gives your desired result:
DECLARE #T TABLE (Month INT, SalesAmount MONEY);
INSERT #T
VALUES (4, 50000), (5, 60000), (6, 70000), (7, 50000), (8, 60000), (9, 40000);
WITH CTE AS
( SELECT FromMonth = T2.Month,
ToMonth = T.Month,
Result = CASE T2.Result
WHEN -1 THEN 'Decreasing'
WHEN 0 THEN 'Static'
WHEN 1 THEN 'Increasing'
END,
GroupingSet = ROW_NUMBER() OVER(ORDER BY T.Month) - ROW_NUMBER() OVER(PARTITION BY T2.Result ORDER BY T.Month)
FROM #T T
CROSS APPLY
( SELECT TOP 1
T2.SalesAmount,
T2.Month,
Result = SIGN(T.SalesAmount - T2.SalesAmount)
FROM #T T2
WHERE T2.Month < T.Month
ORDER BY T2.Month DESC
) T2
)
SELECT FromMonth = MIN(FromMonth),
ToMonth = MAX(ToMonth),
Result
FROM CTE
GROUP BY Result, GroupingSet
ORDER BY FromMonth;
The first stage is to get the sales amount for the previous month each time:
SELECT *
FROM #T T
CROSS APPLY
( SELECT TOP 1
T2.SalesAmount,
T2.Month,
Result = SIGN(T.SalesAmount - T2.SalesAmount)
FROM #T T2
WHERE T2.Month < T.Month
ORDER BY T2.Month DESC
) T2
ORDER BY T.MONTH
Will Give:
Month SalesAmount SalesAmount Month Result
5 60000.00 50000.00 4 1.00
6 70000.00 60000.00 5 1.00
7 50000.00 70000.00 6 -1.00
8 60000.00 50000.00 7 1.00
9 40000.00 60000.00 8 -1.00
Where Result is just an indicator of whether or not the amount has increased or decreased. You then need to apply an ordering trick whereby each member of a sequence - it's postion in the sequence is constant for sequential members. So with the above data set if we added:
RN1 = ROW_NUMBER() OVER(ORDER BY T.Month),
RN2 = ROW_NUMBER() OVER(PARTITION BY T2.Result ORDER BY T.Month)
Month SalesAmount SalesAmount Month Result RN1 RN2 | RN1 - RN2
5 60000.00 50000.00 4 1.00 1 1 | 0
6 70000.00 60000.00 5 1.00 2 2 | 0
7 50000.00 70000.00 6 -1.00 3 1 | 2
8 60000.00 50000.00 7 1.00 4 3 | 1
9 40000.00 60000.00 8 -1.00 5 2 | 3
So you can see for the first 2 rows the final column RN1 - RN2 remains the same as they are both increasing, then when the result changes, the difference between these two row_numbers chnages, so creates a new group.
You can then group by this calculation (the GroupingSet column in the original query), to group your consecutive periods of increase and decrease together.
Example on SQL Fiddle
If you are using only month no in your table structure, you can try something like this
SELECT s1.month AS From_Month,
s2.month AS To_Month,
CASE
WHEN s2.salesamount > s1.salesamount THEN 'Increasing'
ELSE 'Decresing'
END AS res
FROM sales AS s1,
sales AS s2
WHERE s1.month + 1 = s2.month
demo at http://sqlfiddle.com/#!6/0819d/11