Sql Server2005 query problem - sql

i have a table which contains the following fields
Supervisorid
Empid
This is just like a referral program. A guy can refer 3 guys under him i.e,
3 is referring three guys namely 4 5 8 similarly 4 is referring 9 10 and 11 likewise 8 is referring 12, 13 it goes like this..
I want a query to get the total no of down line members under a guy say 3

You can make use of Recursive CTE.
Something like this
DECLARE #Table TABLE(
Supervisorid INT,
Empid INT
)
INSERT INTO #Table SELECT 3, 4
INSERT INTO #Table SELECT 3, 5
INSERT INTO #Table SELECT 3, 8
INSERT INTO #Table SELECT 4, 9
INSERT INTO #Table SELECT 4, 10
INSERT INTO #Table SELECT 4, 11
INSERT INTO #Table SELECT 8, 12
INSERT INTO #Table SELECT 8, 13
DECLARE #ID INT
SELECT #ID = 3
;WITH Vals AS (
SELECT *
FROM #Table
WHERE SuperVisorID = #ID
UNION ALL
SELECT v.SuperVisorID,
t.Empid
FROM Vals v INNER JOIN
#Table t ON v.Empid = t.Supervisorid
)
SELECT SuperVisorID,
COUNT(Empid) Total
FROM Vals
GROUP BY SuperVisorID

Related

Recursive CTE to find all records SQL Server

I am having below table structure. I want to write a recursive cte to get the bottom most table result.
Highly appreciate your help.
CREATE TABLE Jobcard (
jobcard_id INT NOT NULL PRIMARY KEY,
jobcard_name varchar(20) NOT NULL,
);
CREATE TABLE Vehicle (
vehicle_id INT NOT NULL PRIMARY KEY,
vehicle_name varchar(20) NOT NULL
);
CREATE TABLE Jobacard_vehicle (
jobcard_id INT NOT NULL,
vehicle_id INT NOT NULL
);
INSERT INTO Jobcard (jobcard_id, jobcard_name) VALUES
(1, 'Job1'),(2, 'Job2'),(3, 'Job3'),
(4, 'Job4'),(5, 'Job5'),(6, 'Job6'),
(7, 'Job7'),(8, 'Job8'),(9, 'Job9');
INSERT INTO Vehicle (vehicle_id, vehicle_name) VALUES
(1, 'Vehicle1'),(2, 'Vehicle2'),(3, 'Vehicle3'),
(4, 'Vehicle4'),(5, 'Vehicle5'),(6, 'Vehicle6');
INSERT INTO Jobacard_vehicle (jobcard_id, vehicle_id) VALUES
(3, 1),(4, 2),(5, 3),
(9, 6),(7, 2),(5, 4),
(8, 4),(6, 1),(3, 5);
jobcard_id, vehicle_id
--------------------------
3 1
4 2
5 3
9 6
7 2
5 4
8 4
6 1
3 5
I want to get this result from Jobacard_vehicle table when I pass the vehicle id as 3 as
vehicle id 3 is having jobcard 5
jobcard 5 is having vehicle 4
again vehicle 4 referred to jobcard 8
means all the jobcard refered by 3 or its counter part jobcards vehicles
jobcard_id, vehicle_id
--------------------------
5 3
5 4
8 4
Thank you.
Try to save full path and check it in the next recursion
DECLARE #startVehicleID int=3
;WITH vehCTE AS(
SELECT jobcard_id,vehicle_id,CAST(CONCAT('(',jobcard_id,',',vehicle_id,')') AS varchar(MAX)) [path]
FROM Jobacard_vehicle
WHERE vehicle_id=#startVehicleID
UNION ALL
SELECT v.jobcard_id,v.vehicle_id,c.[path]+CONCAT('(',v.jobcard_id,',',v.vehicle_id,')')
FROM Jobacard_vehicle v
JOIN vehCTE c ON (v.jobcard_id=c.jobcard_id OR v.vehicle_id=c.vehicle_id) AND CHARINDEX(CONCAT('(',v.jobcard_id,',',v.vehicle_id,')'),c.[path])=0
)
SELECT *
FROM vehCTE
ORDER BY [path]
If you need to check Job->Vehicle->Job->Vehicle->... then I think you can use the following
DECLARE #startVehicleID int=3
;WITH vehCTE AS(
SELECT
jobcard_id,
vehicle_id,
CAST(CONCAT('(',jobcard_id,',',vehicle_id,')') AS varchar(MAX)) [path],
1 NextIsJob
FROM Jobacard_vehicle
WHERE vehicle_id=#startVehicleID
UNION ALL
SELECT
v.jobcard_id,
v.vehicle_id,
c.[path]+CONCAT('(',v.jobcard_id,',',v.vehicle_id,')'),
IIF(c.NextIsJob=1,0,1)
FROM Jobacard_vehicle v
JOIN vehCTE c ON ((c.NextIsJob=1 AND v.jobcard_id=c.jobcard_id) OR (c.NextIsJob=0 AND v.vehicle_id=c.vehicle_id)) AND CHARINDEX(CONCAT('(',v.jobcard_id,',',v.vehicle_id,')'),c.[path])=0
)
SELECT *
FROM vehCTE
ORDER BY [path]

