Row to column in SQL Server with dynamic number of rows - sql

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

Related

How to use PIVOT to separate values into different columns without duplication or NULL values

I currently have the three tables below:
Table #Name
Name_id NameValue
========== =============
50 Hannah
51 Jeremy
52 Luna
Table #AttValue
Name_id AttributeValue_id AttributeValue
======= ================= =============
50 11 01216892584
50 12 26
50 13 Female
Table #Attribute
AttributeValue_id AttributeName
================= =============
11 Phone Number
12 Age
13 Gender
What I want to do is combine them into one and this is what I have done so far:
SELECT NameValue,
case when AttributeName = 'Phone Number' then AttributeValue end PhoneNumber
,case when AttributeName = 'Age' then AttributeValue end Age
,case when AttributeName = 'Gender' then AttributeValue end Gender
FROM #Name A
INNER JOIN #AttValue B ON A.Name_id = B.Name_id
INNER JOIN #Attribute C ON B.AttributeValue_id = C.AttributeValue_id
This works fine however it results in multiple NameValues and NULL values which I am looking to avoid:
NameValue PhoneNumber Age Gender
========= ============ === ======
Hannah 01216892584 NULL NULL
Hannah NULL 26 NULL
Hannah NULL NULL Female
The result I am looking for is:
NameValue PhoneNumber Age Gender
========= =========== === ======
Hannah 01216892584 26 Female
I understand you can use PIVOT for this, however I struggle to figure out how to do it exactly.
Assuming Name_id, AttributeValue_id is a key (e.g. you don't have cases where a person might have two values for PhoneNumber - or those cases exist but you don't care which one you get), the simplest way is to just wrap your CASE expressions with conditional aggregation:
SELECT
N.NameValue,
PhoneNumber = MAX(case when A.AttributeName = 'Phone Number'
THEN AV.AttributeValue end),
Age = MAX(case when A.AttributeName = 'Age'
THEN AV.AttributeValue end),
Gender = MAX(case when A.AttributeName = 'Gender'
THEN AV.AttributeValue end)
FROM #Name AS N
INNER JOIN #AttValue AS AV ON N.Name_id = AV.Name_id
INNER JOIN #Attribute AS A ON AV.AttributeValue_id = A.AttributeValue_id
GROUP BY N.NameValue;
Or a PIVOT:
;WITH src AS
(
SELECT N.NameValue, A.AttributeName, AV.AttributeValue
FROM #Name AS N
INNER JOIN #AttValue AS AV ON N.Name_id = AV.Name_id
INNER JOIN #Attribute AS A ON AV.AttributeValue_id = A.AttributeValue_id
)
SELECT
p.NameValue,
PhoneNumber = p.[Phone Number],
p.Age,
p.Gender
FROM src PIVOT (MAX(AttributeValue)
FOR AttributeName IN ([Phone Number], [Age], [Gender])) AS p;
Example db<>fiddle
A slightly different approach using a UNION ALL and then PIVOT
Example
Select *
From (
Select ID = Name_ID
,Item = 'Name'
,Value = NameValue
From #Name
Union All
Select ID = Name_ID
,Item = A.AttributeName
,Value = V.AttributeValue
From #Attribute A
Join #AttValue V on A.AttributeValue_ID = V.AttributeValue_ID
) src
Pivot ( max(Value) for Item in ([Name],[Phone Number],[Age],[Gender] ) ) Pvt
Results
If it helps with the visualization, the UNION ALL looks like this

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

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

SQL Server fetch columns based on specific row numbers

