Delete Trigger with Update Subquery Statement with Multirow Delete - sql

This trigger is working fine with single row delete, but on multiple row
CREATE TRIGGER [dbo].[AfterDeleteStockUpdate]
ON [dbo].[ItemLedger]
AFTER DELETE
AS
DECLARE #productId uniqueidentifier
SELECT #productId = del.Product
FROM deleted del;
UPDATE products
SET inStock = (SELECT SUM(ISNULL(inQty, 0)) - SUM(ISNULL(outQty, 0))
FROM itemledger
WHERE product = #productId),
PurchasedValue = (SELECT SUM(ISNULL(PurchaseAmount, 0)) - SUM(ISNULL(Amount, 0))
FROM itemledger
WHERE product = #productId)
WHERE id = #productId

The Deleted pseudo-table can have 0-N rows in it, which you need to handle. And like all T-SQL you want to be using a fully set-based approach wherever possible anyway as thats what SQL Server is optimised for.
I believe the following should accomplish what you are wanting.
CREATE TRIGGER [dbo].[AfterDeleteStockUpdate]
ON [dbo].[ItemLedger]
AFTER DELETE
AS
BEGIN
SET NOCOUNT ON;
UPDATE P SET
inStock = L.inStock
, PurchasedValue = L.PurchasedValue
FROM Products P
INNER JOIN (
SELECT SUM(ISNULL(L.inQty, 0)) - SUM(ISNULL(L.outQty, 0)) inStock
, SUM(ISNULL(L.PurchaseAmount, 0)) - SUM(ISNULL(L.Amount, 0)) PruchasedValue
FROM itemledger L
GROUP BY L.product
) L ON L.product = P.id
WHERE P.id IN (SELECT Product from Deleted);
END;

Related

SQL Trigger insert problems

I want to make a trigger where I can check if the value from the stock of a product 0 is.
Then the trigger should make the value 1.
My Trigger
CREATE TRIGGER [IfStockIsNull]
ON [dbo].[Products]
FOR DELETE, INSERT, UPDATE
AS
BEGIN
if [Products].Stock = 0
UPDATE [Products] SET [Stock] = 1
FROM [Products] AS m
INNER JOIN inserted AS i
ON m.ProductId = i.ProductId;
ELSE
raiserror('Niks aan het handje',10,16);
END
I'm getting the error:
Altering [dbo].[IfStockIsNull]...
(53,1): SQL72014: .Net SqlClient Data Provider: Msg 4104, Level 16, State 1,
Procedure IfStockIsNull, Line 7 The multi-part identifier "Products.Stock"
could not be bound.
(47,0): SQL72045: Script execution error. The executed script:
ALTER TRIGGER [IfStockIsNull]
ON [dbo].[Products]
FOR DELETE, INSERT, UPDATE
AS BEGIN
IF [Products].Stock = 0
UPDATE [Products]
SET [Stock] = 1
FROM [Products] AS m
INNER JOIN
inserted AS i
ON m.ProductId = i.ProductId;
ELSE
RAISERROR ('Niks aan het handje', 10, 16);
END
An error occurred while the batch was being executed.
Maybe you guys can help me out?
A number of issues and surprises in what you're trying to run here. But basically, don't try to run procedural steps when you're dealing with sets. inserted can contain 0, 1 or multiple rows, so which row's stock are you asking about in your IF?
Better to deal with it in the WHERE clause:
CREATE TRIGGER [IfStockIsNull]
ON [dbo].[Products]
FOR INSERT, UPDATE --Removed DELETE, because ?!?
AS
BEGIN
UPDATE [Products] SET [Stock] = 1
FROM [Products] AS m
INNER JOIN inserted AS i
ON m.ProductId = i.ProductId;
WHERE m.Stock = 0
--Not sure what the error is for - the above update may have updated
--some number of rows, between 0 and the number in inserted.
--What circumstances should produce an error then?
END
Simple demo script that the UPDATE is correctly targetting only matching rows from inserted:
declare #t table (ID int not null, Val int not null)
insert into #t(ID,Val) values (1,1),(2,2),(3,3)
update
#t
set
Val = 4
from
#t t
inner join
(select 2 as c) n
on
t.ID = n.c
select * from #t
Shows that only the row with ID of 2 is updated.
Even Microsoft's own example of UPDATE ... FROM syntax uses the UPDATE <table> ... FROM <table> <alias> ... syntax that Gordon asserts doesn't work:
USE AdventureWorks2012;
GO
UPDATE Sales.SalesPerson
SET SalesYTD = SalesYTD + SubTotal
FROM Sales.SalesPerson AS sp
JOIN Sales.SalesOrderHeader AS so
ON sp.BusinessEntityID = so.SalesPersonID
AND so.OrderDate = (SELECT MAX(OrderDate)
FROM Sales.SalesOrderHeader
WHERE SalesPersonID = sp.BusinessEntityID);
GO
This example does have an issue which is further explained below it but that relates to the "multiple matching rows in other tables will result in only a single update" flaw that anyone working with UPDATE ... FROM should make themselves aware of.
If you want to stop the insert if any rows are bad, then do that before making the changes:
ALTER TRIGGER [IfStockIsNull] ON [dbo].[Products]
FOR INSERT, UPDATE -- DELETE is not really appropriate
AS BEGIN
IF (EXISTS (SELECT 1
FROM Products p JOIN
inserted i
ON m.ProductId = i.ProductId
WHERE p.Stock = 0
)
)
BEGIN
RAISERROR ('Niks aan het handje', 10, 16);
END;
UPDATE p
SET Stock = 1
FROM Products p INNER JOIN
inserted AS i
ON p.ProductId = i.ProductId;
END;
Notes:
The comparison in the IF checks if any of the Product rows have a 0. If so, the entire transaction is rolled back.
You might complain that you cannot reject a single row. But that is how transactions work. If any row fails in an INSERT, the entire INSERT is rolled back, not just a single row.
The UPDATE is not correct, because you have Products in the FROM clause and the UPDATE clause. You need to use the alias for the first one.

SQL Server query inefficient for table with high I/O operations

I'm trying to write an sql script that returns an item from a list, if that item can be found in the list, if not, it returns the most recent item added to the list. I came up with a solution using count and an if-else statement. However my table has very frequent I/O operations and I think this solution is inefficient. Does anyone have a away to optimize this solution or a better approach.
here is my solution:
DECLARE #result_set INT
SET #result_set = (
SELECT COUNT(*) FROM
( SELECT *
FROM notification p
WHERE p.code = #code
AND p.reference = #reference
AND p.response ='00'
) x
)
IF(#result_set > 0)
BEGIN
SELECT *
FROM notification p
WHERE p.code = #code
AND p.reference = #reference
AND p.response ='00'
END
ELSE
BEGIN
SELECT
TOP 1 p.*
FROM notification p (nolock)
WHERE p.code = #code
AND p.reference = #reference
ORDER BY p.id DESC
END
I also think there should be a way around repeating this select statement:
SELECT *
FROM notification p
WHERE p.code = #code
AND p.reference = #reference
AND p.response ='00'
I'm just not proficient enough in SQL to figure it out.
You can do something like this:
SELECT TOP (1) n.*
FROM notification n
WHERE p.code = #code AND p.reference = #reference
ORDER BY (CASE WHEN p.response ='00' THEN 1 ELSE 2 END), id DESC;
This will return the row with response of '00' first and then any other row. I would expect another column i the ORDER BY to handle recency, but your sample code doesn't provide any clue on what this might be.
WITH ItemIWant AS (
SELECT *
FROM notification p
WHERE p.code = #code
AND p.reference = #reference
AND p.response ='00'
),
SELECT *
FROM ItemIWant
UNION ALL
SELECT TOP 1 *
FROM notification p
WHERE p.code = #code
AND p.reference = #reference
AND NOT EXISTS (SELECT * FROM ItemIWant)
ORDER BY id desc
This will do that with minimal passes on the table. It will only return the top row if there are no rows returned by ItemIWant. There is no conditional logic so it can be compiled and indexed effectively.

Update table from another table in SQL is giving an unexpected result

Table Items :
ItemCode---Name-------Stock
111-----------Book---------10
112-----------Bag-----------03
113-----------Pen-----------08
114-----------Pencil--------13
Table INVOICEITEMS(SoldItems) :
BillNumber---ItemCode-----UnitValue(Qty)
1005-----------111------------------3
1005-----------111------------------2
1005-----------113------------------4
1006-----------111------------------6
1007-----------112------------------5
We need to update the ITEMS table from INVOICEITEMS table.
We need to add to the Stock of each ItemCode in ITEMS table from the sum of the UnitValues of BillNumber '1005' in INVOICEITEMS.
Query used :
UPDATE ITEMS SET Stock=Stock+(SELECT SUM(T.UnitValue) FROM INVOICEITEMS T WHERE T.BillNumber LIKE '1005' AND T.ItemCode LIKE ItemCode) WHERE ItemCode IN(SELECT J.ItemCode FROM INVOICEITEMS J WHERE J.BillNumber LIKE '1005')
Expected Result After updation :
Table ITEMS
ItemCode---Name----Stock
111-----------Book-------15
112-----------Bag---------03
113-----------Pen---------12
114-----------Pencil------13
Result we are getting:
Table ITEMS
ItemCode---Name----Stock
111-----------Book--------19
112-----------Bag---------03
113-----------Pen---------17
114-----------Pencil-------13
Please help in correcting this query.
One way to investigate problems with update statements is to convert them to select statements. For your update statement, I come up with the following query:
SELECT Items.*
,(SELECT SUM(T.UnitValue) FROM INVOICEITEMS T WHERE T.BillNumber LIKE '1005' AND T.ItemCode LIKE ItemCode)
FROM Items
WHERE ItemCode IN(SELECT J.ItemCode FROM INVOICEITEMS J WHERE J.BillNumber LIKE '1005')
As you can see, the last column is 9 for every row, which isn't surprising because you have not done anything to return different values based on which row you're looking at.
You can then change the select statement to return the data you do want:
SELECT Items.ItemCode
,Items.Stock
,SUM(T.UnitValue)
,Items.Stock + SUM(T.UnitValue)
FROM Items
JOIN INVOICEITEMS T ON T.ItemCode = Items.ItemCode
WHERE T.BillNumber = 1005
GROUP BY Items.ItemCode, Items.Stock
And finally you can incorporate that back into an UPDATE statement:
UPDATE Items SET Stock = NewSum
FROM Items JOIN
(SELECT Items.ItemCode
,Items.Stock
,Items.Stock + SUM(INVOICEITEMS.UnitValue) NewSum
FROM Items
JOIN INVOICEITEMS ON INVOICEITEMS.ItemCode = Items.ItemCode
WHERE INVOICEITEMS.BillNumber = 1005
GROUP BY Items.ItemCode, Items.Stock
) T ON T.ItemCode = Items.ItemCode
It might then be prudent to extract hard-coded values from the sub-query and make them part of the higher-level query. One reason this might be a good idea is because then the sub-query is more portable. Then you'd end up with this:
UPDATE Items SET Stock = NewSum
FROM Items JOIN
(SELECT Items.ItemCode
,INVOICEITEMS.BillNumber
,Items.Stock
,SUM(INVOICEITEMS.UnitValue) InvoiceQty
,Items.Stock + SUM(INVOICEITEMS.UnitValue) NewSum
FROM Items
JOIN INVOICEITEMS ON INVOICEITEMS.ItemCode = Items.ItemCode
GROUP BY Items.ItemCode, Items.Stock, INVOICEITEMS.BillNumber
) T ON T.ItemCode = Items.ItemCode
WHERE T.BillNumber = 1005
AND T.ItemCode LIKE ItemCode line in the SQL is not getting executed as ItemCode hasn't been specified in the SQL before the execution of the inner SQL statement.
Associate ItemCode explicitly with the ITEMS table and it would work.
Well, in this case you could use a Stored Procedure or a Function:
e.g. Define a Function
CREATE FUNCTION getItemSum(#BillID int, #ItemID int)
RETURNS int
AS
-- Returns the stock level for the product.
BEGIN
DECLARE #ret int;
SELECT #ret = SUM(UnitValue)
FROM INVOICEITEM p
WHERE p.ITEMCODE = #ItemID
AND p.BillNumber = #BillID;
IF (#ret IS NULL)
SET #ret = 0;
RETURN #ret;
END;
GO
And Call SQL Statement like this:
UPDATE ITEMS
SET Stock=Stock + getItemSum(1005, ItemCode)
GO
One way to do this is to use a join with a sub-query:
UPDATE items
SET Stock += item_qty_sum
FROM items i
INNER JOIN (
SELECT itemcode, SUM(unitvalue) AS item_qty_sum
FROM INVOICEITEMS
WHERE BillNumber = 1005
GROUP BY ItemCode
) ii
ON ii.ItemCode = i.ItemCode
Sample SQL Fiddle showing the query in action giving the desired results.

Multiple rows update in oracle sql using for loop

the below FOR loop doesn't work. I have two columns PID, PAYMENT in table t1 and table t2. I want to update PAYMENT in table t1 from table t2 where t1.PID=t2.PID
FOR X IN(select paymentterm,pid from temp_project)
LOOP
update project p
set p.paymentterm=temp_project.PID
where p.PID=X.PID;
END LOOP;
commit;
You can achieve this behavior without looping:
UPDATE project
SET paymentterm = (SELECT peymentterm
FROM temp_project
WHERE project.pid = temp_project.pid)
WHERE pid IN (SELECT pid FROM temp_project)
Try:
update project p
set paymentterm = (select t.paymentterm
from temp_project tp
where tp.pid = p.pid)
where pid in (select pid from temp_project)
... or, if temp_project.pid is constrained to be unique:
update (select p.pid,
p.paymentterm,
t.paymentterm new_paymentterm
from project p join temp_project t on p.pid = t.pid)
set paymentterm = new_paymentterm;
You might make sure that you're not making changes where none are required with:
update (select p.pid,
p.paymentterm,
t.paymentterm new_paymentterm
from project p join temp_project t on p.pid = t.pid
where coalesce(p.paymentterm,-1000) != coalesce(t.paymentterm,-1000))
set paymentterm = new_paymentterm;
(Guessing at -1000 being an impossible value for paymentterm there).
This could also be written as a MERGE statement.

Error using Update Join statement from a Table Variable

If I make a table variable here:
declare #Table Table (ProductID int, Color = varchar(60))
Then populate it of course, and try to use it in an Update with Join statement like below, I get errors.
UPDATE [Product]
SET [Product].[Active] = 1
,[Product].[Color] = t.[Color]
INNER JOIN #Table t
ON t.[ProductID] = [Product].[ProductID]
Error:
Msg 156, Level 15, State 1, Procedure
Incorrect syntax near the keyword 'INNER'.
Any suggestions how to do this?
You can also do this (works on SQL Server and all ANSI SQL conforming database):
UPDATE [Product] SET
[Product].[Active] = 1
,[Product].[Color] = t.[Color]
FROM #Table t
WHERE t.[ProductID] = [Product].[ProductID]
There's something to be desired from SQL Server's proprietary UPDATE FROM though, you can easily change the UPDATE statement to make it match whole table regardless if there's no matching rows.
It's easy to fashion this matched-rows-only update... http://www.sqlfiddle.com/#!3/8b5a3/26
update p SET
Qty = t.Qty
from Product p
inner join Latest t
on t.ProductId = p.ProductId;
...to an UPDATE that matches all rows, you merely change the INNER JOIN to LEFT JOIN: http://www.sqlfiddle.com/#!3/8b5a3/27
update p SET
Qty = ISNULL(t.Qty,0)
from Product p
left join Latest t
on t.ProductId = p.ProductId;
select * from Product;
Whereas if you want to fashion the ANSI SQL UPDATE with matched-rows-only ... http://www.sqlfiddle.com/#!3/8b5a3/28
update Product SET
Qty = t.Qty
from Latest t
where t.ProductId = Product.ProductId
...to UPDATE statement that matches all rows, you have to adjust your query a bit: http://www.sqlfiddle.com/#!3/8b5a3/29
update Product SET
Qty = ISNULL(t.Qty, 0)
from
(
select x.ProductId, lat.Qty
from Product x
left join Latest lat on lat.ProductId = x.ProductId
) as t
where t.ProductId = Product.ProductId;
Though as most choices in development, one should weigh the pros and cons in terms of code readability/maintainability against flexibility
Data sample:
create table Product
(
ProductId int primary key not null,
Name varchar(50) not null,
Qty int not null
);
insert into Product(Name,Qty) values
(1,'CAR',1),
(2,'Computer',1000),
(3,'Shoes',2);
create table Latest
(
ProductId int primary key not null,
Qty int not null
);
insert into Latest(ProductId, Qty) values
(2,2000),
(3,3);