Query to get grandparents and grandkids - sql

I got a table people:
ID name surname mother_id father_id
--------------------------------------------
1 John smith null null
2 kate m null null
3 philip smith 2 1
4 dimitr smith 7 3
etc.
I tried this query.
SELECT
p.name, P.surname,
c.name as 'Mothers name', c.surname as 'Mothers surname',
gm.name as 'Grandmother name', gm.surname as 'Grandmother surname'
FROM
dbo.people AS p
JOIN
dbo.people AS c ON (c.mother = p.Id)
JOIN
dbo.people AS gm ON (gm.mother = c.Id)
But it doesn't really return what I expected

Problem with ON clause :
select p.name, p.surname,
coalesce(pm.name, '') as mothername, coalesce(pm.surname, '') as mothersurname,
coalesce(pf.name, '') as grandmothername,
coalesce(pf.surname, '') as grandmothersurname
from people p left join
people pm
on pm.id = p.mother_id left join -- use mother_id from people p
people pf
on pf.id = p.father_id; -- use father_id from people p;
Here is db-fiddle.

Related

How find people with two specific licenses, separate rows, only one with limited permit

I'm trying to put together a query so I can find people in our db that have both a NY permit/license and also a limited permit/license, but the rows are separate rows in the query. I only want the ones without NYS license, where they are limited.
This is what I have so far:
p.Last_Name
,p.First_Name
,p.Middle_Initial
,p.person_id
,i.Document
,i.Expiration_date
,i.Updated_Date
--,i.IssueDate
--,i.Historical
--,i.Comments
--,f.Current_status
,i.State
from dbo.PersonDoc_ID_Numbers i
inner join "DB"."dbo"."People" p on p.person_id = i.person_id
inner join person_facilities f on p.person_id = f.person_id
where
(Document like '%limited%' and i.State like '%NY%' and ((i.Expiration_date > getdate()) or (i.Expiration_date is null))) or
(Document like '%NY%' and i.State like '%NY%' and ((i.Expiration_date > getdate()) or (i.Expiration_date is null)))
and f.current_status in ('Active')
and i.Historical != 1
order by p.Last_Name
So that's returning like this for someone, but I don't want the ones with NY license as well as limited; I just want ones with limited without NY license:
Last_Name First_Name Middle_Initial person_id Document Expiration_date Updated_Date
GG FF L. 123 Limited Permit 2019-09-23 2018-10-04 456
GG FF L. 123 NY State License NULL 2018-11-13
Smith Tony L. 456 Limited Permit 2019-09-23 2018-10-04 456
Snoopy Dog L. 789 NY State License NULL 2018-11-13
So for FF GG, I wouldn't be interested in her name being returned because she has ny license, and the limited. I only want ones with just limited. But I would expect it to return Tony Smith and that's it.
*Update as requested:
dbo.PersonDoc_ID_Numbers
person_id Expiration_date State license_NO Updated_Date Historical Document
1 2019-9-22 NY 2 2018-10-04 0 Limited Perm *
1 null NY no 2019-4-17 0 NY State
2 null NY 3 2011-06-30 0 Limited Lic
2 null NY 4 2011-06-14 0 NY State
3 2018-5-04 NY 5 2018-05-01 0 Limited Perm*
4 2019-08-29 NY 6 2018-01-12 0 Limited Lic *
5 null null 7 2016-4-20 0 Jujufication
5 null NY 8 2018-4-05 0 NY State
6 2016-07-15 OH 9 2018-01-09 0 Snoopyism
dbo.Person_Facilities
person_id Current_status
1 Active
2 Active
3 Active
4 Active
5 Active
6 Active
dbo.Person
person_id First Last
1 GG FF
2 MM PP
3 Tony Smith
4 Snoopy Dog
5 Sarah Gd
6 Bethany Yoda
I put an * next to the ones in the id table where they are expected to be in the end set. Note that it turns out FF GG doesn't have a license number so she needs to be in the end set.
Assuming your current query is all fine, you could use COUNT(*) OVER(PARTITION BY p.person_id) AS noOfRows to determine how many rows the query will generate, then select only the entries with a single row with a query along these lines:
WITH myCte AS (
SELECT
p.Last_Name
,p.First_Name
,p.Middle_Initial
,p.person_id
,i.Document
,i.Expiration_date
,i.Updated_Date
,i.IssueDate
,i.Historical
,i.Comments
,f.Current_status
,i.State as DocumentState
,COUNT(*) OVER(PARTITION BY p.person_id) AS noOfRows
from dbo.PersonDoc_ID_Numbers i
inner join "DB"."dbo"."People" p on p.person_id = i.person_id
left join person_facilities f on p.person_id = f.person_id
where
(Document like '%limited%' and i.State like '%NY%' and ((i.Expiration_date > getdate()) or (i.Expiration_date is null))) or
(Document like '%NY%' and i.State like '%NY%' and ((i.Expiration_date > getdate()) or (i.Expiration_date is null)))
and f.current_status in ('Active')
and i.Historical != 1
)
SELECT Last_Name
,First_Name
,Middle_Initial
,person_id
,Document
,Expiration_date
,Updated_Date
,IssueDate
,Historical
,Comments
,Current_status
,DocumentState
FROM myCte
WHERE noOfRows = 1
AND Document like '%limited%'
Here's a simplified example of this at work: SQLFiddle
EDIT:
Given the sample data you have provided, here is my suggested solution:
WITH myCte AS (
SELECT
p.Last,
p.First,
p.person_id,
i.Document,
i.Expiration_date,
i.Updated_Date,
i.Historical,
f.Current_status,
i.State as DocumentState,
COUNT(*) OVER(PARTITION BY p.person_id) AS noOfRows
FROM dbo.PersonDoc_ID_Numbers i
JOIN dbo.Person p ON p.person_id = i.person_id
JOIN person_facilities f ON p.person_id = f.person_id
WHERE
(Document like '%limited%' AND i.State like '%NY%' AND ((i.Expiration_date > getdate()) OR (i.Expiration_date is null))) or
(Document like '%NY%' AND i.State like '%NY%' AND ((i.Expiration_date > getdate()) OR (i.Expiration_date is null)))
AND f.current_status in ('Active')
AND i.Historical != 1
)
SELECT Last
,First
,person_id
,Document
,Expiration_date
,Updated_Date
,Historical
,Current_status
,DocumentState
FROM myCte
WHERE noOfRows = 1
AND Document like '%limited%'
Here's a new example using the data provided: SQLFiddle
You can try the query below.
WITH cte AS (
SELECT p.Last_Name
, p.First_Name
, p.Middle_Initial
, p.person_id
, i.Document
, i.Expiration_date
, i.Updated_Date
FROM dbo.PersonDoc_ID_Numbers i
JOIN "DB"."dbo"."People" p ON p.person_id = i.person_id
JOIN person_facilities f ON p.person_id = f.person_id
WHERE /*Document LIKE '%limited%'
AND i.State LIKE '%NY%'
AND*/ (i.Expiration_date > GETDATE() OR i.Expiration_date IS NULL)
AND f.current_status IN ('Active')
AND i.Historical != 1
)
SELECT *
FROM cte c
WHERE Document LIKE '%limited%' AND i.State LIKE '%NY%'
AND NOT EXISTS (
SELECT 1
FROM cte
WHERE person_id = p.person_id AND Document NOT LIKE '%limited%' AND i.State LIKE '%NY%'
)
ORDER BY Last_Name

