SQL Join / Pivot / Unpivot = Madness - sql

So, I've done quite a bit of searching and fiddling and can't quite find a solution. Maybe my situation is unique - or more likely I just don't know what the heck I'm doing. I'm much closer than when I started, so that's uplifting. Anyway - here we go braintrust- any assistance is much appreciated.
I need to join 2 lookup tables that look like this (not enough rep points yet to post images so here's my pretend tables):
Social Network Member [table]
member_id social_network_id connection_id_string
16972 1 www.linkedin.com/somename
16972 2 www.twitter.com/somename
16972 3 www.facebook.com/somename
180301 1 www.linkedin.com/anothername
Social Network [table]
social_network_id name calling_string
1 Linkedin www.linkedin.com
2 Twitter www.twitter.com
3 Facebook www.facebook.com
This is what I want. I've tried a number of things including pivots and unpivots, cross apply - but I cant seem to get this result:
member_id linkedin facebook twitter
16972 www.linkedin.com/somename www.facebook.com/somename www.twitter.com/somename
I'll be able to work with that, I have no use for the social_network_id or calling_string after the join. Here's my query that's not quite doing the job.
SELECT member_id, [facebook],[linkedin],[myspace],[twitter]
FROM (
SELECT member_id,name,social_network_id,calling_string,connection_id_string
FROM social_network_member INNER JOIN
social_network ON social_network_member.social_network_id = social_network.social_network_id
CROSS APPLY (VALUES ('NAME',name),
('CONNECTION STRING', connection_id_string),
('CALLING STRING',calling_string))
Unpivoted(club_id,member_id)) as Sourcetable
Pivot (MAX(connection_id_string) For name in([facebook],[linkedin],[myspace],[twitter])) AS PVT
The best I can tell is the cross apply really isn't doing anything. I kind of guessed on the syntax there..can you tell?
This is what I'm getting (somewhat typical from what I've been seeing in searches):
member_id facebook linkedin myspace twitter
16972 NULL www.linkedin.com/somename NULL NULL
16972 www.facebook.com/somename NULL NULL NULL
...
...
Is what I want even possible? Any pointers on how to get there? Is my query all jacked up?
Thanks in advance Ladies and Gents.
I forgot to mention this earlier, but I'm using SQL server 2012 - SSMS.
RESOLVED I used the answer provided by Bluefeet below and it worked like a charm. Also thanks to Cha for taking the time to help.

Your current query is really close. If your tables above are the correct structure, then it doesn't seem that you need to use CROSS APPLY because you don't have anything that needs to be unpivoted.
If you remove the CROSS APPLY, then you can easily get the result using PIVOT:
SELECT member_id, [facebook],[linkedin],[myspace],[twitter]
FROM
(
SELECT m.member_id,
n.name,
m.connection_id_string
FROM social_network_member m
INNER JOIN social_network n
ON m.social_network_id = n.social_network_id
) d
pivot
(
max(connection_id_string)
for name in ([facebook],[linkedin],[myspace],[twitter])
) piv;
See SQL Fiddle with Demo.
If you have an unknown number of values, then you can use dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(name)
from social_network
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT member_id, ' + #cols + '
from
(
SELECT m.member_id,
n.name,
m.connection_id_string
FROM social_network_member m
INNER JOIN social_network n
ON m.social_network_id = n.social_network_id
) x
pivot
(
max(connection_id_string)
for name in (' + #cols + ')
) p '
execute sp_executesql #query;
See SQL Fiddle with Demo

I think it is a very simple PIVOT case. You do not need to complicate it with CROSS APPLY/JOIN stuff at all. The query as simple as this will do:
SELECT member_id, [facebook],[linkedin],[myspace],[twitter]
FROM (
SELECT member_id,name,connection_id_string
FROM social_network_member snm INNER JOIN
social_network sn ON snm.social_network_id = sn.social_network_id
) as Sourcetable
Pivot (MAX(connection_id_string) For [name] in([facebook],[linkedin],[myspace],[twitter])) AS PVT
A hint: do not include unnecessary columns in your SourceTable for PIVOT
SQL Fiddle

Related

How does Partitioning By a Substring in T-SQL Work?

