How do you turn string values into columns in unpivot statement? - sql

Im trying write an unpivot query that turns User data stored in columns into one row per user. Heres what I have so far, thanks to other SO posts:
http://sqlfiddle.com/#!18/fa77c/2
Error (makes sense because "email" is a string stored in the Name column):
Invalid column name 'email'.
End goal:
ID Email Phone
1 a#a.com 111
2 b#b.com 222
3 NULL 333

Not sure that unpivot is really what you want here. You can do this pretty easily with some aggregation.
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
from AccountContacts ac
left join Contacts c on c.ID = ac.ContactID
group by ac.AccountID
Please realize the reason your struggling here is because your data structure is an anti-pattern known as EAV (entity attribute value). It is a real pain to work with. If at all possible fixing your data structure would be a great plan.

Based on the desired results and the setup provided in your SQL Fiddle, you are looking to PIVOT your data, not UNPIVOT it.
This query does the trick:
with src as (
select accountid
, name
, data
from accountcontacts ac
join contacts c
on c.id = ac.contactid
)
select *
from src
pivot (max(data)
for name in ([email],[phone])) pvt

You can do conditional aggregation:
SELECT ac.AccountID as ID,
max(case when c.name = 'email' then c.data end) email,
max(case when c.name = 'phone' then c.data end) phone
from Contacts c
left join AccountContacts ac on ac.ContactID = c.id
group by ac.AccountID;

select a.data email, b.data phone from
(select * from Contacts a
join AccountContacts b on a.id=b.ContactID where name='email')a
full join (
select * from Contacts a
join AccountContacts b on a.id=b.ContactID where name='phone'
)b on a.AccountID=b.AccountID

Related

SQL IN "Too many values"

I have a problem I want to get all of the DISTINCT CLIENTIDs in my LCMINV table in which the CLIENTID is in LAAPPL's CLIENTID and LACOBORW's COBORWID and also the APPLICATION STATUS is either 'PEN' OR 'REC'.
Here is my table:
++++++++++++++++++++++++++++++++++++++++++++
+ LAAPPL + LACOBORW + LCMINV +
++++++++++++++++++++++++++++++++++++++++++++
+ CLIENTID + APPLNO + CLIENTID +
+ APPLNO + COBORWID + +
+ STATUS + + +
++++++++++++++++++++++++++++++++++++++++++++
Here is my SQL Query I've done so far:
SELECT DISTINCT(CLIENTID)
FROM LCMINV
WHERE CLIENTID
IN (SELECT CLIENTID, COBORWID
FROM LAAPPL
LEFT OUTER JOIN LACOBORW ON LACOBORW.APPLNO = LAAPPL.APPLNO
WHERE STATUS = 'PEN' OR STATUS = 'REC')
Can it be done in one query or do I need separate queries? I tried my query above an I am getting an error "Too many values"
I would probably use a CTE to break the query up into smaller parts, and then use INNER JOINs to combine the tables. Since You query LAAPPL, I put both columns used into a single subquery, and then join on each column in the next CTE.
It would look something like this:
WITH CTE_LAAPPL AS
(
SELECT
CLIENTID
,APPLNO
FROM LAAPPL
WHERE STATUS IN ('PEN','REC')
)
,CLIENTIDs AS
(
SELECT DISTINCT
l.COBORWID AS CLIENTID
FROM CTE_LAAPPL c
INNER JOIN LACOBORW l ON l.APPLNO = c.APPLNO
UNION
SELECT DISTINCT
l.CLIENTID
FROM CTE_LAAPPL c
INNER JOIN LCMINV l ON l.CLIENTID = c.CLIENTID
)
SELECT
c.CLIENTID
,c.NAME
FROM LCCLIENT c
INNER JOIN CLIENTIDs i ON i.CLIENTID = c.CLIENTID
ORDER BY NAME
I think you just want:
SELECT DISTINCT i.CLIENTID
FROM LCMINV i
WHERE i.CLIENTID IN (SELECT a.CLIENTID
FROM LAAPPL a JOIN
LACOBORW c
ON c.APPLNO = a.APPLNO
WHERE a.STATUS IN ('PEN', 'REC')
);
I'm not really sure why you are joining in LACOBORW. You can probably remove that logic.
I would recommend left joins to get the data you need. Unfortunately, your question 3 does not seem to fit your model because your LCMINV table does not seem to have an id that matches to another table and I cannot understand where LCMINV.CLIENTID should be related to in another context. This works in MSSQL - cannot be sure about Oracle.
--this statement will give you all the names that have a status in the where clause
--you will receive NULL if there is no Co-BorrowerID
select
a.name
, b.applno
, b.status
, c.coborwid
from
lcclient a left join
laapl b on a.clientid = b.clientid left join
lacoborw c on b.applno = c.applno
where
b.status in ('PEN', 'REC')
order by
a.name

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.

