SQL Transform a list of numbers into ranges in one column - sql

If I have a list of ranges in a table e.g.
ID Number
1 4
1 5
1 6
1 7
1 9
Is there a way to put this into the format: '4-7,9' into one varchar column using SQL ?
Thanks.

You can use ROW_NUMBER and XML PATH:
DECLARE #Mock TABLE (Id INT, Number INT)
INSERT INTO #Mock
VALUES
(1, 4),
(1, 5),
(1, 6),
(1, 7),
(1, 9)
;WITH CTE
AS
(
SELECT ROW_NUMBER() OVER (ORDER BY Number) AS RowId,*
FROM #Mock
)
SELECT
STUFF(
(
SELECT
',' + CAST(MIN(C.Number) AS VARCHAR(10)) + CASE WHEN MIN(C.Number) = MAX(C.Number) THEN '' ELSE '-' + CAST(MAX(C.Number) AS VARCHAR(10)) END
FROM
CTE C
GROUP BY
C.Number - C.RowId
FOR XML PATH ('')
), 1, 1, '') Result
Output: 4-7,9

Considering you have another to find the order of ranges
;WITH cte
AS (SELECT *,
Sum(CASE
WHEN number = prev_lag + 1 THEN 0
ELSE 1
END)
OVER(
ORDER BY iden_col) AS grp
FROM (SELECT *,
Lag(number)
OVER(
partition BY [ID]
ORDER BY iden_col) AS prev_lag
FROM Yourtable)a),
intr
AS (SELECT id,
CASE
WHEN Min(number) = Max(number) THEN Cast(Min(number) AS VARCHAR(50))
ELSE Concat(Min(number), '-', Max(number))
END AS intr_res
FROM cte
GROUP BY id,
grp)
SELECT DISTINCT Id,
Stuff(concat_col, 1, 1, '')
FROM intr a
CROSS apply (SELECT ',' + intr_res
FROM intr b
WHERE a.ID = b.ID
FOR xml path('')) cs (concat_col)
Demo

Related

How to split strings basing on spaces?

