Error when creating SQL Trigger

  • Hi all,

    I am a newbie in SQL scripts, appreciate if anyone can help.

    I have a table "SALEITEM", which has 4 column "SaleID,ProductID,Quantity,UnitSellingPrice". I would like to create a Trigger in this table, after a new row is inserted into the "SALEITEM' table, it will deduct the total quantity in inventory for that item. Then it will check total quantity, if less than 5, it will write to EmailData table. The script as the following:

    CREATE TRIGGER NewSaleItem

    AFTER INSERT ON SALEITEM

    REFERENCING NEW ROW AS newRow

    FOR EACH ROW

    AS

    BEGIN

    SET NOCOUNT ON;

    Declare @UpdatedQty int,@Subject as varchar(200), @Body varchar(8000), @To varchar(50),

    @From varchar(50),@ProductID int

    SET @UpdatedQty=NULL

    SET @Subject=NULL

    SET @Body=NULL

    SET @To=NULL

    SET @From=NULL

    SET @ProductID=newRow.ProductID

    UPDATE PRODUCT

    SET TotalQuantity = (TotalQuantity - newRow.Quantity)

    WHERE ProductID = @ProductID;

    --If updated qty less than 5 then trigger email

    SELECT @UpdatedQty=[dbo].[fnCheckProductQuantity](@ProductId)

    IF @UpdatedQty<5

    BEGIN

    SET @Subject= 'ALERT: Low Product Quantity'

    SET @Body= 'Please note that Product ' + STR(@ProductID) + ' is running low in quantity. Less than 5 quantity is available.'

    SET @From= 'abc@mail.com'

    SET @To= 'bcd@mail.com'

    --Insert into Email Table

    insert into EmailData([From],[To],[Subject],[Description],CreatedOn)

    values (@From,@To,@Subject,@Body,getdate())

    END

    END;

    However, when i compile it in the SQL server management studio, i got this error "Msg 102, Level 15, State 1, Procedure NewSaleItem, Line 2

    Incorrect syntax near 'AFTER'."

    IS MS SQL support AFTER INSERT Trigger? Anyone has any ideal what went wrong?

    Regards,

    Ken

  • gohsiauken (8/15/2008)


    CREATE TRIGGER NewSaleItem

    AFTER INSERT ON SALEITEM

    REFERENCING NEW ROW AS newRow

    FOR EACH ROW

    AS

    That's not SQL syntax. Looks like it might be Oracle. Not sure.

    Is this a SQL Server database, or is it another database type?

    Gail Shaw
    Microsoft Certified Master: SQL Server, MVP, M.Sc (Comp Sci)
    SQL In The Wild: Discussions on DB performance with occasional diversions into recoverability

    We walk in the dark places no others will enter
    We stand on the bridge and no one may pass
  • Hi All,

    I am sorry that mis-post the earlier question. Just get it updated.

    If this is not the correct way to write a trigger, can anyone tell me hwow to create a trigger with my requirements??

    Thank you very much,

    Regards,

    Ken

  • A few things that may help you get started. Firstly, if you check Books Online, you will see that the syntax for a trigger is as follows:

    CREATE TRIGGER < Trigger Name > ON < Table Name >

    AFTER < Operation to fire trigger >

    AS

    -- Code goes here

    GO

    In your code, you have the ON table name in the wrong place.

    The section

    REFERENCING NEW ROW AS newRow

    FOR EACH ROW

    is no T-SQL and has no equivalent. SQL Triggers fire once for an operation and the inserted/deleted tables will contain all the rows affected. Hence it is important to make sure that your code makes no assumptions about the number of rows in those tables.

    The rest of your code looks OK, except it needs to be adapted to handle any number of rows in the inserted/deleted tables, not just one.

    Gail Shaw
    Microsoft Certified Master: SQL Server, MVP, M.Sc (Comp Sci)
    SQL In The Wild: Discussions on DB performance with occasional diversions into recoverability

    We walk in the dark places no others will enter
    We stand on the bridge and no one may pass
  • Hi, this is the edited version after your advice.

    CREATE TRIGGER NewSaleItem

    ON SALEITEM

    AFTER INSERT

    AS

    BEGIN

    SET NOCOUNT ON;

    Declare @UpdatedQty int,@Subject as varchar(200), @Body varchar(8000), @To varchar(50),

    @From varchar(50),@ProductID int, @Quantity int

    SET @UpdatedQty=NULL

    SET @Subject=NULL

    SET @Body=NULL

    SET @To=NULL

    SET @From=NULL

    SET @ProductID=(select ProductID from inserted)

    SET @Quantity=(select Quantity from inserted)

    UPDATE PRODUCT

    SET TotalQuantity = (TotalQuantity - @Quantity)

    WHERE ProductID = @ProductID;

    --If updated qty less than 5 then trigger email

    SELECT @UpdatedQty=[dbo].[fnCheckProductQuantity](@ProductId)

    IF @UpdatedQty<5

    BEGIN

    SET @Subject= 'ALERT: Low Product Quantity'

    SET @Body= 'Please note that Product ' + STR(@ProductID) + ' is running low in quantity. Less than 5 quantity is available.'

    SET @From= 'abc@mail.com'

    SET @To= 'bcd@mail.com'

    --Insert into Email Table

    insert into EmailData([From],[To],[Subject],[Description],CreatedOn)

    values (@From,@To,@Subject,@Body,getdate())

    --Send Actual Email

    exec dbo.Cust_send_cdosysmail @From, @To, @Subject, @Body

    END

    END;

    Is it correct now???

    Regards,

    Ken

  • Better but you're still assuming that there will only be one row in the inserted tables, example the following line

    SET @ProductID=(select ProductID from inserted)

    There could be any number of rows in the inserted table. Consider how you will handle cases where two or more rows will be inserted into the SalesItem table in one insert statement.

    Gail Shaw
    Microsoft Certified Master: SQL Server, MVP, M.Sc (Comp Sci)
    SQL In The Wild: Discussions on DB performance with occasional diversions into recoverability

    We walk in the dark places no others will enter
    We stand on the bridge and no one may pass
  • This is DB2 syntax. Not MS SQL Server and a Gail pointed above there is no "exact" equivalent in TSQL.

    In addition sending email from a trigger is a very bad practice.


    * Noel

  • GilaMonster (8/15/2008)


    Better but you're still assuming that there will only be one row in the inserted tables, example the following line

    SET @ProductID=(select ProductID from inserted)

    There could be any number of rows in the inserted table. Consider how you will handle cases where two or more rows will be inserted into the SalesItem table in one insert statement.

    Hi

    Thank for your reply.

    To take care of multiple row insertion, can i use a CURSOR in the Trigger to filter which of the inserted row?

    Regards,

    Ken

  • You can.

    What would probably be best is to make the entire thing handle an number of rows (set-based processing instead of row by row) and then use a cursor just for the dbo.Cust_send_cdosysmail proc, since that's the only thing that has to process one row at a time.

    To get you started, consider this. I haven't included the send mail proc, but the rest should handle any number of rows in the inserted table

    CREATE TRIGGER NewSaleItem

    ON SALEITEM

    AFTER INSERT

    AS

    BEGIN

    SET NOCOUNT ON;

    UPDATE PRODUCT

    SET TotalQuantity = (TotalQuantity - i.Quantity)

    FROM inserted i

    WHERE i.ProductID = Product.ProductID

    --Insert into Email Table if any updated quantity is less than 5

    INSERT INTO EmailData([From],[To],[Subject],[Description],CreatedOn)

    SELECT 'abc@mail.com','bcd@mail.com' ,'ALERT: Low Product Quantity' ,'Please note that Product ' + STR(ProductID) + ' is running low in quantity. Less than 5 quantity is available.', getdate())

    FROM inserted where dbo.fnCheckProductQuantity(ProductID) < 5

    -- TODO: Send the mails that have just been inserted into the EmailData table.

    -- probably require a cursor, since the mail send is 1 row at a time

    -- dbo.Cust_send_cdosysmail

    END

    GO

    Gail Shaw
    Microsoft Certified Master: SQL Server, MVP, M.Sc (Comp Sci)
    SQL In The Wild: Discussions on DB performance with occasional diversions into recoverability

    We walk in the dark places no others will enter
    We stand on the bridge and no one may pass
  • The trigger source you posted adheres to the SQL Standard but is not supported by SQL Server,

    which uses a TransAct/SQL dialect. This is understandable, as SQL Server Triggers predate the

    standard by about 15 years.

    The SQL Standard Clause is "REFERENCING NEW ROW AS newRow"

    SQL Server does not support this clause, but instead has a fixed option which is the equivalent to

    "REFERENCING NEW ROW AS inserted"

    The SQL Standard Clause is "FOR EACH ROW"

    SQL Server does not support row level triggers, but instead has a fixed option which is the equivalent to:

    "FOR EACH STATEMENT"

    You will also find the SQL Standard Clause for when the trigger is to fire has options for BEFORE, AFTER,

    or INSTEAD OF, but SQL Server does not support the BEFORE option and does not support allowing

    the virtual table of "new row" to be updated.

    Be warned that you need to be very careful that all SQL Statements handle multi-row actions correctly.

    Here is example of the SQL necessary to maintain the PRODUCT.TotalQuantity when there are multiple rows

    inserted into the SALESITEM table, which might be for the same ProductID:

    UPDATE PRODUCT

    SET TotalQuantity = (PRODUCT.TotalQuantity - NewRow.Quantity)

    FROM (select inserted.ProductID

    , sum(Quantity) as Quantity

    from inserted

    group by inserted.ProductID

    HAVING sum(Quantity) <> 0

    ) as NewRow

    WHERE Product.ProductID = NewRow.ProductID

    SQL = Scarcely Qualifies as a Language

  • Additional comments:

    The SQL Standard allows multiple triggers on a table for the same action

    and allows specification of the order with the SEQUENCE keyword but

    SQL Server allows multiple triggers but only allows specification of what trigger will fire first and last.

    The logic to send an email when product.TotalQuantity is less than 5 should

    not be in a trigger on the SaleItem table. There are many solutions offered under SQL Server:

    1. Use Notification services.

    2. Put all the logic in a trigger on the Product table

    3. Put the detection logic in a trigger on the Product table, which inserts into a pending email table

    and then have a SQL Server job that runs on a regular basis that sends the actual emails.

    Using Notification services is the best practice as with a trigger, the only way to invoke the send mail routines is one at a time, which will require a cursor, and a cursor in a trigger will create a performance problem.

    CREATE TRIGGER Product_tUpdateAfter800

    -- Trigger names convention is the concatenation of TableName, underscore, "t", DML action, when and then a sequence number

    on dbo.Product AFTER update

    as

    set nocount on

    -- Note that the trigger is executed on the statement including the case when no rows are affected:

    IF 0 = (select count(*) from inserted) RETURN

    Declare @QuantityLevel integer

    set @QuantityLevel = 5

    --Insert into Email Table

    insert into EmailData([From],[To],[Subject],[Description],CreatedOn)

    select 'abc@mail.com' as From

    , 'bcd@mail.com' as Tp

    ,'ALERT: Low Product Quantity' as Subject

    , 'Please note that Product ' + STR(inserted.ProductID) + ' is running low in quantity. Less than ' + STR(@QuantityLevel) + ' quantity is available.'

    as Description

    , getdate() as createon

    from inserted

    join deleted

    on deleted.ProductID = inserted.ProductID

    where deleted.TotalQuantity <> inserted.TotalQuantity

    and inserted.TotalQuantity < @QuantityLevel

    go

    SQL = Scarcely Qualifies as a Language

  • GilaMonster (8/16/2008)


    You can.

    What would probably be best is to make the entire thing handle an number of rows (set-based processing instead of row by row) and then use a cursor just for the dbo.Cust_send_cdosysmail proc, since that's the only thing that has to process one row at a time.

    To get you started, consider this. I haven't included the send mail proc, but the rest should handle any number of rows in the inserted table

    CREATE TRIGGER NewSaleItem

    ON SALEITEM

    AFTER INSERT

    AS

    BEGIN

    SET NOCOUNT ON;

    UPDATE PRODUCT

    SET TotalQuantity = (TotalQuantity - i.Quantity)

    FROM inserted i

    WHERE i.ProductID = Product.ProductID

    --Insert into Email Table if any updated quantity is less than 5

    INSERT INTO EmailData([From],[To],[Subject],[Description],CreatedOn)

    SELECT 'abc@mail.com','bcd@mail.com' ,'ALERT: Low Product Quantity' ,'Please note that Product ' + STR(ProductID) + ' is running low in quantity. Less than 5 quantity is available.', getdate())

    FROM inserted where dbo.fnCheckProductQuantity(ProductID) < 5

    -- TODO: Send the mails that have just been inserted into the EmailData table.

    -- probably require a cursor, since the mail send is 1 row at a time

    -- dbo.Cust_send_cdosysmail

    END

    GO

    Hi GilaMonster,

    From your earlier post, if there are multiple rows are inserted at the same time, will the following code able to retreive each of the row inserted?

    UPDATE PRODUCT

    SET TotalQuantity = (TotalQuantity - i.Quantity)

    FROM inserted i

    WHERE i.ProductID = Product.ProductID

    For example, i have 3 rows inserted, does it mean that i = 3 rows and the SQL is inteligent enough to retrieve row by row and update the Product table accordingly??

    Thank for your help.

    Regards,

    Ken

  • Carl Federl (8/16/2008)


    Additional comments:

    The SQL Standard allows multiple triggers on a table for the same action

    and allows specification of the order with the SEQUENCE keyword but

    SQL Server allows multiple triggers but only allows specification of what trigger will fire first and last.

    The logic to send an email when product.TotalQuantity is less than 5 should

    not be in a trigger on the SaleItem table. There are many solutions offered under SQL Server:

    1. Use Notification services.

    2. Put all the logic in a trigger on the Product table

    3. Put the detection logic in a trigger on the Product table, which inserts into a pending email table

    and then have a SQL Server job that runs on a regular basis that sends the actual emails.

    Using Notification services is the best practice as with a trigger, the only way to invoke the send mail routines is one at a time, which will require a cursor, and a cursor in a trigger will create a performance problem.

    CREATE TRIGGER Product_tUpdateAfter800

    -- Trigger names convention is the concatenation of TableName, underscore, "t", DML action, when and then a sequence number

    on dbo.Product AFTER update

    as

    set nocount on

    -- Note that the trigger is executed on the statement including the case when no rows are affected:

    IF 0 = (select count(*) from inserted) RETURN

    Declare @QuantityLevel integer

    set @QuantityLevel = 5

    --Insert into Email Table

    insert into EmailData([From],[To],[Subject],[Description],CreatedOn)

    select 'abc@mail.com' as From

    , 'bcd@mail.com' as Tp

    ,'ALERT: Low Product Quantity' as Subject

    , 'Please note that Product ' + STR(inserted.ProductID) + ' is running low in quantity. Less than ' + STR(@QuantityLevel) + ' quantity is available.'

    as Description

    , getdate() as createon

    from inserted

    join deleted

    on deleted.ProductID = inserted.ProductID

    where deleted.TotalQuantity <> inserted.TotalQuantity

    and inserted.TotalQuantity < @QuantityLevel

    go

    Hi Carl,

    From your post, the method that you suggested is to let Product table handle the trigger. It means when a row is inserted in to SALEITEM table, the SALEITEM_tUpdate will be activated and update the Product table. And there is another trigger in PRODUCT table that will check whether the product quantity in less than 5. Correct me if i am wrong.

    But i do not very understand how this part work. Since there is only update the product's quantity, why do we have a "deleted" in the code?

    inserted

    join deleted

    on deleted.ProductID = inserted.ProductID

    where deleted.TotalQuantity <> inserted.TotalQuantity

    and inserted.TotalQuantity < @QuantityLevel

    go

    I have another question, when we use the trigger to update other table, how can we make sure that the data is in consistency? I afraid that after the data is inserted to SALEITEM table, then immediately there is a error occured before the trigger update the Product table.

    Any suggestion would be much appreciate.

    Regards,

    Ken

  • Hi,

    to your question "For example, i have 3 rows inserted, does it mean that i = 3 rows and the SQL is inteligent enough to retrieve row by row and update the Product table accordingly" - well, not precisely. SQL will not retrieve them row by row. SQL works with the entire set of 3 rows at once. This is probably the main difference from the way you are thinking about it. Don't think about separate rows when you work with SQL Server, think about rowsets.

    UPDATE table SET col1='AAA' WHERE col2 = 'BBB' will update all corresponding rows in one chunk, not one after another. Therefore, if there is update trigger on Table, all updated rows will be "copied" to tables called "inserted" and "deleted" (entire rows, not just the updated columns). Inserted holds new values, deleted holds old values... and you can normally use JOIN to both these tables in triggers, so that the trigger action is also performed for all updated rows at once, not row by row.

  • From your post, the method that you suggested is to let Product table handle the trigger. It means when a row is inserted in to SALEITEM table, the SALEITEM_tUpdate will be activated and update the Product table. And there is another trigger in PRODUCT table that will check whether the product quantity in less than 5. Correct me if i am wrong.

    You are correct - When the trigger is on the Product table, then any update to the TotalQuantity will invoke the notification logic.

    But i do not very understand how this part work. Since there is only update the product's quantity, why do we have a "deleted" in the code?

    The "deleted" table contains the rows as they appear before they are updated and the "inserted" the rows AFTER the update.

    The join of the inserted to the deleted is used and then a where to determine if the value of TotalQuantity has changed.

    As an update may NOT have changed the TotalQuantity, sending a subsequent email notifying that there is low inventory should not be performed.

    SQL = Scarcely Qualifies as a Language

Viewing 15 posts - 1 through 15 (of 18 total)

You must be logged in to reply to this topic. Login to reply