How to Pivot one Row into One Column - sql-server

Can someone please help me out.
I've looked around and can't find something similar to what I need to do. Basically,
I have a table that will need to be pivoted, it is coming from a flat file that loads all columns as one comma delimited column. I will need to break out the columns into their respective order before the pivot and I've got procedures that do this beautifully. However, the crux of this table is that I need to edit the headers before I can continue.
I need help to pivot the information in the first column and put it another table I've created. Therefore, I need this
ID Column01
1 Express,,,Express,,,HyperMakert,,WebStore,Web
To End up like this....
New_ID New_Col
1 Express
2
3
4 Express
5
6
7 HyperMarket
8
9 WebStore
10 Web
Please note that I need to include the '' Black columns from the original row and.
I looked and the links below but they were not helpful;
SQL Server : Transpose rows to columns
Efficiently convert rows to columns in sql server
Mysql query to dynamically convert rows to columns

There are many methods of splitting string in SQL Server you can find on the web, some are really complicated but some are just simple. I like the way of using dynamic query. It's just short and simple (not sure about the performance but I believe it would be not too bad):
declare #s varchar(max)
-- save the Column01 string/text into #s variable
select #s = Column01 from test where ID = 1
-- build the query string
set #s = 'select row_number() over (order by current_timestamp) as New_ID, c as New_Col from (values ('''
+ replace(#s, ',', '''),(''') + ''')) v(c)'
insert newTable exec(#s)
go
select * from newTable
Sqlfiddle Demo
The use of values() clause above is some kind of anonymous table, here is a simple example of such usage (so that you can understand it better). The anonymous table in the following example has just 1 column, the table name is v and the column name is c. Each row has just 1 cell and should be wrapped in a pair of parentheses (). The rows are separated by commas and follow after values. Here is the code:
-- note about the outside (...) wrapping values ....
select * from (values ('a'),('b'),('c'), ('d')) v(c)
The result will be:
c
------
1 a
2 b
3 c
4 d
Just try running that code and you'll understand how useful it is.

You may want to use a tally table here. See http://www.sqlservercentral.com/articles/T-SQL/62867/
declare #parameter varchar(4000)
set #parameter = 'Express,,,Express,,,HyperMakert,,WebStore,Web'
set #parameter = ',' + #parameter + ',' -- add commas
with
e1 as(select 1 as N union all select 1), -- 2 rows
e2 as(select 1 as N from e1 as a, e1 as b), -- 4 rows
e3 as(select 1 as N from e2 as a, e2 as b), -- 16 rows
e4 as(select 1 as N from e3 as a, e3 as b), -- 256 rows
e5 as(select 1 as N from e4 as a, e4 as b), -- 65536 rows
tally as (select row_number() over(order by N) as N from e5
)
select
substring(#parameter, N+1, charindex(',', #parameter, N+1) - N-1)
from tally
where
N < len(#parameter)
and substring(#parameter, N, 1) = ','

Related

how to generate individual rows for each character between two delimiters in a string

I have a data set with square brackets.
CREATE TABLE Testdata
(
SomeID INT,
String VARCHAR(MAX)
)
INSERT Testdata SELECT 1, 'S0000X-T859XX[DEFGH]'
INSERT Testdata SELECT 1, 'T880XX-T889XX[DS]'
INSERT Testdata SELECT 2, 'V0001X-Y048XX[DS]'
INSERT Testdata SELECT 2, 'Y0801X-Y0889X[AB]'
i need to get output like below,
SomeId String
1 S0000XD-T859XXD
1 S0000XE-T859XXE
1 S0000XF-T859XXF
1 S0000XG-T859XXG
1 S0000XH-T859XXH
1 T880XXD-T889XXD
1 T880XXS-T889XXS
2 V0001XD-Y048XXD
2 V0001XS-Y048XXS
2 Y0801XA-Y0889XA
2 Y0801XB-Y0889XB
Appreciate if any one can help this
You don't need a function here, and certainly no need for loops. A tally table will make short work of this. First you need a tally table. I keep one as a view on my system. It is nasty fast!!!
create View [dbo].[cteTally] as
WITH
E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
)
select N from cteTally
GO
You don't have use a view like this, you could just use the CTEs directly in your code.
This is some rather ugly string manipulation but since your data is not properly normalized you are kind of stuck there. Please try this query. It produces what you said you expect as output.
select *
, NewOutput = left(td.String, charindex('-', td.String) - 1) + SUBSTRING(td.String, CHARINDEX('[', td.String) + t.N, 1) +
left(substring(td.String, charindex('-', td.String), len(td.String)), charindex('[', substring(td.String, charindex('-', td.String), len(td.String))) - 1) + SUBSTRING(td.String, CHARINDEX('[', td.String) + t.N, 1)
from TestData td
join cteTally t on t.N <= CHARINDEX(']', td.String) - CHARINDEX('[', td.String) - 1
order by td.String
, t.N
I am posting this because I did it.
select distinct *
,[base]+substring(splitter,number,1)
from
(
select SomeID
-- split your column into a base plus a splitter column
,[base] = left(string,charindex('[',string)-1)
,splitter = substring(string, charindex('[',string)+1,len(string) - charindex('[',string)-1)
from
(
-- converted your insert into a union all
SELECT 1 SomeID, 'S0000X-T859XX[DEFGH]' string
union all
SELECT 1, 'T880XX-T889XX[DS]'
union all
SELECT 2, 'V0001X-Y048XX[DS]'
union all
SELECT 2, 'Y0801X-Y0889X[AB]'
) a
) inside
cross apply (Select number from master..spt_values where number>0 and number <=len(splitter)) b -- this is similar to a tally table using an internal SQL table
Since you didn't include any attempt to solve this, I will assume that you are not stuck on any particular technique, and are looking for a high-level approach.
I would solve this by first creating a Table-valued function that takes the id and the string as parameters, and creates an output table by looping through the characters in between the square-brackets, so that the function's output looks like this
ID String Character
1 S0000X-T859XX D
1 S0000X-T859XX E
1 S0000X-T859XX F
..
2 V0001XD-Y048XX D
etc...
Then you simply write a query that joins the raw table to the function on the ID and the portion of the String without the brackets.

Retrieve Sorted Column Value in SQL Server

What i have:
I have a Column
ID  SerialNo
1  101
2  102
3  103
4  104
5  105
6  116
7  117
8  118
9  119
10 120
These are just the 10 dummy rows. The actual table has over 100 000 rows.
What I Want to get:
A method or formula like any sorting technique which could return me the starting and ending element of [SerialNo] Column for every sub-series. For example
Expected Result: 101-105, 115-120
The comma separation in the above result is not important, only the starting and ending elements are important.
What I have tried:
I did it by PL/SQL programming, by running a loop in which I’m getting the starting and ending elements getting stored in a TABLE.
But due to no. of rows (over 100 000) the query execution is taking around 2 minutes.
I have also searched about some sorting techniques for the SQL Server but I found nothing. Because rendering every row will take twice the time then a sorting algorithm
Assuming every sub series should contain 5 records, I got expected result using below sql. I hope this helps.
DECLARE #subSeriesRange INT=5;
CREATE TABLE #Temp(ID INT,SerialNo INT);
INSERT INTO #Temp VALUES(1,101),
(2,102),
(3,103),
(4,104),
(5,105),
(6,116),
(7,117),
(8,115),
(9,119),
(10,120);
SELECT STUFF((SELECT CONCAT(CASE ID%#subSeriesRange WHEN 1 THEN ',' ELSE '-' END,SerialNo)
FROM #Temp
WHERE ID%#subSeriesRange = 1 OR ID%#subSeriesRange=0
ORDER BY ID
FOR XML PATH('')),1,1,''
);
DROP TABLE #Temp;
Just finding the start and end of each series is quite straightforward:
declare #t table (ID int not null, SerialNo int not null)
insert into #t(ID,SerialNo) values
(1 ,101), (2 ,102), (3 ,103),
(4 ,104), (5 ,105), (6 ,116),
(7 ,117), (8 ,118), (9 ,119),
(10,120)
;With Starts as (
select t1.SerialNo,ROW_NUMBER() OVER (ORDER BY t1.SerialNo) as rn
from
#t t1
left join
#t t1_no
on t1.SerialNo = t1_no.SerialNo + 1
where t1_no.ID is null
), Ends as (
select t1.SerialNo,ROW_NUMBER() OVER (ORDER BY t1.SerialNo) as rn
from
#t t1
left join
#t t1_no
on t1.SerialNo = t1_no.SerialNo - 1
where t1_no.ID is null
)
select
s.SerialNo as StartSerial,
e.SerialNo as EndSerial
from
Starts s
inner join
Ends e
on s.rn = e.rn
The logic being that a Start is a row where there is no row that has the SerialNo one less than the current row, and an End is a row where there is no row that has the SerialNo one more than the current row.
This may still perform poorly if there is no index on the SerialNo column.
Results:
StartSerial EndSerial
----------- -----------
101 105
116 120
Which is hopefully acceptable since you didn't seem to care what the specific results look like. It's also keeping things set-based.

How do I exclude rows when an incremental value starts over?

I am a newbie poster but have spent a lot of time researching answers here. I can't quite figure out how to create a SQL result set using SQL Server 2008 R2 that should probably be using lead/lag from more modern versions. I am trying to aggregate data based on sequencing of one column, but there can be varying numbers of instances in each sequence. The only way I know a sequence has ended is when the next row has a lower sequence number. So it may go 1-2, 1-2-3-4, 1-2-3, and I have to figure out how to make 3 aggregates out of that.
Source data is joined tables that look like this (please help me format):
recordID instanceDate moduleID iResult interactionNum
1356 10/6/15 16:14 1 68 1
1357 10/7/15 16:22 1 100 2
1434 10/9/15 16:58 1 52 1
1435 10/11/15 17:00 1 60 2
1436 10/15/15 16:57 1 100 3
1437 10/15/15 16:59 1 100 4
I need to find a way to separate the first 2 rows from the last 4 rows in this example, based on values in the last column.
What I would love to ultimately get is a result set that looks like this, which averages the iResult column based on the grouping and takes the first instanceDate from the grouping:
instanceDate moduleID iResult
10/6/15 1 84
10/9/15 1 78
I can aggregate to get this result using MIN and AVG if I can just find a way to separate the groups. The data is ordered by instanceDate (please ignore the date formatting here) then interactionNum and the group separation should happen when the query finds a row where the interactionNum is <= than the previous row (will usually start over with '1' but not always, so prefer just to separate on a lower or equal integer value).
Here is the query I have so far (includes the joins that give the above data set):
SELECT
X.*
FROM
(SELECT TOP 100 PERCENT
instanceDate, b.ModuleID, iResult, b.interactionNum
FROM
(firstTable a
INNER JOIN
secondTable b ON b.someID = a.someID)
WHERE
a.someID = 2
AND b.otherID LIKE 'xyz'
AND a.ModuleID = 1
ORDER BY
instanceDate) AS X
OUTER APPLY
(SELECT TOP 1
*
FROM
(SELECT
instanceDate, d.ModuleID, iResult, d.interactionNum
FROM
(firstTable c
INNER JOIN
secondTable d ON d.someID = c.someID)
WHERE
c.someID = 2
AND d.otherID LIKE 'xyz'
AND c.ModuleID = 1
AND d.interactionNum = X.interactionNum
AND c.instanceDate < X.instanceDate) X2
ORDER BY
instanceDate DESC) Y
WHERE
NOT EXISTS (SELECT Y.interactionNum INTERSECT SELECT X.interactionNum)
But this is returning an interim result set like this:
instanceDate ModuleID iResult interactionNum
10/6/15 16:10 1 68 1
10/6/15 16:14 1 100 2
10/15/15 16:57 1 100 3
10/15/15 16:59 1 100 4
and the problem is that interactionNum 3, 4 do not belong in this result set. They would go in the next result set when I loop over this query. How do I keep them out of the result set in this iteration? I need the result set from this query to just include the first two rows, 'seeing' that row 3 of the source data has a lower value for interactionNum than row 2 has.
Not sure what ModuleID was supposed to be used, but I guess you're looking for something like this:
select min (instanceDate), [moduleID], avg([iResult])
from (
select *,row_number() over (partition by [moduleID] order by instanceDate) as RN
from Table1
) X
group by [moduleID], RN - [interactionNum]
The idea here is to create a running number with row_number for each moduleid, and then use the difference between that and InteractionNum as grouping criteria.
Example in SQL Fiddle
Here is my solution, although it should be said, I think #JamesZ answer is cleaner.
I created a new field called newinstance which is 1 wherever your instanceNumber is 1. I then created a rolling sum(newinstance) called rollinginstance to group on.
Change the last select to SELECT * FROM cte2 to show all the fields I added.
IF OBJECT_ID('tempdb..#tmpData') IS NOT NULL
DROP TABLE #tmpData
CREATE TABLE #tmpData (recordID INT, instanceDate DATETIME, moduleID INT, iResult INT, interactionNum INT)
INSERT INTO #tmpData
SELECT 1356,'10/6/15 16:14',1,68,1 UNION
SELECT 1357,'10/7/15 16:22',1,100,2 UNION
SELECT 1434,'10/9/15 16:58',1,52,1 UNION
SELECT 1435,'10/11/15 17:00',1,60,2 UNION
SELECT 1436,'10/15/15 16:57',1,100,3 UNION
SELECT 1437,'10/15/15 16:59',1,100,4
;WITH cte1 AS
(
SELECT *,
CASE WHEN interactionNum=1 THEN 1 ELSE 0 END AS newinstance,
ROW_NUMBER() OVER(ORDER BY recordID) as rowid
FROM #tmpData
), cte2 AS
(
SELECT *,
(select SUM(newinstance) from cte1 b where b.rowid<=a.rowid) as rollinginstance
FROM cte1 a
)
SELECT MIN(instanceDate) AS instanceDate, moduleID, AVG(iResult) AS iResult
FROM cte2
GROUP BY moduleID, rollinginstance

Why was the DelimitedSplit8k udf was written with 2X (cartesian product) in SQL server?

I was asking this question about writing fast inline table valued function in sql server.
The code in the answer is working but I'm asking about that part :
It is clear to me that he wanted to create many numbers ( 1,1,1,1,1,...) and then turn them to sequential numbers (1,2,3,4,5,6....):
In this part :
WITH E1(N) AS (
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
)
,E2(N) AS (SELECT 1 FROM E1 a, E1 b)
,E4(N) AS (SELECT 1 FROM E2 a, E2 b)
SELECT * FROM e4 --10000 rows
He created 10000 rows.
This function is widely used and hence my question:
Question :
Why didn't he (Jeff Moden) use :
WITH E1(N) AS (
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
)
,E2(N) AS (SELECT 1 FROM E1 a, E1 b , E1 c , E1 d)
SELECT * FROM E2 -- ALSO 10000 rows !!!
But choose to split it into E2 , E4 ?
Although I am not Jeff Moden and do not know his reasoning, I find it likely that he simply used a known pattern for number generation which he himself calls Itzik Ben Gan's cross joined CTE method in this Stack Overflow answer.
The pattern goes like this:
WITH E00(N) AS (SELECT 1 UNION ALL SELECT 1),
E02(N) AS (SELECT 1 FROM E00 a, E00 b),
E04(N) AS (SELECT 1 FROM E02 a, E02 b),
E08(N) AS (SELECT 1 FROM E04 a, E04 b),
...
In order to adapt the method for his string splitting function, he apparently found it more convenient to modify the initial CTE to be ten rows instead of two and to cut down the number of cross joining CTEs to two to just cover the 8000 rows necessary for his solution.
Heh... just ran across this and thought I'd answer.
Andriy M answered it exactly right. It was very much modeled after Itzik Ben-Gan's great original BASE 2 code and, yes, I changed it (as have many others) to Base 10 code just to cut down on the number of cCTEs (Cascading CTEs). The latest code that I and many others use cuts down on the number of cCTEs even further. It also uses the VALUES operator to cut down on the bulk of the code, although there's no performance advantage in doing so.
WITH E1(N) AS (SELECT 1 FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))E0(N)) --10 rows
,E4(N) AS (SELECT 1 FROM E1 a, E1 b, E1 c, E1 d)
SELECT * FROM e4 --10000 rows
;
There are a great many other places where the need for such an on-the-fly creation of a sequence is required. Some need to start the sequence at 0 and others at 1. There's also a much larger range of values needed and, to be honest, I got tired of meticulously writing out code similar to the above so I did what Mr. Ben-Gan and many others have done. I wrote an iTVF called "fnTally". I don't normally use Hungarian Notation for functions but I had two reasons for using the "fn" prefix. 1) is because I still maintain a physical Tally Table and so the function needed to be named differently and 2) I can tell people at work "If you had used the 'eff-n' Tally function I told you about, you wouldn't have this problem" without it actually being an HR violation. ;-)
Just in case anyone should need such a thing, here's the code I wrote for my version of an fnTally function. There's a tiny bit of trade off in allowing it to start at 0 or 1 performance wise but it's worth the extra flexibility, to me anyways. And, yes... you could reduce the number of cCTEs in it by doing 12 CROSS JOINs in the 2nd and final cCTE. I just didn't go that route. You could without harm.
Also note that I still use the SELECT/UNION ALL method to form the first 10 pseudo-rows because I still do a lot of work with folks on 2005 and was stuck using 2005 myself until about 6 months ago. Full documentation is included in the code.
CREATE FUNCTION [dbo].[fnTally]
/**********************************************************************************************************************
Purpose:
Return a column of BIGINTs from #ZeroOrOne up to and including #MaxN with a max value of 1 Trillion.
As a performance note, it takes about 00:02:10 (hh:mm:ss) to generate 1 Billion numbers to a throw-away variable.
Usage:
--===== Syntax example (Returns BIGINT)
SELECT t.N
FROM dbo.fnTally(#ZeroOrOne,#MaxN) t
;
Notes:
1. Based on Itzik Ben-Gan's cascading CTE (cCTE) method for creating a "readless" Tally Table source of BIGINTs.
Refer to the following URLs for how it works and introduction for how it replaces certain loops.
http://www.sqlservercentral.com/articles/T-SQL/62867/
http://sqlmag.com/sql-server/virtual-auxiliary-table-numbers
2. To start a sequence at 0, #ZeroOrOne must be 0 or NULL. Any other value that's convertable to the BIT data-type
will cause the sequence to start at 1.
3. If #ZeroOrOne = 1 and #MaxN = 0, no rows will be returned.
5. If #MaxN is negative or NULL, a "TOP" error will be returned.
6. #MaxN must be a positive number from >= the value of #ZeroOrOne up to and including 1 Billion. If a larger
number is used, the function will silently truncate after 1 Billion. If you actually need a sequence with
that many values, you should consider using a different tool. ;-)
7. There will be a substantial reduction in performance if "N" is sorted in descending order. If a descending
sort is required, use code similar to the following. Performance will decrease by about 27% but it's still
very fast especially compared with just doing a simple descending sort on "N", which is about 20 times slower.
If #ZeroOrOne is a 0, in this case, remove the "+1" from the code.
DECLARE #MaxN BIGINT;
SELECT #MaxN = 1000;
SELECT DescendingN = #MaxN-N+1
FROM dbo.fnTally(1,#MaxN);
8. There is no performance penalty for sorting "N" in ascending order because the output is explicity sorted by
ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
Revision History:
Rev 00 - Unknown - Jeff Moden
- Initial creation with error handling for #MaxN.
Rev 01 - 09 Feb 2013 - Jeff Moden
- Modified to start at 0 or 1.
Rev 02 - 16 May 2013 - Jeff Moden
- Removed error handling for #MaxN because of exceptional cases.
Rev 03 - 22 Apr 2015 - Jeff Moden
- Modify to handle 1 Trillion rows for experimental purposes.
**********************************************************************************************************************/
(#ZeroOrOne BIT, #MaxN BIGINT)
RETURNS TABLE WITH SCHEMABINDING AS
RETURN WITH
E1(N) AS (SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1) --10E1 or 10 rows
, E4(N) AS (SELECT 1 FROM E1 a, E1 b, E1 c, E1 d) --10E4 or 10 Thousand rows
,E12(N) AS (SELECT 1 FROM E4 a, E4 b, E4 c) --10E12 or 1 Trillion rows
SELECT N = 0 WHERE ISNULL(#ZeroOrOne,0)= 0 --Conditionally start at 0.
UNION ALL
SELECT TOP(#MaxN) N = ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E12 -- Values from 1 to #MaxN
;

How to find difference of two row values and store it as third row in the same table?

I want to find difference between two rows of a table and insert the difference value as third row in the same table. If the first row value is less than second row then the difference should appear within parenthesis instead of negative symbol.
Ex:
Name S1 S2 S3 S4
xxx 98 70 50 85
xxx1 50 90 35 105
Diff 48 (20) 15 (20)
Kindly say whether is it possible either in front-end, that is after storing the values in gridview. But in Gridview the colums and rows are Transposed. Then in grid view difference of two column store as third new column.
Here's some code which will do it:
SELECT
Name
,S1
,S2
FROM
(
select
x.Name as xName
,CONVERT(varchar(99), x.S1) as xS1 -- Have to convert everything since you require negatives as ()
,CONVERT(varchar(99), x.S2) as xS2
,1 as xOrder -- To ensure the rows are presented as requested
,x1.Name as x1Name
,CONVERT(varchar(99), x1.S1) as x1S1
,CONVERT(varchar(99), x1.S2) as x1S2
,2 as x1Order
,case
when SIGN(x.S1 - x1.S1) = -1
then '(' + CONVERT(varchar(99), x.S1 - x1.S1) + ')'
else CONVERT(varchar(99), x.S1 - x1.S1)
end as d1
,case
when SIGN(x.S2 - x1.S2) = -1
then '(' + CONVERT(varchar(99), x.S2 - x1.S2) + ')'
else CONVERT(varchar(99), x.S2 - x1.S2)
end as d2
-- etc for S3 and S4
,3 as DiffOrder
from
(select * from dbo.Data where Name = 'xxx') as x
inner join
(select * from dbo.Data where Name = 'xxx1') as x1
on x1.Name = x.Name + '1' -- I suspect your row matching conditin will have
-- to me more sophisticated that this in realit
) as Data
-- The cross apply does an UNPIVOT on the cheap
CROSS APPLY ( VALUES
(xName, xS1, xS2, xOrder),
(x1Name, x1S1, x1S2, x1Order),
('Diff', d1, d2, DiffOrder)
) -- Close paren from the CROSS APPLY
as ca(Name, S1, S2, SortOrder)
order by ca.SortOrder;
It would be much, much better to have your presentation layer perform the formatting of negatives.
Because you require negatives presented in this way the columns have to be CONVERTed to character types. This adds complexity to the SQL. It may also mean they are no longer right-aligned. Again, good presentation software should be able to deal with that for you better than SQL can.
I suspect that in your actual system you will have many row-pairs. You haven't said what the rules to match them are. You will have to add this into the code. Make sure you change the sorting to account for this also.
The code could be reformatted using CTEs, if you find that layout simpler to understand and maintain.
I have no idea how you intend to insert varchar into a numeric column. I assume s1-s4 are numeric as they should be.
You have your data reversed, so best thing is to spin it 90 degrees first to do the calculation, then after the result turn it back to display it correct:
declare #t table(Name varchar(4), S1 int, S2 int, S3 int, S4 int)
insert #t values('xxx',98,70,50,85),
('xxx1',50,90,35,105)
--Diff 48 (20) 15 (20)
-- this next part can be put into a view
;with cte as
(
select
sum(case when Name = 'xxx' then Value else -value end) Total, Col
from #t t1
UNPIVOT
(Value FOR Col IN
([S1], [S2], [S3], [S4]) ) AS unpvt
group by col
), cte2 as
(
select case when Total < 0 then
'(' + cast(-Total as varchar(10))+')'
else cast(Total as varchar(10)) end Totalvarchar, Col
from cte
)
select Name,
cast(S1 as varchar(20)) S1, cast(S2 as varchar(20)) S2,
cast(S3 as varchar(20)) S3, cast(S4 as varchar(20)) S4
from #t
union all
select 'Diff' Name, [S1], [S2], [S3], [S4]
from
cte2
PIVOT (max(TotalVarchar) FOR [Col] IN ([S1], [S2], [S3], [S4])) AS pvt
Result:
Name S1 S2 S3 S4
xxx 98 70 50 85
xxx1 50 90 35 105
Diff 48 (20) 15 (20)

Resources