Recursion in sql with loop - sql

I have a table,
where there are users and each of them has supervisior. However the CEO does not have a supervisior and he is supervisior of himself. (i.e, supervisiorid of CEO is UserId of CEO)
I have a requirement to find list of all subordinates under a given userid. I am using below query.
WITH CTE_EMPLOYEE_HEIRARCHY AS
(
SELECT E.UserId, E.SupervisiorId AS Supervisor, d.DepartmentName,d.DepartmentId
FROM T_BIT_Users E with (nolock) inner join T_BIT_Department d with (nolock) on d.DepartmentId=e.DepartmentId WHERE E.UserId = 13
UNION ALL
SELECT E1.UserId, E1.SupervisiorId AS Supervisor,d.DepartmentName,d.DepartmentId
FROM CTE_EMPLOYEE_HEIRARCHY
JOIN T_BIT_Users E1
ON E1.SupervisiorId = CTE_EMPLOYEE_HEIRARCHY.UserId
inner join T_BIT_Department d on d.DepartmentId=e1.DepartmentId)
SELECT * FROM CTE_EMPLOYEE_HEIRARCHY
OPTION ( MAXRECURSION 0 )
this keeps on going in loop forever.
Any suggestions.?

Just limit the recursive part to exclude where supervisorId = userId
SELECT E1.UserId, E1.SupervisiorId AS Supervisor,d.DepartmentName,d.DepartmentId
FROM CTE_EMPLOYEE_HEIRARCHY
JOIN T_BIT_Users E1
ON E1.SupervisiorId = CTE_EMPLOYEE_HEIRARCHY.UserId
inner join T_BIT_Department d on d.DepartmentId=e1.DepartmentId
WHERE E1.SupervisorId != E1.UserId # <-- here

Related

Join on TOP 1 from subquery while referencing outer tables

I am starting with this query, which works fine:
SELECT
C.ContactSys
, ... a bunch of other rows...
FROM Users U
INNER JOIN Contacts C ON U.ContactSys = C.ContactSys
LEFT JOIN UserWatchList UW ON U.UserSys = UW.UserSys
LEFT JOIN Accounts A ON C.AccountSys = A.AccountSys
WHERE
C.OrganizationSys = 1012
AND U.UserTypeSys = 2
AND C.FirstName = 'steve'
Now, I've been given this requirement:
For every visitor returned by the Visitor Search, take ContactSys, get the most recent entry in the GuestLog table for that contact, then return the columns ABC and XYZ from the GuestLog table.
I'm having trouble with that. I need something like this (I think)...
SELECT
C.ContactSys
, GL.ABC
, GL.XYZ
, ... a bunch of other rows...
FROM Users U
INNER JOIN Contacts C ON U.ContactSys = C.ContactSys
LEFT JOIN UserWatchList UW ON U.UserSys = UW.UserSys
LEFT JOIN Accounts A ON C.AccountSys = A.AccountSys
LEFT JOIN (SELECT TOP 1 * FROM GuestLog GU WHERE GU.ContactSys = ????? ORDER BY GuestLogSys DESC) GL ON GL.ContactSys = C.ContactSys
WHERE
C.OrganizationSys = 1012
AND U.UserTypeSys = 2
AND C.FirstName = 'steve'
Only that's not it because that subquery on the JOIN doesn't know anything about the outer tables.
I've been looking at these posts and their answers, but I'm having a hard time translating them to my needs:
SQL: Turn a subquery into a join: How to refer to outside table in nested join where clause?
Reference to outer query in subquery JOIN
Referencing outer query in subquery
Referencing outer query's tables in a subquery
If that is the logic you want, you can use OUTER APPLY:
SELECT C.ContactSys, GL.ABC, GL.XYZ,
... a bunch of other columns ...
FROM Users U JOIN
Contacts C
ON U.ContactSys = C.ContactSys LEFT JOIN
UserWatchList UW
ON U.UserSys = UW.UserSys LEFT JOIN
Accounts A
ON C.AccountSys = A.AccountSys OUTER APPLY
(SELECT TOP 1 gl.*
FROM GuestLog gl
WHERE gl.ContactSys = C.ContactSys
ORDER BY gl.GuestLogSys DESC
) GL
WHERE C.OrganizationSys = 1012 AND
U.UserTypeSys = 2 AND
C.FirstName = 'steve'

