How to pass multiple arguments to delete operation on trigger? - sql

estado numFactura
a 1
a 2
c 3
c 4
create trigger deleteFactura
on factura
INSTEAD OF DELETE
as
declare #estado varchar
set #estado= (select estado from deleted);
begin
if #estado='c'
THROW 51000, 'La factura esta cerrada, no se puede eliminar', 1
else
delete from factura
where numFactura=(select numFactura from deleted)
end
go
Goal: delete registers with estado ='c' and leave the ones with estado='a' be
I need it to work with more than one value.
If I try to delete two values, I get this error:
Msg 512, Level 16, State 1, Procedure deleteFactura, Line 6 [Batch Start Line 9]
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
How can I make it accept multiple values?

When writing triggers always keep in mind that it may be processing several records.
The records that are being inserted, deleted, or updated, are available in tables INSERTED and DELETED. i.e. They appear to be tables, but they only exist within your trigger.
create table dbo.Invoices ( Status varchar(1), InvoiceNum int )
go
create trigger DeleteInvoice on Invoices
instead of delete
as
delete from Invoices where InvoiceNum in ( select InvoiceNum from deleted ) and Status='a'
go
insert into Invoices ( Status, InvoiceNum ) values
( 'a', 1 ), ('a', 2 ), ('c', 3 ), ('c', 4 )
select * from Invoices
delete from Invoices
select * from Invoices

You want to JOIN with the deleted collection to select just the rows you want to delete, and add the appropriate WHERE clause... something like this:
CREATE TRIGGER DeleteInvoice ON Invoices
INSTEAD OF DELETE
AS
BEGIN
DELETE inv
FROM Invoices inv
INNER JOIN deleted d ON inv.InvoiceNum = d.InvoiceNum
WHERE inv.Status = 'a'
END

Related

Why merge-delete-trigger causes ORA-30926: unable to get a stable set of rows in the source tables?

Prepare the schema to reproduce the issue (on db<>fiddle):
create table t (id int, val int, modified timestamp default systimestamp)
/
create or replace trigger trigg_on_t
before update on t for each row enable
begin
:new.modified := systimestamp;
end;
/
insert into t (id, val) values (1, 1);
commit;
The error ocures in a merge stataement with delete clause and a trigger defined on the target table:
merge into t
using (
select 1 id, 10 val, 1 flag from dual
) s on (t.id = s.id)
when matched then
update set t.val=s.val
delete where s.flag=1
ORA-30926: unable to get a stable set of rows in the source tables
30926, 00000, "unable to get a stable set of rows in the source tables"
// *Cause: A stable set of rows could not be got because of large dml
// activity or a non-deterministic where clause.
// *Action: Remove any non-deterministic where clauses and reissue the dml.
What goes wrong here? Or rather, where is non-deterministic Where-clause?
Explicitly setting the timestamp value or even disabling the trigger will work:
merge into t
using (
select 1 id, 1 val, 1 flag from dual
) s on (t.id = s.id)
when matched then
update set t.val=s.val, modified=systimestamp
delete where s.flag=1
/
1 row merged.
rollback;
alter trigger trigg_on_t disable;
merge into t
using (
select 1 id, 1 val, 1 flag from dual
) s on (t.id = s.id)
when matched then
update set t.val=s.val
delete where s.flag=1
/
1 row merged.
It seems that the problem is when you are trying to update and delete the same record in the same time. It is a strange error message I admit. You asked where is non-deterministic Where-clause - there is no such clause and that is the problem. Lets say you have 3 records in your taable:
insert into t (id, val) values (1, 1);
insert into t (id, val) values (2, 2);
insert into t (id, val) values (3, 3);
commit;
--
-- ID VAL MODIFIED
-- 1 1 30-JUN-22 06.55.45.683595000
-- 2 2 30-JUN-22 06.55.45.690624000
-- 3 3 30-JUN-22 06.55.45.693622000
Now, your command is:
merge into t
using (
select 1 id, 10 val, 1 flag from dual
) s on (t.id = s.id)
when matched then
update set t.val=s.val
delete where s.flag=1
-- Result is SQL Error: ORA-30926
Update should be done on record with id=1 and delete should be done with s.flag=1 which means s.id=1 and therefore the record to delete is id=1. The same record.
Now lets see this:
merge into t
using (
select 1 id, 10 val, 1 flag from dual union all
select 2 id, 20 val, 2 flag from dual union all
select 3 id, 30 val, 3 flag from dual
) s on (t.id = s.id)
when matched then
update set t.val=s.val
delete where s.flag=1
-- Result is SQL Error: ORA-30926
... above is trying to do the same as in your question but on all three records
But if you change the where clause of delete statement and add the where clause to update statement to be deterministic there will be no error messages
merge into t
using (
select 1 id, 10 val, 1 flag from dual union all
select 2 id, 20 val, 2 flag from dual union all
select 3 id, 30 val, 3 flag from dual
) s on (t.id = s.id)
when matched then
update set t.val=s.val where s.flag = 1
delete where s.flag != 1
Finaly, lets go back to your command. There are some data from dual merged to the record with id=1. Update should be done over that record and delete command should have deterministic where clause that excludes deletion of the record that is being updated. That deterministic where clause shoud be like here:
merge into t
using (
select 1 id, 10 val, 1 flag from dual
) s on (t.id = s.id)
when matched then
update set t.val=s.val
delete where s.flag != 1
-- 1 rows merged
The problem disapears if you disable the before update trigger. If it is enabled then the trigger demands this determinism. And if you have an active after update trigger there is no problem because after update has been done there is no conflict and the record will be deleted.
CONCLUSION:
In this case when there is a table with BEFORE UPDATE trigger enabled when you try to update and delete the record simultaneously using merge there are two transactions that should be imposed. As the update transaction is interrupted by a db trigger changing the record data the second transaction (delete) gets the flag that the record that should be deleted is changed by another transaction and demands for determinism.
If before update trigger do something else (updating some other table or record) and not change the record itself then the command as it is does not raise the error.
You can try to change the trigger to update some value in any other table and run your command as it is ---> there will be no error.

