NULL Values on query condition - sql-server

I'm trying to compare null values using not in and exists but I found some diferences
Why the latest query returns null? is it related to a db parameter?
--SET ANSI_NULLS On
CREATE TABLE #Tmp (id INT)
CREATE TABLE #Tmp1 (id INT)
INSERT INTO #Tmp(id) VALUES (1)
INSERT INTO #Tmp(id) VALUES (null)
INSERT INTO #Tmp1(id) VALUES (1)
INSERT INTO #Tmp1(id) VALUES (null)
SELECT id FROM #Tmp WHERE id IN (SELECT id FROM #tmp1)
SELECT id FROM #Tmp WHERE EXISTS(SELECT 1 FROM #Tmp1 WHERE #Tmp1.id = #Tmp.id)
SELECT id FROM #Tmp WHERE id NOT IN (SELECT id FROM #tmp1)
SELECT id FROM #Tmp WHERE NOT EXISTS(SELECT 1 FROM #Tmp1 WHERE #Tmp1.id = #Tmp.id)
DROP TABLE #Tmp
DROP TABLE #Tmp1

In SQL Server, remember that NULL does not return an equality check of TRUE with anything (unless you are playing with ANSI_NULLS):
SELECT 1
WHERE 1=NULL
SELECT 1
WHERE NULL = NULL
It only returns True when you use IS/IS NOT
SELECT 1
WHERE NULL IS NULL
For your last query, the only record that satisfies that requirement in the WHERE clause is your NULL row. The equivalent is:
SELECT id FROM #Tmp
WHERE id IS NULL
To solve your issue, throw a second clause on there:
SELECT id FROM #Tmp
WHERE NOT EXISTS(
SELECT 1
FROM #Tmp1
WHERE #Tmp1.id = #Tmp.id OR #Tmp.id IS NULL
)

It is very simple:
id IN (1, NULL)
<=>
id = 1 OR id = NULL
And negation of alternative:
id NOT IN (1, NULL)
<=>
id != 1 AND id != NULL -- yields NULL
Summary:
If you want to use NOT IN make sure your list does not contain NULL.
SELECT id FROM #Tmp WHERE id NOT IN (SELECT id FROM #tmp1 WHERE id IS NOT NULL)
SELECT id FROM #Tmp WHERE id NOT IN (SELECT COALESCE(id,-1) FROM #tmp1)

Related

How to autoincrement the id without identity?

