SQL Server Flatten Data - sql

I have 3 tables:
tUsers
-uid
tColors
-colorid
-colorname
tColors_User_Detail
-uid_fk
-colorid_fk
Users select which colors they like, and only the colors they like. This creates records in tColors_User_Detail. I need to flatten this out, so that each user has one record with the color from tColors as a column name, and they have a True/False value in the row for each color depending on if they had a record in tColors_User_Detail. If the user did not have a color selected in tColors_User_Detail, it would be a False value in the specific color column. And, if they do have a record in tColors_User_Detail for a color, it would be a true value for the corresponding color column.
Any help appreciated.

Here's a basic PIVOT example with a COALESCE to show 'false' if no value is available. This assumes you have to hard-code the names of the colors for the column names.
DECLARE #tUsers TABLE ([uid] INT)
DECLARE #tColors TABLE ([colorid] INT, [colorname] VARCHAR(50))
DECLARE #tColors_User_Detail TABLE ([uid_fk] INT, [colorid_fk] INT)
INSERT #tUsers VALUES (1),(2)
INSERT #tColors VALUES (1,'Blue'),(2,'Red'),(3,'Green')
INSERT #tColors_User_Detail VALUES (1,1),(1,2),(1,3),(2,1)
SELECT
uid,
COALESCE([Red], 'False') AS [Red],
COALESCE([Blue], 'False') AS [Blue],
COALESCE([Green], 'False') AS [Green]
FROM #tUsers U
LEFT OUTER JOIN #tColors_User_Detail CUD
ON CUD.uid_fk = U.uid
LEFT OUTER JOIN #tColors C
ON C.colorid = CUD.colorid_fk
PIVOT (MAX(colorname) FOR colorname IN (
[Red],
[Blue],
[Green]
)) PVT
If you want to let the columns be dynamic from the colors, you'll have to use dynamic sql.
DECLARE #Sql VARCHAR(1000) =
'SELECT uid'
+ (SELECT ', CASE WHEN [' + [colorname] + '] IS NOT NULL THEN ''True'' ELSE ''False'' END AS [' + [colorname] + ']' AS [text()] FROM tColors FOR XML PATH(''))
+ ' FROM tUsers U
LEFT OUTER JOIN tColors_User_Detail CUD
ON CUD.uid_fk = U.uid
LEFT OUTER JOIN tColors C
ON C.colorid = CUD.colorid_fk
PIVOT (MAX(colorname) FOR colorname IN ('
+ SUBSTRING((SELECT ',[' + [colorname] + ']' AS [text()] FROM tColors FOR XML PATH('')), 2, 1000)
+ ')) PVT'
EXEC (#Sql)

What flavor of SQL?
Something along the lines of:
http://sqlfiddle.com/#!6/ec4e2
SELECT U.uid
, C.colorid
, C.colorname
, ( CASE WHEN cud.uid_fk IS NOT NULL THEN 'True' ELSE 'False' END ) AS ColorChosen
FROM tUsers U
FULL OUTER JOIN tColors C ON 1=1
LEFT OUTER JOIN tColors_User_Detail cud ON
U.uid = cud.uid_fk
AND C.colorid = cud.colorID_FK
EDIT: I missed the pivot for one row per user. Meeting time though. Be back in a bit.

Related

Create a pivot of same columns in to 1 row

I'm using a SQL Server, I've a query which return the data of all the fields, The main thing is that 1 field can belongs to multiple records, the record ID differentiate them.
I've a data set like this.
This is my current data set
My current query:
Select fd.FieldName ,FV.FieldID, Data , R.RecordID from FieldValues FV
Inner Join Records R on R.RecordID = FV.RecordID
Inner Join Forms F On f.FormID = R.FormID
Inner join Fields fd on fd.FieldID = fv.FieldID
Where R.RecordID IN (45,46)
I need to create 1 row of each columns that belongs to the same RecordID like this.
Service Name Location city VendorCode RecordID
Raj ABC LOCATION ABC CITY 32 45
BEN ABC LOCATION ABC CITY -- 46
The above is my desired output.
I've tried with pivot but have not succeeded.
If you don't like to deal with dynamic pivot and you do know the key of the rows you want to convert into columns, you can use standard sql with max and case when
select
max(case fd.FieldName when 'SelectService' then Data else null end) as ServiceName,
max(case fd.FieldName when 'EnterYourLocation' then Data else null end) as Location,
max(case fd.FieldName when 'City' then Data else null end) as city,
max(case fd.FieldName when 'VendorCodeOption' then Data else null end) as VendorCode,
R.RecordId
from FieldValues FV
Inner Join Records R on R.RecordID = FV.RecordID
Inner Join Forms F On f.FormID = R.FormID
Inner join Fields fd on fd.FieldID = fv.FieldID
where R.RecordID IN (45,46)
group by R.RecordId
This is the solution with pivot but it is missing to include adjust joins
declare #columns varchar(max) set #columns = ''
select #columns = coalesce(#columns + '[' + cast(col as varchar(MAX)) + '],', '')
FROM ( select FieldName as col from FieldValues group by FieldName ) m
set #columns = left(#columns,LEN(#columns)-1)
DECLARE #SQLString nvarchar(max);
set #SQLString = '
select * from
( select RecordId, FieldName, Data from FieldValues) m
PIVOT
( MAX(Data)
FOR FieldName in (' + #columns + ')
) AS PVT'
EXECUTE sp_executesql #SQLString

SQL Pivot Convert Null to 0 [duplicate]

I tried to convert the (null) values with 0 (zeros) output in PIVOT function but have no success.
Below is the table and the syntax I've tried:
SELECT
CLASS,
[AZ],
[CA],
[TX]
FROM #TEMP
PIVOT (SUM(DATA)
FOR STATE IN ([AZ], [CA], [TX])) AS PVT
ORDER BY CLASS
CLASS AZ CA TX
RICE 10 4 (null)
COIN 30 3 2
VEGIE (null) (null) 9
I tried to use the ISNULL but did not work.
PIVOT SUM(ISNULL(DATA,0)) AS QTY
What syntax do I need to use?
SELECT CLASS,
isnull([AZ],0),
isnull([CA],0),
isnull([TX],0)
FROM #TEMP
PIVOT (SUM(DATA)
FOR STATE IN ([AZ], [CA], [TX])) AS PVT
ORDER BY CLASS
If you have a situation where you are using dynamic columns in your pivot statement you could use the following:
DECLARE #cols NVARCHAR(MAX)
DECLARE #colsWithNoNulls NVARCHAR(MAX)
DECLARE #query NVARCHAR(MAX)
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(Name)
FROM Hospital
WHERE Active = 1 AND StateId IS NOT NULL
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SET #colsWithNoNulls = STUFF(
(
SELECT distinct ',ISNULL(' + QUOTENAME(Name) + ', ''No'') ' + QUOTENAME(Name)
FROM Hospital
WHERE Active = 1 AND StateId IS NOT NULL
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
EXEC ('
SELECT Clinician, ' + #colsWithNoNulls + '
FROM
(
SELECT DISTINCT p.FullName AS Clinician, h.Name, CASE WHEN phl.personhospitalloginid IS NOT NULL THEN ''Yes'' ELSE ''No'' END AS HasLogin
FROM Person p
INNER JOIN personlicense pl ON pl.personid = p.personid
INNER JOIN LicenseType lt on lt.licensetypeid = pl.licensetypeid
INNER JOIN licensetypegroup ltg ON ltg.licensetypegroupid = lt.licensetypegroupid
INNER JOIN Hospital h ON h.StateId = pl.StateId
LEFT JOIN PersonHospitalLogin phl ON phl.personid = p.personid AND phl.HospitalId = h.hospitalid
WHERE ltg.Name = ''RN'' AND
pl.licenseactivestatusid = 2 AND
h.Active = 1 AND
h.StateId IS NOT NULL
) AS Results
PIVOT
(
MAX(HasLogin)
FOR Name IN (' + #cols + ')
) p
')
You cannot place the IsNull() until after the data is selected so you will place the IsNull() around the final value in the SELECT:
SELECT CLASS,
IsNull([AZ], 0) as [AZ],
IsNull([CA], 0) as [CA],
IsNull([TX], 0) as [TX]
FROM #TEMP
PIVOT
(
SUM(DATA)
FOR STATE IN ([AZ], [CA], [TX])
) AS PVT
ORDER BY CLASS
Sometimes it's better to think like a parser, like T-SQL parser. While executing the statement, parser does not have any value in Pivot section and you can't have any check expression in that section. By the way, you can simply use this:
SELECT CLASS
, IsNull([AZ], 0)
, IsNull([CA], 0)
, IsNull([TX], 0)
FROM #TEMP
PIVOT (
SUM(DATA)
FOR STATE IN (
[AZ]
, [CA]
, [TX]
)
) AS PVT
ORDER BY CLASS
You have to account for all values in the pivot set. you can accomplish this using a cartesian product.
select pivoted.*
from (
select cartesian.key1, cartesian.key2, isnull(relationship.[value],'nullvalue') as [value]
from (
select k1.key1, k2.key2
from ( select distinct key1 from relationship) k1
,( select distinct key2 from relationship) k2
) cartesian
left outer join relationship on relationship.key1 = cartesian.key1 and relationship.key2 = carterisan.key2
) data
pivot (
max(data.value) for ([key2_v1], [key2_v2], [key2_v3], ...)
) pivoted
To modify the results under pivot, you can put the columns in the selected fields and then modify them accordingly. May be you can use DECODE for the columns you have built using pivot function.
Kranti A
I have encountered a similar problem. The root cause is that (use your scenario for my case), in the #temp table, there is no record for:
a. CLASS=RICE and STATE=TX
b. CLASS=VEGIE and (STATE=AZ or STATE=CA)
So, when MSSQL does pivot for no record, MSSQL always shows NULL for MAX, SUM, ... (aggregate functions).
None of above solutions (IsNull([AZ], 0)) works for me, but I do get ideas from these solutions.
Sorry, it really depends on the #TEMP table. I can only provide some suggestions.
Make sure #TEMP table have records for below condition, even Data is null.
a. CLASS=RICE and STATE=TX
b. CLASS=VEGIE and (STATE=AZ or STATE=CA)
You may need to use cartesian product: select A.*, B.* from A, B
In the select query for #temp, if you need to join any table with WHERE, then would better put where inside another sub select query. (Goal is 1.)
Use isnull(DATA, 0) in #TEMP table.
Before pivot, make sure you have achieved Goal 1.
I can't give an answer to the original question, since there is no enough info for #temp table. I have pasted my code as example here.
SELECT * FROM (
SELECT eeee.id as enterprise_id
, eeee.name AS enterprise_name
, eeee.indicator_name
, CONVERT(varchar(12) , isnull(eid.[date],'2019-12-01') , 23) AS data_date
, isnull(eid.value,0) AS indicator_value
FROM (select ei.id as indicator_id, ei.name as indicator_name, e.* FROM tbl_enterprise_indicator ei, tbl_enterprise e) eeee
LEFT JOIN (select * from tbl_enterprise_indicator_data WHERE [date]='2020-01-01') eid
ON eeee.id = eid.enterprise_id and eeee.indicator_id = enterprise_indicator_id
) AS P
PIVOT
(
SUM(P.indicator_value) FOR P.indicator_name IN(TX,CA)
) AS T

Pivot table giving wrong result

Below code gives me result set as shown in first image
SELECT dbo.tbStudent.Name, dbo.tbStudent.RegNo, dbo.tbFee.PID, dbo.tbFee.Purpose, dbo.tbFee.AmountPaid, dbo.tbFee.StudentID, dbo.tbFee.Date, dbo.tbFee.FeeID,
dbo.tbFee.SemID, dbo.tbFee.CourseID, dbo.tbFee.ModeOfPayment, dbo.tbFee.CheckNo, dbo.tbFee.DDNo, dbo.tbFee.HostelDDNo, dbo.tbFee.FRID,
dbo.tbStudent.Parentage, dbo.tbCourse.Name AS Course, ISNULL(dbo.tbSemester.SemName, ' + #st +') AS Semester
FROM dbo.tbFee INNER JOIN
dbo.tbStudent ON dbo.tbFee.StudentID = dbo.tbStudent.StudentID INNER JOIN
dbo.tbCourse ON dbo.tbFee.CourseID = dbo.tbCourse.CourseID LEFT OUTER JOIN
dbo.tbSemester ON dbo.tbFee.SemID = dbo.tbSemester.SemID Where tbFee.SemID=1
However using Pivot table I need result as below:
My code for pivot table is :
SET #values = '';
If(#SemID=0)
BEGIN
SELECT #values = #values +'['+ CAST(PurPose AS varchar(max))+ ']' + ','
FROM tbFee Where CourseID=#CourseID
SET #values = SUBSTRING(#values, 1, Len(#values) - 1)
END
ELSE
BEGIN
SELECT #values = #values +'['+ CAST(PurPose AS varchar(max))+ ']' + ','
FROM tbFee Where SemID=#SemID
SET #values = SUBSTRING(#values, 1, Len(#values) - 1)
END
Declare #st nvarchar(max)
set #st='''Not Available''';
declare #q nvarchar(max)
set #q = '
Select * from(
SELECT dbo.tbStudent.Name, dbo.tbStudent.RegNo, dbo.tbFee.PID, dbo.tbFee.Purpose, dbo.tbFee.AmountPaid, dbo.tbFee.StudentID, dbo.tbFee.Date, dbo.tbFee.FeeID,
dbo.tbFee.SemID, dbo.tbFee.CourseID, dbo.tbFee.ModeOfPayment, dbo.tbFee.CheckNo, dbo.tbFee.DDNo, dbo.tbFee.HostelDDNo, dbo.tbFee.FRID,
dbo.tbStudent.Parentage, dbo.tbCourse.Name AS Course, ISNULL(dbo.tbSemester.SemName, ' + #st +') AS Semester
FROM dbo.tbFee INNER JOIN
dbo.tbStudent ON dbo.tbFee.StudentID = dbo.tbStudent.StudentID INNER JOIN
dbo.tbCourse ON dbo.tbFee.CourseID = dbo.tbCourse.CourseID LEFT OUTER JOIN
dbo.tbSemester ON dbo.tbFee.SemID = dbo.tbSemester.SemID Where tbFee.SemID=1
) as x
pivot (
max(AmountPaid)
for Purpose in (' + #values + ')
) as pvt
'
exec (#q)
I am getting Values of Purpose columns in #values due to the reason that number of rows can change. However instead of getting result as single row for same student having same regNo , I am getting below result :
But what I am getting is below:
In the source query for your PIVOT, you should only specify those columns which are involved in the actual pivot - namely dbo.tbStudent.Name, dbo.tbStudent.RegNo, dbo.tbFee.Purpose, dbo.tbFee.AmountPaid.
SELECT
dbo.tbStudent.Name,
dbo.tbStudent.RegNo,
dbo.tbFee.Purpose,
dbo.tbFee.AmountPaid
FROM
dbo.tbFee
INNER JOIN dbo.tbStudent ON dbo.tbFee.StudentID = dbo.tbStudent.StudentID
INNER JOIN dbo.tbCourse ON dbo.tbFee.CourseID = dbo.tbCourse.CourseID
LEFT OUTER JOIN dbo.tbSemester ON dbo.tbFee.SemID = dbo.tbSemester.SemID
Where tbFee.SemID=1
If any other columns apart from these are present, they will be factored into the pivot computation, and you will get multiple rows accordingly.

Operand type clash: varchar is incompatible with User-Defined Table Type

I created a User-Defined Table Type:
CREATE TYPE dbo.ListTableType AS TABLE(
ITEM varchar(500) NULL
)
I leverage it in a function:
CREATE FUNCTION dbo.fn_list_to_string
(
#LIST dbo.ListTableType READONLY
)
RETURNS varchar(max)
AS
BEGIN
DECLARE #RESULT varchar(max)
SET #RESULT = ''
DECLARE #NL AS CHAR(2) = CHAR(13) + CHAR(10)
SELECT #RESULT = #RESULT + ITEM + #NL FROM #LIST
SET #RESULT = SUBSTRING(#RESULT, 1, LEN(#RESULT) - 1)
RETURN #RESULT
END
Finally, I try to use this function in a simple select:
SELECT
P.PROGRAM_ID,
PROGRAM_NAME,
PROGRAM_DESC,
P.STATUS_ID,
STATUS_DESC,
P.CONTACT_SID,
I.FIRST_NAME + ' ' + I.LAST_NAME as CONTACT_NAME,
P.CLARITY_ID,
dbo.fn_list_to_string(
( SELECT CONVERT(varchar,CLARITY_ID) as ITEM
FROM dbo.MUSEUM_PROGRAM_PROJECTS as A
JOIN dbo.MUSEUM_PROJECTS as B on B.PROJECT_ID = A.PROJECT_ID
WHERE PROGRAM_ID = P.PROGRAM_ID )
) as PROJECT_CLARITY_IDS
FROM dbo.MUSEUM_PROGRAMS as P
LEFT JOIN dbo.MUSEUM_PROGRAM_STATUS_TYPES as S on S.STATUS_ID = P.STATUS_ID
LEFT JOIN dbo.v_IDVAULT_ENRICHED_CURRENT_EMPLOYEES as I on I.[SID] = P.CONTACT_SID
But I get this error:
Operand type clash: varchar is incompatible with ListTableType
Any idea why? Also if there's another [more elegant] way to achieve what I'm trying to do I'm open to suggestions as well! Thanks in advance!
Here is a simple demonstration of the FOR XML PATH technique which does all of this with a very simple subquery and no table types or extremely inefficient multi-statement table-valued functions etc.
USE tempdb;
GO
CREATE TABLE dbo.P(Program_ID INT);
CREATE TABLE dbo.M(Clarity_ID INT, Program_ID INT);
INSERT dbo.P VALUES(1),(2),(3),(4);
INSERT dbo.M VALUES(1,1),(1,2),(2,3),(3,2),(1,4),(4,1);
SELECT
P.PROGRAM_ID,
PROJECT_CLARITY_IDS = STUFF((
SELECT CHAR(13)+CHAR(10)+CONVERT(VARCHAR(12),Clarity_ID)
FROM dbo.M WHERE Program_ID = p.Program_ID
FOR XML PATH(''), TYPE).value('.[1]','nvarchar(max)'),1,2,'')
FROM dbo.P AS p;
SQLfiddle demo
The output doesn't look right in SQLfiddle or in results to grid in Management Studio, because they strip out carriage returns/line feeds for display purposes, but you can replace CHAR(13)+CHAR(10) with two commas or semi-colons or something to verify that there are two characters there.
Using STUFF..FOR XML PATH construct for concatanation in combination with CTE will get the results you'd like. Something like this:
WITH CTE_PROJECT_CLARITIES AS
(
SELECT DISTINCT PROGRAM_ID
, STUFF((
SELECT CHAR(13) + CHAR(10) + CONVERT(varchar(11),CLARITY_ID)
FROM dbo.MUSEUM_PROGRAM_PROJECTS as A
JOIN dbo.MUSEUM_PROJECTS as B on B.PROJECT_ID = A.PROJECT_ID
WHERE A.PROGRAM_ID = X.PROGRAM_ID
FOR XML PATH ('')),1,2,'') AS PROJECT_CLARITY_IDS
FROM MUSEUM_PROGRAM_PROJECTS X
)
SELECT
P.PROGRAM_ID,
PROGRAM_NAME,
PROGRAM_DESC,
P.STATUS_ID,
STATUS_DESC,
P.CONTACT_SID,
I.FIRST_NAME + ' ' + I.LAST_NAME as CONTACT_NAME,
P.CLARITY_ID,
X.PROJECT_CLARITY_IDS
FROM dbo.MUSEUM_PROGRAMS as P
LEFT JOIN dbo.MUSEUM_PROGRAM_STATUS_TYPES as S on S.STATUS_ID = P.STATUS_ID
LEFT JOIN dbo.v_IDVAULT_ENRICHED_CURRENT_EMPLOYEES as I on I.[SID] = P.CONTACT_SID
LEFT JOIN CTE_PROJECT_CLARITIES X ON X.PROGRAM_ID = p.PROGRAM_ID
SQLFiddle DEMO (not sure if I got the columns right, but you'll get the idea)

How to select records as columns in SQL

I have two tables: tblSizes and tblColors. tblColors has columns called ColorName, ColorPrice and SizeID. There is one size to multiple colors. I need to write a query to select the size and all the colors (as columns) for a that size with the price of each size in its respective column.
The colors must be returned as columns, for instance:
SizeID : Width : Height : Red : Green : Blue
1---------220-----220----£15----£20-----£29
Hope this makes sense
Thank you
Edit: Tried the following code but not quite sure what's wrong:
DECLARE #Colors NVARCHAR(4000), #Query NVARCHAR(MAX)
SET #Colors = ''
SELECT #Colors = #Colors + '[' + C.Color +'],'
FROM tblTempProductSizesColors SC
INNER JOIN tblColors C on SC.ColorID=C.ID
GROUP BY Color
ORDER BY Color
SET #Colors = LEFT(#Colors,LEN(#Colors)-1)
SET #Query = '
SELECT *
FROM ( SELECT TS.Sizeid, TS.Width, TS.Height, TS.Depth, TC.Price
FROM tblTempProductSizes TS
INNER JOIN tblTempProductSizesColors TC
ON TS.SizeId = TC.SizeId INNER JOIN tblColors C on TC.ColorID=C.ID) A
PIVOT(SUM(Price) FOR C.Color IN ('+#Colors+')) AS PT'
EXEC sp_executesql #Query
select s.Sizeid, s.Width, s.Height,
Red = SUM(CASE WHEN c.ColorName = "Red" THEN c.ColorPrice ELSE 0 END),
Blue = SUM(CASE WHEN c.ColorName = "Blue" THEN c.ColorPrice ELSE 0 END),
Green = SUM(CASE WHEN c.ColorName = "Green" THEN c.ColorPrice ELSE 0 END)
from tblSizes s
join tblColors c on c.SizeId = s.SizeId
group by s.Sizeid, s.Width, s.Height
If you don't want to write every color, and the expression for those columns, you can use Dynamic SQL if you have MSSQL 2005+ (first, be sure of take a look to this link).
UPDATED following comment
DECLARE #Colors NVARCHAR(4000), #Query NVARCHAR(MAX)
SET #Colors = ''
SELECT #Colors = #Colors + '[' + C.Color +'],'
FROM tblTempProductSizesColors SC
INNER JOIN tblColors C on SC.ColorID=C.ID
GROUP BY Color
ORDER BY Color
SET #Colors = LEFT(#Colors,LEN(#Colors)-1)
SET #Query = '
SELECT *
FROM ( SELECT TS.Sizeid, TS.Width, TS.Height, TS.Depth, TC.Price, C.Color
FROM tblTempProductSizes TS
INNER JOIN tblTempProductSizesColors TC
ON TS.SizeId = TC.SizeId INNER JOIN tblColors C on TC.ColorID=C.ID) A
PIVOT(SUM(Price) FOR Color IN ('+#Colors+')) AS PT'
EXEC sp_executesql #Query