IF response unexpected result in stored procedure, unique key violation - sql

I might have looked on this too long, so i hope someone can help me out here.
i'm playing around with comparing files metadata to identify unique data chunks, and thereby detect deduplication potential... here goes.
drop proc insertFile
go
create proc [dbo].[insertFile] #fileHash char(64), #name varchar(200)
as
set nocount on;
declare #fileId int
declare #klientId int
set #klientId = (SELECT cast(RAND() * 10 + 1 as int))
IF NOT EXISTS (select * from data_file where hash_key = '#fileHash')
begin
insert into data_file (hash_key) values (#fileHash)
end
set #fileId = (select id from data_file where hash_key = '#fileHash')
insert into klient_file (data_file, klient, name) values (#fileId, #klientId, #name)
there is a unique constraint on hash_key and this is violated when i enter a value that exists, this should not happen, the IF checks if it exist, and should only insert if the hash value does not.
the data should enter klient_file no matter what...
again, the error is the violation of the unique constraint, that should have been avoided with the IF check, the IF works by its own, just not in the procedure. any thoughts?
(this all runs on a localdb instance)

You have your parameter within quote marks in the EXISTS check, so in this line
IF NOT EXISTS (select * from data_file where hash_key = '#fileHash')
you are checking if '#fileHash' exists, not the actual value assigned to the parameter, so even if the hash_key exists you are trying to insert it because '#FileHash' does not exist in the table
Your procedure should be:
create proc [dbo].[insertFile] #fileHash char(64), #name varchar(200)
as
set nocount on;
declare #fileId int
declare #klientId int
set #klientId = (SELECT cast(RAND() * 10 + 1 as int))
IF NOT EXISTS (select * from data_file where hash_key = #fileHash)
begin
insert into data_file (hash_key) values (#fileHash)
end
set #fileId = (select id from data_file where hash_key = #fileHash)
insert into klient_file (data_file, klient, name) values (#fileId, #klientId, #name)

Related

Unable to get computed value with After Insert trigger

I'm using the FileTables feature and have setup a trigger to populate a FileProperties table whenever a file is copied to the SQL Server file share.
I'm trying to grab the file size (cached_file_size), and I get a value of zero. I set up the trigger the same way a standard FileTable does using DATALENTGH(file_stream). The code below is using the inserted tables' file_stream:
ALTER TRIGGER [dbo].[FileTable_Insert_Trigger]
ON [dbo].[Files]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #s_id UNIQUEIDENTIFIER,
#fs VARBINARY(MAX),
#nm NVARCHAR(255),
#fp NVARCHAR(MAX),
#cfs BIGINT,
#ft NVARCHAR(255);
SELECT #s_id = ins.stream_id FROM inserted ins;
SELECT #fs = ins.file_stream FROM inserted ins;
SELECT #nm = ins.name FROM inserted ins;
SELECT #fp = ins.file_stream.GetFileNamespacePath() FROM inserted ins;
SELECT #ft = ins.file_type FROM inserted ins;
SELECT #cfs = DATALENGTH(#fs);
INSERT INTO [FileProperties] (stream_id, [name], filepath, file_type,
cached_file_size, DateAdded, UserID)
VALUES (#s_id, #nm, #fp, #ft,
#cfs, CURRENT_TIMESTAMP, 1);
END
I've also tried grabbing the computed value directly from the FileTable:
SELECT #cfs = Files_1.cached_file_size
FROM [dbo].Files AS Files_1
WHERE Files_1.stream_id = #s_id;
Still getting zero. What am I missing here? Thank you.
[EDIT] I tried playing around with getting the DATALENGTH of other values such as the name (#nm) and stream_id (#s_id) and I do get the values correctly.

Trigger not calling for huge rows insert

I have one table which consists of one trigger which will be called if any insert or update operation performed on that table.
This trigger will insert a new row in other physical table.
First I am taking the entire data to be inserted into a temporary table and then I am inserting data into my physical table(which has trigger).
After performing insert operation all the records in the temporary table are getting inserted into physical table but the trigger is executing for only first record, for rest of the records it is not executing.
Can anyone please help me with this issue.
NOTE : With cursor it is working fine but for performance issue I don't want to use cursor.
ALTER TRIGGER [dbo].[MY_TRG]
ON [dbo].[T_EMP_DETAILS]
FOR INSERT , UPDATE
AS
BEGIN
IF UPDATE(S_EMPLOYEE_ID)OR UPDATE(S_GRADE_ID)OR UPDATE(D_EFFECTIVE_DATE) OR UPDATE(S_EMPLOYEE_STATUS)
BEGIN
DECLARE #EmpId varchar(6)
DECLARE #HeaderId Int
DECLARE #FYStartYear varchar(4)
DECLARE #EffDate Smalldatetime
DECLARE #UpdatedBy varchar(10)
DECLARE #ActionType varchar(1)
DECLARE #RowCount Int
DECLARE #EmpRowCount Int
DECLARE #AuditRowsCount Int
DECLARE #EMP_STATUS VARCHAR(1)
DECLARE #D_FIN_START_YEAR DATETIME
DECLARE #Food_Count int
SELECT #FYStartYear = CAST(YEAR(D_CURRENT_FY_ST_DATE)AS VARCHAR) FROM dbo.APPLICATION WHERE B_IS_CURRENT_FY = 1
SELECT #UpdatedBy = 'SHARDUL'
select #EmpId = S_EMPLOYEE_ID from inserted
select #HeaderId = N_HEADER_TXN_ID from inserted
select #EffDate = D_EFFECTIVE_DATE from inserted
select #FLEXI_AMT = N_FLEX_BASKET_AMT from inserted
select #EMP_STATUS = S_EMPLOYEE_STATUS from inserted
select #D_FIN_START_YEAR=D_FIN_START_DATE from inserted
SELECT #RowCount = count(*) from T_EMP_DETAILS
WHERE S_EMPLOYEE_ID = #EmpId and
SUBSTRING(CAST(D_EFFECTIVE_DATE AS VARCHAR),1,11) = SUBSTRING(CAST(#EffDate AS VARCHAR),1,11)
BEGIN
exec INSERT_DEFAULT_VALUES #EmpId,#HeaderId,#UpdatedBy
END
That's one of many reasons Bulk is so fast :). Read Bulk Insert syntax and you'll see FIRE_TRIGGERS parameter. Use it.
As I wrote in my comment - you are using inserted in improper way. As written now it will work only for 1 row.
The second one is a WEIRD number of variables, and only few are used, why?
Third - you are using SP in the end of batch, you need to post it's code, I bet there is some insert in it, maybe you could avoid using this SP and insert directly in some table from inserted.

insert the null record in the print lable

I am trying to create a SP which print the label of my vendor, vendor name. I want the user set the startposition, before the startposition I just simply insert a null value. I want be able to reuse the label sheet.
I have the SP code like this:
Alter PROCEDURE [dbo].[z_sp_APVendorLabel]
(#VendorGroup bGroup ,
#StartPosition int)
AS
BEGIN
SET NOCOUNT ON;
Create table #data_null
(Vendor int,
Name varchar(60)null)
Declare #counter int
SET #counter = 0
WHILE #counter < #StartPosition
BEGIN
UPDATE #data_null SET Vendor='',Name=' '
SET #counter = #counter + 1
END
Create table #detial
(Vendor int,
Name varchar (60)null)
select Vendor, Name into #data from APVM
WHERE VendorGroup= #VendorGroup
select * from #data_null
Union All
select * from #detial
END
It is very simple, but when I test it, I did not get any data.
You're creating the table #data_null, and updating it, but never inserting any rows. If you inspect ##rowcount after each update, you'll see it's zero.
Before you change that loop to insert instead of update, please consider setting up a permanent table to select from. A loop to generate N values on every invocation of the procedure is really not the best use of your server's time, or yours. ;-)

How to exec a stored procedure for each row in a select statement?

I have a stored procedure that returns an unique Id. I need to call this sp to get the unique ID for each row. I must use this SP because an application also uses this.
How can I select for each row a ID that is returned from the SP?
CREATE procedure [dbo].[SelectNextNumber]
#TableName nvarchar(255)
as
begin
declare #NewSeqVal int
set NOCOUNT ON
update Number --This is a table that holds for each table the max ID
set #NewSeqVal = Next = Next + Increase
where TableNaam= #TableName
if ##rowcount = 0
begin
Insert into Number VALUES (#TableName, 1, 1)
return 1
end
return #NewSeqVal
The number table:
CREATE TABLE [dbo].[Number](
[TableName] [varchar](25) NOT NULL,
[Next] [int] NULL,
[Increase] [int] NULL
I have seen that a While loop is usable for this but in my situation I don't know how to use a while loop.
You can't use stored procedures inside a SELECT statement, only functions.
You can iterate on a resultset with a cursor if you really have to use a stored procedure:
http://msdn.microsoft.com/library/ms180169.aspx
EDIT:
To be honest I'm not very sure to have understood what you really need, it looks like you are building a IDENTITY by yourself ( http://msdn.microsoft.com/library/ms174639(v=sql.105).aspx );
still, if you really need to run a cursor here's an example which uses your stored procedure:
http://sqlfiddle.com/#!3/2b81a/1
Taking the singular INSERT INTO.. SELECT apart:
Temporarily store the SELECT results away
declare #rc int, #NewSeqVal int;
SELECT ..
INTO #tmp -- add this
FROM ..
Store the rowcount and get that many numbers
set #rc = ##rowcount;
For which you have to use the code in the SP directly:
update Number --This is a table that holds for each table the max ID
set #NewSeqVal = Next = Next + #rc
where TableNaam= 'sometbl';
Finally, the insert
INSERT ...
SELECT ID = #NewSeqVal + 1 - row_number() over (ORDER BY col1)
, {all the other columns}
FROM #tmp;
ORDER by Col1 is arbitrary, choose something sensible, or make it ORDER BY NEWID() if you don't care.

SQL Select or Insert return ID

Alright so a quick SQL question here(using sql-server-2008).
I have a mapping table names with the following columns
ID DisplayName
What I want to do is first
SELECT [ID] FROM [names] WHERE [DisplayName] = 'chuck';
BUT, if the name 'chuck' doesn't exist in the database, I would like to create it, and return the auto incremented ID.
I was wondering if SQL had some built in way of doing this in an easy way, or if I have to go the long route?
long route being something like this
SELECT COUNT(ID) AS count, ID FROM names WHERE DisplayName='chuck'
IF(count > 0)
SELECT ID as ReturnID;
ELSE
BEGIN
INSERT INTO names(DisplayName) values('chuck');
SELECT scope_identity() as ReturnID;
END
I didn't test that last statement, but I assume the long way would be something like that. If there is no built in way, I'd appreciate it if someone could simply correct that statement(as I'm sure it isn't completely correct).
You should take care about transactions as well:
set XACT_ABORT on
begin tran
declare #ID int
select #ID = ID from names with (holdlock, updlock) WHERE DisplayName='chuck'
if ##rowcount = 0
begin
INSERT INTO names(DisplayName) values('chuck');
set #ID = scope_identity();
end
select #ID as ReturnID;
commit tran
Note the usage of table hints - holdlock and updlock. They prevent another thread from executing exactly the same query and creating the row a second time. For more information look for isolation, synchronization, deadlocks, concurrent updates.
I would do:
IF (NOT EXISTS(SELECT null from names where DisplayName='Chuck'))
INSERT INTO Names (DisplayName) Values ('Chuck')
SELECT ID as ReturnID FROM Names where DisplayName='Chuck'
Doesn't save much though
go
create table names
(
Id int primary key identity(1,1),
displayname varchar(100),
);
go
create procedure P1
#displayname varchar(100)
as
insert into names (displayname)
select #displayname
where not exists (
select * from names, (select #displayname as displayname) as names2
where names.displayname = names2.displayname);
-- race condition is possible here,
-- but in some cases you still may get away with this
select id from names where displayname = #displayname;
go
declare #dn varchar(100);
set #dn = 'chuck'; exec P1 #dn; exec P1 #dn; exec P1 #dn;
set #dn = 'buck'; exec P1 #dn; exec P1 #dn;
select * from names;
go
drop table names; drop procedure P1;
Output would 1, 1, 1, 2, 2 and the table content two rows big.