How to use Dense Rank and automatically generate dates - sql

I have two questions in regards to DENSE_RANK and the other based on inserting dates. Basically I have 2 leagues with 4 teams per league. Each league has a round of fixtures like so:
League 1
Week1: 1v4, 2v3 - Date: 10-June-2016
Week2: 1v3, 2v4 - Date: 17-June-2016
Week3: 1v2, 3v4 - Date: 24-June-2016
League 2
Week1: 5v8, 6v7 - Date: 10-June-2016
Week2: 5v7, 6v8 - Date: 17-June-2016
Week3: 5v6, 7v8 - Date: 24-June-2016
(They play each other home and away)
Ok so league 1 and League 2 is (LeagueID 1 and League ID 2)
Week 1 and 2 are displayed under WeekNumber column
Teams 1 -8 have their own IDs (TeamID which is then displayed as HomeTeamID and AwayTeamID)
Date goes into a column which is FixtureDate
My questions are:
1- How under WeekNumber can I set it so that the group of games mentioned, it notices them as these games belong to week 1, these week 2, these games week 3 etc.
2- How to auto generate the date so that if week 1 is played 10 June 2016, the next round of fixtures are played 7 days later, then the round after 7 days later etc.
Below is what the table looks like currently:
WeekNumber HomeTeamID AwayTeamID FixtureWeek LeagueID
1 1 4 NULL 1
1 1 3 NULL 1
1 1 2 NULL 1
1 2 3 NULL 1
1 2 4 NULL 1
1 3 4 NULL 1
1 5 8 NULL 2
1 5 7 NULL 2
1 5 6 NULL 2
1 6 7 NULL 2
1 6 8 NULL 2
1 7 8 NULL 2
Below is what it should like:
WeekNumber HomeTeamID AwayTeamID FixtureWeek LeagueID
1 1 4 10-06-2016 1
2 1 3 17-06-2016 1
3 1 2 24-06-2016 1
1 2 3 10-06-2016 1
2 2 4 17-06-2016 1
3 3 4 24-06-2016 1
1 5 8 10-06-2016 2
2 5 7 17-06-2016 2
3 5 6 24-06-2016 2
1 6 7 10-06-2016 2
2 6 8 17-06-2016 2
3 7 8 24-06-2016 2
Below is my current code which needs to be modified but I need help with this:
CREATE PROCEDURE [dbo].[Fixture_Insert]
#LeagueID INT
AS
SET NOCOUNT ON
BEGIN
INSERT INTO dbo.Fixture (WeekNumber, HomeTeamID, AwayTeamID, FixtureWeek, LeagueID)
SELECT
ROW_NUMBER() OVER (ORDER BY a.LeagueID) AS WeekNumber,
h.TeamID,
a.TeamID,
NULL AS FixtureWeek, -- Don't know what to set this to for automatic dates
h.LeagueID
FROM dbo.Team h
CROSS JOIN dbo.Team a
WHERE h.TeamID <> a.TeamID
AND h.LeagueID = a.LeagueID
END
UPDATE:
I've applied images to showcase what is happening so you can see what needs to be done to fix it (the table displayed is when I did a select* from dbo.Fixture):
The proc I excuted for the above is displayed here:

