How can I only pull back the latest result in SQL? - sql

I'm struggling to find out how to only return the latest iteration of the claimtid in the result set. I'm using this query:
SELECT
claimid, paiddate
CASE
WHEN actid = '119' THEN 'Channel Exception'
WHEN actid = '127' THEN 'Rejected'
WHEN actid = '128' THEN 'Accepted'
WHEN actid = '130' THEN 'Adjustment Complete'
WHEN actid = '133' THEN 'Channel Ready'
END AS [Status]
FROM
Encounter
WHERE
claimtid LIKE '173225AR0%' OR claimtid LIKE '197565GL0%' OR
claimtid LIKE '293215QW0%' OR claimtid LIKE
ORDER BY
claimtid
This query returns the following result:
|claimtid |paiddt |Status |
-+----------+----------+-------------------+
1|173225AR00|2017-03-01|Adjustment Complete|
2|173225AR01|2017-04-11|Accepted |
3|197565GL00|2017-03-17|Accepted |
4|197565GL01|2017-03-19|Adjustment Complete|
5|197565GL02|2017-04-01|Rejected |
6|293215QW00|2017-04-19|Adjustment Complete|
7|293215QW01|2017-04-23|Accepted |
I'm not sure what I can add to my query so that the results will only bring back lines 2, 5, and 7. My actual query contains produces more rows in the result.
This is only an example, but is accurate to the situation. I'll need to pull back more than 3 rows, but it needs to be the latest iteration.
Each additional iteration makes the last number in the claimtid go up by one. I won't know how many iterations there are of each claimtid.

Try this, but I am not sure of the performance penalty for using a string for claimtid as in your case:
SELECT claimid, paiddate
FROM
Encounter
WHERE claimtid IN (
SELECT MAX(paiddt), claimtid
FROM Encounter
GROUP BY SUBSTRING (claimtid, 6, 8)
) t
Assuming you can count on the paiddate field to know the most recent record.

If you can't separate the two fields of the claim ID into separate columns of the data table itself, you can pull them apart in a query, to allow you to use the max() aggregate to find the largest value of the second field.
select
claim,
paiddate,
CASE
WHEN actid = '119' THEN 'Channel Exception'
WHEN actid = '127' THEN 'Rejected'
WHEN actid = '128' THEN 'Accepted'
WHEN actid = '130' THEN 'Adjustment Complete'
WHEN actid = '133' THEN 'Channel Ready'
END AS [Status]
from
(
select
left(claimid, 6) as claim,
max(right(claimid,4)) as seq,
from
Encounter
group by
left(claimid, 6)
) as ms
inner join Encounter as e
on e.claimid = ms.claim + ms.seq;

This should do the trick...
IF OBJECT_ID('tempdb..#ClaimData', 'U') IS NOT NULL
DROP TABLE #ClaimData;
CREATE TABLE #ClaimData (
RN INT NOT NULL IDENTITY(1,1),
claimtid CHAR(10) NOT NULL,
paiddt DATE NOT NULL,
[Status] VARCHAR(20) NOT NULL
);
INSERT #ClaimData (claimtid, paiddt, Status) VALUES
('173225AR00', '2017-03-01', 'Adjustment Complete'),
('173225AR01', '2017-04-11', 'Accepted'),
('197565GL00', '2017-03-17', 'Accepted'),
('197565GL01', '2017-03-19', 'Adjustment Complete'),
('197565GL02', '2017-04-01', 'Rejected'),
('293215QW00', '2017-04-19', 'Adjustment Complete'),
('293215QW01', '2017-04-23', 'Accepted');
--SELECT * FROM #ClaimData cd;
--=========================================================
SELECT TOP 1 WITH TIES
cd.RN, cd.claimtid, cd.paiddt, cd.Status
FROM
#ClaimData cd
CROSS APPLY ( VALUES (SUBSTRING(cd.claimtid, 1, 6), SUBSTRING(cd.claimtid, 7, 4)) ) sc (Claim_1, Claim_2)
ORDER BY
ROW_NUMBER() OVER (PARTITION BY sc.Claim_1 ORDER BY sc.Claim_2 DESC);
Results...
RN claimtid paiddt Status
----------- ---------- ---------- --------------------
2 173225AR01 2017-04-11 Accepted
5 197565GL02 2017-04-01 Rejected
7 293215QW01 2017-04-23 Accepted
Edit...
A slightly better performing solution that produces the same results...
SELECT
RN = CAST(SUBSTRING(MAX(bv.BinaryValue), 39, 4) AS INT),
claimtid = CAST(SUBSTRING(MAX(bv.BinaryValue), 1, 10) AS CHAR(10)),
paiddt = CAST(SUBSTRING(MAX(bv.BinaryValue), 11, 8) AS DATE),
Status = CAST(SUBSTRING(MAX(bv.BinaryValue), 19, 20) AS VARCHAR(20))
FROM
#ClaimData cd
CROSS APPLY ( VALUES (CAST(cd.claimtid AS BINARY(10)) + CAST(cd.paiddt AS BINARY(8)) + CAST(cd.Status AS BINARY(20)) + CAST(cd.RN AS BINARY(4))) ) bv (BinaryValue)
GROUP BY
SUBSTRING(cd.claimtid, 1, 6);