I have a requirement where I need to select last one year orders based on email (billing address email address). I need to show order number, shipping first name, shipping last name, billing address email id and item descriptions along with submitted date. Same address table use to store shipping (ship_to 1) and billing address (ship_to always 0). The email field in shipping address is optional and if both the addresses match only billing address is written to DB.
Address:
Order_num first_name last_name email ship_to_num
-----------------------------------------------------------
ord1 abc abc abc#c.com 0
ord1 cdf ccc XXc#m.com 1
I wrote a initial query like below:
select
addr.first_name, addr.last_name, addr.order_num, addr.Update_Date_Time,
ord.Order_Amt, item.Item_Desc
from
ADDR addr
inner join
ORDER ord on ord.Order_num = addr.Order_Num
inner join
ITEM item on ord.Order_num = item.Order_Num
where
addr.Order_Num in (select Order_num
from TOEADDR addr
where addr.ShipTo_Num = 0
and addr.EM_EMAIL_ADDR = 'avc#abc.com'
and addr.Update_Date_Time > DATEADD(year,-1, GETDATE()))
order by
addr.Update_Date_Time desc
Is there any way I can add this logic to above query:
(SELECT TOP 1 First_Name, LAST_NAME FROM ADDR ORDER BY ShipTo_Num ASC)
Thanks, with CTE it worked like a charm. Below is the final query:
;with cte_addr
AS
(SELECT First_Name,LAST_NAME, EM_EMAIL_ADDR, order_num,Update_Date_Time,ShipTo_Num
, ROW_NUMBER() OVER (PARTITION BY order_num ORDER BY ShipTo_Num desc) as ROWID
FROM ADDR
)
SELECT addr.first_name , addr.last_name, addr.order_num, addr.Update_Date_Time,
ord.Order_Amt, item.Item_Desc
FROM cte_addr addr
inner join ORDER ord on ord.Order_num = addr.Order_Num
inner join ITEM item on ord.Order_num = item.Order_Num
WHERE addr.ROWID =1 and
addr.Order_Num in(
select Order_num from ADDR billAddress where
billAddress.ShipTo_Num = 0 and billAddress.EM_EMAIL_ADDR = 'afv#abv.com'
and billAddress.Update_Date_Time > DATEADD(year,-1,GETDATE())
)
order by addr.Update_Date_Time desc
You can use CTE to achieve it. You can write query like below. This can be a sample query for the logic you are looking for
;with cte_addr
AS
(SELECT First_Name,LAST_NAME, email EM_EMAIL_ADDR, order_num,Update_Date_Time,ShipTo_Num
, ROW_NUMBER() OVER (PARTITION BY First_Name,LAST_NAME ORDER BY ShipTo_Num) ROWID
FROM ADDR
)
SELECT addr.first_name , addr.last_name, addr.order_num, addr.Update_Date_Time,
ord.Order_Amt, item.Item_Desc
FROM cte_addr addr
inner join ORDER ord on ord.Order_num = addr.Order_Num
inner join ITEM item on ord.Order_num = item.Order_Num
WHERE t.ROWID =1
and addr.Order_Num in(
select Order_num from TOEADDR addr where
addr.ShipTo_Num = 0 and addr.EM_EMAIL_ADDR = 'avc#avc.com'
and addr.Update_Date_Time > DATEADD(year,-1,GETDATE())
)
order by addr.Update_Date_Time desc

SQL results in one row

