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.
Related
I have some data in SQL Server:
test1^test2^test3^test4
test5
test6^test7
null
Desired output:
test4
test5
test7
null
You can use right() with charindex() & reverse():
select *, right(col, charindex('^', reverse(col) + '^')-1)
from ( values ('test1^test2^test3^test4'), ('test5'), ('test6^test7'), (null)
) t(col);
Demo
One method would be to split the value, using DelimitedSplit8k_LEAD and then take the "last" one (by ordering high to low). This method uses TOP 1 WITH TIES I used this method as order seems to be irrelevant here:
WITH VTE AS (
SELECT Col
FROM (VALUES('test1^test2^test3^test4'),
('test5'),
('test6^test7'),
('test9^test10'),
(null)) V(col))
SELECT TOP 1 WITH TIES
V.Col,
DS.item
FROM VTE V
CROSS APPLY dbo.delimitedsplit8k_LEAD(V.Col,'^') DS
ORDER BY ROW_NUMBER() OVER (PARTITION BY V.Col ORDER BY DS.ItemNumber DESC);
This avoids what can be an expensive REVERSE.
using charindex and reverse:
I left all the functions in there so you could see the process.
declare #t table (test varchar(100))
insert into #t
values
('test1^test2^test3^test4')
,('test5')
,('test6^test7')
,(null )
select test
,revTest
,charInd
,reverse(left(revTest,case when charind = 0 then len(revTest) else charInd-1 end))
from #t t
cross apply (select reverse(rtrim(ltrim(test)))) a(revTest)
cross apply (select CHARINDEX('^',revTest)) b(charInd)
Results:
**test revTest charInd (Result)**
test1^test2^test3^test4 4tset^3tset^2tset^1tset 6 test4
test5 5tset 0 test5
test6^test7 7tset^6tset 6 test7
NULL NULL NULL NULL
Try This
declare
#col varchar(500), #reveCol varchar(500);
set #col='test133^test2^test3^test4';
set #reveCol=reverse(#col)
Select charindex('^', #revecol )
select right( #col ,charindex('^', #revecol )-1)
Assuming you're on SQL Server 2016+
Also assuming this schema and data:
DROP TABLE IF EXISTS #Data;
CREATE TABLE #Data(StringValue NVARCHAR(MAX));
INSERT INTO #Data(StringValue)VALUES
('test1^test2^test3^test4')
,('test5')
,('test6^test7')
,(NULL)
;
The query:
;WITH cte AS (
SELECT '["' + REPLACE(COALESCE(d.StringValue,''),'^','","') + '"]' AS [JSONValue]
,ROW_NUMBER()OVER(ORDER BY d.StringValue) AS [ID]
FROM #Data d
)
SELECT NULLIF(a.value,'') AS [Value]
FROM (
SELECT j.value,ROW_NUMBER()OVER(PARTITION BY c.ID ORDER BY j.[key] DESC) AS [rn]
FROM cte AS c
CROSS APPLY OPENJSON(c.JSONValue) j
) a
WHERE a.rn = 1
;
If you're on SQL Server 2017+
SELECT MAX(CASE WHEN d.StringValue LIKE '%'+s.value THEN s.[value] ELSE NULL END) AS [Result]
FROM ( VALUES
('test1^test2^test3^test4')
,('test5')
,('test6^test7')
,(NULL)
) d(StringValue)
CROSS APPLY STRING_SPLIT(COALESCE(d.StringValue,''),'^') s
GROUP BY d.StringValue
;
Hi I have a SQL Server table that one column has comma separated values:
12323,234322,1112,99323.....
And I have a parameter #values nvarchar(500) that will also have comma separated values.
In my query I need to check if anything from the parameter exists in my table field.
Something like this>
...
WHERE
(#values = '' OR select s from dbo.Split(',',t.Data) in ( select s from dbo.Split(',',#values )))
Of course the above gives me errors.
Any clue?
Join both tables that you got out of the split
SELECT *
...
FROM (SELECT s FROM dbo.Split(',',t.Data)) X
INNER JOIN (SELECT s FROM dbo.Split(',',#values)) Y
ON X.s = Y.s
...
EXISTS is your friend here.
WHERE
(#values = '' OR EXISTS (select a.value from string_split(t.Data, ',') a inner join ( select value from string_split(#values, ',')) b ON a.value = b.value))
Try this below code it may helps you
IF OBJECT_ID('Tempdb..#Temp') IS NOT NULL
Drop table #Temp
Declare #SearchVariable varchar(1000)='12323,234322,1112,99323,22222,4545,656565,8989,1111,22222'--Varibale Contains these values to search
CREATE TABLE #Temp (CommaValue Varchar(100))-- This is the table having comma separted value columns
INSERT INTO #Temp
SELECT '12323,234322,1112,99323' Union all
SELECT '12323,656565,1112,4545'
Declare #VariableSearch TABLE (ValueName varchar(1000))
Insert into #VariableSearch
SELECT #SearchVariable
;With cte
AS
(
SELECT Split.a.value('.', 'VARCHAR(1000)') AS TablesData
FROM (
SELECT CAST('<S>' + REPLACE(CommaValue, ',', '</S><S>') + '</S>' AS XML) AS TablesData
FROM #Temp
) AS A
CROSS APPLY TablesData.nodes('/S') AS Split(a)
)
SELECT DISTINCT ROW_NUMBER()Over(Order by (SELECT 1)) AS Rno, * from cte C Inner join
(
SELECT Split.a.value('.', 'VARCHAR(1000)') AS VariableSeachData
FROM (
SELECT CAST('<S>' + REPLACE(ValueName, ',', '</S><S>') + '</S>' AS XML) AS VariableSeachData
FROM #VariableSearch
) AS A
CROSS APPLY VariableSeachData.nodes('/S') AS Split(a)
)DT
On C.TablesData=DT.VariableSeachData
OutPut
Rno TablesData VariableSeachData
---------------------------------
1 1112 1112
2 1112 1112
3 12323 12323
4 12323 12323
5 234322 234322
6 4545 4545
7 656565 656565
8 99323 99323
Not quite sure, but maybe this can give you an idea.
using Outer Apply and EXISTS operator.
SELECT x.value
FROM Table T
OUTER APPLY ( SELECT value
FROM dbo.Split(t.data)
) X
WHERE EXISTS ( SELECT 1
FROM dbo.Split(#values) S
WHERE s.value = x.value )
I have a table like
TABLEX -
+------+------------+
| NAME | TABLE_NAME |
+------+------------+
| X1 | X001 |
| X2 | X002 |
+------+------------+
This table contains a name column which is nothing but description and a table_name column which is actually a table already present in the database.
X001 Table has columns like X1_A, X1_B
X002 Table has columns like X2_A, X2_B
Now I want to concatenate all columns in the actual table present in the TABLE_NAME column in a comma separated string and display that as a column.
+------+------------+------------+
| NAME | TABLE_NAME | COLUMNS |
+------+------------+------------+
| X1 | X001 | X1_A, X1_B |
| X2 | X002 | X2_A, X2_B |
+------+------------+------------+
Now can this be achieved using CTE. I've already successfully created the query using STUFF with XML PATH, but I'm having performance issues because there are like 200 odd rows in the table that I've show above and each subsequent tables linked have like 100 columns each.
EDIT -
SELECT
P.NAME,
P.TABLE_NAME,
[COLUMNS]=(SELECT STUFF((SELECT ',' + NAME FROM sys.syscolumns WHERE ID = OBJECT_ID(P.TABLE_NAME) ORDER BY colorder FOR XML PATH('') ), 1, 1,''))
FROM TABLEX P
Where TABLEX is the table posted above.
Try this one -
DDL:
IF OBJECT_ID (N'dbo.TABLEX') IS NOT NULL
DROP TABLE TABLEX
IF OBJECT_ID (N'dbo.X001') IS NOT NULL
DROP TABLE X001
IF OBJECT_ID (N'dbo.X002') IS NOT NULL
DROP TABLE X002
CREATE TABLE dbo.TABLEX (NAME VARCHAR(50), TABLE_NAME VARCHAR(50))
INSERT INTO dbo.TABLEX (NAME, TABLE_NAME)
VALUES ('X1', 'X001'), ('X2', 'X002')
CREATE TABLE dbo.X001 (X1_A VARCHAR(50), X1_B VARCHAR(50))
CREATE TABLE dbo.X002 (X2_A VARCHAR(50), X2_B VARCHAR(50))
Query:
;WITH cte AS
(
SELECT
NAME
, TABLE_NAME
, [COLUMN] = CAST('' AS VARCHAR(1024))
, POS = 1
FROM TABLEX t
UNION ALL
SELECT
t.NAME
, t.TABLE_NAME
, CAST([COLUMN] + ', ' + c.name AS VARCHAR(1024))
, POS + 1
FROM cte t
JOIN sys.columns c ON
OBJECT_ID('dbo.' + t.TABLE_NAME) = c.[object_id]
AND
t.POS = c.column_id
)
SELECT
NAME
, TABLE_NAME
, [COLUMNS] = STUFF([COLUMN], 1, 2, '')
FROM (
SELECT *, rn = ROW_NUMBER() OVER (PARTITION BY NAME ORDER BY POS DESC)
FROM cte
) t
WHERE t.rn = 1
Results:
NAME TABLE_NAME COLUMNS
------ ------------- -------------
X1 X001 X1_A, X1_B
X2 X002 X2_A, X2_B
Query cost:
Statistic:
Query Presenter Scans Logical Reads
------------------- ----- -------------
XML 5 9
CTE 3 48
I'm aware that the "combine multiple rows into list" question has been answered a million times, and here's a reference to an awesome article: Concatenating row values in transact sql
I have a need to combine multiple rows into lists for multiple columns at the same time
ID | Col1 | Col2 ID | Col1 | Col2
------------------ => ------------------
1 A X 1 A X
2 B Y 2 B,C Y,Z
2 C Z
I tried to use the xml method, but this has proven to be very slow over large tables
SELECT DISTINCT
[ID],
[Col1] = STUFF((SELECT ',' + t2.[Col1]
FROM #Table t2
WHERE t2.ID = t.ID
FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'),1,1,''),
[Col2] = STUFF((SELECT ',' + t2.[Col2]
FROM #Table t2
WHERE t2.ID = t.ID
FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'),1,1,''),
FROM #Table t
My current solution is to use a stored procedure that builds each ID row separately. I'm wondering if there's another approach I could use (other than using a loop)
For each column, rank the rows to combine (partition by the key column)
End up with a table like
ID | Col1 | Col2 | Col1Rank | Col2Rank
1 A X 1 1
2 B Y 1 1
2 C Z 2 2
Create a new table containing top rank columns for each ID
ID | Col1Comb | Col2Comb
1 A X
2 B Y
Loop through each remaining rank in increasing order (in this case 1 iteration)
for irank = 0; irank <= 1; irank++
update n set
n.col1Comb = n.Col1Comb + ',' + o.Col1, -- so append the rank 2 items
n.col2comb = n.Col2Comb + ',' + o.Col2 -- if they are not null
from #newtable n
join #oldtable o
on o.ID = n.ID
where o.col1rank = irank or o.col2rank = irank
A CTE trick can be used where you update the CTE.
Method 1: a new parallel table to which the data is copied and then concatenated:
CREATE TABLE #Table1(ID INT, Col1 VARCHAR(1), Col2 VARCHAR(1), RowID INT IDENTITY(1,1));
CREATE TABLE #Table1Concat(ID INT, Col3 VARCHAR(MAX), Col4 VARCHAR(MAX), RowID INT);
GO
INSERT #Table1 VALUES(1,'A','X'), (2,'B','Y'), (2,'C','Z');
GO
INSERT #Table1Concat
SELECT * FROM #Table1;
GO
DECLARE #Cat1 VARCHAR(MAX) = '';
DECLARE #Cat2 VARCHAR(MAX) = '';
; WITH CTE AS (
SELECT TOP 2147483647 t1.*, t2.Col3, t2.Col4, r = ROW_NUMBER()OVER(PARTITION BY t1.ID ORDER BY t1.Col1, t1.Col2)
FROM #Table1 t1
JOIN #Table1Concat t2 ON t1.RowID = t2.RowID
ORDER BY t1.ID, t1.Col1, t1.Col2
)
UPDATE CTE
SET #Cat1 = Col3 = CASE r WHEN 1 THEN ISNULL(Col1,'') ELSE #Cat1 + ',' + Col1 END
, #Cat2 = Col4 = CASE r WHEN 1 THEN ISNULL(Col2,'') ELSE #Cat2 + ',' + Col2 END;
GO
SELECT ID, Col3 = MAX(Col3)
, Col4 = MAX(Col4)
FROM #Table1Concat
GROUP BY ID
Method 2: Add the concatenation columns directly to the original table and concatenate the new columns:
CREATE TABLE #Table1(ID INT, Col1 VARCHAR(1), Col2 VARCHAR(1), Col1Cat VARCHAR(MAX), Col2Cat VARCHAR(MAX));
GO
INSERT #Table1(ID,Col1,Col2) VALUES(1,'A','X'), (2,'B','Y'), (2,'C','Z');
GO
DECLARE #Cat1 VARCHAR(MAX) = '';
DECLARE #Cat2 VARCHAR(MAX) = '';
; WITH CTE AS (
SELECT TOP 2147483647 t1.*, r = ROW_NUMBER()OVER(PARTITION BY t1.ID ORDER BY t1.Col1, t1.Col2)
FROM #Table1 t1
ORDER BY t1.ID, t1.Col1, t1.Col2
)
UPDATE CTE
SET #Cat1 = Col1Cat = CASE r WHEN 1 THEN ISNULL(Col1,'') ELSE #Cat1 + ',' + Col1 END
, #Cat2 = Col2Cat = CASE r WHEN 1 THEN ISNULL(Col2,'') ELSE #Cat2 + ',' + Col2 END;
GO
SELECT ID, Col1Cat = MAX(Col1Cat)
, Col2Cat = MAX(Col2Cat)
FROM #Table1
GROUP BY ID;
GO
Try this one -
Query1:
DECLARE #temp TABLE
(
ID INT
, Col1 VARCHAR(30)
, Col2 VARCHAR(30)
)
INSERT INTO #temp (ID, Col1, Col2)
VALUES
(1, 'A', 'X'),
(2, 'B', 'Y'),
(2, 'C', 'Z')
SELECT
r.ID
, Col1 = STUFF(REPLACE(REPLACE(CAST(d.x.query('/t1/a') AS VARCHAR(MAX)), '<a>', ','), '</a>', ''), 1, 1, '')
, Col2 = STUFF(REPLACE(REPLACE(CAST(d.x.query('/t2/a') AS VARCHAR(MAX)), '<a>', ','), '</a>', ''), 1, 1, '')
FROM (
SELECT DISTINCT ID
FROM #temp
) r
OUTER APPLY (
SELECT x = CAST((
SELECT
[t1/a] = t2.Col1
, [t2/a] = t2.Col2
FROM #temp t2
WHERE r.ID = t2.ID
FOR XML PATH('')
) AS XML)
) d
Query 2:
SELECT
r.ID
, Col1 = STUFF(REPLACE(CAST(d.x.query('for $a in /a return xs:string($a)') AS VARCHAR(MAX)), ' ,', ','), 1, 1, '')
, Col2 = STUFF(REPLACE(CAST(d.x.query('for $b in /b return xs:string($b)') AS VARCHAR(MAX)), ' ,', ','), 1, 1, '')
FROM (
SELECT DISTINCT ID
FROM #temp
) r
OUTER APPLY (
SELECT x = CAST((
SELECT
[a] = ',' + t2.Col1
, [b] = ',' + t2.Col2
FROM #temp t2
WHERE r.ID = t2.ID
FOR XML PATH('')
) AS XML)
) d
Output:
ID Col1 Col2
----------- ---------- ----------
1 A X
2 B,C Y,Z
One solution, one that is at least syntactically straight-forward, is to use a User-Defined Aggregate to "Join" the values together. This does require SQLCLR and while some folks are reluctant to enable it, it does provide for a set-based approach that does not need to re-query the base table per each column. Joining is the opposite of Splitting and will create a comma-separated list of what was individual rows.
Below is a simple example that uses the SQL# (SQLsharp) library which comes with a User-Defined Aggregate named Agg_Join() that does exactly what is being asked for here. You can download the Free version of SQL# from http://www.SQLsharp.com/ and the example SELECTs from a standard system view. (And to be fair, I am the author of SQL# but this function is available for free).
SELECT sc.[object_id],
OBJECT_NAME(sc.[object_id]) AS [ObjectName],
SQL#.Agg_Join(sc.name) AS [ColumnNames],
SQL#.Agg_Join(DISTINCT sc.system_type_id) AS [DataTypes]
FROM sys.columns sc
GROUP BY sc.[object_id]
I recommend testing this against your current solution(s) to see which is the fastest for the volume of data you expect to have in at least the next year or two.
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