How to count in SQL all fields with null values in one record? - sql

Is there any way to count all fields with null values for specific record excluding PrimaryKey column?
Example:
ID Name Age City Zip
1 Alex 32 Miami NULL
2 NULL 24 NULL NULL
As output I need to get 1 and 3. Without explicitly specifying column names.

declare #T table
(
ID int,
Name varchar(10),
Age int,
City varchar(10),
Zip varchar(10)
)
insert into #T values
(1, 'Alex', 32, 'Miami', NULL),
(2, NULL, 24, NULL, NULL)
;with xmlnamespaces('http://www.w3.org/2001/XMLSchema-instance' as ns)
select ID,
(
select *
from #T as T2
where T1.ID = T2.ID
for xml path('row'), elements xsinil, type
).value('count(/row/*[#ns:nil = "true"])', 'int') as NullCount
from #T as T1
Result:
ID NullCount
----------- -----------
1 1
2 3
Update:
Here is a better version. Thanks to Martin Smith.
;with xmlnamespaces('http://www.w3.org/2001/XMLSchema-instance' as ns)
select ID,
(
select T1.*
for xml path('row'), elements xsinil, type
).value('count(/row/*[#ns:nil = "true"])', 'int') as NullCount
from #T as T1
Update:
And with a bit faster XQuery expression.
;with xmlnamespaces('http://www.w3.org/2001/XMLSchema-instance' as ns)
select ID,
(
select T1.*
for xml path('row'), elements xsinil, type
).value('count(//*/#ns:nil)', 'int') as NullCount
from #T as T1

