SQL Server 2008 Cross Tab Query

Crosstab Query with Dynamic Columns in SQL Server 2008

The query you will need to get the results in your question is:

create table StudentResults(StudentID int,Name nvarchar(50),Course nvarchar(50), CourseLevel nvarchar(10));
insert into StudentResults values(1,'John','English','E2'),(1,'John','Maths','E3'),(1,'John','Computing','L2');

select StudentID
,Name
,[Computing]
,[Maths]
,[English]
from StudentResults
pivot(max(CourseLevel) for Course in([Computing],[Maths],[English])
) as p;

Output:

StudentID   Name    Computing   Maths   English
1 John L2 E3 E2

Though as you may be able to work out, this requires hard coding the subjects. If your list of subjects is likely to change, then this query will no longer be fit for purpose.

If you are comfortable, you can remedy this with dynamic SQL:

declare @cols as  nvarchar(max)
,@query as nvarchar(max);

set @cols = stuff(
(select distinct ','+quotename(Course)
from StudentResults
for xml path(''),type).value('.','nvarchar(max)'
)
,1,1,''
);

set @query = 'select StudentID
,Name
,'+@cols+'
from StudentResults
pivot (max(CourseLevel) for Course in ('+@cols+')
) p';

execute (@query);

Ideally though, you would simply return a set of data, as it appears to be in your source table and let your reporting layer (SSRS for example) handle the pivoting, which it is much better suited towards than pure SQL.

Crosstab query in SQL Server 2008

I would query it like

select
Date,
Sum(case when Calendar = 'Canada' then Days else 0 end) as Canada,
Sum(case when Calendar = 'Europe' then Days else 0 end) as Europe,
Sum(case when Calendar = 'UK' then Days else 0 end) as UK,
Sum(case when Calendar = 'US' then Days else 0 end) as Us,
Sum(case when Calendar not in ('Canada','Europe','UK','US') then Days else 0 end)
as Other
from
MyTable
group by
Date

Crosstab query with count of values in SQL Server 2008 R2

There are a few different ways that you can convert the rows to columns. One way that you can do this is by using an aggregate function with a CASE expression:

select ApproachStatus,
sum(case when Clinic = 'GI Med Onc' then 1 else 0 end) [GI Med Onc],
sum(case when Clinic = 'Breast Med Onc' then 1 else 0 end) [Breast Med Onc]
from yt
group by ApproachStatus;

See SQL Fiddle with Demo

Or since you are using SQL Server 2005+, you can use the PIVOT function:

select ApproachStatus, [GI Med Onc],[Breast Med Onc]
from yt
pivot
(
count(Clinic)
for Clinic in ([GI Med Onc],[Breast Med Onc])
) piv;

See SQL Fiddle with Demo.

If you have an unknown Clinic values, then you will need to look at using dynamic SQL to get the result:

DECLARE @cols AS NVARCHAR(MAX),
@query AS NVARCHAR(MAX)

