Pivoting duplicate data from rows to columns in SQL Server - sql

I have a dataset with duplicate values due to number of feedback received for a particular candidate.
There are 3 feedback types are Security, Others and Social.
A candidate can have multiple of these feedback. And this feedback type name has to be taken by joining Org and orgtype table. But this gives me duplicate values in my result set.
The query with few columns is like this:
select
c.id as [Candidate ID]
,c.name as [Candidate Name]
,cf.status as [feedback status]
,e.name as [Feedback Type]
from
Candidates c
left join
Candidate_Feedback CF ON CF.CandidateId = c.ID
left join
Organizations d on CF.OrgId = d.ID
left join
OrganizationTypes e on d.OrganizationTypeId = e.Id
How can I pivot data where I need only one row for each candidate and feedback types in columns instead of rows? ( Such as col1 - Feedback_social, Col2 - Feedback_Other, col3 - Feedback_Security , col4 - 'N/A' (if feedback not present)
Due to other scenarios in my query I need these 3 columns for each candidate.

You could use using conditional aggregation, like:
select
c.id as [Candidate ID],
c.name as [Candidate Name]
max(case when e.name = 'Social' then cf.status end) [Feedback Social],
max(case when e.name = 'Other' then cf.status end) [Feedback Other],
max(case when e.name = 'Security' then cf.status end) [Feedback Security]
from Candidates c
left join Candidate_Feedback CF ON CF.CandidateId = c.ID
left join Organizations d on CF.OrgId = d.ID
left join OrganizationTypes e on d.OrganizationTypeId = e.Id
group by c.id, c.name

Related

Select an ID where there is only one row and that row is a specific value

I have this query. There's a lot of joins because I am checking if an ID is linked to any of those tables.
Currently, this query shows me any ID's that are not linked to any of those tables. I would like to add to it so that it also shows any IDs that are linked to the d table, but only if there is only 1 row in the D table and the type in the D field is 'member'.
SELECT
c.ID,
c.location,
c.pb,
c.name,
c.surname
FROM c
LEFT JOIN l on c.rowno = l.rowno
LEFT JOIN d on c.rowno = d.rowno
LEFT JOIN t on c.rowno = t.rowno
LEFT JOIN cj ON (c.rowno = cj.rowno OR c.rowno = cj.rowno2)
LEFT JOIN dj ON c.rowno = d.rowno
LEFT JOIN lg ON c.rowno = lg.rowno
LEFT JOIN tj ON c.rowno = tj.rowno
WHERE
c.status != 'closed'
AND l.rowno IS NULL
AND d.rowno IS NULL
AND t.rowno IS NULL
AND cj.rowno IS NULL
AND dj.rowno IS NULL
AND lg.rowno IS NULL
AND tj.rowno IS NULL
My first thought is to just add
WHERE D.type = 'member'
But that gives me all IDs that have a row with D.type = member (they could have 10 rows with all different types, but as long as 1 of those has type = member it shows up). I want to see ID's that ONLY have d.type = member
I'm sorry if I'm wording this badly, I'm having trouble getting this straight in my head. Any help is appreciated!
I would use exists for all conditions except the one on the D table:
SELECT c.*
FROM c JOIN
(SELECT d.rownum, COUNT(*) as cnt,
SUM(CASE WHEN d.type = 'Member' THEN 1 ELSE 0 END) as num_members
FROM t
GROUP BY d.rownum
) d
ON c.rownum = d.rownum
WHERE c.status <> 'closed' AND
NOT EXISTS (SELECT 1 FROM t WHERE c.rowno = t.rowno) AND
NOT EXISTS (SELECT 1 FROM l WHERE c.rowno = l.rowno) AND
. . .
I find NOT EXISTS is easier to follow logically. I don't think there is a big performance difference between the two methods in SQL Server.

invalid column name in HAVING [duplicate]

This question already has answers here:
Why can't I use an alias for an aggregate in a having clause?
(8 answers)
Closed 4 years ago.
Anyone have an idea why the 'CoID' value isnt recognized?
select
ac.AccountID
, max(case when c.Name = 'email' then c.Data end) as Email
, max(case when c.Name = 'phone' then c.Data end) as Phone
, max(a.CompanyID) as CoID
from paul_AccountContacts ac
left join paul_Contact c on c.ID = ac.ContactID
left join paul_Account a on a.ID = ac.AccountID
having (CoID in (1506)) --ERROR HERE
order by ac.AccountID
Error:
Invalid column name 'CoID'.
If, you want to filter the single record then use where clause instead of having
select
ac.AccountID,
max(case when c.Name = 'email' then c.Data end) as Email,
max(case when c.Name = 'phone' then c.Data end) as Phone,
max(a.CompanyID) as CoID
from paul_AccountContacts ac
left join paul_Contact c on c.ID = ac.ContactID
left join paul_Account a on a.ID = ac.AccountID
group by ac.AccountID
where a.CompanyID = 1506 -- use IN clause whenever you have multiple CompanyID ids
order by ac.AccountID
However, having is used when you want to filter out after some aggregation or filter out based on aggregation or withing the aggregation
So. your query will look like with having clause :
select
ac.AccountID,
max(case when c.Name = 'email' then c.Data end) as Email,
max(case when c.Name = 'phone' then c.Data end) as Phone,
max(a.CompanyID) as CoID
from paul_AccountContacts ac
left join paul_Contact c on c.ID = ac.ContactID
left join paul_Account a on a.ID = ac.AccountID
group by ac.AccountID
having max(a.CompanyID) = 1506
order by ac.AccountID;
In logical query processing SELECT is executed after HAVING or WHERE clause clause. therefore it is not able to recognize column name created in SELECT. Try using max(a.CompanyID) instead:
select
ac.AccountID
, max(case when c.Name = 'email' then c.Data end) as Email
, max(case when c.Name = 'phone' then c.Data end) as Phone
, max(a.CompanyID) as CoID
from paul_AccountContacts ac
left join paul_Contact c on c.ID = ac.ContactID
left join paul_Account a on a.ID = ac.AccountID
Group by ac.AccountID
having max(a.CompanyID) = 1506 --ERROR HERE
order by ac.AccountID
You are missing the group by clause, which you obviously want since you are using aggregation functions. I assume from your select what you want is:
group by ac.AccountID
Afterwards, are you trying to take only CompanyIDs of 1506, or you want to only show grouping results for which max is 1506: The first is done like this:
Where a.CompanyID = 1506
the second like this
having max(a.CompanyID)=1506

SQL SELECT clause with WHERE statement for specific order

I have a through table, doctor_specialties that has a column ordinal that I would like to use in order to create a column named primary_specialty and also secondary_specialty. The logic for primary_specialty is WHERE ordinal == 1.
How can I add the primary_specialty and secondary_specialty columns? One approach would be to use a WHERE statement with the INNER JOIN but I think that would be less efficient?
SELECT pd.name AS "doctor_name",
s.name AS "primary_specialty" WHERE ds.ordinal == 1
FROM doctor_profiles AS dp
INNER JOIN doctor_specialties AS ds on dp.id = ds.doctor_profile_id
INNER JOIN specialties AS s on ds.specialty_id = s.id
Desired output is
name primary_specialty secondary_specialty
Josh Dermatology, Cosmetic Dermatology
Linda Primary Care null
You need to achive this by using case statement. example is shared below
SELECT pd.name AS "doctor_name",
case when ds.ordinal = 1 then s.name end as "primary_specialty",
case when ds.ordinal <> 1 then s.name end as "secondary_specialty"
FROM doctor_profiles AS dp
INNER JOIN doctor_specialties AS ds on dp.id = ds.doctor_profile_id
INNER JOIN specialties AS s on ds.specialty_id = s.id
You can use conditional aggregation:
SELECT dp.name AS "doctor_name",
MAX(CASE WHEN ds.ordinal = 1 THEN s.name END) AS "primary_specialty",
MAX(CASE WHEN ds.ordinal != 2 THEN s.name END) AS "secondary_specialty"
FROM doctor_profiles AS dp
INNER JOIN doctor_specialties AS ds on dp.id = ds.doctor_profile_id
INNER JOIN specialties AS s on ds.specialty_id = s.id
GROUP BY pd.name
You can alter the existing, or use additional MAX aggregates containing CASE expressions, in order to implement the logic for secondary specialties.

Selecting columns based on a case SQL

I'm wondering how I can return specific results depending on my first selected statement. Basically I have two IDs. CustBillToID and CustShipToID. If CustShipToID is not null I want to select that and all the records that are joined to it. If it is null default to the CustBillToID and all the results that are joined to that.
Here is my SQL that obviously doesn't work. I should mention I tried to do a sub query in the conditional, but since it returns multiple results it won't work. I am using SQL Server 2012.
SELECT CASE WHEN cp.CustShipToID IS NOT NULL
THEN
cy.CustDesc,
cy.Address1,
cy.Address2,
cy.City,
cy.State,
cy.ZIP,
cy.Phone
ELSE
c.CustDesc,
c.Address1,
c.Address2,
c.City,
c.State,
c.ZIP,
c.Phone
END
LoadID,
cp.CustPOID,
cp.POBillToRef,
cp.POShipToRef,
cp.CustBillToID,
cp.CustShipToID,
cp.ArrivalDate,
cp.LoadDate,
cp.StopNum,
cp.ConfNum,
cp.EVNum,
cp.ApptNum,
ld.CarrId,
ld.Temperature,
cr.CarrDesc
FROM [Sales].[dbo].[CustPO] AS cp
LEFT OUTER JOIN Load AS ld
ON cp.LoadID = ld.LoadID
LEFT OUTER JOIN Carrier AS cr
ON ld.CarrId = cr.CarrId
LEFT OUTER JOIN Customer AS c
ON c.CustId = cp.CustBillToID
WHERE CustPOID=5213
Any ideas?
Also my current SQL is below, I do a conditional to determine if it's set. I'd rather do it in SQL if possible.
SELECT cp.LoadID,
cp.CustPOID,
cp.POBillToRef,
cp.POShipToRef,
cp.CustBillToID,
cp.CustShipToID,
cp.ArrivalDate,
cp.LoadDate,
cp.StopNum,
cp.ConfNum,
cp.EVNum,
cp.ApptNum,
ld.CarrId,
ld.Temperature,
cr.CarrDesc,
c.CustDesc as CustBillToDesc,
c.Address1 as CustBillAddress1,
c.Address2 as CustBillAddress2,
c.City as CustBillCity,
c.State as CustBillState,
c.ZIP as CustBillZIP,
c.Phone as CustBillPhone,
cy.CustDesc as CustShipToDesc,
cy.Address1 as CustShipAddress1,
cy.Address2 as CustShipAddress2,
cy.City as CustShipCity,
cy.State as CustShipState,
cy.ZIP as CustShipZIP,
cy.Phone as CustShipPhone
FROM [Sales].[dbo].[CustPO] as cp
left outer join Load as ld
on cp.LoadID = ld.LoadID
left outer join Carrier as cr
on ld.CarrId = cr.CarrId
left outer join Customer as c
on c.CustId = cp.CustBillToID
left outer join Customer as cy
on cy.CustId = cp.CustShipToID
WHERE CustPOID=?
You need a separate case for each column:
SELECT (CASE WHEN cp.CustShipToID IS NOT NULL THEN cy.CustDesc ELSE c.CustDesc END) as CustDesc,
(CASE WHEN cp.CustShipToID IS NOT NULL THEN cy.Address1 ELSE c.Address1 END) as Address1,
(CASE WHEN cp.CustShipToID IS NOT NULL THEN cy.Address2 ELSE c.Address2 END) as Address2,
(CASE WHEN cp.CustShipToID IS NOT NULL THEN cy.City ELSE c.City END) as City,
(CASE WHEN cp.CustShipToID IS NOT NULL THEN cy.State ELSE c.State END) as State,
(CASE WHEN cp.CustShipToID IS NOT NULL THEN cy.ZIP ELSE c.ZIP END) as ZIP,
(CASE WHEN cp.CustShipToID IS NOT NULL THEN cy.Phone ELSE c.Phone END) as Phone,
. . .
For this, you basically want to build a string that is your SQL and then execute the string...look # the answer to this one ::
SQL conditional SELECT
Did you try coalesce(CustShipToID,CustBillToID ) ?
...
FROM [Sales].[dbo].[CustPO] as cp
left outer join Load as ld
on cp.LoadID = ld.LoadID
left outer join Carrier as cr
on ld.CarrId = cr.CarrId
inner join Customer as c
on c.CustId = coalesce(cp.CustShipToID,cp.CustBillToID )
...

comparing fields in 2 different tables using SQL

I would like to compare if the address fields in contact table are different to that of the delivery table.
SELECT contactID, addressline1
FROM contact
where contactID = '0018319'
Below is the delivery table which contains the old details.
SELECT contactID, addressline1
FROM delivery
where contactID = '0018319'
SELECT contactID, d.addressline1, c.addressline1
FROM delivery d
INNER JOIN contact c on d.contactID = c.contactID
where d.addressline1 != c.addressline1
If you want to return a flag, then you would use case in the select statement:
select contactId,
(case when d.addressline1 = c.addressline1 or d.addressline1 is null and c.addressline1 is null
then 'SAME'
else 'DIFFERENT'
end) as SameOrDifferent
from contact c join
delivery d
on c.contactId = d.contactId
where contactId = '0018319';
This is going to compare each address in the two tables.
If you want to know if all are the same, then the query is more complicated. The idea is to do a full outer join between the two table (for the given contractid) on addressline1. If all the addresslines match, then the full outer join will never produce NULL values. If any are missing (on either side), then there will be NULL values.
select coalesce(c.contactId, d.contactId) as contactId,
(case when sum(case when c.addressline1 is null or d.addressline1 is null
then 1
else 0
end) = 0
then 'SAME'
else 'DIFFERENT'
end) as SameOrDifferent
from (select c.* from contact c where c.contactId = '0018319') c full outer join
(select d.* from delivery d where d.contactId = '0018319') d
on c.addressline1 = d.addressline1 and
c.contactId = d.contactId -- not actually necessary but useful for multiple contacts
group by coalesce(c.contactId, d.contactId)