SQL Server exists not working in select statement - sql-server

Query 1 below returns the correct result. But why does query2 return a wrong result? It looks like the exists in query2 doesn't work as expected. Tested on SQL Server 2016.
create table #t1 (id nvarchar(20), typ nvarchar(20))
insert into #t1
values ('1', 'stu'), ('2', 'exstu'), ('string', null)
create table #t2 (id int)
insert into #t2
values (1), (3)
--
--select * from #t1
--select * from #t2
--drop table #t1
--drop table #t2
-- Query #1:
select *
from #t1
where #t1.typ = 'stu' or typ = 'exstu'
and exists (select * from #t2
where #t1.id = #t2.id)
-- Query #2:
select *
from #t1
where exists (select * from #t2
where #t1.id = #t2.id)
and #t1.typ = 'stu' or typ = 'exstu'

AND takes precedence hence the last condition typ = 'exstu' in your second query causing and additional rows to be included in result set. Please check operator-precedence-transact-sql

Assuming you want both queries to give the same result, you'll need to add parentheses to your second query's WHERE clause to have the boolean logic match up:
select *
from #t1
where
exists (
select * from #t2
where #t1.id = #t2.id
)
and (#t1.typ = 'stu' or typ = 'exstu')

Related

Error when using MERGE UPDATE with a uniqueidentifier data type

I have 2 table that have a column with a data type of uniqueidentifier. When using the MERGE statement it will INSERT rows with no problem. The UPDATE part fails with the error Conversion failed when converting from a character string to uniqueidentifier.
I have searched and could not find an answer to why this happens. I have tested this on SQL2017 and SQL2019, same error. Here is a simple script that shows the error.
DECLARE #T2 TABLE(ID_var VARCHAR(36), ID_unique uniqueidentifier);
INSERT INTO #T1 ( ID_var ) VALUES ('0E984725-C51C-4BF4-9960-E1C80E27ABA0');
INSERT INTO #T2 ( ID_var ) SELECT NEWID();
INSERT INTO #T2 ( ID_var, ID_unique) VALUES ('0E984725-C51C-4BF4-9960-E1C80E27ABA0', '0E984725-C51C-4BF4-9960-E1C80E27ABA0')
-- This works
UPDATE #T2
SET ID_unique = NEWID()
WHERE ID_unique IS NULL;
SELECT * FROM #T1;
SELECT * FROM #T2;
MERGE #T1 AS t1
USING (SELECT ID_var, ID_unique FROM #T2) AS t2
ON t1.ID_var = t2.ID_var
-- Insert NEW Rows -- this works
WHEN NOT MATCHED
THEN INSERT (ID_var, ID_unique)
VALUES (ID_var, ID_unique)
-- Update CHANGED Rows -- This fails
WHEN MATCHED AND COALESCE(t1.ID_unique,'') <> COALESCE(t2.ID_unique,'')
THEN UPDATE
SET
ID_unique = t2.ID_unique
OUTPUT $action, inserted.*, deleted.*
;
SELECT * FROM #T1;
Does anyone know of documentation of why this happens or how to get around it.

Be careful when using T-Sql IN operator [closed]

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 5 years ago.
Improve this question
Is there anyone to explain that why this kind of queries running like where id_t1=id_t1 because of IN operator?
My colleague deleted whole table rows even though he wanted to delete some rows, when we analyzed query we realized that there is a problem using "IN" operator. Query structure was not proper but it ran.
I want to show this stuation how to become:
create table #table1(id_t1 int)
create table #table2(id_t2 int)
--insert data
insert into #table1(id_t1) values(1)
insert into #table1(id_t1) values(2)
insert into #table1(id_t1) values(3)
insert into #table1(id_t1) values(4)
insert into #table2(id_t2) values(10)
insert into #table2(id_t2) values(20)
insert into #table2(id_t2) values(30)
insert into #table2(id_t2) values(40)
as you can see there is no id_t1 in #table2,
when you run
select id_t1 from #table2 where id_t2 = 10
query returns:
Invalid column name 'id_t1'.
but it can be using with "in" operator
select * from #table1 where id_t1 in (select id_t1 from #table2 where id_t2 = 10)
it returns whole #table1 rows
id_t1
---------
1
2
3
4
when you use for delete operation like above
delete from #table1 where id_t1 in (select id_t1 from #table2 where id_t2 = 10)
your #table1 have no more rows :)
select * from #table1
id_t1
-----------
(0 row(s)
drop table #table1
drop table #table2
whole of my sample script to easily copy/paste:
create table #table1(id_t1 int)
create table #table2(id_t2 int)
--insert data
insert into #table1(id_t1) values(1)
insert into #table1(id_t1) values(2)
insert into #table1(id_t1) values(3)
insert into #table1(id_t1) values(4)
insert into #table2(id_t2) values(10)
insert into #table2(id_t2) values(20)
insert into #table2(id_t2) values(30)
insert into #table2(id_t2) values(40)
select * from #table1 where id_t1 in (select id_t1 from #table2 where id_t2 = 10)
delete from #table1 where id_t1 in (select id_t1 from #table2 where id_t2 = 10)
select * from #table1
drop table #table1
drop table #table2
In your select query
select * from #table1 where id_t1 in (select id_t1 from #table2 where id_t2 = 10)
SQL interprets it as
select * from #table1 where #table1.id_t1 in (select #table1.id_t1 from #table2 where id_t2 = 10)
So, for each row in #table1, the in clause is the value of #table1.id_t1, repeated for each row of table 2. So of course, every row qualifies.
This is why you should never run a delete statement without checking the select first...
that is because id_t1 does exists in table1 and in your where you did not say that id_t1 has to come from the correct table.
Using an alias for every table can solve this problem very easy.
This would be better
select *
from #table1 t1
where t1.id_t1 in (select t2.id_t1 from #table2 t2 where t2.id_t2 = 10)
now you get Invalid column name id_t1 like you expected
Sub query
select id_t1 from #table2 where id_t2 = 10
will return column Id_t1 of #Table1 corresponding, so condition IN always matched.
The correct query should be
select *
from #table1 where id_t1 in
(select t2.id_t2 from #table2 t2 where t2.id_t2 = 10)
Or using EXIST
select * from #table1 t1
where Exist (select t2.id_t2 from #table2 t2 where t2.id_t2 = 10 and t2.id_t2 = t1.id_t1)
Or using INNER JOIN
select *
from #table1 t1
inner join #table2 t2 on t1.id_t1 = t2.id_t2
where t2.id_t2 = 10
Logic is same when delete

How to get updated columns from a table?

I am working with SQL Server Triggers. And I need a sql query to find columns from a table whose values has been updated using of INSERTED and DELETED tables.
Can anyone help me out on this ? For ex -
DECLARE #T1 TABLE (Name NVARCHAR(MAX), LName nvarchar(max), Address1 Nvarchar(max),id int)
DECLARE #T2 TABLE (Name NVARCHAR(MAX), LName nvarchar(max), Address1 Nvarchar(max), id int)
insert into #T1 values('Ricky','Broad','a b road',1)
insert into #T1 values('Mike','Halls','m g road',2)
insert into #T2 values('Ricky_Update','Broad','a b road',1)
insert into #T2 values('Mike','Halls','m g road',2)
;WITH ChangedData AS (
SELECT d.name , d.LName FROM #T1 d
EXCEPT
SELECT i.name , i.LName FROM #T2 i
)
I tried to find out by "EXCEPT" but it's returning whole updated row. And I need only updated columns like in above example I only need-
Name column for id =1 because it's updated.
You have to do except for individual columns and then do Union.
SELECT NAME
FROM (
SELECT d.NAME
FROM #T1 d
WHERE id = 1
EXCEPT
SELECT i.NAME
FROM #T2 i
WHERE id = 1
) A
UNION
SELECT LName
FROM (
SELECT d.LName
FROM #T1 d
WHERE id = 1
EXCEPT
SELECT i.LName
FROM #T2 i
WHERE id = 1
) B

SQL Server 2008 throws "Declare table variable" errors

SQL Server 2008 throws errors when trying to write following query to create a stored procedure:
DECLARE #T table (TimeId uniqueIdentifier)
INSERT INTO #T
SELECT DISTINCT ref1.value( './#v', 'uniqueIdentifier')
FROM #xmlTimeIds.nodes('/a/i') T(ref1)
UPDATE TAB1
SET TAB1.TMAT_INVOICED_HOURS = 0
FROM dbo.TMAT_TimeRegisterAttribute AS TAB1
INNER JOIN #T on TAB1.TMAT_AT_GUID = #T.TimeId
WHERE TAB1.TMAT_TN_GUID = #guidTenantId
Give #T an alias in the update sql. The compiler regards #T as a variable rather than a table.
DECLARE #T table (TimeId uniqueIdentifier)
INSERT INTO #T
SELECT DISTINCT ref1.value( './#v', 'uniqueIdentifier')
FROM #xmlTimeIds.nodes('/a/i') T(ref1)
UPDATE TAB1
SET TAB1.TMAT_INVOICED_HOURS = 0
FROM dbo.TMAT_TimeRegisterAttribute AS TAB1
INNER JOIN #T TempTable on TAB1.TMAT_AT_GUID = TempTable.TimeId
WHERE TAB1.TMAT_TN_GUID = #guidTenantId
You have on repeated twice: INNER JOIN #T on on TAB1.TMAT_AT_GUID = #T.TimeId. Should be like this:
DECLARE #T table (TimeId uniqueIdentifier)
INSERT INTO #T
SELECT DISTINCT ref1.value( './#v', 'uniqueIdentifier')
FROM #xmlTimeIds.nodes('/a/i') T(ref1)
UPDATE TAB1
SET TAB1.TMAT_INVOICED_HOURS = 0
FROM dbo.TMAT_TimeRegisterAttribute AS TAB1
INNER JOIN #T on TAB1.TMAT_AT_GUID = #T.TimeId
WHERE TAB1.TMAT_TN_GUID = #guidTenantId

Matching Multiple Rows from a SubQuery into the main query

I would like to match multiple rows (exclusively) from a table to another table, and only return the rows that match ALL. Here is my example:
DECLARE #T1 TABLE (Gr int, Dim char(1), Val int)
INSERT INTO #T1 VALUES (1,'A',10)
INSERT INTO #T1 VALUES (1,'B',200)
INSERT INTO #T1 VALUES (1,'B',201)
INSERT INTO #T1 VALUES (1,'B',202)
INSERT INTO #T1 VALUES (1,'C',22)
INSERT INTO #T1 VALUES (1,'C',23)
INSERT INTO #T1 VALUES (1,'C',24)
DECLARE #T2 TABLE (eDim char(1), eVal int)
INSERT INTO #T2 VALUES ('B', 200)
INSERT INTO #T2 VALUES ('C', 29)
SELECT T1.Gr, T1.Dim, T1.Val FROM #T1 T1
WHERE EXISTS (SELECT * FROM #T2 T2 WHERE T2.eDim = T1.Dim AND T2.eVal = T1.Val)
In the example above, the query returns one row from T1 corresponding to the B-200 value pair. What I really need is to either return two records when both B and C values match T2, or return nothing if one or more of the records to not match. Something like "WHERE EXISTS ALL" but this is not recognized by SQL SERVER.
One to do it
SELECT Gr, eDim Dim, eVal Val
FROM
(
SELECT T1.Gr
FROM #T1 t1 JOIN #T2 t2
ON t1.dim = t2.edim
AND t1.val = t2.eval
GROUP BY t1.gr
HAVING COUNT(*) =
(
SELECT COUNT(*) FROM #T2
)
) q CROSS JOIN #T2
Here is SQLFiddle demo Try to uncomment last INSERT INTO #T1 and run query again
or
SELECT gr, dim, val
FROM
(
SELECT gr, dim, val, COUNT(*) OVER (PARTITION BY gr) cnt
FROM #T1 t1 JOIN #T2 t2
ON t1.dim = t2.edim
AND t1.val = t2.eval
GROUP BY gr, dim, val
) q
WHERE cnt =
(
SELECT COUNT(*) FROM #T2
)
Here is SQLFiddle demo Try to uncomment last INSERT INTO #T1 and run query again

Resources