Pivot multiple rows in SQL Sever - sql

I have data like below:
CREATE TABLE #Temp
(RuleName VARCHAR(100),
ParamValue VARCHAR(100),
ParamName VARCHAR(100))
INSERT INTO #Temp
VALUES ('BroadBandAviva','BroadBandAviva1','Rule Name'),
('BroadBandAviva','BroadBand','Expense Category name'),
('BroadBandAviva','DSF','org unit name'),
('BroadBandAviva','ASSISTANT SALES MANAGER','designation'),
('BroadBandAviva','Category A','cityclassification'),
('BroadBandAviva','2000','Eligible Amount'),
('BroadBandAviva','BroadBandAviva2','Rule Name'),
('BroadBandAviva','BroadBand','Expense Category name'),
('BroadBandAviva','DSF','org unit name'),
('BroadBandAviva','ASSISTANT SALES MANAGER','designation'),
('BroadBandAviva','ROI-Rest of India','cityclassification'),
('BroadBandAviva','2000','Eligible Amount')
and I want the output like this:
RuleName Col1 Col2 Col3 Col4 Col5 Col6
---------------------------------------------------------------------------------
BroadBandAviva Rule Name Expense Category name org unit name designation cityclassification Eligible Amount
BroadBandAviva BroadBandAviva1 BroadBand DSF ASSISTANT SALES MANAGER Category A 2000
BroadBandAviva BroadBandAviva2 BroadBand DSF ASSISTANT SALES MANAGER ROI-Rest of India 2000
Basically the first column should stay as it is, third column should become the first row and the second column should come in second row onwards. I tried with pivot function but I could not get the desired output.

Here is an alternative approach which places the values into separate columns, like this:
| RULENAME | GRPNO | COL1 | COL2 | COL3 | COL4 | COL5 | COL6 |
|----------------|-------|-----------------|-----------------------|---------------|-------------------------|---------------------|-----------------|
| BroadBandAviva | -1 | Rule Name | Expense Category name | org unit name | designation | cityclassification | Eligible Amount |
| BroadBandAviva | 0 | BroadBandAviva1 | BroadBand | DSF | ASSISTANT SALES MANAGER | Category A | 2000 |
| BroadBandAviva | 1 | BroadBandAviva2 | BroadBand | DSF | ASSISTANT SALES MANAGER | ROI-Rest of India | 2000 |
Like my previous attempt I use a CTE to retain some needed calculations that are reused in a subsequent union. Unlike the previous attempt though I use conventional case expressions and group by to achieve the pivot. Here is the query.
declare #i int = (select count(distinct ParamName) from Temp)
;with cte as (
select
*
, row_number() over(order by (select 1)) as rn
, (row_number() over(order by (select 1))-1) / #i as grpno
, row_number() over(order by (select 1)) % #i as prow
from temp
)
select
rulename
, -1 as grpno
, max(case when prow = 1 then paramname end) as col1
, max(case when prow = 2 then paramname end) as col2
, max(case when prow = 3 then paramname end) as col3
, max(case when prow = 4 then paramname end) as col4
, max(case when prow = 5 then paramname end) as col5
, max(case when prow = 0 then paramname end) as col6
from cte
WHERE rn <= #i
group by
rulename
union all
select
rulename
, grpno
, max(case when prow = 1 then paramvalue end) as col1
, max(case when prow = 2 then paramvalue end) as col2
, max(case when prow = 3 then paramvalue end) as col3
, max(case when prow = 4 then paramvalue end) as col4
, max(case when prow = 5 then paramvalue end) as col5
, max(case when prow = 0 then paramvalue end) as col6
from cte
group by
rulename
, grpno
;
To follow the logic it is useful to understand the columns PROW (parameter row) and GRPNO (grouping number), here is the CTE contents showing those columns:
/*
| RULENAME | PARAMVALUE | PARAMNAME | RN | GRPNO | PROW |
|----------------|-------------------------|-----------------------|----|-------|------|
| BroadBandAviva | BroadBandAviva1 | Rule Name | 1 | 0 | 1 |
| BroadBandAviva | BroadBand | Expense Category name | 2 | 0 | 2 |
| BroadBandAviva | DSF | org unit name | 3 | 0 | 3 |
| BroadBandAviva | ASSISTANT SALES MANAGER | designation | 4 | 0 | 4 |
| BroadBandAviva | Category A | cityclassification | 5 | 0 | 5 |
| BroadBandAviva | 2000 | Eligible Amount | 6 | 0 | 0 |
| BroadBandAviva | BroadBandAviva2 | Rule Name | 7 | 1 | 1 |
| BroadBandAviva | BroadBand | Expense Category name | 8 | 1 | 2 |
| BroadBandAviva | DSF | org unit name | 9 | 1 | 3 |
| BroadBandAviva | ASSISTANT SALES MANAGER | designation | 10 | 1 | 4 |
| BroadBandAviva | ROI-Rest of India | cityclassification | 11 | 1 | 5 |
| BroadBandAviva | 2000 | Eligible Amount | 12 | 1 | 0 |
*/
see the SQLFiddle Demo

