SQL recursion on a self-referencing table to obtain specific order - sql

I have a table as below:
Id
LinkSlug
ParentPageId
Order
1
home
0
2
page2
1
3
page3
2
4
page11
1
0
5
page12
1
1
6
page13
1
2
7
page21
2
0
8
page22
2
1
9
page121
5
0
10
page122
5
1
11
page123
5
2
I'm sure you can already see the pattern - each Page can have any number of "SubPages" defined by the ParentPageId
I've been trying to get a query that can produce the following ordered output (without using the LinkSlug alphabetical ordering because they can be anything):
Id
LinkSlug
ParentPageId
Order
1
home
0
4
page11
1
0
5
page12
1
1
9
page121
5
0
10
page122
5
1
11
page123
5
2
6
page13
1
2
2
page2
1
7
page21
2
0
8
page22
2
1
3
page3
2
I tried doing some self-joins and grouping but ended up with only one level of recursion so it was no good for the 3rd and potentially nth degree sub pages, and then also tried using a CTE as I understand they're good for recursive queries but somehow ended up producing the same table I started with and am now at a loss!
The more I try the worse it gets - I know I need to effectively select top levels (with null ParentPageId) ordered by [Order], then inject wherever there's sub pages ordering by [Order], and repeat until there are no children left - but no idea how to do this in SQL.
View on DB Fiddle
And here's the fiddle script just in case:
CREATE TABLE [Pages](
[Id] [int] IDENTITY(1,1) NOT NULL,
[LinkSlug] [nvarchar](450) NOT NULL,
[ParentPageId] [int] NULL,
[Order] [int] NOT NULL
);
INSERT into [Pages] ([Id], [LinkSlug], [ParentPageId], [Order]) VALUES (1, 'home', NULL, 0);
INSERT into [Pages] ([Id], [LinkSlug], [ParentPageId], [Order]) VALUES (2, 'page2', NULL, 1);
INSERT into [Pages] ([Id], [LinkSlug], [ParentPageId], [Order]) VALUES (3, 'page3', NULL, 2);
INSERT into [Pages] ([Id], [LinkSlug], [ParentPageId], [Order]) VALUES (4, 'page11', 1, 0);
INSERT into [Pages] ([Id], [LinkSlug], [ParentPageId], [Order]) VALUES (5, 'page12', 1, 1);
INSERT into [Pages] ([Id], [LinkSlug], [ParentPageId], [Order]) VALUES (6, 'page13', 1, 2);
INSERT into [Pages] ([Id], [LinkSlug], [ParentPageId], [Order]) VALUES (7, 'page21', 2, 0);
INSERT into [Pages] ([Id], [LinkSlug], [ParentPageId], [Order]) VALUES (8, 'page22', 2, 1);
INSERT into [Pages] ([Id], [LinkSlug], [ParentPageId], [Order]) VALUES (9, 'page121', 5, 0);
INSERT into [Pages] ([Id], [LinkSlug], [ParentPageId], [Order]) VALUES (10, 'page122', 5, 1);
INSERT into [Pages] ([Id], [LinkSlug], [ParentPageId], [Order]) VALUES (11, 'page123', 5, 2);

You would need a recursive CTE to build the hierarchy and maintain the sequence
Example
with cte1 as (
Select [Id]
,[LinkSlug]
,[ParentPageId]
,[Order]
,Seq = cast(10000+Row_Number() over (Order by [Order]) as varchar(500))
From pages
Where [ParentPageId] is null
Union All
Select cte2.[Id]
,cte2.[LinkSlug]
,cte2.[ParentPageId]
,cte2.[Order]
,Seq = cast(concat(cte1.Seq,'.',10000+Row_Number() over (Order by cte2.[Order])) as varchar(500))
From pages cte2
Join cte1 on cte2.[ParentPageId] = cte1.[Id])
Select *
From cte1
Order By Seq
Results

You can try this:
WITH cte_org (n, id,
LinkSlug,
ParentPageId) AS (
SELECT
CAST([order] as CHAR(200)),
id,
LinkSlug,
ParentPageId
FROM
pages
WHERE ParentPageId IS NULL
UNION ALL
SELECT
o.n || '_' || e.[order],
e.id,
e.LinkSlug,
e.ParentPageId
FROM
pages e
INNER JOIN cte_org o
ON o.id = e.ParentPageId)
SELECT * FROM cte_org order by n;
Note: that in MS SQL you need to use concat instead of ||; in MySQL - +

