IF NOT EXISTS working in query mode but not working properly in stored procedure call from code - sql

I have a stored procedure that does something like this:
MY_STORED_PROCEDURE:
IF NOT EXISTS(SELECT 1 FROM MY_TABLE WHERE MY_COLUMN1 = #MY_COLUMN1_VALUE_FROM_CODE AND MY_COLUMN2 = #MY_COLUMN2_VALUE_FROM_CODE)
BEGIN
--INSERT INTO MY_TABLE
This stored procedure is called automatically from C# code.
Now, this means that:
if we try to insert an entry with NEW VALUES for combination COLUMN1 and COLUMN2, then it should be inserted into MY_TABLE.
AND
If we try to insert an entry with EXISTING VALUES for combination COLUMN1 and COLUMN2, then it should not be inserted into MY_TABLE.
However, what happens is that the insertions are happening ALL THE TIME, independent of "IF NOT EXISTS" statement.
I already checked the "IF NOT EXISTS" statement directly from the query builder of SSMS and it seems that nothing is wrong with that statement.
Is there something that I do not take into consideration? What could be the error here?
This is also my C# code for invoking my SP:
public void InsertIntoMyTable(List<SqlParameter> parameters, SqlConnection connection)
{
DataTable dt = new DataTable();
SqlCommand command = new SqlCommand("MY_STORED_PROCEDURE", connection)
{
CommandType = CommandType.StoredProcedure
};
if (parameters != null)
{
foreach (SqlParameter parameter in parameters)
{
command.Parameters.Add(parameter);
}
}
dt.Load(command.ExecuteReader());
}
NOTE: MY_STORED_PROCEDURE is being called from different sources. So it is also possible that this procedure is being called at the SAME TIME from 1+ sources. I don't know if this can have some effects on my problem or not. I am reporting it, just in case it relates.
Thanks!

First, `NOT EXISTS* is the wrong way to do this, because it introduces race conditions. What you appear to want is a unique constraint. So add this as a constraint or index and then check for errors:
alter table t add constraint unq_my_table_column1_column2 on my_table (column1, column2);
Then simply do the insert . . . but in the TRY/CATCH block:
begin try
insert into my_table ( . . . )
values ( . . .);
end try;
begin catch
. . .
end catch;
If I had to speculate on the problem, then you have an issue with the parameters. Perhaps they are declared incorrectly -- such as varchar() with no length. Or perhaps they are being passed in incorrectly, as NULLs.

Something similar happened to me sometime ago because I didn't add SET NOCOUNT ON; at the beginning of my stored procedure.

Related

Checking some columns for specific values before insert/update using a trigger

I have a database FOO with several columns, among those I have one column "Url". I need to write a trigger before insert/update that will check the Url columns whether the newer value matches any existing values, i.e. "hello" except some predefined value. That means if "hello" is inserted or updated multiple times no error will happen otherwise it will check for duplicity. And if it finds some aborts the insertion update. This will also return some code so that my script calling for the insertion/update will know a failure has occurred. I know there might be other workarounds but I will need to have it this way. I am pretty new to SQL.
Foo {
Url
}
Here is the algorithm
Before update insert
if new value of Url is not "hello1" o "hello 2"
check if new value of Url already exists in Foo.Url if so abort otherwise allow update/insert
return something if aborted/success
try something like this.. you'll need to index your table..
IF EXISTS(SELECT URL FROM Foo.Url)
BEGIN
SELECT 'URL Exists Already'
END
ELSE
BEGIN
INSERT/UPDATE
END
A unique constraint wouldn't do what you want but you could create an instead of trigger with content something like as:
Create TRIGGER [dbo].[Trig_Insert_XXX]
ON [dbo].[XXX]
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO xxx ([url], field1, field2, fieldN)
SELECT [url], field1, field2, fieldN
FROM inserted i
WHERE i.url = 'hello' OR NOT EXISTS (SELECT * FROM xxx t2 WHERE t2.url = i.url);
END;
I suppose you're looking for a UNIQUE constraint & a CHECK constraint as
CREATE TABLE Foo(
Url VARCHAR(250) NOT NULL,
CONSTRAINT UQ_Url UNIQUE(Url),
CONSTRAINT CHK_Url CHECK (Url NOT IN ('hello1', 'hello2'))
);
See how it's working online.
If you are using SQL Server 2008 or newer version you can use MERGE as well, the syntax is like the following :
MERGE [TableName] AS TARGET
USING ( SELECT #UrlName ) AS SOURCE (UrlName) ON SOURCE.UrlName = TARGET.UrlName
WHEN MATCHED THEN
UPDATE SET ...
WHEN NOT MATCHED THEN INSERT ()
VALUES ();

Use trigger after multiple insert to update log table

I have the following trigger:
ALTER TRIGGER [Staging].[tr_UriData_ForInsert]
ON [Staging].[UriData]
FOR INSERT
AS
BEGIN
DECLARE #_Serial NVARCHAR(50)
DECLARE #_Count AS INT
IF ##ROWCOUNT = 0
RETURN
SET NOCOUNT ON;
IF EXISTS(SELECT * FROM inserted)
BEGIN
SELECT #_Count = COUNT(Id) FROM inserted
SELECT #_Serial = SerialNumber FROM inserted
INSERT INTO [Staging].[DataLog]
VALUES (CURRENT_TIMESTAMP, #_Serial + ': Data Insert --> Rows inserted: ' + #_Count, 'New data has been received')
END
END
The table receives multiple rows at once. I want to be able to add one row in the log table to tell me the insert has happened.
It works great with one row being inserted, but with multiple rows, the trigger doesn't fire. I have read other items on here and it is quite clear that you shouldn't use ROW_NUMBER().
In summary: I want to update my log table when a multiple row insert happens in another table called UriData.
The data is inserted from C# using the following:
using (var sqlBulk = new SqlBulkCopy(conn, SqlBulkCopyOptions.Default, transaction))
{
sqlBulk.DestinationTableName = tableName;
try
{
sqlBulk.WriteToServer(dt);
}
catch(SqlException sqlEx)
{
transaction.Rollback();
var msg = sqlEx.Message;
return false;
}
finally {
transaction.Commit();
conn.Close();
}
}
I don't want to know what is being inserted, but when it has happened, so I can run a set of SPROCS to clean and pivot the data.
TIA
The problem is your trigger assumes that only one row will be updated. A scalar variable can only have 1 value. So, for example, the statement SELECT #_Serial = SerialNumber FROM inserted will set #_Serial with the last value returned from the object inserted.
Treat your data as what it is, a dataset. This is untested, however, I suspect this gives you the result you want:
ALTER TRIGGER [Staging].[tr_UriData_ForInsert]
ON [Staging].[UriData]
FOR INSERT
AS
BEGIN
--No need for a ROWCOUNT. If there are no rows, then nothing was inserted, and this trigger won't happen.
INSERT INTO [Staging].[DataLog] ({COLUMNS LIST})
SELECT CURRENT_TIMESTAMP,
SerialNumber + ': Data Insert --> Rows inserted: ' +
CONVERT(varchar(10),COUNT(SerialNumber) OVER (PARTITION BY SerialNumber)), --COUNT returns an INT, so this statement would have failed with a conversion error too
'New data has been received'
FROM inserted;
END
Please note my comments or sections in braces ({}).
Edit: Sean, who has since deleted his answer, used GROUP BY. I copied what exact method you had, however, GROUP BY might well be the clause you want, rather than OVER.
So after a lot of digging and arguing, my hosting company told me that they have disabled bulk inserts of any kind, without bothering to notify their customers.

Stored procedure best practice? Should they check if foreign keys exist before inserting?

When building a stored procedure that inserts into a simple link table, should it check if the FK's exist and gracefully return an error or just let SQL throw an exception?
What is best practice?
What is most efficient?
Just in case someone doesn't understand my question:
Table A
Table B
Table AB
Should I do:
IF EXISTS(SELECT 1 FROM A WHERE Id = #A) AND EXISTS(SELECT 1 FROM B WHERE Id = #B)
BEGIN
INSERT AB (AId,BId) VALUES (#A, #B)
END
ELSE
--handle gracefully, return error code or something
or
INSERT AB (AId,BId) VALUES (#A, #B)
and let SQL throw an exception
Thanks
If the tables are under your control, there is no reason to perform an extra check. Just assume they are set up correctly, and let SQL handle any error. Constantly checking that you have indeed done what you intended to do is overly defensive programming that adds unnecessary complexity to your code.
For example, you wouldn't write code like this:
i = 1;
if (i != 1)
{
print "Error: i is not 1!";
}
And I view this situation as similar.
If the tables are not under your control it may be useful to handle the error gracefully. For example, if this procedure can run on an arbitrary set of tables created by the user, or if it will be distributed to external users who are required to set up the tables in their own database, you may want to add some custom error handling. The purpose of this would be to give the user a clearer description of what went wrong.
As a basic concept, validating values before a potentially error raising code is a good thing. However, in this case, there could be (at least theoretically) a change in table a or table b between the exists checks and the insert statement, that would raise the fk violation error.
I would do something like this:
BEGIN TRY
INSERT AB (AId,BId) VALUES (#A, #B)
SELECT NULL As ErrorMessage
END TRY
BEGIN CATCH
SELECT ERROR_MESSAGE() AS ErrorMessage
END CATCH
The ERROR_MESSAGE() function returns the error that was rasied in the try block.
Then in the executing code you can simply check if the returned error message is null. If it is, you know the insert was successful. If not, you can handle this exception how ever you see fit.
Usually i will check Fk. If the Fk isn't exist, then throw a error friendly, and the insert statement will not be execute, database will not lock the table too.

UPSERT in SQLite

I don't want to use REPLACE INTO because it's basically a DELETE and INSERT and it's complicated to use the data from the old columns.
INSERT OR IGNORE is a bit of a hack because all errors are ignored, so this is not an option.
I've read a blog article which uses the following:
UPDATE Table1 SET (...) WHERE Column1='SomeValue'
IF ##ROWCOUNT=0
INSERT INTO Table1 VALUES (...)
I like this approach really much, but I don't know how I can implement this IF-clause with the ##ROWCOUNT in SQLite, this is what I got:
SELECT CASE (SELECT
CASE
WHEN EXISTS(SELECT 1 FROM t WHERE id=2)
THEN 1
ELSE 0
END)
WHEN 1
UPDATE t set a='pdf' WHERE id=2;
ELSE
INSERT INTO t (a) VALUES ('pdf');
END
the SELECT CASE seems to be the only way to use a CASE-clause in SQLite because everything else throws an syntax error. But it's also not possible to use a UPDATE- or INSERT-statement in the SELECT CASE, so this throws an error.
I've tried the following
UPDATE t set a='pdf' WHERE id=2;
CASE WHEN (changes()=0) THEN
INSERT INTO t (a) VALUES ('pdf');
END
but this doesn't work, because the CASE-clause throws an syntax error.
can someone provide an example using ##ROWCOUNT for an UPSERT in SQLite?
SQLite has no built-in UPSERT-like statement that doesn't delete the old record.
You should check the number of changes in your program, and execute the INSERT conditionally.
However, if you really want to do this in SQL, it's possible; but you have to use the INSERT ... SELECT ... form so that you are able to insert zero records, if needed:
BEGIN;
UPDATE t SET a = 'pdf' WHERE id = 2;
INSERT INTO t(id, a) SELECT 2, 'pdf' WHERE changes() = 0;
COMMIT;
You should use sqlite API in this case and write "IF logic" in your application.
sqlite3_prepare16_v2(stmt1, "UPDATE t SET a=? WHERE id=?");
sqlite3_prepare16_v2(stmt2, "INSERT INTO t(id, a) VALUES(?, ?)");
for(...) // iterate by rows to be updated/inserted
{
//set parameter values for stmt1
sqlite3_step(stmt1);
if( !sqlite3_changes(dbh) )
{
//set parameter values for stmt2
sqlite3_step(stmt2);
}
}

get new SQL record ID

How can I get back the autogenerated ID for a new record I just inserted?
(Using ASP classic and MSSQL 2005)
SELECT SCOPE_IDENTITY()
using ##IDENTITY can have unexpected results, so be careful how you use that one. Triggers that insert records to other tables will cause the ##IDENTITY value to change - where SCOPE_IDENTITY() will give you the last identity from only your current scope.
Here's a sample that'll show the difference between ##IDENTITY and SCOPE_INSERT() and how they can return different values..
use tempdb
go
create table table1
(ID int identity)
go
create table table2
(ID int identity(100, 1))
go
create trigger temptrig
on table1
for insert
as
begin
insert table2
default values;
end
go
insert table1
default values;
select SCOPE_IDENTITY(),
##IDENTITY
Another option that nobody has discussed here is to use the OUTPUT clause that is in SQL 2005. In this case, you'd just have to add the output clause to your insert, and then catch that recordset from your code. This works well when inserting multiple records instead of just 1...
use tempdb
go
create table table1
(ID int identity)
go
insert table1
output inserted.ID
default values;
--OR...
insert table1
output inserted.$identity
default values;
SELECT ##IDENTITY usually works, but could return the identity of a record inserted because of a trigger or something, and not the original.
SELECT SCOPE_IDENTITY is what I'd recommend. It returns values inserted only within the current scope.
There is also a "IDENT_CURRENT(tablename)" that returns the last identity inserted for a specific table.
There are three ways to get the the last identity in sql.
They were already mentioned by others, but for completeness:
##IDENTITY - can also return ids created in other objects in the same scope (think triggers)
IDENT_CURRENT - limited to a table, but not to your scope, so it can give bad results for busy tables
Scope_Idenity() - Limited to the scope of the request. Use this 99% of the time
Additionally, there are three ways to take that ID and return it to your client code:
Use an output parameter in a stored procedure
INSERT INTO [MyTable] ([col1],[col2],[col3]) VALUES (1,2,3);
SELECT #OutputParameterName = Scope_Identity();
Use a return value.
INSERT INTO [MyTable] ([col1],[col2],[col3]) VALUES (1,2,3);
Return Scope_Identity();
Select the id into a result set. For example, your sql statement would look something like this:
Dim ResultID As Integer
Dim strSQL As String
strSQL = "INSERT INTO [MyTable] ([col1],[col2],[col3]) VALUES (1,2,3); SELECT Scope_Identity();"
rsResults.Open strSQL, oConn
ResultID = rsResults("ID")
Unfortunately (or fortunately, from my point of view) my Classic ASP it too far gone to show examples of the first two from client code.
SELECT ##Identity or SELECT SCOPE_IDENTITY() both work however Selecting SCOPE_Identity() is safer because it returns the last auto generated ID within your current scope. So for example assume we have a table called ScopeIDTable and on this table we have a trigger. This trigger will insert into a record into TriggerIdTable both tables have an auto increment column.
If you use SELECT ##Identity you will get the last auto increment in that session which would be the Id generated from within the trigger (TriggerIdTable).
If you use SELECT SCOPE_IDENTITY() you will get the id from your ScopeIdTable.
You run the query
select scope_identity()
using the same database connection, before doing anything else with it. The result is, as you probably expect, a record set containing a single row that has a single field. You can access the field using index 0, or you can give it a name if you prefer that:
select scope_identity() as lastId
I always wondered why one would ever want to use
##identity
since
select scope_identity()
obviously is the most save way to accomplish what Scot is asking for.
Where multiple records need to inserted at once in a set-based fashion, it can get more interesting.
I have sometimes used GUIDs generated clientside (but for classic ASP you'd probably need to use a utility to generate the values) or, more often, a NEWSQUENTIALID() constraint on the GUID key column at the server end.
I'm aware not everyone like GIUDS though, for some quite valid reasons (their size and how it affects indexing/paging for one).
http://www.sqlmag.com/Articles/Index.cfm?ArticleID=50164&pg=2
Thanks all who suggested SELECT SCOPE_IDENTITY(). I was able to create a stored procedure:
USE [dbname]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[spInsert]
(
#Nn varchar(30)
)
AS
BEGIN TRANSACTION InsertRecord
INSERT INTO A (Nn)
VALUES (#Nn)
SELECT NewID = SCOPE_IDENTITY() -- returns the new record ID of this transaction
COMMIT TRANSACTION InsertRecord
and call the sproc using VB:
Dim strNn '<- var to be passed'
Set cn = Server.CreateObject("ADODB.Connection")
connectString = "DSN"
cn.Open connectString, "user", "PW0rd"
Set rs = Server.CreateObject("ADODB.Recordset")
set rs = cn.Execute("EXEC [dbname].[dbo].[A] #Nn=" & strNn)
'return the value'
resultID = rs(0)
I can now use resultID anytime I refer to the newly created ID.