I have a sample data like this
DECLARE #Table1 table ([name] varchar(62));
INSERT INTO #Table1
([name])
VALUES
('2018-08-08 23:02:57,731 INFO [AllRequestInterceptor] CRTST020'),
('2018-08-08 23:03:11,687 INFO [SOAPLoggingHandler] CRTST020'),
('2018-08-08 23:03:02,028 ERROR [AJAXController] CRTST003');
I'm trying to create 4 columns based on the spaces provided.
SELECT
Reverse(ParseName(Replace(Reverse([name]), ' ', '.'), 1)) As [M1]
,Reverse(ParseName(Replace(Reverse([name]), ' ', '.'), 2)) As [M2]
,Reverse(ParseName(Replace(Reverse([name]), ' ', '.'), 3)) As [M3]
,Reverse(ParseName(Replace(Reverse([name]), ' ', '.'), 4)) As [M4]
FROM (Select [name] from #Table1
) As [x]
Expected output :
Date Name Req Code
8/8/2018 23:02:57,731 INFO [AllRequestInterceptor] CRTST020
8/8/2018 23:03:11,687 INFO [SOAPLoggingHandler] CRTST020
8/8/2018 23:03:02,028 ERROR [AJAXController] CRTST003
Try this
DECLARE #Table1 table ([name] varchar(1000));
INSERT INTO #Table1
([name])
VALUES
('2018-08-08 23:02:57,731 INFO [AllRequestInterceptor] CRTST020'),
('2018-08-08 23:03:11,687 INFO [SOAPLoggingHandler] CRTST020'),
('2018-08-08 23:03:02,028 ERROR [AJAXController] CRTST003');
SELECT DISTINCT Split.a.value('/S[1]', 'NVARCHAR(MAX)')+' '+ Split.a.value('/S[2]', 'NVARCHAR(MAX)') [Date],
Split.a.value('/S[3]', 'NVARCHAR(MAX)') As Name,
Split.a.value('/S[4]', 'NVARCHAR(MAX)') As Req,
Split.a.value('/S[5]', 'NVARCHAR(MAX)') As Code
FROM
(
SELECT CAST('<S>'+REPLACE([name] ,' ','</S><S>' ) +'</S>' AS XML) AS [name]
FROM #Table1
) AS A
CROSS APPLY [name].nodes('S') AS Split(a)
Result
Date Name Req Code
----------------------------------------------------------------
2018-08-08 23:02:57,731 INFO [AllRequestInterceptor] CRTST020
2018-08-08 23:03:02,028 ERROR [AJAXController] CRTST003
2018-08-08 23:03:11,687 INFO [SOAPLoggingHandler] CRTST020
This isn't ideal, but assuming that the only place a space will appear (other than as a delimiter) in the value of date, you could use a string splitter and then pivot the data back. This uses delimitedsplit8k_LEAD, as ordinal position is important:
WITH CTE AS(
SELECT T1.name,
DS.ItemNumber,
DS.Item,
ROW_NUMBER() OVER (PARTITION BY T1.[name] ORDER BY DS.ItemNumber ASC) AS RN
FROM #Table1 T1
CROSS APPLY dbo.delimitedsplit8k_LEAD(T1.[name],' ') DS
WHERE DS.Item <> '')
SELECT MAX(CASE WHEN RN = 1 THEN Item END) + ' ' + MAX(CASE WHEN RN = 2 THEN Item END) AS [Date],
MAX(CASE WHEN RN = 3 THEN Item END) AS [Name],
MAX(CASE WHEN RN = 4 THEN Item END) AS Req,
MAX(CASE WHEN RN = 5 THEN Item END) AS Code
FROM CTE
GROUP BY [Name];
db<>fiddle
Blarg, if you can't create the funciton, you could do thuis (but yuck):
DECLARE #Table1 table ([name] varchar(62));
INSERT INTO #Table1 ([name])
VALUES ('2018-08-08 23:02:57,731 INFO [AllRequestInterceptor] CRTST020'),
('2018-08-08 23:03:11,687 INFO [SOAPLoggingHandler] CRTST020'),
('2018-08-08 23:03:02,028 ERROR [AJAXController] CRTST003');
DECLARE #Delimiter char(1) = ' ';
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), --10E+1 or 10 rows
E2 (N) AS
(SELECT 1
FROM E1 AS a,
E1 AS b), --10E+2 or 100 rows
E4 (N) AS
(SELECT 1
FROM E2 AS a,
E2 AS b), --10E+4 or 10,000 rows max
cteTally (N) AS
( --==== This provides the "zero base" and limits the number of rows right up front
-- for both a performance gain and prevention of accidental "overruns"
SELECT 0
UNION ALL
SELECT TOP 62
ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM E4),
cteStart (N1, [name]) AS
( --==== This returns N+1 (starting position of each "element" just once for each delimiter)
SELECT t.N + 1,
T1.[name]
FROM cteTally AS t
CROSS JOIN #Table1 AS T1
WHERE (SUBSTRING(T1.[name], t.N, 1) = #Delimiter
OR t.N = 0)),
Splits AS
(SELECT s.[name],
ROW_NUMBER() OVER (ORDER BY s.N1) AS ItemNumber,
SUBSTRING(s.[name], s.N1, ISNULL(NULLIF((LEAD(s.N1, 1, 1) OVER (PARTITION BY s.[name] ORDER BY s.N1) - 1), 0) - s.N1, 8000)) AS item
FROM cteStart AS s),
CTE AS
(SELECT name,
ItemNumber,
item,
ROW_NUMBER() OVER (PARTITION BY [name] ORDER BY ItemNumber ASC) AS RN
FROM Splits
WHERE item <> '')
SELECT MAX(CASE WHEN RN = 1 THEN item END) + ' ' + MAX(CASE WHEN RN = 2 THEN item END) AS [Date],
MAX(CASE WHEN RN = 3 THEN item END) AS [Name],
MAX(CASE WHEN RN = 4 THEN item END) AS Req,
MAX(CASE WHEN RN = 5 THEN item END) AS Code
FROM CTE
GROUP BY [name];
Your data is almost a fixed-length format. For your sample data, you can use string functions:
select left(name, 23) as date,
trim(substring(name, 25, 6)) as name,
trim(substring(name, 31, len(name) - 39)) as req,
right(name, 8) as code
from #table1 t1;
Here is a db<>fiddle.
You can use multi apply for this :
select substring(name, 0, n) as [Date], substring(d, 0, d1) as Name, substring(d, d1, d2-d1+1) as Req, substring(d, d2+1, len(name)) as Code
from #Table1 t1 cross apply
( values (patindex('%[a-z]%', name))
) tt(n) cross apply
( values (substring(name, n, len(name)))
) ttt(d) cross apply
( values (charindex('[', d), charindex(']', d))
) tttt(d1, d2);

