SQL Update question - sql

I am wondering how this can be achieved.
Let's say I have a table with two columns (IU(uniqueidentifier),(ID(int), SEL(char(1))
ID column has the following values in each row(ordered by IU):
0, 1, 2, 2, 0, 0, 1, 2, 2, 2, 0, 0, 4, 2, 2, 0, 0, 1, 2, 0, 0
I need to update column SEL with 'Y' for rows which are part of the group:
1, 2, 2, 2 ...
(Starts With 1 and in the next rows thare are 2's. (Group 4, 2, 2 is not correct).
So in this example column: SEL should be:
null, Y, Y, Y, null, null, Y, Y, Y, Y, null, null, 4, 2, 2, null, null, Y, Y, null, null
Thanks!

Here's a set-based approach.
DDL & sample data:
DECLARE #atable TABLE (
UI uniqueidentifier DEFAULT NEWSEQUENTIALID(),
ID int,
SEL char(1)
);
INSERT INTO #atable (ID)
SELECT 0 UNION ALL
SELECT 1 UNION ALL
SELECT 2 UNION ALL
SELECT 2 UNION ALL
SELECT 0 UNION ALL
SELECT 0 UNION ALL
SELECT 1 UNION ALL
SELECT 2 UNION ALL
SELECT 2 UNION ALL
SELECT 2 UNION ALL
SELECT 0 UNION ALL
SELECT 0 UNION ALL
SELECT 4 UNION ALL
SELECT 2 UNION ALL
SELECT 2 UNION ALL
SELECT 0 UNION ALL
SELECT 0 UNION ALL
SELECT 1 UNION ALL
SELECT 2 UNION ALL
SELECT 0 UNION ALL
SELECT 0;
The UPDATE statement:
WITH marked AS (
SELECT
*,
grp = CASE ID WHEN 0 THEN 0 ELSE 1 END
FROM #atable
),
grouped AS (
SELECT
*,
grpID = ROW_NUMBER() OVER (ORDER BY UI)
- ROW_NUMBER() OVER (PARTITION BY grp ORDER BY UI)
FROM marked
),
ranked AS (
SELECT
*,
rnk = ROW_NUMBER() OVER (PARTITION BY grp, grpID ORDER BY UI)
FROM grouped
)
UPDATE g
SET SEL = CASE r.ID
WHEN 0 THEN NULL
WHEN 1 THEN 'Y'
ELSE CAST(g.ID AS varchar)
END
FROM grouped g
INNER JOIN ranked r ON g.grp = r.grp AND g.grpID = r.grpID
WHERE r.rnk = 1;
The result of SELECT * FROM #atable after the update:
UI ID SEL
------------------------------------ ----------- ----
A4095E70-A0CC-E011-813B-20CF30905E89 0 NULL
A5095E70-A0CC-E011-813B-20CF30905E89 1 Y
A6095E70-A0CC-E011-813B-20CF30905E89 2 Y
A7095E70-A0CC-E011-813B-20CF30905E89 2 Y
A8095E70-A0CC-E011-813B-20CF30905E89 0 NULL
A9095E70-A0CC-E011-813B-20CF30905E89 0 NULL
AA095E70-A0CC-E011-813B-20CF30905E89 1 Y
AB095E70-A0CC-E011-813B-20CF30905E89 2 Y
AC095E70-A0CC-E011-813B-20CF30905E89 2 Y
AD095E70-A0CC-E011-813B-20CF30905E89 2 Y
AE095E70-A0CC-E011-813B-20CF30905E89 0 NULL
AF095E70-A0CC-E011-813B-20CF30905E89 0 NULL
B0095E70-A0CC-E011-813B-20CF30905E89 4 4
B1095E70-A0CC-E011-813B-20CF30905E89 2 2
B2095E70-A0CC-E011-813B-20CF30905E89 2 2
B3095E70-A0CC-E011-813B-20CF30905E89 0 NULL
B4095E70-A0CC-E011-813B-20CF30905E89 0 NULL
B5095E70-A0CC-E011-813B-20CF30905E89 1 Y
B6095E70-A0CC-E011-813B-20CF30905E89 2 Y
B7095E70-A0CC-E011-813B-20CF30905E89 0 NULL
B8095E70-A0CC-E011-813B-20CF30905E89 0 NULL