sql subquery join group by

I am trying to get a list of our users from our database along with the number of people from the same cohort as them - which in this case is defined as being from the same medical school at the same time.
medical_school_id is stored in the doctor_record table
graduation_dt is stored in the doctor_record table as well.
I have managed to write this query out using a subquery which does a select statement counting the number of others for each row but this takes forever. My logic is telling me that I ought to run a simple GROUP BY query once first and then somehow JOIN the medical_school_id on to that.
The group by query is as follows
select count(ca.id) , cdr.medical_school_id, cdr.graduation_dt
from account ca
LEFT JOIN doctor cd on ca.id = cd.account_id
LEFT JOIN doctor_record cdr on cd.gmc_number = cdr.gmc_number
GROUP BY cdr.medical_school_id, cdr.graduation_dt
The long select query is
select a.id, a.email , dr.medical_school_id,
(select count(ba.id) from account ba
LEFT JOIN doctor bd on ba.id = bd.account_id
LEFT JOIN doctor_record bdr on bd.gmc_number = bdr.gmc_number
WHERE bdr.medical_school_id = dr.medical_school_id AND bdr.graduation_dt = dr.graduation_dt) AS med_count,
from account a
LEFT JOIN doctor d on a.id = d.account_id
LEFT JOIN doctor_record dr on d.gmc_number = dr.gmc_number
If you could push me in the right direction that would be amazing
I think you just want window functions:
select a.id, a.email, dr.medical_school_id, dr.graduation_dt,
count(*) over (partition by dr.medical_school_id, dr.graduation_dt) as cohort_size
from account a left join
doctor d
on a.id = d.account_id left join
doctor_record dr
on d.gmc_number = dr.gmc_number;
Using your same code for group by:
SELECT * FROM (
(
SELECT acc.[id]
, acc.[email]
FROM
account acc
LEFT JOIN
doctor doc
ON
acc.id = doc.account_id
LEFT JOIN
doctor_record doc_rec
ON
doc.gmc_number = doc_rec.gmc_number
) label
LEFT JOIN
(
SELECT count(acco.id)
, doc_reco.medical_school_id
, doc_reco.graduation_dt
FROM
account acco
LEFT JOIN
doctor doct
ON
acco.id = doct.account_id
LEFT JOIN
doctor_record doc_reco
ON
doct.gmc_number = doc_reco.gmc_number
GROUP BY
doc_reco.medical_school_id,
doc_reco.graduation_dt
) count
ON
count.[medical_school_id]=label.[medical_school_id]
AND
count.[graduation_dt]=label.[graduation_date]
)
how about something like this?
select a.doctor_id
, count(*) - 1
from doctor_record a
left join doctor_record b on a.medical_school_id = b.medical_school_id
and a.graduation_dt = b.graduation_dt
group by a.doctor_id
Subtract 1 from the count so that you're not counting the doctor in the "other folks in same cohort" number
I'm defining "same cohort" as "same medical school & graduation date".
I'm unclear on what GMC number is and how it is related. Is it something to do with cohort?

SQL complex join query with sum

