August 29, 2007 at 7:13 am
Hi,
I've this problem. I've a table to manage spedition with an identity column (id), a year field and a numberInYear field. The identity is the pk of the table and I want to make the numberInYear growing sequentially and restart every change year. I don't want to make a table to manage this sequence.
I thought this alghoritm:
1) Extract the max identity value (id) of the year before this, before the insert of the new record;
2) Insert the new record;
3) NumberInYear = the id just inserted minus the max id extract before.
The problem is the NumberInYear cannot be null, so I've to know the id of the record inserting before the inserting itself... Is it possible?
August 29, 2007 at 7:31 am
I'm sure there is a more efficient way and someone will probably post it, but to get started I would create and "After Insert" trigger. The trigger can first determine if this is the first record of the year and set the value to 1 if true, if false add 1 to the max value for current year and update the record. The initial insert can always put ZERO in the column which the trigger would use to know it is supposed to replace the ZERO with a valid number. This would also allow inserts that WANT to set the number to do so without being changed by the trigger. If you would like an example for the trigger post the DDL for the table and I (or someone) will post the actual trigger code.
James.
August 29, 2007 at 7:56 am
Ok, below is the script for the table...
The field of interest are:
ID_Spedizione
AA_Anno
Val_NumSpedizione
The last is the number to create...
Tnx
----------------------------------------
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[FK_GSP_Spedizione_GSP_Spedizione1]') and OBJECTPROPERTY(id, N'IsForeignKey') = 1)
ALTER TABLE [dbo].[GSP_Spedizione] DROP CONSTRAINT FK_GSP_Spedizione_GSP_Spedizione1
GO
/****** Object: Table [dbo].[GSP_Spedizione] Script Date: 29/08/2007 15.55.05 ******/
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[GSP_Spedizione]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table [dbo].[GSP_Spedizione]
GO
/****** Object: Table [dbo].[GSP_Spedizione] Script Date: 29/08/2007 15.55.07 ******/
CREATE TABLE [dbo].[GSP_Spedizione] (
[ID_Spedizione] [int] IDENTITY (1, 1) NOT NULL ,
[AA_Anno] [smallint] NOT NULL ,
[Val_NumSpedizione] [smallint] NOT NULL ,
[ID_Mittente] [smallint] NOT NULL ,
[Cod_UORichiedente] [varchar] (6) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL ,
[Des_ResponsabileRichiedente] [varchar] (100) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
[Dat_Inserimento] [datetime] NOT NULL ,
[ID_TipoSpedizione] [tinyint] NULL ,
[ID_TipoContenuto] [smallint] NULL ,
[Des_Contenuto] [varchar] (255) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
[Txt_Note] [text] COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
[ID_Status] [tinyint] NOT NULL ,
[Cod_MatricolaGestore] [char] (6) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
[Dat_PresaInCarico] [datetime] NULL ,
[Loc_MailRichiedente] [varchar] (100) COLLATE SQL_Latin1_General_CP1_CI_AS NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
August 29, 2007 at 8:27 am
Try this:
Create trigger tri_gsp_spedizione on gsp_spedizione for Insert as
begin
declare @LastVal int
if exists (select 1 from inserted where val_numspedizione = 0)
begin
select @LastVal = Coalesce(Max(g.val_numspedizione),0)
from gsp_spedizione g join inserted i on (g.aa_anno = i.aa_anno)
where g.id_spedizione <> i.id_spedizione
update gsp_spedizione
set gsp_spedizione.val_numspedizione = @LastVal + 1
from gsp_spedizione g join inserted i on (g.id_spedizione = i.id_spedizione)
end
end
go
NOTE: This will only work for single record inserts, if you will be doing multi-record inserts such as (insert into ... select from) then the trigger will need to be tweaked.
James.
August 29, 2007 at 8:27 am
Change the algorithm.
When inserting, check if there is any record for same year. if there is, just add +1 to maximum NumberInYear for that year.
If a record is not found, just put 1.
This is because you may insert records out-of chronological order.
N 56°04'39.16"
E 12°55'05.25"
August 29, 2007 at 8:31 am
DECLARE
@Sample TABLE (ID INT IDENTITY, yyyy INT, NumberInYear int)
select
* from @sample
declare
@year int
set
@year = 1996
insert
@sample (yyyy, numberinyear)
select
@year,
isnull((select max(numberinyear) from @sample where yyyy = @year), 1)
select
* from @sample
insert
@sample (yyyy, numberinyear)
select
@year,
isnull((select 1 + max(numberinyear) from @sample where yyyy = @year), 1)
select
* from @sample
set
@year = 1997
insert
@sample (yyyy, numberinyear)
select
@year,
isnull((select 1 + max(numberinyear) from @sample where yyyy = @year), 1)
select
* from @sample
set
@year = 1996
insert
@sample (yyyy, numberinyear)
select
@year,
isnull((select 1 + max(numberinyear) from @sample where yyyy = @year), 1)
select
* from @sample
N 56°04'39.16"
E 12°55'05.25"
August 29, 2007 at 8:44 am
I think the solution of James is almost the right one...
Some explanation:
"if exists (select 1 from inserted where val_numspedizione = 0)"
is to prevent the trigger executio if the insert was wrong?
"g.id_spedizione i.id_spedizione"
is necessary to "eliminate" the record just inserted, right?
Now, probably I'll use an INSERT INTO, so what tweak dou you mean?? I'm new with triggers..
@peter-2: I'm sure that the cronological oreder is respected
August 29, 2007 at 8:55 am
the "If Exists" check is to only perform the "increment update" if the incomming value is zero. This would allow a manual insert of a non-zero value such as 1000 in the val_numspedizione column if for some reason you suddenly wanted to jump that number up (the trigger would then increment from new MAX value for all other inserts that have a zero value in that field).
Yes the "g.id_spedizione <> i.id_spedizione" is the eliminate the "just inserted" record, though not really necessary because even if it considered it, it has a zero value and would not adversly affect the calculation.
The tweak is to make the the trigger handle a "multiple rows" of data in the "inserted" table. Give me a few minutes and I'll rework it.
James.
August 29, 2007 at 9:04 am
This may be what is wanted:
SET QUOTED_IDENTIFIER, ANSI_NULLS ON
GO
CREATE TRIGGER TR_I_GSP_Spedizione
ON dbo.GSP_Spedizione
AFTER INSERT
AS
DECLARE @I TABLE
(
ID_Spedizione int NOT NULL
,AA_Anno smallint NOT NULL
,RowID int IDENTITY NOT NULL
)
INSERT INTO @I (ID_Spedizione, AA_Anno)
SELECT ID_Spedizione, AA_Anno
FROM inserted
ORDER BY AA_Anno, ID_Spedizione
OPTION (MAXDOP 1)
UPDATE S
SET Val_NumSpedizione = I.RowID - D1.MinID + 1 + D2.MaxVal
FROM dbo.GSP_Spedizione S
JOIN @I I
ON S.ID_Spedizione = I.ID_Spedizione
JOIN (
SELECT S2.AA_Anno
,ISNULL(MAX(S2.Val_NumSpedizione), 0) AS MaxVal
FROM dbo.GSP_Spedizione S2 WITH (UPDLOCK)
GROUP BY S2.AA_Anno
) D2
ON S.AA_Anno = D2.AA_Anno
JOIN (
SELECT I1.AA_Anno
,MIN(I1.RowID) AS MinID
FROM @I I1
GROUP BY I1.AA_Anno
) D1
ON S.AA_Anno = D1.AA_Anno
GO
August 29, 2007 at 9:21 am
Nice! That works great. Now I don't have to think as hard
Since it is completely set based there is no need to check for the ZERO value in the incremented column, if it is not zero and larger then any existing value it will be used as the starting "max" value.
Only thing to note is that you can not have an alternate key assigned to the Year/Number columns since you need to initially assign duplicate values to the Number column when inserting. Just something for the OP to keep in mind.
James.
August 29, 2007 at 9:24 am
Oops! You do need to check for the non-zero value otherwise you end up over writting that value with a +1. add the following where clause to the insert into portion of the code:
INSERT INTO @I (ID_Spedizione, AA_Anno)
SELECT ID_Spedizione, AA_Anno
FROM inserted
WHERE [Val_NumSpedizione] = 0
ORDER BY AA_Anno, ID_Spedizione
OPTION (MAXDOP 1)
James.
August 29, 2007 at 9:29 am
Tnx to all!!
I'll never reach it without you... Probably only the single trigger, but the trigger for more than one insert is... awesome!! Tnx again.
Just last two questions:
1) I'll put the insert code in a SP with inside Transaction. If it goes in error the trigger is not execute, right? And if the trigger goes in error (for some reason...), the transaction is rollbacked??
August 29, 2007 at 9:35 am
Yes the trigger executes within the transaction of the original insert, so any error that prevents the "Commit" statement from running results in all work being rolledback.
You can easily test with this statement:
begin transaction
insert into gsp_spedizione (aa_anno,val_numspedizione)
select 2007,0 union all
select 2007,0 union all
select 2007,0 union all
select 2008,0 union all
select 2007,0 union all
select 2006,0 union all
select 2007,1000 union all
select 2006,0 union all
select 2008,0 union all
select 2007,0
go
select * from gsp_spedizione
go
rollback
select * from gsp_spedizione
The first select will show all the rows inserted and the second would not show them. NOTE: I abreviated the insert statements for testing (my table only had the three columns you were interested in).
James.
August 29, 2007 at 9:43 am
>>>And if the trigger goes in error (for some reason...), the transaction is rollbacked??
Maybe.
You need to be careful with transactions. If SET XACT_ABORT is off, as it normally is, then the statement is terminated but you have to check @@ERROR and do the ROLLBACK yourself. IF SET XACT_ABORT is on then the transaction will automatically be rolled back.
August 29, 2007 at 9:59 am
>>>>have to check @@ERROR
I know, i'm using always this.
Tnx to all.
Viewing 15 posts - 1 through 15 (of 18 total)
You must be logged in to reply to this topic. Login to reply