SQL Server Trigger Variable scope - sql

Could you please tell me what's wrong this this trigger (below is just a portion of the trigger).
When put variable on where parameter I don't get any result, instead, when I set is a default value I get correct result
Like this it doesn't work (I verified and #Cont_local is not null and data really exists inside the database):
Note:
info (information) is in a different table but on the same database.
the trigger is set to work on Cont (Contact) Table
select #Cont_local = ContID from inserted
SET #Addr = (select Addr from Info I where I.ContID = #Cont_local)
But like this, it works
select #Cont_local = '454877'
SET #Addr = (select Addr from Info I where I.ContID = #Cont_local)
While #Cont_local is returning some values, the select statement cannot detect it.
Is this related to the scope of #Cont_local variable or something else?
Instead, when I try to put data manually, query return values and store it on #Addr
Here is Complete code
ALTER TRIGGER [dbo].[Insert__]
ON [dbo].[Cont]
AFTER INSERT
AS
BEGIN
DECLARE #Cont_local nvarchar(50);
DECLARE #Name nvarchar(50);
DECLARE #firstnameme nvarchar(50);
DECLARE #Addr nvarchar(50);
select #Name = contName from inserted
select #firstname = contfirstname from inserted
select #Cont_local = ContID from inserted
SET #Addr = (select Addr from Info I where I.ContID = #Cont_local)
INSERT INTO Final_Contact (Name, firstname, address) values
(#Name, #firstname ,#Addr)
END

select #Cont_local = ContID from inserted
This line of code works only when in the inserted-table is only one record

This addresses the original version of the question.
I don't see why your code would return an error. However, it is malformed and an error waiting to happen. Let me explain.
This code:
select #Cont_local = ContID from inserted
Assumes that inserted has one row. Of course, the code runs when inserted has more than one row. It just returns one value.
The inserted and deleted views in SQL Server triggers can refer to multiple rows. In other words, triggers are set-based processing rather than row-by-row processing. In a properly structured trigger, the references should always be in FROM clauses.
The following example is totally made up, but it illustrates how to update a column in another table using information from inserted:
update a
set addr = c.addr
where inserted i join
info i
on i.contid = a.contid join
addresses a
on a.addrid = i.addrid

Related

T-SQL String Replace is Not Locking - Last Update Wins

I have the following stored procedure, which is intended to iterate through a list of strings, which contains several substrings of the form prefix.bucketName. I want to iterate through each string and each bucket name, and replace the old prefix with a new prefix, but keep the same bucket name.
To give an example, consider this original string:
"(OldPrefix.BucketA)(OldPrefix.BucketB)"
So for example I would like to get:
"(NewPrefix.BucketA)(NewPrefix.BucketB)"
What I actually get is this:
"(OldPrefix.BucketA)(NewPrefix.BucketB)"
So, in general, only one of the prefixes get updated, and it is not predictable which one. Based on some investigation I have done, it appears that both replacements actually work, but only the last one is actually saved. It seems like SQL should be locking this column but instead, both are read at the same time, the replace is applied, and then both are written, leaving the last write as what shows in the column.
Here is the query - All variable names have been changed for privacy - Some error handling and data validation code was left out for brevity:
DECLARE #PrefixID INT = 1478,
DECLARE #PrefixName_OLD NVARCHAR(50) = 'OldPrefix',
DECLARE #PrefixName_NEW NVARCHAR(50) = 'NewPrefix'
BEGIN TRAN
-- Code to rename the section itself here not shown for brevity
UPDATE
dbo.Component
SET
AString= REPLACE(AString,'('+#Prefix_OLD+'.'+b.BucketName+')', '('+#PrefixName_NEW+'.'+b.BucketName+')'),
FROM
dbo.Component sc
JOIN
dbo.ComponentBucketFilterInString fis
ON
sc.ComponentID = fis.ComponentID
JOIN
dbo.Buckets b
ON
fis.BucketID = b.BucketID
WHERE
b.PrefixID = #PrefixID
COMMIT
RETURN 1
When I write the same query using a while loop, it performs as expected:
DECLARE #BucketsToUpdate TABLE
(
BucketID INT,
BucketName VARCHAR(256)
)
INSERT INTO #BucketsToUpdate
SELECT BucketID, BucketName
FROM Buckets WHERE PrefixID = #PrefixID
WHILE EXISTS(SELECT 1 FROM #BucketsToUpdate)
BEGIN
DECLARE #currentBucketID INT,
#currentBucketName VARCHAR(256)
SELECT TOP 1 #currentBucketID = bucketID, #currentBucketName = bucketName FROM #BucketsToUpdate
UPDATE
dbo.Component
SET
AString = REPLACE(AString,'('+#PrefixName_OLD+'.'+#currentBucketName+')', '('+#PrefixName_NEW+'.'+#currentBucketName+')')
FROM
dbo.Component sc
JOIN
dbo.ComponentBucketFilterInString fis
ON
sc.ComponentID = fis.ComponentID
WHERE fis.BucketID = #currentBucketID
DELETE FROM #BucketsToUpdate WHERE BucketID = #currentBucketID
END
Why does the first version fail? How can I fix it?
The problem you are experiencing is "undefined" behavior when there is more than single match possible for UPDATE FROM JOIN.
In order to make your update possible you should run it multiple times updating one pair of values at a time as you proposed in your second code demo.
Related: How is this script updating table when using LEFT JOINs? and Let’s deprecate UPDATE FROM!:
SQL Server will happily update the same row over and over again if it matches more than one row in the joined table, >>with only the result of the last of those updates sticking<<.
Not sure why you are making the whole process so complex. May be I am not clearly understanding the requirement. As per my understanding, you are looking to update only Prefix part for column 'AString' in the table dbo.Component. Current value for example is-
(OldPrefix.BucketA)(OldPrefix.BucketB)
You wants to update the value as-
(NewPrefix.BucketA)(NewPrefix.BucketB)
Am I right? If yes, you can update all records with a simple Update script as below-
DECLARE #PrefixID INT = 1478
DECLARE #PrefixName_OLD NVARCHAR(50) = 'OldPrefix'
DECLARE #PrefixName_NEW NVARCHAR(50) = 'NewPrefix'
UPDATE Component
SET AString= REPLACE(AString,#PrefixName_OLD,#PrefixName_NEW)

Prevent column change without the other

No-one should be allowed to update the customer address column unless the postcode column is also updated. If an attempt is made to update one without the other, then a trigger will fire and the user will be shown an error message.
Any help on how I can do this in Microsoft SQL Server 2012 will be appreciated!
You can use below logic
CREATE TRIGGER [dbo].AU_MyTrigger ON [dbo].MyTable
FOR UPDATE
AS
BEGIN
declare #bu_addr varchar(100)
declare #bu_zip varchar(100)
declare #au_addr varchar(100)
declare #au_zip varchar(100)
select #bu_addr = addr, #bu_zip = zip from DELETED
select #au_addr = addr, #ay_zip = zip from INSERTED
if (#bu_addr <> #au_addr) and (#bu_zip = #au_zip)
BEGIN
-- update table with old values
-- raise error
END
END
Note that if this update can happen in batch, you need to loop through each record and update their value to old and only return error at the end of trigger (outside of loop). In that case, for iterating on updated rows, you need to use CURSOR
You case might not be as easy as I explained, but this is the approach that works.

Update Trigger Appends data twice to column

O.F.,
So I have been attempting to build a trigger that will update a table based on the when a a row is updated in a different table. The trigger so far looks like this.
ALTER TRIGGER [dbo].[tst_update_USCATVLS_6]
ON [dbo].[IV00101]
AFTER UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #ITEMNUMBER VARCHAR(75)
SET #ITEMNUMBER = (SELECT ITEMNMBR FROM dbo.IV00101 WHERE DEX_ROW_TS = (SELECT MAX(DEX_ROW_TS) FROM dbo.IV00101))
UPDATE dbo.EXT00101 SET STRGA255 = (RTRIM(LTRIM(STRGA255))) + '_0' WHERE PT_UD_KEY = #ITEMNUMBER AND PT_UD_Number = 2
What seems to happen when I run the test update like the one below.
UPDATE PDM.TEST.dbo.IV00101
SET USCATVLS_6 = 'OBSOLETE'
WHERE ITEMNMBR = 'HMLGDN-7563252-4'
Is that the trigger fires but updates the desired column twice. The end result being this 20025947756319_0_0 instead of this 20025947756319_0.
The weird part of all of this is if I drop the trigger and run the same test update and then run the update statement that was in the trigger all statements execute and the data is updated as desired.
So running this as one block of code works:
UPDATE PDM.TEST.dbo.IV00101
SET USCATVLS_6 = 'OBSOLETE'
WHERE ITEMNMBR = 'HMLGDN-7563252-4'
DECLARE #ITEMNUMBER VARCHAR(75)
SET #ITEMNUMBER = (SELECT ITEMNMBR FROM PDM.TEST.dbo.IV00101 WHERE DEX_ROW_TS = (SELECT MAX(DEX_ROW_TS) FROM PDM.TEST.dbo.IV00101))
UPDATE PDM.TEST.dbo.EXT00101 SET STRGA255 = (RTRIM(LTRIM(STRGA255))) + '_0' WHERE PT_UD_KEY = #ITEMNUMBER AND PT_UD_Number = 2
If any one can help me figure out why this is happening I would greatly appreciate it.
Kindest regards,
Z.
Having read Sean Lange's Comments I looked up inserted and deleted tables. I saw reference to these tables before but didn't realize they were temporary tables and thought they were physical tables of the database in the example/answers I saw. Anyway I created a trigger to show the data in each which helped me see how to join them to the update statement in the trigger. All the code is below.
Trigger to see what was in Inserted and Deleted as well as the join referencing the Inserted table:
ALTER TRIGGER [dbo].[inserted_deleted_Table]
ON [dbo].[IV00101]
AFTER UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
select 'NEW DATA', * from inserted --new data
select 'OLD Data', * from deleted --old data
select 'iv00101', * From iv00101 as i JOIN inserted as u on i.itemNmbr = u.itemNmbr and i.DEX_ROW_TS = u.DEX_ROW_TS
-- Insert statements for trigger here
END
The end solution was to remove this peice of code:
SET #ITEMNUMBER = (SELECT ITEMNMBR FROM INV00101 WHERE DEX_ROW_TS = (SELECT MAX(DEX_ROW_TS) FROM INV00101 ) AND USCATVLS_6 ='OBSOLETE' )
And then Add a the FROM clause to the UPDATE Statement:
UPDATE PDM.TEST.dbo.EXT00101 SET STRGA255 = (RTRIM(LTRIM(STRGA255))) + '_0'
FROM (
SELECT u.ITEMNMBR ,
u.DEX_ROW_TS
From iv00101 as i JOIN inserted as u on i.itemNmbr = u.itemNmbr and i.DEX_ROW_TS = u.DEX_ROW_TS) as p
WHERE PT_UD_KEY = p.ITEMNMBR AND PT_UD_Number = 2
Once I figured out that insert and deleted table were temp tables not actual tables in a DB all the pieces sorta fell into place. Thanks for pointing me in the direction I needed to go #Sean Lange.
Best Regards,
Z.

How to check number of rows changed by last update in SQL Server

I can use ##ROWCOUNT to check number of affected rows by the last UPDATE statement. But it is not number of changed rows in database.
After executing statements like:
UPDATE User
SET FirstName = #FirstName
WHERE Id = #Id
value in ##ROWCOUNT is always 1 - in both situations: when new FirstName is different than old one or not (has been changed or not).
Is there any built-in method to check how many rows has been really changed (not only affected) by last UPDATE?
Check if you have any Triggers being fired instead of the actual Update statement.
To get the row affected by your update statement you can use OUTPUT clause inside your UPDATE statement to see the rows that were actually updated, by doing something like this.....
DECLARE #TABLE TABLE (FirstName VARCHAR(100));
UPDATE [User]
SET FirstName = #FirstName
OUTPUT Inserted.FirstName INTO #TABLE
WHERE Id = #Id;
SELECT COUNT(*) FROM #TABLE;
Not really, because mysql isn't checking to see if it needs to do an update, it's just doing it. What you can do is this:
UPDATE User SET FirstName = #FirstName WHERE Id = #Id AND FirstName <> #FirstName

How to deal with Stored Procedure?

Hello I am new in creating stored procedure can you help me how to do this.
Error:
Incorrect syntax near the keyword 'AS'.
Must declare scalar variable #Serial.
CREATE PROCEDURE sp_SIU
-- Add the parameters for the stored procedure here
#Serial varchar(50),
#Part varchar(50),
#Status varchar(50),
AS
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
/*SET NOCOUNT ON;*/
-- Insert statements for procedure here
--where in my form if i enter serial number it will show select values
Select SerialNumber,PartNumber,Status from Table1 where SerialNUmber = #Serial
--Then if is correct it will Update Status on combobox
Update Table1 SET
Status=#Status
where SerialNumber=#SerialNumber
--then Insert Serial Number,Parnumber to Table 2
DECLARE #Count int
select #Count = Count(SerialNumber) from Table1 WHERE SerialNumber = #Serial
IF #Count = 0
BEGIN
INSERT INTO Table2 (SerialNumber,PArtNumber)
VALUES
(#Serial, #Part)
END
RETURN #Count
RETURN
Edit: Moved Updated info posted as an answer into Question
Oops my post is not that kind a miss.
It is possible to join this 3 sql string in one stored procedure?
Scenario:
{
What i have to do in my form is that i will enter serial number to txtserial.text by using the select sql it will show serialnumber,partnumber and status on lblserial.text,lblpartnumber.text and lblstatus.text.
And i will compare:
txtserial.text == lblserial.text
txtpartnumber.text == lblpartnumber.text
for my error handler.
{
Select SerialNumber,PartNumber,Status from Table1 where SerialNUmber = #Serial
}
Then if they are equal then:
I will update my Status from cbostatus.text if serial and part is correct then use sql upate.
{
Update Table1 SET
Status=#Status,
Modifiedby=#username,
DateModified=#Date
where SerialNumber=#Serial
}
Then insert serialnumber, using sql insert to another table.
{
INSERT INTO Table2 (SerialNumber,DateCreated,Createdby)
VALUES
(#Serial,#date,#username)
}
something likethis.
")
You have a rogue comma here
#Status varchar(50),
AS
and the name lurches between #Serial and #SerialNumber are these intended to be 2 different parameters?
Also what is the purpose of this line?
Select SerialNumber,PartNumber,Status from Table1 where SerialNUmber = #Serial
Currently it will just send back a 3 column result set to the calling application. Is that what it is intended to do (it doesn't seem to match the following comment which seems to imply it is meant to be some kind of check)?
Yes, you can execute 3 SQL statements inside one stored procedure. You probably want to declare some local variables inside your sproc to hold the intermediate results, i.e.
CREATE PROCEDURE BLAHBLAH
#SerialNumber VarChar(50)
AS
BEGIN
DECLARE #partnumber varchar(50);
SELECT #partnumber = partnumber FROM Table WHERE serialnumber = #SerialNumber;
...
SELECT #partnumber; --- return as recordset
RETURN #partnumber; --- return as return value
END
Then you can later insert #partnumber, test #partnumber, return #partnumber etc. I don't quite understand what you want to do; seems like you mostly want to look up a partnumber based on a serial number, but you want to do some uniqueness tests also. It would help if you could clarify the goal a bit more.
I recommend you ignore the user interface stuff for the moment. Write yourself some nice clean stored procedures that encapsulate the transaction and will do the right thing even if fired off at the same time from two different connections. Get everything working to your satisfaction in your SQL environment. Then go back to the user interface.
Oops my post is not that kind a miss.
It is possible to join this 3 sql string in one stored procedure?
Scenario:
What I have to do in my form is that I will enter serial number to txtserial.text by using the select sql it will show serialnumber,partnumber and status on lblserial.text,lblpartnumber.text and lblstatus.text.
AndI will compare:
txtserial.text == lblserial.text
txtpartnumber.text == lblpartnumber.text
for my error handler.
{
Select SerialNumber,PartNumber,Status from Table1 where SerialNUmber = #Serial
}
Then if they are equal then:
I will update my Status from cbostatus.text if serial and part is correct then use sql update.
{
Update Table1
SET Status = #Status,
Modifiedby = #username,
DateModified = #Date
where SerialNumber = #Serial
}
Then insert serialnumber, using sql insert to another table.
{
INSERT INTO Table2(SerialNumber, DateCreated, Createdby)
VALUES(#Serial, #date, #username)
}
something like this.