Recursive CTE with 2 tables not working in Oracle / SQL - sql

There are 2 tables Department and subdepartment which have id in common.
I am trying to recursively fetch all the ids reporting to AB directly and indirectly. BC is reporting to AB, hence 4,5,6 are indirectly reporting to AB, likewise fetching till the last id.
Tried the below recursive CTE query but I am getting the result of only the first level. Seems recursive query is not executing.
I am not sure what is wrong in the query. Can someone help me in spotting the error.
Department
Name id
AB 1
AB 2
AB 3
BC 4
BC 5
BC 6
CD 7
CD 8
EF 9
EF 10
EF 11
Subdepartment
ID Reporting
1
2
3 BC
4
5 CD
6
7
8 EF
9
10
11
Query:
With reportinghierarchy (Name, Id, Reporting, Level) As
(
--Anchor
Select A.name,A.id,reporting,0 from department A, subdepartment B
where A.id=B.id and A.name='AB'
Union All
--Recursive member
Select C.name,C.id,D.reporting, Level+1 from department C, subdepartment D
Inner Join reportinghierarchy R
On (C.Name = R.reporting)
Where C.name != 'AB' and C.Id =D.id
And R.Reporting is not null
)
Select * from reportinghierarchy
Current Output :
Name Id Reporting Level
AB 1 0
AB 2 0
AB 3 BC 0
Expected output :
Name id Reporting Level
AB 1 0
AB 2 0
AB 3 BC 0
BC 4 1
BC 5 CD 1
BC 6 1
CD 7 2
CD 8 EF 2
EF 9 3
EF 10 3
EF 11 3

Hmmm, "horrible data structure" comes to mind. This approach gets one row per "reporting" name to use for the recursive CTE portion. It then joins the level back to the original data.
with ds as (
select d.name, d.id, sd.reporting
from department d join
subdepartment sd
on d.id = sd.id
),
nd as (
select d.name, sd.reporting
from ds
where sd.reporting is not null
),
cte as (
select ds.name, nd.reporting, 0 as lev
from nd
where not exists (select 1 from nd nd2 where nd2.reporting = nd.name)
union all
select nd.name, nd.reporting, lev + 1
from cte join
nd
on nd.name = cte.reporting
)
select ds.*, cte.lev
from ds join
cte
on ds.name = cte.name;
Also, learn to use proper, explicit JOIN syntax. It has been the standard syntax for decades.

Your original query was actually VERY, VERY close to working. The reasons it didn't work are:
You used the keyword LEVEL as a column name without quoting it. In Oracle LEVEL has specific meaning, and using it out of context causes the parser no end of headaches. I've changed it to LVL, which works fine.
In the recursive half of the UNION you mixed old-style and new-style joins. This is a huge problem and should never be done. Either use "old-style" implied joins, or use "new-style" explicit joins. To keep as close to your original as possible I used implicit joins, but good coding practice says you should use explicit joins all the time.
The corrected query is:
With reportinghierarchy (Name, Id, Reporting, lvl) As
(
--Anchor
Select A.name,A.id,reporting,0 from department A, subdepartment B
where A.id=B.id and A.name='AB'
Union All
--Recursive member
Select C.name,C.id,D.reporting, lvl+1 from department C, subdepartment D, reportinghierarchy R
Where C.name != 'AB' and C.Id =D.id and C.Name = R.reporting
And R.Reporting is not null
)
Select * from reportinghierarchy;
Given the above, the following results are returned, which appear to match your desired results:
NAME ID REPORTING LVL
AB 1 (null) 0
AB 2 (null) 0
AB 3 BC 0
BC 4 (null) 1
BC 5 CD 1
BC 6 (null) 1
CD 7 (null) 2
CD 8 EF 2
EF 9 (null) 3
EF 10 (null) 3
EF 11 (null) 3
SQLFiddle here
Best of luck.

Related

Select only the most recent records by record ID

