(CTE) Recursive SQL Query - sql
I'm confused about some problem that related with recursive query.
I'm using SQL SERVER 2012
My scnerio,
locations are defined hierarchical,
Each locations has their own asset.
My Locations table like;
Id | Name | ParentLocationId
----+------+-----------------
1 | L1 | NULL
2 | L2 | 1
3 | L3 | 1
4 | L4 | 1
5 | L5 | 1
6 | L6 | 4
7 | L7 | 4
8 | L8 | 4
9 | L9 | 2
10 | L10 | 2
11 | L11 | 6
12 | L12 | 6
13 | L13 | 6
My Asset table like;
Id | AssetNo | Description | CurrentLocationId
-------+---------+-------------+------------------
1 | AN001 | ADesc | 1
2 | AN002 | BDesc | 1
L1 has 1, L2 has 2, L3 has 0, L4 has 3, L5 has 5, L6 has 5, L7 has 1,
L8 has 0, L9 has 3, L10 has 2, L11 has 5, L12 has 3, L13 has 6 Assets
My question is, how can I take the total number of selected Location's
1 level down asset count?
For Example; Selected LocationId = 1 (L1)
Sample Output is;
Id | Name | Qty
-------+------+-----
2 | L2 | 7
3 | L3 | 0
4 | L4 | 23
5 | L5 | 5
Another Example; Selected LocationId = 4 (L4)
Id | Name | Qty
---+------+-----
6 | L6 | 19
7 | L7 | 1
8 | L8 | 0
I try wrote a query,
WITH recursiveTable
AS (SELECT *
FROM location l
WHERE ParentLocationId = 1
UNION ALL
SELECT l.*
FROM location l
INNER JOIN recursiveTable r
ON r.Id = l.ParentLocationId),
allLocations
AS (SELECT *
FROM recursiveTable
UNION
SELECT *
FROM Location
WHERE Id = 0),
resultset
AS (SELECT r.NAME AS LocationName,
a.*
FROM allLocations r
INNER JOIN Asset a ON a.CurrentLocationId = r.Id
WHERE r.DataStatus = 1)
select CurrentLocationId
,min(LocationName) as LocationName
,count(Id) as NumberOfAsset
from resultset
group by CurrentLocationId
Additional;
Create Table Location
(
Id int,
Name nvarchar(100),
Description nvarchar(250),
ParentLocationId int,
DataStatus int
)
Create Table Asset
(
Id int,
AssetNo nvarchar(50),
Description nvarchar(250),
CurrentLocationId int,
DataStatus int
)
Insert Into Location Values(1,'L1','LDesc1',NULL,1)
Insert Into Location Values(2,'L2','LDesc2',1,1)
Insert Into Location Values(3,'L3','LDesc3',1,1)
Insert Into Location Values(4,'L4','LDesc4',1,1)
Insert Into Location Values(5,'L5','LDesc5',1,1)
Insert Into Location Values(6,'L6','LDesc6',4,1)
Insert Into Location Values(7,'L7','LDesc7',4,1)
Insert Into Location Values(8,'L8','LDesc8',4,1)
Insert Into Location Values(9,'L9','LDesc9',2,1)
Insert Into Location Values(10,'L10','LDesc10',2,1)
Insert Into Location Values(11,'L11','LDesc11',6,1)
Insert Into Location Values(12,'L12','LDesc12',6,1)
Insert Into Location Values(13,'L13','LDesc13',6,1)
Insert Into Asset Values (1,'FDB-001','Desc1',1,1)
Insert Into Asset Values (2,'FDB-002','Desc2',2,1)
Insert Into Asset Values (3,'FDB-003','Desc3',2,1)
Insert Into Asset Values (4,'FDB-004','Desc4',4,1)
Insert Into Asset Values (5,'FDB-005','Desc5',4,1)
Insert Into Asset Values (6,'FDB-006','Desc6',4,1)
Insert Into Asset Values (7,'FDB-007','Desc7',5,1)
Insert Into Asset Values (8,'FDB-008','Desc8',5,1)
Insert Into Asset Values (9,'FDB-009','Desc9',5,1)
Insert Into Asset Values (10,'FDB-010','Desc10',5,1)
Insert Into Asset Values (11,'FDB-011','Desc11',5,1)
Insert Into Asset Values (12,'FDB-012','Desc12',6,1)
Insert Into Asset Values (13,'FDB-013','Desc13',6,1)
Insert Into Asset Values (14,'FDB-014','Desc14',6,1)
Insert Into Asset Values (15,'FDB-015','Desc15',6,1)
Insert Into Asset Values (16,'FDB-016','Desc16',6,1)
Insert Into Asset Values (17,'FDB-017','Desc17',7,1)
Insert Into Asset Values (18,'FDB-018','Desc18',9,1)
Insert Into Asset Values (19,'FDB-019','Desc19',9,1)
Insert Into Asset Values (20,'FDB-020','Desc20',9,1)
Insert Into Asset Values (21,'FDB-021','Desc21',10,1)
Insert Into Asset Values (22,'FDB-022','Desc22',10,1)
Insert Into Asset Values (23,'FDB-023','Desc23',11,1)
Insert Into Asset Values (24,'FDB-024','Desc24',11,1)
Insert Into Asset Values (25,'FDB-025','Desc25',11,1)
Insert Into Asset Values (26,'FDB-026','Desc26',11,1)
Insert Into Asset Values (27,'FDB-027','Desc27',11,1)
Insert Into Asset Values (28,'FDB-028','Desc28',12,1)
Insert Into Asset Values (29,'FDB-029','Desc29',12,1)
Insert Into Asset Values (30,'FDB-030','Desc30',12,1)
Insert Into Asset Values (31,'FDB-031','Desc31',13,1)
Insert Into Asset Values (32,'FDB-032','Desc32',13,1)
Insert Into Asset Values (33,'FDB-033','Desc33',13,1)
Insert Into Asset Values (34,'FDB-034','Desc34',13,1)
Insert Into Asset Values (35,'FDB-035','Desc35',13,1)
Insert Into Asset Values (36,'FDB-036','Desc36',13,1)
Best Regards,
We can apply a Level and a Path to try and get your child counts, but only display the first level of children. We end up grouping the count of assets by the path, which is the ID of the first level of children. Then select only the first Level at the end
DECLARE #LocationID INT = 1;
WITH recursiveCTE AS
(
SELECT
*,
1 AS [Level],
Id [Path]
FROM
location l
WHERE
l.ParentLocationId = #LocationID
UNION ALL
SELECT
l.*,
[Level] + 1,
[Path]
FROM
location l
JOIN recursiveCTE r ON l.ParentLocationId = r.Id
),
countCte AS (
SELECT
[Path] Id,
COUNT(a.AssetNo) Qty
FROM recursiveCTE c
JOIN Asset a ON c.Id = a.CurrentLocationId
GROUP BY [Path]
)
SELECT r.Id,
r.[Name],
COALESCE(c.Qty,0) Qty
FROM recursiveCTE r
LEFT JOIN countCte c ON r.Id = c.Id
WHERE r.[Level] = 1;
Related
I dont understand what column ORA-01779 is refering too
I have a table with POI, you can have multiple POI on each city. SQL DEMO CREATE TABLE POI ("poi_id" int GENERATED BY DEFAULT AS IDENTITY, "city_id" int, PRIMARY KEY("poi_id") ); Now there were some changes on the cities polygons and now have to reassign some POI CREATE TABLE newCities ("city_id" int, "new_city_id" int) ; DATA INSERT ALL INTO POI ("poi_id", "city_id") VALUES (10, 1) INTO POI ("poi_id", "city_id") VALUES (11, 1) INTO POI ("poi_id", "city_id") VALUES (12, 2) INTO POI ("poi_id", "city_id") VALUES (13, 2) INTO POI ("poi_id", "city_id") VALUES (14, 5) SELECT * FROM dual ; INSERT ALL INTO newCities ("city_id", "new_city_id") VALUES (1, 100) INTO newCities ("city_id", "new_city_id") VALUES (2, 200) INTO newCities ("city_id", "new_city_id") VALUES (3, 200) SELECT * FROM dual ; When I do a JOIN: SELECT * FROM poi p JOIN newCities nc ON p."city_id" = nc."city_id"; OUTPUT +--------+---------+---------+-------------+ | poi_id | city_id | city_id | new_city_id | +--------+---------+---------+-------------+ | 10 | 1 | 1 | 100 | | 11 | 1 | 1 | 100 | | 12 | 2 | 2 | 200 | | 13 | 2 | 2 | 200 | +--------+---------+---------+-------------+ But when I try to do the update but got the error: ORA-01779: cannot modify a column which maps to a non key-preserved table UPDATE ( SELECT p.*, nc."new_city_id" FROM poi p JOIN newCities nc ON p."city_id" = nc."city_id" ) t SET t."city_id" = t."new_city_id"; I know city_id isn't a PK but the row is match with a row including a PK. So why isn't working? I know I can do a sub query to get the value: UPDATE poi p SET "city_id" = COALESCE((SELECT "new_city_id" FROM newCities c WHERE c."city_id" = p."city_id") , p."city_id"); But still want to know what cases the UPDATE JOIN would work because looks like only can work to update the PK:
After doing the sample test decide try creating a PK on the second table and works: SQL DEMO CREATE TABLE newCities ("city_id" int GENERATED BY DEFAULT AS IDENTITY, "new_city_id" int, PRIMARY KEY("city_id") );
Recursive SQL with 1:n relation to another table
I have a problem that is giving me a headache: We work with T-SQL (MS SQL-Server). I have a ragged parent/child hierachy in one table. Each row in the table with the parent/child relation (T1) has multiple values in another table (T2). My goal is to get the values from table T2 for each row of table T1, inluding those of it's ancenstors. Here is an example: T1 has the ragged parent child hierarchy. ClassID | ParentclassID ____________________|___________________________ 1 | NULL --------------------|--------------------------- 2 | 1 --------------------|--------------------------- 3 | 2 -------------------|--------------------------- 4 | 1 T2 has multiple values for each of the values from table T1 ClassID | FeatureID ____________________|___________________________ 1 | A --------------------|--------------------------- 1 | B --------------------|--------------------------- 2 | C --------------------|--------------------------- 2 | D --------------------|--------------------------- 3 | E --------------------|--------------------------- 4 | F My goal is the following Output: ClassID | FeatureID ____________________|___________________________ 1 | A --------------------|--------------------------- 1 | B --------------------|--------------------------- 2 | A --------------------|--------------------------- 2 | B --------------------|--------------------------- 2 | C --------------------|--------------------------- 2 | D --------------------|--------------------------- 3 | A --------------------|--------------------------- 3 | B --------------------|--------------------------- 3 | C --------------------|--------------------------- 3 | D --------------------|--------------------------- 3 | E --------------------|--------------------------- 4 | A --------------------|--------------------------- 4 | B --------------------|--------------------------- 4 | F If it would only be the ragged hierarchy, I could solve this with an recursive cte. But it is the 1:n relation to the table T2 that is causing the problems. Any suggestions will be highly appreciated.
Join on tree declare #H table (id int primary key, par int); insert into #H values (1, NULL) , (2, 1) , (3, 2) , (4, 1); DECLARE #Feature AS TABLE ( ClassID int, FeatureID char(1) ) INSERT INTO #Feature (ClassID, FeatureID) VALUES (1, 'A'), (1, 'B'), (2, 'C'), (2, 'D'), (3, 'E'), (4, 'F'), (5, 'G'); with cte as ( select h.id, h.par, h.id as tree from #H h union all select cte.id, cte.par, h.par from cte join #H H on cte.tree = h.id ) select * from cte join #Feature f on f.ClassID = cte.tree where cte.tree is not null order by cte.id, cte.par, cte.tree
Displaying whole table after stripping characters in SQL Server
This question has 2 parts. Part 1 I have a table "Groups": group_ID person ----------------------- 1 Person 10 2 Person 11 3 Jack 4 Person 12 Note that not all data in the "person" column have the same format. In SQL Server, I have used the following query to strip the "Person " characters out of the person column: SELECT REPLACE([person],'Person ','') AS [person] FROM Groups I did not use UPDATE in the query above as I do not want to alter the data in the table. The query returned this result: person ------ 10 11 12 However, I would like this result instead: group_ID person ------------------- 1 10 2 11 3 Jack 4 12 What should be my query to achieve this result? Part 2 I have another table "Details": detail_ID group1 group2 ------------------------------- 100 1 2 101 3 4 From the intended result in Part 1, where the numbers in the "person" column correspond to those in "group1" and "group2" of table "Details", how do I selectively convert the numbers in "person" to integers and join them with "Details"? Note that all data under "person" in Part 1 are strings (nvarchar(100)). Here is the intended query output: detail_ID group1 group2 ------------------------------- 100 10 11 101 Jack 12 Note that I do not wish to permanently alter anything in both tables and the intended output above is just a result of a SELECT query.
I don't think first part will be a problem here. Your query is working fine with your expected result. Schema: CREATE TABLE #Groups (group_ID INT, person VARCHAR(50)); INSERT INTO #Groups SELECT 1,'Person 10' UNION ALL SELECT 2,'Person 11' UNION ALL SELECT 3,'Jack' UNION ALL SELECT 4,'Person 12'; CREATE TABLE #Details(detail_ID INT,group1 INT, group2 INT); INSERT INTO #Details SELECT 100, 1, 2 UNION ALL SELECT 101, 3, 4 ; Part 1: For me your query is giving exactly what you are expecting SELECT group_ID,REPLACE([person],'Person ','') AS person FROM #Groups +----------+--------+ | group_ID | person | +----------+--------+ | 1 | 10 | | 2 | 11 | | 3 | Jack | | 4 | 12 | +----------+--------+ Part 2: ;WITH CTE AS( SELECT group_ID ,REPLACE([person],'Person ','') AS person FROM #Groups ) SELECT D.detail_ID, G1.person, G2.person FROM #Details D INNER JOIN CTE G1 ON D.group1 = G1.group_ID INNER JOIN CTE G2 ON D.group1 = G2.group_ID Result: +-----------+--------+--------+ | detail_ID | person | person | +-----------+--------+--------+ | 100 | 10 | 10 | | 101 | Jack | Jack | +-----------+--------+--------+
Try following query, it should give you the desired output. ;WITH MT AS ( SELECT GroupId, REPLACE([person],'Person ','') Person AS [person] FROM Groups ) SELECT Detail_Id , MT1.Person AS group1 , MT2.Person AS AS group2 FROM Details D INNER JOIN MT MT1 ON MT1.GroupId = D.group1 INNER JOIN MT MT2 ON MT2.GroupId= D.group2
The first query works declare #T table (id int primary key, name varchar(10)); insert into #T values (1, 'Person 10') , (2, 'Person 11') , (3, 'Jack') , (4, 'Person 12'); declare #G table (id int primary key, grp1 int, grp2 int); insert into #G values (100, 1, 2) , (101, 3, 4); with cte as ( select t.id, t.name, ltrim(rtrim(replace(t.name, 'person', ''))) as sp from #T t ) -- select * from cte order by cte.id; select g.id, c1.sp as grp1, c2.sp as grp2 from #G g join cte c1 on c1.id = g.grp1 join cte c2 on c2.id = g.grp2 order by g.id; id grp1 grp2 ----------- ----------- ----------- 100 10 11 101 Jack 12
Count Based on Columns in SQL Server
I have 3 tables: SELECT id, letter FROM As +--------+--------+ | id | letter | +--------+--------+ | 1 | A | | 2 | B | +--------+--------+ SELECT id, letter FROM Xs +--------+------------+ | id | letter | +--------+------------+ | 1 | X | | 2 | Y | | 3 | Z | +--------+------------+ SELECT id, As_id, Xs_id FROM A_X +--------+-------+-------+ | id | As_id | Xs_id | +--------+-------+-------+ | 9 | 1 | 1 | | 10 | 1 | 2 | | 11 | 2 | 3 | | 12 | 1 | 2 | | 13 | 2 | 3 | | 14 | 1 | 1 | +--------+-------+-------+ I can count all As and Bs with group by. But I want to count As and Bs based on X,Y and Z. What I want to get is below: +-------+ | X,Y,Z | +-------+ | 2,2,0 | | 0,0,2 | +-------+ X,Y,Z A 2,2,0 B 0,0,2 What is the best way to do this at MSSQL? Is it an efficent way to use foreach for example? edit: It is not a duplicate because I just wanted to know the efficent way not any way.
For what you're trying to do without knowing what is inefficient with your current code (because none was provided), a Pivot is best. There are a million resources online and here in the stack overflow Q/A forums to find what you need. This is probably the simplest explanation of a Pivot which I frequently need to remind myself of the complicated syntax of a pivot. To specifically answer your question, this is the code that shows how the link above applies to your question First Tables needed to be created DECLARE #AS AS TABLE (ID INT, LETTER VARCHAR(1)) DECLARE #XS AS TABLE (ID INT, LETTER VARCHAR(1)) DECLARE #XA AS TABLE (ID INT, AsID INT, XsID INT) Values were added to the tables INSERT INTO #AS (ID, Letter) SELECT 1,'A' UNION SELECT 2,'B' INSERT INTO #XS (ID, Letter) SELECT 1,'X' UNION SELECT 2,'Y' UNION SELECT 3,'Z' INSERT INTO #XA (ID, ASID, XSID) SELECT 9,1,1 UNION SELECT 10,1,2 UNION SELECT 11,2,3 UNION SELECT 12,1,2 UNION SELECT 13,2,3 UNION SELECT 14,1,1 Then the query which does the pivot is constructed: SELECT LetterA, [X],[Y],[Z] FROM (SELECT A.LETTER AS LetterA ,B.LETTER AS LetterX ,C.ID FROM #XA C JOIN #AS A ON A.ID = C.ASID JOIN #XS B ON B.ID = C.XSID ) Src PIVOT (COUNT(ID) FOR LetterX IN ([X],[Y],[Z]) ) AS PVT When executed, your results are as follows: Letter X Y Z A 2 2 0 B 0 0 2
As i said in comment ... just join and do simple pivot if object_id('tempdb..#AAs') is not null drop table #AAs create table #AAs(id int, letter nvarchar(5)) if object_id('tempdb..#XXs') is not null drop table #XXs create table #XXs(id int, letter nvarchar(5)) if object_id('tempdb..#A_X') is not null drop table #A_X create table #A_X(id int, AAs int, XXs int) insert into #AAs (id, letter) values (1, 'A'), (2, 'B') insert into #XXs (id, letter) values (1, 'X'), (2, 'Y'), (3, 'Z') insert into #A_X (id, AAs, XXs) values (9, 1, 1), (10, 1, 2), (11, 2, 3), (12, 1, 2), (13, 2, 3), (14, 1, 1) select LetterA, ISNULL([X], 0) [X], ISNULL([Y], 0) [Y], ISNULL([Z], 0) [Z] from ( select distinct a.letter [LetterA], x.letter [LetterX], count(*) over (partition by a.letter, x.letter order by a.letter) [Counted] from #A_X ax join #AAs A on ax.AAs = A.ID join #XXs X on ax.XXs = X.ID )src PIVOT ( MAX ([Counted]) for LetterX in ([X], [Y], [Z]) ) piv You get result as you asked for LetterA X Y Z A 2 2 0 B 0 0 2
Joining conditionally based on values in 2 different columns in current table
SELECT l.*, qqs.*, qq.*, -- cqqs.id As Cosigner_Quote_Set_ID, -- borrower_cosigner_quote_set_uuid as Cosigner_Quote_Set_UUID, -- cqq.id As Cosigner_Quote_ID, -- cqq.uuid As Cosigner_Quote_UUID, -- cqq.accept_federal_loan As Cosigner_Quote_Loan_Type, -- cqq.program_id As Cosigner_Quote_Program_ID, -- cqq.lender_name As Cosigner_Quote_Lender_Name, -- cqq.term_in_months As Cosigner_Loan_Term, -- cqq.apr As Cosigner_Loan_APR, -- cqq.monthly_payment As Cosigner_Loan_Pay, IF(tentative_quote_uuid IS NOT NULL,1,0) As Quote_Accepted, IF(selected_quote_uuid IS NOT NULL,1,0) As Lender_Accepted, loan_key As Loan_Key FROM leads_production.leads l LEFT JOIN greenhouse_production.citizenship_statuses csb ON csb.id = l.citizenship_status_ref LEFT JOIN greenhouse_production.citizenship_statuses csc ON csc.id = l.cosigner_citizenship_status_ref LEFT JOIN core_production.quotes_quote_sets qqs ON qqs.uuid = l.quote_set_uuid LEFT JOIN core_production.quotes_quotes qq ON qq.quote_set_id = qqs.id; -- LEFT JOIN core_production.quotes_quote_sets cqqs -- ON cqqs.uuid = l.borrower_cosigner_quote_set_uuid -- LEFT JOIN core_production.quotes_quotes cqq -- ON cqq.quote_set_id = qqs.id; Please look at the commented lines in the above query. I want to write a query wherein the join is either on (qqs and qq tables) OR (cqqs and cqq tables) based on the value in borrower_cosigner_quote_set_uuid. Something like this: If borrower_cosigner_quote_set_uuid IS NOT NULL THEN -- LEFT JOIN core_production.quotes_quote_sets cqqs -- ON cqqs.uuid = l.borrower_cosigner_quote_set_uuid -- LEFT JOIN core_production.quotes_quotes cqq -- ON cqq.quote_set_id = qqs.id; ELSE LEFT JOIN core_production.quotes_quote_sets qqs ON qqs.uuid = l.quote_set_uuid LEFT JOIN core_production.quotes_quotes qq ON qq.quote_set_id = qqs.id;` Thanks in advance for your help.
You can conditionally JOIN using OR logic like so: CREATE TABLE #temp1 ( foo INT ); CREATE TABLE #temp2 ( bar INT ); INSERT INTO #temp1 ( foo ) VALUES ( 1 ),( 2 ),( NULL ),( 4 ),( 5 ) INSERT INTO #temp2 ( bar ) VALUES ( 1 ),( 2 ),( 3 ),( 4 ),( 5 ),( 99 ) SELECT * FROM #temp1 INNER JOIN #temp2 ON ( foo = bar AND foo IS NOT NULL) OR ( foo IS NULL AND bar = 99 ) DROP TABLE #temp1 DROP TABLE #temp2 It's not a great example, but I want to JOIN any NULL value to 99 to produce: foo bar 1 1 2 2 NULL 99 4 4 5 5 Hopefully you can follow the simplified logic to apply it to your problem.
Looke like you want a conditional join between TableA, TableB and TableC If TableB and TableC have similar fields you can join with both tables and solve what field use by checking for the null value SqlFiddle Demo Setup Test db CREATE TABLE TableA (`Id` varchar(4), `keyA` int, `valor` int); INSERT INTO TableA (`Id`, `keyA`, `valor`) VALUES ('1', 10, 90),('2', 20, 91),('3', 30, 92),(NULL, 40, 93); CREATE TABLE TableB (`Id` int, `valor` int); INSERT INTO TableB (`Id`, `valor`) VALUES (1, 200),(2, 201),(3, 202); CREATE TABLE TableC (`Id` int, `valor` int); INSERT INTO TableC (`Id`, `valor`) VALUES (10, 500),(20, 501),(30, 502),(40, 503); QUERY SELECT A.*, IF(A.id is NULL, C.id, B.id) as joinkey, IF(A.id is NULL, C.valor, B.valor) as valor FROM TableA A LEFT JOIN TableB B ON A.id = B.id LEFT JOIN TableC C ON A.keyA = C.id OUTPUT | Id | keyA | valor | joinkey | valor | |--------|------|-------|---------|-------| | 1 | 10 | 90 | 1 | 200 | | 2 | 20 | 91 | 2 | 201 | | 3 | 30 | 92 | 3 | 202 | | (null) | 40 | 93 | 40 | 503 |