SQL Server identity number is not consecutive - sql

I am creating a database table with an index number (CustRef) for each row.
The index starts from 1 and increases 1 with each new row. The query language as followed:
CREATE TABLE [dbo].[CustDetails]
(
[CustRef] [int] IDENTITY(1,1) NOT NULL,
[LName] [nchar](25) NOT NULL,
[FName] [nchar](25) NOT NULL,
[Address] [nchar](80) NULL,
[Suburb] [nchar](25) NULL,
[State] [nchar](5) NULL,
[PCode] [nchar](5) NULL,
CONSTRAINT [PK_CustDetails]
PRIMARY KEY CLUSTERED ([CustRef] ASC)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
The table is created successfully and I was testing it by inserting some sample data. At one stage, I deleted a row, which the index number (CustRef) was 6. After I deleted index 6 row and continue inserting sample data.
Unfortunately, the index number is not consecutive. In other word, I was expecting the new data content will use the index 6 as its row index number. However, new entries skip index 6, it starts from index 7.
As you can see from the above screenshot, between index 5 and 7, index 6 is missing.
How to resolve this issue? Thanks in advance.

1. Create the table
CREATE TABLE [dbo].[CustDetails]
(
[CustRef] [int] IDENTITY(1,1) NOT NULL,
[LName] [nchar](25) NOT NULL,
[FName] [nchar](25) NOT NULL,
[Address] [nchar](80) NULL,
[Suburb] [nchar](25) NULL,
[State] [nchar](5) NULL,
[PCode] [nchar](5) NULL,
)
GO
Insert records
deleted the last record. in my case it was 4th record.
Set identity insert on
SET IDENTITY_INSERT [dbo].[CustDetails] ON;
Inserted the record with identity column included.
insert into [dbo].[CustDetails] ([CustRef] , [LName], [FName] , [Address],
[Suburb], [State] , [PCode])values (4, 'test4',
'test','test','test','test','test')
this approach is good for testing. But normally, unless you truncate the table, your index will not reset in identity columns in SQL server as it is already assigned. So this is kind of a solution. using Identity insert you get the row count and by based on max(row count) you can set identity column value when inserting a record.

In your table CustDetails column CustRef is identity column. It auto increments at 1,2,3,4,5,6 now you deleted last row i.e. delete from CustDetails where CustRef=6. Technically next row will be inserted is 7 because SQL has already assigned value 6 to previous row. In other scenario if you had entered upto 1,2,3,4,5,6,7,8,9 and now deleting 6th row. what do you expect next value according to your requirement? 10 or 6?
I had implemented logic when client was asking to provide facility to delete last row only and no number to be skipped, I had write a logic of maxvalue + 1 for next row. you can write as remove identity and max(CustRef)+1 at time of insert. Alternatively you can use dbcc reseed that after each delete you write
declare #id int = 0
SELECT #id = MAX(CustRef) FROM CustDetails
DBCC CHECKIDENT ('dbo.CustDetails', reseed, #id)
this will tell sql to reset value to last max. which is not at all recommended way to do so. this is just an option but yeah you can choose remove identity and write logic for get max(CustRef) before insert statement and increment it and insert into table CustDetails.

Well, if it didn't work that way, then you would run into this:
insert custRef ...; // id = 1
insert custRef ...; // id = 2
insert custRef (fName, lName)
values ('Jane', 'Goodall');
// id = 3
Jane Goodall! She deserves a prize.
declare #giveAPrizeTo int = (
select id
from custRef
where fname = 'Jane'
and lname = 'Goodall'
);
We'll actually deliver it in a bit.
But first, another task. Customers that can't be contacted aren't really useful. The boss asked that we keep the table pure.
delete custRef where address is null;
alter table custRef alter column address nchar(80) not null;
Okay, well, moving on, add the next person.
insert custRef (fName, lName, address)
values ('Jeffrey', 'Dahmer', '1234 W Somewhere; Milwaukee, Wisconsin 12345');
In this hypothetical dialect where id's get recycled, Jeffrey Dahmer now has id = 3.
Hmm, that's interesting. I should be careful of the newest customer. Well, I got distracted, what was I doing? Oh yeah, that prize! I'd better deliver it.
declare #message nvarchar(max) = (
select 'Congratulations ' + fName + ' ' + lName ', ' +
'we support your good works. Have a prize!'
from custRef
where id = #giveAPrizeTo;
);
print (#message);
Congratulations Jeffrey Dahmer, we support your good works. Have a Prize!
Oops!

Related

Computed column at creation of table gets evaluated every time

I have the following script that creates a table:
CREATE TABLE [dbo].[Persons]
(
[Id] INT IDENTITY(1,1) NOT NULL,
[Name] NVARCHAR(250) NOT NULL,
[Surname] NVARCHAR(250) NOT NULL,
[NumberOfNotes] INT NOT NULL,
[TotalCash] FLOAT NOT NULL,
[Result] AS ([NumberOfNotes] * [TotalCash] * ROUND(RAND() * 2, 0)),
CONSTRAINT [PK_Persons] PRIMARY KEY ([Id] ASC)
);
The table gets created correctly and whenever I insert a new person the Result gets calculated. Problem is that it gets re-evaluated every time I do a select. I would like the computed value to stay the same for that record. How do I achieve this? Thanks in advance!
I simple trick is to seed rand():
[Result] AS ([NumberOfNotes] * [TotalCash] * ROUND(RAND(id) * 2, 0)),
Basically, this is using a "deterministic" random number generator. You can do that in other ways as well.
Alternatively, you could just assign it a value when you insert new rows into the table.

Data gets changed when copying data in chunks between two identical tables

In short, I am trying to copy data from one table to another nearly identical table (minus constraints, indices, and a precision change to a decimal column) in batches using Insert [NewTable] Select Top X * from [Table] but some data is getting changed during the copy. Read on for more details.
Why we are copying in the first place
We are altering the precision of a couple of columns in our largest table and do not have the time in our deployment window to do a simple alter statement. As an alternative, we decided to create a table with the new schema and copy the data in batches in the days leading up to the deploy to allow us to simple drop the old table and rename this table during the deployment window.
Creation scripts for new and old tables
These are not the exact tables we have in our DB, but they've been trimmed down for this question. The actual table has ~100 columns.
CREATE TABLE [dbo].[Table]
(
[Id] BIGINT NOT NULL PRIMARY KEY NONCLUSTERED IDENTITY,
[ForeignKey1] INT NOT NULL,
[ForeignKey2] INT NOT NULL,
[ForeignKey3] INT NOT NULL,
[Name] VARCHAR(MAX) NOT NULL,
[SomeValue] DECIMAL(14, 5) NULL,
CONSTRAINT [FK_Table_ForeignKeyTable1] FOREIGN KEY ([ForeignKey1]) REFERENCES [ForeignKeyTable1]([ForeignKey1]),
CONSTRAINT [FK_Table_ForeignKeyTable2] FOREIGN KEY ([ForeignKey2]) REFERENCES [ForeignKeyTable2]([ForeignKey2]),
CONSTRAINT [FK_Table_ForeignKeyTable3] FOREIGN KEY ([ForeignKey3]) REFERENCES [ForeignKeyTable3]([ForeignKey3]),
)
GO
CREATE INDEX [IX_Table_ForeignKey2] ON [dbo].[Table] ([ForeignKey2])
GO
CREATE TABLE [dbo].[NewTable]
(
[Id] BIGINT NOT NULL PRIMARY KEY NONCLUSTERED IDENTITY,
[ForeignKey1] INT NOT NULL,
[ForeignKey2] INT NOT NULL,
[ForeignKey3] INT NOT NULL,
[Name] VARCHAR(MAX) NOT NULL,
[SomeValue] DECIMAL(16, 5) NULL
)
SQL I wrote to copy data
DECLARE #BatchSize INT
DECLARE #Count INT
​
-- Leave these the same --
SET #Count = 1
​
-- Update these to modify run behavior --
SET #BatchSize = 5000
​
WHILE #Count > 0
BEGIN
SET IDENTITY_INSERT [dbo].[NewTable] ON;
INSERT INTO [dbo].[NewTable]
([Id],
[ForeignKey1],
[ForeignKey2],
[ForeignKey3],
[Name],
[SomeValue])
SELECT TOP (#BatchSize)
[Id],
[ForeignKey1],
[ForeignKey2],
[ForeignKey3],
[Name],
[SomeValue]
FROM [dbo].[Table]
WHERE not exists(SELECT 1 FROM [dbo].[NewTable] WHERE [dbo].[NewTable].Id = [dbo].[Table].Id)
ORDER BY Id
​
SET #Count = ##ROWCOUNT
​
SET IDENTITY_INSERT [dbo].[NewTable] OFF;
END
The Problem
Somehow data is getting garbled or modified in a seemingly random pattern during the copy. Most (maybe all) of the modified data we've seen has been for the ForeignKey2 column. And the value we end up with in the new table is seemingly random as well as it didn't exist at all in the old table. There doesn't seem to be any rhyme or reason to which records it affects either.
For example, here is one row for the original table and the corresponding row in the new table:
Old Table
ID: 204663
FK1: 452
FK2: 522413
FK3: 11190
Name: Masked
Some Value: 0.0
New Table
ID: 204663
FK1: 452
FK2: 120848
FK3: 11190
Name: Masked but matches Old Table
Some Value: 0.0
Environment
SQL was run in SSMS. Database is an Azure SQL Database.

To Find and Eliminate the Duplicates

I use this code to update one of my table by calling a function which generates a random ID each item. I started with around 1000 rows but now the size is growing and i find that there are duplicate ID's in the table. Is there any way to can modify the code i am using, so that it look for ID's that are already generated in the table so that it will generate a new code if there is a similar one. I also noticed
Your code shows you setting the field password, but the results show that UniqueID is the duplicated field. (Maybe it's password renamed?)
Assuming userId is unique: (if not, ADD an actual identity column NOW, "ALTER TABLE dbo.Users ADD ID INT NOT NULL IDENTITY(1, 1)" should do the trick) and assuming password is the field to change, use the following:
DECLARE #FN VARCHAR(20);
DECLARE #LN VARCHAR(20);
DECLARE #PW VARCHAR(20);
DECLARE #ID INT;
SELECT TOP 1
#FN = FirstName,
#LN = LastName,
#ID = userID
FROM dbo.Users
WHERE Password IS NULL;
WHILE ##ROWCOUNT = 1
BEGIN
SET #PW = dbo.GenerateID(FirstName, LastName);
WHILE EXIST (SELECT TOP 1 Password FROM dbo.Users WHERE Password = #PW)
SET #PW = dbo.GenerateID(FirstName, LastName);
UPDATE dbo.Users SET Password = #PW WHERE userId = #ID;
SELECT TOP 1
#FN = FirstName,
#LN = LastName,
#ID = userID
FROM dbo.Users
WHERE Password IS NULL;
END
This should look for a blank password. If none is found the outer loop is skipped. If one is found, we generate passwords until we find one not in the table. Next we look for another row with a blank password before the end of the outer loop.
Sounds like your new to this. Don't worry, TSQL is pretty easy to learn. First thing first, I suggest that you create a unique non-clustered index on the UniqueID column--this will prevent duplicates values from being inserted into your table. If someone does try to insert a duplicate value into the table it will throw an exception. Before you can use this though you'll need to remove all the duplicate 'UniqueID' values from your table.
CREATE UNIQUE NONCLUSTERED INDEX [IDX_UniqueID] ON [dbo].[Users]
(
[UniqueID] ASC
) ON [PRIMARY]
You can learn more about non-clustered indexes here: https://learn.microsoft.com/en-us/sql/relational-databases/indexes/clustered-and-nonclustered-indexes-described
I also suggest that you consider changing the underlying type of your UniqueID field to a 'uniqueidentifier.' Here's an example of a table schema that uses a 'uniqueidentifier' column type for the UniqueID column:
CREATE TABLE [dbo].[Users](
[personId] [int] IDENTITY(1,1) NOT NULL,
[firstName] [nvarchar](50) NOT NULL,
[lastName] [nvarchar](50) NOT NULL,
[UniqueID] [uniqueidentifier] NOT NULL,
CONSTRAINT [PK_Users] PRIMARY KEY CLUSTERED
(
[personId] ASC
) ON [PRIMARY]
) ON [PRIMARY]
A 'uniqueidentifier' column type in SQL Serever holds a Global Unique Identifier (aka a GUID or UUID). It's easy to generate a GUID in most languages. To generate a GUID in TSQL you just new to invoke the NEWID() function.
SELECT NEWID() -- output: D100FC00-B482-4580-A161-199BE264C1D1
You can learn more about GUIDs here: https://en.wikipedia.org/wiki/Universally_unique_identifier
Hope this helps. Best of luck on your project. :)

Insert only modified values and column names into a table

I have a sql server 2012 database. In which i have a changeLog table that contains
TableName, ColumnName, FromValue and ToValue columns. Which will be used to keep track of modified columns and data.
So if any update occur through application then only modified columns should insert into this table with its new and old value.
Can anyone help me in this.
For Example:
If the procedure updates all columns of property table (propertyName, address)
then if user update propertyName (but update also contains address column but with no data change) then only propertyName and its data will be inserted into ChangeLog table not address column and its data because address data does not contains any data change.
IF there is no other auditing requirement at all - you would not be thinking about Auditing in any way without this - then OK, go for it. However this is a very limited use of Auditing: User X changed this field at time Y. Generally this is interesting as part of a wider question: what did user X do? What happened to that customer data in the database to end up the way it is now?
Questions like that are harder to answer if you have the data structure you propose and would be quite onerous to reconstruct. My usual approach would be as follows. Starting from a base table like so (this from one of my current projects):
CREATE TABLE [de].[Generation](
[Id] [int] IDENTITY(1,1) NOT NULL,
[LocalTime] [datetime] NOT NULL,
[EntityId] [int] NOT NULL,
[Generation] [decimal](18, 4) NOT NULL,
[UpdatedAt] [datetime] NOT NULL CONSTRAINT [DF_Generation_UpdatedAt] DEFAULT (getdate()),
CONSTRAINT [PK_Generation] PRIMARY KEY CLUSTERED
(
[Id] ASC
)
(I've excluded FK definitions as they aren't relevant here.)
First create an Audit table for this table:
CREATE TABLE [de].[GenerationAudit](
[AuditId] int identity(1, 1) not null,
[Id] [int] NOT NULL,
[LocalTimeOld] [datetime] NULL,
[EntityIdOld] [int] NULL,
[GenerationOld] [decimal](18, 4) null,
[UpdatedAtOld] [datetime] null,
[LocalTimeNew] [datetime] null,
[EntityIdNew] [int] null,
[GenerationNew] [decimal](18, 4) null,
[UpdatedAtNew] [datetime] NOT NULL CONSTRAINT [DF_GenerationAudit_UpdatedAt] DEFAULT (getdate()),
[UpdatedBy] varchar(60) not null
CONSTRAINT [PK_GenerationAudit] PRIMARY KEY CLUSTERED
(
[AuditId] ASC
)
This table has an *Old and a *New version of each column that can't change. The Id, being an IDENTITY PK, can't change so no need for an old/new. I've also added an UpdatedBy column. It also has a new AuditId IDENTITY PK.
Next create three triggers on the base table: one for INSERT, one for UPDATE and one for DELETE. In the Insert trigger, insert a row into the Audit table with the New columns selected from the inserted table and the Old values as null. In the UPDATE one, the Oldvalues come from the deleted and the new from the inserted. In the DELETE trigger, old from from deleted and the new are all null.
The UPDATE trigger would look like this:
CREATE TRIGGER GenerationAuditUpdate
ON de.Generation
AFTER UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
insert into de.GenerationAudit (Id, LocalTimeOld, EntityIdOld, GenerationOld, UpdatedAtOld,
LocalTimeNew, EntityIdNew, GenerationNew, UpdatedAtNew,
UpdatedBy)
select isnull(i.Id, d.Id), d.LocalTime, d.EntityId, d.Generation, d.UpdatedAt,
i.LocalTime, i.EntityId, d.Generation, getdate(),
SYSTEM_USER)
from inserted i
full outer join deleted d on d.Id = i.Id;
END
GO
You then have a full before/after picture of each change (and it'll be faster than seperating out diffs column by column). You can create views over the Audit table to get entries where the Old value is different to the new, and include the base table Id (which you will also need in your structures!), the user who did it, and the time they did it (UpdatedAtNew).
That's my version of Auditing and it's mine!

sometimes Identity isn't working

I have a following table
CREATE TABLE [dbo].[test_table]
(
[ShoppingCartID] [int] IDENTITY(1,1) NOT NULL,
[CartTimeoutInMinutes] [int] NOT NULL,
[MaximumOrderLimitPerUser] [int] NOT NULL,
[MaximumOrderLimitPerSession] [int] NOT NULL,
CONSTRAINT [PK_test_table] PRIMARY KEY CLUSTERED
(
[ShoppingCartID] ASC
)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
)
ON [PRIMARY]
GO
Sometimes Identity isn't working, it's start with 0 and sometimes its start with 1.
Thank you in advance.
How are you putting the data in there? If you are using regular INSERT it should start at 1. You can, however, bulk-insert into the table, or otherwise use identity-insert; in which case all bets are off:
create table test (
id int not null identity(1,1),
name varchar(20) not null)
set identity_insert test on
insert test (id, name) values (0, 'abc')
insert test (id, name) values (27, 'def')
set identity_insert test off
select * from test
with output:
id name
----------- --------------------
0 abc
27 def
Or is the problem relating to ##IDENTITY (in which case: use SCOPE_IDENTITY() instead).
Possible
Are you using DBCC CHECKIDENT? This is invoked by some data compare tools (eg Red Gate) and has the following behaviour:
DBCC CHECKIDENT ( table_name, RESEED, new_reseed_value )
Current identity value is set to the new_reseed_value.
If no rows have been inserted into the table since the table was created, or if all rows have been removed by using the TRUNCATE TABLE statement, the first row inserted after you run DBCC CHECKIDENT uses new_reseed_value as the identity. Otherwise, the next row inserted uses new_reseed_value + the current increment value.
Or: are you using SET IDENTITY_INSERT?
These assume you are looking at the table, rather then using ##IDENTITY (as Mark suggested)