T-SQL UPDATE statement affects less records than select statement

I am trying to update a DateTime column of one table with a date column from another table.
Before updating it, I am getting the records affected in order to see previously what records will be affected in the UPDATE. So I perform a SELECT statement with below WHERE clause:
NOTE:
DateTimeField is of type DateTime
DateField is of type Date
Code:
SELECT tblToUpdate.*
FROM MyTable1 tblToUpdate
INNER JOIN MyTable2 fromTbl on tblToUpdate.Id = fromTbl.Id
WHERE
ISNULL(fromTbl.DateField, GETDATE()) >= DATEFROMPARTS(1753, 1, 1)
AND ((fromTbl.DateField IS NOT NULL AND tblToUpdate.DateTimeField IS NULL)
OR
(fromTbl.DateField IS NULL AND tblToUpdate.DateTimeField IS NOT NULL)
OR
fromTbl.DateField <> CAST(tblToUpdate.DateTimeField AS DATE))
UPDATE tblToUpdate
SET tblToUpdate.DateTimeField = fromTbl.DateField
FROM MyTable1 tblToUpdate
INNER JOIN MyTable2 fromTbl ON tblToUpdate.Id = fromTbl.Id
WHERE
ISNULL(fromTbl.DateField, GETDATE()) >= DATEFROMPARTS(1753, 1, 1)
AND (
(fromTbl.DateField IS NOT NULL AND tblToUpdate.DateTimeField IS NULL)
OR
(fromTbl.DateField IS NULL AND tblToUpdate.DateTimeField IS NOT NULL)
OR
fromTbl.DateField <> CAST(tblToUpdate.DateTimeField AS DATE)
)
Note that I check DateField in Where clause to be in DateTime range before I update it.
The problem is that the number of records returned by the SELECT statement is not the same as the number of records affected returned by UPDATE statement.
The UPDATE statement affects fewer records than the SELECT statement returns.
Why is it happening if from and where clause are the same in both statements?
I think the number of records returned by SELECT and affected by UPDATE statements respectively should be the same.
This is easy to demonstrate. When there are multiple rows meeting the join predicates you will get differing row counts from a select and an update.
create table Header(HeadID int identity, Name varchar(50))
insert Header select 'test'
create table Details(DetailsID int identity, HeadID int, Name varchar(50))
insert Details values(1, 'asdf'),(1,'qwer')
select * --this returns 2 rows
from Header h
join Details d on d.HeadID = h.HeadID
update h --only 1 row affected
set Name = 'what?'
from Header h
join Details d on d.HeadID = h.HeadID

Update Table From Select