Splitting single row into more columns based on column value

I've a requirement to get 3 similar set of row data replacing the column value if any certain value exists in the given column('[#]' in this case). For example
---------------------
Type Value
---------------------
1 Apple[#]
2 Orange
3 Peach[#]
I need to modify the query to get value as below
----------------------
Type Value
--------------------
1 Apple1
1 Apple2
1 Apple3
2 Orange
3 Peach1
3 Peach2
3 Peach3
I could not come up with logic how to get this
You can also get the same result without recursivity :
select Type, Value from MyTable where Right(Value, 3) <> '[#]'
union
select Type, Replace(Value, '[#]', '1') from MyTable where Right(Value, 3) = '[#]'
union
select Type, Replace(Value, '[#]', '2') from MyTable where Right(Value, 3) = '[#]'
union
select Type, Replace(Value, '[#]', '3') from MyTable where Right(Value, 3) = '[#]'
order by 1, 2
Assuming there is only one digit (as in your example), then I would go for:
with cte as (
select (case when value like '%\[%%' then left(right(value, 2), 1) + 0
else 1
end) as cnt, 1 as n,
left(value, charindex('[', value + '[')) as base, type
from t
union all
select cnt, n + 1, base, type
from cte
where n + 1 <= cnt
)
select type,
(case when cnt = 1 then base else concat(base, n) end) as value
from cte;
Of course, the CTE can be easily extended to any number of digits:
(case when value like '%\[%%'
then stuff(left(value, charindex(']')), 1, charindex(value, '['), '') + 0
else 1
end)
And once you have the number, you can use another source of numbers. But the recursive CTE seems like the simplest solution for the particular problem in the question.
Try this query
DECLARE #SampleData AS TABLE
(
Type int,
Value varchar(100)
)
INSERT INTO #SampleData
VALUES (1, 'Apple[#]'), (2, 'Orange'), (3, 'Peach[#]')
SELECT sd.Type, cr.Value
FROM #SampleData sd
CROSS APPLY
(
SELECT TOP (IIF(Charindex('[#]', sd.Value) > 0, 3, 1))
x.[Value] + Cast(v.t as nvarchar(5)) as Value
FROM
(SELECT Replace(sd.Value, '[#]', '') AS Value) x
Cross JOIN (VALUES (1),(2),(3)) v(t)
Order by v.t asc
) cr
Demo link: Rextester
Using a recursive CTE
CREATE TABLE #test
(
Type int,
Value varchar(50)
)
INSERT INTO #test VALUES
(1, 'Apple[#]'),
(2, 'Orange'),
(3, 'Peach[#]');
WITH CTE AS (
SELECT
Type,
IIF(RIGHT(Value, 3) = '[#]', LEFT(Value, LEN(Value) - 3), Value) AS 'Value',
IIF(RIGHT(Value, 3) = '[#]', 1, NULL) AS 'Counter'
FROM
#test
UNION ALL
SELECT
B.Type,
LEFT(B.Value, LEN(B.Value) - 3) AS 'Value',
Counter + 1
FROM
#test AS B
JOIN CTE
ON B.Type = CTE.Type
WHERE
RIGHT(B.Value, 3) = '[#]'
AND Counter < 3
)
SELECT
Type,
CONCAT(Value, Counter) AS 'Value'
FROM
CTE
ORDER BY
Type,
Value
DROP TABLE #test

how to write SQL query for this result?

I have so many long database so I used seq_no in commas separate using more than one sequence store in single column but now I want all sequence in a single column so I am confused how to create this sql result for this.
For example:
TABLE STRUCTURE
SR_NO IS INT ,
SEQ_NO IS VARCHAR(MAX)
SR_NO SEQ_NO
---------------------------------
1 1839073,
2 1850097,1850098,
3 1850099,1850100,1850110
I need to get this result:
SEQ_NO
--------------
1839073
1850097
1850098
1850099
1850100
1850110
Thanks!
declare #t table(Id int,seq varchar(100))
insert into #t (Id,seq) values (1,'1839073,'),(2,'1839073,1850098,'),(3,'1850099,1850100,1850110 ')
;With Cte as (
SELECT A.Id,
Split.a.value('.', 'VARCHAR(100)') AS Seq
FROM
(
SELECT Id,
CAST ('<M>' + REPLACE(seq, ',', '</M><M>') + '</M>' AS XML) AS Data
FROM #t
) AS A CROSS APPLY Data.nodes ('/M') AS Split(a) )
Select ID,Seq from Cte Where Seq > ''
Try splitting it with XML
SELECT SR_NO, t.c.value('.', 'VARCHAR(2000)') COL1
FROM (
SELECT SR_NO, x = CAST('<t>' +
REPLACE(SEQ_NO, ',', '</t><t>') + '</t>' AS XML)
FROM
(values(1,'1839073'),(2, '1850097,1850098'),
(3, '1850099,1850100,1850110')) y(SR_NO, SEQ_NO)
) a
CROSS APPLY x.nodes('/t') t(c)
Result:
SR_NO COL1
1 1839073
2 1850097
2 1850098
3 1850099
3 1850100
3 1850110
You can replace this with your table:
(values (1,'1839073'),(2, '1850097,1850098'),
(3, '1850099,1850100,1850110')) y(SR_NO, SEQ_NO)
This should do it: (Replace YourTableName with your table name)
;WITH CTE(NEW_SEQ_NO, SEQ_NO) as (
SELECT LEFT(SEQ_NO, CHARINDEX(',',SEQ_NO + ',') -1),
STUFF(SEQ_NO, 1, CHARINDEX(',',SEQ_NO + ','), '')
FROM YourTableName
WHERE SEQ_NO <> '' AND SEQ_NO IS NOT NULL
UNION all
SELECT LEFT(SEQ_NO, CHARINDEX(',',SEQ_NO + ',') -1),
STUFF(SEQ_NO, 1, CHARINDEX(',',SEQ_NO + ','), '')
FROM CTE
WHERE SEQ_NO <> '' AND SEQ_NO IS NOT NULL
)
SELECT NEW_SEQ_NO from CTE ORDER BY NEW_SEQ_NO
You can check this topic for more information:
Turning a Comma Separated string into individual rows
I have written the following query after referring Turning a Comma Separated string into individual rows
It will work for you
create table STRUCTURE(SR_NO int, SEQ_NO varchar(max))
insert STRUCTURE select 1, '1839073,'
insert STRUCTURE select 2, '1850097,1850098,'
insert STRUCTURE select 3, '1850099,1850100,1850110'
;with tmp(SR_NO, DataItem, SEQ_NO) as (
select SR_NO, LEFT(SEQ_NO, CHARINDEX(',',SEQ_NO+',')-1),
STUFF(SEQ_NO, 1, CHARINDEX(',',SEQ_NO+','), '')
from STRUCTURE
union all
select SR_NO, LEFT(SEQ_NO, CHARINDEX(',',SEQ_NO+',')-1),
STUFF(SEQ_NO, 1, CHARINDEX(',',SEQ_NO+','), '')
from tmp
where SEQ_NO > ''
)
Select DataItem as SEQ_NO from tmp order by SEQ_NO;

How to compare string type columns character by character without looping?

I have two columns: One column holds actual answers, the other one is the answer_key.
I want to compare answers to answer_key and have scores in the third column:
ID Answers Answer_key Score
1 ABCD ABCC 1110
2 ACD DCA 010
Of course, I can check the length, loop through each character to compare them individually, and get the score.
However, is there an alternative? Possibly based on XML path?
You might try binary values rather than letters.
A=0001 B=0010 C=0100 D=1000
ABCD = 0001001001001000 (0x1248)
ABCC = 0001001001000100 (0x1244)
Score = (Answers XOR Answer_key) XOR 11111111
The XOR 11111111 is optional
What you want to do is to split each char in Answers and Answers_Key into separate rows and then compare them. This can be done using a Recursive CTE. The concatenation is done using the FOR XML PATH function.
CREATE TABLE temp(
Answers VARCHAR(10),
Answer_Key VARCHAR(10)
)
INSERT INTO temp VALUES ('ABCD', 'ABCC'), ('ACD', 'DCA');
;WITH temp_numbered AS(
SELECT
ID = ROW_NUMBER() OVER(ORDER BY Answer_Key),
*
FROM temp
),
cte AS(
SELECT
ID,
Answer_Key_Char = SUBSTRING(Answer_Key, 1, 1),
Answer_Key = STUFF(Answer_Key, 1, 1, ''),
Answers_Char = SUBSTRING(Answers, 1, 1),
Answers = STUFF(Answers, 1, 1, ''),
RowID = 1
FROM temp_numbered t
UNION ALL
SELECT
ID,
Answer_Key_Char = SUBSTRING(Answer_Key, 1, 1),
Answers = STUFF(Answer_Key, 1, 1, ''),
Answers_Char = SUBSTRING(Answers, 1, 1),
Answers = STUFF(Answers, 1, 1, ''),
RowID = RowID + 1
FROM cte
WHERE LEN(Answer_Key) > 0
)
SELECT
Answers,
Answer_Key,
Score = (SELECT
CASE WHEN Answer_Key_Char = Answers_Char THEN '1' ELSE '0' END
FROM cte
WHERE ID = t.ID
ORDER BY ID, RowID
FOR XML PATH(''))
FROM temp_numbered t
DROP TABLE temp
Here is another way using a Tally Table:
;WITH tally(N) AS(
SELECT TOP 11000 ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) FROM sys.columns
)
,temp_numbered AS(
SELECT
ID = ROW_NUMBER() OVER(ORDER BY Answer_Key),
*
FROM temp
)
,cte AS(
SELECT
ID,
Answer_Key_Char = SUBSTRING(Answer_Key, N, 1),
Answers_Char = SUBSTRING(Answers, N, 1),
RowID = N
FROM temp_numbered tn
CROSS JOIN Tally t
WHERE t.N <= LEN(tn.Answer_Key)
)
SELECT
Answers,
Answer_Key,
Score = (SELECT
CASE WHEN Answer_Key_Char = Answers_Char THEN '1' ELSE '0' END
FROM cte
WHERE ID = t.ID
ORDER BY ID, RowID
FOR XML PATH(''))
FROM temp_numbered t
I seems easiest to loop through each character for the whole set at once:
-- get max Answer length
declare #len int,#max_len int
select #max_len = max(len(Answers)),
#len = 1
from Answers
-- update scores
while #len <= #max_len
begin
update Answers
set Score = isnull(Score,'') + '1'
where substring(Answers,#len,1) = substring(Answer_Key,#len,1)
and len(Answers) >= #len
update Answers
set Score = isnull(Score,'') + '0'
where substring(Answers,#len,1) != substring(Answer_Key,#len,1)
and len(Answers) >= #len
set #len = #len + 1
end
-- return Scores
select * from Answers
SQL FIDDLE
Expanding on the answer from #weswesthemenace to get around cte limit.
DECLARE #Answers TABLE
(
Id INT IDENTITY(1, 1) not null,
Answers VARCHAR(MAX) not null,
Answer_Key VARCHAR(MAX) not null
)
INSERT INTO #Answers (Answers, Answer_Key) VALUES ('ABCD', 'ABCC')
INSERT INTO #Answers (Answers, Answer_Key) VALUES ('ACD', 'DCA')
INSERT INTO #Answers (Answers, Answer_Key) VALUES ('ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ', 'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEGGHIJKLMNOPQRSTUVXXYZABCDEFGHIIKKMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXZZ');
WITH
E01(N) AS (SELECT 1 UNION ALL SELECT 1),
E02(N) AS (SELECT 1 FROM E01 a CROSS JOIN E01 b),
E04(N) AS (SELECT 1 FROM E02 a CROSS JOIN E02 b),
E08(N) AS (SELECT 1 FROM E04 a CROSS JOIN E04 b),
E16(N) AS (SELECT 1 FROM E08 a CROSS JOIN E08 b),
E32(N) AS (SELECT 1 FROM E16 a CROSS JOIN E16 b),
cteTally(N) AS (SELECT row_number() OVER (ORDER BY N) FROM E32)
SELECT b.Answers, b.Answer_Key,
(
SELECT CASE when SUBSTRING(a.Answer_Key, n.N, 1) = SUBSTRING(a.Answers, n.N, 1) then '1' else '0' end
FROM #Answers a
CROSS APPLY cteTally n
WHERE b.Id = a.Id AND n.N <= DATALENGTH(b.Answers)
ORDER BY ID, n.N
FOR XML PATH('')
) Score
FROM #Answers b
this can be simplified by a utility Number function in the database. Mine is called dbo.Number(start, end)
SELECT b.Answers, b.Answer_Key,
(
SELECT CASE WHEN SUBSTRING(a.Answer_Key, n.N, 1) = SUBSTRING(a.Answers, n.N, 1) THEN '1' ELSE '0' END
FROM #Answers a
CROSS APPLY dbo.Number(1, DATALENGTH(b.Answers)) n
WHERE b.Id = a.Id
ORDER BY ID, n.N
FOR XML PATH('')
) Score
FROM #Answers b

Displaying the difference between rows in the same table in SQL SERVER

I'm using SQL Server 2005.
I have a table that has an archive of rows each time some field was changed. I have to produce a report that displays fields that were changed for each employee.
My table schema:
tblEmp(empid, name, salary, createddate)
My table data:
Row 1: 1, peter, 1000, 11/4/2012
Row 2: 1, peter, 2000, 11/5/2012
Row 3: 1, pete, 2000, 11/6/2012
Row 4: 1, peter, 4000, 11/7/2012
Based on the above data for employee Peter (employee id 1), the output (changes) would be:
resultset:
1, oldsalary: 1000 newsalary: 2000 (changed on 11/5/2012)
1, oldname: peter newname: pete (changed on 11/6/2012)
1, oldname: pete newname: peter, oldsalary:2000, newsalary: 4000 (changed on 11/7/2012)
I'm trying to come up with the sql that would produce the above resultset.
I've tried to do something similar to the first answer in this thread: How to get difference between two rows for a column field?
However, it's not coming together, so wondering if anyone could help.
You are looking at the difference column by column. This suggests using unpivot. The following creates output with each change in a column, along with the previous value and date:
DECLARE #t TABLE(empid INT,name SYSNAME,salary INT,createddate DATE);
INSERT #t SELECT 1, 'peter', 1000, '20121104'
UNION ALL SELECT 1, 'peter', 2000, '20121105'
UNION ALL SELECT 1, 'pete', 2000, '20121106'
UNION ALL SELECT 1, 'peter', 4000, '20121107';
with cv as (
select empid, createddate, col, val
from (select empid, CAST(name as varchar(8000)) as name,
CAST(salary as varchar(8000)) as salary, createddate
from #t
) t
unpivot (val for col in (name, salary)) as unpvt
),
cvr as (
select cv.*,
ROW_NUMBER() over (partition by empid, col order by createddate) as seqnum_all
from (select cv.*, ROW_NUMBER() over (partition by empid, col, thegroup order by createddate) as seqnum_group
from (select cv.*,
(ROW_NUMBER() over (partition by empid, col order by createddate) -
ROW_NUMBER() over (partition by empid, col, val order by createddate)
) as thegroup
from cv
) cv
) cv
where seqnum_group = 1
) -- select * from cvr
select cvr.*, cvrprev.val as preval, cvrprev.createddate as prevdate
from cvr left outer join
cvr cvrprev
on cvr.empid = cvrprev.empid and
cvr.col = cvrprev.col and
cvr.seqnum_all = cvrprev.seqnum_all + 1
Perhaps these joined CTE's with ROW_NUMBER + CASE:
WITH cte AS
(
SELECT empid,
name,
salary,
rn=ROW_NUMBER()OVER(PARTITION BY empid ORDER BY createddate)
FROM tblemp
)
SELECT oldname=CASE WHEN c1.Name=c2.Name THEN '' ELSE C1.Name END,
newname=CASE WHEN c1.Name=c2.Name THEN '' ELSE C2.Name END,
oldsalary=CASE WHEN c1.salary=c2.salary THEN NULL ELSE C1.salary END,
newsalary=CASE WHEN c1.salary=c2.salary THEN NULL ELSE C2.salary END
FROM cte c1 INNER JOIN cte c2
ON c1.empid=c2.empid AND c2.RN=c1.RN + 1
Sql-Fiddle Demo
DECLARE #t TABLE(empid INT,name SYSNAME,salary INT,createddate DATE);
INSERT #t SELECT 1, 'peter', 1000, '20121104'
UNION ALL SELECT 1, 'peter', 2000, '20121105'
UNION ALL SELECT 1, 'pete', 2000, '20121106'
UNION ALL SELECT 1, 'peter', 4000, '20121107';
;WITH x AS
(
SELECT empid, name, salary, createddate, rn = ROW_NUMBER() OVER
(PARTITION BY empid ORDER BY createddate)
FROM #t
-- WHERE empid = 1 -- for example
)
SELECT LTRIM(
CASE WHEN x.salary <> y.salary THEN
'oldsalary: ' + RTRIM(x.salary)
+ ' newsalary: ' + RTRIM(y.salary)
ELSE '' END
+ CASE WHEN x.name <> y.name THEN
' oldname: ' + x.name
+ ' newname: ' + y.name
ELSE '' END
+ ' (changed on ' + CONVERT(CHAR(10), y.createddate, 101) + ')')
FROM x INNER JOIN x AS y
ON x.rn = y.rn - 1
AND x.empid = y.empid
AND
(
x.salary <> y.salary
OR x.name <> y.name
);
Unless you have a where clause to target a specific empid, however, the output is not very useful unless it also includes empid. SQLfiddle demo
Based on what you explain, It would be easier to create a Trigger when this table is Changed and then create the table with the result you expect, Since you have in that moment the old values and the New values, there should be not a problem to come up with the result you expect.