Concatenate columns in Pivot View - sql

DECLARE #DynamicPivotQuery AS NVARCHAR(MAX)
DECLARE #ColumnName AS NVARCHAR(MAX)
--Get distinct values of the PIVOT Column
SELECT #ColumnName= ISNULL(#ColumnName + ',','')
+ QUOTENAME(ACT_DESC)
FROM (SELECT DISTINCT ACT_DESC FROM tpc) AS desc
--Prepare the PIVOT query using the dynamic
SET #DynamicPivotQuery =
N'SELECT MDCODE, ' + #ColumnName + '
FROM tpc
PIVOT(MAX(ACTUAL_DATE)
FOR tpcIN (' + #ColumnName + ')) AS PVTTable'
--Execute the Dynamic Pivot Query
EXEC sp_executesql #DynamicPivotQuery
This query displays:
MDCODE | sample1 | sample2
--------------------------
123 | 1/2014 |
123 | | 2/2014
123 | | 3/2014
What I want is this:
MDCODE | sample1 | sample2
--------------------------
123 | 1/2014 | 2/2014,3/2014
Does anyone have an idea how to concat the column data? Any tips?
This is the table where i get the data:
mdcode | act_desc | actual_date
--------------------------
1234 | sample1 | 1/2014
1234 | sample2 | 2/2014
1234 | sample2 | 3/2014
the actual_date is datetime

In SQL Server, the type of comma separated output is achieved with "for xml path", but is also usually associated with stuff()
Optionally via "cross apply" although it doesn't have to be done that way.
I would approach it like this. Don't use pivot now, just reduce MDCODE & ACT_DESC by distinct or group by, then using cross apply concatenate the string of "dates". If they really are dates then you will need to convert them to varchar e.g. convert(varchar,actual_date,112)
| MDCODE | ACT_DESC | ACTUAL_DATES |
|--------|----------|---------------|
| 1234 | sample1 | 1/2014 |
| 1234 | sample2 | 2/2014,3/2014 |
produced by:
SELECT
d.mdcode
, d.act_desc
, ca1.actual_dates
FROM (
SELECT DISTINCT
[mdcode]
, [act_desc]
FROM Table1
) d
CROSS APPLY (
SELECT
STUFF((
SELECT
',' + a.actual_date
FROM table1 a
WHERE a.mdcode = d.mdcode
AND a.act_desc = d.act_desc --<< change here
ORDER BY a.actual_date
FOR xml PATH ('')
)
, 1, 1, '')
) AS ca1 (actual_dates)
;
By the way STUFF() is used to remove the first comma from the concatenation, that is its only role. CROSS APPLY is arguably better than using a correlated subquery in the select clause, because it is performed before that clause as part of the from clause. As I said earlier its optional but I prefer it.
see: http://sqlfiddle.com/#!3/a24ba/4
& also see these samples

