SQL trigger leaves last matching row untouched - sql

This is a trigger used to add the number of pages when a document's metadata row is added to a table.
USE [DD1234]
GO
/****** Object: Trigger [dbo].[AfterIns_Pages_ABC_LandCont] Script Date: 10/02/2014 16:30:33 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[AfterIns_Pages_ABC_LandCont]
ON [dbo].[PVDM_DOCS_1234_13]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
UPDATE D
SET D.DOCINDEX13 = O.Tot_Pages
FROM dbo.PVDM_DOCS_1234_13 D,
(SELECT DOCID, Sum(PAGES) AS Tot_Pages FROM dbo.PVDM_OBJS_1234_13
GROUP BY DOCID) O
WHERE D.DOCID = O.DOCID
AND D.DOCINDEX13 IS NULL
END
GO
So basically after a row (or many) are added to the PVDM_DOCS_1234_13 table, use the DOCID from that table to match the same DOCID in the Object (PVDM_OBJS_1234_13) table to retrieve the PAGES value, and then insert that into DOCINDEX13 (the field where we're storing the user visible page count) where DOCINDEX13 is null.
If a batch of 5, or 500 rows are inserted into PVDM_DOCS_1234_13, the last one inserted never gets the page count inserted, it remains NULL. All the rest get the page count inserted. Cannot figure out why the last row always gets left behind.
Note I'm an SQL novice, this was coded by someone no longer available.
Any ideas why this would work for all new rows except the last one inserted?
Thanks!

One possibility is that DOCINDEX13is notNULL` on the row being updated. Without sample data, it is had to see what is going wrong.
By the way, I'd be inclined to write this query as:
with toupdate as (
select d.*, sum(pages) over (order by docid) as tot_pages
from dbo.PVDM_DOCS_1234_13
)
update toupdate
set docindex13 = tot_pages
where docindex13 is null;
As a general rules in SQL, don't use commas in the from clause. Always use explicit join syntax.

Related

Edit inserted table sql

When insert I need edit a value if it is null. I create a trigger but I don't know how to edit inserted table.
ALTER TRIGGER [trigger1] on [dbo].[table]
instead of insert
as
declare #secuencia bigint, #ID_PERSONA VARCHAR;
select #secuencia = SECUENCIA from inserted
select #ID_PERSONA = ID_PERSONA from inserted
if #secuencia is null begin
set inserted.SECUENCIA = NEXT VALUE FOR SEQ_BIOINTEG --(Sequence)
end
i dont know how to edit inserted table.
You do not. That table is read only.
Note how your trigger also says:
instead of insert
There is no way to edit the inserted table.
What you do instead, is setting up an INSERT command for the original table, using the data from the inserted table to filter to the ROWS of inserted - mostly by a join.
Changing inserted makes no sense, logically - because triggers in SQL are one of two things:
INSTEAD OF - then there is no actual insert happening for inserted to start with. Instead of doing the insert, the trigger is called. As such, changing inserted - makes no sense.
AFTER - then the insert already happened (and you UPDATE the rows). As the trigger runs after the update, changing inserting makes no sense.
Note that I say ROWS - your trigger has one very basic error: it assumes inerted contains ONE row. It is a table - it is possible the changes come from an insert statement that inserts multiple rows (which is trivial, i.e. select into, or simply an insert with values for multiple rows). Handle those.
select #ID_PERSONA = ID_PERSONA from inserted
Makes NO sense - inserted is a table, so ID_PERSONA from inserted contains what value, if 2 rows are inserted? You must treat inserted like any other table.
Apart from all the varied issues with your trigger code, as mentioned by others, the easiest way to use a SEQUENCE value in a table is to just put it in a DEFAULT constraint:
ALTER TABLE dbo.[table]
ADD CONSTRAINT DF_table_seq
DEFAULT (NEXT VALUE FOR dbo.SEQ_BIOINTEG)
FOR SECUENCIA;

Trigger to update value with no of records

trying to set up a trigger but struggling to it to work the way i want, i want to update a field oppo_pono with the no of opportunity records created for a particular company record
so 1 company can have multiple opportunities and i want to record the no of master opportunities created for a company, so the first master opp created for a company would be set to 1 and so on
ive set the trigger up below but its setting the oppo_pono with the count from all companies rather then the one i am creating the opportunity for
my trigger below
USE [CRM]
GO
/****** Object: Trigger [dbo].[GeneratePNo] Script Date: 1/7/2021 3:55:27 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[GeneratePNo]
ON [dbo].[Opportunity]
FOR insert
AS
declare #OppPrimary Int
declare #company Int
declare #compid Int
declare #type nvarchar(40)
declare #childopp nchar(1)
declare #pono int
Select #OppPrimary = Oppo_OpportunityId,
#company = Oppo_PrimaryCompanyId,
#compid = comp_companyid,
#type = Oppo_Type,
#childopp = oppo_childoppo,
#pono = oppo_pono
FROM Inserted inner join company on Oppo_PrimaryCompanyId = #compid
Begin
UPDATE [Opportunity] SET oppo_pono = (select count(*) from vSearchListOpportunity where Oppo_Deleted is null and #type = 'Master' and Oppo_PrimaryCompanyId = ) +1
WHERE Oppo_OpportunityId =#OppPrimary
End
As mentioned in the comments, you have not taken into account the inserted pseudo-table having multiple rows. You also have a number of outright syntax errors.
EDIT: Following your comments I think I understand what you are trying to do. My original solution will not work here because indexed view cannot have ranking functions, but I have modified it to work with what you need.
Ideally, you wouldn't care about the actual IDs lining up, and just use an IDENTITY column, but often an ID series per group is needed.
Generally a view with correct indexing will be the more performant option, but it depends what you need.
Using a View
I will show you a solution that can be used for a lot of different types of aggregations which normally require triggers. This only works for your problem if you intend to have the numbering change if a row gets deleted out the middle of the grouping. If you want the numbering to remain then use a trigger instead
I am unsure the exact relation of Opportunity, Company and vSearchListOpportunity (seems to be a view on Opportunity) but you should be able to modify this to suit.
Create an indexed view on the data, and include a row number for each row:
CREATE VIEW vOpportunityNumbered
AS
SELECT
o.Oppo_OpportunityId,
o.Oppo_PrimaryCompanyId,
o.Oppo_Type,
o.oppo_childoppo,
ROW_NUMBER() OVER
(PARTITION BY o.Oppo_PrimaryCompanyId ORDER BY o.Oppo_OpportunityId)
-- Order by primary key to get deterministic ordering
FROM Opportunity AS o
WHERE o.Oppo_Deleted IS NULL;
GO
Now, to support this view, we cannot index it directly, as mentioned. We can, however, create an index on the base table that will support it:
CREATE UNIQUE CLUSTERED INDEX opp_CompanyOpportunity
ON Opportunity (Oppo_PrimaryCompanyId, Oppo_OpportunityId)
-- note the ordering of the columns
INCLUDE (Oppo_Type, oppo_childoppo)
WITH (OPTIMIZE_FOR_SEQUENTIAL_KEY = ON) -- ONLY FOR SQL2019
;
GO
This view will now give you a sequential row numbering of Opportunity for each distinct Company.
Triggers
If you wish for the IDs to always remain the same no matter what happens to intervening rows, you will need a trigger (i.e. a deleted row will leave a gap in the numbers).
Every trigger has two tables, inserted and deleted, which contain the data that was changed. For update triggers, both tables have data, a row in each for each changed row.
This means that the trigger is executed once per statement, and these tables contain all the relevant rows. You cannot, however, update them directly; you must join the real tables to them.
So let's take a look at how to write a trigger. Again, I'm somewhat guessing as to the relations of the tables:
CREATE OR ALTER TRIGGER [dbo].[GeneratePNo]
ON [dbo].[Opportunity]
AFTER INSERT -- FOR is an alternative syntax, AFTER is more usual
AS
-- No need for BEGIN and END, the whole batch until GO is the trigger
SET NOCOUNT ON; -- Prevent DONE_IN_PROC rowcount messages
IF (NOT EXISTS (SELECT 1 FROM inserted))
RETURN; -- Bail-out early if no rows
-- We do not declare variables because we cannot store multiple rows in variables
UPDATE
o
SET oppo_pono =
ISNULL( --If there are no other rows we would get a null
(SELECT MAX(allO.oppo_pono)
FROM Opportunity allO
-- no need for the following two filters as the oppo_pono needs to be unique anyway
-- where allO.Oppo_Deleted is null and allO.type = 'Master'
WHERE allO.Oppo_PrimaryCompanyId = inserted.Oppo_PrimaryCompanyId
), 0) + 1
FROM inserted i
JOIN Opportunity o ON o.Oppo_OpportunityId = i.Oppo_OpportunityId;
-- We join inserted table on primary key always
GO
There are more efficient ways to write that update, but it depends whether you are inserting a lot of rows. An INSTEAD OF trigger will also be more performant here, but I haven't attempted that as I don't have your table definition.

Automatically fill row with value based on inserted id

I have a table where the user is able to insert the ID of a Node that corresponds to a title elsewhere in the database. I want this tile to be automatically inserted into the row after the user has chosen the id.
This is my table:
I need to have the "SommerhusNavn" column be automatically filled with values based on the "SommerhusId" inserted.
I am using a third party to handle the CRUD functionality, where the user picks the ID from a dropdown. I already know in which table the title for the ID is located, I'm just not sure how to fill the row with the insert statement. Would I need to run a separate query for this to happen?
Edit:Solution
CREATE TRIGGER [dbo].[BlokeredePerioderInsert]
ON dbo.BlokeredePerioder
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
UPDATE BlokeredePerioder SET SommerhusNavn = text FROM umbracoNode AS umbNode
where SommerhusId = umbNode.id
END
GO
Yes, you need to run additional UPDATE query. Let's assume that you have the TitlesTable, with columns ID and Title. Then it should look like:
UPDATE MyTable SET SommerhusNavn = Title FROM TitlesTable AS A
WHERE SommerhusId = A.ID
AND SommerhusNavn IS NOT NULL --not necessary
Perhaps i'm not understanding, but why can't you use send the value across in the initial update?
Can you use a trigger on the database side?
Alternatively, you'll need to send a update across, following the insert.

SQL Server : make update trigger don't activate with no changing value

I want to track the update changes in a table via a trigger:
CREATE TABLE dbo.TrackTable(...columns same as target table)
GO
CREATE TRIGGER dboTrackTable
ON dbo.TargetTable
AFTER UPDATE
AS
INSERT INTO dbo.TrackTable (...columns)
SELECT (...columns)
FROM Inserted
However in real production some of the update queries select rows with vague conditions and update them all regardless of whether they are actually changed, like
UPDATE Targettable
SET customer_type = 'VIP'
WHERE 1 = 1
--or is_obsolete = 0 or register_date < '20160101' something
But due to table size and to analyze, I only want to choose those actually modified data for tracking. How to achieve this goal?
My track table has many columns (so I do not prefer checking inserted and deleted column one by one) but it seldom changes structure.
I guess the following code will be useful.
CREATE TABLE dbo.TrackTable(...columns same as target table)
GO
CREATE TRIGGER dboTrackTable
ON dbo.TargetTable
AFTER UPDATE
AS
INSERT INTO dbo.TrackTable (...columns)
SELECT *
FROM Inserted
EXCEPT
SELECT *
FROM Deleted
I realize this post is a couple months old now, but for anyone looking for a well-rounded answer:
To exit the trigger if no rows were affected on SQL Server 2016 and up, Microsoft recommends using the built-in ROWCOUNT_BIG() function in the Optimizing DML Triggers section of the Create Trigger documentation.
Usage:
IF ROWCOUNT_BIG() = 0
RETURN;
To ensure you are excluding rows that were not changed, you'll need to do a compare of the inserted and deleted tables inside the trigger. Taking your example code:
INSERT INTO dbo.TrackTable (...columns)
SELECT (...columns)
FROM Inserted i
INNER JOIN deleted d
ON d.[SomePrimaryKeyCol]=i.[SomePrimaryKeyCol] AND
i.customer_type<>d.customer_type
Microsoft documentation and w3schools are great resources for learning how to leverage various types of queries and trigger best practices.
Prevent trigger from doing anything if no rows changed.
Writing-triggers-the-right-way
CREATE TRIGGER the_trigger on dbo.Data
after update
as
begin
if ##ROWCOUNT = 0
return
set nocount on
/* Some Code Here */
end
Get a list of rows that changed:
CREATE TRIGGER the_trigger on dbo.data
AFTER UPDATE
AS
SELECT * from inserted
Previous stack overflow on triggers
#anna - as per #Oded's answer, when an update is performed, the rows are in the deleted table with the old information, and the inserted table with the new information –