I have following statement:
SELECT
Person.PersonID, Person.PersonName, Person.Surname,
Address.Street, Address.PostCode,
(SELECT Phones.Num
WHERE (Phones.CommunicationTypeId = '78')) AS email,
(SELECT Phones.Num
WHERE (Phones.CommunicationTypeId = '83')) AS mobile,
(SELECT Phones.Num
WHERE (Phones.CommunicationTypeId = '88')) AS phone
FROM
Address
RIGHT OUTER JOIN
Person ON Address.ObjectId = Person.PersonID
RIGHT OUTER JOIN
Phones ON Person.PersonID = Phones.ObjectID
Now results shows separate rows, while I want to have it in one row:
How to consolidate it into just one row?
Looks like your person has more than one corresponding phone records (I guess 2 records with different types.
SELECT
ObjectID,
MAX(CASE WHEN CommunicationTypeId = '78' THEN Num ELSE null END) as email,
MAX(CASE WHEN CommunicationTypeId = '83' THEN Num ELSE null END) as mobile,
MAX(CASE WHEN CommunicationTypeId = '88' THEN Num ELSE null END) as phone
FROM Phones
GROUP BY ObjectID
The select above returns 3 columns for each person and you can use it in the main query
SELECT
Person.PersonID, Person.PersonName, Person.Surname,
Address.Street, Address.PostCode,
Phones.email, Phones mobile, Phones.phone
FROM
Address
RIGHT OUTER JOIN
Person ON Address.ObjectId = Person.PersonID
RIGHT OUTER JOIN
(sub query above) Phones ON Person.PersonID = Phones.ObjectID

SQL Where in for Many to Many Join Table

I have the following (moderately epic query) which I have been writing
Select *
from
(Select
Salutation,
FirstName, LastName, FullName,
PhotoUrl, CountryCode, Email, Birthday,
Gender, HomePhone, M.MemberId, IDType, JoinDate,
HomeLocation, HomeLocationId,
Region.Name as RegionName,
M.MembershipId,
coalesce(case
when Package.PackageIsReoccuring = 1 then 'Recurring'
when Package.PackageIsSession = 1 then 'Paid In Full'
when membership.TotalPrice = 0 then 'Free'
when Package.PackagePayInFull = 1 then 'Paid In Full'
end, 'N/A') as PackageTerm,
coalesce(PackageType.Name, 'N/A') as PackageType,
coalesce(membershipstate.name, 'N/A') as MembershipState,
MembershipStartDate =
case
when membership.StartDate IS NULL
then ''
else CONVERT(varchar(50),membership.StartDate)
end,
MembershipEndDate =
case
when membership.EndDate IS NULL
then ''
else CONVERT(varchar(50),membership.EndDate)
end,
Region.Id as RegionId
from
(select
AspNetUsers.Salutation,
AspNetUsers.FirstName, AspNetUsers.LastName,
CONCAT (AspNetUsers.FirstName, ' ', AspNetUsers.LastName) as FullName,
AspNetUsers.PhotoUrl, AspNetUsers.CountryCode, AspNetUsers.Email,
AspNetUsers.Birthday, AspNetUsers.Gender,
AspNetUsers.HomePhone as HomePhone,
Member.Id as MemberId, Member.IDType, Member.JoinDate,
HomeLocation.Name as HomeLocation,
HomeLocation.Id as HomeLocationId,
(case when (select top 1 id from membership where membership.memberid = Member.id and (membership.membershipstateid = 1 or membership.membershipstateid = 6)) IS NULL Then (select top 1 id from membership where membership.memberid = Member.id order by membership.enddate desc) ELSE (select top 1 id from membership where membership.memberid = Member.id and (membership.membershipstateid = 1 or membership.membershipstateid = 6)) END) as MembershipId
from
AspNetUsers
join
Member on AspNetUsers.id = Member.aspnetuserid
join
Location as HomeLocation on Member.HomeLocationId = HomeLocation.id) as M
left join
Membership on M.MembershipId = Membership.Id
left join
Package on Membership.packageid = Package.Id
left join
PackageType on Package.packagetypeid = PackageType.Id
left join
MembershipState on Membership.membershipstateid = MembershipState.Id
left join
Region on Membership.RegionId = Region.Id) as Result
order by
Result.LastName desc
I have a final join table which I want to use which is a many-to-many relationship on Region. Region has a Join Table (RegionLocations) which is a join between Region and Locations.
With my query below I would like to get all results where the HomeLocationId = 2 OR he has a LocationId (from RegionLocations) which also contains 2. The RegionId is a nullable value and isn't always populated.
How can I get this? Do I need to return values into a CSV? This final hurdle is a battle..
Thanks
You could extend this:
left join
Region on Membership.RegionId = Region.Id) as Result
to this:
left join
Region on Membership.RegionId = Region.Id
where M.HomeLocationId = 2
or Region.Id in (select RegionId from RegionLocation where LocationId = 2)
) as Result
Some other remarks about your query:
The fields MembershipStartDate and MembershipEndDate can be evaluated more concisely as:
COALESCE(CONVERT(varchar(50),membership.StartDate), '') as MembershipStartDate,
COALESCE(CONVERT(varchar(50),membership.EndDate), '') as MembershipEndDate,
The inner field MembershipId is defined with three sub-queries in a case when, which can be shortened to just one query. It uses in instead of an or condition, and puts it in the order by clause in a way that gets the priority right:
(select top 1 id
from membership
where membership.memberid = Member.id
order by case when membership.membershipstateid in (1,6) then 0 else 1 end,
membership.enddate desc
) as MembershipId
Finally, if you just have an outer query that performs a select * from (...) order by, then why not skip that outer query and perform that order by on the inner query direcly?