Parent/Child hierarchy tree view - sql-server

I have a parents table looks like this
CHILD_ID | PARENT_ID | NAME
1 | Null | Bill
2 | 1 | Jane
3 | 1 | Steve
4 | 2 | Ben
5 | 3 | Andrew
Id like to get a result set like this
Bill
---Jane
------Ben
---Steve
------Andrew
I know I need to do a rank query to rank the levels and a self join but all I can find on the net is CTE recursion
I have done this in Oracle before but not in MS SQL

Bit hacky and can be improved but hopefully it shows the principle...
;with relation (childId, parentId, childName, [level], [orderSequence])
as
(
select childId, parentId, childName, 0, cast(childId as varchar(20))
from #parents
where parentId is null
union all
select p.childId, p.parentId, r.[level]+1, cast(r.orderSequence + '_' + cast(p.childId as varchar) as varchar(20))
from #parents p
inner join relation r on p.parentId = r.childId
)
select right('----------', ([level]*3)) +childName
from relation
order by orderSequence
If however you want to avoid recursion then an alternative approach is to implement a tree table with the relevant tree structure information - see http://www.sqlteam.com/article/more-trees-hierarchies-in-sql for a walk through

declare #pc table(CHILD_ID int, PARENT_ID int, [NAME] varchar(80));
 
insert into #pc
select 1,NULL,'Bill' union all
select 2,1,'Jane' union all
select 3,1,'Steve' union all
select 4,2,'Ben' union all
select 5,3,'Andrew' union all
select 6,NULL,'Tom' union all
select 7,8,'Dick' union all
select 8,6,'Harry' union all
select 9,3,'Stu' union all
select 10,7,'Joe';
 
 
; with r as (
      select CHILD_ID, PARENT_ID, [NAME], depth=0, sort=cast(CHILD_ID as varchar(max))
      from #pc
      where PARENT_ID is null
      union all
      select pc.CHILD_ID, pc.PARENT_ID, pc.[NAME], depth=r.depth+1, sort=r.sort+cast(pc.CHILD_ID as varchar(30))
      from r
      inner join #pc pc on r.CHILD_ID=pc.PARENT_ID
where r.depth<32767
)
select tree=replicate('-',r.depth*3)+r.[NAME]
from r
order by sort
option(maxrecursion 32767);
This was a tough one:). I expanded the example to include > 1 tree. Results looking good so far.

WITH DirectReports (ParentUniqId, UniqId, SortID)
AS
(
SELECT e.ParentUniqId, e.UniqId, e.UniqId as SortID
FROM Coding.Coding AS e
WHERE isnull(ParentUniqId ,0)=0
UNION ALL
SELECT e.ParentUniqId, e.UniqId,, d.SortID * 100 + e.UniqId as SortID
FROM Coding.Coding AS e
INNER JOIN DirectReports AS d
ON e.ParentUniqId = d.UniqId
)
SELECT ParentUniqId, Perfix,SortID
FROM DirectReports order by rtrim(SortID) , uniqid

Related

SQL Server pivot with string concatenation