DECLARE #StartFixtureWeek date = '2016-06-10'
;WITH team AS (
SELECT *
FROM (VALUES
(1,1),(2,1),(3,1),(4,1),(5,2),(6,2),(7,2),(8,2)
) as t (teamid, leagueid)
)
, cte AS (
SELECT h.teamid AS HomeTeamID,
a.teamid AS AwayTeamID,
h.leagueid AS LeagueID
FROM team h
CROSS JOIN team a
WHERE h.teamid != a.teamid AND h.leagueid = a.leagueid
), final AS (
SELECT ROW_NUMBER() OVER (PARTITION BY c.LeagueID ORDER BY c.LeagueID, c.HomeTeamID, c.AwayTeamID) as rn,
c.HomeTeamID,
c.AwayTeamID,
c.LeagueID
FROM cte c
CROSS APPLY (
SELECT TOP 1 a.HomeTeamID, a.AwayTeamID
FROM cte a
WHERE a.LeagueID= c.LeagueID and a.AwayTeamID=c.HomeTeamID and a.HomeTeamID =c.AwayTeamID
ORDER BY a.HomeTeamID, a.LeagueID) as b
WHERE c.HomeTeamID < b.HomeTeamID
)
SELECT CASE WHEN rn > 3 THEN rn-3 ELSE rn END as WeekNumber,
HomeTeamID,
AwayTeamID,
CAST(DATEADD(week,(CASE WHEN rn > 3 THEN rn-3 ELSE rn END)-1,#StartFixtureWeek) as date) FixtureWeek,
LeagueID
FROM final
Output:
WeekNumber HomeTeamID AwayTeamID FixtureWeek LeagueID
-------------------- ----------- ----------- ----------- -----------
1 1 2 2016-06-10 1
2 1 3 2016-06-17 1
3 1 4 2016-06-24 1
1 2 3 2016-06-10 1
2 2 4 2016-06-17 1
3 3 4 2016-06-24 1
1 5 6 2016-06-10 2
2 5 7 2016-06-17 2
3 5 8 2016-06-24 2
1 6 7 2016-06-10 2
2 6 8 2016-06-17 2
3 7 8 2016-06-24 2
(12 row(s) affected)

Add a parameter to your stored procedure called #StartFixtureWeek DATETIME
Then you can use DATEADD
CREATE PROCEDURE [dbo].[Fixture_Insert]
#LeagueID INT,
#StartFixtureWeek DATETIME
AS
SET NOCOUNT ON
BEGIN
INSERT INTO dbo.Fixture (WeekNumber, HomeTeamID, AwayTeamID, FixtureWeek, LeagueID)
SELECT
ROW_NUMBER() OVER (ORDER BY a.LeagueID) AS WeekNumber,
h.TeamID,
a.TeamID,
SELECT DATEADD(day,(ROW_NUMBER() OVER (ORDER BY a.LeagueID)-1)*7,#StartFixtureWeek) AS FixtureWeek,
h.LeagueID
FROM dbo.Team h
CROSS JOIN dbo.Team a
WHERE h.TeamID <> a.TeamID
AND h.LeagueID = a.LeagueID
END

Related

Group by values that are each multiple of number

This is the table t. I want to group it every time the TotalQty >= 5n (let n = group). i.e. once the TotalQty >= 5n I want to sum together the qty from n-1 to n.
ID DateCreated CurrQty
1 01-20-2020 1
2 01-21-2020 4
3 01-22-2020 3
4 01-23-2020 3
5 01-25-2020 1
6 02-13-2020 3
7 02-16-2020 2
With this query I can get pretty close but I doesn't consider the the previous "valid" TotalQty + 5
select DateCreated, CurrQty, TotalQty
, ceiling(TotalQty/5.0) GroupNum
from
(
select DateCreated, CurrQty
, SUM(CurrQty) OVER (ORDER BY DateCreated ROWS BETWEEN UNBOUNDED PRECEDING AND 0 PRECEDING) TotalQty
from t
) t2
ID DateCreated CurrQty TotalQty GroupNum
1 01-20-2020 1 1 1
2 01-21-2020 4 5 1
3 01-22-2020 3 8 2
4 01-23-2020 3 11 3
5 01-25-2020 1 12 3
6 02-13-2020 3 15 3
7 02-16-2020 2 17 4
---
How do I get this result?
ID DateCreated CurrQty TotalQty GroupNum
1 01-20-2020 1 1 1
2 01-21-2020 4 5 1
3 01-22-2020 3 8 2
4 01-23-2020 3 11 2 (from ID2, 11 >= (5+5))
5 01-25-2020 1 12 3
6 02-13-2020 3 15 3
7 02-16-2020 2 17 3 (from ID4, 17 >= (11+5))
And so on, the next group would be until 17+5 = 22
You need to use a recursive CTE for this:
with cte as (
select id, datecreated, currqty, currqty as totalqty, 1 as groupnum
from t
where id = 1
union all
select t.id, t.datecreated, t.currqty,
(case when cte.totalqty >= 5 then t.currqty else t.currqty + cte.totalqty end),
(case when cte.totalqty >= 5 then groupnum + 1 else groupnum end)
from cte join
t
on t.id = cte.id + 1
)
select *
from cte;
EDIT:
Hold on. I think the answer is simpler.
select t.*,
1 + ceil((totalqty - qty + 1) / 5.0)
from (select t.*,
sum(qty) over (order by date) as totalqty
from t
) t;

Select top year and term for each student

I want to return each student final semester record from these tables.
Table: dbo.Stdetail
StID YearID TermID
2 1 1
3 1 1
3 2 1
3 2 2
3 3 1
3 3 2
4 1 1
4 1 2
5 1 1
5 1 2
Table: dbo.lastyear
StID YearID TermID
1 5 1
2 5 1
2 6 2
3 5 1
3 6 2
From these two tables I want to return final yearID and term ID.
Desired output:
StID yearID TermID
1 5 1
2 6 2
3 6 2
4 1 2
5 1 2
I think you want to union together dbo.Stdetail and dbo.lastyear and then apply use row_number() to identify the most last record for each student. Like this:
;with cte as (select *
, row_number() over (partition by StID order by YearID desc, TermID desc) rn
from (select StID, YearID, TermID from dbo.Stdetail
union
select StID, YearID, TermID from dbo.lastyear) x
)
select *
from cte
where rn = 1

Assign rownumber in SQL grouped on value and n rows per rownumber

I am trying to generate a report with 3 rows per page for each order number using the following SQL.
As you can see from the results the fields Actual & Expected do not match up.
Any help would be appreciated.
set nocount on
DECLARE #Orders TABLE (Expected int, OrderNumber INT, OrderDetailsNumber int)
Insert into #orders values (0,1,1)
Insert into #orders values (0,1,2)
Insert into #orders values (0,1,3)
Insert into #orders values (1,1,4)
Insert into #orders values (2,2,5)
Insert into #orders values (2,2,6)
Insert into #orders values (2,2,7)
Insert into #orders values (3,2,8)
Insert into #orders values (3,2,9)
select cast(((row_number() over( order by OrderNumber)) -1) /3 as int) as [Actual]
,*
from #orders
Actual Expected OrderNumber OrderDetailsNumber
----------- ----------- ----------- ------------------
0 0 1 1
0 0 1 2
0 0 1 3
1 1 1 4
1 2 2 5
1 2 2 6
2 2 2 7
2 3 2 8
2 3 2 9
Right, after a couple of edits I have the final answer:
SELECT DENSE_RANK() OVER (Order BY OrderNumber, floor(RowNumber/3)) - 1 AS Actual,
Expected,
OrderNumber,
OrderDetailsNumber
FROM
(
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY OrderNumber
ORDER BY OrderDetailsNumber
) - 1 AS RowNumber
FROM #Orders
) RowNumberTable
Gives the result (with extra rows for testing):
Actual Expected OrderNumber OrderDetailsNumber
-------------------- ----------- ----------- ------------------
0 0 1 1
0 0 1 2
0 0 1 3
1 1 1 4
1 1 1 12
2 2 2 5
2 2 2 6
2 2 2 7
3 3 2 8
3 3 2 9
3 4 2 11
4 3 2 27
5 5 3 10
This only works where OrderDetailsNumber is unique such that the result is deterministic.
Edit
I've now got the complete code working, however the dependence on OrderDetailsNumber being in order is very iffy, hopefully you can test and edit as required.
Edit 2
I've put the 'golfed' version in the main answer.
WITH FirstCTE AS
(
SELECT
OrderNumber,
OrderDetailsNumber,
Expected,
ROW_NUMBER() OVER (
PARTITION BY OrderNumber
ORDER BY OrderDetailsNumber
) - 1 AS RowNumber
FROM #Orders
)
, SecondCTE AS
(
SELECT OrderDetailsNumber as odn,
floor(RowNumber/3) as page_for_order_number,
DENSE_RANK() OVER (Order BY OrderNumber, floor(RowNumber/3)) - 1 AS Actual
FROM FirstCTE
)
SELECT c2.page_for_order_number,
c1.RowNumber,
C2.Actual,
c1.Expected,
c1.OrderNumber,
c1.OrderDetailsNumber
FROM FirstCTE AS c1
INNER JOIN SecondCTE AS c2
on c2.odn = c1.OrderDetailsNumber
This strikes me as a bit of a hack, but it works...
Divide the row_number() by 3, and use CEILINGto get the smallest integer greater than or equal to the result of that division.
select row_number() over( order by OrderNumber) as [Actual],
cast (row_number() over(order by ordernumber) as decimal(5,1)) / 3,
CEILING(cast (row_number() over(order by ordernumber) as decimal(5,1)) / 3)as GRPR,
*
from #orders
EDIT: Dang it, can never get results to line up. The 3rd column in the result set is your "page number".
Which yields:
Actual (No column name) PG_NBR Expected OrderNumber OrderDetailsNumber
1 0.333333 1 0 1 1
2 0.666666 1 0 1 2
3 1.000000 1 0 1 3
4 1.333333 2 1 1 4
5 1.666666 2 2 2 5
6 2.000000 2 2 2 6
7 2.333333 3 2 2 7
8 2.666666 3 3 2 8
9 3.000000 3 3 2 9

T-SQL Reverse Pivot on every character of a string

We have a table like below in an sql server 2005 db:
event_id staff_id weeks
1 1 NNNYYYYNNYYY
1 2 YYYNNNYYYNNN
2 1 YYYYYYYYNYYY
This is from a piece of timetabling software and is basically saying which staff members are assigned to an event (register) and the set of weeks they are teaching that register. So staff_id 1 isn't teaching the first 3 weeks of event 1 but is teaching the following 4....
Is there an easy way to convert that to an easier form such as:
event_id staff_id week
1 1 4
1 1 5
1 1 6
1 1 7
1 1 10
1 1 11
1 1 12
1 2 1
1 2 2
1 2 3
1 2 7
1 2 8
1 2 9
2 1 1
2 1 2
2 1 3
2 1 4
2 1 5
2 1 6
2 1 7
2 1 8
2 1 10
2 1 11
2 1 12
WITH cte AS
(
SELECT 1 AS [week]
UNION ALL
SELECT [week] + 1
FROM cte
WHERE [week] < 53
)
SELECT t.event_id, t.staff_id, cte.[week]
FROM your_table AS t
INNER JOIN cte
ON LEN(ISNULL(t.weeks, '')) >= cte.[week]
AND SUBSTRING(t.weeks, cte.[week], 1) = 'Y'
ORDER BY t.event_id, t.staff_id, cte.[week]

Grouping Hierarchical data (parentID+ID) and running sum?

I have the following data:
ID parentID Text Price
1 Root
2 1 Flowers
3 1 Electro
4 2 Rose 10
5 2 Violet 5
6 4 Red Rose 12
7 3 Television 100
8 3 Radio 70
9 8 Webradio 90
I am trying to group this data with Reporting Services 2008 and have a sum of the price per group of level 1 (Flowers/Electro) and for level 0 (Root).
I have a table grouped on [ID] with a recursive parent of [parendID] and I am able to calculate the sum for the level 0 (just one more row in the table outside the group), but somehow I am not able to create sum's per group as SRSS does "create" groups per level. My desired result looks like so:
ID Text Price
1 Root
|2 Flowers
|-4 Rose 10
|-5 Violet 5
| |-6 Red Rose 12
| Group Sum-->27
|3 Electro
|-7 Television 100
|-8 Radio 70
|-9 Webradio 90
Group Sum-->260
----------------------
Total 287
(indentation of ID just added for level clarification)
With my current approach I cannot get the group sums, so I figured out I would need the following data structure:
ID parentID Text Price level0 level1 level2 level3
1 Root 1
2 1 Flowers 1 1
3 1 Electro 1 2
4 2 Rose 10 1 1 1
5 2 Violet 5 1 1 2
6 4 Red Rose 12 1 1 1 1
7 3 Television 100 1 2 1
8 3 Radio 70 1 2 2
9 8 Webradio 90 1 2 2 1
When having the above structure I can create an outer grouping of level0, with child groupings level1, level2, level3 accordingly . When now having a "group sum" on level1, and the total sum outside the group I have EXACTLY what I want.
My question is the following:
How do I either achieve my desired result with my current data structure, or how do I convert my current data structure (outer left joins?) into the "new data structure" temporarily - so I can run my report off of the temp table?
Thanks for taking your time,
Dennis
WITH q AS
(
SELECT id, parentId, price
FROM mytable
UNION ALL
SELECT p.id, p.parentID, q.price
FROM q
JOIN mytable p
ON p.id = q.parentID
)
SELECT id, SUM(price)
FROM q
GROUP BY
id
Update:
A test script to check:
DECLARE #table TABLE (id INT NOT NULL PRIMARY KEY, parentID INT, txt VARCHAR(200) NOT NULL, price MONEY)
INSERT
INTO #table
SELECT 1, NULL, 'Root', NULL
UNION ALL
SELECT 2, 1, 'Flowers', NULL
UNION ALL
SELECT 3, 1, 'Electro', NULL
UNION ALL
SELECT 4, 2, 'Rose', 10
UNION ALL
SELECT 5, 2, 'Violet', 5
UNION ALL
SELECT 6, 4, 'Red Rose', 12
UNION ALL
SELECT 7, 3, 'Television', 100
UNION ALL
SELECT 8, 3, 'Radio', 70
UNION ALL
SELECT 9, 8, 'Webradio', 90;
WITH q AS
(
SELECT id, parentId, price
FROM #table
UNION ALL
SELECT p.id, p.parentID, q.price
FROM q
JOIN #table p
ON p.id = q.parentID
)
SELECT t.*, psum
FROM (
SELECT id, SUM(price) AS psum
FROM q
GROUP BY
id
) qo
JOIN #table t
ON t.id = qo.id
Here's the result:
1 NULL Root NULL 287,00
2 1 Flowers NULL 27,00
3 1 Electro NULL 260,00
4 2 Rose 10,00 22,00
5 2 Violet 5,00 5,00
6 4 Red Rose 12,00 12,00
7 3 Television 100,00 100,00
8 3 Radio 70,00 160,00
9 8 Webradio 90,00 90,00
I found a really ugly way to do what I want - maybe there is something better?
SELECT A.Text, A.Price,
CASE
WHEN D.Text IS NULL
THEN
CASE
WHEN C.Text IS NULL
THEN
CASE
WHEN B.Text IS NULL
THEN
A.ID
ELSE B.ID
END
ELSE C.ID
END
ELSE D.ID
END
AS LEV0,
CASE
WHEN D.Text IS NULL
THEN
CASE
WHEN C.Text IS NULL
THEN
CASE
WHEN B.Text IS NULL
THEN
NULL
ELSE A.ID
END
ELSE B.ID
END
ELSE C.ID
END
AS LEV1,
CASE
WHEN D.Text IS NULL
THEN
CASE
WHEN C.Text IS NULL
THEN
NULL
ELSE A.ID
END
ELSE B.ID
END
AS LEV2,
CASE
WHEN D.Text IS NULL
THEN NULL
ELSE A.ID
END
AS LEV3
FROM dbo.testOld AS A LEFT OUTER JOIN
dbo.testOld AS B ON A.parentID = B.ID LEFT OUTER JOIN
dbo.testOld AS C ON B.parentID = C.ID LEFT OUTER JOIN
dbo.testOld AS D ON C.parentID = D.ID
Output of this is:
Text Price LEV0 LEV1 LEV2 LEV3
---------- ----------- ----------- ----------- ----------- -----------
Root NULL 1 NULL NULL NULL
Flowers NULL 1 3 NULL NULL
Electro NULL 1 4 NULL NULL
Television 100 1 4 5 NULL
Radio 70 1 4 6 NULL
Rose 10 1 3 7 NULL
Violet 5 1 3 8 NULL
Webradio 90 1 4 5 14
Red Rose 12 1 3 7 15
With this structure I can go ahead and create 4 nested groups on the LEV0-3 columns including subtotals per group (as shown above in my desired result).