I am trying to obtain sum of amounts based on a column(sysprocode) which is unique for the composite key (Organizationunitid and payrollcodeid).So far I have managed to get amount based on Organizationunitid and payrollcodeid but not alongside sysprocode.A look at my SQLfiddle should clarify this more Click
Here I have tried this sql query
SELECT TB1.OUId,TB1.OUName,(TB2.Amount - TB3.ManualDeduction) AS amt
FROM
(
SELECT ou.OrganizationUnitID AS OUId,ou.OrganizationUnitName AS OUName
FROM OrganizationUnits ou
) TB1,
(SELECT e.OrganizationUnitID AS OUId,SUM(trn.Amount) AS Amount
FROM Employees e
LEFT JOIN tblPeriodTransactions trn ON (e.EmployeeID=trn.Employee_ID)
where trn.Period_Month =6 and trn.Period_Year=2013 and trn.PayrollCode_ID=2
GROUP BY e.OrganizationUnitID
)TB2,
(SELECT e.OrganizationUnitID AS OUId,SUM(ep.ManualDeduction) AS ManualDeduction
FROM Employees e
LEFT JOIN tblEmployeePension ep ON (e.EmployeeID=ep.Employee_ID)
GROUP BY e.OrganizationUnitID
)TB3
WHERE (TB2.OUId=TB1.OUId)
AND (TB3.OUId=TB1.OUId)
Here is sample output in imgur sample output
Check the following query whether it is working or not but i think calculating pension is problem because there is no PayrollCode_ID in tblEmployeePension:
SELECT
OU.OrganizationUnitID,
OU.OrganizationUnitName,
SPC.sysprocode,
PC.PayrollCode_ID,
SUM(PD.Amount),
(SELECT SUM(tblEmployeePension.ManualDeduction) FROM tblEmployeePension WHERE Employee_ID IN (SELECT Employee_ID FROM Employees WHERE OrganizationUnitID = OU.OrganizationUnitID)) AS Pension,
SUM(PD.Amount) - (SELECT SUM(tblEmployeePension.ManualDeduction) FROM tblEmployeePension WHERE Employee_ID IN (SELECT Employee_ID FROM Employees WHERE OrganizationUnitID = OU.OrganizationUnitID)) as amt
FROM
tblPeriodTransactions PD
INNER JOIN
Employees E
ON
PD.Employee_ID = E.EmployeeID
INNER JOIN
OrganizationUnits OU
ON
E.OrganizationUnitID = OU.OrganizationUnitID
INNER JOIN
tblPayrollCode PC
ON
PD.PayrollCode_ID = PC.PayrollCode_ID
INNER JOIN
sysprocodes SPC
ON
SPC.organisationunitid = OU.OrganizationUnitID AND
SPC.PayrollCode_ID = PC.PayrollCode_ID
GROUP BY
OU.OrganizationUnitID,
PC.PayrollCode_ID,
SPC.sysprocode,
OU.OrganizationUnitName
I solved it finally after fumbling about
SELECT TB4.Syscode,
CASE TB4.accountType WHEN 'c' THEN concat('-', (TB2.Amount - TB3.ManualDeduction))
ELSE (TB2.Amount - TB3.ManualDeduction) end AS amount
FROM
(
SELECT ou.OrganizationUnitID AS OUId,ou.OrganizationUnitName AS OUName
FROM OrganizationUnits ou
) TB1,
(SELECT e.OrganizationUnitID AS OUId,SUM(trn.Amount) AS Amount
FROM Employees e
LEFT JOIN tblPeriodTransactions trn ON (e.EmployeeID=trn.Employee_ID)
WHERE trn.Period_Month = 6
AND trn.Period_Year = 2013
AND trn.PayrollCode_ID = 2
GROUP BY e.OrganizationUnitID
)TB2,
(SELECT e.OrganizationUnitID AS OUId,SUM(ep.ManualDeduction) AS ManualDeduction
FROM Employees e
LEFT JOIN tblEmployeePension ep ON (e.EmployeeID=ep.Employee_ID)
GROUP BY e.OrganizationUnitID
)TB3,
(SELECT ou.OrganizationUnitID AS OUId,sp.sysprocode as Syscode,sp.accountType AS accountType
FROM OrganizationUnits ou
INNER JOIN sysprocodes sp ON (ou.OrganizationUnitID=sp.organisationunitid)
INNER JOIN tblpayrollcode pc ON (pc.PayrollCode_ID = sp.PayrollCode_ID)
where sp.PayrollCode_ID = 2
GROUP BY ou.OrganizationUnitID,sp.sysprocode,sp.PayrollCode_ID,sp.accountType
)TB4
WHERE (TB2.OUId=TB1.OUId)
AND (TB3.OUId=TB1.OUId)
AND(TB4.OUId =tb3.OUId)
Click HERE to view the SQLFIDDLE

Oracle SQL (no listagg) need to combine multiple rows into a single column