SELECT id,
CASE WHEN Name IS NULL THEN 1 ELSE 0 END +
CASE WHEN City IS NULL THEN 1 ELSE 0 END +
CASE WHEN Zip IS NULL THEN 1 ELSE 0 END
FROM YourTable
If you do not want explicit column names in query, welcome to dynamic querying
DECLARE #sql NVARCHAR(MAX) = ''
SELECT #sql = #sql + N' CASE WHEN '+QUOTENAME(c.name)+N' IS NULL THEN 1 ELSE 0 END +'
FROM sys.tables t
JOIN sys.columns c
ON t.object_id = c.object_id
WHERE
c.is_nullable = 1
AND t.object_id = OBJECT_ID('YourTableName')
SET #sql = N'SELECT id, '+#sql +N'+0 AS Cnt FROM [YourTableName]'
EXEC(#sql)

This should solve your problem:
select count (id)
where ( isnull(Name,"") = "" or isnull(City,"") = "" or isnull(Zip,"") = "" )
Not a smart solution, but it should do the work.

DECLARE #tempSQL nvarchar(max)
SET #tempSQL = N'SELECT '
SELECT #tempSQL = #tempSQL + 'sum(case when ' + cols.name + ' is null then 1 else 0 end) "Null Values for ' + cols.name + '",
sum(case when ' + cols.name + ' is null then 0 else 1 end) "Non-Null Values for ' + cols.name + '",' FROM sys.columns cols WHERE cols.object_id = object_id('TABLE1');
SET #tempSQL = SUBSTRING(#tempSQL, 1, LEN(#tempSQL) - 1) + ' FROM TABLE1;'
EXEC sp_executesql #tempSQL

Related

Restructure table by removing NULL values

I have a table in SQL that looks like this:
Customer Product 1999 2000 2001 2002 2003
Smith 51 NULL NULL 15 14 NULL
Jones 14 11 7 NULL NULL NULL
Jackson 13 NULL NULL NULL 3 9
The figures under each year column are amounts, in dollars. Each customer has two consecutive years of amounts, and the rest of the years are zero. I would like to re-structure this table so that instead of wide list of years, it just has two columns Amount-Year1 and Amount-Year2. So it selects the two non-zero years and puts them in those columns, in the correct order. This would greatly reduce the size of my table.
So far I've been able to re-structure it so that there is one amount column and one year column, but I then get multiple rows per customer, which unfortunately I can't have (due to downstream analysis). Can anyone think of a way to get the two Amount-Year columns?
I would like the final table to look like this:
Customer Product Amount_Y1 Amount_Y2
Smith 51 15 14
Jones 14 11 7
Jackson 13 3 9
I don't mind that I lose the information about the specific years, as I can get that from another source. The actual table has data for all years between 1999 and 2018, and there will be further years in the future.
Thanks
Thankfully UNPIVOT removes NULLs anyway, so we can do this with UNPIVOT/ROWNUMBER(),PIVOT:
declare #t table (Customer varchar(15),Product int,[1999] int,
[2000] int,[2001] int,[2002] int,[2003] int)
insert into #T(CUstomer,Product,[1999],[2000],[2001],[2002],[2003]) values
('Smith' ,51,NULL,NULL, 15, 14,NULL),
('Jones' ,14, 11, 7,NULL,NULL,NULL),
('Jackson',13,NULL,NULL,NULL, 3, 9)
;With Numbered as (
select
Customer,Product,Value,
ROW_NUMBER() OVER (PARTITION BY Customer,Product
ORDER BY Year) rn
from
#t t
unpivot
(Value for Year in ([1999],[2000],[2001],[2002],[2003])) u
)
select
*
from
Numbered n
pivot
(SUM(Value) for rn in ([1],[2])) w
Results:
Customer Product 1 2
--------------- ----------- ----------- -----------
Jackson 13 3 9
Jones 14 11 7
Smith 51 15 14
Use COALESCE that will do the job for you. The query is dynamics so that if tomorrow year columns are changed, i.e. removed or added you do not have to change anything.
Sample query: (Assuming table to be table1 and column names to be same as year).
DECLARE #columnsdesc nvarchar(max), #columnsasc nvarchar(max)
SET #columnsdesc = ''
SELECT #columnsdesc = (select + '[' + ltrim(c.Name) + ']' + ','
FROM sys.columns c
JOIN sys.objects o ON o.object_id = c.object_id
WHERE o.type = 'U' and o.Name = 'table1' and c.Name not in ('Customer', 'Product')
ORDER BY c.Name desc for xml path ( '' ))
SET #columnsasc = ''
SELECT #columnsasc = (select + '[' + ltrim(c.Name) + ']' + ','
FROM sys.columns c
JOIN sys.objects o ON o.object_id = c.object_id
WHERE o.type = 'U' and o.Name = 'table1' and c.Name not in ('Customer', 'Product')
ORDER BY c.Name asc for xml path ( '' ))
SELECT #columnsasc = LEFT( #columnsasc,LEN(#columnsasc)-1)
SELECT #columnsdesc = LEFT( #columnsdesc,LEN(#columnsdesc)-1)
DECLARE #sql nvarchar(max)
SET #sql = 'SELECT Customer, Product, COALESCE('+ #columnsasc +') as Amount_Y1,
COALESCE(' + #columnsdesc +' ) as Amount_Y2
FROM Table1'
EXEC(#sql)
If you're dealing with a temporary table, then the code will change slightly:
Test it here: http://rextester.com/MRVR48808
DECLARE #columnsdesc nvarchar(max), #columnsasc nvarchar(max)
SET #columnsdesc = ''
SELECT #columnsdesc = (select + '[' + ltrim(c.Name) + ']' + ','
FROM tempdb.sys.columns c --Changes here
JOIN tempdb.sys.objects o ON o.object_id = c.object_id --Changes here
WHERE o.type = 'U' and o.Name like '#table1%' and c.Name not in ('Customer', 'Product') --Changes here
ORDER BY c.Name desc for xml path ( '' ))
SET #columnsasc = ''
SELECT #columnsasc = (select + '[' + ltrim(c.Name) + ']' + ','
FROM tempdb.sys.columns c --Changes here
JOIN tempdb.sys.objects o ON o.object_id = c.object_id --Changes here
WHERE o.type = 'U' and o.Name like '#table1%' and c.Name not in ('Customer', 'Product') --Changes here
ORDER BY c.Name asc for xml path ( '' ))
SELECT #columnsasc = LEFT( #columnsasc,LEN(#columnsasc)-1)
SELECT #columnsdesc = LEFT( #columnsdesc,LEN(#columnsdesc)-1)
DECLARE #sql nvarchar(max)
SET #sql = 'SELECT Customer, Product, COALESCE('+ #columnsasc +') as Amount_Y1,
COALESCE(' + #columnsdesc +' ) as Amount_Y2
FROM #Table1' --Changes here
EXEC(#sql)
Try using COALESCE as follows : For one field from beginning to end and for the second in the reverse manner.
SELECT Customer,Product, COALESCE([1999],[2000],[2001],[2002],[2003]) as Y1,
COALESCE([2003],[2002],[2001],[2000],[1999]) as Y2
FROM #TEMPDATA
I would do this using cross apply:
select t.customer, t.product, v.Amount_Y1, v.Amount_Y2
from t cross apply
(select max(case when which = 1 then val end) as Amount_Y1,
max(case when which = 2 then val end) as Amount_Y2
from (select val, yr, row_number() over (order by yr) as which
from (values (t.[1999], 1999), (t.[2000], 2000), (t.[2001], 2001),
(t.[2002], 2002), (t.[2003], 2003)
) v(val, yr)
where val is not null
) v

form a query as per column values

I am using sql server 2012 and I have a table like this:
FieldName FieldValue
DivisionId 1
DivisionId 2
DivisionId 3
CompanyId 2
CompanyId 3
LocationId 1
What i want is concatenate columns and form a where clause query like this
(DivisionId=1 OR DivisionId=2 OR DivisionId=3) AND
(CompanyId=2 OR CompanyId=3) AND
(LocationId = 1)
What I was able to figure out is, I need to concatenate columns values like this
DECLARE #Query VARCHAR(MAX)
SELECT #Query =
ISNULL(#Query,'') + IIF(#Query IS NOT NULL, ' AND ', '') + CONCAT(DF.FieldName,'=',DA.FieldValue)
FROM TABLE
SELECT #Query;
But this code will not handle OR condition.
Try following solution:
DECLARE #eav TABLE (
FieldName NVARCHAR(128) NOT NULL,
FieldValue VARCHAR(50) NOT NULL
)
INSERT #eav (FieldName, FieldValue)
SELECT 'DivisionId', 1 UNION ALL
SELECT 'DivisionId', 2 UNION ALL
SELECT 'DivisionId', 3 UNION ALL
SELECT 'CompanyId ', 2 UNION ALL
SELECT 'CompanyId ', 3 UNION ALL
SELECT 'LocationId', 1
DECLARE #Predicate NVARCHAR(MAX) = N''
SELECT #Predicate = #Predicate
+ CASE WHEN rn_asc = 1 THEN ' AND ' + FieldName + ' IN (' + LTRIM(FieldValue) ELSE '' END
+ CASE WHEN rn_asc > 1 THEN ', ' + LTRIM(FieldValue) ELSE '' END
+ CASE WHEN rn_desc = 1 THEN ') ' ELSE '' END
FROM (
SELECT *,
rn_asc = ROW_NUMBER() OVER(PARTITION BY x.FieldName ORDER BY x.FieldValue),
rn_desc= ROW_NUMBER() OVER(PARTITION BY x.FieldName ORDER BY x.FieldValue DESC)
FROM #eav x
) y
ORDER BY FieldName, FieldValue
SELECT #Predicate = STUFF(#Predicate, 1, 5, '')
SELECT #Predicate
-- Results: CompanyId IN (2, 3) AND DivisionId IN (1, 2, 3) AND LocationId IN (1)
Then you could use #Predicate to create a dynamic SQL SELECT statement (for example)
DECLARE #SqlStatement NVARCHAR(MAX)
SET #SqlStatement = 'SELECT ... FROM dbo.Table1 WHERE ' + #Predicate
EXEC sp_executesql #SqlStatement

Dynamic pivot data with multiple datatypes

I have a trick problem with a pivot table to make:
I have a table which looks like:
id table object name type nvarchar date int bit
1 1 2 name 1 tables NULL NULL NULL
2 1 2 name 1 columns NULL NULL NULL
3 1 2 name 1 datatypes NULL NULL NULL
4 1 2 name 1 _users NULL NULL NULL
1 1 3 active 3 NULL NULL NULL 1
2 1 3 active 3 NULL NULL NULL 1
3 1 3 active 3 NULL NULL NULL 1
4 1 3 active 3 NULL NULL NULL 1
the output should look like:
id name active
1 tables 1
2 columns 1
3 datatypes 1
4 _users 1
Based upon the "type" I should put the correct data from the column in it, these columns are formated in nvarchar, bit, datetime, int, ect.
The "id" is the row id, the "name, active" comes from the name column and the values from nvarchar, date, int and bit columns.
UPDATE: the columns like nvarchar, date, int and bit (and most other SQL formats) are actually contain this type of data. The column "type" gives which column contains the data to being used, so if "type" is "1", than I want to use the "nvarchar" if "type" is "3" than I want to use the "bit" which contains really a bit and not a nvarchar. In the Pivot I want to have the bit under "active" column, if I have in the example a 3th column (name) for example "activation_date" I want to see a third column with the value (type = 2) from the date column.
I am lost in this, please help
Assuming there's only one not null column for each row:
with cte as (
select
id,
name,
coalesce(
[nvarchar],
convert(nvarchar(max), [date], 120),
cast([int] as nvarchar(max)),
cast([bit] as nvarchar(max))
) as value
from Table1 as t
)
select
id,
max(case when [name] = 'name' then value end) as [name],
max(case when [name] = 'active' then value end) as [active]
from cte
group by id
sql fiddle demo
But I must warn you, this types of database schema is not best way to use SQL.
If you want to do this dynamically without hardcoding columns:
declare #stmt nvarchar(max)
select #stmt =
isnull(#stmt + ', ', '') +
'max(case when [name] = ''' + name + ''' then value end) as ' + quotename([name])
from (select distinct [name] from Table1) as t
select #stmt = '
with cte as (
select
id,
name,
coalesce(
[nvarchar],
convert(nvarchar(max), [date], 120),
cast([int] as nvarchar(max)),
cast([bit] as nvarchar(max))
) as value
from Table1 as t
)
select
id, ' + #stmt + '
from cte
group by id
'
exec sp_executesql
#stmt = #stmt
sql fiddle demo
If you have some Mapping table like this:
name value
--------------------
name nvarchar
active bit
you can use this query:
declare #stmt nvarchar(max)
select #stmt =
isnull(#stmt + ', ', '') +
'max(case when [name] = ''' + name + ''' then [' + value + '] end) as ' + quotename([name])
from Mapping
select #stmt = '
select
id, ' + #stmt + '
from Table1
group by id
'
exec sp_executesql
#stmt = #stmt
sql fiddle demo

Rotate/pivot 1000 columns to rows

I inherited a DB from which I need to extract data in columns that is currently arranged in rows. The query to retrieve the data is:
select distinct
LIMSNo, p250.*, p500.*, p750.*, p1000.*
from
results as r
inner join Points250 as p250
on r.resultsid = p250.resultsid and p250.channel = 1
inner join Points500 as p500
on r.resultsid = p500.resultsid and p500.channel = 1
inner join Points750 as p750
on r.resultsid = p750.resultsid and p750.channel = 1
inner join Points1000 as p1000
on r.resultsid = p1000.resultsid and p1000.channel = 1
where
r.limsno between '120053698' and '120053704'
which produces
LIMSNo P0001 P0002 P0003 ... P1000
120053698 251.6667 302.0196 302.2353 305.9608
120053699 291.6667 342.6545 347.9635 353.6236
120053700 243.3333 298.3206 296.7235 299.5342
120053701 308.3333 365.8397 365.4071 368.3206
120053702 315 363.4153 366.6052 373.1695
Note that there are 1000 columns (P0001...P1000)
I want to pivot/rotate the data so that I get this output:
120053698 120053699 120053700 120053701 120053702
P0001 251.6667 291.6667 243.3333 308.3333 315
P0002 302.0196 342.6545 298.3206 365.8397 363.4153
...
P1000 305.9608 353.6236 299.5342 368.3206 373.1695
How do I structure the SQL query? The DB is SQL 2000.
I have tried a number of approaches to this problem. My latest attempt was:
create table #tempCols
(
ColName nchar(15)
)
insert into #tempCols
select
column_name
from
information_schema.columns
where
table_name = 'Points250'
and column_name like 'P%'
insert into #tempCols
select
column_name
from
information_schema.columns
where
table_name = 'Points500'
and column_name like 'P%'
insert into #tempCols
select
column_name
from
information_schema.columns
where
table_name = 'Points750'
and column_name like 'P%'
insert into #tempCols
select
column_name
from
information_schema.columns
where
table_name = 'Points1000'
and column_name like 'P%'
create table #LIMSList
(
LIMSNumber nchar(9)
)
insert into #LIMSList
select LimsNo from results
where LimsNo between '100030460' and '100030500'
declare #colList varchar(max)
select #colList = COALESCE(#colList + ',' ,'') + ColName from #tempCols
declare #command varchar(max)
set #command =
'
select LimsNo, Point, Data
from (
select LimsNo, p250.*, p500.*, p750.*, p1000.*
from
results as r
inner join Points250 as p250
on r.resultsid = p250.resultsid and p250.channel = 1
inner join Points500 as p500
on r.resultsid = p500.resultsid and p500.channel = 1
inner join Points750 as p750
on r.resultsid = p750.resultsid and p750.channel = 1
inner join Points1000 as p1000
on r.resultsid = p1000.resultsid and p1000.channel = 1
where
limsno in (select LimsNo from #LIMSList)) d
unpivot (Data for Point in (' + #colList + ')) as unpvt
'
print #command
exec(#command)
drop table #tempCols
drop table #LIMSList
But the error I get is:
Msg 8156, Level 16, State 1, Line 1
The column 'ResultsID' was specified multiple times for 'd'.
Msg 8156, Level 16, State 1, Line 1
The column 'ResultsID' was specified multiple times for 'unpvt'.
Since SQL Server 2000 does not have either an UNPIVOT or PIVOT operator, you will have to use UNION ALL for the UNPIVOT and then a CASE statement for the PIVOT. Here is a dynamic solution that should work:
DECLARE #query AS NVARCHAR(MAX),
#rowCount as int = 1,
#unpivotCount as int = 0,
#pivotCount as int,
#unRow as varchar(10) = '',
#pivotRow as varchar(10) = ''
select c.Name col
into #colsUnpivot
from sys.columns as C
where C.object_id = object_id('yourtable') and
C.name LIKE 'P%'
set #unpivotCount = (select count(*) from #colsUnpivot)
-- unpivot the data
while #rowCount <= #unpivotCount
begin
set #unRow = (select Top 1 col from #colsUnpivot)
set #query =' insert into unpivotData (LIMSNo, Val, Col)
select LIMSNo, ' + #unRow + ' as val, '''
+ #unRow + ''' as col
from yourtable'
exec(#query)
delete from #colsUnpivot where col = #unRow
if #rowCount <= #unpivotCount
set #rowCount = #rowCount + 1
end
--select *
--from unpivotData
-- pivot the data
select distinct LIMSNo
into #colsPivot
from yourtable
set #pivotCount= (select COUNT(*) from #colsPivot)
-- reset rowcount
set #rowCount = 1
set #query = ''
---- create the CASE string
while #rowCount <= #pivotCount
begin
set #pivotRow = (select Top 1 LIMSNo from #colsPivot)
set #query = #query + ', max(case when LIMSNo = ' + #pivotRow + ' then val end) as ''' + #pivotRow + ''''
delete from #colsPivot where LIMSNo = #pivotRow
if #rowCount <= #pivotCount
begin
set #rowCount = #rowCount + 1
print #rowCount
end
end
-- add the rest of the SQL Statement
set #query = 'SELECT col ' + #query + ' from dbo.unpivotData group by col'
exec(#query)
delete from dbo.unpivotData
drop table #colsUnpivot
drop table #colspivot
I created a table called UnpivotData to hold the data for use with in the PIVOT process.
See SQL Fiddle with Demo
pivot and unpivot operators are not available in SQL Server 2000. So you have to pivot and unpivot the data using other techniques.
I am listing static methods of unpivot and pivot and i hope you can convert it into dynamic solution if you want.
assuming you have inserted the data of your query into a table you can unpivot the data using UNION ALL and insert it into temporary table.
SELECT
*
INTO dbo.temp
FROM
(
SELECT LIMSNo, P0001 AS [Point],'P0001' AS [ColName] FROM dbo.myTable
UNION ALL
SELECT LIMSNo, P0002 AS [Point],'P0002' AS [ColName] FROM dbo.myTable
UNION ALL
SELECT LIMSNo, P0003 AS [Point],'P0003' AS [ColName] FROM dbo.myTable
UNION ALL
SELECT LIMSNo, P0004 AS [Point],'P0004' AS [ColName] FROM dbo.myTable
....
....
UNION ALL
SELECT LIMSNo, P1000 AS [Point],'P1000' AS [ColName] FROM dbo.myTable
) AS t
then you can pivot the data again
SELECT
ColName
,MAX(CASE LIMSNo WHEN '120053698' THEN [Point] ELSE NULL END ) AS [120053698]
,MAX(CASE LIMSNo WHEN '120053699' THEN [Point] ELSE NULL END ) AS [120053699]
,MAX(CASE LIMSNo WHEN '120053700' THEN [Point] ELSE NULL END ) AS [120053700]
....
....
,MAX(CASE LIMSNo WHEN '120053704' THEN [Point] ELSE NULL END ) AS [120053704]
FROM dbo.temp
GROUP BY ColName

How can I query row data as columns?

I'm sure I'm missing something here.
I have a dataset like this:
FK RowNumber Value Type Status
1 1 aaaaa A New
1 2 bbbbb B Good
1 3 ccccc A Bad
1 4 ddddd C Good
1 5 eeeee B Good
2 1 fffff C Bad
2 2 ggggg A New
2 3 hhhhh C Bad
3 1 iiiii A Good
3 2 jjjjj A Good
I'd like to query the top 3 results and Pivot them as columns, so the end result set looks like this:
FK Value1 Type1 Status1 Value2 Type2 Status2 Value3 Type3 Status3
1 aaaaa A New bbbbb B Good ccccc A Bad
2 fffff C Bad ggggg A New hhhhh C Bad
3 iiiii A Good jjjjj A Good
How can I accomplish this in SQL Server 2005?
I have been attempting this using PIVOT, but I am still very unfamiliar with that keyword and cannot get it to work the way I want.
SELECT * --Id, [1], [2], [3]
FROM
(
SELECT Id, Value, Type, Status
, ROW_NUMBER() OVER (PARTITION BY Id ORDER Status, Type) as [RowNumber]
FROM MyTable
) as T
PIVOT
(
-- I know this section doesn't work. I'm still trying to figure out PIVOT
MAX(T.Value) FOR RowNumber IN ([1], [2], [3]),
MAX(T.Type) FOR RowNumber IN ([1], [2], [3]),
MAX(T.Status) FOR RowNumber IN ([1], [2], [3])
) AS PivotTable;
My actual data set is a bit more complex than this, and I need the top 10 records, not the top 3, so I don't want to simply do CASE WHEN RowNumber = X THEN... for each one.
Update
I tested all the answers below, and found most of them seem about the same with no apparent performance difference in smaller data sets (around 3k records), however there was a slight difference when running the queries against larger data sets.
Here are the results of my tests using 80,000 records and querying for 5 columns in the top 10 rows, so my end result set was 50 columns + the Id column. I'd suggest you test them on your own to decide which one works best for you and your environment.
bluefoot's answer of unpivoting and re-pivoting the data averaged the fastest at about 12 seconds. I also liked this answer because I found it easiest to read and maintain.
Aaron's answer and koderoid's answer both suggest using a MAX(CASE WHEN RowNumber = X THEN ...), and was close behind averaging at around 13 seconds.
Rodney's answer of using multiple PIVOT statements averaged around 16 seconds, although it might be faster with fewer PIVOT statements (my tests had 5).
And the first half of Aaron's answer that suggested using a CTE and OUTER APPLY was the slowest. I don't know how long it would take to run because I cancelled it after 2 minutes, and that was with around 3k records, 3 rows, and 3 columns instead of 80k records, 10 rows, and 5 columns.
You can do an UNPIVOT and then a PIVOT of the data. this can be done either statically or dynamically:
Static Version:
select *
from
(
select fk, col + cast(rownumber as varchar(1)) new_col,
val
from
(
select fk, rownumber, value, cast(type as varchar(10)) type,
status
from yourtable
) x
unpivot
(
val
for col in (value, type, status)
) u
) x1
pivot
(
max(val)
for new_col in
([value1], [type1], [status1],
[value2], [type2], [status2],
[value3], [type3])
) p
see SQL Fiddle with demo
Dynamic Version, this will get the list of columns to unpivot and then to pivot at run-time:
DECLARE #colsUnpivot AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#colsPivot as NVARCHAR(MAX)
select #colsUnpivot = stuff((select ','+quotename(C.name)
from sys.columns as C
where C.object_id = object_id('yourtable') and
C.name not in ('fk', 'rownumber')
for xml path('')), 1, 1, '')
select #colsPivot = STUFF((SELECT ','
+ quotename(c.name
+ cast(t.rownumber as varchar(10)))
from yourtable t
cross apply
sys.columns as C
where C.object_id = object_id('yourtable') and
C.name not in ('fk', 'rownumber')
group by c.name, t.rownumber
order by t.rownumber
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query
= 'select *
from
(
select fk, col + cast(rownumber as varchar(10)) new_col,
val
from
(
select fk, rownumber, value, cast(type as varchar(10)) type,
status
from yourtable
) x
unpivot
(
val
for col in ('+ #colsunpivot +')
) u
) x1
pivot
(
max(val)
for new_col in
('+ #colspivot +')
) p'
exec(#query)
see SQL Fiddle with Demo
Both will generate the same results, however the dynamic is great if you do not know the number of columns ahead of time.
The Dynamic version is working under the assumption that the rownumber is already a part of the dataset.
You can try to do the pivot in three separate pivot statements. Please give this a try:
SELECT Id
,MAX(S1) [Status 1]
,MAX(T1) [Type1]
,MAX(V1) [Value1]
--, Add other columns
FROM
(
SELECT Id, Value , Type, Status
, 'S' + CAST(ROW_NUMBER() OVER (PARTITION BY Id ORDER BY Status, Type) AS VARCHAR(10)) [Status_RowNumber]
, 'T' + CAST(ROW_NUMBER() OVER (PARTITION BY Id ORDER BY Status, Type) AS VARCHAR(10)) [Type_RowNumber]
, 'V' + CAST(ROW_NUMBER() OVER (PARTITION BY Id ORDER BY Status, Type) AS VARCHAR(10)) [Value_RowNumber]
FROM MyTable
) as T
PIVOT
(
MAX(Status) FOR Status_RowNumber IN ([S1], [S2], [S3],[S4],[S5],[S6],[S7],[S8],[S9],[S10])
)AS StatusPivot
PIVOT(
MAX(Type) FOR Type_RowNumber IN ([T1], [T2], [T3],[T4],[T5],[T6],[T7],[T8],[T9],[T10])
)AS Type_Pivot
PIVOT(
MAX(Value) FOR Value_RowNumber IN ([V1], [V2], [V3],[V4],[V5],[V6],[V7],[V8],[V9],[V10])
)AS Value_Pivot
GROUP BY Id
I don't know the full scope of the criteria for selecting the top ten records, but this produces and output that may get you closer to your answer.
SQL Fiddle Example
Rodney's muli-pivot is clever, that's for sure. Here are two other alternatives that are of course less appealing when you get into the 10X vs. 3X area.
;WITH a AS
(
SELECT Id, Value, Type, Status,
n = ROW_NUMBER() OVER (PARTITION BY Id ORDER BY [Status], [Type])
FROM dbo.MyTable
)
SELECT a.Id,
Value1 = a.Value, Type1 = a.[Type], Status1 = a.[Status],
Value2 = b.Value, Type2 = b.[Type], Status2 = b.[Status],
Value3 = c.Value, Type3 = c.[Type], Status3 = c.[Status]
FROM a
OUTER APPLY (SELECT * FROM a AS T2 WHERE n = a.n + 1 AND id = a.id) AS b
OUTER APPLY (SELECT * FROM a AS T2 WHERE n = b.n + 1 AND id = b.id) AS c
WHERE a.n = 1
ORDER BY a.Id;
-- or --
;WITH a AS
(
SELECT Id, Value, [Type], [Status],
n = ROW_NUMBER() OVER (PARTITION BY Id ORDER BY [Status], [Type])
FROM dbo.MyTable
)
SELECT Id,
Value1 = MAX(CASE WHEN n = 1 THEN Value END),
Type1 = MAX(CASE WHEN n = 1 THEN [Type] END),
Status1 = MAX(CASE WHEN n = 1 THEN [Status] END),
Value2 = MAX(CASE WHEN n = 2 THEN Value END),
Type2 = MAX(CASE WHEN n = 2 THEN [Type] END),
Status2 = MAX(CASE WHEN n = 2 THEN [Status] END),
Value3 = MAX(CASE WHEN n = 3 THEN Value END),
Type3 = MAX(CASE WHEN n = 3 THEN [Type] END),
Status3 = MAX(CASE WHEN n = 3 THEN [Status] END)
FROM a
GROUP BY Id
ORDER BY a.Id;
This might work for you, though it's not elegant.
select aa.FK_Id
, isnull(max(aa.Value1), '') as Value1
, isnull(max(aa.Type1), '') as Type1
, isnull(max(aa.Status1), '') as Status1
, isnull(max(aa.Value2), '') as Value2
, isnull(max(aa.Type2), '') as Type2
, isnull(max(aa.Status2), '') as Status2
, isnull(max(aa.Value3), '') as Value3
, isnull(max(aa.Type3), '') as Type3
, isnull(max(aa.Status3), '') as Status3
from
(
select FK_Id
, case when RowNumber = 1 then Value else null end as Value1
, case when RowNumber = 1 then [Type] else null end as Type1
, case when RowNumber = 1 then [Status] else null end as Status1
, case when RowNumber = 2 then Value else null end as Value2
, case when RowNumber = 2 then [Type] else null end as Type2
, case when RowNumber = 2 then [Status] else null end as Status2
, case when RowNumber = 3 then Value else null end as Value3
, case when RowNumber = 3 then [Type] else null end as Type3
, case when RowNumber = 3 then [Status] else null end as Status3
from Table1
) aa
group by aa.FK_Id
try something like this:
declare #rowCount int
set #rowCount = 10
declare #isNullClause varchar(4024)
set #isnullClause = ''
declare #caseClause varchar(4024)
set #caseClause = ''
declare #i int
set #i = 1
while(#i <= #rowCount) begin
set #isnullClause = #isNullClause +
' , max(aa.Value' + CAST(#i as varchar(3)) + ') as Value' + CAST(#i as varchar(3)) +
' , max(aa.Type' + CAST(#i as varchar(3)) + ') as Type' + CAST(#i as varchar(3)) +
' , max(aa.Status' + CAST(#i as varchar(3)) + ') as Status' + CAST(#i as varchar(3)) + ' ';
set #caseClause = #caseClause +
' , case when RowNumber = ' + CAST(#i as varchar(3)) + ' then Value else null end as Value' + CAST(#i as varchar(3)) +
' , case when RowNumber = ' + CAST(#i as varchar(3)) + ' then Type else null end as Type' + CAST(#i as varchar(3)) +
' , case when RowNumber = ' + CAST(#i as varchar(3)) + ' then Status else null end as Status' + CAST(#i as varchar(3)) + ' '
set #i = #i + 1;
end
declare #sql nvarchar(4000)
set #sql = 'select aa.FK_Id ' + #isnullClause + ' from ( select FK_Id '
+ #caseClause + ' from Table1) aa group by aa.FK_Id '
exec SP_EXECUTESQL #sql