Try this, assuming you can't have a early claimtid with a later paiddt:
IF OBJECT_ID('tempdb..#ClaimData') IS NOT NULL
DROP TABLE #ClaimData
CREATE TABLE #ClaimData (
ID INT NOT NULL IDENTITY(1,1)
, claimtid CHAR(10) NOT NULL
, paiddt DATE NOT NULL
, [Status] VARCHAR(20) NOT NULL
)
INSERT #ClaimData (claimtid, paiddt, Status) VALUES
('173225AR00', '2017-03-01', 'Adjustment Complete')
,('173225AR01', '2017-04-11', 'Accepted')
,('197565GL00', '2017-03-17', 'Accepted')
,('197565GL01', '2017-03-19', 'Adjustment Complete')
,('197565GL02', '2017-04-01', 'Rejected')
,('293215QW00', '2017-04-19', 'Adjustment Complete')
,('293215QW01', '2017-04-23', 'Accepted')
SELECT
ID
, x.claimtid
, x.paiddt
, x.Status
FROM (
SELECT
ROW_NUMBER() OVER (PARTITION BY LEFT(Claimtid, LEN(Claimtid) - 2) ORDER BY paiddt DESC) RN
, *
FROM #ClaimData
) x
WHERE x.RN = 1
Otherwise change RN to ROW_NUMBER() OVER (PARTITION BY LEFT(Claimtid, LEN(Claimtid) - 2) ORDER BY RIGHT(Claimtid, 2) DESC) RN

Related

SQL Server SELECT first occurrence OR if no occurrence SELECT other criteria