Row to column in SQL Server with dynamic number of rows

My source data looks something like this
ID Phone Type Phone Number
-------------------------------------
308820 P 8136542273
308820 F 8136541384
308820 P 8139108555
308820 P 8136842229
308820 F 8139108655
211111 P 6874598695
456788 F 5687659867
In the above data, Phone type = P is phone and F is fax.
I need to sort the data and then pick only one F type and one P type phone number and populate the data as below
ID Fax Number Phone Number
-------------------------------------
308820 8136541384 8136542273
211111 6874598695
456788 5687659867
Can someone help me out how to achieve this. There can be n number of Phone Numbers and Fax numbers against one ID but I need to sort it and pick the first.
You can use conditional aggregation:
select id, max(case when phone_type = 'F' then phone_number end) as fax,
max(case when phone_type = 'P' then phone_number end) as phone
from t
group by id;
Guys thanks for the response. The query which I created is below. The help I got above was used in my query below. However the problem is that in the Binary_CheckSum it randomly picks up a Phone number and as such again selects the same record.
What I am trying to find is that for a particular ID and AddressID combination has the iSActive flag or Phone Number or Fax number has changed so that I need to select that record and insert it in PrescriberLocation table.
select TESTID, ADDRESSID, max(PhoneNumber) as PhoneNumber,max(FaxNumber) as FaxNumber,'1' as isactive from (select distinct a.TESTID
, b.AddressId , CASE WHEN c.PhoneType = 'P'THEN C.PhoneNumber END PhoneNumber
, CASE WHEN c.PhoneType = 'F'THEN C.PhoneNumber END FaxNumber
,'1' as isactive from stg_Address a inner join stg_AddressPhone c on a.TESTID = c.TESTID and a.AddressID = c.AddressID INNER join pbmsys_new.dbo.sc_Address b on
upper(a.Address1) = upper(b.Address1) and upper(isnull(a.Address2,'')) = upper(b.Address2) join pbmsys_new.[dbo].[dr_PrescriberLocation] d
on a.TESTID = d.TESTID and b.AddressId = d.AddressId
where BINARY_CHECKSUM(1,c.PhoneNumber, FaxNumber) != BINARY_CHECKSUM(d.IsActive,d.PhoneNumber,d.FaxNumber) and d.PrescriberLocationId = SELECT max(Z.PrescriberLocationId) as PrescriberLocationId FROM pbmsys_new.dbo.dr_PrescriberLocation Z where d.TESTID = z.TESTID and d.AddressId =z.AddressId))f group by TESTID, AddressId
you can use aggregate function and sub-query
select id,max(fax_Number) as fax_Number, max(phone_Number) as phone_Number
from
(select id, case when phone_type = 'F' then phone_number end as fax_Number,
case when phone_type = 'P' then phone_number end as phone_Number
from yourtable
) as t group by id
If the requirement is to pick first value and not the max value then you can use it.
; with CTE1
As
(
select row_number() over (partition by id,phonetype order by ID) as Num,ID,
case when phonetype = 'F' then PhoneNumber end as FaxNumber,
case when phonetype = 'P' then PhoneNumber end as PhoneNumber
from ContactDetails
)
select ID,max(FaxNumber),max(PhoneNumber)
from (select ID,FaxNumber,PhoneNumber from CTE1 where num = 1)as T2
group by ID

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

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)