This is as close as I can get, maybe others can use it? note the 2000 below is in the wrong place and I cannot see why or how.
| RULENAME | PARAMS |
|----------------|----------------------------------------------------------------------------------------------------|
| BroadBandAviva | Rule Name, Expense Category name, org unit name, designation, cityclassification, Eligible Amount |
| BroadBandAviva | BroadBandAviva1, BroadBand, DSF, ASSISTANT SALES MANAGER, Category A |
| BroadBandAviva | 2000, BroadBandAviva2, BroadBand, DSF, ASSISTANT SALES MANAGER, ROI-Rest of India |
^^^^^
Here's the query I used:
declare #i int = (select count(distinct ParamName) from Temp)
;with cte as (
select
*
, row_number() over(order by (select 1)) as rn
, row_number() over(order by (select 1)) / #i as grpno
, row_number() over(order by (select 1)) % #i as prow
from temp
)
SELECT
(select RuleName from cte where rn = 1) as RULENAME
, STUFF((
SELECT
', ' + c2.ParamName
FROM CTE as c2
WHERE rn <= #i
order by c2.rn
FOR XML PATH ('')
)
, 1, 1, '') as PARAMS
--, -1, -1, -1
UNION ALL
select
cte.RuleName
, caxml.params
--, cte.grpno, prow, rn
from cte
CROSS APPLY (
SELECT
STUFF((
SELECT
', ' + c2.ParamValue
FROM CTE as c2
WHERE c2.grpno = cte.grpno
order by case when c2.prow = #i then #i else c2.prow end
FOR XML PATH ('')
)
, 1, 1, '')
) caxml (params)
where prow = 1
;
see SQLFiddle Demo for more

Related

Single query to split out data of one column, into two columns, from the same table based on different criteria [SQL]

