I have a table with around 80000 records. Here is an example:
startyear
division
account
budget
forecast
2021
40
4100
5122952,22
0,012306656
2022
40
4100
0
0,011424198
2023
40
4100
0
0,010431491
2024
40
4100
0
0,009311863
2025
40
4100
0
0,008324122
2026
40
4100
0
0,007763793
2027
40
4100
0
0,007557735
2028
40
4100
0
0,007357883
2029
40
4100
0
0,007160051
2030
40
4100
0
0,006953345
2031
40
4100
0
0,006737952
2032
40
4100
0
0,006535297
2033
40
4100
0
0,006364179
2034
40
4100
0
0,006213237
2035
40
4100
0
0,006085724
2036
40
4100
0
0,005944279
2037
40
4100
0
0,005758285
2038
40
4100
0
0,005559474
2039
40
4100
0
0,005360105
2040
40
4100
0
0,005163794
2041
40
4100
0
0,004972228
I'm trying to calculate a new column starting with the budget from line 1. So year 2021 will be the same. the first calculation is correct. But the rest of the calculation ignores the changes before.
This is the code:
WITH cte AS (
SELECT startyear, division, account, forecast, SUM(budget) AS asum
FROM #table
GROUP BY startyear,
division,
account,
forecast
), cteRanked AS (
SELECT cte.startyear,
cte.division,
cte.account,
cte.forecast,
cte.asum AS asum
FROM cte
)
SELECT c1.startyear,
c1.division,
c1.account,
c1.forecast,
c1.asum , ISNULL((SELECT SUM(c2.asum)*c1.forecast
FROM cteRanked c2
WHERE c1.startyear > 2021
AND c2.startyear < c1.startyear
AND c2.division = c1.division
AND c2.account = c1.account), c1.asum) AS Bud
FROM cteRanked c1
ORDER BY c1.division, c1.account, c1.startyear
Gives this result:
startyear
division
account
forecast
budget
result
correct
2021
40
4100
0,012306656
5122952,22
5122952,22
5122952,22
2022
40
4100
0,011424198
0
58525,62051
58525,62
2023
40
4100
0,010431491
0
53440,02998
54050,54
2024
40
4100
0,009311863
0
47704,22923
48752,52
2025
40
4100
0,008324122
0
42644,07928
43987,00
2026
40
4100
0,007763793
0
39773,54059
41367,57
As you can see this query does not cumulate the results when the query runs.
Anyone have any ideas?
While your post is lacking some vital information...I don't necessarily blame you because it's a difficult problem to both explain and to solve.
It looks like you're basically trying to come up with a compounding interest calculator. Except in this case, the rate changes every year.
To calculate the PRODUCT aggregate of your forecasts, I found this blog post:
https://blog.jooq.org/2018/09/21/how-to-write-a-multiplication-aggregate-function-in-sql/
It just required a very tiny bit of tweaking.
This is my answer:
DECLARE #StartingYear int = 2021,
#StartingBudget decimal(12, 2);
SELECT #StartingBudget = yt.budget
FROM #YourTable yt
WHERE yt.startyear = #StartingYear
SELECT yt.startyear, yt.division, yt.account, yt.budget, yt.forecast, YearBudget = yt.budget, PrevYearDiff = yt.budget
FROM #YourTable yt
WHERE yt.startyear = #StartingYear
UNION ALL
SELECT x.startyear, x.division, x.account, x.budget, x.forecast
, YearBudget = CONVERT(decimal(10,2), x.SumProdBudget)
, PrevYearDiff = CONVERT(decimal(10,2), x.SumProdBudget - LAG(x.SumProdBudget,1,#StartingBudget) OVER (ORDER BY x.startyear))
FROM (
SELECT yt.startyear, yt.division, yt.account, yt.budget, yt.forecast
, SumProdBudget = EXP(SUM(LOG(1+yt.forecast)) OVER (ORDER BY yt.startyear)) * #StartingBudget
FROM #YourTable yt
WHERE yt.startyear > #StartingYear
) x
Returns:
| startyear | division | account | budget | forecast | YearBudget | PrevYearDiff |
|-----------|----------|---------|------------|-------------|------------|--------------|
| 2021 | 40 | 4100 | 5122952.22 | 0.012306656 | 5122952.22 | 5122952.22 |
| 2022 | 40 | 4100 | 0.00 | 0.011424198 | 5181477.84 | 58525.62 |
| 2023 | 40 | 4100 | 0.00 | 0.010431491 | 5235528.38 | 54050.54 |
| 2024 | 40 | 4100 | 0.00 | 0.009311863 | 5284280.90 | 48752.52 |
| 2025 | 40 | 4100 | 0.00 | 0.008324122 | 5328267.90 | 43987.00 |
| 2026 | 40 | 4100 | 0.00 | 0.007763793 | 5369635.47 | 41367.57 |
The key to this whole thing is this line:
SELECT SumProdBudget = EXP(SUM(LOG(1+yt.forecast)) OVER (ORDER BY yt.startyear)) * #StartingBudget
This is saying to return a running product of all previous forecasts, then multiply that by the original budget. This will produce the budget for each year, technically based on the budget of the previous year.
Then once you have that, I stuck it in a sub-query to find the difference between the year and its previous year.
Sample Data:
IF OBJECT_ID('tempdb..#YourTable','U') IS NOT NULL DROP TABLE #YourTable; --SELECT * FROM #YourTable
CREATE TABLE #YourTable (
startyear int NOT NULL,
division int NOT NULL,
account int NOT NULL,
budget decimal(12, 2) NOT NULL,
forecast decimal(10, 9) NOT NULL,
);
INSERT INTO #YourTable (startyear, division, account, budget, forecast)
VALUES (2021, 40, 4100, 5122952.22, 0.012306656)
, (2022, 40, 4100, 0 , 0.011424198)
, (2023, 40, 4100, 0 , 0.010431491)
, (2024, 40, 4100, 0 , 0.009311863)
, (2025, 40, 4100, 0 , 0.008324122)
, (2026, 40, 4100, 0 , 0.007763793)
, (2027, 40, 4100, 0 , 0.007557735)
, (2028, 40, 4100, 0 , 0.007357883)
, (2029, 40, 4100, 0 , 0.007160051)
, (2030, 40, 4100, 0 , 0.006953345)
, (2031, 40, 4100, 0 , 0.006737952)
, (2032, 40, 4100, 0 , 0.006535297)
, (2033, 40, 4100, 0 , 0.006364179)
, (2034, 40, 4100, 0 , 0.006213237)
, (2035, 40, 4100, 0 , 0.006085724)
, (2036, 40, 4100, 0 , 0.005944279)
, (2037, 40, 4100, 0 , 0.005758285)
, (2038, 40, 4100, 0 , 0.005559474)
, (2039, 40, 4100, 0 , 0.005360105)
, (2040, 40, 4100, 0 , 0.005163794)
, (2041, 40, 4100, 0 , 0.004972228);
Related
Please consider this table:
Year Month Value YearMonth
2011 1 70 201101
2011 1 100 201101
2011 2 200 201102
2011 2 50 201102
2011 3 80 201103
2011 3 250 201103
2012 1 100 201201
2012 2 200 201202
2012 3 250 201203
I want to get a cumulative sum based on each year. For the above table I want to get this result:
Year Month Sum
-----------------------
2011 1 170
2011 2 420 <--- 250 + 170
2011 3 750 <--- 330 + 250 + 170
2012 1 100
2012 2 300 <--- 200 + 100
2012 3 550 <--- 250 + 200 + 100
I wrote this code:
Select c1.YearMonth, Sum(c2.Value) CumulativeSumValue
From #Tbl c1, #Tbl c2
Where c1.YearMonth >= c2.YearMonth
Group By c1.YearMonth
Order By c1.YearMonth Asc
But its CumulativeSumValue is calculated twice for each YearMonth:
YearMonth CumulativeSumValue
201101 340 <--- 170 * 2
201102 840 <--- 420 * 2
201103 1500
201201 850
201202 1050
201203 1300
How can I achieve my desired result?
I wrote this query:
select year, (Sum (aa.[Value]) Over (partition by aa.Year Order By aa.Month)) as 'Cumulative Sum'
from #Tbl aa
But it returned multiple records for 2011:
Year Cumulative Sum
2011 170
2011 170
2011 420
2011 420
2011 750
2011 750
2012 100
2012 300
2012 550
You are creating a cartesian product here. In your ANSI-89 implicit JOIN (you really need to stop using those and switch to ANSI-92 syntax) you are joining on c1.YearMonth >= c2.YearMonth.
For your first month you have two rows with the same value of the year and month, so each of those 2 rows joins to the other 2; this results in 4 rows:
Year
Month
Value1
Value2
2011
1
70
70
2011
1
70
100
2011
1
100
70
2011
1
100
100
When you SUM this value you get 340, not 170, as you have 70+70+100+100.
Instead of a triangular JOIN however, you should be using a windowed SUM. As you want to also get aggregate nmonths into a single rows, you'll need to also aggregate inside the windowed SUM like so:
SELECT V.YearMonth,
SUM(SUM(V.Value)) OVER (PARTITION BY Year ORDER BY V.YearMonth) AS CumulativeSum
FROM (VALUES (2011, 1, 70, 201101),
(2011, 1, 100, 201101),
(2011, 2, 200, 201102),
(2011, 2, 50, 201102),
(2011, 3, 80, 201103),
(2011, 3, 250, 201103),
(2012, 1, 100, 201201),
(2012, 2, 200, 201202),
(2012, 3, 250, 201203)) V (Year, Month, Value, YearMonth)
GROUP BY V.YearMonth,
V.Year;
This is an SQL question.
I have a column of numbers which can be positive or negative, and I'm trying to figure out a way to have a running sum of the column, but where the total cannot go below zero.
Date | Number | Desired | Actual
2020-01-01 | 8 | 8 | 8
2020-01-02 | 11 | 19 | 19
2020-01-03 | 30 | 49 | 49
2020-01-04 | -10 | 39 | 39
2020-01-05 | -12 | 27 | 27
2020-01-06 | -9 | 18 | 18
2020-01-07 | -26 | 0 | -8
2020-01-08 | 5 | 5 | -3
2020-01-09 | -23 | 0 | -26
2020-01-10 | 12 | 12 | -14
2020-01-11 | 14 | 26 | 0
I have tried a number of different window functions on this, but haven't found a way to prevent the running total from going into negative numbers.
Any help would be greatly appreciated.
EDIT - Added a date column to indicate the ordering
Unfortunately, there is no way to do this without cycling through the records one-by-one. That, in turn, requires something like a recursive CTE.
with t as (
select t.*, row_number() over (order by date) as seqnum
from mytable t
),
cte as (
select NULL as number, 0 as desired, 0 as seqnum
union all
select t.number,
(case when cte.desired + t.number < 0 then 0
else cte.desired + t.number
end),
cte.seqnum + 1
from cte join
t
on t.seqnum = cte.seqnum + 1
)
select cte.*
from cte
where cte.number is not null;
I would recommend this approach only if your data is rather small. But then again, if you have to do this, there are not many alternatives other then going through the table row-by-agonizing-row.
Here is a db<>fiddle (using Postgres).
You can use a CASE operator and the SIGN function to do so…
CASE SIGN(my computed expression) WHEN -1 THEN 0 ELSE my computed expression END AS Actual
This can be done via a USER DEFINE TABLE FUNCTION to "manage" the state you want to carry
CREATE OR REPLACE FUNCTION non_neg_sum(val float) RETURNS TABLE (out_sum float)
LANGUAGE JAVASCRIPT AS
'{
processRow: function (row, rowWriter) {
this.sum += row.VAL;
if(this.sum < 0)
this.sum = 0;
rowWriter.writeRow({OUT_SUM: this.sum})
},
initialize: function() {
this.sum = 0;
}
}';
And used like so:
WITH input AS
(
SELECT *
FROM VALUES ('2020-01-01', 8, 8),
('2020-01-02', 11, 19 ),
('2020-01-03', 30, 49 ),
('2020-01-04',-10, 39 ),
('2020-01-05',-12, 27 ),
('2020-01-06', -9, 18 ),
('2020-01-07',-26, 0 ),
('2020-01-08', 5, 5 ),
('2020-01-09',-23, 0 ),
('2020-01-10', 12, 12 ),
('2020-01-11', 14, 26 ) d(day,num,wanted)
)
SELECT d.*
,sum(d.num)over(order by day) AS simple_sum
,j.*
FROM input AS d,
TABLE(non_neg_sum(d.num::float) OVER (ORDER BY d.day)) j
ORDER BY day
;
gives the results:
DAY NUM WANTED SIMPLE_SUM OUT_SUM
2020-01-01 8 8 8 8
2020-01-02 11 19 19 19
2020-01-03 30 49 49 49
2020-01-04 -10 39 39 39
2020-01-05 -12 27 27 27
2020-01-06 -9 18 18 18
2020-01-07 -26 0 -8 0
2020-01-08 5 5 -3 5
2020-01-09 -23 0 -26 0
2020-01-10 12 12 -14 12
2020-01-11 14 26 0 26
Another UDF solution:
select d, x, conditional_sum(x) from values
('2020-01-01', 8),
('2020-01-02', 11),
('2020-01-03', 30),
('2020-01-04', -10),
('2020-01-05', -12),
('2020-01-06', -9),
('2020-01-07', -26),
('2020-01-08', 5),
('2020-01-09', -23),
('2020-01-10', 12),
('2020-01-11', 14)
t(d,x)
order by d;
where conditional_sum is defined as:
create or replace function conditional_sum(X float)
returns float
language javascript
volatile
as
$$
if (!('sum' in this)) this.sum = 0
return this.sum = (X+this.sum)<0 ? 0 : this.sum+X
$$;
Demo :
WITH input AS
( SELECT *
FROM (VALUES
('2020-01-01', 8, 8),
('2020-01-02', 11, 19 ),
('2020-01-03', 30, 49 ),
('2020-01-04',-10, 39 ),
('2020-01-05',-12, 27 ),
('2020-01-06', -9, 18 ),
('2020-01-07',-26, 0 ),
('2020-01-08', 5, 5 ),
('2020-01-09',-23, 0 ),
('2020-01-10', 12, 12 ),
('2020-01-11', 14, 26 ),
('2020-01-12', 3, 26 )) AS d (day,num,wanted)
)
SELECT *, sum(num)over(order by day) AS CUM_SUM,
CASE SIGN(sum(num)over(order by day))
WHEN -1 THEN 0
ELSE sum(num)over(order by day)
END AS Actual
FROM input
ORDER BY day;
Return :
day num wanted CUM_SUM Actual
---------- ----------- ----------- ----------- -----------
2020-01-01 8 8 8 8
2020-01-02 11 19 19 19
2020-01-03 30 49 49 49
2020-01-04 -10 39 39 39
2020-01-05 -12 27 27 27
2020-01-06 -9 18 18 18
2020-01-07 -26 0 -8 0
2020-01-08 5 5 -3 0
2020-01-09 -23 0 -26 0
2020-01-10 12 12 -14 0
2020-01-11 14 26 0 0
2020-01-12 3 26 3 3
I add one more row to your test values… to demonstrate the final conditionnal sum is 3
In order to verify if Deliveries are done on time, I need to match delivery Documents to PO schedule lines (SchLin) based on the comparison between Required Quantity (ReqQty) and Delivered Quantity (DlvQty).
The Delivery Docs have a reference to the PO and POItm but not to the SchLin.
Once a Delivery Doc is assigned to a Schedule Line I can calculate the Delivery Delta (DlvDelta) as the number of days it was delivered early or late compared to the requirement (ReqDate).
Examples of the two base tables are as follows:
Schedule lines
PO POItm SchLin ReqDate ReqQty
123 1 1 10/11 20
123 1 2 30/11 30
124 2 1 15/12 10
124 2 2 24/12 15
Delivery Docs
Doc Item PO POItm DlvDate DlvQty
810 1 123 1 29/10 12
816 1 123 1 02/11 07
823 1 123 1 04/11 13
828 1 123 1 06/11 08
856 1 123 1 10/11 05
873 1 123 1 14/11 09
902 1 124 2 27/11 05
908 1 124 2 30/11 07
911 1 124 2 08/12 08
923 1 124 2 27/12 09
Important: Schedule Lines and Deliveries should have the same PO and POItm.
The other logic to link is to sum the DlvQty until we reach (or exceed) ReqQty.
Those deliveries are then linked to the schedule line. Subsequent deliveries are used for the following schedule line(s). A delivery schould be matched to only one schedule line.
After comparing the ReqQty and DlvQty the assignments should result in following:
Result
Doc Item PO POItm Schlin ReqDate DlvDate DlvDelta
810 1 123 1 1 10/11 29/10 -11
816 1 123 1 1 10/11 02/11 -08
823 1 123 1 1 10/11 04/11 -06
828 1 123 1 2 30/11 06/11 -24
856 1 123 1 2 30/11 10/11 -20
873 1 123 1 2 30/11 14/11 -16
902 1 124 2 1 15/12 27/11 -18
908 1 124 2 1 15/12 30/11 -15
911 1 124 2 2 24/12 08/12 -16
923 1 124 2 2 24/12 27/12 +03
Up till now, I have done this with loops using cursors but performance is rather sluggish.
Is there another way in SQL (script) using e.g. joins by comparing measures to achieve the same result?
Regards,
Eric
If you can express the rule for matching a delivery with a schedule line, you can produce the results you want in a single query. And, yes, I promise it will be faster (and simpler) than executing the same logic in loops on cursors.
I can't reproduce your exact results because I don't quite understand how the two tables relate. Hopefully from the code below you'll be able to figure it out by adjusting the join criteria.
I don't have your DBMS. My code uses SQLite, which has its own peculiar date functions. You'll have to substitute the ones your system provides. In any event, I can't recommend 5-character strings for dates. Use a datetime type if you have one, and include 4-digit years regardless. Else how many days are there between Christmas and New Years Day?
create table S (
PO int not NULL,
POItm int not NULL,
SchLin int not NULL,
ReqDate char not NULL,
ReqQty int not NULL,
primary key (PO, POItm, SchLin)
);
insert into S values
(123, 1, 1, '10/11', 20 ),
(123, 1, 2, '30/11', 30 ),
(124, 2, 1, '15/12', 10 ),
(124, 2, 2, '24/12', 15 );
create table D (
Doc int not NULL,
Item int not NULL,
PO int not NULL,
POItm int not NULL,
DlvDate char not NULL,
DlvQty int not NULL,
primary key (Doc, Item)
);
insert into D values
(810, 1, 123, 1, '29/10', 12 ),
(816, 1, 123, 1, '02/11', 07 ),
(823, 1, 123, 1, '04/11', 13 ),
(828, 1, 123, 1, '06/11', 08 ),
(856, 1, 123, 1, '10/11', 05 ),
(873, 1, 123, 1, '14/11', 09 ),
(902, 1, 124, 2, '27/11', 05 ),
(908, 1, 124, 2, '30/11', 07 ),
(911, 1, 124, 2, '08/12', 08 ),
(923, 1, 124, 2, '27/12', 09 );
select D.Doc, D.Item, D.PO, S.SchLin, S.ReqDate, D.DlvDate
, cast(
julianday('2018-' || substr(DlvDate, 4,2) || '-' || substr(DlvDate, 1,2))
- julianday('2018-' || substr(ReqDate, 4,2) || '-' || substr(ReqDate, 1,2))
as int) as DlvDelta
from S join D on S.PO = D.PO and S.POItm = D.POItm
;
Result:
Doc Item PO SchLin ReqDate DlvDate DlvDelta
---------- ---------- ---------- ---------- ---------- ---------- ----------
810 1 123 1 10/11 29/10 -12
810 1 123 2 30/11 29/10 -32
816 1 123 1 10/11 02/11 -8
816 1 123 2 30/11 02/11 -28
823 1 123 1 10/11 04/11 -6
823 1 123 2 30/11 04/11 -26
828 1 123 1 10/11 06/11 -4
828 1 123 2 30/11 06/11 -24
856 1 123 1 10/11 10/11 0
856 1 123 2 30/11 10/11 -20
873 1 123 1 10/11 14/11 4
873 1 123 2 30/11 14/11 -16
902 1 124 1 15/12 27/11 -18
902 1 124 2 24/12 27/11 -27
908 1 124 1 15/12 30/11 -15
908 1 124 2 24/12 30/11 -24
911 1 124 1 15/12 08/12 -7
911 1 124 2 24/12 08/12 -16
923 1 124 1 15/12 27/12 12
923 1 124 2 24/12 27/12 3
i want to convert this query to start GRP to End Grp and Start Date to End date.
select * from (
select z.ZoneGroupId, zs.ObjectId,z.Name,case when zs.Inside=0 then 'Left' else 'Entered' end Crossing ,zs.TimeFirst
from zs join
z on zs.Zid=z.Zid
where zs.ObjectId=5696 and z.ZoneGroupId in (1095,1096) and convert(date,zs.TimeFirst)>='2016/07/01' ) s
where (ZoneGroupId=1096 and s.Crossing='Entered') or (ZoneGroupId=1095 and s.Crossing='Left')
2 table involve in query
table Z
Zid(int) Name(varchar) ZoneGroupId (int)
59 Oil 1095
60 ENR 1096
61 NRL 1096
table Zs
zsid(int) zid(int) ObjectId(int) Timefirst(datetime) Inside(boolan)
1 60 1988 2016-07-01 00:39 1
2 59 1988 2016-07-05 15:47 0
3 61 1988 2016-07-06 22:54 1
4 59 1988 2016-07-09 13:40 0
5 60 1988 2016-07-10 07:58 1
6 59 1988 2016-07-13 10:30 0
7 59 1988 2016-09-10 10:21 0
8 59 1990 2016-07-05 15:47 0
9 61 1990 2016-07-06 22:54 1
the result which i required from above query
ZoneGroupId ObjectId Name Crossing TimeFirst
1096 1988 ENR Entered 2016-07-01 00:39
1095 1988 Oil Left 2016-07-05 15:47
1096 1988 NRL Entered 2016-07-06 22:54
1095 1988 Oil Left 2016-07-09 13:40
1096 1988 ENR Entered 2016-07-10 07:58
1095 1988 Oil Left 2016-07-13 10:30
1095 1988 Oil Left 2016-09-10 10:21
1095 1990 Oil Left 2016-07-05 15:47
1096 1990 NRL Entered 2016-07-06 22:54
required result
ObjectId StartName StartDate EndName EndDate
1988 Null Null ENR 2016-07-01 00:39
1988 Oil 2016-07-05 15:47 NRL 2016-7-06 22:54
1988 Oil 2016-07-09 13:40 ENR 2016-07-10 07:58
1988 Oil 2016-07-13 10:30 Null Null
1988 Oil 2016-09-10 10:21 Null Null
1988 Oil 2016-07-05 15:47 NRL 2016-7-06 22:54
First your sample data - please include this in your question in future.
declare #z table (Zid int, Name nvarchar(3), ZoneGroupId int);
insert into #z values
(59, 'Oil', 1095),
(60, 'ENR', 1096),
(61, 'NRL', 1096);
declare #zs table(zsid int, zid int, ObjectId int, Timefirst datetime, Inside bit);
insert into #zs values
( 1, 60, 1988, '2016-07-01 00:39', 1),
( 2, 59, 1988, '2016-07-05 15:47', 0 ),
( 3, 61, 1988, '2016-07-06 22:54', 1 ),
( 4, 59, 1988, '2016-07-09 13:40', 0 ),
( 5, 60, 1988, '2016-07-10 07:58', 1 ),
( 6, 59, 1988, '2016-07-13 10:30', 0 ),
( 7, 59, 1988, '2016-09-10 10:21', 0 ),
( 8, 59, 1990, '2016-07-05 15:47', 0 ),
( 9, 61, 1990, '2016-07-06 22:54', 1 );
Then, I split the data into 2 tables, left and entered by using common-table-expressions. Add in a row number to put them in order by time.
Join the tables back together with a full outer join (to get all rows from both sides). The specify the following criteria on the join:
ObjectIds match
Entered.TimeFirst is the smallest one that is bigger than Left.TimeFirst
Left.TimeFirst is the largest one that is smaller than entered.TimeFirst
I also added an order by for clarity. Here is the query.
with entered as (
select z.ZoneGroupId
, zs.ObjectId
, z.Name
, 'Entered' Crossing
, zs.TimeFirst
, ROW_NUMBER() over (partition by ObjectID order by zs.TimeFirst) row_no
from #zs zs
inner join #z z on zs.Zid=z.Zid
where zs.Inside <> 0 and (z.ZoneGroupId = 1096 and convert(date,zs.TimeFirst)>='2016/07/01')
), [left] as (
select z.ZoneGroupId
, zs.ObjectId
, z.Name
, 'Left' Crossing
, zs.TimeFirst
, ROW_NUMBER() over (partition by ObjectID order by zs.TimeFirst) row_no
from #zs zs
inner join #z z on zs.Zid=z.Zid
where zs.Inside = 0 and (z.ZoneGroupId = 1095 and convert(date,zs.TimeFirst)>='2016/07/01')
)
select *
from [left]
full outer join entered
on entered.row_no = (select MIN(row_no) from entered e
where e.TimeFirst > [left].TimeFirst
and e.ObjectId = [left].ObjectId)
and [left].row_no = (select max(row_no) from [left] l
where l.TimeFirst < entered.TimeFirst
and l.ObjectId = entered.ObjectId)
and [left].ObjectId = [entered].ObjectId
order by isnull([left].ObjectId,entered.ObjectId), ISNULL([left].TimeFirst,entered.TimeFirst)
I've seen a lot of "Pivot, No Agg" posts, but all of them seem to involve some pretty simple data to pivot, so the solutions work fairly well and easily. But how about when the data isn't as simple?
I'd like to turn this:
wwnID Tenant WeekOfTheMonth ReportingDate TotalEmployeesPerBranch TotalOpenCount TotalClosedCount OpenCount_TitleAndEscrow ClosedCount_TitleAndEscrow OpenCount_EscrowOnly ClosedCount_EscrowOnly OpenCount_PreListingTask ClosedCount_PreListingTask OFPE CFPE OpenCount_TitleOnly ClosedCount_TitleOnly CurrentBusinessDay TotalBusinessDaysMTD ReportingDateId CreatedDate
----------- -------------------------------------------------- -------------- ----------------------- --------------------------------------- -------------- ---------------- ------------------------ -------------------------- -------------------- ---------------------- ------------------------ -------------------------- --------------------------------------- --------------------------------------- ------------------- --------------------- ------------------ -------------------- --------------- -----------------------
3 King 1 2014-08-08 00:00:00.000 144.00 235 0 137 0 64 0 34 0 4.81 0.00 270 0 7 21 411 2014-09-05 08:53:11.313
5 King 2 2014-08-15 00:00:00.000 150.00 399 0 224 0 112 0 63 0 4.62 0.00 524 0 12 21 412 2014-09-05 08:53:19.573
7 King 3 2014-08-22 00:00:00.000 150.00 584 0 335 0 159 0 90 0 4.76 0.00 721 0 17 21 413 2014-09-05 08:53:26.980
9 King 4 2014-08-31 00:00:00.000 150.00 797 0 436 0 226 0 135 0 5.18 0.00 946 0 21 21 414 2014-09-05 08:53:35.593
4 Pierce 1 2014-08-08 00:00:00.000 21.00 85 0 31 0 39 0 15 0 12.00 0.00 54 0 7 21 411 2014-09-05 08:53:11.670
6 Pierce 2 2014-08-15 00:00:00.000 22.00 160 0 62 0 74 0 24 0 12.41 0.00 83 0 12 21 412 2014-09-05 08:53:20.000
8 Pierce 3 2014-08-22 00:00:00.000 22.00 222 0 82 0 107 0 33 0 12.41 0.00 127 0 17 21 413 2014-09-05 08:53:27.407
10 Pierce 4 2014-08-31 00:00:00.000 23.00 272 0 99 0 130 0 43 0 10.96 0.00 159 0 21 21 414 2014-09-05 08:53:36.063
into this:
Data Types Week 1 Week 2 Week 3 Week 4
------------------------------- ----------- ----------- ----------- -----------
Tenant King King King King
ReportingDate 8/8/2014 8/15/2014 8/22/2014 8/31/2014
TotalEmployeesPerBranch 144 150 150 150
TotalOpenCount 235 399 584 797
TotalClosedCount 0 0 0 0
OpenCount_TitleAndEscrow 137 224 335 436
ClosedCount_TitleAndEscrow 0 0 0 0
OpenCount_EscrowOnly 64 112 159 226
ClosedCount_EscrowOnly 0 0 0 0
OpenCount_PreListingTask 34 63 90 135
ClosedCount_PreListingTask 0 0 0 0
OFPE 4.81 4.62 4.76 5.18
CFPE 0 0 0 0
OpenCount_TitleOnly 270 524 721 946
ClosedCount_TitleOnly 0 0 0 0
CurrentBusinessDay 7 12 17 21
TotalBusinessDaysMTD 21 21 21 21
ReportingDateId 411 412 413 414
CreatedDate 9/5/2014 9/5/2014 9/5/2014 9/5/2014
Tenant Pierce Pierce Pierce Pierce
ReportingDate 8/8/2014 8/15/2014 8/22/2014 8/31/2014
TotalEmployeesPerBranch 21 22 22 23
TotalOpenCount 85 160 222 272
TotalClosedCount 0 0 0 0
OpenCount_TitleAndEscrow 31 62 82 99
ClosedCount_TitleAndEscrow 0 0 0 0
OpenCount_EscrowOnly 39 74 107 130
ClosedCount_EscrowOnly 0 0 0 0
OpenCount_PreListingTask 15 24 33 43
ClosedCount_PreListingTask 0 0 0 0
OFPE 12 12.41 12.41 10.96
CFPE 0 0 0 0
OpenCount_TitleOnly 54 83 127 159
ClosedCount_TitleOnly 0 0 0 0
CurrentBusinessDay 7 12 17 21
TotalBusinessDaysMTD 21 21 21 21
ReportingDateId 411 412 413 414
CreatedDate 9/5/2014 9/5/2014 9/5/2014 9/5/2014
I've tried several methods of pivoting, and none of them seem to do the trick, but if anyone out there knows a way to do it, that'd be fantastic!
Thanks ahead of time!
UPDATE
This works beautifully! (initial variable declarations are for the where clause toward the end)
DECLARE #ReportDate DATETIME = '2014-08-31';
DECLARE #RepDateId INT = ( SELECT MAX([WRD].[ReportingDateID])
FROM [SMS].[dbo].[WSOBReportingDates] AS WRD
WHERE ( [WRD].[ReportingDate] <= #ReportDate )
AND ( [WRD].[Submitted] = 1 ) );
DECLARE #WSOBRepDate DATETIME = ( SELECT [WRD].[ReportingDate]
FROM [SMS].[dbo].[WSOBReportingDates] AS WRD
WHERE [WRD].[ReportingDateID] = #RepDateId );
DECLARE #WSOBStartDate DATETIME = DATEADD(mm, DATEDIFF(mm, 0, #WSOBRepDate), 0);
SELECT Datatype
, MAX(CASE WHEN WeekOfTheMonth = 1 THEN value ELSE '0' END) Week1
, MAX(CASE WHEN WeekOfTheMonth = 2 THEN value ELSE '0' END) Week2
, MAX(CASE WHEN WeekOfTheMonth = 3 THEN value ELSE '0' END) Week3
, MAX(CASE WHEN WeekOfTheMonth = 4 THEN value ELSE '0' END) Week4
FROM ( SELECT WeekOfTheMonth
, DataType
, Value
, SortOrder
, Sequence = ROW_NUMBER() OVER ( PARTITION BY WeekOfTheMonth ORDER BY wwnId )
FROM [dbo].[SSRS_WSOBWeeklyNumbers] AS SWWN
CROSS APPLY ( SELECT 'Tenant'
, [SWWN].[Tenant]
, 1
UNION ALL
SELECT 'ReportingDate'
, CONVERT(VARCHAR(10), [SWWN].[ReportingDate], 120)
, 2
UNION ALL
SELECT 'TotalEmployeesPerBranch'
, CAST([SWWN].[TotalEmployeesPerBranch] AS VARCHAR(10))
, 3
UNION ALL
SELECT 'TotalOpenCount'
, CAST([SWWN].[TotalOpenCount] AS VARCHAR(10))
, 4
UNION ALL
SELECT 'TotalClosedCount'
, CAST([SWWN].[TotalClosedCount] AS VARCHAR(10))
, 5
UNION ALL
SELECT 'OpenCount_TitleAndEscrow'
, CAST([SWWN].[OpenCount_TitleAndEscrow] AS VARCHAR(10))
, 6
UNION ALL
SELECT 'ClosedCount_TitleAndEscrow'
, CAST([SWWN].[ClosedCount_TitleAndEscrow] AS VARCHAR(10))
, 7
UNION ALL
SELECT 'OpenCount_EscrowOnly'
, CAST([SWWN].[OpenCount_EscrowOnly] AS VARCHAR(10))
, 8
UNION ALL
SELECT 'ClosedCount_EscrowOnly'
, CAST([SWWN].[ClosedCount_EscrowOnly] AS VARCHAR(10))
, 9
UNION ALL
SELECT 'OpenCount_PreListingTask'
, CAST([SWWN].[OpenCount_PreListingTask] AS VARCHAR(10))
, 10
UNION ALL
SELECT 'ClosedCount_PreListingTask'
, CAST([SWWN].[ClosedCount_PreListingTask] AS VARCHAR(10))
, 11
UNION ALL
SELECT 'OFPE'
, CAST([SWWN].[OFPE] AS VARCHAR(10))
, 12
UNION ALL
SELECT 'CFPE'
, CAST([SWWN].[CFPE] AS VARCHAR(10))
, 13
UNION ALL
SELECT 'OpenCount_TitleOnly'
, CAST([SWWN].[OpenCount_TitleOnly] AS VARCHAR(10))
, 14
UNION ALL
SELECT 'ClosedCount_TitleOnly'
, CAST([SWWN].[ClosedCount_TitleOnly] AS VARCHAR(10))
, 15
UNION ALL
SELECT 'CurrentBusinessDay'
, CAST([SWWN].[CurrentBusinessDay] AS VARCHAR(10))
, 16
UNION ALL
SELECT 'TotalBusinessDaysForMonth'
, CAST([SWWN].[TotalBusinessDaysMTD] AS VARCHAR(10))
, 17
UNION ALL
SELECT 'ReportingDateId'
, CAST([SWWN].[ReportingDateId] AS VARCHAR(10))
, 18
UNION ALL
SELECT 'CreatedDate'
, CAST([SWWN].[CreatedDate] AS VARCHAR(10))
, 19 ) c ( DataType, Value, SortOrder )
WHERE [SWWN].[ReportingDate] BETWEEN #WSOBStartDate AND #ReportDate ) d
GROUP BY DataType
, Sequence
, SortOrder
ORDER BY Sequence
, SortOrder;
and results in:
Datatype Week1 Week2 Week3 Week4
-------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- --------------------------------------------------
Tenant King King King King
ReportingDate 2014-08-08 2014-08-15 2014-08-22 2014-08-31
TotalEmployeesPerBranch 144.00 150.00 150.00 150.00
TotalOpenCount 235 399 584 797
TotalClosedCount 0 0 0 0
OpenCount_TitleAndEscrow 137 224 335 436
ClosedCount_TitleAndEscrow 0 0 0 0
OpenCount_EscrowOnly 64 112 159 226
ClosedCount_EscrowOnly 0 0 0 0
OpenCount_PreListingTask 34 63 90 135
ClosedCount_PreListingTask 0 0 0 0
OFPE 4.81 4.62 4.76 5.18
CFPE 0.00 0.00 0.00 0.00
OpenCount_TitleOnly 270 524 721 946
ClosedCount_TitleOnly 0 0 0 0
CurrentBusinessDay 7 12 17 21
TotalBusinessDaysForMonth 21 21 21 21
ReportingDateId 411 412 413 414
CreatedDate Sep 5 201 Sep 5 201 Sep 5 201 Sep 5 201
Tenant Pierce Pierce Pierce Pierce
ReportingDate 2014-08-08 2014-08-15 2014-08-22 2014-08-31
TotalEmployeesPerBranch 21.00 22.00 22.00 23.00
TotalOpenCount 85 160 222 272
TotalClosedCount 0 0 0 0
OpenCount_TitleAndEscrow 31 62 82 99
ClosedCount_TitleAndEscrow 0 0 0 0
OpenCount_EscrowOnly 39 74 107 130
ClosedCount_EscrowOnly 0 0 0 0
OpenCount_PreListingTask 15 24 33 43
ClosedCount_PreListingTask 0 0 0 0
OFPE 12.00 12.41 12.41 10.96
CFPE 0.00 0.00 0.00 0.00
OpenCount_TitleOnly 54 83 127 159
ClosedCount_TitleOnly 0 0 0 0
CurrentBusinessDay 7 12 17 21
TotalBusinessDaysForMonth 21 21 21 21
ReportingDateId 411 412 413 414
CreatedDate Sep 5 201 Sep 5 201 Sep 5 201 Sep 5 201
Thanks tons for the answer!
You'll need to UNPIVOT all those columns first, then convert your Weeks into new columns. But in order to UNPIVOT the data, you'll have to convert all of the data types to be the same.
Since you are using SQL Server 2008, you can use CROSS APPLY to unpivot. The basic syntax will be:
select
WeekOfTheMonth,
DataType,
Value
from yourtable
cross apply
(
select 'Tenant', Tenant union all
select 'ReportingDate', convert(varchar(10), ReportingDate, 120) union all
select 'TotalEmployeesPerBranch', cast(TotalEmployeesPerBranch as varchar(10)) union all
select 'TotalOpenCount', cast(TotalOpenCount as varchar(10)) union all
select 'TotalClosedCount', cast(TotalClosedCount as varchar(10)) union all
select 'OpenCount_TitleAndEscrow', cast(OpenCount_TitleAndEscrow as varchar(10)) union all
select 'ClosedCount_TitleAndEscrow', cast(ClosedCount_TitleAndEscrow as varchar(10)) union all
select 'OpenCount_EscrowOnly', cast(OpenCount_EscrowOnly as varchar(10)) union all
select 'ClosedCount_EscrowOnly', cast(ClosedCount_EscrowOnly as varchar(10)) union all
select 'OpenCount_PreListingTask', cast(OpenCount_PreListingTask as varchar(10))
--- union all more columns
) c (DataType, value);
See SQL Fiddle with Demo. Then you'd apply the PIVOT to your Weeks:
select DataType,
Week1 = [1],
Week2 = [2],
Week3 = [3],
Week4 = [4]
from
(
select
WeekOfTheMonth,
DataType,
Value,
so,
seq = row_number() over(partition by WeekOfTheMonth order by wwnId)
from yourtable
cross apply
(
select 'Tenant', Tenant, 1 union all
select 'ReportingDate', convert(varchar(10), ReportingDate, 120), 2 union all
select 'TotalEmployeesPerBranch', cast(TotalEmployeesPerBranch as varchar(10)), 3 union all
select 'TotalOpenCount', cast(TotalOpenCount as varchar(10)), 4 union all
select 'TotalClosedCount', cast(TotalClosedCount as varchar(10)), 5 union all
select 'OpenCount_TitleAndEscrow', cast(OpenCount_TitleAndEscrow as varchar(10)), 6 union all
select 'ClosedCount_TitleAndEscrow', cast(ClosedCount_TitleAndEscrow as varchar(10)), 7 union all
select 'OpenCount_EscrowOnly', cast(OpenCount_EscrowOnly as varchar(10)),8 union all
select 'ClosedCount_EscrowOnly', cast(ClosedCount_EscrowOnly as varchar(10)), 9 union all
select 'OpenCount_PreListingTask', cast(OpenCount_PreListingTask as varchar(10)), 10
) c (DataType, value, so)
) d
pivot
(
max(value)
for WeekOfTheMonth in ([1], [2], [3], [4])
)p
order by seq, so
See SQL Fiddle with Demo.
Or you can use an aggregate function to create the new columns:
select Datatype,
max(case when WeekOfTheMonth = 1 then value else '0' end) Week1,
max(case when WeekOfTheMonth = 2 then value else '0' end) Week2,
max(case when WeekOfTheMonth = 3 then value else '0' end) Week3,
max(case when WeekOfTheMonth = 4 then value else '0' end) Week4
from
(
select
WeekOfTheMonth,
DataType,
Value,
so,
seq = row_number() over(partition by WeekOfTheMonth order by wwnId)
from yourtable
cross apply
(
select 'Tenant', Tenant, 1 union all
select 'ReportingDate', convert(varchar(10), ReportingDate, 120), 2 union all
select 'TotalEmployeesPerBranch', cast(TotalEmployeesPerBranch as varchar(10)), 3 union all
select 'TotalOpenCount', cast(TotalOpenCount as varchar(10)), 4 union all
select 'TotalClosedCount', cast(TotalClosedCount as varchar(10)), 5 union all
select 'OpenCount_TitleAndEscrow', cast(OpenCount_TitleAndEscrow as varchar(10)), 6 union all
select 'ClosedCount_TitleAndEscrow', cast(ClosedCount_TitleAndEscrow as varchar(10)), 7 union all
select 'OpenCount_EscrowOnly', cast(OpenCount_EscrowOnly as varchar(10)),8 union all
select 'ClosedCount_EscrowOnly', cast(ClosedCount_EscrowOnly as varchar(10)), 9 union all
select 'OpenCount_PreListingTask', cast(OpenCount_PreListingTask as varchar(10)), 10
) c (DataType, value, so)
) d
group by datatype, seq, so
order by seq, so
See SQL Fiddle with Demo