I found the perfect example while browsing through sites of what I'm looking for. In this code example, all country names that appear in long formatted rows are concatenated together into one result, with a comma and space between each country.
Select CountryName from Application.Countries;
Select SUBSTRING(
(
SELECT ',' + CountryName AS 'data()'
FROM Application.Countries FOR XML PATH('')
), 2 , 9999) As Countries
Source: https://www.mytecbits.com/microsoft/sql-server/concatenate-multiple-rows-into-single-string
My question is: how can you partition these results with a second column that would read as "Continent" in such a way that each country would appear within its respective continent? The theoretical "OVER (PARTITION BY Continent)" in this example would not work without an aggregate function before it. Perhaps there is a better way to accomplish this? Thanks.
Use a continents table (you seem not to have one, so derive one with distinct), and then use the same code in a cross apply using the where as a "join" condition:
select *
from
(
select distinct continent from Application.Countries
) t1
cross apply
(
Select SUBSTRING(
(
SELECT ',' + CountryName AS 'data()'
FROM Application.Countries as c FOR XML PATH('')
where c.continent=t1.continent
), 2 , 9999) As Countries
) t2
Note that it is more usual, and arguably has more finesse, to use stuff(x,1,1,'')instead of substring(x,2,9999) to remove the first comma.

How to retrieve single column separated by comma with multiples values, and apply join in sql

I have the following two tables in sql.I want to get the calendarId from calenderschedule and join with calendar table to get the calendarcode for each productId. Output format is described below.
MS SQL Server 2012 version string_split is not working. Please help to get the desired output.
Table1: calenderschedule
productid, calendarid
100 1,2,3
200 1,2
Table2: calendar
calendarid, calendarCode
1 SIB
2 SIN
3 SIS
Output:
productId, calendarCode
100 SIB,SIN,SIS
200 SIB,SIN
You can normalize the data by converting to XML and then using CROSS APPLY to split it. Once it's normalized, use the STUFF function to combine the calendar codes into a comma-separated list. Try this:
;WITH normalized_data as (
SELECT to_xml.productid
,split.split_calendarid
FROM
(
SELECT *,
cast('<X>'+replace(cs.calendarid,',','</X><X>')+'</X>' as XML) as xmlfilter
FROM calendarschedule cs
) to_xml
CROSS APPLY
(
SELECT new.D.value('.','varchar(50)') as split_calendarid
FROM to_xml.xmlfilter.nodes('X') as new(D)
) split
) select distinct
n.productid
,STUFF(
(SELECT distinct ', ' + c.calendarCode
FROM calendar c
JOIN normalized_data n2 on n2.split_calendarid = c.calendarid
WHERE n2.productid = n.productid
FOR XML PATH ('')), 1, 1, '') calendarCode
from normalized_data n
I feel like this solution is a bit overly complex, but it's the only way I got it to work. If anybody knows how to simplify it, I'd love to hear some feedback.

SQL Server - If the field had been pivot, how to pivot again by another field? Is that the DB design correct?