I have the following data in a table, this is a single column shown from a table that has multiple columns, but only data from this column needs to be pulled into two column output using a query:
+----------------+--+
| DataText | |
| 1 DEC20 DDD | |
| 1 JUL20 DDD | |
| 1 JAN21 DDD | |
| 1 JUN20 DDD500 | |
| 1 JUN20 DDD500 | |
| 1 JUN20DDDD500 | |
| 1 JUN20DDDD500 | |
| 1 JUL20 DDD800 | |
| 1 JUL20 DDD800 | |
| 1 JUL20DDDD800 | |
| 1 JUL20DDDD400 | |
| 1 JUL20DDDD400 | |
+----------------+--+
Required result: distinct values based on the first 13 characters of the data, split into two columns based on "long data", and "short data", BUT only giving the first 13 characters in output for both columns:
+-------------+-------------+
| ShortData | LongData |
| 1 DEC20 DDD | 1 JUN20 DDD |
| 1 JUL20 DDD | 1 JUN20DDDD |
| 1 JAN21 DDD | 1 JUL20 DDD |
| | 1 JUL20DDDD |
+-------------+-------------+
Something like:
Select
(Select DISTINCT LEFT(DataText,13)
From myTable)
Where LEN(DataText)=13) As ShortData
,
(Select DISTINCT LEFT(DataText,13)
From myTable)
Where LEN(DataText)>13) As LongData
I would also like to query/"scan" the table only once if possible. I can't get any of the SO examples modified to make such a query work.
This is quite ugly, but doable. As a starter, you need a column that defines the order of the rows - I assumed that you have such a column, and that is called id.
Then you can select the distinct texts, put them in separate groups depending on their length, and finally pivot:
select
max(case when grp = 0 then dataText end) shortData,
max(case when grp = 1 then dataText end) longData
from (
select
dataText,
grp,
row_number() over(partition by grp order by id) rn
from (
select
id,
case when len(dataText) <= 13 then 0 else 1 end grp,
substring(dataText, 1, 13) dataText
from (select min(id) id, dataText from mytable group by dataText) t
) t
) t
group by rn
If you are content with ordering the records by the string column itself, it is a bit simpler (and, for your sample data, it produces the same results):
select
max(case when grp = 0 then dataText end) shortData,
max(case when grp = 1 then dataText end) longData
from (
select
dataText,
grp,
row_number() over(partition by grp order by dataText) rn
from (
select distinct
case when len(dataText) <= 13 then 0 else 1 end grp,
substring(dataText, 1, 13) dataText
from mytable
) t
) t
group by rn
Demo on DB Fiddle:
shortData | longData
:---------- | :------------
1 DEC20 DDD | 1 JUL20 DDD80
1 JAN21 DDD | 1 JUL20DDDD40
1 JUL20 DDD | 1 JUL20DDDD80
null | 1 JUN20 DDD50
null | 1 JUN20DDDD50

SQL - self join 'n' times with condition