Rows in a table have no inherent order, so your grouping (1,2,2,2) is completely arbitrary. It is not guaranteed that your id's will always come in this order:
0, 1, 2, 2, 0, 0, 1, 2, 2, 2, 0, 0, 4, 2, 2, 0, 0, 1, 2, 0, 0
It could be that they come in a completely other order. So you need to specify a ORDER BY clause to get your order. As you have no other fields in your table but SEL and ID, I suppose this is not possible.

I really hope someone comes up with something better than this, because I hate this answer.
create table #test (
IU int identity primary key,
id int,
sel varchar(1)
)
insert into #test(id)
values (0), (1), (2), (2), (0), (0), (1), (2), (2), (2), (0), (0), (4), (2), (2), (0), (0), (1), (2), (0), (0)
DECLARE myCur CURSOR FORWARD_ONLY
FOR
select t.ID
from #test t
order by t.IU
FOR UPDATE OF t.sel
DECLARE #ID int, #lagSel varchar(1)
OPEN myCur
FETCH myCur INTO #ID
WHILE (##FETCH_STATUS = 0) BEGIN
SET #lagSel = CASE
WHEN #lagSel = 'Y' AND #ID in (1,2) THEN 'Y'
WHEN #ID = 1 THEN 'Y'
ELSE NULL
END
UPDATE #test
SET sel = #lagSel
WHERE CURRENT OF myCur
FETCH myCur INTO #ID
END
CLOSE myCur
DEALLOCATE myCur
A couple of things to note:
We're manually managing the value of #lagSel within the cursor so we can carry a value from one row to the next.
In order to be able to use the cursor FOR UPDATE, the table has to have a primary key.
In the UPDATE statement, the WHERE CURRENT OF myCur gives (at least in theory) a big performance gain over any other where clause.
I first tried doing this with lagged joins, but couldn't quite get it there. Here's my work in case someone else can do better:
select main.IU, main.id,
CASE
WHEN main.id = 1 THEN 'Y'
WHEN main.id = 2 AND lag.id in (1, 2) THEN 'Y'
ELSE NULL
END as new_sel
from #test main left outer join
#test lag on main.IU = lag.IU + 1

I think you have the design error here. MS SQL Server does not knows nothing about "next" and "previous" rows. if you try to select the records, the order of the records can be changed from time to time, unless you specify the ordering using ORDER BY statement.
I think you need to change the structure of the tables first.
EDIT: As I see, you have the field and can order your records. Now you can achive your goal using CURSOR.
Briefly, you can create the CURSOR FOR SELECT IU, ID ORDER BY IU ASC.
looping through the cursor records you can check the sequence of the values of ID field, and when sequence will be fully equivalent, you can update the corresponding record.

Related

Choose a record from the table where two of the default values are 0 or 1, a bit tricky

Looking for a more elegant and most logical solution for:
The table:
index
id
by_default
text
1
1
0
AAA
2
1
1
ABA
3
1
0
ABC
4
2
0
BCA
5
2
0
BCB
The task is to find the minimum index value with defaults set to 1 and/or defaults set to 0.
I have the following code (not very elegant, but it works, also very slow):
declare #byd_1 as int=
(select min(t.index) idx from Table t where t.[id]=1 and t.by_default=1)
declare #byd_2 as int=
(select min(t.index) idx from Table t where t.[id]=1 and t.by_default=0)
select (case when #byd_1 is null then #byd_2 else #byd_1 end)
The tricky part is: sometimes the by_default column is always 0 (for example: id:2 may have no by_default values set) and as mentioned earlier the task is: need to get the minimum value of the index column.
What is the most elegant (one-line) code possible?
Using MSSQL
The expected results, according to the sample table, should be the following:
index
id
by_default
text
2
1
1
ABA
4
2
0
BCA
Edited to add text also.
A bit ugly but:
drop table #t
select *
into #t
from (
VALUES (1, 1, 0, N'AAA')
, (2, 1, 1, N'ABA')
, (3, 1, 0, N'ABC')
, (4, 2, 0, N'BCA')
, (5, 2, 0, N'BCB')
) t (index_,id,by_default,text)
select index_, id, by_default, text
from (
select min(index_) OVER(PARTITION BY id) AS minIndex
, MIN(case when by_default = 1 then index_ end) over(partition by id) AS minIndexDefault
, *
from #t
) t
where isnull(minIndexDefault, minIndex) = index_

sql SERVER - distinct selection based on priority columns

hello I would like to find a solution to solve my problem in a single request if possible.
For the moment I take all the records then I go through the lines one by one to eliminate what I don't want.
I have 2 tables : first table with links
the second with the prefered label for the url
the second table must be consulted keeping only the row with maximum priority
priority rules are
the current user then
the user group and finally
everyone.
if the hidden column is true, exclude any reference to the url
here is the expected result.
Unfortunately, I don't see any other solution than to multiply the conditions on several selects and unions.
if you have a idea to solve my problem, thank you in advance for your help
It appears as though you can rely on pref_id for the preference ordering, correct? If so, you could try:
SELECT *
FROM table2
INNER JOIN table1 ON table2.url_id = table1.url_id
QUALIFY ROW_NUMBER() OVER (
PARTITION BY table1.url
ORDER BY pref_id ASC
) = 1
This will partition by the url and then provide only the one with lowest pref_id.
I didn't test this SQL as I wasn't sure which RDBMS you're running on, but I used Rasgo to translate the SQL.
maybe of interest in this tricky query:
select so.*, table1.url from
(select distinct t.url_id,
(select pref_id from table2 s where s.url_id = t.url_id order by "user" is null, "group" is null limit 1) pref_id
from table2 t
where not exists(select 1 from table2 s where s.hide and s.url_id = t.url_id)
) ids
join table2 so on so.pref_id = ids.pref_id
join table1 ON table1.url_id = ids.url_id
order by so.url_id;
here is my solution but i think there is better to do.
in the condition's select, I built a column which gives a level note according to the priorities
DECLARE #CUR_USER VARCHAR(10) = 'ROBERT'
DECLARE #CUR_GROUP VARCHAR(10) = 'DEV'
DECLARE #TABLE1 TABLE (
URL_ID INT
,URLNAME VARCHAR(100)
);
DECLARE #TABLE2 TABLE (
PREF_ID INT
,URL_ID INT
,FAVORITE_LABEL VARCHAR(100)
,USER_GROUP VARCHAR(10)
,USER_CODE VARCHAR(10)
,HIDE_URL DECIMAL(1, 0) DEFAULT 0
);
INSERT INTO #TABLE1
VALUES
(1, 'https://stackoverflow.com/')
,(2, 'https://www.microsoft.com/')
,(3, 'https://www.apple.com/')
,(4, 'https://www.wikipedia.org/')
;
INSERT INTO #TABLE2
VALUES
(1000, 1, 'find everything', NULL, 'ROBERT', 0)
,(1001, 1, 'a question ? find the answer here', 'DEV', NULL, 0)
,(1002, 1, 'StackOverFlow', NULL, NULL, 0)
,(1003, 2, 'Windows', 'DEV', NULL, 0)
,(1004, 2, 'Microsoft', NULL, NULL, 0)
,(1005, 3, 'Apple', NULL, NULL, 0)
,(1006, 4, 'Free encyclopedia', NULL, 'ROBERT', 1)
,(1007, 4, 'Wikipedia', NULL, NULL, 0)
,(1008, 1, 'StackOverFlow FOR MAT', 'MAT', NULL, 0)
,(1009, 2, 'Microsoft FOR MAT', 'MAT', NULL, 0)
,(1010, 3, 'Apple', 'MAT', NULL, 1)
,(1011, 4, 'Wikipedia FOR MAT', 'MAT', NULL, 0)
,(1012, 1, 'StackOverFlow', NULL, 'JEAN', 1)
,(1013, 2, 'Microsoft ', NULL, 'JEAN', 0)
,(1014, 3, 'Apple', NULL, 'JEAN', 0)
,(1015, 4, 'great encyclopedia', NULL, 'JEAN', 0)
;
SELECT t2.* ,t1.URLName
FROM #TABLE1 t1
INNER JOIN #TABLE2 t2 ON t1.URL_ID = t2.URL_ID
WHERE EXISTS (
SELECT 1
FROM (
SELECT TOP (1) test.PREF_ID
,CASE
-- if I do not comment this case: jean from the MAT group will not see apple
-- WHEN Hide_Url = 1
-- THEN 3
WHEN USER_code IS NOT NULL
THEN 2
WHEN USER_GROUP IS NOT NULL
THEN 1
ELSE 0
END AS ROW_LEVEL
FROM #TABLE2 test
WHERE (
(
test.USER_GROUP IS NULL
AND test.user_group IS NULL
AND test.USER_code IS NULL
)
OR (test.USER_GROUP = #CUR_GROUP)
OR (test.USER_code = #CUR_USER)
)
AND t2.URL_ID = test.URL_ID
ORDER BY ROW_LEVEL DESC
) test
WHERE test.PREF_ID = t2.PREF_ID
AND Hide_Url = 0
)
Simply use an ORDER BY clause that puts the preferred row first. You can use this in the window function ROW_NUMBER and work with this or use a lateral top(1) join with CROSS APPLY.
select *
from urls
cross apply
(
select top(1) *
from labels
where labels.url_id = urls.url_id
where [Group] is not null or [user] is not null or hide is not null
order by
case when [Group] is null then 2 else 1 end,
case when [user] is null then 2 else 1 end,
case when hide is null then 2 else 1 end
) top_labels
order by urls.url_id;

SQL: Pinned rows and row number calculation

We have a requirement to assign row number to all rows using following rule
Row if pinned should have same row number
Otherwise sort it by GMD
Example:
ID GMD IsPinned
1 2.5 0
2 0 1
3 2 0
4 4 1
5 3 0
Should Output
ID GMD IsPinned RowNo
5 3 0 1
2 0 1 2
1 2.5 0 3
4 4 1 4
3 2 0 5
Please Note row number for Id's 2 and 4 stayed intact as they are pinned with values of 2 and 4 respectively even though the GMD are not in any order
Rest of rows Id's 1, 3 and 5 row numbers are sorted using GMD desc
I tried using RowNumber SQL 2012 however, it is pushing pinned items from their position
Here's a set-based approach to solving this. Note that the first CTE is unnecessary if you already have a Numbers table in your database:
declare #t table (ID int,GMD decimal(5,2),IsPinned bit)
insert into #t (ID,GMD,IsPinned) values
(1,2.5,0), (2, 0 ,1), (3, 2 ,0), (4, 4 ,1), (5, 3 ,0)
;With Numbers as (
select ROW_NUMBER() OVER (ORDER BY ID) n from #t
), NumbersWithout as (
select
n,
ROW_NUMBER() OVER (ORDER BY n) as rn
from
Numbers
where n not in (select ID from #t where IsPinned=1)
), DataWithout as (
select
*,
ROW_NUMBER() OVER (ORDER BY GMD desc) as rn
from
#t
where
IsPinned = 0
)
select
t.*,
COALESCE(nw.n,t.ID) as RowNo
from
#t t
left join
DataWithout dw
inner join
NumbersWithout nw
on
dw.rn = nw.rn
on
dw.ID = t.ID
order by COALESCE(nw.n,t.ID)
Hopefully my naming makes it clear what we're doing. I'm a bit cheeky in the final SELECT by using a COALESCE to get the final RowNo when you might have expected a CASE expression. But it works because the contents of the DataWithout CTE is defined to only exist for unpinned items which makes the final LEFT JOIN fail.
Results:
ID GMD IsPinned RowNo
----------- --------------------------------------- -------- --------------------
5 3.00 0 1
2 0.00 1 2
1 2.50 0 3
4 4.00 1 4
3 2.00 0 5
Second variant that may perform better (but never assume, always test):
declare #t table (ID int,GMD decimal(5,2),IsPinned bit)
insert into #t (ID,GMD,IsPinned) values
(1,2.5,0), (2, 0 ,1), (3, 2 ,0), (4, 4 ,1), (5, 3 ,0)
;With Numbers as (
select ROW_NUMBER() OVER (ORDER BY ID) n from #t
), NumbersWithout as (
select
n,
ROW_NUMBER() OVER (ORDER BY n) as rn
from
Numbers
where n not in (select ID from #t where IsPinned=1)
), DataPartitioned as (
select
*,
ROW_NUMBER() OVER (PARTITION BY IsPinned ORDER BY GMD desc) as rn
from
#t
)
select
dp.ID,dp.GMD,dp.IsPinned,
CASE WHEN IsPinned = 1 THEN ID ELSE nw.n END as RowNo
from
DataPartitioned dp
left join
NumbersWithout nw
on
dp.rn = nw.rn
order by RowNo
In the third CTE, by introducing the PARTITION BY and removing the WHERE clause we ensure we have all rows of data so we don't need to re-join to the original table in the final result in this variant.
this will work:
CREATE TABLE Table1
("ID" int, "GMD" number, "IsPinned" int)
;
INSERT ALL
INTO Table1 ("ID", "GMD", "IsPinned")
VALUES (1, 2.5, 0)
INTO Table1 ("ID", "GMD", "IsPinned")
VALUES (2, 0, 1)
INTO Table1 ("ID", "GMD", "IsPinned")
VALUES (3, 2, 0)
INTO Table1 ("ID", "GMD", "IsPinned")
VALUES (4, 4, 1)
INTO Table1 ("ID", "GMD", "IsPinned")
VALUES (5, 3, 0)
SELECT * FROM dual
;
select * from (select "ID","GMD","IsPinned",rank from(select m.*,rank()over(order by
"ID" asc) rank from Table1 m where "IsPinned"=1)
union
(select "ID","GMD","IsPinned",rank from (select t.*,rank() over(order by "GMD"
desc)-1 rank from (SELECT * FROM Table1)t)
where "IsPinned"=0) order by "GMD" desc) order by rank ,GMD;
output:
2 0 1 1
5 3 0 1
1 2.5 0 2
4 4 1 2
3 2 0 3
Can you try this query
CREATE TABLE Table1
(ID int, GMD numeric (18,2), IsPinned int);
INSERT INTO Table1 (ID,GMD, IsPinned)
VALUES (1, 2.5, 0),
(2, 0, 1),
(3, 2, 0),
(4, 4, 1),
(5, 3, 0)
select *, row_number () over(partition by IsPinned order by (case when IsPinned =0 then GMD else id end) ) [CustOrder] from Table1
This took longer then I thought, the thing is row_number would take a part to resolve the query. We need to differentiate the row_numbers by id first and then we can apply the while loop or cursor or any iteration, in our case we will just use the while loop.
dbo.test (you can replace test with your table name)
1 2.5 False
2 0 True
3 3 False
4 4 True
6 2 False
Here is the query I wrote to achieve your result, I have added comment under each operation you should get it, if you have any difficultly let me know.
Query:
--user data table
DECLARE #userData TABLE
(
id INT NOT NULL,
gmd FLOAT NOT NULL,
ispinned BIT NOT NULL,
rownumber INT NOT NULL
);
--final result table
DECLARE #finalResult TABLE
(
id INT NOT NULL,
gmd FLOAT NOT NULL,
ispinned BIT NOT NULL,
newrownumber INT NOT NULL
);
--inserting to uer data table from the table test
INSERT INTO #userData
SELECT t.*,
Row_number()
OVER (
ORDER BY t.id ASC) AS RowNumber
FROM test t
--creating new table for ids of not pinned
CREATE TABLE #ids
(
rn INT,
id INT,
gmd FLOAT
)
-- inserting into temp table named and adding gmd by desc
INSERT INTO #ids
(rn,
id,
gmd)
SELECT DISTINCT Row_number()
OVER(
ORDER BY gmd DESC) AS rn,
id,
gmd
FROM #userData
WHERE ispinned = 0
--declaring the variable to loop through all the no pinned items
DECLARE #id INT
DECLARE #totalrows INT = (SELECT Count(*)
FROM #ids)
DECLARE #currentrow INT = 1
DECLARE #assigningNumber INT = 1
--inerting pinned items first
INSERT INTO #finalResult
SELECT ud.id,
ud.gmd,
ud.ispinned,
ud.rownumber
FROM #userData ud
WHERE ispinned = 1
--looping through all the rows till all non-pinned items finished
WHILE #currentrow <= #totalrows
BEGIN
--skipping pinned numers for the rows
WHILE EXISTS(SELECT 1
FROM #finalResult
WHERE newrownumber = #assigningNumber
AND ispinned = 1)
BEGIN
SET #assigningNumber = #assigningNumber + 1
END
--getting row by the number
SET #id = (SELECT id
FROM #ids
WHERE rn = #currentrow)
--inserting the non-pinned item with new row number into the final result
INSERT INTO #finalResult
SELECT ud.id,
ud.gmd,
ud.ispinned,
#assigningNumber
FROM #userData ud
WHERE id = #id
--going to next row
SET #currentrow = #currentrow + 1
SET #assigningNumber = #assigningNumber + 1
END
--getting final result
SELECT *
FROM #finalResult
ORDER BY newrownumber ASC
--dropping table
DROP TABLE #ids
Output:

MSSQL Recursive dateadd

Hello Iv got big trouble with one query whith next DateTo is counted from previous record.
I have one table
create table #t1 (
M_ID int, --ID of group
STEP int, -- Step number
DateTo Datetime, --DateTo
AddDays int) --Number of days to add to NEXT record
--TestData
INSERT INTO #t1
select 1, 1, GETDATE(), 0 union
select 1, 2, null, 1 union
select 1, 3, null, 0 union
select 1, 4, null, 0 union
select 2, 1, GETDATE(), 0 union
select 2, 2, NULL, 1 union
select 2, 3, NULL, 0
How table looks.
Logic:
If step = 1 then DateTo = GETDATE()
At step 2 of M_ID 1 previous row had 0 days to add so it should copy DateTo from previous row
At step 3 of M_ID 1 previous row has 1 day to add to previous DateTo
Im on the end of my rope...
FAQ
Have to be done in T-SQL (8KK records+)
;with cte as (
select M_ID, STEP, DateTo, AddDays
from #t1
where STEP = 1
union all
select t.M_ID, t.STEP, dateadd(dd, c.AddDays, c.DateTo), t.AddDays
from #t1 t
inner join cte c on t.M_ID = c.M_ID and t.STEP = c.STEP + 1
)
select *
from cte

Except table with a conditions

we have two tables, id is primary key
Old
{
id
name
school
...
version
}
New
{
id
name
school
...
version
}
i want to find same id in both table have same key, but different other columns and ignore the version.
Select * From [New] n Inner Join On [Old] o On n.id = o.id
Where n.name != o.name OR n.school!=o.school ....(Do all the columns without version)
that is works, but there are actually a lot of columns, can i do it with Except?
SELECT * FROM [New] WHERE id IN (SELECT id FROM [New] EXCEPT (SELECT id FROM [Old]))
this is Except version, but this one did not consider that WE NEED TO IGNORE THE VERSION COLUMN.
Here is the framework for the solution:
select <columnlist>
from new
where id in (select id from old)
except
select <columnlist>
from old
To get <columnlist>, you can type it in manually. Or, you can query from information_schema.columns. Or, you can go into SQL Server Management studio and do the following:
Open the database in the Object Explorer
Open "Tables"
Open the table of interest (New)
Click on "Columns" (no need to open it) and drag it into a query window
All the columns appear. Then delete the version that you don't want.
SET STATISTICS IO ON;
SET NOCOUNT ON;
DECLARE #Old TABLE (
Id INT PRIMARY KEY,
Col1 INT NULL,
Col2 VARCHAR(50) NULL
);
DECLARE #New TABLE (
Id INT PRIMARY KEY,
Col1 INT NULL,
Col2 VARCHAR(50) NULL
);
INSERT #Old (Id, Col1, Col2)
SELECT 1, 11, 'A'
UNION ALL SELECT 2, 22, 'B'
UNION ALL SELECT 3, 33, 'C'
UNION ALL SELECT 4, NULL, NULL
UNION ALL SELECT 5, NULL, NULL;
INSERT #New (Id, Col1, Col2)
SELECT 1, 11, 'A'
UNION ALL SELECT 2, 222, 'B'
UNION ALL SELECT 3, NULL, 'C'
UNION ALL SELECT 4, 44, NULL
UNION ALL SELECT 5, NULL, NULL;
PRINT 'Begin of test';
PRINT '#Old:';
SELECT * FROM #Old;
PRINT '#New:';
SELECT * FROM #New;
PRINT 'Last SELECT:'
SELECT *
FROM (
SELECT x.Id, x.Col1, x.Col2, x.Rowtype,
DENSE_RANK() OVER(PARTITION BY x.Id ORDER BY x.Col1, x.Col2) AS Rnk1,
DENSE_RANK() OVER(PARTITION BY x.Id ORDER BY x.Col1 DESC, x.Col2 DESC) AS Rnk2
FROM (
SELECT o.Id, o.Col1, o.Col2, 1 AS RowType
FROM #Old o
UNION ALL
SELECT n.Id, n.Col1, n.Col2, 2 AS RowType
FROM #New n
) x
) y
--WHERE y.RowType=1 AND (y.Rnk1=2 OR y.Rnk2=2) -- Only old rows
WHERE y.RowType=2 AND (y.Rnk1=2 OR y.Rnk2=2) -- Only new rows
PRINT 'End of test';
Results:
Id Col1 Col2 Rowtype Rnk1 Rnk2
-- ---- ---- ------- ---- ----
2 222 B 2 2 1
3 NULL C 2 1 2
4 44 NULL 2 2 1
Messages:
Begin of test
#Old:
Table '#671F4F74'. Scan count 1, logical reads 2
#New:
Table '#6AEFE058'. Scan count 1, logical reads 2
Last SELECT:
Table '#6AEFE058'. Scan count 1, logical reads 2
Table '#671F4F74'. Scan count 1, logical reads 2
End of test
(Not sure why I doubted it!) but I couldn't visualize Gordon's answer.
I set up the following example to prove it to myself:
CREATE TABLE #old (id INT,y INT,z INT);
INSERT INTO #old
values
(1,2,3),
(2,3,4),
(5,6,7),
(8,9,10);
CREATE TABLE #new (id INT,y INT,z INT);
INSERT INTO #new
values
(1,2,3),
(2,30,4),
(5,6,7),
(8,9,100);
--Existing script
SELECT n.id, n.y, n.z
FROM #new n
INNER JOIN #old o
ON n.id = o.id
WHERE
n.y != o.y
OR
n.z != o.z;
--Gordon's answer
SELECT id, y, z
FROM #new
WHERE id IN (SELECT id from #old)
EXCEPT
SELECT id, y, z
FROM #old;