I am using ms-sql server. I have table which I want to update from select statement. For example the table which I want to update is Table_A with 2 rows in it. The update statement from which I want to update Table_A return 10 rows. So I want to update Table_A 10 times. The problem is that Table_A is updated 2 times(the count of rows in Table_A).
Example:
CREATE TABLE #tmp
(
AccountID INT,
Inflow DECIMAL(10,2)
)
DECLARE #n INT = 0
WHILE (#n <10 )
BEGIN
INSERT INTO #tmp SELECT 2, 10
SET #n += 1
END
UPDATE dbo.Table_A
SET Balance += sss.Inflow
FROM ( SELECT t.AccountID ,
t.Inflow
FROM #tmp AS t
) AS sss
WHERE dbo.tAccount.AccountID = sss.AccountID;
-- Updates only 2 times
-- What I expected here is Table_A to be updated as many times as the count of the select statement which is 10, based on the insert before.
Your expectation is wrong. Admittedly, the documentation buries this idea:
The example runs without error, but each SalesYTD value is updated
with only one sale, regardless of how many sales actually occurred on
that day. This is because a single UPDATE statement never updates the
same row two times.
The documentation continues with the solution:
In the situation in which more than one sale for a specified
salesperson can occur on the same day, all the sales for each sales
person must be aggregated together within the UPDATE statement, as
shown in the following example:
So, simply aggregate before doing the join:
UPDATE dbo.Table_A
SET Balance += sss.Inflow
FROM (SELECT t.AccountID, SUM(t.Inflow) as Inflow
FROM #tmp t
GROUP BY t.AccountId
) sss
WHERE dbo.tAccount.AccountID = sss.AccountID;
Note you can also write this as:
UPDATE a
SET Balance += sss.Inflow
FROM dbo.Table_A a JOIN
(SELECT t.AccountID, SUM(t.Inflow) as Inflow
FROM #tmp t
GROUP BY t.AccountId
) sss
ON a.AccountID = sss.AccountID;
This makes the JOIN more explicit.

Only expression can be specified when subquery... SQL server

So, I've been working on a loop to get data from each day going back 211 days for a report.
And I'm getting this error message. I'm using SQL Server 2008 R2, and although I don't know
why I get this error, I've tried some different things that haven't worked. So I'm asking here really thankful for answers.
Msg 116, Level 16, State 1, Line 21
Only one expression can be specified in the select list when the subquery is not introduced with EXISTS.
My code
create table #ExcelPrint (row int IDENTITY (1, 1) NOT NULL, Col01 varchar(100),
Col02 varchar(100), Col03 varchar(100), Col04 varchar(100),Col05 varchar(100))
declare #counter int
set #counter = 0
insert into #excelprint (Col01,Col02,Col03) values ('Text','Number', 'Amount')
while #counter > -211
begin
insert into #ExcelPrint (Col01,Col02)
select (CONVERT(varchar,Dateadd(DD,#counter,GETDATE()),112)),
(
select TableA.ColA ,sum(colB)
from db.TableA as A
inner join db.TableB B on B.Col1 = A.Col1
inner join db.TableC C on C.Col1 = B.Col1
where amount = CONVERT(varchar,Dateadd(DD,#counter,GETDATE()),112)
and A.Col1 = 123
and B = 12
group by ColA.A
)
set #counter = #counter -1
end
select isnull(Col01,''), Replace(ISNULL(col02,''),'.',','),
Replace(ISNULL(col03,''),'.',',')
,Replace(ISNULL(Col04,''),'.',',') from #ExcelPrint
order by row
drop table #ExcelPrint
i don't know if it's the real syntax of your query but i have some interrogation about it.
Why naming db.TableA as A and then in the select do : TableA.xxx do A.xxx
In your select you have a ColA and in your where clause a Col1 is it correct and supposed to be different?
Still in your where clause, you have a table B, but what is the B = 12?
If you wanna have a sum of everything by date, you shoud remove the third column A.ColA or write your query differently. At this moment as you are doing a group by Date, ColA it's splitted :)
in the where clause, amount is the date of the "transaction"?
Except these and they are most syntax than logic, it should work.
change
select (CONVERT(varchar,Dateadd(DD,#counter,GETDATE()),112)),
(
select TableA.ColA ,sum(colB)
from db.TableA as A
to
select CONVERT(varchar,Dateadd(DD,#counter,GETDATE()),112))
,TableA.ColA
,sum(colB)
from db.TableA as A ...
fix the number of column in the insert clause and group on the appropriate column...

CHECK Constraint on Group function?

I need an SQL constraint (using SQLDeveloper) to check that for a specific account_id, only ONE or NO regular_id exists, such as the data attached, cell containing '6' being what should not be allowed, even though it is a different value.
AccountID RegularID OpenID
1 5 null
1 null 10
1 null 11
1 6 <-- Forbidden
Best way is with a trigger
Create trigger trig_NoSingleRegId
On MyTable For Insert, Update
As
if Exists(Select * From MyTable t
Where AccountId In (Select AcountId From inserted)
Group By AccountId
Having Count(Distinct regularId) > 1)
Begin
RollBack Transaction
Raiserror('Cannot have more than one RegularId per AccountId', 16, 1)
End
Note: The Where clause is for performance only, to limit trigger to only those accountIds inserted or updated by the triggering update or insert.
or you can also can use join to accomplish same restriction.
Create trigger trig_NoSingleRegId
On MyTable For Insert, Update
As
if Exists(Select * From MyTable t
join inserted I
on i.AccountId = t.AccountId
Group By t.AccountId
Having Count(Distinct t.regularId) > 1)
Begin
RollBack Transaction
Raiserror('Cannot have more than one RegularId per AccountId', 16, 1)
End