I am having an issue trying to form the proper SQL query for the job here. I have two tables, one is called CUSTOMER and the other is called CUSTOMER_CONTACT. To simplify this, I will only include the relevant column names.
CUSTOMER columns: ID, CUSTOMERNAME
CUSTOMER_CONTACT columns: ID, CUSTOMER_ID, CONTACT_VC, EMAIL
CUSTOMER_ID is the foreign key to link to the CUSTOMER table from CUSTOMER_CONTACT. CONTACT_VC is just the entry number for their contact information. There could be multiple CUSTOMER_CONTACT records for each customer, but they will have a unique CONTACT_VC.
EMAIL can be null/blank on some or all as well.
I need to select the first CUSTOMER_CONTACT entry where EMAIL is NOT NULL/blank but if none of the CUSTOMER_CONTACT entries have an email address, then select CUSTOMER_CONTACT WHERE CONTACT_VC = 1
Any suggestions on how to accomplish this?
The following approach uses ROW_NUMBER to retrieve a number based on your ordering logic within each CUSTOMER_ID group, then filters by the first record retrieved.
You may try the following:
SELECT
*
FROM (
SELECT
*,
ROW_NUMBER() OVER (
PARTITION BY CUSTOMER_ID
ORDER BY (CASE WHEN EMAIL IS NOT NULL THEN 0 ELSE 1 END),CONTACT_VC
) as rn
FROM
CUSTOMER_CONTACT
) t
WHERE rn=1
If you would like to join this to the customer table you may use the above query as a subquery eg
SELECT
c.*,
contact.*
FROM
CUSTOMER c
INNER JOIN (
SELECT
*,
ROW_NUMBER() OVER (
PARTITION BY CUSTOMER_ID
ORDER BY (CASE WHEN EMAIL IS NOT NULL THEN 0 ELSE 1 END),CONTACT_VC
) as rn
FROM
CUSTOMER_CONTACT
) contact ON c.ID = contact.CUSTOMER_ID and contact.rn=1
Here is almost the same answer as ggordon, but I used a common table expression and I think the ordering in the subquery portion should go by CONTACT_VS first then by non-NULL email addresses. I created some very simple test data to run this:
DECLARE #CUSTOMER AS TABLE
(
[ID] INT NOT NULL,
[CUSTOMERNAME] VARCHAR(10) NOT NULL
);
INSERT INTO #CUSTOMER
(
[ID],
[CUSTOMERNAME]
)
VALUES
(1, 'Alice'),
(2, 'Bob'),
(3, 'Cathy');
DECLARE #CUSTOMER_CONTACT AS TABLE
(
[ID] INT NOT NULL,
[CUSTOMER_ID] INT NOT NULL,
[CONTACT_VC] INT NOT NULL,
[EMAIL] VARCHAR(40) NULL
);
INSERT INTO #CUSTOMER_CONTACT
(
[ID],
[CUSTOMER_ID],
[CONTACT_VC],
[EMAIL]
)
VALUES
(1, 1, 1, 'alice#email.com'),
(2, 1, 2, 'alice#gmail.com'),
(3, 2, 1, NULL),
(4, 2, 2, 'bob#work.com'),
(5, 3, 1, NULL),
(6, 3, 2, NULL),
(7, 3, 3, NULL);
;WITH [cc]
AS (SELECT [ID],
[CUSTOMER_ID],
[CONTACT_VC],
[EMAIL],
ROW_NUMBER() OVER (PARTITION BY [CUSTOMER_ID]
ORDER BY [CONTACT_VC],
(CASE WHEN [EMAIL] IS NOT NULL THEN
0
ELSE
1
END
)
) AS [rn]
FROM #CUSTOMER_CONTACT)
SELECT [c].[ID], [c].[CUSTOMERNAME], [cc].[ID], [cc].[CUSTOMER_ID], [cc].[CONTACT_VC], [cc].[EMAIL]
FROM #CUSTOMER AS [c]
INNER JOIN [cc]
ON [c].[ID] = [cc].[CUSTOMER_ID]
AND [cc].[rn] = 1;
select * from CUSTOMER_CONTACT where EMAIL IS NOT NULL
union all
select * from CUSTOMER_CONTACT where
(CONTACT_VC=1 and NOT EXISTS (select 1 FROM CUSTOMER_CONTACT where EMAIL IS NOT NUL)
order by CONTACT_VC asc limit 1

Select rows after the current row which are null and combine them to rows

I have written the following SQL code to display the data as rows, where the row after data is having null values except on the description column.
DECLARE #StudentData TABLE
(
RowID INT NOT NULL PRIMARY KEY IDENTITY(1,1),
RemarksDate NVARCHAR(20),
StudentName NVARCHAR(1000),
Description NVARCHAR(MAX),
TotStudents NVARCHAR(100)
)
INSERT INTO #StudentData(RemarksDate, StudentName, Description, TotStudents)
VALUES('2/1/2021', NULL, 'Poor In English', '14'),
(NULL, NULL, '1 ABC', NULL),
(NULL, NULL, '1 XYZ', NULL),
(NULL, NULL, '1 MNO', NULL),
(NULL, NULL, '1 IGH', NULL),
(NULL, NULL, '10 KKK', NULL),
('2/1/2021', NULL, 'Poor In Maths', '5'),
(NULL, NULL, '5 PQR', NULL),
('2/8/2021', NULL, 'Poor In Social', '1'),
(NULL, NULL, '1 RST', NULL)
This results in the output as follows:
I have written the following query to group and display rows:
SELECT t1.RemarksDate, LTRIM(RIGHT(t2.Description, LEN(t2.Description) - PATINDEX('%[0-9][^0- 9]%', t2.Description ))) StudentName, t1.Description
,LEFT(t2.Description, PATINDEX('%[0-9][^0-9]%', t2.Description ))
FROM (
SELECT *, RowID + TotStudents MaxVal
FROM #StudentData
WHERE RemarksDate is NOT NULL
) t1
JOIN (
SELECT *
FROM #StudentData
WHERE RemarksDate is NULL
) t2 ON t2.RowId BETWEEN t1.RowID and t1.MaxVal
The data is displayed as follows
Expected output is as follows
2/1/2021 ABC Poor In English 1
2/1/2021 XYZ Poor In English 1
2/1/2021 MNO Poor In English 1
2/1/2021 IGH Poor In English 1
2/1/2021 KKK Poor In English 10
2/1/2021 PQR Poor In Maths 5
2/8/2021 RST Poor In Social 1
This is a type of gaps-and-islands problem. There are many solutions, I will give you one that only requires a single scan of the base table.
We have a header row and child rows, and we need to apply the header row values to the child rows.
We can solve this by defining the start point of each group, then taking windowed header values for each group and finally filtering out the header rows
WITH Groupings AS (
SELECT *,
GroupId = MAX(CASE WHEN Description LIKE 'Poor%' THEN RowID END)
OVER (ORDER BY RowID ROWS UNBOUNDED PRECEDING)
FROM #StudentData s
),
GroupValues AS (
SELECT
RemarksDate = MAX(CASE WHEN Description LIKE 'Poor%' THEN RemarksDate END)
OVER (PARTITION BY GroupId),
DescriptionHeader = MAX(CASE WHEN Description LIKE 'Poor%' THEN Description END)
OVER (PARTITION BY GroupId),
Space = CHARINDEX(' ', Description),
Description
FROM Groupings
)
SELECT
RemarksDate,
DescriptionHeader,
StudentName = SUBSTRING(Description, Space + 1, LEN(Description)),
SomeNumber = LEFT(Description, Space - 1)
FROM GroupValues
WHERE Description NOT LIKE 'Poor%';
db<>fiddle
Except the fact that the table design is pretty awful, I would suggest the following approach:
WITH cteRemarks AS(
SELECT *, LEAD(RowId) OVER (ORDER BY RowID) AS RowIdNxt
FROM #StudentData
WHERE TotStudents IS NOT NULL
)
SELECT r.RemarksDate
,RIGHT(t.Description, LEN(t.Description)-CHARINDEX(' ', t.Description)) AS StudentsName
,r.Description AS Description
,LEFT(t.Description, CHARINDEX(' ', t.Description)-1) AS Val
FROM cteRemarks r
LEFT JOIN #StudentData t ON t.TotStudents IS NULL
AND t.RowID > r.RowID
AND t.RowID < ISNULL(r.RowIDNxt, 99999999)

SQL number greater than select results

I'm struggling to think of a way to do this with T-SQL.
I have a table which is populated every 5 seconds with the prices of three currencies (GBP, EUR & USD)
I've created a trigger (after insert), which selects the last 5 records entered for a given currency:
SELECT TOP 5 Price from dbo.prices where coin='GBP' ORDER BY Date Desc
I want to determine if the last inserted currency price is greater than the selected 5 above, how do i do this?
Thanks
As I guess: there cant be two entries for the same currency at one time. Only one insert per currency per some time (5sec). So this should fit yours requirements:
declare #prices table ([Date] int IDENTITY(1,1) primary key, Price float, coin varchar(3));
insert into #prices (coin, Price) values
('GBP', 3.20),('EUR', 3.14),('USD', 3.14),
('GBP', 3.17),('EUR', 3.16),('USD', 3.11),
('GBP', 3.14),('EUR', 3.13),('USD', 3.16),
('GBP', 3.15),('EUR', 3.12),('USD', 3.17),
('GBP', 3.16),('EUR', 3.17),('USD', 3.11),
('GBP', 3.15),('EUR', 3.14),('USD', 3.12),
('GBP', 3.19),('EUR', 3.14),('USD', 3.16)
select
case
when NEW.Price > PREV.Price Then 'yes'
else 'No'
end as CURR_JUMP_UP
from
(
select top 1 COALESCE(Price,0) Price, [Date]
from #prices where coin='GBP' order by [Date] desc
) NEW
cross apply
(
select MAX(Price) Price from
(
select top 5 Price
from #prices
where coin='GBP' and [Date]<NEW.[Date]
order by [Date] desc
) t
) PREV
Try this query:
DECLARE #AmountLastFiveEntry DECIMAL= (SELECT TOP 5 SUM(Price) FROM dbo.prices WHERE
ID NOT IN (SELECT TOP 1 ID
FROM dbo.prices where coin='GBP' ORDER BY Date Desc) where coin='GBP' ORDER BY Date Desc)
IF #AmountLastFiveEntry<(SELECT TOP 1 Price
FROM dbo.prices where coin='GBP' ORDER BY Date Desc)
BEGIN
SELECT #AmountLastFiveEntry --To do task
END
Trigger part is confusing
This will report if the latest price is higher (or equal) to the largest of the prior 5.
declare #currency table (iden int IDENTITY(1,1) primary key, exchange smallint, coin tinyint);
insert into #currency (coin, exchange) values
(1, 1)
, (1, 2)
, (1, 3)
, (1, 4)
, (1, 5)
, (1, 6)
, (2, 1)
, (2, 2)
, (2, 3)
, (2, 4)
, (2, 5)
, (2, 3);
select cccc.coin, cccc.exchange
, case when cccc.rn = cccc.rne then 'yes'
else 'no'
end as 'high'
from ( select ccc.iden, ccc.coin, ccc.exchange, ccc.rn
, ROW_NUMBER() over (partition by ccc.coin order by ccc.exchange desc, ccc.rn) rne
from ( select cc.iden, cc.coin, cc.exchange, cc.rn
from ( select c.iden, c.coin, c.exchange
, ROW_NUMBER() over (partition by coin order by iden desc) as rn
from #currency c
) cc
where cc.rn <= 6
) ccc
) cccc
where cccc.rn = 1
order by cccc.coin