Concat specific string to every inserted row

This is an hypothetical case..
I'm trying to find a good approach to make sure that every value inserted in an specific column col1 of my table mytable has a specific string http:// at the begining of the value.
Example:
I want to insert myprofile into mytable so (after my check condition..) the final value would be http://myprofile
I guess that a good approach could be using a trigger on insert but I didn't find anything concrete yet..
Any ideas?
Thank you.
You can try something like this as a starting point - this is for SQL Server (don't know MySQL well enough to provide that trigger code for you):
-- create the trigger, give it a meaningful name
CREATE TRIGGER PrependHttpPrefix
ON dbo.YourTableName -- it's on a specific table
AFTER INSERT, UPDATE -- it's for a specific operation, or several
AS
BEGIN
-- the newly inserted rows are stored in the "Inserted" pseudo table.
-- It has the exact same structure as your table that this trigger is
-- attached to.
-- SQL Server works in such a way that if the INSERT affected multiple
-- rows, the trigger is called *once* and "Inserted" contains those
-- multiple rows - you need to work with "Inserted" as a multi-row data set
--
-- You need to join the "Inserted" rows to your table (based on the
-- primary key for the table); for those rows newly inserted that
-- **do not** start with "http://" in "YourColumn", you need to set
-- that column value to the fixed text "http:/" plus whatever has been inserted
UPDATE tbl
SET YourColumn = 'http://' + i.YourColumn
FROM dbo.YourTableName tbl
INNER JOIN Inserted i ON tbl.PKColumn = i.PKColumn
WHERE LEFT(i.YourColumn, 7) <> 'http://'
END