select @cols = STUFF((SELECT distinct ',' + QUOTENAME(Clinic)
from yt
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')

set @query = 'SELECT ApproachStatus,' + @cols + '
from yt
pivot
(
count(Clinic)
for Clinic in (' + @cols + ')
) p '

execute(@query)

See SQL Fiddle with Demo. All queries will give a result:

| APPROACHSTATUS | GI MED ONC | BREAST MED ONC |
------------------------------------------------
| Approached | 2 | 1 |
| Not Approached | 0 | 1 |
| Pending | 1 | 0 |

Sql Server 2008 Cross Tab Query

You should be able to do this with the 'pivot' operator. Something like this (though I am sure I muffed some spelling or syntax details...):

select catTitle, [1] as site1, [2] as site2, [3] as site3, [4] as site4, [5] as site5
from (select category.catTitle, equipment.quantity, site.title
from equipment
inner join site
on (equipment.siteid = site.siteid)
inner join category
on (category.catid = equipment.catid)
)
pivot
(
sum (quantity)
for equipment.siteid in ( [1], [2], [3], [4], [5] )
) as pvt
order by pvt.category;

The problem with this is that you need to know the exact set of site ids you want to include in the query. If you need a more dynamic crosstab (like you can get in Excel), then you need to generate the query text as a string and use sp_executesql to run it. In the generated text, you include as many of the "[1], [2], [3], [4], [5]..." and "[1] as site1, [2] as site2..." things as you need.

Sql Cross Tab (Query)

I tested the below with your provided sample data and it returns accordingly. Basically for each column you want to conditionally pass in the TotalPatients tot he SUM aggregator based on the PatientType value.

select
Dept_Name
,sum(case when Patient_Type = 'Entitled' then TotalPatients ELSE 0 end) Entitled
,sum(case when Patient_Type = 'General' then TotalPatients ELSE 0 end) General
,sum(case when Patient_Type = 'Staff' then TotalPatients ELSE 0 end) Staff
,sum(case when Patient_Type = 'Referred' then TotalPatients ELSE 0 end) Referred
from @t
group by Dept_Name

In the cases where you need dynamic columns...

IF EXISTS (SELECT * 
FROM tempdb.dbo.sysobjects o
WHERE o.xtype IN ( 'U' )
AND o.id = Object_id(N'tempdb..#t'))
DROP TABLE #t;

CREATE TABLE #t
(
dept_name NVARCHAR(255),
patient_type NVARCHAR(255),
totalpatients INT
)

INSERT INTO #t
(dept_name,
patient_type,
totalpatients)
Insert Into #T (Dept_Name, Patient_Type , TotalPatients)
SELECT department.dept_name,
patient.patient_type,
Count(*) AS TotalPatients
FROM patient
INNER JOIN payment
ON patient.regno = payment.regno
INNER JOIN department
ON payment.deptid = department.dept_id
WHERE ( CONVERT(VARCHAR, patient.regdatetime, 112) =
CONVERT(VARCHAR, Getdate() - 30, 112) )
GROUP BY department.dept_name,
patient.patient_type

DECLARE @cols AS NVARCHAR(max),
@query AS NVARCHAR(max)

SELECT @cols = Stuff((SELECT DISTINCT ',' + Quotename(patient_type)
FROM #t
GROUP BY dept_name,
patient_type
ORDER BY 1
FOR xml path(''), type).value('.', 'NVARCHAR(MAX)'), 1, 1,
'')

SET @query = N'SELECT Dept_Name, ' + @cols + N'
from
#T x
pivot
(
SUM(TotalPatients)
for Patient_Type in ('
+ @cols + N')
) p '

EXEC Sp_executesql @query;

Cross Tab query in SQL Server 2008

In SQL Server you will want to look at the PIVOT function. Considering you did not provide many details on your table or sample data, based on your attempt in the comments something like this might point you in the right direction.

SELECT *
FROM
(
SELECT Equipt, Shed
FROM PunctualityMain
) x
PIVOT
(
COUNT(Equipt)
FOR Shed IN ([BSL], [AQ])
) p

If you post some more details, then we could provide additional help.

I need to know how to create a crosstab query

This type of transformation is called a pivot. You did not specify what database you are using so I will provide a answers for SQL Server and MySQL.


SQL Server: If you are using SQL Server 2005+ you can implement the PIVOT function.

If you have a known number of values that you want to convert to columns then you can hard-code the query.

select typename, total, Deployed, Inventory, shipped
from
(
select count(*) over(partition by t.typename) total,
s.statusname,
t.typename
from assets a
inner join assettypes t
on a.assettype = t.id
inner join assetstatus s
on a.assetstatus = s.id
) d
pivot
(
count(statusname)
for statusname in (Deployed, Inventory, shipped)
) piv;

See SQL Fiddle with Demo.

But if you have an unknown number of status values, then you will need to use dynamic sql to generate the list of columns at run-time.

DECLARE @cols AS NVARCHAR(MAX),
@query AS NVARCHAR(MAX)

select @cols = STUFF((SELECT distinct ',' + QUOTENAME(statusname)
from assetstatus
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')

set @query = 'SELECT typename, total,' + @cols + ' from
(
select count(*) over(partition by t.typename) total,
s.statusname,
t.typename
from assets a
inner join assettypes t
on a.assettype = t.id
inner join assetstatus s
on a.assetstatus = s.id
) x
pivot
(
count(statusname)
for statusname in (' + @cols + ')
) p '

execute(@query)

See SQL Fiddle with Demo

This can also be written using an aggregate function with a case expression:

select typename,
total,
sum(case when statusname ='Deployed' then 1 else 0 end) Deployed,
sum(case when statusname ='Inventory' then 1 else 0 end) Inventory,
sum(case when statusname ='Shipped' then 1 else 0 end) Shipped
from
(
select count(*) over(partition by t.typename) total,
s.statusname,
t.typename
from assets a
inner join assettypes t
on a.assettype = t.id
inner join assetstatus s
on a.assetstatus = s.id
) d
group by typename, total

See SQL Fiddle with Demo


MySQL: This database does not have a pivot function so you will have to use the aggregate function and a CASE expression. It also does not have windowing functions, so you will have to alter the query slightly to the following:

select typename,
total,
sum(case when statusname ='Deployed' then 1 else 0 end) Deployed,
sum(case when statusname ='Inventory' then 1 else 0 end) Inventory,
sum(case when statusname ='Shipped' then 1 else 0 end) Shipped
from
(
select t.typename,
(select count(*)
from assets a1
where a1.assettype = t.id
group by a1.assettype) total,
s.statusname
from assets a
inner join assettypes t
on a.assettype = t.id
inner join assetstatus s
on a.assetstatus = s.id
) d
group by typename, total;

See SQL Fiddle with Demo

Then if you need a dynamic solution in MySQL, you will have to use a prepared statement to generate the sql string to execute:

SET @sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'sum(CASE WHEN statusname = ''',
statusname,
''' THEN 1 else 0 END) AS `',
statusname, '`'
)
) INTO @sql
FROM assetstatus;

SET @sql
= CONCAT('SELECT typename,
total, ', @sql, '
from
(
select t.typename,
(select count(*)
from assets a1
where a1.assettype = t.id
group by a1.assettype) total,
s.statusname
from assets a
inner join assettypes t
on a.assettype = t.id
inner join assetstatus s
on a.assetstatus = s.id
) d
group by typename, total');

PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

See SQL Fiddle with Demo.

The result is the same for all queries in both databases:

| TYPENAME | TOTAL | DEPLOYED | INVENTORY | SHIPPED |
-----------------------------------------------------
| Desktop | 2 | 1 | 1 | 0 |
| Laptop | 1 | 0 | 0 | 1 |
| Server | 1 | 1 | 0 | 0 |

CrossTab Query / Pivot Table in MS SQL?

Something like this might help:

Query 1:

SELECT
terminal,
count(CASE WHEN load_time_mns >= 0 AND load_time_mns < 1 THEN 1 END) [0-1 mns],
count(CASE WHEN load_time_mns >= 1 AND load_time_mns < 2 THEN 1 END) [1-2 mns],
count(CASE WHEN load_time_mns >= 2 AND load_time_mns < 3 THEN 1 END) [2-3 mns]
FROM t
GROUP BY terminal

Results:

|   TERMINAL | 0-1 MNS | 1-2 MNS | 2-3 MNS |
|------------|---------|---------|---------|
| Terminal 1 | 0 | 1 | 0 |
| Terminal 2 | 0 | 0 | 0 |
| Terminal 3 | 0 | 0 | 1 |
| Terminal 6 | 0 | 1 | 0 |

Fiddle here.

Note that in your example you did not include 1 in the [0-1] range but you did include 3 in the [0-3] range, which seems not right.

SQL Server 2008 CrossTab equivalent

There are a few different ways that you can do this in SQL Server you can use the PIVOT function:

select userid,
[12] moduleNum12,
[10] moduleNum10,
[18] moduleNum18
from
(
select userid, moduleid, cast(passed as int) passed
from yourtable
) d
pivot
(
max(passed)
for moduleId in ([12], [10], [18])
) piv;

See Demo

Or you can use an aggregate function with a CASE expression:

select userid,
max(case when moduleid = 12 then cast(passed as int) end) moduleNum12,
max(case when moduleid = 10 then cast(passed as int) end) moduleNum10,
max(case when moduleid = 18 then cast(passed as int) end) moduleNum18
from yourtable
group by userid;

See Demo.

The above work great if the values are known nut if you have unknown values, then you will need to use dynamic SQL:

DECLARE @cols AS NVARCHAR(MAX),
@colsAlias AS NVARCHAR(MAX),
@query AS NVARCHAR(MAX)

select @cols = STUFF((SELECT distinct ',' + QUOTENAME(ModuleID)
from yourtable
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')

select @colsAlias = STUFF((SELECT distinct ', ' + QUOTENAME(ModuleID) +' as moduleNum'+cast(ModuleID as varchar(10))
from yourtable
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')

set @query = 'SELECT userid,' + @colsAlias + '
from
(
select userid, moduleid, cast(passed as int) passed
from yourtable
) d
pivot
(
max(passed)
for moduleid in (' + @cols + ')
) p '

execute(@query)

See Demo

A peculiar crosstab query in SQL Server

Assuming you need DYNAMIC

Declare @SQL varchar(max) = Stuff((Select Distinct ',' + QuoteName(Color) From YourTable Order by 1 For XML Path('')),1,1,'') 
Select @SQL = '
Select [Package Barcode],[Total],' + @SQL + '
From (
Select [Package Barcode],B.[Color],B.[Cnt]
From YourTable A
Cross Apply (
Select Color=A.Color,Cnt=1
Union All
Select Color=''Total'',Cnt=1
) B
) A
Pivot (Sum(Cnt) For [Color] in ([Total],' + @SQL + ') ) p'
Exec(@SQL);

Returns

Package Barcode Total   BLUE    PINK    RED YELL
12345 3 1 NULL 1 1
19999 5 2 1 2 NULL

EDIT - If it helps, the SQL Generates looks like this

 Select [Package Barcode],[Total],[BLUE],[PINK],[RED],[YELL]
From (
Select [Package Barcode],B.[Color],B.[Cnt]
From YourTable A
Cross Apply (
Select Color=A.Color,Cnt=1
Union All
Select Color='Total',Cnt=1
) B
) A
Pivot (Sum(Cnt) For [Color] in ([Total],[BLUE],[PINK],[RED],[YELL]) ) p

EDIT 2

Declare @SQL varchar(max) = Stuff((Select Distinct ',' + QuoteName(Color) From YourTable Order by 1 For XML Path('')),1,1,'') 
Select @SQL = '
Select [Package Barcode],[Total],' + @SQL + '
From (
Select [Package Barcode],[Color],[Cnt]=case when Sum(Cnt)=0 then ''>>'' else cast(Sum(Cnt) as nvarchar(25)) end
From (
Select [Package Barcode],[Color],[Cnt]=Sum(1) from YourTable Group By [Package Barcode],[Color]
Union ALL
Select Distinct [Package Barcode],C.[Color],[Cnt]=0
From YourTable
Cross Join (Select Distinct Color From YourTable) C
Union All
Select [Package Barcode],[Color]=''Total'',[Cnt]=Sum(1) from YourTable Group By [Package Barcode]
) A
Group By [Package Barcode],[Color]
) A
Pivot (max(Cnt) For [Color] in ([Total],' + @SQL + ') ) p'
Exec(@SQL);

Returns

Package Barcode Total   BLUE    PINK    RED YELL
12345 3 1 >> 1 1
19999 5 2 1 2 >>

Having trouble getting → to display, so I put a >> as a place marker.

Edit 3 - Conditional Aggregation

Select [Package Barcode]
,[Total] = sum(1)
,[BLUE] = sum(case when color='Blue' then 1 else 0 end)
,[Pink] = sum(case when color='Pink' then 1 else 0 end)
--.. Add more colors
From YourTable
Group By [Package Barcode]


Related Topics



Leave a reply



Submit