How to replace the 'Strings' with numerical values based on a group by clause

I have the below table (#temp1) where I need to replace the string in the column'Formula' with the matching input 'VALUE' column based on the group 'Yearmonth'.
The 'Formula' column may be of any mathematical expression for better understanding I have mentioned a simple example below.
IDNUM formula INPUTNAME VALUE YEARMONTH
---------------------------------------------------------------------
1 imports(398)+imports(399) imports(398) 17.000 2003:1
2 imports(398)+imports(399) imports(398) 56.000 2003:2
3 imports(398)+imports(399) imports(399) 15.000 2003:1
4 imports(398)+imports(399) imports(399) 126.000 2003:2
For eg :From the above table i need the output as
Idnum Formula Yearmonth
1. 17.00 +15.00 2003:1
2. 56.00 +126.00 2003:2
I tried with the below different query from various suggestions but coludnt achieve it. Could someone help me this out ?
Type1 :
SELECT
REPLACE(FORMULA, INPUTName, AttributeValue) AS realvalues,
yearmonth
FROM #temp1
GROUP BY yearmonth
TYPE2 :
USING XML PATH... In this case it got worked but I need to replace only the strings with the values and not to stuff the strings based on mathematcal operation.(Because the formula might be of any type).
SELECT
IDNUM = MIN(IDNUM),
FORMULA =
(SELECT STUFF(
(SELECT ' +' + CONVERT(VARCHAR(10), Value)
FROM #temp1
WHERE YEARMONTH = t1.YEARMONTH
FOR XML PATH(''))
,1, 2, '')),
YEARMONTH
FROM #TEMP1 t1
GROUP BY YEARMONTH
TYPE3:Using Recursions...This is returning only the null values...
;with t as (
select t.*,
row_number() over (partition by yearmonth order by idnum) as seqnum,
count(*) over (partition by yearmonth) as cnt
from #temp1 t
)
,cte as (
select t.seqnum, t.yearmonth, t.cnt,
replace(formula, inputname, AttributeValue) as formula1
from t
where seqnum = 1
union all
select cte.seqnum, cte.yearmonth, cte.cnt,
replace(CTE.formula1, T.inputname, T.AttributeValue) as formula2
from cte join
t
on cte.yearmonth = t.yearmonth
AND cte.seqnum = t.seqnum + 1
)
select row_number() over (order by (select null)) as id,formula1
from cte
where seqnum = cnt
This is full working example using recursive CTE:
DECLARE #DataSource TABLE
(
[IDNUM] TINYINT
,[formula] VARCHAR(MAX)
,[INPUTNAME] VARCHAR(128)
,[VALUE] DECIMAL(9,3)
,[YEARMONTH] VARCHAR(8)
);
INSERT INTO #DataSource ([IDNUM], [formula], [INPUTNAME], [VALUE], [YEARMONTH])
VALUES ('1', 'imports(398)+imports(399)', 'imports(398)', '17.000', '2003:1')
,('2', 'imports(398)+imports(399)', 'imports(398)', '56.000', '2003:2')
,('3', 'imports(398)+imports(399)', 'imports(399)', '15.000', '2003:1')
,('4', 'imports(398)+imports(399)', 'imports(399)', '126.000', '2003:2')
,('5', '(imports(391)+imports(392)-imports(393))/imports(394)', 'imports(391)', '5.000', '2003:3')
,('6', '(imports(391)+imports(392)-imports(393))/imports(394)', 'imports(392)', '10.000', '2003:3')
,('7', '(imports(391)+imports(392)-imports(393))/imports(394)', 'imports(393)', '3.000', '2003:3')
,('8', '(imports(391)+imports(392)-imports(393))/imports(394)', 'imports(394)', '-5.000', '2003:3');
WITH DataSource AS
(
SELECT ROW_NUMBER() OVER(PARTITION BY [YEARMONTH] ORDER BY [IDNUM]) AS [ReplacementOrderID]
,[YEARMONTH]
,[formula]
,[INPUTNAME] AS [ReplacementString]
,[VALUE] AS [ReplacementValue]
FROM #DataSource
),
RecursiveDataSource AS
(
SELECT [ReplacementOrderID]
,[YEARMONTH]
,REPLACE([formula], [ReplacementString], [ReplacementValue]) AS [formula]
FROM DataSource
WHERE [ReplacementOrderID] = 1
UNION ALL
SELECT DS.[ReplacementOrderID]
,DS.[YEARMONTH]
,REPLACE(RDS.[formula], DS.[ReplacementString], DS.[ReplacementValue]) AS [formula]
FROM RecursiveDataSource RDS
INNER JOIN DataSource DS
ON RDS.[ReplacementOrderID] + 1 = DS.[ReplacementOrderID]
AND RDS.[YEARMONTH] = DS.[YEARMONTH]
)
SELECT RDS.[YEARMONTH]
,RDS.[formula]
FROM RecursiveDataSource RDS
INNER JOIN
(
SELECT [YEARMONTH]
,MAX([ReplacementOrderID]) AS [ReplacementOrderID]
FROM DataSource
GROUP BY [YEARMONTH]
) DS
ON RDS.[YEARMONTH] = DS.[YEARMONTH]
AND RDS.[ReplacementOrderID] = DS.[ReplacementOrderID]
ORDER BY RDS.[YEARMONTH]
Generally, you simply want to perform multiple replacements over a string in one statement. You can have many replacement values, just play with the MAXRECURSION option.
--Create sample data
DROP TABLE #temp1
CREATE TABLE #temp1 (IDNUM int, formula varchar(max), INPUTNAME varchar(max), VALUE decimal, YEARMONTH varchar(max))
INSERT INTO #temp1 VALUES
(1, 'imports(398)+imports(399)', 'imports(398)', 17.000, '2003:1'),
(2, 'imports(398)+imports(399)', 'imports(398)', 56.000, '2003:2'),
(3, 'imports(398)+imports(399)', 'imports(399)', 15.000, '2003:1'),
(4, 'imports(398)+imports(399)', 'imports(399)', 126.000, '2003:2')
--Query
;WITH t as (
SELECT formula, YEARMONTH, IDNUM
FROM #temp1
UNION ALL
SELECT REPLACE(a.formula, b.INPUTNAME, CAST(b.VALUE AS varchar(100))) AS formula, a.YEARMONTH, a.IDNUM
FROM t a
JOIN #temp1 b ON a.YEARMONTH = b.YEARMONTH AND a.formula LIKE '%' + b.INPUTNAME + '%'
)
SELECT MIN(IDNUM) AS IDNUM, formula, YEARMONTH
FROM t
WHERE formula not LIKE '%imports(%'
GROUP BY formula, YEARMONTH

Excluding blank rows in CTE query

I am new to using CTE queries in SQL Server. I've built this query with help from the web in an effort to start building my "change log" to highlight changes made in my database. Please see example linked below. I'd like to exclude rows where there are no changes. Can you assist in how to accomplish this?
Row #3 with Nov 7 changedate has blank values. I would like for this row to not display. I also don't want to have to do something like WHERE row1 <> '' AND row2 <> '' AND row3 <> '', etc, because my final query will contain much much more rows. Is this possible?
http://sqlfiddle.com/#!6/134bd/4/0
Here is an option that you can use.
Below uses COALESCE function.
Using the same data and just modified your code from sqlfiddle.
The CASE statement to return NULL when match and cast data types to varchar, then use COALESCE in the where
Below modified script to include nTEXT column. You can use DATALENGTH with COALESCE in the WHERE clause.
Modified:
CREATE TABLE tblEmp
([memid] int, [empid] int, [name] varchar(50),[salary] int, [room] varchar(50), changedate datetime, ntxt ntext);
INSERT INTO tblEmp
([memid], [empid], [name], [salary], [room], [changedate], [ntxt])
VALUES
(41, 1, 'peter', 1000, 'Regency', '11/4/2012', ''),
(43, 1, 'peterz', 2000, 'Regency','11/5/2013', 'nn') ,
(44, 1, 'peterz', 2000, 'Regency','11/7/2013', '') ,
(45, 4, 'sally', 2001, 'Sheratio','11/2/2013', '') ,
(46, 4, 'sally', 2000, 'Sheraton','11/6/2013', ''),
(47, 1, 'peter', 3000, 'Regency','12/4/2013', '') ,
(48, 4, 'sallye', 2000,'Sheraton 1', '11/9/2013', '') ,
(49, 4, 'sally', 3000, 'Sheraton','11/6/2013', 'kljslkdjflkajslkjasdlkjalskjdlakjsdlkjasldjfk')
;
WITH cte AS
(
SELECT
empid,
name,
salary, room,
changedate,
ntxt,
rn=ROW_NUMBER()OVER(PARTITION BY empid ORDER BY changedate)
FROM tblemp
)
SELECT *
FROM
(
SELECT c1.empid, oldname=CASE WHEN c1.Name=c2.Name THEN NULL ELSE C1.Name END,
newname=CASE WHEN c1.Name=c2.Name THEN NULL 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,
oldroom=CASE WHEN c1.Room=c2.Room THEN NULL ELSE C1.Room END,
newroom=CASE WHEN c1.room=c2.room THEN NULL ELSE C2.room END,
c2.changedate
, c2.ntxt
FROM cte c1 INNER JOIN cte c2
ON c1.empid=c2.empid AND c2.RN=c1.RN+1
) x
WHERE NOT (COALESCE(oldname, newname, CAST(oldsalary AS VARCHAR), CAST(newsalary AS VARCHAR), CAST(oldroom AS VARCHAR), CAST(newroom AS VARCHAR)) is null
AND DATALENGTH(ntxt) = 0)
ORDER BY ChangeDate DESC
A slightly different approach with SQL Server 2012 would be to use LAG in the common table expression to be able to detect row by row changes. The CTE basically pulls out each row along with the relevant data from the previous row, and does a straight forward compare in the outer query to generate the result.
WITH cte AS (
SELECT
empid, changeDate,
LAG(name) OVER (PARTITION BY empid ORDER BY changeDate) oldname, name,
LAG(salary) OVER (PARTITION BY empid ORDER BY changeDate) oldsalary, salary,
LAG(room) OVER (PARTITION BY empid ORDER BY changeDate) oldroom, room
FROM tblEmp
)
SELECT empid,
CASE WHEN name<>oldname THEN oldname ELSE '' END oldname,
CASE WHEN name<>oldname THEN name ELSE '' END newname,
CASE WHEN salary<>oldsalary THEN oldsalary ELSE '' END oldsalary,
CASE WHEN salary<>oldsalary THEN salary ELSE '' END newsalary,
CASE WHEN room<>oldroom THEN oldroom ELSE '' END oldroom,
CASE WHEN room<>oldroom THEN room ELSE '' END newroom,
changeDate
FROM cte
WHERE oldname<>name OR oldsalary<>salary OR oldroom<>room
ORDER BY empid, changeDate;
An SQLfiddle to test with.