sql case when x=? then y=? end

I ahve a problem with an sql case, I want to check a column value, and if that returns true then I want another column value to change, but in a 'case' you can only check one column and change only the value of that, how do I make it work?
select f.[film name],f.city,p.[participant name],
case rt.[type name]
when 'director' then 'director' else null
end 'role type'
from Films f
join FilmParticipants fp on fp.filmID=f.filmID
join Participants p on p.participantID=fp.participantID
join RoleType rt on rt.roleID=fp.roleID
where f.city in(
select f.city
from Films f
group by f.city
having COUNT(*)>1)
order by f.city
it gives this table:
film name | city | participate name | role type
Shrek | London | John | null
Shrek | London | John | null
Dragon | London | Rick | null
Drago | London | Morty | Director
now I want that whenever there is null in the 'role type column' that the 'participate name' column will be null as well.
You could use case when same way for role type and show partecipant.name
select f.[film name],f.city,
case rt.rt.[type name]
when 'director' then p.[participant name] else null end 'partecipant',
case rt.[type name]
when 'director' then 'director' else null
end 'role type'
from Films f
join FilmParticipants fp on fp.filmID=f.filmID
join Participants p on p.participantID=fp.participantID
join RoleType rt on rt.roleID=fp.roleID
where f.city in(
select f.city
from Films f
group by f.city
having COUNT(*)>1)
order by f.city
for remove the null
select [film name], city, max(partecipant), max([role type])
from (
select f.[film name],f.city,
case rt.rt.[type name]
when 'director' then p.[participant name] else null end 'partecipant',
case rt.[type name]
when 'director' then 'director' else null
end 'role type'
from Films f
join FilmParticipants fp on fp.filmID=f.filmID
join Participants p on p.participantID=fp.participantID
join RoleType rt on rt.roleID=fp.roleID
where f.city in(
select f.city
from Films f
group by f.city
having COUNT(*)>1)
order by f.city ) t
group by [film name], city