Segregate data between 2 intervals based on an identifier

One of my friends had this Q to me and I am too puzzled.
His team is loading a DW and the data keeps coming in incremental and full load fashion on an adhoc basic. Now there is identifier flag that says
as to when the full load has started or stopped. Now we need to collect and then segregate all full load.
For ex:
create table #tmp (
id int identity(1,1) not null,
name varchar(30) null,
val int null
)
insert into #tmp (name, val) select 'detroit', 3
insert into #tmp (name, val) select 'california', 9
insert into #tmp (name, val) select 'houston', 1
insert into #tmp (name, val) select 'los angeles', 4
insert into #tmp (name, val) select 'newyork', 8
insert into #tmp (name, val) select 'chicago', 1
insert into #tmp (name, val) select 'seattle', 9
insert into #tmp (name, val) select 'michigan', 6
insert into #tmp (name, val) select 'atlanta', 9
insert into #tmp (name, val) select 'philly', 6
insert into #tmp (name, val) select 'brooklyn', 8
drop table #tmp
The rule is:
whenever val is 9, the full load starts; whenever val is 8, the full
load stops; (or when whenever next val is 8, full load stops).
In this case, for full load, I should only collect these records:
id name val
3 houston 1
4 los angeles 4
10 philly 6
My Approach so far:
;with mycte as (
select id, name, val, row_number() over (order by id) as rnkst
from #tmp
where val in (8,9))
SELECT *
FROM mycte y
WHERE val = 9
AND Exists (
SELECT *
FROM mycte x
WHERE x.id =
----> this gives start 9 record but not stop record of 8
(SELECT MIN(id)
FROM mycte z
WHERE z.id > y.id)
AND val = 8)
I do not want to venture into cursor within cursor approach but with a CTE , please enlighten!
UPDATE:
As mentioned by one of the answerers I am restating the rules.
--> the full load records start coming AFTER 9. (9th records are NOT included)
--> the full load continues till it sees immediate 8.
--> So effectively all records BETWEEN 9 and 8 form small chunks of full load
--> An individual 9th record itself does not get considered as it has no 8 as partner
--> The result set shown below satisfies these conditions
I am not sure if my command of English will allow me to explain my approach fully, but I'll try, just in case it can help.
Rank all the rows and rank the bounds (val IN (8, 9)) separately.
Join the subset where val = 8 with the subset where val = 9 on the condition that the bound ranking of the former should be exactly 1 (one) greater than that of the latter.
Join the subset of non-(8, 9) rows to the result set of Step 2 on the condition that the (general) ranking should be between the ranking of the val = 9 subset and that of the val = 8 one.
Here's the query to illustrate my attempt at verbal description:
WITH ranked AS (
SELECT
*,
rnk = ROW_NUMBER() OVER (ORDER BY id),
bound_rnk = ROW_NUMBER() OVER (
PARTITION BY CASE WHEN val IN (8, 9) THEN 1 ELSE 2 END
ORDER BY id
)
FROM #tmp
)
SELECT
load.id,
load.name,
load.val
FROM ranked AS eight
INNER JOIN ranked AS nine ON eight.bound_rnk = nine.bound_rnk + 1
INNER JOIN ranked AS load ON load.rnk BETWEEN nine.rnk AND eight.rnk
WHERE eight.val = 8
AND nine .val = 9
AND load .val NOT IN (8, 9)
;
And you might not believe me but, when I tested it, it did return the following:
id name val
-- ----------- ---
3 houston 1
4 los angeles 4
10 philly 6
I do not believe there is a way to do this without a while loop or possibly a recursive cte that is going to be complex. So, my question would be if this is at all possible to accomplish in code? SQL is not as strong as a procedural language, so code would handle this better. If this is not an option, then I would go with a while loop (MUCH better than a cursor). I will create the SQL for this shortly.
/*
drop table #tmp
drop table #finalTmp
drop table #startStop
*/
create table #tmp (
id int identity(1,1) not null,
name varchar(30) null,
val int null
)
insert into #tmp (name, val) select 'detroit', 3
insert into #tmp (name, val) select 'california', 9
insert into #tmp (name, val) select 'houston', 1
insert into #tmp (name, val) select 'los angeles', 4
insert into #tmp (name, val) select 'newyork', 8
insert into #tmp (name, val) select 'chicago', 1
insert into #tmp (name, val) select 'seattle', 9
insert into #tmp (name, val) select 'michigan', 6
insert into #tmp (name, val) select 'atlanta', 9
insert into #tmp (name, val) select 'philly', 6
insert into #tmp (name, val) select 'brooklyn', 8
CREATE TABLE #Finaltmp
(
id INT,
name VARCHAR(30),
val INT
)
SELECT id, val, 0 AS Checked
INTO #StartStop
FROM #tmp
WHERE val IN (8,9)
DECLARE #StartId INT, #StopId INT
WHILE EXISTS (SELECT 1 FROM #StartStop WHERE Checked = 0)
BEGIN
SELECT TOP 1 #StopId = id
FROM #StartStop
WHERE EXISTS
--This makes sure we grab a stop that has a start before it
(
SELECT 1
FROM #StartStop AS TestCheck
WHERE TestCheck.id < #StartStop.id AND val = 9
)
AND Checked = 0 AND val = 8
ORDER BY id
--If no more starts, then the rest are stops
IF #StopId IS NULL
BREAK
SELECT TOP 1 #StartId = id
FROM #StartStop
WHERE Checked = 0 AND val = 9
--Make sure we only pick up the 9 that matches
AND Id < #StopId
ORDER BY Id DESC
IF #StartId IS NULL
BREAK
INSERT INTO #Finaltmp
SELECT *
FROM #tmp
WHERE id BETWEEN #StartId AND #StopId
AND val NOT IN (8,9)
--Make sure to "check" any values that fell in the middle (double 9's)
--If not, then you would start picking up overlap data
UPDATE #StartStop
SET Checked = 1
WHERE id <= #StopId
END
SELECT * FROM #Finaltmp
I noticed that the data looked a little wonky, so I tried to put some edge case checks and comments about them

Get parent of parent of Parent from self join table

Pleae copy and run following script.
DECLARE #Locations TABLE
(
LocationId INT,
LocationName VARCHAR(50),
ParentId INT
)
INSERT INTO #Locations SELECT 1, 'Europe', NULL
INSERT INTO #Locations SELECT 2, 'UK', 1
INSERT INTO #Locations SELECT 3, 'England', 2
INSERT INTO #Locations SELECT 4, 'Scotland', 2
INSERT INTO #Locations SELECT 5, 'Wales', 2
INSERT INTO #Locations SELECT 6, 'Cambridgeshire', 3
INSERT INTO #Locations SELECT 7, 'Cambridge', 6
INSERT INTO #Locations SELECT 8, 'North Scotland', 4
INSERT INTO #Locations SELECT 9, 'Inverness', 8
INSERT INTO #Locations SELECT 10, 'Somerset', 3
INSERT INTO #Locations SELECT 11, 'Bath', 10
INSERT INTO #Locations SELECT 12, 'Poland', 1
INSERT INTO #Locations SELECT 13, 'Warsaw', 12
I need following kind of result
Thanks.
There's no way you can do this with the current set of data; how would you know that in the case of LocationId=11, you have a county/country/continent, while in the case of LocationId=13, there's no county - just a country/continent??
And how do you know to "skip" the entries for Somerset, North Scotland etc. from your output result??
You definitely need more information here....
With this recursive CTE (Common Table Expression) query, you can get the "ladder" up the hierarchy to the top, for any given location:
DECLARE #LocID INT = 13
;WITH LocationHierarchy AS
(
SELECT LocationId, LocationName, ParentId, 1 AS 'Level'
FROM #Locations
WHERE LocationId = #LocID
UNION ALL
SELECT l.LocationId, l.LocationName, l.ParentId, lh.Level + 1 AS 'Level'
FROM #Locations l
INNER JOIN LocationHierarchy lh ON lh.ParentId = l.LocationId
)
SELECT
LocationName,
LocationId,
Level
FROM LocationHierarchy
This CTE works on SQL Server 2005 and up - on SQL Server 2000, you're out of luck, unfortunately (time to upgrade!!).
This again allows you to walk up the hierarchy for a single entry - but it cannot possibly return that data set you're looking for - there's not enough information to determine this from the current data.
For #LocID=13 (Warsaw), you get this output:
LocationName LocationId Level
Warsaw 13 1
Poland 12 2
Europe 1 3
and for #LocID=7 (Cambridge), you get:
LocationName LocationId Level
Cambridge 7 1
Cambridgeshire 6 2
England 3 3
UK 2 4
Europe 1 5
From there on, you'd have to use some smarts in your app to get the exact output you're looking for.

t-sql recursive query

Based on an existing table I used CTE recursive query to come up with following data. But failing to apply it a level further.
Data is as below
id name parentid
--------------------------
1 project 0
2 structure 1
3 path_1 2
4 path_2 2
5 path_3 2
6 path_4 3
7 path_5 4
8 path_6 5
I want to recursively form full paths from the above data. Means the recursion will give the following output.
FullPaths
-------------
Project
Project\Structure
Project\Structure\Path_1
Project\Structure\Path_2
Project\Structure\Path_3
Project\Structure\Path_1\path_4
Project\Structure\Path_2\path_5
Project\Structure\Path_3\path_6
Thanks
Here's an example CTE to do that:
declare #t table (id int, name varchar(max), parentid int)
insert into #t select 1, 'project' , 0
union all select 2, 'structure' , 1
union all select 3, 'path_1' , 2
union all select 4, 'path_2' , 2
union all select 5, 'path_3' , 2
union all select 6, 'path_4' , 3
union all select 7, 'path_5' , 4
union all select 8, 'path_6' , 5
; with CteAlias as (
select id, name, parentid
from #t t
where t.parentid = 0
union all
select t.id, parent.name + '\' + t.name, t.parentid
from #t t
inner join CteAlias parent on t.parentid = parent.id
)
select *
from CteAlias
Try something like this:
WITH Recursive AS
(
SELECT
ID,
CAST(PathName AS VARCHAR(500)) AS 'FullPaths',
1 AS 'Level'
FROM
dbo.YourTable
WHERE
ParentID = 0
UNION ALL
SELECT
tbl.ID,
CAST(r.FullPaths + '\' + tbl.PathName AS VARCHAR(500)) AS 'FullPaths',
r.Level + 1 AS 'Level'
FROM
dbo.YourTable tbl
INNER JOIN
Recursive r ON tbl.ParentID = r.ID
)
SELECT * FROM Recursive
ORDER BY Level, ID
Output:
ID FullPaths Level
1 project 1
2 project\structure 2
3 project\structure\path_1 3
4 project\structure\path_2 3
5 project\structure\path_3 3
6 project\structure\path_1\path_4 4
7 project\structure\path_2\path_5 4
8 project\structure\path_3\path_6 4
try this:
DECLARE #YourTable table (id int, nameof varchar(25), parentid int)
INSERT #YourTable VALUES (1,'project',0)
INSERT #YourTable VALUES (2,'structure',1)
INSERT #YourTable VALUES (3,'path_1',2)
INSERT #YourTable VALUES (4,'path_2',2)
INSERT #YourTable VALUES (5,'path_3',2)
INSERT #YourTable VALUES (6,'path_4',3)
INSERT #YourTable VALUES (7,'path_5',4)
INSERT #YourTable VALUES (8,'path_6',5)
;WITH Rec AS
(
SELECT
CONVERT(varchar(max),nameof) as nameof,id
FROM #YourTable
WHERE parentid=0
UNION ALL
SELECT
CONVERT(varchar(max),r.nameof+'\'+y.nameof), y.id
FROM #yourTable y
INNER jOIN Rec r ON y.parentid=r.id
)
select * from rec
output:
nameof
-----------------------------------------------
project
project\structure
project\structure\path_1
project\structure\path_2
project\structure\path_3
project\structure\path_3\path_6
project\structure\path_2\path_5
project\structure\path_1\path_4
(8 row(s) affected)
Something like
;WITH MyCTE AS
(
SELECT
name AS FullPaths, id
FROM
MyTable
WHERE
parentid = 0 /*Normally it'd be IS NULL with an FK linking the 2 columns*/
UNION ALL
SELECT
C.FullPaths + '\' + M.name, M.id
FROM
MyCTE C
JOIN
MyTable M ON M.parentid = C.id
)
SELECT FullPaths FROM MyCTE
You'll have to change the name of #test table I was using.
WITH cte(id, name, parentid) AS
(
SELECT id, convert(varchar(128), name), parentid
FROM #test
WHERE parentid = 0
UNION ALL
SELECT t.id, convert(varchar(128), c.name +'\'+t.name), t.parentid
FROM #test t
INNER JOIN cte c
ON c.id = t.parentid
)
SELECT name as FullPaths
FROM cte
order by id

SQL Server + Select only two records for each masterID in a table

I got a child table that contains 1 to n records linked to the master table via a MasterID column.
How can I select from the child table only the first 5 records for each MasterID?
Using Sql Server CTE and ROW_NUMBER you could try using
DECLARE #ParentTable TABLE(
ID INT
)
INSERT INTO #ParentTable SELECT 1
INSERT INTO #ParentTable SELECT 2
INSERT INTO #ParentTable SELECT 3
DECLARE #ChildTable TABLE(
ID INT,
ParentID INT
)
INSERT INTO #ChildTable SELECT 1, 1
INSERT INTO #ChildTable SELECT 2, 1
INSERT INTO #ChildTable SELECT 3, 1
INSERT INTO #ChildTable SELECT 4, 1
INSERT INTO #ChildTable SELECT 5, 1
INSERT INTO #ChildTable SELECT 6, 1
INSERT INTO #ChildTable SELECT 7, 1
INSERT INTO #ChildTable SELECT 8, 2
INSERT INTO #ChildTable SELECT 9, 2
INSERT INTO #ChildTable SELECT 10, 3
INSERT INTO #ChildTable SELECT 11, 3
;WITH RowNums AS(
SELECT pt.ID ParentID,
ct.ID ChildID,
ROW_NUMBER() OVER (PARTITION BY pt.ID ORDER BY ct.ID) RowNum
FROM #ParentTable pt INNER JOIN
#ChildTable ct ON pt.ID = ct.ParentID
)
SELECT ParentID,
ChildID
FROM RowNums
WHERE RowNum <= 5
Try a regular join where the constraint is a subquery pulling the TOP 5 from the child table. In untested pseudocode:
SELECT A.MasterID, B.*
FROM MasterTable A
JOIN ChildTable B
ON A.MasterID = B.MasterID
AND B.ChildID IN (SELECT Top 5 ChildID FROM ChildTable
WHERE MasterID = A.MasterID ORDER BY Whatever)