(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 |