I have the following table:
Id | Type
--------------------------------------------
C1C1A90D-B131-4450-B1BF-5041F36F9144 | 1
C7B1752D-FD30-445A-AD6C-51D1434607D3 | 2
3AAF8BB6-A6D4-4780-BEF9-ACBBF75A85DE | 3
--------------------------------------------
67EF1537-A22E-4D2D-AAEA-FC0D9E2B9912 | 1
546519ED-5E78-4DAD-ADFF-9DC0AA67B763 | 2
8F66A3F9-C652-4758-8E17-B4DE0B0D85A4 | 3
--------------------------------------------
-- ... and so on ... --
Now, I need a specific type of SELECT (something like this):
SELECT
[Id] AS [OneId] -- Where [Type] = 1,
[Id] AS [TwoId] -- Where [Type] = 2,
[Id] AS [ThreeId] -- Where [Type] = 3
FROM Table
This is what I've tried so far but with bad results:
SELECT
oneI.[Id] AS [OneId] -- Where [Type] = 1,
twoI.[Id] AS [TwoId] -- Where [Type] = 2,
threeI.[Id] AS [ThreeId] -- Where [Type] = 3
FROM Table AS i
INNER JOIN Table AS oneI ON
i.[Id] = oneI.[Id]
INNER JOIN Table AS twoI ON
i.[Id] = twoI.[Id]
INNER JOIN Table AS threeI ON
i.[Id] = threeI.[Id]
WHERE
oneI.[Type] = 1
AND twoI.[Type] = 2
AND threeI.[Type] = 3
Or even worse (which gave me more lots of combinations):
SELECT
oneI.[Id] AS [OneId] -- Where [Type] = 1,
twoI.[Id] AS [TwoId] -- Where [Type] = 2,
threeI.[Id] AS [ThreeId] -- Where [Type] = 3
FROM Table AS i, TABLE AS oneI, Table AS twoI, Table AS threeI
WHERE
oneI.[Type] = 1
AND twoI.[Type] = 2
AND threeI.[Type] = 3
EDIT:
I would also have another column on which I would group those Ids. So the updated table look like:
Id | GroupId | Type
------------------------------------------------------------------
C1C1A90D-B131-4450-B1BF-5041F36F9144 -- OneId | 1 | 1
C7B1752D-FD30-445A-AD6C-51D1434607D3 -- TwoId | 1 | 2
3AAF8BB6-A6D4-4780-BEF9-ACBBF75A85DE -- ThreeId | 1 | 3
------------------------------------------------------------------
67EF1537-A22E-4D2D-AAEA-FC0D9E2B9912 -- OneId | 2 | 1
546519ED-5E78-4DAD-ADFF-9DC0AA67B763 -- TwoId | 2 | 2
8F66A3F9-C652-4758-8E17-B4DE0B0D85A4 -- ThreeId | 2 | 3
----------------------------------------------------------------
So, I would need the following result:
OneI | TwoI | ThreeI | GroupId
------------------------------------------------------
OneId | TwoId | ThreeId | 1
OneId | TwoId | ThreeId | 2
EDIT:
I have one more special case - [Type] column can repeat:
Id | GroupId | Type
-----------------------------------------------------------------------
C1C1A90D-B131-4450-B1BF-5041F36F9144 -- OneId | 1 | 1
C7B1752D-FD30-445A-AD6C-51D1434607D3 -- TwoId | 1 | 2
3AAF8BB6-A6D4-4780-BEF9-ACBBF75A85DE -- ThreeId | 1 | 3
FEB4A345-FEA0-4530-AE52-6CF4F07E37BA -- OtherThreeId | 1 | 3
-----------------------------------------------------------------------
67EF1537-A22E-4D2D-AAEA-FC0D9E2B9912 -- OneId | 2 | 1
546519ED-5E78-4DAD-ADFF-9DC0AA67B763 -- TwoId | 2 | 2
8F66A3F9-C652-4758-8E17-B4DE0B0D85A4 -- ThreeId | 2 | 3
----------------------------------------------------------------
And now the result would be:
OneI | TwoI | ThreeI | GroupId
------------------------------------------------------
OneId | TwoId | ThreeId | 1
OneId | TwoId | OtherThreeId | 1
OneId | TwoId | ThreeId | 2
sqlfiddle
Well, this would required some sequential ordering columns, but you could also express this as
select max(case when [Type] = 1 then Id end) OneId,
max(case when [Type] = 2 then Id end) TwoId,
max(case when [Type] = 3 then Id end) ThreeId
from (select *,
row_number() over (order by (select 1)) Seq
from table
) t
group by (Seq - [Type]);
EDIT :- However, if you want to include group also then use them as in select statement as
select (Seq - [Type]) as GroupId,
max(case when [Type] = 1 then 'OneId' end) OneI,
max(case when [Type] = 2 then 'TwoId' end) TwoI,
max(case when [Type] = 3 then 'ThreeId' end) ThreeI
from (select *,
row_number() over (order by (select 1)) Seq
from table
) t
group by (Seq - [Type]);
For your updated table you can directly use table with group by clause with your GroupId column as then you don't use row_number() function and subquery
select max(case when [Type] = 1 then 'OneId' end) OneI,
max(case when [Type] = 2 then 'TwoId' end) TwoI,
max(case when [Type] = 3 then 'ThreeId' end) ThreeI,
GroupId
from table t
group by GroupId;
Demo
You need an Grp attribute then specifies which rows are together in one group. Something like this
Id | Type | Grp
----------------------------------------------------
C1C1A90D-B131-4450-B1BF-5041F36F9144 | 1 | 1
C7B1752D-FD30-445A-AD6C-51D1434607D3 | 2 | 1
3AAF8BB6-A6D4-4780-BEF9-ACBBF75A85DE | 3 | 1
---------------------------------------------------
67EF1537-A22E-4D2D-AAEA-FC0D9E2B9912 | 1 | 2
546519ED-5E78-4DAD-ADFF-9DC0AA67B763 | 2 | 2
8F66A3F9-C652-4758-8E17-B4DE0B0D85A4 | 3 | 2
--------------------------------------------------
Then you can use conditional aggregation like this
SELECT
MAX(CASE WHEN [Type] = 1 THEN [Id] END) AS [OneId],
MAX(CASE WHEN [Type] = 2 THEN [Id] END) AS [TwoId],
MAX(CASE WHEN [Type] = 3 THEN [Id] END) AS [ThreeId]
FROM YourTable
GROUP BY Grp
Looks like this would do the job for you
; WITH CTE
AS
(
SELECT
Id,
[Type]
FROM YourTable
WHERE [Type]
IN
(
1,2,3,4,5
)
)
SELECT
*
FROM CTE
PIVOT
(
MAX(id)
FOR [Type] IN
(
[1],[2],[3],[4],[5]
)
)Pvt

