Select Data with Order By Respecting Ancestor - sql

I have a table MyStackFiles that has 3 columns:
FileID (The primary key)
FileName
OriginalFileID (This can be either 0 if there is no original file or one of the other file IDs)
My goal is to select the whole data sorted by name. In addition, I need to always have the original files appear before their children. In other words, the desired result will start with the first alphabetical file whose OriginalFileID is 0 followed by all its children (if available) alphabetically. The following SQL script creates the sample data and illustrates exactly what I'm trying to achieve. Notice that the last select command is the desired output.
What is the query that can return the desired result?
The Script:
-------------------------- Creating the Example Schema --------------------------
IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.Tables WHERE Table_Name = 'MyStackFiles')
Drop table MyStackFiles
GO
CREATE TABLE [dbo].[MyStackFiles](
[FileID] [int] IDENTITY(1,1) NOT NULL,
[FileName] [varchar](50) NULL,
[OriginalFileID] [int] NOT NULL DEFAULT (0),
CONSTRAINT [PK_MyStackFiles] PRIMARY KEY CLUSTERED
(
[FileID] 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
-------------------------- Insert Into the Sample Data --------------------------
INSERT INTO MyStackFiles(FileName) values ('S')
INSERT INTO MyStackFiles(FileName) values ('G')
INSERT INTO MyStackFiles(FileName, OriginalFileID) values ('E', 1)
INSERT INTO MyStackFiles(FileName) values ('F')
INSERT INTO MyStackFiles(FileName, OriginalFileID) values ('Q', 2)
INSERT INTO MyStackFiles(FileName, OriginalFileID) values ('N', 3)
INSERT INTO MyStackFiles(FileName) values ('A')
INSERT INTO MyStackFiles(FileName, OriginalFileID) values ('X', 1)
INSERT INTO MyStackFiles(FileName) values ('W')
------------------------------------------------------------------------------
GO
-------------------------- Simple select sorted by FileName --------------------------
SELECT * From MyStackFiles ORDER BY FileName
-------------------------- A representation of the desired result --------------------------
SELECT * FROM MyStackFiles WHERE FileID = 7 UNION ALL -- We insert "A" (respecting the alphabetical order) since its OriginalFileID is 0
SELECT * FROM MyStackFiles WHERE FileID = 4 UNION ALL -- Then we insert F.
SELECT * FROM MyStackFiles WHERE FileID = 2 UNION ALL -- Then we insert G. G has children so we insert them
SELECT * FROM MyStackFiles WHERE FileID = 5 UNION ALL -- Q is the only child of G. We insert it
SELECT * FROM MyStackFiles WHERE FileID = 1 UNION ALL -- Now we insert S. Notice that S has two children (E and X)
SELECT * FROM MyStackFiles WHERE FileID = 3 UNION ALL -- E is before X alphabetically so it gets inserted first
SELECT * FROM MyStackFiles WHERE FileID = 6 UNION ALL -- E happens to have children so we insert them right away (in a depth first fashion)
SELECT * FROM MyStackFiles WHERE FileID = 8 UNION ALL -- Now we insert the other child of S which is X
SELECT * FROM MyStackFiles WHERE FileID = 9 -- Finally we insert W the only file left
--Drop Table MyStackFiles
I'm open to any schema modification if that helps find an efficient query.

I'm using the technique called Recursive CTE to try to solve your problem:
with t (RowID, FileID, FileName, OriginalFileID)
as (
select convert(varchar(max), row_number() over (order by s.FileName)), s.*
from MyStackFiles s
where s.OriginalFileID = 0
union all
select t.RowID + '.' + convert(varchar(max), row_number() over (order by s.FileName)), s.*
from MyStackFiles s
inner join t on t.FileID = s.OriginalFileID
)
select FileID, FileName, OriginalFileID from t
order by RowID
A temporary column RowID is created on-the-fly to chain up the ancestor's RowID to the row's row_number, so that for instance the file "N" will have RowID = '4.1.1', the file "X" will have RowID = '4.2', and this is the column to sort that fits your sorting requirement.

Related

Table not updating in single select query

i have a temporary table which i need to update, the first row is updated but the second row updates as null , please help
declare #T Table
(
ID int,
Name nvarchar(20),
rownum int
)
insert into #T(ID,rownum)
select ID, rownum = ROW_NUMBER() OVER(order by id) from testtabel4
select * from testtabel4
update #t
set Name=case when rownum>1 then (select top 1 Name from #T x where x.rownum=(y.rownum-1))
else 'first' end
from #t y
select * from #T
and here the definition of testtabel4
CREATE TABLE [dbo].[testtabel4](
[ID] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](80) NULL,
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
and here is the output
ID Name
1 first
2 NULL
I think your update would be better written with lag() and an updateable CTE.
with cte as (
select name, lag(name, 1, 'first') over(order by rownum) lag_name
from #t
)
update cte set name = lag_name
With this technique at hand, it is plain to see that don't actually need to feed the table first, then insert into it. You can do both at once, like so:
insert into #t (id, name, rownum)
select
id,
lag(name, 1, 'first') over(order by id),
row_number() over(order by id)
from testtabel4
I am not sure that you even need rownum column anymore, unless it is needed for some other purpose.
You are only inserting two columns in the #T:
insert into #T (ID, rownum)
select ID, ROW_NUMBER() OVER (order by id)
from testtabel4;
You are not inserting name so it is NULL on all rows. Hence, the then part of the case expression will always be NULL.

T-SQL Select row only if not exist already

I have a table with two Ids, ResourceId and LanguageId
I need to join those two selects where second result would be added only if ResourceId not already in the list.
SELECT * FROM Resources WHERE Language = 1
SELECT * FROM Resources WHERE Language = 0
JOIN
/*where ResourceId not present already*/
So far I came up with nothing except complicated partitions. Is there better solution to this?
Not all ResourceIds have Language 0 entry
Not all ResourceIds have Language 1 entry
Some ResourceIds have both
CREATE TABLE [dbo].[Resources](
[Id] [bigint] NOT NULL,
[ResourceId] [bigint] NOT NULL,
[LanguageId] [int] NOT NULL,
[Text] [nvarchar](2000) NULL,
[Path] [varchar](2000) NULL,
CONSTRAINT [PK_Resourcces] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
You could use a union with exists logic:
SELECT * FROM Resources WHERE Language = 1
UNION ALL
SELECT *
FROM Resources r1
WHERE
Language = 0 AND
NOT EXISTS (SELECT 1 FROM Resources r2
WHERE r2.Language = 1 AND r2.ResourceId = r1.ResourceId);
You can number the rows per resourceid by languageid using the row_number() window function and then just select the "first" one.
SELECT id,
resourceid,
languageid,
text,
path
FROM (SELECT id,
resourceid,
languageid,
text,
path,
row_number() OVER (PARTITION BY resourceid
ORDER BY languageid DESC) rn
FROM resources
WHERE languageid IN (0,
1) x
WHERE rn = 1;
Since I had started answering but Tim was more effective than me, I still put my answer as you indicated that, and I quote:
If somebody finds something faster and simpler, I would love to see it
USE TEST
CREATE DATABSE TEST
CREATE TABLE Ressources
(
RessourceId INT,
LanguageId INT
);
INSERT INTO Ressources
VALUES
(1,1),
(1,0),
(1,2),
(1,3),
(2,1),
(2,0),
(2,2),
(3,1),
(4,1),
(5,0)
WITH CTE_L1 AS (SELECT * FROM Ressources WHERE LanguageId = 1)
SELECT * FROM CTE_L1
UNION ALL
SELECT * FROM Ressources
WHERE LanguageId = 0
AND RessourceId NOT IN(SELECT RessourceId FROM CTE_L1)
Results I got:
RessourceId LanguageId
----------- -----------
1 1
2 1
3 1
4 1
5 0
(Same result if I execute the #Tim Biegeleisen query)
See which one you like best.
--> Cost of mine query 0.010132
--> Cost of Tim query 0.0100952
(Based on the execution plan)

How to get result from parent child table

Work on SQL-Server. My table structure is below
CREATE TABLE [dbo].[AgentInfo](
[AgentID] [int] NOT NULL,
[ParentID] [int] NULL,
CONSTRAINT [PK_AgentInfo] PRIMARY KEY CLUSTERED
(
[AgentID] 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
INSERT [dbo].[AgentInfo] ([AgentID], [ParentID]) VALUES (1, -1)
INSERT [dbo].[AgentInfo] ([AgentID], [ParentID]) VALUES (2, -1)
INSERT [dbo].[AgentInfo] ([AgentID], [ParentID]) VALUES (3, 1)
INSERT [dbo].[AgentInfo] ([AgentID], [ParentID]) VALUES (4, 2)
Required output
Use my below syntax get required output but not satisfied. Is there any better way to get the required output
--get parent child list
---step--1
SELECT *
INTO #temp1
FROM ( SELECT a.AgentID ,
a.ParentID,
a.AgentID AS BaseAgent
FROM dbo.AgentInfo a WHERE ParentID=-1
UNION ALL
SELECT a.ParentID ,
0 as AgentID,
a.AgentID AS BaseAgent
FROM dbo.AgentInfo a WHERE ParentID!=-1
UNION ALL
SELECT a.AgentID ,
a.ParentID,
a.AgentID AS BaseAgent
FROM dbo.AgentInfo a
WHERE ParentID!=-1
) AS d
SELECT * FROM #temp1
DROP TABLE #temp1
Help me to improve my syntax. If you have any questions please ask.
You could use a recursive SELECT, see the examples in the documentation for WITH, starting with example D.
The general idea within the recursive WITH is: You have a first select that is the starting point, and then a UNION ALL and a second SELECT which describes the step from on level to the next, where the previous level can either be the result of the first select or the result of the previous run of the second SELECT.
You can try this, to get a tree of the elements:
WITH CTE_AgentInfo(AgentID, ParentID, BaseAgent)
AS(
SELECT
AgentID,
ParentID,
AgentID AS BaseAgent
FROM AgentInfo
WHERE ParentID = -1
UNION ALL
SELECT
a.AgentID,
a.ParentID,
a.AgentID AS BaseAgent
FROM AgentInfo a
INNER JOIN CTE_AgentInfo c ON
c.AgentID = a.ParentID
)
SELECT * FROM CTE_AgentInfo
And here is an SQLFiddle demo to see it.
Try something like this:
WITH Merged (AgentId, ParentId) AS (
SELECT AgentId, ParentId FROM AgentInfo WHERE ParentId = -1
UNION ALL
SELECT AgentInfo.AgentId, AgentInfo.ParentId FROM AgentInfo INNER JOIN Merged ON AgentInfo.AgentId = Merged.ParentId
)
SELECT * FROM Merged
You can use a Common Table Expression to do this.
The sql statement will then look like this:
WITH [Parents]([AgentID], [ParentID], [BaseAgent])
AS
(
SELECT
[AgentID],
[ParentID],
[AgentID] AS [BaseAgent]
FROM [AgentInfo]
WHERE [ParentID] = -1
UNION ALL
SELECT
[ai].[AgentID],
[ai].[ParentID],
[p].[BaseAgent]
FROM [AgentInfo] [ai]
INNER JOIN [Parents] [p]
ON [ai].[ParentID] = [p].[AgentID]
)
SELECT *
FROM [Parents]
ORDER BY
[BaseAgent] ASC,
[AgentID] ASC
But, the results are different from your desired output, since every Agent is only listed once.
The output is:
AGENTID PARENTID BASEAGENT
1 -1 1
3 1 1
2 -1 2
4 2 2
The Fiddle is over here.
And here is a nice post on working with hierarchies: What are the options for storing hierarchical data in a relational database?

How To Get A Hierarchical CTE In SQL Server To Filter With Parent and Child Logic

I'm having a vexing problem with a hierarchical CTE and some strange logic that we need to address that I really hope someone could assist with pointing out what I'm doing wrong to address this scenario with a CTE.
Here is the hierarchical data we're dealing with in this example:
This is the problematic SQL followed by the description of the problem and SQL statements to create a test table with data:
DECLARE #UserId nvarchar(50);
SET #UserId = 'A';
DECLARE #StatusType int;
SET #StatusType = '2';
;WITH recursiveItems (Id, Depth)
AS
(
SELECT Id, 0 AS Depth
FROM dbo.CteTest
WHERE UserId = #UserId
--AND StatusType = #StatusType
-- This would also be incorrect for the issue
AND ParentId IS NULL
UNION ALL
SELECT dbo.CteTest.Id, Depth + 1
FROM dbo.CteTest
INNER JOIN recursiveItems
ON dbo.CteTest.ParentId = recursiveItems.Id
WHERE UserId = #UserId
AND StatusType = #StatusType
)
SELECT A.*, recursiveItems.Depth
FROM recursiveItems
INNER JOIN dbo.CteTest A WITH(NOLOCK) ON
recursiveItems.Id = A.Id
ORDER BY A.Id
This is not returning the desired data. The data that is currently returned is in the NOT CORRECT section of the image below. The row with the Id of 10 is the row that we want to omit.
Essentially the logic should be that any parent record (record with children) where the status type of any of its children is equal to 2 should be returned along with its children. In the example this is the rows with Ids: 1, 5, 6, 7, 9.
Currently the CTE/SQL/Code is returning ALL parent records no matter what,
The record with the Id 1 should be returned, even though it's status type is 1 because at least one of its children, their children, grandchildren, etc. have a status type that is equal to 2.
The record with the Id of 10 should not be returned because it does not have a status that is equal to 2 or any children. If the record had a status type of 2 when it has no child records it should also be returned.
This is the DDL to create a test table that helps to show the problem:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[CteTest](
[Id] [int] IDENTITY(1,1) NOT NULL,
[StatusType] [int] NOT NULL,
[UserId] [nvarchar](50) NOT NULL,
[ParentId] [int] NULL,
CONSTRAINT [PK_CteTest] 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
This is the seed data for the table, that can demonstrate the issue:
INSERT INTO [dbo].[CteTest]([StatusType],[UserId],[ParentId]) VALUES (1,'A',NULL)
INSERT INTO [dbo].[CteTest]([StatusType],[UserId],[ParentId]) VALUES (1,'B',NULL)
INSERT INTO [dbo].[CteTest]([StatusType],[UserId],[ParentId]) VALUES (2,'B',NULL)
INSERT INTO [dbo].[CteTest]([StatusType],[UserId],[ParentId]) VALUES (1,'A',1)
INSERT INTO [dbo].[CteTest]([StatusType],[UserId],[ParentId]) VALUES (2,'A',1)
INSERT INTO [dbo].[CteTest]([StatusType],[UserId],[ParentId]) VALUES (2,'A',5)
INSERT INTO [dbo].[CteTest]([StatusType],[UserId],[ParentId]) VALUES (2,'A',6)
INSERT INTO [dbo].[CteTest]([StatusType],[UserId],[ParentId]) VALUES (3,'A',6)
INSERT INTO [dbo].[CteTest]([StatusType],[UserId],[ParentId]) VALUES (2,'A',NULL)
INSERT INTO [dbo].[CteTest]([StatusType],[UserId],[ParentId]) VALUES (4,'A',NULL)
INSERT INTO [dbo].[CteTest]([StatusType],[UserId],[ParentId]) VALUES (3,'A',10)
The issue is that your base case includes all null (parentless) items, and there is no way to filter them out later.
Because you are looking for only items with a particular statustype, you may want to refactor the CTE; Instead of having a base case be the root values, you can have it be all items with the given statustype, and then recursively find the parents. In the solution below, I have depth be a negative number, for distance from the item with a value of 2 in the given tree (so negative height, instead of depth.).
DECLARE #UserId nvarchar(50);
SET #UserId = 'A';
DECLARE #StatusType int;
SET #StatusType = '2';
WITH recursiveItems (Id, ParentID, Depth)
AS
(
SELECT Id, ParentID, 0 AS Depth
FROM dbo.CteTest
WHERE UserId = #UserId AND StatusType = #StatusType
UNION ALL
SELECT dbo.CteTest.Id, CteTest.ParentID, Depth - 1
FROM dbo.CteTest
INNER JOIN recursiveItems
ON dbo.CteTest.Id = recursiveItems.ParentId
WHERE UserId = #UserId
)
SELECT A.Id, A.StatusType, A.UserId, A.ParentId, min(recursiveItems.Depth)
FROM recursiveItems
INNER JOIN dbo.CteTest A WITH(NOLOCK) ON
recursiveItems.Id = A.Id
group by A.Id, A.StatusType, A.UserId, A.ParentId
ORDER BY A.Id

Struggling to count and order by a column by reference in T-SQL database

I'm not sure if I'm writing the following SQL statement correctly? (Using T-SQL)
I have two tables:
Table 1: [dbo].[t_Orgnzs]
[id] = INT
[nm] = NVARCHAR(256)
Table 2: [dbo].[t_Usrs]
[id] = INT
[ds] = NVARCHAR(256)
[oid] = INT (referenced [dbo].[t_Orgnzs].[id])
I need to select elements from Table 2, ordered by the [oid] column ascending from 1 to 16, but the catch is that the [oid] references a string in the Table 1, that I actually need to return as a result.
So for say, if tables were laid out like so:
Table 1:
id nm
1 Name 1
2 Name 2
3 Name 3
4 Name 4
And Table 2:
id ds oid
1 A 2
2 B 4
3 C 1
The resulting query must return:
3 C Name 1
1 A Name 2
2 B Name 4
So here's the SQL I'm using:
WITH ctx AS (
SELECT [id],
[ds],
(SELECT [nm] FROM [dbo].[t_Orgnzs] WHERE [id]=[dbo].[t_Usrs].[oid]) AS organizName,
ROW_NUMBER() OVER (ORDER BY organizName ASC) AS rowNum
FROM [dbo].[t_Usrs]
)
SELECT [id], [ds], organizName
FROM ctx
WHERE rowNum>=1 AND rowNum<=16;
And I'm getting an error: "Invalid column name 'organizName'."
I do not understand the meaning of use ROW_NUMBER() in your case. Why?
CREATE TABLE [t_Orgnzs] ([id] int PRIMARY KEY, [nm] NVARCHAR(256))
GO
CREATE TABLE [t_Usrs] ([id] int, [ds] NVARCHAR(256), [oid] int FOREIGN KEY REFERENCES [t_Orgnzs]([id]))
GO
INSERT [t_Orgnzs] VALUES (1,'Name_1')
INSERT [t_Orgnzs] VALUES (2,'Name_2')
INSERT [t_Orgnzs] VALUES (3,'Name_3')
INSERT [t_Orgnzs] VALUES (4,'Name_4')
GO
INSERT [t_Usrs] VALUES (1,'A',2)
INSERT [t_Usrs] VALUES (2,'B',4)
INSERT [t_Usrs] VALUES (3,'C',1)
GO
SELECT *
FROM [t_Orgnzs]
INNER JOIN [t_Usrs] ON [t_Orgnzs].[id]=[t_Usrs].[oid]
ORDER BY [oid]
How about this one
select id, ds, nm
from
(
select ROW_NUMBER() OVER (ORDER BY o.nm ASC) AS rowNum, u.id, u.ds, o.nm
from t_Usrs u inner join t_Orgnzs o on (u.oid = o.id)
) t
WHERE rowNum>=1 AND rowNum<=16;
SELECT TOP 16 * FROM [t_Orgnzs]
INNER JOIN [t_Usrs]
ON [t_Orgnzs].[id] = [t_Usrs].[oid]
ORDER BY [oid]