Make trigger with variables work for multiple row inserts - sql

I have a warehouse database and want to implement a trigger which makes sure that the number of products returned by customers cannot exceed the number of products sold minus the number of products returned previously.
It works for one insert in the RETURNS table, but gives me an error if I insert multiple rows at once.
How can I fix that?
Thanks!
The tables affected are
SALES_ITEMS (PRODUCT, SALE, SALES_QUANTITY)
RETURNS (IDRETURN, PRODUCT, SALE, RETURN_QUANTITY)
CREATE TRIGGER tr
ON Returns
AFTER UPDATE, INSERT
AS
BEGIN
DECLARE #product INTEGER;
DECLARE #sale INTEGER;
SET #product = (SELECT PRODUCT FROM Inserted);
SET #sale = (SELECT SALE FROM Inserted);
IF (SELECT SUM(r.RETURN_QUANTITY)
FROM RETURNS r
WHERE r.PRODUCT = #product
AND r.SALE = #sale) > (SELECT s.SALES_QUANTITY
FROM SALE_ITEMS s
WHERE s.PRODUCT = #product AND s.SALE = #sale)
BEGIN
ROLLBACK TRANSACTION;
END
END

Hmmm . . . this is a bit complicated. One method is to join the returns and sales together to see if there are any cases where the returns exceed the sales:
if exists (select 1
from RETURNS r join
inserted i
on r.PRODUCT = i.product and r.SALE = i.sale join
sales_items si
on si.product = i.product and si.sale = i.sale
group by r.product, s.sales_quantity
having sum(r.return_quantity) > s.sales_quantity
)
begin
ROLLBACK TRANSACTION;
end;

Related

SQL insert trigger condition statement and multiple rows

Could you please help me to finish my trigger. What i got so far:
CREATE TRIGGER [dbo].[atbl_Sales_OrdersLines_ITrigGG]
ON [dbo].[atbl_Sales_OrdersLines]
FOR INSERT
AS
BEGIN
DECLARE #ID INT = (SELECT ProductID
FROM INSERTED)
DECLARE #OrderedQ INT = (SELECT SUM(Amount)
FROM atbl_Sales_OrdersLines
WHERE ProductID = #ID)
DECLARE #CurrentQ INT = (SELECT Quantity
FROM atbl_Sales_Products
WHERE ProductID = #ID)
DECLARE #PossibleQ INT = (SELECT Amount
FROM INSERTED
WHERE ProductID = #ID)
IF (#CurrentQ - #OrderedQ >= #PossibleQ)
ELSE
END
I need to complete the code. Can not figure out how to do it. I need that if condition is met - trigger would allow the insert. If else, trigger would stop the insert/or rollback and prompt a message that quantity is not sufficient.
Also, will this code work if insert is multiple lines with different product ids?
Thanks.
Something like this might work. This trigger checks the products that are in the insert, summing the total that have been ordered (now and in the past), and if any of them exceed the available quantity, the whole transaction is rolled back. Whenever writing triggers, you want to avoid any assumptions that there is a single row being inserted/updated/deleted, and avoid cursors. You want to just use basic set based operations.
CREATE TRIGGER [dbo].[atbl_Sales_OrdersLines_ITrigGG]
ON [dbo].[atbl_Sales_OrdersLines]
FOR INSERT
AS
BEGIN
IF (exists (select 1 from (
select x.ProductId, totalOrdersQty, ISNULL(asp.Quantity, 0) PossibleQty from (
select i.ProductId, sum(aso.Amount) totalOrdersQty
from (select distinct ProductId from inserted) i
join atbl_Sales_OrdersLines aso on aso.ProductId = i.ProductId
group by productId) x
left join atbl_Sales_Product asp on asp.ProductId = x.ProductId
) x
where PossibleQty < totalOrdersQty))
BEGIN
RAISERROR ('Quantity is not sufficient' ,10,1)
ROLLBACK TRANSACTION
END
END
I still think this is a horrible idea.
Try this,
CREATE TRIGGER [dbo].[atbl_Sales_OrdersLines_ITrigGG]
ON [dbo].[atbl_Sales_OrdersLines]
INSTEAD OF INSERT --FOR INSERT
AS
BEGIN
DECLARE #ID INT = (SELECT ProductID
FROM INSERTED)
DECLARE #OrderedQ INT = (SELECT SUM(Amount)
FROM atbl_Sales_OrdersLines
WHERE ProductID = #ID)
DECLARE #CurrentQ INT = (SELECT Quantity
FROM atbl_Sales_Products
WHERE ProductID = #ID)
DECLARE #PossibleQ INT = (SELECT Amount
FROM INSERTED
WHERE ProductID = #ID)
IF (#CurrentQ - #OrderedQ >= #PossibleQ)
BEGIN
INSERT INTO YOURTABLE (COLUMN1, COLUMN2, COLUMN3, ..)
SELECT COLUMN1, COLUMN2, COLUMN3, ..
FROM inserted
END
ELSE
BEGIN
RAISERROR ('Quantity is not sufficient' ,10,1)
ROLLBACK TRANSACTION
END

inserting from a table to another table and activeting trigger

INSERT INTO SaleItem (ProdId, SaleQuantity)
SELECT ProdId, BasketProdQuantity FROM Basket;
CREATE TRIGGER TRG_Stock_Decrease
ON SaleItem
AFTER INSERT
AS
DECLARE #ProdId INT
DECLARE #SaleQuantity INT
SELECT #ProdId=ProdId,#SaleQuantity=SaleQuantity FROM inserted
UPDATE Product SET ProdStockQuantity=ProdStockQuantity-#SaleQuantity WHERE ProdId=#ProdId
UPDATE Product SET ProdNoOfSold =ProdNoOfSold+#SaleQuantity WHERE ProdId=#ProdId
DELETE FROM Basket Where ProdId = #ProdId
When I insert datas from Basket to SaleItem it works well, but trigger only work for first id that inserted. How can I make it work for all ids?
EDIT!
I changed my trigger like this and it kinda worked I guess.
CREATE TRIGGER TRG_Stock_Decrease
ON SaleItem
AFTER INSERT
AS
BEGIN
UPDATE Product SET ProdStockQuantity = ProdStockQuantity - (Select SaleQuantity From inserted Where Product.ProdId = inserted.ProdId ) Where Product.ProdId IN (Select ProdId From inserted)
UPDATE Product SET ProdNoOfSold =ProdNoOfSold + (Select SaleQuantity From inserted Where Product.ProdId = inserted.ProdId ) Where Product.ProdId IN (Select ProdId From inserted)
DELETE FROM Basket WHERE Basket.ProdId IN (Select ProdId From inserted)
END
Is that correct?
No need for each row or nested selects, just inner join with inserted :)
CREATE TRIGGER TRG_Stock_Decrease
ON SaleItem
AFTER INSERT
AS
UPDATE P
SET ProdStockQuantity=ProdStockQuantity-i.SaleQuantity,
ProdNoOfSold =ProdNoOfSold+i.SaleQuantity
FROM Product P
INNER JOIN inserted i
ON P.ProdId=i.ProdId
DELETE B
FROM Basket B
INNER JOIN inserted i
ON i.ProdId = B.ProdId
add FOR EACH ROW
DROP TRIGGER `TRG_Stock_Decrease`;
DELIMITER //
CREATE TRIGGER TRG_Stock_Decrease
AFTER INSERT ON `TABLE` FOR EACH ROW
BEGIN
DECLARE #ProdId INT;
DECLARE #SaleQuantity INT;
SELECT #ProdId=ProdId,#SaleQuantity=SaleQuantity FROM inserted;
UPDATE Product SET ProdStockQuantity=ProdStockQuantity-#SaleQuantity WHERE ProdId=#ProdId;
UPDATE Product SET ProdNoOfSold =ProdNoOfSold+#SaleQuantity WHERE ProdId=#ProdId;
DELETE FROM Basket Where ProdId = #ProdId;
END; //
DELIMITER ;