Related

Performing complex data partitioning when using PARTITION BY in SQL Server

I am not a SQL expert, not even close. In the below example am using the PARTITION BY clause in a PERCENT_RANK() operation (Microsoft SQL Server hosted in Azure) to group my data for ranking which works as expected:
DECLARE #Dinky TABLE
(
[Id] INT PRIMARY KEY NOT NULL,
[Name] nvarchar(32) NOT NULL,
[Score] float NOT NULL,
[Type] nvarchar(4) NOT NULL,
[Pool] int NOT NULL
);
INSERT INTO #Dinky ([Id], [Name], [Score], [Type], [Pool]) VALUES (1, 'Bob', 1.4, 'A', 1);
INSERT INTO #Dinky ([Id], [Name], [Score], [Type], [Pool]) VALUES (2, 'Mary', 2.6, 'A', 2);
INSERT INTO #Dinky ([Id], [Name], [Score], [Type], [Pool]) VALUES (3, 'Kim', 7.8, 'A', 3);
INSERT INTO #Dinky ([Id], [Name], [Score], [Type], [Pool]) VALUES (4, 'Chris', 3.3, 'A', 1);
INSERT INTO #Dinky ([Id], [Name], [Score], [Type], [Pool]) VALUES (5, 'Linda', 4.5, 'A', 1);
INSERT INTO #Dinky ([Id], [Name], [Score], [Type], [Pool]) VALUES (6, 'Frank', 2.1, 'A', 2);
INSERT INTO #Dinky ([Id], [Name], [Score], [Type], [Pool]) VALUES (7, 'Julie', 1.0, 'A', 3);
INSERT INTO #Dinky ([Id], [Name], [Score], [Type], [Pool]) VALUES (8, 'Greg', 6.7, 'A', 3);
INSERT INTO #Dinky ([Id], [Name], [Score], [Type], [Pool]) VALUES (9, 'Tom', 8.3, 'A', 2);
SELECT [Id]
,[Name]
,[Score]
,[Type]
,[Pool]
,PERCENT_RANK() OVER (PARTITION BY [Type], [Pool] ORDER BY [Score] DESC) [Rank]
FROM #Dinky
This will produce the expected output with my rows being partitioned first by their [Type] (which I realize are all the same in this example) and then by their respective [Pool]:
Id Name Score Type Pool Rank
5 Linda 4.5 A 1 0
4 Chris 3.3 A 1 0.5
1 Bob 1.4 A 1 1
9 Tom 8.3 A 2 0
2 Mary 2.6 A 2 0.5
6 Frank 2.1 A 2 1
3 Kim 7.8 A 3 0
8 Greg 6.7 A 3 0.5
7 Julie 1 A 3 1
So far, so good - but of course it can't be that simple. In my particular business case, the rows in Pool 1 should be ranked along side all the rows in Pools 2 and 3 (think of it like a super-pool of everyone); further, the rows in Pool 2 should be ranked along with the rows in Pool 3 (a more exclusive pool); and of course the rows in Pool 3 are ranked only among themselves (the elite). Putting aside the question of 'how to express this' for a moment, the other part of the problem is that of duplicate rows. I only want to retain the rank result for the highest pool for each row. So in this example, Greg would be used for ranking in the 1-2-3 pool, and used for ranking in the 2-3 pool, and used for ranking in the 3 pool - but I only want his result for the 3 pool in my output.
After some fiddling around I was eventually able to get my desired result, but (I think) the result is far from elegant. I can't help but feel there is a better way to express this in SQL that I'm simply not fluent enough to produce. Here is what I came up with:
WITH poolOne AS
(
SELECT [Id]
,[Name]
,[Score]
,[Type]
,[Pool]
,PERCENT_RANK() OVER (PARTITION BY [Type] ORDER BY [Score] DESC) [Rank]
FROM #Dinky
WHERE [Pool] >= 1
), poolTwo AS
(
SELECT [Id]
,[Name]
,[Score]
,[Type]
,[Pool]
,PERCENT_RANK() OVER (PARTITION BY [Type] ORDER BY [Score] DESC) [Rank]
FROM #Dinky
WHERE [Pool] >= 2
), poolThree AS
(
SELECT [Id]
,[Name]
,[Score]
,[Type]
,[Pool]
,PERCENT_RANK() OVER (PARTITION BY [Type] ORDER BY [Score] DESC) [Rank]
FROM #Dinky
WHERE [Pool] >= 3
)
SELECT *
FROM poolThree
UNION
SELECT *
FROM poolTwo
WHERE NOT EXISTS (SELECT 1 FROM poolThree
WHERE poolTwo.Id = poolThree.Id)
UNION
SELECT *
FROM poolOne
WHERE NOT EXISTS (SELECT 1 FROM poolTwo
WHERE poolOne.Id = poolTwo.Id)
I get this output:
Id Name Score Type Pool Rank
5 Linda 4.5 A 1 0.375
4 Chris 3.3 A 1 0.5
1 Bob 1.4 A 1 0.875
9 Tom 8.3 A 2 0
2 Mary 2.6 A 2 0.6
6 Frank 2.1 A 2 0.8
3 Kim 7.8 A 3 0
8 Greg 6.7 A 3 0.5
7 Julie 1 A 3 1
Notice Kim, Greg and Julie are the same as they were in my original query because they are exclusively ranked within their pool (Pool 3). But the ranking for Tom, Mary and Frank have slightly changed because they were ranked with their pool (Pool 2) and the more exclusive pool (Pool 3). Finally, the rankings for Linda, Chris and Bob were ranked against everyone (Pools 1, 2 and 3).
I realize in my example above I could rewrite the WITH poolOne to remove the WHERE clause, and rewrite the WITH pool3 to remove the WHERE and change the PARTITION BY to it's original form. But for what it is (all things being equal), I think the logic is more clearly expressed the way it is (and is certainly better for an example asking for help).
What could I do to rewrite this in a more elegant, shorter, better, more performant, (pick your adjective) way? Can someone please dazzle me with their sql-foo?
I don't know if there's a way to avoid using PERCENT_RANK() multiple times, but one way to do achieve this result without the unions would be with a CASE expression.
SELECT [Id]
,[Name]
,[Score]
,[Type]
,[Pool]
,[Rank] =
CASE [Pool]
WHEN 3 THEN PERCENT_RANK() OVER (PARTITION BY [Type], [Pool] ORDER BY [Score] DESC)
WHEN 2 THEN PERCENT_RANK() OVER (PARTITION BY [Type], CASE WHEN [Pool] >= 2 THEN 1 END ORDER BY [Score] DESC)
WHEN 1 THEN PERCENT_RANK() OVER (PARTITION BY [Type] ORDER BY [Score] DESC)
END
FROM #Dinky
ORDER BY [Type], [Pool], [Score] DESC;
Just remove the = sign and and create a second helper column after. Also no offense but the naming conventions and way you use SQL is just well not efficient. The fact you even unioned anything in this is beyond me. This is a simple query to write and you massively over complicated it. What's the point of writing CTEs and using unions? You literally just join correctly. In 10 years I literally have NEVER unioned in a CTE. Also not sure why you are using parameters etc.