I have an access database, in it, I have a number of records where I need to pull just the latest information .
Here's the code I am trying to get working :
SELECT c.RecordID, c.Employee, c.DecShotDate, c.DecShotLocation, c.DecReason, c.DecExplanation, e.ID, e.Employee, c.Training, c.CompletedDate, IIf([DecShotLocation]=0,"Hospital", IIf([DecShotLocation]=1,"MCS", IIf([DecShotLocation]=2,"Other Employer",IIf([DecShotLocation]=3,"Pharmacy",IIf([DecShotLocation]=4,"Primary Care",IIf([DecShotLocation]=5,"Urgent Care"," ")))))) AS StrDecLoc
FROM ((EmployeeInformation AS e
INNER JOIN CompletedTrainings AS c ON e.ID = c.Employee
) AS t
INNER JOIN
(
SELECT MAX(RecordID) AS mID, c.Training
FROM CompletedTrainings c
)
ON t.RecordID = c.RecordID
INNER JOIN
(
SELECT DISTINCT c.Training, c.RecordID AS dID
FROM CompletedTrainings c
) AS d
ON c.RecordID = d.dID
GROUP BY c.Training
) as M
on M.RecordID = t.RecordID
WHERE c.Training = 69 AND (((c.CompletedDate+365)>=IIf(Forms![frm_Decflu]!FrameAllOrCurrent=1,(c.CompletedDate+365),Date())))
GROUP BY c.RecordID, c.Employee, c.DecShotDate, c.DecShotLocation, c.DecReason, c.DecExplanation, e.ID, e.Employee, c.Training, c.CompletedDate);
The idea here, is I need 1 Training, and I need just the employees who took that training, and only the last time they took it.
Currently I am getting a Syntax error in the JOIN statement.
So the two inner Selects:
(
SELECT MAX(RecordID) AS mID, c.Training
FROM CompletedTrainings c
)
ON t.RecordID = c.RecordID
INNER JOIN
(
SELECT DISTINCT c.Training, c.RecordID AS dID
FROM CompletedTrainings c
) AS d
ON c.RecordID = d.dID
GROUP BY c.Training
) as M
on M.RecordID = t.RecordID
Work together to get me a list of the last time every training was taken.
And the first select
SELECT c.RecordID, c.Employee, c.DecShotDate, c.DecShotLocation, c.DecReason, c.DecExplanation, e.ID, e.Employee, c.Training, c.CompletedDate, IIf([DecShotLocation]=0,"Hospital", IIf([DecShotLocation]=1,"MCS", IIf([DecShotLocation]=2,"Other Employer",IIf([DecShotLocation]=3,"Pharmacy",IIf([DecShotLocation]=4,"Primary Care",IIf([DecShotLocation]=5,"Urgent Care"," ")))))) AS StrDecLoc
FROM ((EmployeeInformation AS e
INNER JOIN CompletedTrainings AS c ON e.ID = c.Employee
Pulls just the information I need from each of the tables.
Independently these work, except that the first select pulls duplicate trainings (These trainings are done yearly).
I am trying to get rid of that duplication, to produce a yearly report on who has had a specific training.
Sample Tables :
tbl_E
IDNum
LastName
GivenName
Active
PersonalInfoColumns
EmployeeName
1
Dole
Bob
-1
stuff
Bob Dole
2
Clinton
Bill
-1
stuff
Bill Clinton
3
Bush
George HW
0
stuff
George HW Bush
4
Reagan
Ronald
0
stuff
Ronald Reagan
5
Eastwood
Clint
-1
stuff
Clint Eastwood
tbl_C
RecordID
Employee
Training
CompletedDate
DSD
DSL
DR
DE
1
1
69
mmddyyyy
0
2
2
74
mmddyyyy
3
3
69
mmddyyyy
1
somewhere
4
somereason
4
4
52
mmddyyyy
5
1
74
mmddyyyy
6
2
69
mmddyyyy
2
somewhere
4
somereason
7
5
69
mmddyyyy
0
...
...
...
...
...
...
...
...
972
1
69
mmddyyyy
1
somewhere
4
somereason
973
5
69
mmddyyyy
1
somewhere
2
somereason
974
1
73
mmddyyyy
1
somewhere
2
somereason
974
2
69
mmddyyyy
0
tbl_T (not relevant but it is linked to tbl_C)
ID
TrainingName
requalifiy
69
Shots
yearly
73
Reference
Once
74
CORI
Once
52
Training
yearly
The ideal output of this query is this :
RecordID
Employee
Training
CompletedDate
DSD
DSL
DR
DE
3
3
69
mmddyyyy
1
somewhere
4
somereason
972
1
69
mmddyyyy
1
somewhere
4
somereason
973
5
69
mmddyyyy
1
somewhere
2
somereason
974
2
69
mmddyyyy
0
Assuming employee 3 didn't do it after the first time.
access is a little bizarre in using SQL as standard, so this SQL that would perfectly work in any SQL backend might not in access. Unfortunately at this moment I don't have a chance to try:
SELECT t1.*
FROM tbl_C t1
INNER JOIN
(SELECT Max(recordId) AS maxRecId
FROM tbl_C WHERE Training=69 GROUP BY Employee) t2
ON t1.RecordId = t2.maxRecId;
(If you want, you can join to other such as tbl_E to get names)

Creating a VIEW to get a connection count

I have a table below which stores Connections between 2 person
TABLE (CONNECTION)
ID | REQUEST_PERSON | REQUESTEE_PERSON
I would like to build a VIEW which gets the REQUEST_PERSON, REQUESTEE_PERSON and MUTUAL_CONNECTION_COUNT(other common connections count between them). Any help is appreciated
For Example if we have a table data as below
ID | REQUEST_PERSON | REQUESTEE_PERSON
1 A B
2 A C
3 B C
4 D B
5 D A
6 A E
7 B E
8 A F
9 C G
I need a VIEW display below
ID | REQUEST_PERSON | REQUESTEE_PERSON | MUTUAL_CONNECTION_COUNT
1 A B 3
2 A C 1
3 B C 1
4 D B 1
5 D A 1
6 A E 1
7 B E 1
8 A F 0
9 C G 0
This is rather tricky. Here is code that does what you want:
select c.*,
(select count(*)
from (select v.person2
from connections c2 cross apply
(values (c2.REQUESTEE_PERSON, c2.REQUEST_PERSON), (c2.REQUEST_PERSON, c2.REQUESTEE_PERSON)
) v(person1, person2)
where v.person1 IN (c.Request_Person, c.Requestee_Person)
group by v.person2
having count(*) = 2
) v
) in_common
from connections c
order by id;
Here is a SQL Fiddle.
The essence of the problem is finding people who are connected to both people in each row. Your connections are unidirectional, which makes the logic hard to express -- C could be either the first or second person in either connection.
Arrgh!
So, the innermost subquery adds reverse links to the graph. Then, it can focus on filtering by the first person -- who has to match the persons in the outer query. The second person is the one that might be in common.
The inner aggregation is just summarizing by the second person. It filters using having count(*) = 2 to indicate that both people in the outer query need to be connected to the second person in the inner query. The count(*) assumes that you have no duplicates.
Then, these are counted, which is the value you want.

full-join issue in SQL server

I have two tables total_sales_store :
store. total_sales
23 198750953.849999
29 77141561.3099999
9 77789512.9899997
15 89133935.9200002
3 57586980.0699998
32 166819624.16
26 143416610.79
12 144287538.149999
35 131520910.08
6 223756634.64
43 90565869.4100002
21 108118179.92
27 253856294.88
38 55159990.42
7 81598450.1399996
and storesdata:
store. type
1 A
2 A
3 B
4 A
5 B
6 A
7 B
8 A
9 B
10 B
11 A
I want to full join the two table so that I have this desired table:
store. Type. totalsales
1 A 123124
2 B 141221
3 C 134141
4 A. 234234
5 B 2323
6 C 123214
...
So I used the following query:
SELECT A.Store,a.total_sales
FROM totalsales_store A
FULL JOIN
(
SELECT [Type], Store
FROM storesdata
) B
ON A.Store = B.Store
ORDER BY total_sales DESC
But what I got is this:
[enter image description here][3]
which neglects the Type column. What can I do to make this work? Thank you!
With a FULL JOIN you will get all results from both tables, even when they don't match each other, so you should account for the NULL store values across tables. Otherwise simply include the Type column in the select.
I didn't see a need for a sub-query.
SELECT
ISNULL(A.Store, B.Store) Store,
B.Type,
a.total_sales
FROM
totalsales_store A
FULL JOIN storesdata B ON
A.Store = B.Store
ORDER BY
a.total_sales DESC
I suspect you can get away with
SELECT
A.Store,
B.Type,
a.total_sales
FROM
totalsales_store A
JOIN storesdata B ON
A.Store = B.Store
ORDER BY
a.total_sales DESC

Query to join two tables using two different columns from the first table

I have two tables .
Table A:
Table A ID Table Name owner1ID owner2ID
1 Work1 85 91
2 Work2 86 92
3 Work3 87 93
4 Work4 88 94
5 Work5 89 95
6 Work6 90 96
Table B:
OwnerID 0WNERFIRSTNAME 0WNERlASTNAME
85 A M
86 B N
87 C O
88 D P
90 E Q
91 F R
89 G S
92 H T
86 I U
94 J V
93 K W
95 L X
Can you please help me out in getting a query where i need the table which contains TABLEID OWNERFIRSTNAME and OWNERSECONDNAME.
Expected output:
TableAID 0WNER1FIRSTNAME 0WNER1LASTNAME 0WNER2FIRSTNAME 0WNER2LASTNAME
1 A M F R
You need to join on to TableB twice.
That means you need to give each instance of the table an alias, so you can differentiate which instance you're referring to...
SELECT
TableA.TableAID,
TableB1.0WNERFIRSTNAME AS 0WNER1FIRSTNAME,
TableB1.0WNERlASTNAME AS 0WNER1LASTNAME,
TableB2.0WNERFIRSTNAME AS 0WNER2FIRSTNAME,
TableB2.0WNERlASTNAME AS 0WNER2LASTNAME
FROM
TableA
INNER JOIN
TableB TableB1
ON TableB1.OwnerID = TableA.owner1ID
INNER JOIN
TableB TableB2
ON TableB2.OwnerID = TableA.owner2ID
P.S. Don't Spell 0WNERFIRSTNAME with a ZERO, Spell it OWNERFIRSTNAME!
While MatBaile's answer is the most common practice, your own example shows some problems. First is that we lose info about table 6 for which second owner is not found in second table. This can be easily corrected with left join:
select a.id, a.table_name,
b1.OwnerFirstName O1FN, b1.OwnerLastName O1LN,
b2.OwnerFirstName O2FN, b2.OwnerLastName O2LN
from a
left join b b1 on b1.OwnerId = a.Owner1Id
left join b b2 on b2.OwnerId = a.Owner2Id
What gives us:
ID TABLE_NAME O1FN O1LN O2FN O2LN
---------- ---------- ---- ---- ---- ----
1 Work1 A M F R
2 Work2 I U H T <-- two first owners
2 Work2 B N H T <-- two first owners
4 Work4 D P J V
3 Work3 C O K W
5 Work5 G S L X
6 Work6 E Q <-- null second owner
And second problem - for table 2 we got two entries, because in your example there are two owners with id = 86. I suspect that this is typo, but this can happen in similiar cases. You can leave it as is, or take only last row (if owner changed and you have info about this in some date column), or you can list all owners using listagg(), or take max value. Things are worse when there are more rows connected to 1. and 2. owner, your output is multiplied.
As a curiosity here is unpivot-pivot solution. In this case this query looks more complicated, but if there were 10 columns you had to do 10 joins and in this query only lists of columns requires change.
select *
from (
select id, table_name, type, ownerfirstname, ownerlastname
from (select * from a unpivot (ownerId for type in (owner1ID as 1, owner2ID as 2))) a
join b using (ownerId))
pivot (listagg(ownerfirstname||' '||ownerlastname, ', ') within group (order by null) owner
for type in (1, 2))
SQL Fiddle demo
ID TABLE_NAME 1_OWNER 2_OWNER
---------- ---------- ---------- ----------
1 Work1 A M F R
2 Work2 B N, I U H T <-- listagg() used to aggregate data
3 Work3 C O K W
4 Work4 D P J V
5 Work5 G S L X
6 Work6 E Q

Teradata SQL count on Top n subgroups

I am using TD v15. I have a table as below - each row is a single record, I want to perform Count in the following way:
In Question Column: I have 4 'A', 5 'B', 3 'C' and 2 'D'. Select top 2 from them, which are A & B. Group the rest Questions as 'OtherQ' - Put them in Result Question Column.
In Change Column, I have 2 'AA', 3 'AB', 2'AC', 2 'AD', 4 'AE' and 2 'AG', select top 2, which are AE & AB, group the rest Change as 'Other' - Put them in Result Change Column.
Then, count according...
Question Result Change
A Pass AG
A Pass AE
A Pass AA
A Pass AB
B Pass AC
B Pass AG
B Pass AB
B Pass AE
B Pass AD
B Pass AA
C Pass AB
C Pass AC
C Pass AD
D Pass AE
D Pass AE
A Fail Null
A Fail Null
C Fail Null
E Fail Null
B Fail Null
This is the desired result, it counts on top 2 questions (A&B) and OtherQ with Top 2 changes (AE&AB) and other Changes, also, it counts Pass&Fail for A&B and OtherQ.
The sum of Count is 20, this should match the 20 individual row in the table above.
Question Result Change Count
A Pass AE 1
A Pass AB 1
A Pass Other 2
B Pass AE 1
B Pass AB 1
B Pass Other 4
OtherQ Pass AE 2
OtherQ Pass AB 1
OtherQ Pass Other 2
A Fail Null 2
B Fail Null 1
OtherQ Fail Null 2
Could you please kindly help? It's very large data table, needs the code to be efficient. Many thank for your time and help in advance.
I would suggest using aggregations and subqueries:
select coalesce(tq.question, 'Other') as question
(case when t.change is null then null
else coalesce(tch.change, 'Other')
end) as change,
count(*)
from t left join
(select question, count(*) as cnt,
row_number() over (order by count(*) desc) as seqnum
from t
group by question
) tq
on tq.question = t.question and tq.seqnum <= 2 left join
(select change, count(*) as cnt,
row_number() over (order by count(*) desc) as seqnum
from t
group by change
) tch
on tch.change = t.change and tch.seqnum <= 2
group by coalesce(tq.question, 'Other'),
coalesce(tch.change, 'Other');