Microsoft SQL Server uses outdated values in stored procedures

I have a SQL Stored Procedure which is never ending because of some values which are cached. But that is just a guess after debugging the procedure.
while #poid is not NULL
BEGIN
Update Item set Sales = (Select Sales from V_ITEM_Hierarchy where POID=#poid) where ItemID=#poid
Select #poid = i.ItemID
from V_ITEM_Hierarchy t inner join Item i on (t.POID = i.POID)
where ( abs(coalesce(t.Sales,0)-coalesce(i.Sales,0)) > 0.0001
END
I update the value "Sales" in Table Item with the Sales value of a view called "V_ITEM_Hierarchy" and then look again for values which are different. When I debug through the procedure the select-statement always returns the same value even if the Sales values are not different anymore because they were updated.
I tried to insert the command "DBCC DROPCLEANBUFFERS" but the select-statement still returns old values.
If the second query does not return any rows the value of #poid will NOT be updated. What you need is
while #poid is not NULL
BEGIN
Update Item set Sales = (Select Sales from V_ITEM_Hierarchy where POID=#poid) where ItemID=#poid
set #poid = null
Select #poid = i.ItemID
from V_ITEM_Hierarchy t inner join Item i on (t.POID = i.POID)
where ( abs(coalesce(t.Sales,0)-coalesce(i.Sales,0)) > 0.0001
END
I think you would be much better off changing this to a cursor because you will always have the possibility of an endless loop the way it is written now.
DECLARE #poid INT
DECLARE item_cursor CURSOR FOR
Select i.ItemID
from V_ITEM_Hierarchy t inner join Item i on (t.POID = i.POID)
where ( abs(coalesce(t.Sales,0)-coalesce(i.Sales,0)) > 0.0001
OPEN item_cursor
FETCH NEXT FROM item_cursor INTO #poid
WHILE ##FETCH_STATUS = 0
BEGIN
Update Item set Sales = (Select Sales from V_ITEM_Hierarchy where POID=#poid) where ItemID=#poid
FETCH NEXT FROM item_cursor INTO #poid
END
DEALLOCATE item_cursor

update multiple rows with trigger after insert (sql server)

I have a table orderDetails that contains the products of an order
productId
color
size
quantity
and a table stock
productId
size
color
stock
When a order is completed I use this query to insert the items in the table orderDetails
INSERT INTO orderDetail(orderId, productId, productColor, productSize, productQuantity , cost productName)
SELECT
#orderId, products_translations.id, cart.productColor, cart.productSize,
cart.productQuantity, cart.cost, products_translations.name
FROM cart
INNER JOIN products_translations ON cart.productID = products_translations.id
WHERE
(cart.cartId = #cartId) AND
(products_translations.language = 1)
Then I have a trigger on table orderDetails:
ALTER TRIGGER [dbo].[scalaProdotti]
ON [dbo].[orderDetail]
FOR INSERT
AS
DECLARE #size int
DECLARE #color char(6)
DECLARE #quantity int
DECLARE #product int
BEGIN
SELECT #size = productSize FROM inserted
SELECT #color = productColor FROM inserted
SELECT #quantity = productQuantity FROM inserted
SELECT #product = productId FROM inserted
UPDATE stock SET quantity = quantity - #quantity WHERE size=#size AND color=#color AND product=#product
END
With this trigger I want to decrease the stock, but only the first product is affected, the other quantities remain the same.
What am I missing?
Thank you.
The main point is: you're assuming that the trigger will be called for each row being inserted - this is not the case.
Your trigger will be called once per statement - but that statement can insert mulitple rows at once.
In such a case, the Inserted table inside the trigger will contain multiple rows and your statements:
SELECT #size = productSize FROM inserted
SELECT #color = productColor FROM inserted
SELECT #quantity = productQuantity FROM inserted
SELECT #product = productId FROM inserted
will fail or will happen to select only the first row inserted and disregard the rest of the inserts.
You need to rewrite your trigger to cope with the fact that Inserted can contain multiple inserted rows at the same time
Your code in the trigger should look something like this:
UPDATE stock
FROM Inserted i
SET
stock.quantity = quantity - i.quantity
WHERE
stock.size = i.size
AND stock.color = i.color
AND stock.product = i.product

How to update a column fetched by a cursor in TSQL

Before I go any further: Yes, I know that cursors perform poorly compared with set-based operations. In this particular case I'm running a cursor on a temporary table of 100 or so records, and that temporary table will always be fairly small, so performance is less crucial than flexibility.
My difficulty is that I'm having trouble finding an example of how to update a column fetched by a cursor. Previously when I've used cursors I've retrieved values into variables, then run an update query at each step based upon these values. On this occasion I want to update a field in the temporary table, yet I can't figure out how to do it.
In the example below, I'm trying to update the field CurrentPOs in temporary table #t1, based upon a query that uses #t1.Product_ID to look up the required value. You will see in the code that I have attempted to use the notation curPO.Product_ID to reference this, but it doesn't work. I have also attempted to use an update statement against curPO, also unsuccessfully.
I can make the code work by fetching to variables, but I'd like to know how to update the field directly.
I think I'm probably missing something obvious, but can anyone help?
declare curPO cursor
for select Product_ID, CurrentPOs from #t1
for update of CurrentPOs
open curPO
fetch next from curPO
while ##fetch_status = 0
begin
select OrderQuantity = <calculation>,
ReceiveQuantity = <calculation>
into #POs
from PurchaseOrderLine POL
inner join SupplierAddress SA ON POL.Supplier_ID = SA.Supplier_ID
inner join PurchaseOrderHeader POH ON POH.PurchaseOrder_ID = POL.PurchaseOrder_ID
where Product_ID = curPO.Product_ID
and SA.AddressType = '1801'
update curPO set CurrentPOs = (select sum(OrderQuantity) - sum(ReceiveQuantity) from #POs)
drop table #POs
fetch next from curPO
end
close curPO
deallocate curPO
After doing a bit more googling, I found a partial solution. The update code is as follows:
UPDATE #T1
SET CURRENTPOS = (SELECT SUM(ORDERQUANTITY) - SUM(RECEIVEQUANTITY)
FROM #POS)
WHERE CURRENT OF CURPO
I still had to use FETCH INTO, however, to retrieve #t1.Product_ID and run the query that produces #POs, so I'd still like to know if it's possible to use FETCH on it's own.
Is this what you want?
declare curPO cursor
for select Product_ID, CurrentPOs from #t1
for update of CurrentPOs
open curPO
fetch next from curPO
while ##fetch_status = 0
begin
update curPO set CurrentPOs =
(select sum(<OrderQuantityCalculation>)
from PurchaseOrderLine POL
inner join SupplierAddress SA ON POL.Supplier_ID = SA.Supplier_ID
inner join PurchaseOrderHeader POH ON POH.PurchaseOrder_ID = POL.PurchaseOrder_ID
where Product_ID = curPO.Product_ID
and SA.AddressType = '1801') -
(select sum(<ReceiveQuantityCalculation>)
from PurchaseOrderLine POL
inner join SupplierAddress SA ON POL.Supplier_ID = SA.Supplier_ID
inner join PurchaseOrderHeader POH ON POH.PurchaseOrder_ID = POL.PurchaseOrder_ID
where Product_ID = curPO.Product_ID
and SA.AddressType = '1801')
fetch next from curPO
end
close curPO
deallocate curPO
Maybe you need something like that:
update DataBaseName..TableName
set ColumnName = value
where current of your_cursor_name;
Here's an example to calculate one column based upon values from two others (note, this could be done during the original table select). This example can be copy / pasted into an SSMS query window to be run without the need for any editing.
DECLARE #cust_id INT = 2, #dynamic_val NVARCHAR(40), #val_a INT, #val_b INT
DECLARE #tbl_invoice table(Cust_ID INT, Cust_Fees INT, Cust_Tax INT)
INSERT #tbl_invoice ( Cust_ID, Cust_Fees, Cust_Tax ) SELECT 1, 111, 11
INSERT #tbl_invoice ( Cust_ID, Cust_Fees, Cust_Tax ) SELECT 2, 222, 22
INSERT #tbl_invoice ( Cust_ID, Cust_Fees, Cust_Tax ) SELECT 3, 333, 33
DECLARE #TblCust TABLE
(
Rec_ID INT
, Val_A INT
, Val_B INT
, Dynamic_Val NVARCHAR(40)
, PRIMARY KEY NONCLUSTERED (Rec_ID)
)
INSERT #TblCust(Rec_ID, Val_A, Val_B, Dynamic_Val)
SELECT Rec_ID = Cust_ID, Val_A = Cust_Fees, Val_B = Cust_Tax, NULL
FROM #tbl_invoice
DECLARE cursor_cust CURSOR FOR
SELECT Rec_ID, Val_A, Val_B, Dynamic_Val
FROM #TblCust
WHERE Rec_ID <> #cust_id
FOR UPDATE OF Dynamic_Val;
OPEN cursor_cust;
FETCH NEXT FROM cursor_cust INTO #cust_id, #val_a, #val_b, #dynamic_val;
WHILE ##FETCH_STATUS = 0
BEGIN
UPDATE #TblCust
SET Dynamic_Val = N'#c = "' + LTRIM(STR((#val_a + #val_b), 40)) + N'"'
WHERE CURRENT OF cursor_cust
FETCH NEXT FROM cursor_cust INTO #cust_id, #val_a, #val_b, #dynamic_val;
END
CLOSE cursor_cust
DEALLOCATE cursor_cust
SELECT * FROM #TblCust