First character replace in SQL Server

I have a question about SQL Server. How to replace only 1st character value when same character have multiple times?
CREATE TABLE [dbo].[productdetails]
(
[pid] [int] NULL,
[productName] [varchar](100) NULL
)
INSERT INTO [dbo].[productdetails] ([pid], [productName])
VALUES (1, N'cinphol')
INSERT INTO [dbo].[productdetails] ([pid], [productName])
VALUES (2, N'apple')
INSERT INTO [dbo].[productdetails] ([pid], [productName])
VALUES (3, N'ppens')
INSERT INTO [dbo].[productdetails] ([pid], [productName])
VALUES (4, N'penrpos')
GO
Based on this data, I want output like this:
pid | productname
----+------------
1 | cinZhol
2 | azple
3 | zpens
4 | zenrpos
My query:
select
pid, replace(productname, 'p', 'z') productname
from
productdetails
This query is not returning the expected results.
Could you please tell me how to achieve this task in SQL Server ?
select
pid,
replace(left(productname, 1), 'p', 'z') +
substring(productname, 2, len(productname)) productname
from
productdetails

How Result using cross apply, can achieve using any other joins like inner,left,right join in sql server

This is my SQL script with sample data
CREATE TABLE [dbo].[Employee]
(
[ID] [INT] IDENTITY(1,1) NOT NULL,
[Name] [VARCHAR](100) NULL
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[LoginEntry]
(
[ID] [INT] IDENTITY(1,1) NOT NULL,
[LoginTime] [DATETIME] NULL,
[EmpID] [INT] NULL,
[GateNumber] [VARCHAR](50) NULL
) ON [PRIMARY]
GO
ALTER TABLE Employee
ADD CONSTRAINT Pk_Employee PRIMARY KEY (Id)
GO
ALTER TABLE LoginEntry
ADD CONSTRAINT Fk_LoginEntry_Employee
FOREIGN KEY (EmpId) REFERENCES Employee(Id)
GO
SET IDENTITY_INSERT [dbo].[Employee] ON
GO
INSERT [dbo].[Employee] ([ID], [Name])
VALUES (1, N'Employee 1'), (2, N'Employee 2'), (3, N'Employee 3'),
(4, N'Employee 4'), (5, N'Employee 5'), (6, N'Employee 6')
GO
SET IDENTITY_INSERT [dbo].[Employee] OFF
GO
SET IDENTITY_INSERT [dbo].[LoginEntry] ON
GO
INSERT [dbo].[LoginEntry] ([ID], [LoginTime], [EmpID], [GateNumber])
VALUES (1, CAST(N'2014-10-24 08:00:00.000' AS DateTime), 1, N'Gate 1'),
(2, CAST(N'2014-10-24 09:00:00.000' AS DateTime), 1, N'Gate 1'),
(3, CAST(N'2014-10-24 10:00:00.000' AS DateTime), 1, N'Gate 2'),
(4, CAST(N'2014-10-24 08:00:00.000' AS DateTime), 2, N'Gate 1'),
(5, CAST(N'2014-10-24 09:00:00.000' AS DateTime), 2, N'Gate 1'),
(6, CAST(N'2014-10-24 10:00:00.000' AS DateTime), 2, N'Gate 2'),
(7, CAST(N'2014-10-24 08:00:00.000' AS DateTime), 3, N'Gate 1'),
(8, CAST(N'2014-10-24 09:00:00.000' AS DateTime), 3, N'Gate 1'),
(9, CAST(N'2014-10-24 10:00:00.000' AS DateTime), 3, N'Gate 2'),
(10, CAST(N'2014-10-24 08:00:00.000' AS DateTime), 4, N'Gate 1'),
(11, CAST(N'2014-10-24 09:00:00.000' AS DateTime), 4, N'Gate 1'),
(19, CAST(N'2014-10-24 08:00:00.000' AS DateTime), 5, N'Gate 1'),
(20, CAST(N'2014-10-24 09:00:00.000' AS DateTime), 5, N'Gate 1'),
(21, CAST(N'2014-10-24 10:00:00.000' AS DateTime), 5, N'Gate 2'),
(22, CAST(N'2014-10-24 08:00:00.000' AS DateTime), 6, N'Gate 1'),
(23, CAST(N'2014-10-24 09:00:00.000' AS DateTime), 6, N'Gate 1'),
(24, CAST(N'2014-10-24 10:00:00.000' AS DateTime), 6, N'Gate 2')
SET IDENTITY_INSERT [dbo].[LoginEntry] OFF
GO
SELECT
e.ID, dt.EmpId, Name, LoginTime
FROM
Employee e
CROSS APPLY
(SELECT TOP 1
l.ID, l.LoginTime, l.EmpId
FROM
LoginEntry l
WHERE
l.EmpId = e.id) dt
GO
The result I get:
ID EmpId Name LoginTime
-----------------------------------------------
1 1 Employee 1 2014-10-24 08:00:00.000
2 2 Employee 2 2014-10-24 08:00:00.000
3 3 Employee 3 2014-10-24 08:00:00.000
4 4 Employee 4 2014-10-24 08:00:00.000
5 5 Employee 5 2014-10-24 08:00:00.000
6 6 Employee 6 2014-10-24 08:00:00.000
I am Expecting the same result using Joins(inner,right,left,full) in sql server i tried my luck but couldn't, pls can any one help me out thanks in advance
First, your query is incomplete. When you use TOP 1 without an ORDER BY you never have a guarantee which one it will pick. New data, concurrent processes, re-indexing, software patches, the time of day, they all can cause the result to change.
So, it should be something like...
SELECT
e.ID,dt.EmpId,Name,LoginTime
FROM
Employee e
CROSS APPLY
(
SELECT TOP 1
l.ID
,l.LoginTime
,l.EmpId
FROM
LoginEntry l
WHERE
l.EmpId=e.id
ORDER BY
l.LoginTime DESC -- Will cause TOP 1 to pick the most recent value (per employee)
)
dt
As for doing it with joins, doing the TOP 1 (or greatest-n-per-group, for which your n is 1), is longer, messier, and slower. So I won't go in to that.
But you can use ROW_NUMBER() to do the TOP 1 part, and then use a JOIN to associate that result with your main table...
WITH
ordered_logins AS
(
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY EmpID ORDER BY LoginTime DESC, ID DESC) AS row_ordinal
FROM
LoginEntry
)
SELECT
e.ID, l.EmpId, e.Name, l.LoginTime
FROM
Employee e
LEFT JOIN
ordered_logins l
ON l.EmpID = e.ID
AND l.row_oridnal = 1
The ROW_NUMBER() assigns every row a value from 1 upwards (per EmpID - the partition clause). It's order by loginTime descending, so the latest logins are first, and just in case two logins have the exact same time, it's secondarily ordered by ID desc.
Then the LEFT JOIN only picks the rows numbered 1 (the latest logins) and if there are no logins gives NULLs instead (so that the employee record is Not discarded due to a lack of join).
Note: the LEFT JOIN equivalent for APPLY is to use OUTER APPLY instead of CROSS APPLY.
Just for the sake of showing one more way to solve the problem, the "longer, messier, and slower" JOIN method that (I thought) MatBailie didn't show would look like this:
SELECT TOP (1) WITH TIES
e.ID
,l.EmpID
,e.Name
,l.LoginTime
FROM
dbo.Employee AS e
JOIN
dbo.LoginEntry AS l
ON
l.EmpID = e.ID
ORDER BY
ROW_NUMBER() OVER (PARTITION BY e.ID ORDER BY l.LoginTime DESC, ID DESC)
The ROW_NUMBER in the ORDER BY clause takes all of the logins for each Employee ID and numbers them by LoginTime with the most recent first (and using the LoginEntry ID as tie breaker, and thanks for that touch, Mat).
Then, the SELECT TOP (1) WITH TIES does its thing. The WITH TIES bit means that it selects the number one result from each PARTITION BY group in the ORDER BY clause.
you can use window function row_number()
with cte as(
SELECT e.ID,l.EmpId,Name,l.LoginTime, row_number() over(partition by e.ID order by l.LoginTime) as rn
FROM Employee e join LoginEntry l on l.EmpId=e.id
) select ID,EmpId,Name,LoginTime from cte where rn=1
demo link