I am query multiple tables and need to combine all the values from a table called "NOTES" for a given ID.
Here is what I have:
SELECT e.HR_NUMBER, s.SALES_ID, s.SALES_ID_TYPE, m.REGION_ID,
e.ADDED_DATE_TIME, e.TERMINATION_DATE, s.HOUSE_ACCOUNT,
(SELECT t.NOTE FROM employee_note t WHERE e.hr_number = t.HR_NUMBER)
FROM employee e
INNER JOIN sales_id s
ON e.HR_NUMBER = s.HR_NUMBER
LEFT JOIN market m
ON s.MARKET_ID = m.MARKET_ID
LEFT JOIN region r
ON m.REGION_ID = r.REGION_ID
LEFT JOIN sales_id_type t
ON s.SALES_ID_TYPE = t.SALES_ID_TYPE
LEFT JOIN employee_note n
ON e.HR_NUMBER = n.HR_NUMBER
The problem is obviously it will not work when there are multiple notes for a given ID.
If I do the following sql:
SELECT e.HR_NUMBER, s.SALES_ID, s.SALES_ID_TYPE, m.REGION_ID,
e.ADDED_DATE_TIME, e.TERMINATION_DATE, s.HOUSE_ACCOUNT, n.NOTE
FROM employee e
INNER JOIN sales_id s
ON e.HR_NUMBER = s.HR_NUMBER
LEFT JOIN market m
ON s.MARKET_ID = m.MARKET_ID
LEFT JOIN region r
ON m.REGION_ID = r.REGION_ID
LEFT JOIN sales_id_type t
ON s.SALES_ID_TYPE = t.SALES_ID_TYPE
LEFT JOIN employee_note n
ON e.HR_NUMBER = n.HR_NUMBER
I get the following:
000000 E0019 XXX XXX 23-JUN-10 N NOTE 1
000000 E0019 XXX XXX 23-JUN-10 N NOTE 2
000000 E0019 XXX XXX 23-JUN-10 N NOTE 3
What I really need is:
000000 E0019 XXX XXX 23-JUN-10 N NOTE 1, NOTE 2, NOTE 3
I know you can use LISTAGG in oracle by my version doesn't support it. Also I have tried several other answers on stackoverflow but I can't seem to get them to work for my query.
Any help is much appreciated.
Check if the below query using Common Table Expression [cte] would help:
WITH CTE AS
(
SELECT e.HR_NUMBER, s.SALES_ID, s.SALES_ID_TYPE, m.REGION_ID,
e.ADDED_DATE_TIME, e.TERMINATION_DATE, s.HOUSE_ACCOUNT, n.NOTE,
, ROW_NUMBER () OVER (ORDER BY NOTE ) rn,
COUNT (*) OVER () cnt
FROM employee e
INNER JOIN sales_id s
ON e.HR_NUMBER = s.HR_NUMBER
LEFT JOIN market m
ON s.MARKET_ID = m.MARKET_ID
LEFT JOIN region r
ON m.REGION_ID = r.REGION_ID
LEFT JOIN sales_id_type t
ON s.SALES_ID_TYPE = t.SALES_ID_TYPE
LEFT JOIN employee_note n
ON e.HR_NUMBER = n.HR_NUMBER
)
SELECT HR_NUMBER, SALES_ID, SALES_ID_TYPE,REGION_ID,
ADDED_DATE_TIME, TERMINATION_DATE, HOUSE_ACCOUNT,
SUBSTR (SYS_CONNECT_BY_PATH (NOTE , ','), 2) csv
FROM CTE
WHERE rn = cnt
START WITH rn = 1
CONNECT BY rn = PRIOR rn + 1;
I am basically trying to merge your query with a sample solution provided here

selecting the max values based on a count