I would skip the PIVOT altogether and just use FOR XML_PATH, like this:
DECLARE #DynamicPivotQuery AS NVARCHAR(MAX)
--Get distinct values of the PIVOT Column
SELECT #DynamicPivotQuery= ISNULL(#DynamicPivotQuery + ',','select mdcode,')
+ 'stuff((select '',''+actual_date from tpc where mdcode=t.mdcode and act_desc = ''' + ACT_DESC + ''' for xml path(''''),type).value(''.'',''varchar(max)''),1,1,'''') '
+ QUOTENAME(ACT_DESC)
FROM (SELECT DISTINCT ACT_DESC FROM tpc) AS des
select #DynamicPivotQuery = #DynamicPivotQuery + 'from tpc t group by mdcode'
EXEC sp_executesql #DynamicPivotQuery
The dynamic query generates a query like this:
select mdcode,
stuff(
(
select ','+actual_date
from tpc where mdcode=t.mdcode and act_desc = 'sample1'
for xml path(''),type
).value('.','varchar(max)')
,1,1,'') sample1,
stuff(
(
select ','+actual_date
from tpc where mdcode=t.mdcode and act_desc = 'sample2'
for xml path(''),type
).value('.','varchar(max)')
,1,1,'') sample2
from tpc t
group by mdcode;
The SQL Fiddle demonstrates the static query and the dynamic one

Related

SQL group rows to columns [duplicate]

This question already has answers here:
Simple way to transpose columns and rows in SQL?
(9 answers)
Closed 6 years ago.
I have a table
ID | Customer | Type | Value |
---+----------+---------+-------+
1 | John | Income | 50 |
2 | John | Income | 20 |
3 | Mike | Outcome | 150 |
4 | Robert | Income | 100 |
5 | John | Outcome | 300 |
Want a table like that;
| John | Mike | Robert |
--------+------+------+--------+
Income | 70 | 0 | 100 |
Outcome| 300 | 150 | 0 |
What should be the SQL Query? Thanks
The problem is Customers and Type are not static they are dynamic.
What I tried:
SELECT 'TotalIncome' AS TotalSalaryByDept,
[John], [Mike]
FROM
(SELECT Customer, Income
FROM table001) AS a
PIVOT
(
SUM(Income)
FOR ID IN ([John], [Mike])
) AS b;
Here is a quick dynamic pivot. We use a CROSS APPLY to unpivot the desired measures.
Declare #SQL varchar(max)
Select #SQL = Stuff((Select Distinct ',' + QuoteName(Customer) From YourTable Order by 1 For XML Path('')),1,1,'')
Select #SQL = '
Select [Type],' + #SQL + '
From (
Select Item=A.Customer,B.*
From YourTable A
Cross Apply (
Select Type=''Income'' ,Value=A.Income Union All
Select Type=''Outcome'',Value=A.Outcome
) B
) A
Pivot (sum(value) For Item in (' + #SQL + ') ) p'
Exec(#SQL);
Returns
EDIT - For the Revised Question
Declare #SQL varchar(max) = Stuff((Select Distinct ',' + QuoteName(Customer) From YourTable Order by 1 For XML Path('')),1,1,'')
Select #SQL = '
Select [Type],' + #SQL + '
From (Select Customer,Type,Value from YourTable ) A
Pivot (Sum(Value) For [Customer] in (' + #SQL + ') ) p'
Exec(#SQL);
Returns
The Table as you have it is how it should be in your SQL database. Columns are reserved for classifying your data, and rows are where you add new instances.
What you need to do is set up your ASP, Excel Pivot Table, or whatever you are using to display the data to format it into a horizontal table. I would need to know what you are using to interface with your database to give you an example.

Count the number of matches for a prefix in a SQL query

I have a table in an SQL server that looks like the one below and I want to count the number of unique occurrences where specific prefixes are used in the data column, like "21:00:00".
Dataset:
+-------------------------+
| data |
+-------------------------+
| 21:00:00:24:ff:5e:3a:bd |
| 50:01:43:80:18:6b:2a:4c |
| 21:00:00:1b:32:0f:a7:54 |
| 10:00:00:90:fa:a8:da:2a |
+-------------------------+
Desired query output:
+----------+----------+----------+
| 21:00:00 | 50:01:43 | 10:00:00 |
+----------+----------+----------+
| 2 | 1 | 1 |
+----------+----------+----------+
I have been able to get the query to count a single prefix at a time by using this:
SELECT COUNT(DISTINCT wwpn) AS "21:00:00" FROM table WHERE wwpn LIKE '21:00:00%'
However, I want to count multiple prefixes as shown in the desired query output.
I've been waiting for someone to do a dynamic pivot (like Matt said in the comments) but no one has done it yet : (...I tried it myself and this is what I managed...
Query:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + LEFT(QUOTENAME(data), 9) + ']'
FROM DataTable
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = N'SELECT ' + #cols + N' from
(
select LEFT(data, 8) as data, COUNT(*) as count
from DataTable
GROUP BY LEFT(data, 8)
) x
pivot
(
max(count)
for data in (' + #cols + N')
) p '
exec sp_executesql #query;
Results:
10:00:00 | 21:00:00 | 50:01:43
---------|----------|---------
1 | 2 | 1
---------|----------|---------
Use this query:
SELECT LEFT([Data], 8) as prefix, count(*) as cnt
FROM tableName
GROUP BY LEFT([Data], 8);
If you know the prefixes in advance then you can do something simple like this;
Create test data;
CREATE TABLE #TestData (FieldName nvarchar(50))
INSERT INTO #TestData
VALUES
('21:00:00:24:ff:5e:3a:bd')
,('50:01:43:80:18:6b:2a:4c')
,('21:00:00:1b:32:0f:a7:54')
,('10:00:00:90:fa:a8:da:2a')
Query
SELECT
SUM(CASE WHEN FieldName LIKE '21:00:00%' THEN 1 ELSE 0 END) [21:00:00]
,SUM(CASE WHEN FieldName LIKE '50:01:43%' THEN 1 ELSE 0 END) [50:01:43]
,SUM(CASE WHEN FieldName LIKE '10:00:00%' THEN 1 ELSE 0 END) [10:00:00]
FROM #TestData
Result
21:00:00 50:01:43 10:00:00
2 1 1

SQL Double Dynamic Pivot

I am working on a double dynamic pivot based on 2 columns (HardwarePhase & HardwarePhase_Result).
Using the first result set in the image below, is the raw data that I have. Each set of 5 items (highlighted in images) are grouped based on HardwareTestCaseID.
The second result set in the image, is the current results that I'm getting from how I've constructed this query. Ideally, the result of the second column would be the same results, but instead it would be grouping the results.
The grouping is supposed to be based on the HardwareTestCaseID, but however, this is not happening.
The results I actually want are shown here. (There should be multiple rows, but this is just how it should be grouped per 5 entries).
This is the query I am currently using:
NOTE: The #col variables are built up based on the list of HardwarePhases (P0, M1, M2, M3).
select #query = 'SELECT ' + #colsNames + ',' + #colsResultNames + ', HardwareTestCaseID FROM
(
SELECT HardwarePhase_Result, HardwarePhase, ResultValue, HardwareTestCaseID, HardwareStatus
FROM #temp4
) as x
pivot
(
MAX(ResultValue)
FOR HardwarePhase_Result IN (' + #colsResult + ')
) as p
pivot
(
MAX(HardwareStatus)
FOR HardwarePhase IN (' + #cols + ')
) as p2 ';
using this table:
create table #temp4
(
HardwarePhase nvarchar(max),
HardwarePhase_Result nvarchar(max),
ResultValue bigint,
HardwareTestCaseID bigint,
HardwareStatus nvarchar(max),
Block nvarchar(max)
);
I personally would do it slightly different since you want to PIVOT on two columns. I would look at unpivoting the data in the multiple columns first, then apply the PIVOT function. I also would suggest that you start with writing a hard-coded version of the query first then convert it to dynamic SQL - this allows you to get the correct logic.
To unpivot the data, I would use CROSS APPLY so you can convert the pairs of columns into rows at the same time, the syntax would be similar to the following:
select col, value, HardwareTestCaseID
from temp4
cross apply
(
select HardwarePhase, HardwareStatus union all
select HardwarePhase_Result, cast(ResultValue as varchar(10))
) c (col, value)
See SQL Fiddle with Demo. Your data is then in the format:
| COL | VALUE | HARDWARETESTCASEID |
|-----------|-------------|--------------------|
| P0 | Not Started | 365 |
| P0_Result | 1 | 365 |
| M1 | Pass | 365 |
| M1_Result | 1 | 365 |
| M4 | Pass | 365 |
| M4_Result | 1 | 365 |
| M2 | Blocked | 365 |
| M2_Result | 1 | 365 |
Then you just apply the pivot function to the data:
select M1, M2, M3, M4, P0,
M1_Result, M2_Result, M3_Result,
M4_Result, P0_Result,
HardwareTestCaseID
from
(
select col, value, HardwareTestCaseID
from temp4
cross apply
(
select HardwarePhase, HardwareStatus union all
select HardwarePhase_Result, cast(ResultValue as varchar(10))
) c (col, value)
) d
pivot
(
max(value)
for col IN (M1, M2, M3, M4, P0,
M1_Result, M2_Result, M3_Result,
M4_Result, P0_Result)
) piv;
See SQL Fiddle with Demo.
Once you have the logic down, then convert it to dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(col)
from temp4
cross apply
(
select HardwarePhase, 1 union all
select HardwarePhase_Result, 2
) c (col, so)
group by col, so
order by so, col
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT ' + #cols + ' , HardwareTestCaseID
from
(
select col, value, HardwareTestCaseID
from temp4
cross apply
(
select HardwarePhase, HardwareStatus union all
select HardwarePhase_Result, cast(ResultValue as varchar(10))
) c (col, value)
) x
pivot
(
max(value)
for col in (' + #cols + ')
) p '
execute sp_executesql #query;
See SQL Fiddle with Demo. This process gets a result:
| M1 | M2 | M3 | M4 | P0 | M1_RESULT | M2_RESULT | M3_RESULT | M4_RESULT | P0_RESULT | HARDWARETESTCASEID |
|---------|---------|---------|---------|-------------|-----------|-----------|-----------|-----------|-----------|--------------------|
| Pass | Blocked | Pass | Pass | Not Started | 1 | 1 | 1 | 1 | 1 | 365 |
| Blocked | Blocked | Blocked | Blocked | Pass | 1 | (null) | 1 | 1 | 1 | 366 |
--This is Just AWESOME. Simplified it for just one table as it's a much more common case (and could not find anything even close to this elegant after trying for hours)
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(your_key_column)
from YOUR_ORIGINAL_KEY_AND_VALUE_TABLE
group by your_key_column
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT your_row_heading_columns,' + #cols + '
INTO YOUR_NEW_PIVOTED_TABLE
from
(
select your_row_heading_columns,your_key_column,your_value_column
from YOUR_ORIGINAL_KEY_AND_VALUE_TABLE
) x
pivot
(
max(your_value_column)
for your_key_column in (' + #cols + ')
) p '
execute sp_executesql #query;

Flatten variable length DB rows into a single column

I've searched and can't find a solution to this that exactly fits my needs, nor can I find one that I can modify. I have a database table, for simplicity we'll say it has three columns (packageID, carrier, and sequence). For any package there can be one or more carriers that have handled the package. I can do a query like
SELECT packageID, carrier
FROM packageFlow
ORDER BY sequence
to get a list of all the people that have handled packages that looks like:
packageID, carrier
1, Bob
1, Jim
1, Sally
1, Ron
2, Reggie
2, Mary
2, Bruce
What I need though is to get the results into rows that look like:
packageID|carrier1|carrier2|carrier3|carrier4
1 |Bob |Jim |Sally |Ron
2 |Reggie |Mary |Bruce
Pivot doesn't seem to do what I need since I'm not aggregating anything and I can't get a CTE to work correctly either. I'd appreciate any nudges in the right direction.
This data transformation is a PIVOT. Starting in SQL Server 2005, there is a function that will convert the rows into columns.
If you have a known number of values, then you can hard-code your query:
select *
from
(
select packageid, carrier,
'Carrier_'+cast(row_number() over(partition by packageid order by packageid) as varchar(10)) col
from packageflow
) src
pivot
(
max(carrier)
for col in (Carrier_1, Carrier_2, Carrier_3, Carrier_4)
) piv
See SQL Fiddle with Demo.
If you have an unknown number of Carrier values that you want to turn into columns, then you can use dynamic sql:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(t.col)
from
(
select 'Carrier_'+cast(row_number() over(partition by packageid order by packageid) as varchar(10)) col
from packageFlow
) t
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT packageid,' + #cols + ' from
(
select packageid, carrier,
''Carrier_''+cast(row_number() over(partition by packageid order by packageid) as varchar(10)) col
from packageflow
) x
pivot
(
max(carrier)
for col in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo.
Note: you will replace the order by packageid with order by sequence
The result of both queries is:
| PACKAGEID | CARRIER_1 | CARRIER_2 | CARRIER_3 | CARRIER_4 |
-------------------------------------------------------------
| 1 | Bob | Jim | Sally | Ron |
| 2 | Reggie | Mary | Bruce | (null) |

Dynamic Pivot Columns in SQL Server

I have a table named Property with following columns in SQL Server:
Id Name
There are some property in this table that certain object in other table should give value to it.
Id Object_Id Property_Id Value
I want to make a pivot table like below that has one column for each property I've declared in 1'st table:
Object_Id Property1 Property2 Property3 ...
I want to know how can I get columns of pivot dynamically from table. Because the rows in 1'st table will change.
Something like this:
DECLARE #cols AS NVARCHAR(MAX);
DECLARE #query AS NVARCHAR(MAX);
select #cols = STUFF((SELECT distinct ',' +
QUOTENAME(Name)
FROM property
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
, 1, 1, '');
SELECT #query =
'SELECT *
FROM
(
SELECT
o.object_id,
p.Name,
o.value
FROM propertyObjects AS o
INNER JOIN property AS p ON o.Property_Id = p.Id
) AS t
PIVOT
(
MAX(value)
FOR Name IN( ' + #cols + ' )' +
' ) AS p ; ';
execute(#query);
SQL Fiddle Demo.
This will give you something like this:
| OBJECT_ID | PROPERTY1 | PROPERTY2 | PROPERTY3 | PROPERTY4 |
-------------------------------------------------------------
| 1 | ee | fd | fdf | ewre |
| 2 | dsd | sss | dfew | dff |