Long time reader, first time poster ;-)
I'm implementing a system based on an old system. The new system uses SQL Server 2008 and my problem comes when trying to insert new items in the main table. This will happen in two ways: It may be imported from the existing system (1) or may be created in the new system (2).
In case (1) the item already has an ID (int) which I would like to keep. In case (2) the ID will not be filled in and I'd like to generate an ID which is +1 of the maximum current value in the table. This should of course also work for inserts of mutiple rows.
As far as I can see, the solution will be to create a INSTEAD OF TRIGGER, but I can't quite figure out how this is done. Can anyone give me a hint or point me in the direction of how this can be done?
Chris
Following your request of using an INSTEAD OF trigger this SQL code can get you started.
CREATE TABLE dbo.SampleTable
(
ID INT,
SomeOtherValue VARCHAR(100) NULL
)
GO
CREATE TRIGGER dbo.TR_SampleTable_Insert
ON dbo.SampleTable
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON;
-- Inserting rows with IDs
INSERT INTO dbo.SampleTable (
ID,
SomeOtherValue)
SELECT
ID,
SomeOtherValue
FROM
Inserted
WHERE
ID IS NOT NULL
-- Now inserting rows without IDs
INSERT INTO dbo.SampleTable (
ID,
SomeOtherValue)
SELECT
(SELECT ISNULL(MAX(ID), 0) FROM dbo.SampleTable)
+ ROW_NUMBER() OVER(ORDER BY ID DESC),
SomeOtherValue
FROM
Inserted
WHERE
ID IS NULL
END
GO
INSERT INTO dbo.SampleTable
SELECT 1, 'First record with id'
UNION
SELECT NULL, 'First record without id'
UNION
SELECT 2, 'Second record with id'
UNION
SELECT NULL, 'Second record without id'
GO
SELECT * FROM dbo.SampleTable
GO
How about using a stored procedure to do your inserts, with the primary key as an optional parameter. In the stored procedure, you can set the primary key when it is not passed.
I would caution that if old and new records are being inserted mix and match, your scenario will probably fail, as new records will be getting old ID's before the old records are inserted. I recommend getting the max ID of the old table right now, and in the stored procedure setting the new primary key to be the Greater value of (old max + 1, current table max)
Others have shown you how to write such trigger.
Another, and often recommended, approach is to store both IDs in new database. Each record gets new ID in new system (by IDENTITY column or some other means). Additionally, if the record is imported from another system, it has associated OriginSystem and OriginID. This way you can keep old IDs for reference. This approach has additional benefit of being able to support new system to import data from, e.g. when merging or exchanging data with another system.
Related
I have an insert statement in a stored procedure who's primary key is a serial id. I want to be able to populate an additional field in the same table during the same insert statement with the serial id used for the primary key. Is this possible?
Unfortunately this is a solution already in place... I just have to implement it.
Regards
I can't imagine a reason why you would want a copy of the key in another column. But in order to do it, I think you'll need to follow your update with a statement to get the value of the identity key, and then an update to put that value in the other column. Since you're already in a stored procedure, it's probably ok to have a few extra statements, instead of doing it in the very same one.
DECLARE #ID INT;
INSERT INTO TABLE_THINGY (Name, Address) VALUES ('Joe Blow', '123 Main St');
SET #ID = SCOPE_IDENTITY();
UPDATE TABLE_THINGY SET IdCopy = #Id WHERE ID = #ID
If it's important that this be done every single time, you might want to create a Trigger to do it; beware, however, that many people hate triggers because of the obfuscation and difficulty in debugging, among other reasons.
http://blog.sqlauthority.com/2007/03/25/sql-server-identity-vs-scope_identity-vs-ident_current-retrieve-last-inserted-identity-of-record/
I agree, it is odd that you would replicate the key within the same table but with that said you could use a trigger, thus making it have no impact to current insert statements.
The below trigger is "After Insert" so technically it happens milliseconds after the insert if you truly wanted it to happen at the same time you would use a FOR INSERT instead and just replicate the logic used to create the serial id field into the new field.
CREATE TRIGGER triggerName ON dbo.tableName
AFTER INSERT
AS
BEGIN
update dbo.tableName set newField = inserted.SerialId where serialId = inserted.SerialId
END
GO
You could have a computed column that just returns the id column.
CREATE TABLE dbo.Products
(
ProductID int IDENTITY (1,1) NOT NULL
, OtherProductID AS ProductID
);
Having said that, data should only live in one place and to duplicate it in the same table is just a wrong design.
No, you cannot use the same insert statement for identity Id and copy that auto generated Id to the same row.
Multi-Statement using OUTPUT inserted or Trigger is your best bet.
I would like to have a stored procedure which inserts rows into a table (retrieved from a select query from another table) and for each newly inserted row gets its identity and updates the original table with the identity
Pseudo code-
records = select id,city,state,country from USER where name=#name
for each record in records // for each rows selected
insert into LOCATION(city,state,country) values(#record.city,#record.state,#record.country); //inserts a value into LOCATION table
#id = SCOPE_IDENTITY(); // gets the identity of the newly inserted row
update USER set LocationId=#id where Id=#record.id //updates the new id back to old table's column
end
This is a data migration task, where we want to segregate the LOCATION from USER table
Thanks in advance for your time and effort for this thread.
You could do something like this:
DECLARE #InsertedValues TABLE (ID INT, City VARCHAR(50), State VARCHAR(50), Country VARCHAR(50))
INSERT INTO dbo.Location(City, State, Country)
OUTPUT Inserted.ID, Inserted.City, Inserted.State, Inserted.Country INTO #InsertedValues(ID, City, State, Country)
SELECT City, State, Country
FROM dbo.YourSourceTable
With this, you now have the inserted values - including the newly defined identity values - in your #InsertedValues table variable and you can now update the source table as you see fit.
UPDATE dbo.YourSourceTable
SET
Col1 = iv.Col1,
Col2 = iv.Col2, -- etc. - do whatever you nee to do here!
FROM #InsertedValues iv
WHERE ......... -- here, you need some condition to link the inserted values to the original table
This doesn't require any cursor or any other messy RBAR (row-by-agonizing-row) processing at all - everything is nicely set-based and as fast as it can possibly be.
Learn more about the OUTPUT clause at MSDN SQL Server Books Online - you can use the OUTPUT clause on insert, update and even delete statements, too!
I have a table of orders with orderID. I want when I create a new row in orders, and automatically have it add the same orderID to a new row in orderDetails. I got the auto incrementing to work, however whenever I try to link the two, adding cascade delete, it gives me an error.
'order' table saved successfully
'orderDetail' table
- Unable to create relationship 'FK_orderDetail_order'.
Cascading foreign key 'FK_orderDetail_order' cannot be created where the referencing column 'orderDetail.orderID' is an identity column.
Could not create constraint. See previous errors.
Which seems to be because of the fact there is no orderID at row creation. Without these two linked it's pretty hard to link an order to its information.
I am using Microsoft SQL server mgt studio. I learned via command-line MySQL, not SQL, so this whole GUI stuff is throwing me off (and I'm a tad rusty).
Your problem is that 'orderDetail.orderID' should not be an identity column (auto-incrementing). It should be based on the orderId in the Order table. You can do that in a variety of ways. If you are using stored procedures, and making separate calls to the database for the orderDetail records, have the code save the order row first, and return the newly created OrderId value, then use that value on the calls to save orderdetails. If you are making one call to a stored proc that saves the order header record and all order detail records in one call, then in the stored procd, insert the ordfer record forst, use Scope_identity() to extract the newly created orderId into a T-SQL variable,
Declare #orderId Integer
Insert Orders([Order table columns])
Values([Order table column values])
Set #orderId = scope_Identity()
and then use the value in #orderId for all inserts into the OrderDetails table...
Insert OrderDetails(OrderId, [Other OrderDetail table columns])
Values(#orderId , [Other OrderDetail table column values])
You want a AFTER INSERT trigger on the order table - in this, the newly given ID is available as NEW.orderID and can now easily be inserted into orderDetails.
Just do this via the command line. I certainly do.
I have an SQL Server table where I store invoices lets call it invoice. I need to implement a continuous autonumber feauture for invoices (not the autoidentity which is not continuous). Moreover I should take care of concurrency issues for example user A and user B invoice the same time but the two invoices should not have the same number (obviously).
What would be an ideal implementation method in T-SQL?
One way we did something similar was to create a table called useID with only one column called [ID]. We use an Integer data type for it. This table also only has one row. More on that in a bit.
Now, each time we need to log an event we SELECT from useID and run our transaction with this [ID] value being used for tracking purposes. Right after we have SELECTed the [ID] we increment the value in useID by 1 (or whatever we need for the system in question). In this way we maintain unique and contiguous [ID] values. We can delete from the destination of the [ID] value without affecting the order of the new [ID] values. Performance on this is very good as we run ~10million transactions a night using this and we do reset the starting value every 3 months or so since we do not keep items 'live' that long.
An IDENTITY column.
If you need an invoice number that is alphanumeric I suggest you update your question with the required format.
There will only be gaps if you delete records, experience an error during an INSERT, rollback a transaction that contain an INSERT(s) into the table, or the seed is updated by a relevant dbcc command.
If you really have to reuse gaps (and I certainly wouldn't do so for things like invoices, for instance, in your example invoice #32 would have a later date then invoice #190 ...): then you could, in a serializable transaction, find lowest free value, set identity insert on, insert a row with that Id value, and then set identity insert off and commit the transaction.
Something like this (untested):
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRAN
SET IDENTITY_INSERT dbo.myTable ON
DECLARE #minId int = -1
;WITH cterows(Id, rownum)
AS
(
SELECT Id, row_number() OVER(ORDER BY Id ASC) AS rownum
)
SELECT #minId = MIN(rownum) FROM cterows
WHERE Id <> rownum
IF (#minId IS NOT NULL AND #minId <> -1)
BEGIN
-- found a gap
-- Insert at #minId
END
ELSE
BEGIN
-- No gap, INSERT as normal
END
SET IDENTITY_INSERT dbo.myTable OFF;
COMMIT
If it isn't necessary these numbers to be continues, you can create random number,but if it should be continues you can make that column IDENTITY colunm
check this post to create your random number
You can make a SQL Varchar column for alphanumeric invoice number, Here you need to generate unique invoice no or can use IDENTITY column.
given this table definition
create table herb.app (appId int identity primary key
, application varchar(15) unique
, customerName varchar(35),LoanProtectionInsurance bit
, State varchar(3),Address varchar(50),LoanAmt money
,addedBy varchar(7) not null,AddedDt smalldatetime default getdate())
I believe changes will be minimal, usually only a single field, and very sparse.
So I created this table:
create table herb.appAudit(appAuditId int primary key
, field varchar(20), oldValue varchar(50),ChangedBy varchar(7) not null,AddedDt smalldatetime default getdate())
How in a trigger can I get the column name of the value of what was changed to store it? I know how to get the value by joining the deleted table.
Use the inserted and deleted tables. Nigel Rivett wrote a great generic audit trail trigger using these tables. It is fairly complex SQL code, but it highlights some pretty cool ways of pulling together the information and once you understand them you can create a custom solution using his ideas as inspiration, or you could just use his script.
Here are the important ideas about the tables:
On an insert, inserted holds the inserted values and deleted is empty.
On an update, inserted holds the new values and deleted holds the old values.
On a delete, deleted holds the deleted values and inserted is empty.
The structure of the inserted and deleted tables (if not empty) are identical to the target table.
You can determine the column names from system tables and iterate on them as illustrated in Nigel's code.
if exists (select * from inserted)
if exists (select * from deleted)
-- this is an update
...
else
-- this is an insert
...
else
-- this is a delete
...
-- For updates to a specific field
SELECT d.[MyField] AS OldValue, i.[MyField] AS NewValue, system_user AS User
FROM inserted i
INNER JOIN deleted d ON i.[MyPrimaryKeyField] = d.[MyPrimaryKeyField]
-- For your table
SELECT d.CustomerName AS OldValue, i.CustomerName AS NewValue, system_user AS User
FROM inserted i
INNER JOIN deleted d ON i.appId = d.appId
If you really need this kind of auditing in a way that's critical to your business look at SQL Server 2008's Change Data Capture feature. That feature alone could justify the cost of an upgrade.
something like this for each field you want to track
if UPDATE(Track_ID)
begin
insert into [log].DataChanges
(
dcColumnName,
dcID,
dcDataBefore,
dcDataAfter,
dcDateChanged,
dcUser,
dcTableName
)
select
'Track_ID',
d.Data_ID,
coalesce(d.Track_ID,-666),
coalesce(i.Track_ID,-666),
getdate(),
#user,
#table
from inserted i
join deleted d on i.Data_ID=d.Data_ID
and coalesce(d.Track_ID,-666)<>coalesce(i.Track_ID,-666)
end
'Track_ID' is the name of the field, and d.Data_ID is the primary key of the table your tracking. #user is the user making the changes, and #table would be the table your keeping track of changes in case you're tracking more than one table in the same log table
Here's my quick and dirty audit table solution. (from http://freachable.net/2010/09/29/QuickAndDirtySQLAuditTable.aspx)
CREATE TABLE audit(
[on] datetime not null default getutcdate(),
[by] varchar(255) not null default system_user+','+AppName(),
was xml null,
[is] xml null
)
CREATE TRIGGER mytable_audit ON mytable for insert, update, delete as
INSERT audit(was,[is]) values(
(select * from deleted as [mytable] for xml auto,type),
(select * from inserted as [mytable] for xml auto,type)
)