I have data like below
| Name | Subject | Answer |
|:----------|:----------|:---------------------:|
|Pranesh |Physics |Numerical Problems |
|Pranesh |Physics |Other |
|Pranesh |Chemistry |Understanding Concepts |
|Pranesh |Chemistry |Organic chemistry reactions
|Pranesh |Maths |Lack of understanding |
|Pranesh |Maths |Insufficient practice |
|Pranesh |Maths |Other |
Using this SQL query:
select *
from
(select
l.FullName Name, sq.Title Subject, cAns.Name Answer
from
Answer as sa
left join
Question AS sq on sq.ID = sa.QuestionID
left join
Master as cAns on cAns.ID = sa.AnswerID
left join
Profile as l on l.ID = sa.ProfileID) src
pivot
(max(Answer)
for Subject in ([Physics], [Chemistry], [Maths], [Biology],[ComputerScience], [CommonUnderstanding])) piv;
I am able to get the data as follows
How to concatenate answer column of same subject and display the same like in above screen shot ?
I have concatenated the answer list first per name and subject, then applied pivoting -
declare #temp table (name varchar(100), subject varchar(100), answer varchar(100))
insert into #temp
select 'Pranesh','Physics' ,'Numerical Problems'
union all select 'Pranesh','Physics' ,'Other'
union all select 'Pranesh','Chemistry','Understanding Concepts'
union all select 'Pranesh','Chemistry','Organic chemistry reactions'
union all select 'Pranesh','Maths' ,'Lack of understanding'
union all select 'Pranesh','Maths' ,'Insufficient practice'
union all select 'Pranesh','Maths' ,'Other'
union all select 'Ramesh','Biology' , 'Other'
union all select 'Ramesh','Biology' , 'Science'
;with cte as (select distinct name, subject from #temp)
select * from
(
select
c.name,
c.subject,
answer = stuff((select ',' + answer from #temp t where t.name=c.name and t.subject=c.subject for xml path('')), 1, 1, '')
from cte c
) src
pivot
(
max(answer) for subject in ([Physics], [Chemistry], [Maths], [Biology],[ComputerScience],[CommonUnderstanding])
) piv
Try this:-
SELECT ANS, PHYSICS,CHEMISTRY,MATHS
FROM
(
SELECT L.FULLNAME NAME, SQ.TITLE SUBJECT, CANS.NAME ANSWER FROM ANSWER AS SA
LEFT JOIN QUESTION AS SQ ON SQ.ID = SA.QUESTIONID
LEFT JOIN MASTER AS CANS ON CANS.ID = SA.ANSWERID
LEFT JOIN PROFILE AS L ON L.ID = SA.PROFILEID
) AS T
UNPIVOT
(
COMBINE_COLUMN_VALUES FOR ANS IN (ANSWER)
) AS U
PIVOT
(
MAX(COMBINE_COLUMN_VALUES) FOR [SUBJECT] IN (PHYSICS,CHEMISTRY,MATHS)
) AS P

Trouble deleting duplicate records using partition and rank() in sql server

I am trying to identify duplicate records and then delete one of the duplicate record using PARTITION and RANK() n SQL Server 2008. I condition to delete duplicate record is that it should not be referenced in another table.
I have a Language table that has some duplicate languages. Employee table has employees and mapping to language. I have to delete one of the duplicate records if that Language id is not being mapped in Employee table.
CREATE TABLE MY_LANGUAGE (LANGUAGEID INT, LANGUAGENAME VARCHAR(20))
CREATE TABLE MY_EMPLOYEE (EMPID INT, NAME VARCHAR(20), LANGUAGEID INT)
INSERT INTO MY_LANGUAGE VALUES(1, 'ENGLISH')
INSERT INTO MY_LANGUAGE VALUES(2, 'FRENCH')
INSERT INTO MY_LANGUAGE VALUES(3, 'ITALIAN')
INSERT INTO MY_LANGUAGE VALUES(4, 'GERMAN')
INSERT INTO MY_LANGUAGE VALUES(5, 'ITALIAN')
INSERT INTO MY_LANGUAGE VALUES(6, 'GERMAN')
INSERT INTO MY_LANGUAGE VALUES(7, 'SPANISH')
INSERT INTO MY_EMPLOYEE VALUES (10, 'GLEN', 1)
INSERT INTO MY_EMPLOYEE VALUES (20, 'PETER', 2)
INSERT INTO MY_EMPLOYEE VALUES (30, 'MARIA', 3)
If you see, I have two languages that are duplicate and one of them is being used by an employee. I want to delete language ids 4 and 5.
LANGUAGENAME LANGUAGEID EMPNAME
GERMAN 4
GERMAN 6
ITALIAN 3 MARIA
ITALIAN 5
I have tried to create a select statement to return what I want to delete:
WITH CTE AS (
SELECT L.LANGUAGENAME, L.LANGUAGEID, RANK() OVER(PARTITION BY L.LANGUAGENAME ORDER BY L.LANGUAGEID) AS RANKING
FROM MY_LANGUAGE L
INNER JOIN (
SELECT LANGUAGENAME, COUNT(*) AS DUPECOUNT
FROM MY_LANGUAGE
GROUP BY LANGUAGENAME
HAVING COUNT(*) > 1
) LC ON L.LANGUAGENAME = LC.LANGUAGENAME
WHERE NOT EXISTS (SELECT 1 FROM MY_EMPLOYEE WHERE MY_EMPLOYEE.LANGUAGEID = L.LANGUAGEID))
SELECT * FROM CTE WHERE RANKING = 1
This return the following
LANGUAGENAME LANGUAGEID RANKING
GERMAN 4 1
ITALIAN 5 1
When I try to delete I get an error:
WITH CTE AS (
SELECT L.LANGUAGENAME, L.LANGUAGEID, RANK() OVER(PARTITION BY L.LANGUAGENAME ORDER BY L.LANGUAGEID) AS RANKING
FROM MY_LANGUAGE L
INNER JOIN (
SELECT LANGUAGENAME, COUNT(*) AS DUPECOUNT
FROM MY_LANGUAGE
GROUP BY LANGUAGENAME
HAVING COUNT(*) > 1
) LC ON L.LANGUAGENAME = LC.LANGUAGENAME
WHERE NOT EXISTS (SELECT 1 FROM MY_EMPLOYEE WHERE MY_EMPLOYEE.LANGUAGEID = L.LANGUAGEID))
DELETE FROM CTE WHERE RANKING = 1
Error that I get is:
Msg 4405, Level 16, State 1, Line 1
View or function 'CTE' is not updatable because the modification affects multiple base tables.
Any ideas how to fix this or may be it can be simplified. Thanks to #Szymon for showing a temp table solution but I am hoping to get a solution without temp tables (if possible).
Query:
DELETE ll
FROM MY_LANGUAGE ll
JOIN (SELECT L.LANGUAGENAME,
L.LANGUAGEID,
ROW_NUMBER()OVER(PARTITION BY L.LANGUAGENAME, e.EMPID
ORDER BY L.LANGUAGEID ASC) rnk,
COUNT(*)OVER(PARTITION BY L.LANGUAGENAME) cnt,
e.EMPID
FROM MY_LANGUAGE l
LEFT JOIN MY_EMPLOYEE e ON e.LANGUAGEID = l.LANGUAGEID) a
ON ll.LANGUAGEID = a.LANGUAGEID
AND a.rnk = 1
AND a.cnt > 1
and a.EMPID IS NULL
Result:
| LANGUAGEID | LANGUAGENAME |
|------------|--------------|
| 1 | ENGLISH |
| 2 | FRENCH |
| 3 | ITALIAN |
| 6 | GERMAN |
| 7 | SPANISH |
You can get the records from your CTE query into a temporary table and then delete based on that table.
WITH CTE AS (
SELECT L.LANGUAGENAME, L.LANGUAGEID, RANK() OVER(PARTITION BY L.LANGUAGENAME ORDER BY L.LANGUAGEID) AS RANKING
FROM MY_LANGUAGE L
INNER JOIN (
SELECT LANGUAGENAME, COUNT(*) AS DUPECOUNT
FROM MY_LANGUAGE
GROUP BY LANGUAGENAME
HAVING COUNT(*) > 1
) LC ON L.LANGUAGENAME = LC.LANGUAGENAME
WHERE NOT EXISTS (SELECT 1 FROM MY_EMPLOYEE WHERE MY_EMPLOYEE.LANGUAGEID = L.LANGUAGEID))
SELECT * INTO #temp FROM CTE WHERE RANKING = 1
And then use records from #temp to delete from the original table.
Try this..
;WITH CTE AS (
SELECT t.LANGUAGEID, LANGUAGENAME, RANK() OVER(PARTITION BY T.LANGUAGENAME ORDER BY T.LANGUAGEID) AS RANKING
FROM MY_LANGUAGE T
WHERE T.LANGUAGEID IN (
SELECT L.LANGUAGEID
FROM MY_LANGUAGE L LEFT JOIN MY_EMPLOYEE E
ON L.LANGUAGEID = E.LANGUAGEID
WHERE E.LANGUAGEID IS NULL)
)
DELETE FROM CTE
WHERE RANKING = 1 AND (CTE.LANGUAGENAME IN (SELECT LANGUAGENAME
FROM MY_LANGUAGE
GROUP BY LANGUAGENAME
HAVING COUNT(LANGUAGENAME) > 1))

CTE to return all items in hierarchy

I have a table with a recursive hierarchy (i.e. ID, ParentID). For any item in this hierachy, I want to be able to bring back a list of everything UP AND DOWN the hierarchy along with the level for each row. Assume that a parent can only ever have a single child.
Take for example the following:
ID ParentID
--------------
1 NULL
2 1
3 2
4 NULL
5 4
6 5
Given ID 1, 2, or 3, I want to return:
ID ParentID Level
-----------------------
1 NULL 1
2 1 2
3 2 3
I've done this before, but I can't remember how. I know the solution involves a CTE, I just can't get it right! Any help is appreciated.
;with cte as
(
select *, 1 as level from #t where id = #yourid
union all
select t.*, level - 1
from cte
inner join #t t on cte.parent = t.id
),
cte2 as
(
select * from cte
union all
select t.*, level+1
from cte2
inner join #t t on cte2.id = t.parent
)
select id,parent, ROW_NUMBER() over (order by level) level
from ( select distinct id, parent, level from cte2) v
The most barebones version of the CTE query I could come up with is:
WITH Ancestry (AncestorID, DescendantID)
AS
(
SELECT
ParentID, ID
FROM
dbo.Location
WHERE
ParentID IS NOT NULL
UNION ALL
SELECT
P.AncestorID, C.ID
FROM
dbo.Location C
JOIN
Ancestry P on C.ParentID = P.DescendantID
)
SELECT * FROM Ancestry
The result is a list of all Ancestor/Descendant relationships that exist in the table.
The final "SELECT * FROM Ancestry" can be replaced with something more complex to filter, order, etc.
To include reflexive relationships, the query can be modified by adding two lines to the final SELECT statement:
SELECT * FROM Ancestry
UNION
SELECT ID, ID FROM dbo.Location
;WITH Recursive_CTE AS (
SELECT
child.ExecutiveId,
CAST(child.ExecutiveName as varchar(100)) BusinessUnit,
CAST(NULL as bigint) ParentUnitID,
CAST(NULL as varchar(100)) ParentUnit,
CAST('' as varchar(100)) LVL,
CAST(child.ExecutiveId as varchar(100)) Hierarchy,
1 AS RecursionLevel
FROM Sales_Executive_level child
WHERE ExecutiveId = 4000 --your Id which you want to get all parent node
UNION ALL
SELECT
child.ExecutiveId,
CAST(LVL + child.ExecutiveName as varchar(100)) AS BusinessUnit,
child.ParentExecutiveID,
parent.BusinessUnit ParentUnit,
CAST('' + LVL as varchar(100)) AS LVL,
CAST(Hierarchy + ':' + CAST(child.ExecutiveId as varchar(100)) as varchar(100)) Hierarchy,
RecursionLevel + 1 AS RecursionLevel
FROM Recursive_CTE parent
INNER JOIN Sales_Executive_level child ON child.ParentExecutiveID = parent.ExecutiveId
)
SELECT * FROM Recursive_CTE ORDER BY Hierarchy
OPTION (MAXRECURSION 300);

SQL Server CTE -Find top parentID forEach childID?

I have a table which contains hierarchy data - something like:
childID | parentID
____________________
1 | 5
5 | 9
9 | 20
2 | 4
3 | 7
7 | 8
8 | 8
20 | 20
4 | 4
8 | 8
desired output:
I've created a recursive CTE which finds me the top fatherID.
Something like:
;WITH cte AS (
SELECT a.childID
,a.parentID
,1 AS lvl
FROM [Agent_Agents] a
WHERE a.childID = 214 //<==== value to begin with !! - thats part the problem
UNION ALL
SELECT tmp.childID
,tmp.parentID
,cte.lvl+1
FROM [Agent_Agents] tmp
INNER JOIN cte ON tmp.childID = cte.parentID
WHERE cte.childID<>cte.parentID
)
SELECT *
FROM cte
WHERE lvl = (
SELECT MAX(lvl)
FROM cte
)
The problem:
I executed the CTE with explicit childID value to begin with (214) !
So it gives me the value for 214 only.
the CTE do the recursive part and find topParent for childID.
but
I want ForEach row in the Table - to execute the CTE with the childID value !
I have tried to do it with CROSS APPLY:
Something like:
select * from myTable Cross Apply (
;WITH cte AS (....)
)
but IMHO (from my testing !!) - its impossible.
The other idea of putting the recursive CTE in a UDF has a performance penalty (udf's problem as we know).
How can I create this query so that it'll actually work? ( or some near solution )?
here is what I've tried
https://data.stackexchange.com/stackoverflow/query/edit/69458
Can't you do something like this?
;WITH cte AS (....)
SELECT
*
FROM
cte
CROSS APPLY
dbo.myTable tbl ON cte.XXX = tbl.XXX
Put the CROSS APPLY after the CTE definition - into the one SQL statement that refers back to the CTE. Wouldn't that work??
OR: - flip around your logic - do a "top-down" CTE, that picks the top-level nodes first, and then iterates through the hiearchy. This way, you can easily determine the "top-level father" in the first part of the recursive CTE - something like this:
;WITH ChildParent AS
(
SELECT
ID,
ParentID = ISNULL(ParentID, -1),
SomeName,
PLevel = 1, -- defines level, 1 = TOP, 2 = immediate child nodes etc.
TopLevelFather = ID -- define "top-level" parent node
FROM dbo.[Agent_Agents]
WHERE ParentID IS NULL
UNION ALL
SELECT
a.ID,
ParentID = ISNULL(a.ParentID, -1),
a.SomeName,
PLevel = cp.PLevel + 1,
cp.TopLevelFather -- keep selecting the same value for all child nodes
FROM dbo.[Agent_Agents] a
INNER JOIN ChildParent cp ON r.ParentID = cp.ID
)
SELECT
ID,
ParentID,
SomeName,
PLevel,
TopLevelFather
FROM ChildParent
This would give you nodes something like this (based on your sample data, slightly extended):
ID ParentID SomeName PLevel TopLevelFather
20 -1 Top#20 1 20
4 -1 TOP#4 1 4
8 -1 TOP#8 1 8
7 8 ChildID = 7 2 8
3 7 ChildID = 3 3 8
2 4 ChildID = 2 2 4
9 20 ChildID = 9 2 20
5 9 ChildID = 5 3 20
1 5 ChildID = 1 4 20
Now if you select a particular child node from this CTE output, you'll always get all the infos you need - including the "level" of the child, and its top-level parent node.
Not sure I understand what you are looking for but it could be this.
;WITH c
AS (SELECT childid,
parentid,
parentid AS topParentID
FROM #myTable
WHERE childid = parentid
UNION ALL
SELECT T.childid,
T.parentid,
c.topparentid
FROM #myTable AS T
INNER JOIN c
ON T.parentid = c.childid
WHERE T.childid <> T.parentid)
SELECT childid,
topparentid
FROM c
ORDER BY childid
SE-Data
It is the same as answer by marc_s with the difference that I use your table variable and the fact that you have childID = parentID for root nodes where the answer by marc_s has parent_ID = null for root nodes. In my opinion it is better to have parent_ID = null for root nodes.
I have not yet the time to look further into your question and am not sure whether or not i've understood your problem, but couldn't you use this svf to get the top father's id?
CREATE FUNCTION [dbo].[getTopParent] (
#ChildID INT
)
RETURNS int
AS
BEGIN
DECLARE #result int;
DECLARE #ParentID int;
SET #ParentID=(
SELECT ParentID FROM ChildParent
WHERE ChildID = #ChildID
)
IF(#ParentID IS NULL)
SET #result = #ChildID
ELSE
SET #result = [dbo].[getTopParent](#ParentID)
RETURN #result
END
Then you should be able to find each top parent in this way:
SELECT ChildID
, [dbo].[getTopParent](ChildID) AS TopParentID
FROM ChildParent
select distinct
a.ChildID,a.ParentID,
--isnull(nullif(c.parentID,b.parentID),a.parentID) as toppa,
B.parentID
--,c.parentID
,isnull(nullif(d.parentID,a.parentID),c.parentID) as toppa1,a.name
from myTable a
inner join myTable c
on a.parentID=c.parentID
inner join myTable b
on b.childID=a.parentID
inner join myTable d
on d.childID=b.parentID
I have using the without CTE expression and then using joins to get the step to step parent for child and then more important Common table expressions were introduced in SQL Server 2005 not in server 2000 so using joins to get values this is basic way for to get parentid for a child value
select dbo.[fn_getIMCatPath](8)
select Cat_id,Cat_name,dbo.[fn_getIMCatPath](cat_id) from im_category_master
Create FUNCTION [dbo].[fn_getIMCatPath] (#ID INT)
returns NVARCHAR(1000)
AS
BEGIN
DECLARE #Return NVARCHAR(1000),
#parentID INT,
#iCount INT
SET #iCount = 0
SELECT #Return = Cat_name,
#parentID = parent_id
FROM im_category_master
WHERE [cat_id] = #ID
WHILE #parentID IS NOT NULL
BEGIN
SELECT #Return = cat_name + '>' + #Return,
#parentID = parent_id
FROM im_category_master
WHERE [cat_id] = #parentID
SET #iCount = #iCount + 1
IF #parentID = -1
BEGIN
SET #parentID = NULL
END
IF #iCount > 10
BEGIN
SET #parentID = NULL
SET #Return = ''
END
END
RETURN #Return
END
Consider this sample data and respective SQL to access child records along with their top parent.
Sample DATA
SQL code:
;WITH c AS (
SELECT Id, Name, ParentId as CategoryId,
Id as MainCategoryId, Name AS MainCategory
FROM pmsItemCategory
WHERE ParentId is null
UNION ALL
SELECT T.Id, T.Name, T.ParentId, MainCategoryId, MainCategory
FROM pmsItemCategory AS T
INNER JOIN c ON T.ParentId = c.Id
WHERE T.ParentId is not null
)
SELECT Id, Name, CategoryId, MainCategoryId, MainCategory
FROM c
order by Id
select distinct
a.ChildID,a.ParentID,
--isnull(nullif(c.parentID,b.parentID),a.parentID) as toppa,
B.parentID
--,c.parentID
,isnull(nullif(d.parentID,a.parentID),c.parentID) as toppa1,a.name
from myTable a
inner join myTable c
on a.parentID=c.parentID
inner join myTable b
on b.childID=a.parentID
inner join myTable d
on d.childID=b.parentID
With cte as
(
Select ChileId,Name,ParentId from tblHerarchy
where ParentId is null
union ALL
Select h.ChileId,h.Name,h.ParentId from cte
inner join tblHerarchy h on h.ParentId=cte.ChileId
)
Select * from cte
With cteherarchy as
(
Select ChileId,Name,ParentId from tblHerarchy
where ParentId is null
union ALL
Select h.ChileId,h.Name,h.ParentId from cte
inner join tblHerarchy h on h.ParentId=cte.ChileId
)
Select * from cteherarchy

SQL Server - Include NULL using UNPIVOT

UNPIVOT will not return NULLs, but I need them in a comparison query. I am trying to avoid using ISNULL the following example (Because in the real sql there are over 100 fields):
Select ID, theValue, column_name
From
(select ID,
ISNULL(CAST([TheColumnToCompare] AS VarChar(1000)), '') as TheColumnToCompare
from MyView
where The_Date = '04/30/2009'
) MA
UNPIVOT
(theValue FOR column_name IN
([TheColumnToCompare])
) AS unpvt
Any alternatives?
To preserve NULLs, use CROSS JOIN ... CASE:
select a.ID, b.column_name
, column_value =
case b.column_name
when 'col1' then a.col1
when 'col2' then a.col2
when 'col3' then a.col3
when 'col4' then a.col4
end
from (
select ID, col1, col2, col3, col4
from table1
) a
cross join (
select 'col1' union all
select 'col2' union all
select 'col3' union all
select 'col4'
) b (column_name)
Instead of:
select ID, column_name, column_value
From (
select ID, col1, col2, col3, col4
from table1
) a
unpivot (
column_value FOR column_name IN (
col1, col2, col3, col4)
) b
A text editor with column mode makes such queries easier to write. UltraEdit has it, so does Emacs. In Emacs it's called rectangular edit.
You might need to script it for 100 columns.
It's a real pain. You have to switch them out before the UNPIVOT, because there is no row produced for ISNULL() to operate on - code generation is your friend here.
I have the problem on PIVOT as well. Missing rows turn into NULL, which you have to wrap in ISNULL() all the way across the row if missing values are the same as 0.0 for example.
I ran into the same problem. Using CROSS APPLY (SQL Server 2005 and later) instead of Unpivot solved the problem. I found the solution based on this article An Alternative (Better?) Method to UNPIVOT
and I made the following example to demonstrate that CROSS APPLY will NOT Ignore NULLs like Unpivot.
create table #Orders (OrderDate datetime, product nvarchar(100), ItemsCount float, GrossAmount float, employee nvarchar(100))
insert into #Orders
select getutcdate(),'Windows',10,10.32,'Me'
union
select getutcdate(),'Office',31,21.23,'you'
union
select getutcdate(),'Office',31,55.45,'me'
union
select getutcdate(),'Windows',10,null,'You'
SELECT OrderDate, product,employee,Measure,MeasureType
from #Orders orders
CROSS APPLY (
VALUES ('ItemsCount',ItemsCount),('GrossAmount',GrossAmount)
)
x(MeasureType, Measure)
SELECT OrderDate, product,employee,Measure,MeasureType
from #Orders orders
UNPIVOT
(Measure FOR MeasureType IN
(ItemsCount,GrossAmount)
)AS unpvt;
drop table #Orders
or, in SQLServer 2008 in shorter way:
...
cross join
(values('col1'), ('col2'), ('col3'), ('col4')) column_names(column_name)
Using dynamic SQL and COALESCE, I solved the problem like this:
DECLARE #SQL NVARCHAR(MAX)
DECLARE #cols NVARCHAR(MAX)
DECLARE #dataCols NVARCHAR(MAX)
SELECT
#dataCols = COALESCE(#dataCols + ', ' + 'ISNULL(' + Name + ',0) ' + Name , 'ISNULL(' + Name + ',0) ' + Name )
FROM Metric WITH (NOLOCK)
ORDER BY ID
SELECT
#cols = COALESCE(#cols + ', ' + Name , Name )
FROM Metric WITH (NOLOCK)
ORDER BY ID
SET #SQL = 'SELECT ArchiveID, MetricDate, BoxID, GroupID, ID MetricID, MetricName, Value
FROM
(SELECT ArchiveID, [Date] MetricDate, BoxID, GroupID, ' + #dataCols + '
FROM MetricData WITH (NOLOCK)
INNER JOIN Archive WITH (NOLOCK)
ON ArchiveID = ID
WHERE BoxID = ' + CONVERT(VARCHAR(40), #BoxID) + '
AND GroupID = ' + CONVERT(VARCHAR(40), #GroupID) + ') p
UNPIVOT
(Value FOR MetricName IN
(' + #cols + ')
)AS unpvt
INNER JOIN Metric WITH (NOLOCK)
ON MetricName = Name
ORDER BY MetricID, MetricDate'
EXECUTE( #SQL )
I've found left outer joining the UNPIVOT result to the full list of fields, conveniently pulled from INFORMATION_SCHEMA, to be a practical answer to this problem in some contexts.
-- test data
CREATE TABLE _t1(name varchar(20),object_id varchar(20),principal_id varchar(20),schema_id varchar(20),parent_object_id varchar(20),type varchar(20),type_desc varchar(20),create_date varchar(20),modify_date varchar(20),is_ms_shipped varchar(20),is_published varchar(20),is_schema_published varchar(20))
INSERT INTO _t1 SELECT 'blah1', 3, NULL, 4, 0, 'blah2', 'blah3', '20100402 16:59:23.267', NULL, 1, 0, 0
-- example
select c.COLUMN_NAME, Value
from INFORMATION_SCHEMA.COLUMNS c
left join (
select * from _t1
) q1
unpivot (Value for COLUMN_NAME in (name,object_id,principal_id,schema_id,parent_object_id,type,type_desc,create_date,modify_date,is_ms_shipped,is_published,is_schema_published)
) t on t.COLUMN_NAME = c.COLUMN_NAME
where c.TABLE_NAME = '_t1'
</pre>
output looks like:
+----------------------+-----------------------+
| COLUMN_NAME | Value |
+----------------------+-----------------------+
| name | blah1 |
| object_id | 3 |
| principal_id | NULL | <======
| schema_id | 4 |
| parent_object_id | 0 |
| type | blah2 |
| type_desc | blah3 |
| create_date | 20100402 16:59:23.26 |
| modify_date | NULL | <======
| is_ms_shipped | 1 |
| is_published | 0 |
| is_schema_published | 0 |
+----------------------+-----------------------+
Writing in May'22 with testing it on AWS Redshift.
You can use a with clause where you can coalesce the columns where nulls are expected. Alternatively, you can use coalesce in the select statement prior to the UNPIVOT block.
And don't forget to alias with the original column name (Not following won't break or violate the rule but would save some time for coffee).
Select ID, theValue, column_name
From
(select ID,
coalesce(CAST([TheColumnToCompare] AS VarChar(1000)), '') as TheColumnToCompare
from MyView
where The_Date = '04/30/2009'
) MA
UNPIVOT
(theValue FOR column_name IN
([TheColumnToCompare])
) AS unpvt
OR
WITH TEMP1 as (
select ID,
coalesce(CAST([TheColumnToCompare] AS VarChar(1000)), '') as TheColumnToCompare
from MyView
where The_Date = '04/30/2009'
)
Select ID, theValue, column_name
From
(select ID, TheColumnToCompare
from MyView
where The_Date = '04/30/2009'
) MA
UNPIVOT
(theValue FOR column_name IN
([TheColumnToCompare])
) AS unpvt
I had your same problem and this is
my quick and dirty solution :
your query :
select
Month,Name,value
from TableName
unpivot
(
Value for Name in (Col_1,Col_2,Col_3,Col_4,Col_5
)
) u
replace with :
select Month,Name,value from
( select
isnull(Month,'no-data') as Month,
isnull(Name,'no-data') as Name,
isnull(value,'no-data') as value from TableName
) as T1
unpivot
(
Value
for Name in (Col_1,Col_2,Col_3,Col_4,Col_5)
) u
ok the null value is replaced with a string, but all rows will be returned !!
ISNULL is half the answer. Use NULLIF to translate back to NULL. E.g.
DECLARE #temp TABLE(
Foo varchar(50),
Bar varchar(50) NULL
);
INSERT INTO #temp( Foo,Bar )VALUES( 'licious',NULL );
SELECT * FROM #temp;
SELECT
Col,
NULLIF( Val,'0Null' ) AS Val
FROM(
SELECT
Foo,
ISNULL( Bar,'0Null' ) AS Bar
FROM
#temp
) AS t
UNPIVOT(
Val FOR Col IN(
Foo,
Bar
)
) up;
Here I use "0Null" as my intermediate value. You can use anything you like. However, you risk collision with user input if you choose something real-world like "Null". Garbage works fine "!##34())0" but may be more confusing to future coders. I am sure you get the picture.

Resources