Sql Select using CTE to order a recursive data

Please i want a solution for this problem using CTE in SQL Server
Example for the situation
Equation 0 = 0.25*Equation 1
Equation 1 = Equation 2 + Equation 3 + 0.5*Equation 5
Equation 2 = 15 + 40
Equation 3 = Equation 6 + Equation 7
Equation 4 = 10
Equation 5 = 10 + Equation 4
Equation 6 = 10 +5
Equation 7 = Equation 5 + Equation 2
The structure of the tables is
The Element Table
ID | Name
-------|--------------
0 | Equation 0
1 | Equation 1
2 | Equation 2
3 | Equation 3
4 | Equation 4
5 | Equation 5
6 | Equation 6
7 | Equation 7
---------------------
the table holds all items of each equation
The equation Table
FK | Item | Type
-------|-----------|------------------
0 | 0.25 | constant
0 | * | Operator
0 | 1 | Element
1 | 2 | Element
1 | + | Operator
1 | 3 | Element
1 | + | Operator
1 | 0.5 | constant
1 | * | Operator
1 | 5 | Element
2 | 15 | constant
2 | + | Operator
2 | 40 | constant
… | |
… | |
… etc | |
------------------------------------
if the type is element this means it is an element item
is there any sql statement result to the correct order that i must use to calculate these equations one by one without using recursive functions because it is limited in SQL
the alternative is to calculate the last equation without any requirements then calculate the upper ones as when i need an equation i find it calculated already without recursing the equations
i need the sql select statement to produce the following order
Equation 2
Equation 6
Equation 4
Equation 5
Equation 7
Equation 3
Equation 1
Equation 0
i ordered them by eye because it is simple example
is there any select statement is used to do so
or the user must order them manually ???
... Update
1. with a fully working test scenario
The Script to create tables
/****** Object: Table [dbo].[Element] Script Date: 26/03/2017 11:10:10 م ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Element](
[Id] [int] NOT NULL,
[Name] [nvarchar](50) NOT NULL,
CONSTRAINT [PK_Element] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
/****** Object: Table [dbo].[Equation] Script Date: 26/03/2017 11:10:10 م ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Equation](
[fk] [int] NOT NULL,
[Item] [nvarchar](50) NOT NULL,
[Type] [nvarchar](50) NOT NULL
) ON [PRIMARY]
GO
INSERT [dbo].[Element] ([Id], [Name]) VALUES (0, N'Equation 0')
INSERT [dbo].[Element] ([Id], [Name]) VALUES (1, N'Equation 1')
INSERT [dbo].[Element] ([Id], [Name]) VALUES (2, N'Equation 2')
INSERT [dbo].[Element] ([Id], [Name]) VALUES (3, N'Equation 3')
INSERT [dbo].[Element] ([Id], [Name]) VALUES (4, N'Equation 4')
INSERT [dbo].[Element] ([Id], [Name]) VALUES (5, N'Equation 5')
INSERT [dbo].[Element] ([Id], [Name]) VALUES (6, N'Equation 6')
INSERT [dbo].[Element] ([Id], [Name]) VALUES (7, N'Equation 7')
INSERT [dbo].[Equation] ([fk], [Item], [Type]) VALUES (0, N'0.25', N'constant')
INSERT [dbo].[Equation] ([fk], [Item], [Type]) VALUES (0, N'*', N'Operator')
INSERT [dbo].[Equation] ([fk], [Item], [Type]) VALUES (0, N'1', N'Element')
INSERT [dbo].[Equation] ([fk], [Item], [Type]) VALUES (1, N'2', N'Element')
INSERT [dbo].[Equation] ([fk], [Item], [Type]) VALUES (1, N'+', N'Operator')
INSERT [dbo].[Equation] ([fk], [Item], [Type]) VALUES (1, N'3', N'Element')
INSERT [dbo].[Equation] ([fk], [Item], [Type]) VALUES (1, N'+', N'Operator')
INSERT [dbo].[Equation] ([fk], [Item], [Type]) VALUES (1, N'0.5', N'constant')
INSERT [dbo].[Equation] ([fk], [Item], [Type]) VALUES (1, N'*', N'Operator')
INSERT [dbo].[Equation] ([fk], [Item], [Type]) VALUES (1, N'5', N'Element')
INSERT [dbo].[Equation] ([fk], [Item], [Type]) VALUES (2, N'15', N'constant')
INSERT [dbo].[Equation] ([fk], [Item], [Type]) VALUES (2, N'+', N'Operator')
INSERT [dbo].[Equation] ([fk], [Item], [Type]) VALUES (2, N'40', N'constant')
INSERT [dbo].[Equation] ([fk], [Item], [Type]) VALUES (3, N'6', N'Element')
INSERT [dbo].[Equation] ([fk], [Item], [Type]) VALUES (3, N'+', N'Operator')
INSERT [dbo].[Equation] ([fk], [Item], [Type]) VALUES (3, N'7', N'Element')
INSERT [dbo].[Equation] ([fk], [Item], [Type]) VALUES (4, N'10', N'Constant')
INSERT [dbo].[Equation] ([fk], [Item], [Type]) VALUES (5, N'10', N'Constant')
INSERT [dbo].[Equation] ([fk], [Item], [Type]) VALUES (5, N'+', N'Operator')
INSERT [dbo].[Equation] ([fk], [Item], [Type]) VALUES (5, N'4', N'Element')
INSERT [dbo].[Equation] ([fk], [Item], [Type]) VALUES (6, N'10', N'Constant')
INSERT [dbo].[Equation] ([fk], [Item], [Type]) VALUES (6, N'+', N'Operator')
INSERT [dbo].[Equation] ([fk], [Item], [Type]) VALUES (6, N'5', N'Constant')
INSERT [dbo].[Equation] ([fk], [Item], [Type]) VALUES (7, N'5', N'Element')
INSERT [dbo].[Equation] ([fk], [Item], [Type]) VALUES (7, N'+', N'Operator')
INSERT [dbo].[Equation] ([fk], [Item], [Type]) VALUES (7, N'2', N'Element')
*
#Gordon Linoff
... Thanks a lot for your care but i found some errors using the provided script that i cant solve as it is the first time for me to use CTE
The second script gives me some errors that i cant solve
the first script gives me errors as well that i cant solve
You can try it like this:
WITH Related AS
(
SELECT *
FROM Equation AS eq
LEFT JOIN Element AS e ON eq.[Type]='Element' AND eq.Item=CAST(e.Id AS VARCHAR(10))
WHERE eq.[Type]='Element'
)
,Dependecies AS
(
SELECT e.*
,ISNULL(r.Name,'') AS DepName
FROM Element AS e
LEFT JOIN Related AS r ON e.Id=r.fk
)
,recCTE AS
(
SELECT 1 AS lvl,d.Id,d.Name,d.DepName
FROM Dependecies AS d
WHERE d.Name NOT IN(SELECT x.DepName FROM Dependecies AS x)
UNION ALL
SELECT r.lvl+1,d.Id,d.Name,d.DepName
FROM recCTE AS r
INNER JOIN Dependecies AS d ON r.DepName=d.Name
)
,Ranked AS
(
SELECT Name
,DENSE_RANK() OVER(ORDER BY CASE WHEN DepName='' THEN 1000 ELSE lvl END DESC) AS Rnk
FROM recCTE
)
SELECT Name,MIN(Rnk) AS Rnk
FROM Ranked
GROUP BY Name
ORDER BY Min(Rnk)
The result
Equation 2 1
Equation 4 1
Equation 6 1
Equation 5 2
Equation 7 3
Equation 3 4
Equation 1 5
Equation 0 6
Explanation
There is a list of CTEs:
The first CTE will bind the Elements to Equation rows, where the type is Element.
The second will list all Elements with their dependencies
The third CTE is a recursive CTE, starting with the element without any dependecies, working down the dependency path
The next CTE uses DENSE_RANK() OVER() to get the calls ordered
The final SELECT returns each element and the earliest moment it is needed.
Oh, it would be swell if we could express this as:
with cte as (
select e.fk, 1 as lev
from equation e
group by e.fk
having sum(case when type = 'Element' then 1 else 0 end) = 0
union all
select e.fk, max(cte.lev) + 1
from equation e left join
cte
on e.fk = cte.fk
group by e.fk
having count(*) = count(cte.fk)
)
But that is not possible. So, we have to think in terms of string manipulation (I think). This results in putting the dependencies in a string and repeatedly chopping elements off of the string. If I have this right:
with eq as (
select e.fk,
stuff( (select ',' + e2.item
from equation e2
where e2.fk = e.fk and e2.type = 'Element'
order by e2.item
for xml path ('')
), 1, 1, '') as elements
from (select distinct e.fk from equation e) e
)
select e.fk, '' as elements_found, 1 as lev
from eq
where elements = ''
union all
select eq.fk, substring(elements_found, charindex(',', elements_found + ',') + 1), 2 as lev
from eq join
cte
on cte.elements_found like eq.fk + ',%' and eq.fk = cte.fk
where eq.type = 'Element'
)
select cte.fk, max(lev)
from cte
group by cte.fk
order by max(lev);

Add not relevant column to group by script

This is my sample table and values:
CREATE TABLE [dbo].[Test]
(
[Id] BIGINT NOT NULL DEFAULT(0),
[VId] BIGINT NOT NULL DEFAULT(0),
[Level] INT NOT NULL DEFAULT(0)
);
INSERT INTO [dbo].[Test] ([Id], [VId], [Level]) VALUES (100, 1, 1);
INSERT INTO [dbo].[Test] ([Id], [VId], [Level]) VALUES (101, 1, 2);
INSERT INTO [dbo].[Test] ([Id], [VId], [Level]) VALUES (102, 1, 3);
INSERT INTO [dbo].[Test] ([Id], [VId], [Level]) VALUES (103, 2, 1);
INSERT INTO [dbo].[Test] ([Id], [VId], [Level]) VALUES (104, 3, 1);
INSERT INTO [dbo].[Test] ([Id], [VId], [Level]) VALUES (105, 3, 2);
INSERT INTO [dbo].[Test] ([Id], [VId], [Level]) VALUES (106, 4, 1);
INSERT INTO [dbo].[Test] ([Id], [VId], [Level]) VALUES (107, 4, 2);
INSERT INTO [dbo].[Test] ([Id], [VId], [Level]) VALUES (108, 4, 3);
INSERT INTO [dbo].[Test] ([Id], [VId], [Level]) VALUES (109, 4, 4);
So at now I use this script:
SELECT
[T].[VId], MAX ([T].[Level]) AS [MaxLevel]
FROM
[dbo].[Test] AS [T]
GROUP BY
[T].[VId];
And it returns:
VId MaxLevel
1 3
2 1
3 2
4 4
But I need Id column also and I can't add it to Group by script, I need the following values:
VId MaxLevel Id
1 3 102
2 1 103
3 2 105
4 4 109
What is your suggestion?
Also The following values is enough The Id's With Max(Level) in any VId :
Id
102
103
105
109
A 2008 take on the question, since that's what you're working with:
declare #Test table
(
[Id] BIGINT NOT NULL DEFAULT(0),
[VId] BIGINT NOT NULL DEFAULT(0),
[Level] INT NOT NULL DEFAULT(0)
);
INSERT INTO #Test ([Id], [VId], [Level])
VALUES (100, 1, 1),(101, 1, 2),(102, 1, 3),(103, 2, 1),(104, 3, 1),
(105, 3, 2),(106, 4, 1),(107, 4, 2),(108, 4, 3),(109, 4, 4);
;With Numbered as (
select *,
RANK() OVER (PARTITION BY VId ORDER BY [Level] desc) as rn
from #Test)
select VId,Level,Id from Numbered where rn=1
Note that (as with the other solutions) this will output multiple rows per VId if there are two rows with the same maximum level. If you don't want that, switch RANK() to ROW_NUMBER() and an arbitrary one will win - or if you want a specific winner in the case of a tie, add that condition into the ORDER BY of the window function.
Use joining with the same table by VId column
something like this:
SELECT [T].[VId], [T].[MaxLevel], [T1].[Id]
FROM [dbo].[Test] AS [T1] JOIN
(SELECT [T].[VId], MAX ([T].[Level]) AS [MaxLevel]
FROM [dbo].[Test] AS [T]
GROUP BY [T].[VId]) AS [T]
ON [T1].[VId] = [T].[VId]
AND [T1].[Level] = [T].[MaxLevel]
ORDER BY [T].[VId];
the result will be:
VId MaxLevel Id
1 3 102
2 1 103
3 2 105
4 4 109
Use this:
WITH LastLevels AS
(
SELECT
[T].[VId] AS [VID],
MAX ([T].[Level]) AS [MaxLevel]
FROM [dbo].[Test] AS [T]
GROUP BY [T].[VId]
)
SELECT [LastLevels].[VID],[LastLevels].[MaxLevel], [Te].[Id]
FROM [dbo].[Test] AS [Te]
INNER JOIN [LastLevels]
ON [LastLevels].[VID]=[Te].[VId]
AND [LastLevels].[MaxLevel]=[Te].[Level]
ORDER BY [LastLevels].[VID];
SELECT ID, [VId], [MaxLevel] From
(
SELECT
[T].[VId], MAX ([T].[Level]) AS [MaxLevel]
FROM
[dbo].[Test] AS [T]
GROUP BY
[T].[VId]
)K
INNER JOIN [Test] T on T.[VId] = K.[VId] and T.[Level] = K.MaxLevel