I have been able to combine results using the pivot function. Been trying various examples on my solution to group these results together. Am I doing this wrong or is it a simple fix?
create table DBE_LOCATION
(
REF int,
STATUS varchar(1)
);
insert into DBE_LOCATION values
(1, 'A'),
(2, 'A');
create table SYS_SCREEN_FIELD
(
REF int,
FIELD_DISPLAY varchar(20),
ORDER_BY int
);
insert into SYS_SCREEN_FIELD values
(1, 'Location Name', 0),
(2, 'Address', 1),
(3, 'Suburb', 2),
(4, 'Postcode', 3),
(5, 'State', 4),
(6, 'Country', 5);
create table DBE_LOCATION_DATA
(
REF int,
FIELD_REF int,
LOCATION_REF int,
VALUE_TEXT_FIELD varchar(MAX)
);
insert into DBE_LOCATION_DATA values
(1, 1, 1, 'New York'),
(2, 1, 2, 'Japan'),
(3, 2, 1, '123 Address St'),
(4, 2, 2, '456 Address St');
Now the final thing would be to show a result set of each Location with the field display as the column name. Something like this if using the above example:
Ref Location Name Address Status
1 New York 123 Address St A
2 Japan 456 Address Ave A
Have got the following working in gathering the data and creating the dynamic columns:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(FIELD_DISPLAY)
from SYS_SCREEN_FIELD
group by FIELD_DISPLAY, ORDER_BY
order by ORDER_BY
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT REF, ' + #cols + ', STATUS from
(
select l.REF, l.STATUS,
f.FIELD_DISPLAY,
d.FIELD_REF, d.VALUE_TEXT_FIELD
from DBE_LOCATION l
right join DBE_LOCATION_DATA d
on l.REF = d.LOCATION_REF
inner join SYS_SCREEN_FIELD f
on d.FIELD_REF = f.REF
) x
pivot
(
max(VALUE_TEXT_FIELD)
for FIELD_DISPLAY in (' + #cols + ')
) p'
execute(#query)
Results are not grouped by REF. How is this done?
SQL Fiddle Link
The problem is with the addition of the column FIELD_REF in your subquery. Even though you are not including this column in your final select list, since it is in your subquery the column is used during the grouping of the PIVOT.
You can see the issue if you include it in your final select, you get a result:
| REF | FIELD_REF | LOCATION NAME | ADDRESS | SUBURB | POSTCODE | STATE | COUNTRY | STATUS |
|-----|-----------|---------------|----------------|--------|----------|--------|---------|--------|
| 1 | 1 | Adelaide | (null) | (null) | (null) | (null) | (null) | S |
| 1 | 2 | (null) | 1 Adelaide St | (null) | (null) | (null) | (null) | S |
The FIELD_REF has a value of 1 and 2 for REF=1, when the aggregate and group by are applied, you will return multiple rows.
If you remove this column from your subquery you will get the result that you want:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(FIELD_DISPLAY)
from SYS_SCREEN_FIELD
group by FIELD_DISPLAY, ORDER_BY
order by ORDER_BY
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT REF, ' + #cols + ', STATUS from
(
select l.REF, l.STATUS,
f.FIELD_DISPLAY,
d.VALUE_TEXT_FIELD
from DBE_LOCATION l
right join DBE_LOCATION_DATA d
on l.REF = d.LOCATION_REF
inner join SYS_SCREEN_FIELD f
on d.FIELD_REF = f.REF
) x
pivot
(
max(VALUE_TEXT_FIELD)
for FIELD_DISPLAY in (' + #cols + ')
) p'
execute sp_executesql #query;
See SQL Fiddle with Demo. Now your final result is:
| REF | LOCATION NAME | ADDRESS | SUBURB | POSTCODE | STATE | COUNTRY | STATUS |
|-----|---------------|----------------|--------|----------|--------|---------|--------|
| 1 | Adelaide | 1 Adelaide St | (null) | (null) | (null) | (null) | S |
| 2 | Melbourne | 2 Melbourne St | (null) | (null) | (null) | (null) | S |
Related
How to verify the number of unique values in the columns? For example I have a table:
Shop_1
Shop_2
Shop_3
Shop_4
Adidas
Nike
Adidas
Reebok
Nike
Adidas
Asics
Ascics
Asics
Asics
Asics
Nike
Nike
Nike
Adidas
For this table, I would like to have an additional column with information on how many unique stores appeared in a given record. The results should be as follows:
First row: 2 (because there was Nike and Adidas)
Second row: 4
Third row: 1 (there were 4 shops but all Asics)
Fourth row: 2
CREATE TABLE shops
(ID INTEGER PRIMARY KEY,
shop1 CHAR(20),
shop2 CHAR(20),
shop3 CHAR(20),
shop4 CHAR(20),
expected_result INT )
INSERT INTO shops VALUES (1, 'Adidas', 'Nike', 'Adidas', null, 2);
INSERT INTO shops VALUES (2, 'Reebok', 'Nike', 'Adidas', 'Asics', 4);
INSERT INTO shops VALUES (3, 'Asics', 'Asics', 'Asics', 'Asics', 1);
INSERT INTO shops VALUES (4, 'Nike', 'Nike', 'Nike', 'Adidas', 2);
PLease try the following method.
SQL #1
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, Shop_1 VARCHAR(20), Shop_2 VARCHAR(20), Shop_3 VARCHAR(20), Shop_4 VARCHAR(20));
INSERT INTO #tbl (Shop_1, Shop_2, Shop_3, Shop_4) VALUES
('Adidas', 'Nike', 'Adidas', NULL),
('Reebok', 'Nike', 'Adidas', 'Asics'),
('Asics', 'Asics', 'Asics', 'Asics'),
('Nike', 'Nike', 'Nike', 'Adidas');
-- DDL and sample data population, end
SELECT *
, (
SELECT Shop_1, Shop_2, Shop_3, Shop_4
FOR XML PATH(''), TYPE, ROOT('root')
)
.value('count(distinct-values(/root/*/text()))','INT') AS [Counter]
FROM #tbl AS p;
SQL #2
It allows to handle a scenario where column list is vary:
Shop_1, Shop_2, ..., ShopN.
SELECT *
, (
SELECT *
FROM #tbl AS c
WHERE c.ID = p.ID
FOR XML PATH(''), TYPE, ROOT('root')
)
.value('count(distinct-values(/root/*[local-name()!="ID"]/text()))','INT') AS [UniqueCounter]
FROM #tbl AS p;
Output
+----+--------+--------+--------+--------+---------+
| ID | Shop_1 | Shop_2 | Shop_3 | Shop_4 | Counter |
+----+--------+--------+--------+--------+---------+
| 1 | Adidas | Nike | Adidas | NULL | 2 |
| 2 | Reebok | Nike | Adidas | Asics | 4 |
| 3 | Asics | Asics | Asics | Asics | 1 |
| 4 | Nike | Nike | Nike | Adidas | 2 |
+----+--------+--------+--------+--------+---------+
One way you can do this would be to use cross apply to pivot and then count
select *
from #t
cross apply (
select Count (distinct shops) UniqueCount
from (
values (shop_1),(shop_2),(shop_3),(shop_4)
)x(shops)
)a
DB Fiddle
A dynamical solution might be by using a catalog table(information_schema.columns) such as
DECLARE #cols1 AS NVARCHAR(MAX), #cols2 AS NVARCHAR(MAX), #query AS NVARCHAR(MAX)
SET #cols1 = ( SELECT STRING_AGG(QUOTENAME([column_name]),',')
FROM information_schema.columns
WHERE [column_name] != 'ID'
AND [table_name]='shops');
SET #cols2 = ( SELECT STRING_AGG('COUNT('+QUOTENAME([column_name])+')
OVER (PARTITION BY ID ORDER BY ID)','+') AS total
FROM information_schema.columns
WHERE [column_name] != 'ID');
SET #query = N'SELECT [ID], '+ #cols1 +',' + #cols2 +
N' FROM [shops]';
--SELECT #query;
EXEC sp_executesql #query;
Demo
Just count the distinct values for the columns.
SELECT
COUNT(DISTINCT shop_1) count1,
COUNT(DISTINCT shop_2) count2,
COUNT(DISTINCT shop_3) count3,
COUNT(DISTINCT shop_4) count4
FROM
your_table
I have a table with 11 columns. The first column includes the category names. The remaining 10 columns have values like white, green, big, damaged etc. and these values can change in time.
I need a SQL query to find how many are there in table (in 10 columns) each value.
Table 1:
+------------+------------+
| ID | decription |
+------------+------------+
| 1 | white |
| 2 | green |
| 3 | big |
| 4 | damaged |
+------------+------------+
Table 2:
+------------+-----------+-----------+-----------+
| CATEGORY | SECTION 1 | SECTION 2 | SECTION 3 |
+------------+-----------+-----------+-----------+
| Category 1 | white | green | big |
| Category 2 | big | damaged | white |
| Category 1 | white | green | big |
| Category 3 | big | damaged | white |
+------------+-----------+-----------+-----------+
Desired result:
+------------+-------+-------+-----+---------+
| CATEGORY | White | Green | Big | Damaged |
+------------+-------+-------+-----+---------+
| Category 1 | 20 | 10 | 9 | 50 |
| Category 2 | 25 | 21 | 15 | 5 |
+------------+-------+-------+-----+---------+
Is it possible doing like this dynamically just as query ?
its on MS sql in visual studio reporting
Thanks
You've got yourself a bit of a mess with the design and the desired result. The problem is that your table is denormalized and then the final result you want is also denormalized. You can get the final result by unpivoting your Section columns, then pivoting the values of those columns. You further add to the mess by needing to do this dynamically.
First, I'd advise you to rethink your table structure because this is far too messy to maintain.
In the meantime, before you even think about writing a dynamic version to get the result you have to get the logic correct via a static or hard-coded query. Now, you didn't state which version of SQL Server you are using but you first need to unpivot the Section columns. You can use either the UNPIVOT function or CROSS APPLY. Your query will start with something similar to the following:
select
category,
value
from yourtable
unpivot
(
value for cols in (Section1,Section2,Section3)
) u
See SQL Fiddle with Demo. This gets your data into the format:
| CATEGORY | VALUE |
|------------|---------|
| Category 1 | white |
| Category 1 | green |
| Category 1 | big |
| Category 2 | big |
| Category 2 | damaged |
| Category 2 | white |
Now you have multiple Category rows - one for each value that previously were in the Section columns. Since you want a total count of each word in the Category, you can now apply the pivot function:
select
category,
white, green, big, damaged
from
(
select
category,
value
from yourtable
unpivot
(
value for cols in (Section1,Section2,Section3)
) u
) un
pivot
(
count(value)
for value in (white, green, big, damaged)
) p;
See SQL Fiddle with Demo. This will give you the result that you want but now you need this to be done dynamically. You'll have to use dynamic SQL which will create a SQL string that will be executed giving you the final result.
If the number of columns to UNPIVOT is limited, then you will create a list of the new column values in a string and then execute it similar to:
DECLARE #query AS NVARCHAR(MAX),
#colsPivot as NVARCHAR(MAX);
select #colsPivot
= STUFF((SELECT ',' + quotename(SectionValue)
from yourtable
cross apply
(
select Section1 union all
select Section2 union all
select Section3
) d (SectionValue)
group by SectionValue
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query
= 'select category, '+#colspivot+'
from
(
select
category,
value
from yourtable
unpivot
(
value
for cols in (Section1, Section2, Section3)
) un
) x
pivot
(
count(value)
for value in ('+ #colspivot +')
) p'
exec sp_executesql #query
See SQL Fiddle with Demo
If you have an unknown number of columns to unpivot, then your process will be a bit more complicated. You'll need to generate a string with the columns to unpivot, you can use the sys.columns table to get this list:
select #colsUnpivot
= stuff((select ','+quotename(C.name)
from sys.columns as C
where C.object_id = object_id('yourtable') and
C.name like 'Section%'
for xml path('')), 1, 1, '')
Then you'll need to get a list of the new column values - but since these are dynamic we will need to generate this list with a bit of work. You'll need to unpivot the table to generate the list of values into a temporary table for use. Create a temp table to store the values:
create table #Category_Section
(
Category varchar(50),
SectionValue varchar(50)
);
Load the temp table with the data that you need to unpivot:
set #unpivotquery
= 'select
category,
value
from yourtable
unpivot
(
value for cols in ('+ #colsUnpivot +')
) u'
insert into #Category_Section exec(#unpivotquery);
See SQL Fiddle with Demo. You'll see that your data looks the same as the static version above. Now you need to create a string with the values from the temp table that will be used in the final query:
select #colsPivot
= STUFF((SELECT ',' + quotename(SectionValue)
from #Category_Section
group by SectionValue
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
Once you have all this you can put it together into a final query:
DECLARE #colsUnpivot AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#colsPivot as NVARCHAR(MAX),
#unpivotquery 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 like 'Section%'
for xml path('')), 1, 1, '');
create table #Category_Section
(
Category varchar(50),
SectionValue varchar(50)
);
set #unpivotquery
= 'select
category,
value
from yourtable
unpivot
(
value for cols in ('+ #colsUnpivot +')
) u';
insert into #Category_Section exec(#unpivotquery);
select #colsPivot
= STUFF((SELECT ',' + quotename(SectionValue)
from #Category_Section
group by SectionValue
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query
= 'select category, '+#colspivot+'
from
(
select
category,
value
from yourtable
unpivot
(
value
for cols in ('+ #colsunpivot +')
) un
) x
pivot
(
count(value)
for value in ('+ #colspivot +')
) p'
exec sp_executesql #query
See SQL Fiddle with Demo. All versions will get you the end result:
| CATEGORY | BIG | DAMAGED | GREEN | WHITE |
|------------|-----|---------|-------|-------|
| Category 1 | 2 | 0 | 2 | 2 |
| Category 2 | 1 | 1 | 0 | 1 |
| Category 3 | 1 | 1 | 0 | 1 |
If your values are stored in a separate table, then you would generate your list of values from that table:
DECLARE #query AS NVARCHAR(MAX),
#colsPivot as NVARCHAR(MAX);
select #colsPivot
= STUFF((SELECT ',' + quotename(decription)
from descriptions
group by decription
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query
= 'select category, '+#colspivot+'
from
(
select
category,
value
from yourtable
unpivot
(
value
for cols in (Section1, Section2, Section3)
) un
) x
pivot
(
count(value)
for value in ('+ #colspivot +')
) p'
exec sp_executesql #query
See SQL Fiddle with Demo and still get the same result:
| CATEGORY | BIG | DAMAGED | GREEN | WHITE |
|------------|-----|---------|-------|-------|
| Category 1 | 2 | 0 | 2 | 2 |
| Category 2 | 1 | 1 | 0 | 1 |
| Category 3 | 1 | 1 | 0 | 1 |
select category,
SUM(CASE when section1='white' then 1 when section2='white' then 1 when section3='white' then 1 else 0 end) as white,
SUM(CASE when section1='green' then 1 when section2='green' then 1 when section3='green' then 1 else 0 end) as green,
SUM(CASE when section1='damaged' then 1 when section2='damaged' then 1 when section3='damaged' then 1 else 0 end) as damaged,
SUM(CASE when section1='big' then 1 when section2='big' then 1 when section3='big' then 1 else 0 end) as big
from test
group by category
SQLFiddle
You can extend more to n section values as shown above gor section1,section2,section3
I am trying to find a query that can generate cross-tab output at multiple levels dynamically. I did find few solutions online that returns dynamic cross-tab results but it returns only at single level. Below is the SQL fiddle:
CREATE TABLE dbo.PopulationDetails
(
Country VARCHAR(50),
State VARCHAR(50),
Population BIGINT,
SeatsInHouse INT
)
INSERT INTO PopulationDetails
VALUES('United States','California', 38332521, 53),
('United States','Texas', 26448193, 36),
('United States','New York', 19651127, 27),
('United States','Florida', 19552860, 27),
('United States','Illinois', 12882135, 18)
I want my output should look like below. The number of states are not fixed and these may vary as per the requirement.
United States
California Texas New York Florida Illinois
Population 38332521 26448193 19651127 19552860 12882135
SeatsInHouse 53 36 27 27 18
As I said in my comment, multi-level column headers can't be done via SQL. You'd have to format the data in a presentation layer like your application or SSRS. If you want to get the country and state values "together", then you'd have to concatenate the names together and make that your new column names.
If you want to get the result in SQL, I'd start by concatenating the country and state, and unpivot the columns population and SeatsInHouse first. The basic syntax for this process would be:
select
country_state = replace(pd.Country +'_'+pd.State, ' ', ''),
c.col,
c.value
from dbo.PopulationDetails pd
cross apply
(
values
('Population', pd.population),
('SeatsInHouse', pd.SeatsInHouse)
) c (col, value);
See SQL Fiddle with Demo. This gives a result:
| COUNTRY_STATE | COL | VALUE |
|-------------------------|--------------|----------|
| UnitedStates_California | Population | 38332521 |
| UnitedStates_California | SeatsInHouse | 53 |
| UnitedStates_Texas | Population | 26448193 |
| UnitedStates_Texas | SeatsInHouse | 36 |
| UnitedStates_NewYork | Population | 19651127 |
| UnitedStates_NewYork | SeatsInHouse | 27 |
You'll see that you now have two rows for each Country_State combination. You can now pivot those Country_State values into columns:
select col, UnitedStates_California, UnitedStates_Texas,
UnitedStates_NewYork, UnitedStates_Florida,
UnitedStates_Illinois
from
(
select
country_state = replace(pd.Country +'_'+pd.State, ' ', ''),
c.col,
c.value
from dbo.PopulationDetails pd
cross apply
(
values
('Population', pd.population),
('SeatsInHouse', pd.SeatsInHouse)
) c (col, value)
) d
pivot
(
max(value)
for country_state in (UnitedStates_California, UnitedStates_Texas,
UnitedStates_NewYork, UnitedStates_Florida,
UnitedStates_Illinois)
) piv;
See SQL Fiddle with Demo.
Now, if you need this done dynamically then you'd have to use dynamic SQL which creates a string that is then executed.
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(country_state)
from
(
select country_state = replace(Country +'_'+State, ' ', '')
from dbo.PopulationDetails
) d
group by country_state
order by country_state
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT col, ' + #cols + '
from
(
select
country_state = replace(pd.Country +''_''+pd.State, '' '', ''''),
c.col,
c.value
from dbo.PopulationDetails pd
cross apply
(
values
(''Population'', pd.population),
(''SeatsInHouse'', pd.SeatsInHouse)
) c (col, value)
) x
pivot
(
max(value)
for country_state in (' + #cols + ')
) p '
exec sp_executesql #query;
See SQL Fiddle with Demo. Both gives a result:
| COL | UNITEDSTATES_CALIFORNIA | UNITEDSTATES_FLORIDA | UNITEDSTATES_ILLINOIS | UNITEDSTATES_NEWYORK | UNITEDSTATES_TEXAS |
|--------------|-------------------------|----------------------|-----------------------|----------------------|--------------------|
| Population | 38332521 | 19552860 | 12882135 | 19651127 | 26448193 |
| SeatsInHouse | 53 | 27 | 18 | 27 | 36 |
Please find the table (OututTable) that needs to be transposed. Here the QuestionID is formed by concatenating two values -[Question:AnswerID]
refID | SessionID | QuestionID | AnswerValue
9000 | 205545715 | [4907] | Good morning
12251 | 205543469 | [10576:16307] | 3
12255 | 205543469 | [10907:17001] | 4
13157 | 205543703 | [10576:16307] | 3
14387 | 205543493 | [10907:17001] | 2
14389 | 205543493 | [10911:17007] | 3
The expected output should have one row per SessionID and the number of columns are dynamic
SessionID | [4097] | [10576:16307] | [10907:17001] | [10911:17007]
205545715 |Good morning | | |
205543469 | | 3 | 4 |
205543703 | | 3 | |
205543493 | | | 2 | 3
I have the output in the above format but there are only NULL values inserted instead of Answer values
I am thinking there might a mismatch in column names. Any help would be great! please let me know.
Code:
set #Questions = (STUFF((SELECT distinct ',[' + cast(i.SessionID as varchar(20)) + ']'
FROM OutputTable i
FOR XML PATH(''), TYPE).value('.','VARCHAR(max)'), 1, 1, ''))
print #Questions
set #SQLQuery = 'select QuestionID,'+ #Questions +' from '+'('+ 'select SessionID,QuestionID,AnswerValue from OutputTable '+ ') p '+ 'PIVOT'+ '('+'max(Answervalue)'+'FOR p.SessionID IN ('+ #Questions +')' +') as pvt'
Great Question! The problem is with the brackets in the QuestionID. While these are necessary for the Pivot Column Aliases, these don't work as string filters.
The code sample also switches QuestionID and SessionID for the expected output.
This code will return the expected output, sorted slightly differently. A temp table is created here to simulate the OutputTable object. This will need to be switched out with the DB Table.
declare
#Questions varchar(max),
#SQLQuery varchar(max)
create table #OutputTable
(
refID int,
SessionID int,
QuestionID varchar(50),
AnswerValue varchar(50)
)
insert into #OutputTable
values
(9000,205545715,'[4907]','Good morning'),
(12251,205543469,'[10576:16307]','3'),
(12255,205543469,'[10907:17001]','4'),
(13157,205543703,'[10576:16307]','3'),
(14387,205543493,'[10907:17001]','2'),
(14389,205543493,'[10911:17007]','3')
set #Questions = (STUFF((SELECT distinct ',' + cast(i.QuestionID as varchar(20))
FROM #OutputTable i
FOR XML PATH(''), TYPE).value('.','VARCHAR(max)'), 1, 1, ''))
set #SQLQuery = '
select SessionID,'+ #Questions +'
from (
select
SessionID,
replace(replace(QuestionID,''['',''''),'']'','''') QuestionID,
AnswerValue
from #OutputTable
) p
PIVOT (
max(Answervalue)
FOR p.QuestionID IN ('+ #Questions +')
) as pvt
order by SessionID desc'
exec(#SQLQuery)
I have a question and this looks way better in SQLfiddle:
http://www.sqlfiddle.com/#!3/dffa1/2
I have a table with multirows for each user with datestamp and test results and i would like to transpose or pivot it into one line result as follows where each user has listed all time and value results:
USERID PSA1_time PSA1_result PSA2_time PSA2_result PSA3_time PSA3_result ...
1 1999-.... 2 1998... 4 1999... 6
3 1992... 4 1994 6
4 2006 ... 8
Table below:
CREATE TABLE yourtable
([userid] int, [Ranking] int,[test] varchar(3), [Date] datetime, [result] int)
;
INSERT INTO yourtable
([userid], [Ranking],[test], [Date], [result])
VALUES
('1', '1', 'PSA', 1997-05-20, 2),
('1', '2','PSA', 1998-05-07, 4),
('1', '3','PSA', 1999-06-08, 6),
('1', '4','PSA', 2001-06-08, 8),
('1', '5','PSA', 2004-06-08, 0),
('3', '1','PSA', 1992-05-07, 4),
('3', '2','PSA', 1994-06-08, 6),
('4', '1','PSA', 2006-06-08, 8)
;
Since you want to PIVOT two columns my suggestion would be to unpivot the date and result columns first, then apply the PIVOT function.
The unpivot process will convert the two columns date and result into multiple rows:
select userid,
col = test +'_'+cast(ranking as varchar(10))+'_'+ col,
value
from yourtable t1
cross apply
(
select 'time', convert(varchar(10), date, 120) union all
select 'result', cast(result as varchar(10))
) c (col, value)
See Demo. This will give you a result:
| USERID | COL | VALUE |
--------------------------------------
| 1 | PSA_1_time | 1997-05-20 |
| 1 | PSA_1_result | 2 |
| 1 | PSA_2_time | 1998-05-07 |
| 1 | PSA_2_result | 4 |
| 1 | PSA_3_time | 1999-06-08 |
Now that you have the data in this format, then you can apply pivot to get the max/min value for each item in col:
If you have a limited number of columns, then you can hard-code the query:
select *
from
(
select userid,
col = test +'_'+cast(ranking as varchar(10))+'_'+ col,
value
from yourtable t1
cross apply
(
select 'time', convert(varchar(10), date, 120) union all
select 'result', cast(result as varchar(10))
) c (col, value)
) d
pivot
(
max(value)
for col in (PSA_1_time, PSA_1_result,
PSA_2_time, PSA_2_result,
PSA_3_time, PSA_3_result,
PSA_4_time, PSA_4_result,
PSA_5_time, PSA_5_result)
) piv;
See SQL Fiddle with Demo
If you have unknown columns, then you will need to use dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(test +'_'+cast(ranking as varchar(10))+'_'+ col)
from yourtable
cross apply
(
select 'time', 1 union all
select 'result', 2
) c (col, so)
group by test, ranking, col, so
order by Ranking, so
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT userid,' + #cols + '
from
(
select userid,
col = test +''_''+cast(ranking as varchar(10))+''_''+ col,
value
from yourtable t1
cross apply
(
select ''time'', convert(varchar(10), date, 120) union all
select ''result'', cast(result as varchar(10))
) c (col, value)
) x
pivot
(
max(value)
for col in (' + #cols + ')
) p '
execute sp_executesql #query;
See SQL Fiddle with Demo. Both versions will give a result:
| USERID | PSA_1_TIME | PSA_1_RESULT | PSA_2_TIME | PSA_2_RESULT | PSA_3_TIME | PSA_3_RESULT | PSA_4_TIME | PSA_4_RESULT | PSA_5_TIME | PSA_5_RESULT |
------------------------------------------------------------------------------------------------------------------------------------------------------
| 1 | 1997-05-20 | 2 | 1998-05-07 | 4 | 1999-06-08 | 6 | 2001-06-08 | 8 | 2004-06-08 | 0 |
| 3 | 1992-05-07 | 4 | 1994-06-08 | 6 | (null) | (null) | (null) | (null) | (null) | (null) |
| 4 | 2006-06-08 | 8 | (null) | (null) | (null) | (null) | (null) | (null) | (null) | (null) |