Query to generate string based on values in multiple columns

I have a table in database that has structure and data as follows:
+-----+--------+--------+--------+--------+--------+
| ID | Col1 | Col2 | Col3 | Col4 | Col5 |
+-----+--------+--------+--------+--------+--------+
| 1 | MALE | MALE | FEMALE | NULL | NULL |
| 2 | FEMALE | MALE | NULL | NULL | NULL |
| 3 | FEMALE | NULL | NULL | NULL | NULL |
| 4 | MALE | OTHER | FEMALE | FEMALE | NULL |
| 5 | MALE | OTHER | FEMALE | MALE | FEMALE |
+-----+--------+--------+--------+--------+--------+
The order of data has to be in order of first appearance in the columns, from Col1 to Col5, to get the following output:
+-----+--------------------------------------------+
| ID | Remarks |
+-----+--------------------------------------------+
| 1 | 2 Male and 1 Female |
| 2 | 1 Female and 1 Male |
| 3 | 1 Female |
| 4 | 1 Male, 1 Other and 2 Female |
| 5 | 2 Male, 1 Other and 2 Female |
+-----+--------------------------------------------+
I would do this use apply.
select t.*,
(case when num_male = 0 and num_female = 0 and num_other = 0
then ''
when num_male = 0 and num_female = 0
then replace('num_other OTHER', 'num_other', num_other)
when num_male = 0 and num_other = 0
then replace('num_female FEMALE', 'num_female', num_female)
when num_male = 0 and num_female = 0
then replace('num_male MALE', 'num_male', num_male)
when num_male = 0
then replace(replace(replace('num_other OTHER AND num_female FEMALE'), 'num_male', num_male), 'num_other', num_other), 'num_female', num_female)
when num_other = 0
then replace(replace(replace('num_male MALE AND num_female FEMALE), 'num_male', num_male), 'num_other', num_other), 'num_female', num_female)
when num_female = 0
then replace(replace(replace('num_male MALE AND num_other OTHER), 'num_male', num_male), 'num_other', num_other), 'num_female', num_female)
else replace(replace(replace('num_male MALE, num_other OTHER AND num_female FEMALE), 'num_male', num_male), 'num_other', num_other), 'num_female', num_female)
end) as remarks
from t cross apply
(select sum(case when col = 'FEMALE' then 1 else 0 end) as num_females,
sum(case when col = 'MALE' then 1 else 0 end) as num_males,
sum(case when col = 'OTHER' then 1 else 0 end) as num_other
from (values (col1), (col2), (col3), (col4), (col5)) v(col)
) v;
I don't see an advantage to cleverness in calculating the remarks structure.
This is a very unusual requirement and I would imagine that you don't really need to have the values in the order you are asking for, but to answer the question as asked:
-- Build data
declare #t table(ID int,Col1 varchar(10),Col2 varchar(10),Col3 varchar(10),Col4 varchar(10),Col5 varchar(10));
insert into #t values
(1,'MALE','MALE','FEMALE',null,null)
,(2,'FEMALE','MALE',null,null,null)
,(3,'FEMALE',null,null,null,null)
,(4,'MALE','OTHER','FEMALE','FEMALE',null)
,(5,'MALE','OTHER','FEMALE','MALE','FEMALE')
;
-- Query
with d as
( -- Manually unpivot the 5 Cols and add a row number
select 1 as r
,ID
,Col1 as Col
from #t
union all
select 2
,ID
,Col2 as Col
from #t
union all
select 3
,ID
,Col3 as Col
from #t
union all
select 4
,ID
,Col4 as Col
from #t
union all
select 5
,ID
,Col5 as Col
from #t
)
,c as
( -- Replace Col values for presentation and add in aggregated row numbers, ranks and counts for each value
select ID
,case Col when 'MALE' then 'Male'
when 'FEMALE' then 'Female'
when 'OTHER' then 'Other'
end as Col
,row_number() over (partition by ID order by r) as rn
,dense_rank() over (partition by ID order by r) as drk
,rank() over (partition by ID, Col order by r) as rk
,count(Col) over (partition by ID, Col) as c
from d
where Col is not null
)
,o as
( -- Filter rows down to just the first instance of that Col value to get the presentation order
select ID
,row_number() over (partition by ID order by rn) as o
,cast(c as varchar(10)) + ' ' + Col as c
from c
where rk = 1
)
-- Collate the data per presentation rules
select o.ID
,o.c
+ isnull(case when o3.ID is null
then ' and ' + o2.c
else ', ' + o2.c + ' and ' + o3.c
end
,'') as Remarks
from o
left join o as o2
on o.ID = o2.ID
and o2.o = 2
left join o as o3
on o.ID = o3.ID
and o3.o = 3
where o.o = 1
order by o.ID;
Output:
+----+------------------------------+
| ID | Remarks |
+----+------------------------------+
| 1 | 2 Male and 1 Female |
| 2 | 1 Female and 1 Male |
| 3 | 1 Female |
| 4 | 1 Male, 1 Other and 2 Female |
| 5 | 2 Male, 1 Other and 2 Female |
+----+------------------------------+

Aggregation for multiple SQL SELECT statements

I've got a table TABLE1 like this:
|--------------|--------------|--------------|
| POS | TYPE | VOLUME |
|--------------|--------------|--------------|
| 1 | A | 34 |
| 2 | A | 2 |
| 1 | A | 12 |
| 3 | B | 200 |
| 4 | C | 1 |
|--------------|--------------|--------------|
I want to get something like this (TABLE2):
|--------------|--------------|--------------|--------------|--------------|
| POS | Amount_A | Amount_B | Amount_C | Sum_Volume |
|--------------|--------------|--------------|--------------|--------------|
| 1 | 2 | 0 | 0 | 46 |
| 2 | 1 | 0 | 0 | 2 |
| 3 | 0 | 1 | 0 | 200 |
| 4 | 0 | 0 | 1 | 1 |
|--------------|--------------|--------------|--------------|--------------|
My Code so far is:
SELECT
(SELECT COUNT(TYPE)
FROM TABLE1
WHERE TYPE = 'A') AS [Amount_A]
,(SELECT COUNT(TYPE)
FROM TABLE1
WHERE TYPE = 'B') AS [Amount_B]
,(SELECT COUNT(TYPE)
FROM TABLE1
WHERE TYPE = 'C') AS [Amount_C]
,(SELECT SUM(VOLUME)
FROM TABLE AS [Sum_Volume]
INTO [TABLE2]
Now two Questions:
How can I include the distinction concerning POS?
Is there any better way to count each TYPE?
I am using MSSQLServer.
What you're looking for is to use GROUP BY, along with your Aggregate functions. So, this results in:
USE Sandbox;
GO
CREATE TABLE Table1 (Pos tinyint, [Type] char(1), Volume smallint);
INSERT INTO Table1
VALUES (1,'A',34 ),
(2,'A',2 ),
(1,'A',12 ),
(3,'B',200),
(4,'C',1 );
GO
SELECT Pos,
COUNT(CASE WHEN [Type] = 'A' THEN [Type] END) AS Amount_A,
COUNT(CASE WHEN [Type] = 'B' THEN [Type] END) AS Amount_B,
COUNT(CASE WHEN [Type] = 'C' THEN [Type] END) AS Amount_C,
SUM(Volume) As Sum_Volume
FROM Table1 T1
GROUP BY Pos;
DROP TABLE Table1;
GO
if you have a variable, and undefined, number of values for [Type], then you're most likely going to need to use Dynamic SQL.
your first column should be POS, and you'll GROUP BY POS.
This will give you one row for each POS value, and aggregate (COUNT and SUM) accordingly.
You can also use CASE statements instead of subselects. For instance, instead of:
(SELECT COUNT(TYPE)
FROM TABLE1
WHERE TYPE = 'A') AS [Amount_A]
use:
COUNT(CASE WHEN TYPE = 'A' then 1 else NULL END) AS [Amount_A]

Generate three rows in select query

I have following table
SELECT TableCode, Col1, Col2
FROM TableA
WHERE TableCode = 23
Result of Table:
TableCode | Col1 | Col1
23 | CustCode | QS
23 | CatCode | QS
After that i wrote one query on TableA which return following output
Query :
SELECT TableCode,x.ColCode,
x.ColumnName + '_' + CONVERT(VARCHAR(5), ROW_NUMBER() OVER (PARTITION BY X.COL ORDER BY X.COL)) [ColumnName],X.Values,
ROW_NUMBER() OVER (PARTITION BY X.COL ORDER BY X.COL) [RowNo]
FROM TableA a CROSS APPLY
(SELECT 1 ColCode,'ParaName' ColumnName,Col1 Values
UNION ALL
SELECT 2,'ParaSource',Col2
) x
WHERE TableCode = 23;
Result :
TableCode | ColCode | ColumnName | Values | RowNo
23 | 1 | ParaName_1 | CustCode | 1
23 | 1 | ParaName_2 | CatCode | 2
23 | 2 | ParaSource_1 | QS | 1
23 | 2 | ParaSource_2 | QS | 2
And i required following output:
Required Output :
TableCode | ColCode | ColumnName | Values | RowNo
23 | 1 | ParaName_1 | CustCode | 1
23 | 1 | ParaName_2 | CatCode | 2
23 | 1 | ParaName_3 | Null | 3
23 | 2 | ParaSource_1 | QS | 1
23 | 2 | ParaSource_2 | QS | 2
23 | 2 | ParaSource_3 | null | 3
Using a couple of common table expressions and row_number() along with the table value constructor (values (...),(...))
to cross join numbers 1, 2, and 3 then using a left join to return 3 rows per TableCode even when you do not have three rows in the source table.
;with numbered as (
select *, rn = row_number() over (order by (select 1))
from TableA
where TableCode = 23
)
, cte as (
select distinct tc.TableCode, a.Col1, a.Col2, v.rn
from numbered tc
cross join (values (1),(2),(3)) v (rn)
left join numbered a
on a.TableCode = tc.TableCode
and a.rn = v.rn
)
select
a.TableCode
, x.ColCode
, [ColumnName] = x.ColumnName + '_' + convert(varchar(5),a.rn)
, X.Value
,[RowNo] = a.rn
from cte a
cross apply (values (1,'ParaName',Col1),(2,'ParaSource',Col2))
as x(ColCode, ColumnName, Value)
order by ColCode, RowNo;
rextester demo: http://rextester.com/CJU8986
returns:
+-----------+---------+--------------+----------+-------+
| TableCode | ColCode | ColumnName | Value | RowNo |
+-----------+---------+--------------+----------+-------+
| 23 | 1 | ParaName_1 | CustCode | 1 |
| 23 | 1 | ParaName_2 | CatCode | 2 |
| 23 | 1 | ParaName_3 | NULL | 3 |
| 23 | 2 | ParaSource_1 | QS | 1 |
| 23 | 2 | ParaSource_2 | QS | 2 |
| 23 | 2 | ParaSource_3 | NULL | 3 |
+-----------+---------+--------------+----------+-------+
This would appear to do what you want:
SELECT TableCode, x.ColCode, v.*
FROM TableA a CROSS APPLY
(VALUES (1, 'ParaName-1', Col1, 1),
(2, 'ParaName-2', Col2, 2),
(3, 'ParaName-3', NULL, 2)
) v(ColCode, ColumnName, [Values], RowNo)
WHERE TableCode = 23;
I see no reason to use row_number() when you can just read in the correct values. Also, VALUES is a SQL keyword so it is a really bad column name.