I have a raw data like
Title Question Answer AnswerRemark
----------------------------------------
ACCCode1 Q1 Y NULL
ACCCode1 Q2 N 6
ACCCode1 Q3 Y Workout
As you can see the field "AnswerRemark" is free text for "Answer", some answer is not require remark.
I can simply pivot the question and answer like:
Title Q1 Q2 Q3
AccessCode1 Y N Y
My desired Result will be
Title Q1 R1 Q2 R2 Q3 R3
AccessCode1 Y NULL N 6 Y Workout
Is that possible? I can not figure it out how to achieve this, pivot the Answer is not good idea as it have many combinations.
Any suggestion?
Use Conditional Aggregation :
SELECT Title,
MAX(CASE WHEN Question='Q1' THEN Answer END) as Q1 ,
MAX(CASE WHEN Question='Q1' THEN AnswerRemark END) as R1 ,
MAX(CASE WHEN Question='Q2' THEN Answer END) as Q2 ,
MAX(CASE WHEN Question='Q2' THEN AnswerRemark END) as R2 ,
MAX(CASE WHEN Question='Q3' THEN Answer END) as Q3 ,
MAX(CASE WHEN Question='Q3' THEN AnswerRemark END) as R3
FROM [tablename]
GROUP BY Title
Using Pivot we get the result
;With cte(Title, Question,Answer,AnswerRemark)
AS
(
SELECT 'ACCCode1','Q1','Y',NULL UNION ALL
SELECT 'ACCCode1','Q2','N','6' UNION ALL
SELECT 'ACCCode1','Q3','Y','Workout' UNION ALL
SELECT 'ACCCode1','Q2','N','7' UNION ALL
SELECT 'ACCCode1','Q1','Y',NULL UNION ALL
SELECT 'ACCCode1','Q3','N','9' UNION ALL
SELECT 'ACCCode1','Q1','N','4' UNION ALL
SELECT 'ACCCode1','Q2','N','Workout' UNION ALL
SELECT 'ACCCode1','Q4','N','2' UNION ALL
SELECT 'ACCCode1','Q3','Y','Workout' UNION ALL
SELECT 'ACCCode1','Q1','N','1' UNION ALL
SELECT 'ACCCode1','Q4','Y',NULL
)
SELECT *,'Remark'+CAST(ROW_NUMBER()OVER(ORDER BY (SELECT 1))AS varchar(10)) AS Question2
, ROW_NUMBER()OVER(PArtition by Question Order by Question ) AS Seq
INTO #t FROM cte
Using Dynamic Sql where the columns are not static
DECLARE #DyColumn1 Nvarchar(max),
#DyColumn2 Nvarchar(max),
#Sql Nvarchar(max),
#MAxDyColumn1 Nvarchar(max),
#MAxDyColumn2 Nvarchar(max),
#CombineColumn Nvarchar(max)
SELECT #DyColumn1=STUFF((SELECT DISTINCT ', '+QUOTENAME(Question) FROM #t FOR XML PATH ('')),1,1,'')
SELECT #DyColumn2=STUFF((SELECT ', '+QUOTENAME(Question2) FROM #t FOR XML PATH ('')),1,1,'')
SELECT #MAxDyColumn1=STUFF((SELECT DISTINCT ', '+'MAX('+QUOTENAME(Question)+') AS '+QUOTENAME(Question) FROM #t FOR XML PATH ('')),1,1,'')
SELECT #MAxDyColumn2=STUFF((SELECT ', '+'MAX('+QUOTENAME(Question2)+') AS '+QUOTENAME(Question2) FROM #t FOR XML PATH ('')),1,1,'')
SELECT #CombineColumn=STUFF((SELECT DISTINCT ', '+QUOTENAME(Question)+','+QUOTENAME(Question2) FROM #t FOR XML PATH ('')),1,1,'')
SET #Sql='SELECT Title,'+#CombineColumn+' From
(
SELECT Title,'+#MAxDyColumn1+','+#MAxDyColumn2+' FRom
(
SELECT * FROM #t
)AS SRC
PIVOT
(
MAX(Answer) FOR Question IN('+#DyColumn1+')
) AS Pvt1
PIVOT
(
MAX(AnswerRemark) FOR Question2 IN('+#DyColumn2+')
) AS Pvt2
GROUP BY Title
)dt
'
PRINT #Sql
EXEC(#Sql)
Result
Title Q1 Remark1 Q1 Remark2 Q1 Remark3 Q1 Remark4 Q2 Remark5 Q2 Remark6 Q2 Remark7 Q3 Remark8 Q3 Remark9 Q3 Remark10 Q4 Remark11 Q4 Remark12
ACCCode1 Y NULL Y 1 Y 4 Y NULL N 6 N Workout N 7 Y Workout Y Workout Y 9 Y NULL Y 2
I don't know how big your data is, or how many questions are possible. A more generic Q&A structure done at the presentation layer would be far better, but for your specific request a more correct design would be a 3NF table. This will allow you to create a primary key that is highly optimised and create a secondary index by question type id. All your keys are now IDs which are far faster to search and match than strings:
Account Codes
AccID - AccName - columns for other data related to accounts
Stores each account you have.
Questions
QuestionID - QuestionName
List of possible questions, one row for every question you have, Q1, Q2 etc. You could add question categories here to exploit any commonality you have, e.g. if you have different surveys with the same set of questions, you could put them in one category and easily then query the below.
Results
AccId, QuestionID, Result, Result Remark
Contains one row for every question asked.
Query for your result still uses pivot, but now you can select the list of columns to use from a variable or dynamic SQL syntax, which means you can control it somewhat better and hte query itself should be better.
With that said, if you have any knowledge about your data whatsoever you can use it to make a static query which can then be indexed. Examples are here of this query: SQL Server 2005 Pivot on Unknown Number of Columns. You can then set the column names if required using the AS syntax, which unfortunately would require dynamic sql again (Change column name while using PIVOT SQL Server 2008).
By the way, what you are trying to do is specifically dealing with denormalised data, which is what nosql is good for, SQL Server gives you great help but you have to have some structure to your data.
If you aren't working for survey monkey and dealing with millions of variations, I'd seriously look at whether you can just make a table specific to each round of questions you get, and then simply denormalise it and add an explicit columns for each question and then make your entire logic just a select * from surveyxyztable where accountid = abc.

Multi row to a row sql

I have a table as bellow:
I want query to print output as bellow:
Note: Please, do not downvote. I know the rules of posting answers, but for such of questions there's no chance to post short answer. I posted it only to provide help for those who want to find out how to achieve that, but does not expect ready-to-use solution.
I'd suggest to read these articles:
PIVOT on two or more fields in SQL Server
Pivoting on multiple columns - SQL Server
Pivot two or more columns in SQL Server 2005
At first UNPIVOT then PIVOT. If number of rows for each Pod_ID is not always equal 3 then you need to use dynamic SQL. The basic sample:
SELECT *
FROM (
SELECT Pod_ID,
Purs + CASE WHEN RN-1 = 0 THEN '' ELSE CAST(RN-1 as nvarchar(10)) END as Purs,
[Values]
FROM (
SELECT Pod_ID,
Pur_Qty, --All columns that will be UNPIVOTed must be same datatype
Pur_Price,
CAST(ETD_Date as int) ETD_Date, -- that is why I cast date to int
ROW_NUMBER() OVER (ORDER BY (SELECT 1)) as RN
FROM YourTable
) as p1
UNPIVOT (
[Values] FOR [Purs] IN(Pur_Qty, Pur_Price, ETD_Date)
) as unpvt
) as p2
PIVOT (
MAX([Values]) FOR Purs IN (Pur_Qty,Pur_Price,ETD_Date,Pur_Qty1,Pur_Price1,ETD_Date1,Pur_Qty2,Pur_Price2,ETD_Date2)
) as pvt
Will bring you:
Pod_ID Pur_Qty Pur_Price ETD_Date Pur_Qty1 Pur_Price1 ETD_Date1 Pur_Qty2 Pur_Price2 ETD_Date2
F8E2F614-75BC-4E46-B7F8-18C7FC4E5397 24 22 20160820 400 33 20160905 50 44 20160830

How can I join these two tables into the same Sql result

I'm trying to generate a SQL query that can join two tables together and return the result .. but the second table is 'flattened'. I'm not sure if that's the correct technical term. Is it denormalized?
Anyways, can someone suggest how I could do this?
Table: Users
UserId Name
1 Pure.Krome
2 John
3 Jill
4 Jane
Table: UserAliases
UserAliasId UserId Alias
1 1 Idiot
2 1 PewPew
3 3 BlahBlahBlah
Desired results
UserId Name Aliases
1 Pure.Krome Idiot PewPew
2 John
3 Jill BlahBlahBlah
4 Jane
Please note:
A user does NOT need to have an alias. So that's a zero->many relationship (outer join)
The delimiter for the flattening of the 2nd table is a SPACE. If an alias has a space, bad luck for me. (Consider it, bad data).
Another example of my problem is to think of a StackOverflow question + tags.
http://groupconcat.codeplex.com/ has a clone of MySQL's GROUP_CONCAT() implemented as a CLR aggregation function. I guess the SQL is not the problem, but I might as well:
SELECT
[Users].[UserId] AS UserId,
[Users].[Name] AS Name,
GROUP_CONCAT_D([UserAliases].[Alias]," ") AS Aliases
FROM [Users]
OUTER JOIN [UserAliases] ON [Users].[UserId]=[UserAliases].[UserId]
or similar.
This is not tested but give it a try. I have no server here.
SELECT a.UserID,
a.[Name],
coalesce(NewTable.NameValues, '') Aliases
FROM Users a LEFT JOIN
(
SELECT UserID,
STUFF((
SELECT ' ' + [Name]
FROM UserAliases
WHERE ( UserID = Results.UserID )
FOR XML PATH('')), 1, 2, '') AS NameValues
FROM UserAliases Results
GROUP BY UserID
) NewTable
on a.UserID = NewTable.UserID
Here's SQL Fiddle Output
FOR XML PATH is handy in this situation:
SELECT UserID, Name
, LTRIM(RTRIM((SELECT ' ' + Alias
FROM UserAliases WHERE UserID = u.UserID
FOR XML PATH('')))) AS Aliases
FROM Users u