T-SQL - Concatenate Multiple Rows

I am struggling with a query to return a list of managers with their respective employees
I have three tables as follows:
Managers
ManagerID ManagerName
1 Bob
2 Sally
3 Peter
4 George
EmployeeManager
EmployeeID ManagerID
1 1
1 1
2 2
2 2
3 3
3 3
4 4
4 4
Employees
EmployeeID EmployeeName
1 David
1 Joseph
2 Adam
2 Pete
3 Mark
3 Mavis
4 Susan
4 Jennifer
Desired Result Set
ManagerName CountEmployee Employees
Bob 2 David, Joseph
Sally 2 Anish, Pete
Peter 2 Mark, Mavis
George 2 Susan, Jennifer
The query I am currently using is as follows:
Select m.ManagerName
,Count(e.EmployeeName) Over(Partition By m.ManagerID) as CountEmployee
,Rank() Over(Partition By m.ManagerID Order By em.EmployeeID) [RankEmployee]
,e.EmployeeName
From dbo.Employees e
Left Join dbo.EmployeeManager em on em.ManagerID=e.ManagerID
Left Join dbo.Managers m on m.ManagerID=em.ManagerID;
This returns a list of managers and employees on individual rows but I'm struggling to concatenate the employee names as per the above table.
Any ideas or solutions?
Manpaal Singh
You can stuff the result to comma seperated result.
Select m.ManagerName
,Count(e.EmployeeName) Over(Partition By m.ManagerID) as CountEmployee
,Rank() Over(Partition By m.ManagerID Order By em.EmployeeID) [RankEmployee]
,STUFF((SELECT ',' + e.EmployeeName
FOR XML PATH('')), 1, 1, '') AS EmployeeName
From dbo.Employees e
Left Join dbo.EmployeeManager em on em.ManagerID=e.ManagerID
Left Join dbo.Managers m on m.ManagerID=em.ManagerID
SELECT M.ManagerName, E.EmployeeName
FROM Managers AS M
INNER JOIN EmployeeManager AS EM ON M.ManagerID = EM.ManagerID
INNER JOIN Employees AS E ON EM.EmployeeID = E.EmployeeID
ORDER BY M.ManagerName
This will give a list of managers and their employees.
when you fix the ID's of the employees table.
1 David
1 Joseph
2 Adam
2 Pete
3 Mark
3 Mavis
4 Susan
4 Jennifer
should be:
1 David
2 Joseph
3 Adam
4 Pete
5 Mark
6 Mavis
7 Susan
8 Jennifer
you can use recursive sql to convert rows in a string :
with t1 (mngId, empName) as (
select a.mngId,
b.empname
from manager as a, employee as b
where b.mngId = a.mngId),
t2 (mngID, nbr, empName, all_name) as (
select mngId,
cast(1 as Int),
min(empName),
cast(min(empName) as varchar(1000)
from t1
group by mngId
union all
select b.mngId,
b.nbr+1,
a.empName,
trim(b.all_name) concat ', ' concat a.empName
from t0 as a, t1 as b
where b.mngId = a.mngId
and a.empName > b.empName
and a.empName = (
select min( c.empName)
from t0 as c
where c.mngId = b.mngId
and c.empName > b.empName )
)
select *
from t1 as e
where nbr = (
select max(nbr)
from t1 as d
where d.mngId = e.mngId )

sum of sales per postal code group

I have a query where you can see all the sales of a person divided in postal groups. But now I want to have the sum of all the sales per postal group.
I have the query like this:
SELECT
p.LastName,
ROW_NUMBER() OVER (PARTITION BY PostalCode
ORDER BY SUM(s.SalesYTD) DESC) AS 'Row Number',
CAST(s.SalesYTD AS INT) SalesYTD,
a.PostalCode
FROM
Sales.SalesPerson s
INNER JOIN
Person.Person p ON s.BusinessEntityID = p.BusinessEntityID
INNER JOIN
Person.Address a ON a.AddressID = p.BusinessEntityID
GROUP BY
p.LastName, a.PostalCode,s.SalesYTD;
--WHERE TerritoryID IS NOT NULL
--AND SalesYTD <> 0
But how to manage the total of each postal group?
Thank you
This is the ouput:
LastName Row Number SalesYTD PostalCode
Mitchell 1 4251369 98027
Blythe 2 3763178 98027
Carson 3 3189418 98027
Reiter 4 2315186 98027
Vargas 5 1453719 98027
Ansman-Wolfe 6 1352577 98027
Jiang 7 559698 98027
Pak 1 4116871 98055
Varkey Chudukatil 2 3121616 98055
Saraiva 3 2604541 98055
Ito 4 2458536 98055
Valdez 5 1827067 98055
Mensa-Annan 6 1576562 98055
Campbell 7 1573013 98055
Tsoflias 8 1421811 98055
Alberts 9 519906 98055
Abbas 10 172524 98055
So how to get the total of salesYTD of postal code: 98027?
Thank you
To get sales per postal code just:
SELECT PostalCode, SUM(SalesYTD) AS 'Summary sales'
FROM Sales.SalesPerson s
INNER JOIN Person.Person p ON s.BusinessEntityID = p.BusinessEntityID
INNER JOIN Person.Address a ON a.AddressID = p.BusinessEntityID
GROUP BY a.PostalCode
If you want sales of particular PostalCode you can even do it that way:
SELECT SUM(SalesYTD) AS 'Summary sales'
FROM Sales.SalesPerson s
INNER JOIN Person.Person p ON s.BusinessEntityID = p.BusinessEntityID
INNER JOIN Person.Address a ON a.AddressID = p.BusinessEntityID
WHERE PostalCode = 98027

How to join 2 tables with select and count in single query

I need to join 2 tables (Person and PersonLine). The result should contain id and name column from Person table and count of personlineid column from PersonLine Table for each id. But sql query returns count of all personlineid. Can anyone help to form the sql.
Person:
ID NAME AGE
100 John 25
101 James 30
102 Janet 35
PersonLine:
ID NAME PERSONLINEID
100 John 1
100 John 2
100 John 3
101 James 1
101 James 2
102 Janet 1
SQL:
SELECT P.ID, CNT.COUNT_PERSONLINE, P.NAME
FROM PERSON P
LEFT JOIN PERSONLINE PL
ON P.ID = PL.ID,
(SELECT count(PL.PERSONLINEID) cnt FROM PERSON P LEFT JOIN PERSONLINE PL ON P.ID = PL.ID WHERE
P.ID = PL.ID) cnt
JOIN Table (Expected):
ID COUNT_PERSONLINE NAME
100 3 John
101 2 James
102 1 Janet
JOIN Table (Actual):
ID COUNT_PERSONLINE NAME
100 6 John
101 6 James
102 6 Janet
With your sample data, you don't even need the Person table -- because you seem to have redundant table in the two tables. You should probably fix this, but:
select pl.id, pl.name, count(*)
from personline pl
group by pl.id, pl.name;
Your count is just counting all the rows from the join of the tables -- that would be all the rows. A simple aggregation should suffice, even if you decide that the join is still necessary.
EDIT:
You have several choices with lots of columns in persons. One method is to put them in the group by:
select pl.id, pl.name, p.col1, p.col2, count(*)
from persons p join
personline pl
on p.id = pl.id
group by pl.id, pl.name, p.col1, p.col2
Another method is to do the aggregation before the join:
select p.*, pl.cnt
from person p join
(select pl.id, pl.name, count(*) as cnt
from personline pl
group by pl.id, pl.name
) pl
on p.id = pl.id;
Or, a correlated subquery:
select p.*, (select count(*) from personline pl where p.id = pl.id)
from person;