I'm trying do to a bulk insert from another table in sql server. My query is currently like that :
INSERT INTO Table1(Id, Value)
SELECT ??, Value
FROM Table2;
Now, my problem is obviously by what I replace ??. Id is an integer column without an identity property. I would like that for each inserted row, Id take the current max(Id) + 1.
Can I do that directly in my insert command?
If you were using a newer version of SQL Server (2008+) you could try ROW_NUMBER():
DECLARE #BASE INT
SET #BASE = (SELECT IsNull(MAX(ID),0) FROM Table1)
INSERT INTO Table1(Id, Value)
SELECT
#BASE + ROW_NUMBER() OVER (ORDER BY Value) ID,
Value
FROM Table2;
SQL Fiddle
Since you are using SQL Server 2000, you could try like bellow:
DECLARE #BASE INT
SET #BASE = (SELECT IsNull(MAX(ID),0) FROM Table1)
INSERT INTO Table1(Id, Value)
SELECT
#BASE + (SELECT COUNT(*) FROM Table2 AS i2 WHERE i2.Value <= a.Value),
a.Value
FROM Table2 a
But it will only works if Value in Table2 is unique
SQL Fiddle
If Table2 has a primary key (field PK), then you could use:
INSERT INTO Table1(Id, Value)
SELECT
#BASE + (SELECT COUNT(*) FROM Table2 AS i2 WHERE i2.PK <= a.PK),
a.Value
FROM Table2 a
Here is one wicked way.
We create a temp table with identity to generate new ids. This way we avoid the while loop.
DECLARE #CurrentMaxID INT,
#DynamicQuery NVARCHAR(MAX)
--TODO : Acquired table lock here on table1
SELECT #FirstNextID = ISNULL(MAX(Id), 0)
FROM Table1 --WITH(TABLOCK)
CREATE TABLE #TempTableWithID( Table2Id INT,
Table1FuturId INT IDENTITY(1, 1))
INSERT INTO #TempTableWithID(Table2Id)
SELECT Id --Here we use identity to generate table1 futur id
FROM Table2
INSERT INTO Table1(Id, value)
SELECT Temp.Table1FuturId + #FirstNextID,
Table2.Value
FROM Table2
INNER JOIN #TempTableWithID AS Temp ON Table2.Id = Temp.Table2Id
--TODO : release table lock here on table1
DROP TABLE #TempTableWithID
If I'm understanding you correctly, this should work.
CREATE TABLE #tbl1 (ID int, Value float)
CREATE TABLE #tbl2 (ID int, Value float)
INSERT INTO #tbl2 values (4, 2.0)
INSERT INTO #tbl2 values (8, 3.0)
INSERT INTO #tbl2 values (6, 4.0)
INSERT INTO #tbl1 values (1,1.0)
INSERT INTO #tbl1 values (3,3)
INSERT INTO #tbl1 values (9,3)
/*meat and potatoes start*/
INSERT INTO #tbl1(Id, Value)
SELECT (SELECT MAX(ID) FROM #tbl1) + ROW_NUMBER() OVER (ORDER BY Value) ID, Value
FROM #tbl2;
/*meat and potatoes end*/
Select * From #tbl1
drop table #tbl1
drop table #tbl2
Why not IDENT_CURRENT() ?
SELECT IDENT_CURRENT('yourtablename')
It gives you the next ID reference. But this only works if the ID column has IDENTITY turned on.
OR you can try a SEQUENCE and the NEXT VALUE FOR.
i.e.
CREATE TABLE Test.TestTable
(CounterColumn int PRIMARY KEY,
Name nvarchar(25) NOT NULL) ;
GO
INSERT Test.TestTable (CounterColumn,Name)
VALUES (NEXT VALUE FOR Test.CountBy1, 'Syed') ;
GO
SELECT * FROM Test.TestTable;
GO

How to get Identity of new records INSERTED into table with INSTEAD OF trigger

I am using an INSTEAD OF insert trigger on a table to set an incrementing version number on the row and also copy the row to a 2nd history/audit table.
The rows are inserted to both tables without a problem.
However, I am having trouble returning the new identity from the 1st table back to the user.
Schema
CREATE TABLE Table1
(
id INT IDENTITY(1,1) PRIMARY KEY,
name VARCHAR(250) NOT NULL UNIQUE,
rowVersion INT NOT NULL
)
CREATE TABLE Table1History
(
id INT NOT NULL,
name VARCHAR(250) NOT NULL,
rowVersion INT NOT NULL
)
CREATE TRIGGER TRG_INS_Table1
ON Table1
INSTEAD OF INSERT
AS
DECLARE #OutputTbl TABLE (id INT, name VARCHAR(250))
BEGIN
--make the insert
INSERT INTO Table1 (name, rowVersion)
OUTPUT INSERTED.id, INSERTED.name INTO #OutputTbl(id, name)
SELECT i.name, 1
FROM INSERTED i
--copy into history table
INSERT INTO Table1History (id, name, rowVersion)
SELECT t.ID, i.name, 1
FROM INSERTED i
JOIN #OutputTbl t on i.name = t.name
END
CREATE TRIGGER TRG_UPD_Table1
ON Table1
INSTEAD OF UPDATE
AS
BEGIN
--make the update
UPDATE Table1
SET name = i.name,
rowVersion = (SELECT d.rowVersion + 1 FROM DELETED d WHERE d.id = i.id)
FROM INSERTED i
WHERE Table1.id = i.id
--copy into history table
INSERT INTO Table1History (id, name, rowVersion)
SELECT i.id ,i.name, (SELECT d.rowVersion + 1 FROM DELETED d WHERE d.id = i.id)
FROM INSERTED i
END
Joining on the name column in the insert trigger is not ideal, but it needs to handle multiple inserts at once.
eg INSERT INTO Table1 (name) VALUES('xxx'),('yyy')
Attempted Solutions
When doing an insert, SCOPE_IDENTITY is NULL.
INSERT INTO Table1(name)
VALUES('xxx')
SELECT SCOPE_IDENTITY()
or
INSERT INTO Table1(name)
VALUES('xxx')
RETURN SCOPE_IDENTITY()
I've also tried using OUTPUT - which returns 0:
DECLARE #IdentityOutput TABLE (id INT)
INSERT INTO Table1(name)
OUTPUT INSERTED.id INTO #IdentityOutput
VALUES('xxx')
SELECT id FROM #IdentityOutput
The rows are inserted fine and have IDs, but I cannot access them unless I use the below - which seems hacky:
INSERT INTO Table1(name)
VALUES('xxx')
SELECT id from Table1 WHERE name = 'xxx'
What is the proper way to get the new ID??
Solution
Impossible! You can't reliably return the identity when doing an INSERT on a table that has an INSTEAD OF trigger. Sidux's answer below is a good workaround for my situation (replace INSTEAD OF trigger with AFTER trigger and added DEFAULT columns).
CREATE TABLE Table1
(
id INT IDENTITY(1,1) PRIMARY KEY,
name VARCHAR(250) NOT NULL UNIQUE,
rowVersion INT NOT NULL
)
GO
CREATE TABLE Table1History
(
id INT NOT NULL,
name VARCHAR(250) NOT NULL,
rowVersion INT NOT NULL
)
GO
CREATE TRIGGER TRG_INS_Table1
ON Table1
INSTEAD OF INSERT
AS
DECLARE #OutputTbl TABLE (id INT, name VARCHAR(250))
BEGIN
--make the insert
INSERT INTO Table1 (name, rowVersion)
SELECT i.name, 1
FROM INSERTED i
END
GO
CREATE TRIGGER TRG_UPD_Table1
ON Table1
INSTEAD OF UPDATE
AS
BEGIN
--make the update
UPDATE Table1
SET name = i.name,
rowVersion = (SELECT d.rowVersion + 1 FROM DELETED d WHERE d.id = i.id)
FROM INSERTED i
WHERE Table1.id = i.id
END
GO
CREATE TRIGGER TRG_AFT_INS_Table1
ON Table1
AFTER INSERT, UPDATE
AS
BEGIN
INSERT INTO Table1History (id, name, rowVersion)
SELECT i.ID, i.name, i.rowversion
FROM INSERTED i
END
GO
INSERT INTO Table1 (name) VALUES('xxx'),('yyy')
SELECT * FROM Table1History
-----------------------------------------------
id name rowVersion
2 yyy 1
1 xxx 1
-----------------------------------------------
UPDATE Table1 SET name = 'xxx1' WHERE id = 1;
SELECT * FROM Table1History
-----------------------------------------------
id name rowVersion
2 yyy 1
1 xxx 1
1 xxx1 2
-----------------------------------------------
Basically you do not need TRG_INS_Table1 trigger, you can just use DEFAULT value = 1 for column and that's it. Also if you use DATETIME column instead of rowversion, you can just insert the state of INSERTED table to the history with the GETDATE() value. In that case you can order by Dtime column DESC and you have history.

Insert record in table with restriction

I use sql server and a table like this:
MyTbale(ID int IDENTITY, Type Char(1), ParentId int)
I want to restrict my users that insert the first record with some ParentId with Type = 'O', and can insert other record with some parentId with each Type. In other word if user want to insert a record with ParentId=10 must set Type = 'O' and after this, can Insert each type with ParentId = 10.
Sample Data:
Id Type ParentId
1 O 10
2 I 10
3 I 10
4 M 10
5 N 10
6 O 12
7 M 12
8 I 12
What's the best solution for this problem? ( I think that I can use constraint with UDF and trigger for this purpose).
EDIT
by following format I can add check constraint:
Create FUNCTION [dbo].[f](#Id INT, #Date DATE) RETURNS BIT
AS BEGIN
DECLARE #R BIT = CASE WHEN EXISTS(SELECT Date FROM MyTable WHERE Date=#Date AND Id!=#Id) THEN 1 ELSE 0 END
RETURN #R
END
GO
ALTER TABLE [dbo].[MyTable] WITH CHECK ADD CONSTRAINT [CK_test] CHECK (([dbo].[F]([Id],[Date])=(0)))
and by following query I can add trigger for check data:
CREATE TRIGGER dbo.tr_MyTable ON dbo.MyTable
INSTEAD OF INSERT
AS BEGIN
IF EXISTS(SELECT *
FROM Inserted i
LEFT JOIN MyTable t ON t.ParentId = i.ParentId AND t.Type = 'O'
WHERE i.Type <> 'O'
AND t.Id IS NULL) BEGIN
RAISERROR('Type must be equal to O in first row',16,1)
END
INSERT INTO dbo.MyTable(Type,ParentId)
SELECT Type, ParentId
FROM Inserted
END
The INSTEAD OF TRIGGER would be the way to go. Also, if you don't need the error information returned, you might gain some performance with this slightly revised suggestion:
CREATE TRIGGER dbo.tr_MyTable ON dbo.MyTable
INSTEAD OF INSERT
AS BEGIN
INSERT INTO dbo.MyTable(Type,ParentId)
SELECT Type, ParentId
FROM Inserted
LEFT JOIN MyTable t ON t.ParentId = i.ParentId AND t.Type = 'O'
WHERE i.Type <> 'O'
AND t.Id IS NULL

SQL Server : check if all rows exists in other table

I need to know if all rows from one table exists in other:
declare #Table1 table (id int)
declare #Table2 table (id int)
insert into #Table1(id) values (1)
insert into #Table1(id) values (4)
insert into #Table1(id) values (5)
insert into #Table2(id) values (1)
insert into #Table2(id) values (2)
insert into #Table2(id) values (3)
if exists (select id from #Table1 where id in (select id from #Table2))
select 'yes exists'
else
select 'no, doesn''t exist'
This query returns yes exists but should return no, doesn't exist because only 1 exists in #Table2, values 4 and 5 don't.
What should I change in my query? Thanks!
IF NOT EXISTS (
SELECT ID FROM #Table1
EXCEPT
SELECT ID FROM #Table2
)
SELECT 'yes exists'
ELSE SELECT 'no, doesn''t exist'
You could use EXCEPT to get the set difference of both tables. If any ID's are returned, both tables are not equal:
SELECT ID
FROM #Table1
EXCEPT
SELECT ID
FROM #Table2
EXCEPT returns any distinct values from the left query that are not also found on the right query.
So, to get your "no, doesnt exist":
;WITH diff AS(
SELECT ID
FROM #Table1
EXCEPT
SELECT ID
FROM #Table2
)
SELECT CASE WHEN COUNT(diff.ID) = 0
THEN 'yes exists'
ELSE 'no, doesnt exist'
END AS Result
FROM diff
select case when count(*) > 0 then 'no' else 'yes' end as AllExist
from #Table1 t1
left outer join #Table2 t2 on t1.id = t2.id
where t2.id is null
This would work as long as both id columns are unique (which they should be if they are id's)
DECLARE #totalRows int;
SET #totalRows = SELECT count(*) from Table1;
RETURN (#totalRows == SELECT count(*) from Table1 JOIN Table2 on Table1.id = Table2.id)

always return some raws in UNION even if the condition doesnt match

I have a stored proc which unions some data and returns back. At max that SP will return me 3 rows meeting the where condition.
Is there a way i can force the SP to return the blank row if there is no data present which matches the condition?
This is how my SP looks like:
SELECT Top 1 Col1, 'FirstResult' FROM Table T1
where SomeColumn='whatever'
UNION ALL
SELECT Top 1 Col2, 'SecondResult' FROM Table T1
where SomeColumn='whatever'
UNION ALL
SELECT Top 1 Col3, 'ThirdResult' FROM Table T1
where SomeColumn='whatever'
I want to always return me 3 rows regardless even if the condition doesnt match. Of course data will be empty or NULL in the resultset.
You can go here to demonstrate that the query in this answer works with no rows: http://www.sqlfiddle.com/#!3/51d1c/3
With 1 row in YourTable: http://www.sqlfiddle.com/#!3/ad1e8/1
Here is a procedure that should return what you are looking for:
CREATE PROCEDURE pExample_Get3ForcedRows
#FirstMatch VARCHAR(50)
,#SecondMatch VARCHAR(50)
,#ThirdMatch VARCHAR(50)
AS
BEGIN
DECLARE #ForceTable TABLE
(
MatchColumn VARCHAR(50) NOT NULL
)
INSERT #ForceTable (MatchColumn) VALUES (#FirstMatch),(#SecondMatch),(#ThirdMatch)
SELECT
T.Col1
, T.ResultColumn
FROM #ForceTable F
LEFT JOIN YourTable T
ON T.SomeColumn = F.MatchColumn
END
GO
DECLARE #table (Col1 varchar, String2 varchar)
IF ( (SELECT COUNT(*) FROM Table T1 WHERE SomeColumn='whatever')>0 )
INSERT INTO #table
SELECT Top 1 Col1, 'FirstResult' FROM Table T1 WHERE SomeColumn='whatever'
ELSE
INSERT INTO #table
SELECT NULL AS Col1, ''
IF ( (SELECT COUNT(*) FROM Table T1 WHERE SomeColumn='whatever')>0 )
INSERT INTO #table
SELECT Top 1 Col2, 'secondResult' FROM Table T1 WHERE SomeColumn='whatever'
ELSE
INSERT INTO #table
SELECT NULL AS Col2, ''
IF ( (SELECT COUNT(*) FROM Table T1 WHERE SomeColumn='whatever')>0 )
INSERT INTO #table
SELECT Top 1 Col3, 'thirdResult' FROM Table T1 WHERE SomeColumn='whatever'
ELSE
INSERT INTO #table
SELECT NULL AS Col3, ''
RETURN #table
Don't know if there is a more elegant version, but this should work.

Resources