How can i retrieve the max of each ValueCount based on the firmid. I need the data to be output like so.
My code is below
SELECT
F.FirmID,
F.Name,
DL.ValueId,
DL.ValueName,
count(DL.ValueName) AS ValueCount
FROM
dbo.Jobs AS J
INNER JOIN DimensionValues AS DV ON
DV.CrossRef = J.JobId
INNER JOIN dbo.DimensionLists AS DL ON
DV.ValueId = DL.ValueId
INNER JOIN Firms AS F ON
F.FirmId = J.ClientFirmId
WHERE
DL.DimensionId = 4
GROUP BY
F.FirmID,
F.Name,
DL.ValueName,
DL.ValueId
this produces something like
firmid | value | count
1 1 5
1 2 10
2 3 1
2 1 6
i need to return back the records with 10 and 6.
EDIT : SQL 2005 answer deleted.
Then you could push your results into a temporary table (or table variable) and do something like this...
SELECT
*
FROM
TempTable
WHERE
ValueCount = (SELECT MAX(ValueCount) FROM TempTable AS Lookup WHERE FirmID = TempTable.FirmID)
Or...
SELECT
*
FROM
TempTable
INNER JOIN
(SELECT FirmID, MAX(ValueCount) AS ValueCount FROM TempTable GROUP BY FirmID) AS lookup
ON lookup.FirmID = TempTable.FirmID
AND lookup.ValueCount = TempTable.ValueCount
These will give multiple records if any ValueCount is tied with another for the same FirmID. As such, you could try this...
SELECT
*
FROM
TempTable
WHERE
value = (
SELECT TOP 1
value
FROM
TempTable as lookup
WHERE
FirmID = TempTable.FirmID
ORDER BY
ValueCount DESC
)
For this problem you need to produce the result set of the query in order to determine the Max ValueCount, then you need to do the query again to pull just the records with Max ValueCount. You can do this many way, like repeating the main query as subqueries, and in SQL Server 2005/2008 by using a CTE. I think using the subqueries gets a little messy and would prefer the CTE, but for SQL Server 2000 you don't have that as an option. So, I've used a temp table instead of a CTE. I run it once to get the MaxValueCount and save that into a temp table, then run the query again and join against the temp table to get just the record with MaxValueCount.
create table #tempMax
(
FirmID int,
MaxValueCount int
)
insert #tempMax
SELECT t.FirmID, MAX(t.ValueCount) AS MaxValueCount
FROM (
SELECT F.FirmID, F.Name, DL.ValueId, DL.ValueName
, count(DL.ValueName) AS ValueCount
FROM dbo.Jobs AS J
INNER JOIN DimensionValues AS DV ON DV.CrossRef = J.JobId
INNER JOIN dbo.DimensionLists AS DL ON DV.ValueId = DL.ValueId
INNER JOIN Firms AS F ON F.FirmId = J.ClientFirmId
WHERE DL.DimensionId = 4
GROUP BY F.FirmID, F.Name, DL.ValueName, DL.ValueId) t
SELECT t.FirmID, t.Name, t.ValueID, t.ValueName, t.ValueCount
FROM (
SELECT F.FirmID, F.Name, DL.ValueId, DL.ValueName
, count(DL.ValueName) AS ValueCount
FROM dbo.Jobs AS J
INNER JOIN DimensionValues AS DV ON DV.CrossRef = J.JobId
INNER JOIN dbo.DimensionLists AS DL ON DV.ValueId = DL.ValueId
INNER JOIN Firms AS F ON F.FirmId = J.ClientFirmId
WHERE DL.DimensionId = 4
GROUP BY F.FirmID, F.Name, DL.ValueName, DL.ValueId) t
INNER JOIN #tempMax m ON t.FirmID = m.FirmID and t.ValueCount = m.MaxValueCount
DROP TABLE #tempMax
You should be able to use a derived table for this:
SELECT F.FirmID,
F.Name,
DL.ValueId,
DL.ValueName,
T.ValueCount
FROM Jobs J
INNER JOIN DimensionValues DV
ON DV.Crossref = J.JobID
INNER JOIN DimensionList DL
ON DV.ValueID = DL.ValueID
INNER JOIN Firms F
ON F.FirmID = J.ClientFirmID
--derived table
INNER JOIN (SELECT FirmID, MAX(ValueName) ValueCount FROM DimensionList GROUP BY FirmID) T
ON T.FirmID = F.FirmID
WHERE DL.DimensionId = 4
TBL1 and TBL2 is your query:
SELECT *
FROM TBL1
WHERE
TBL1.ValueCount = (SELECT MAX(TBL2.ValueCount) FROM TBL2 WHERE TBL2.FIRMID = TBL1.FIRMID)