How to assign a permanent unique ID with T-SQL - sql-server

Can someone let me know how to permanently assign a unique ID to a field?
I have the following table:
CREATE TABLE PrestigeCars.Reference.Staff
(
StaffName NVARCHAR(50) NULL,
ManagerID INT NULL,
Department NVARCHAR(50) NULL
) ON [PRIMARY]
GO
The following code assigns a new id field to to the table called 'myuniqueID'
SELECT
Staff.StaffName
,Staff.ManagerID
,Staff.Department
,NEWID() AS myuniqueID
FROM Reference.Staff
This will produce the following table:
The problem is I would like the unique IDs generated to become permanent.
Can someone let me know if that is possible?

CREATE TABLE PrestigeCars.Reference.Staff (
StaffName NVARCHAR(50) NULL
,ManagerID INT NULL
,Department NVARCHAR(50) NULL
, UniqueId NVARCHAR(255) NOT NULL default NEWID()
) ON [PRIMARY]
GO
Important is, that this only works for creating the table. If you want to alter the table, you firstly have to add the Column which has to allow null, then fill the values and at last set it to not null.
Edit:
To add a Column you need the alter table statement, as mentioned in many other posts before:
ALTER TABLE PrestigeCars.Reference.Staff
ADD UniqueId NVARCHAR(255) NULL default NEWID()
Next you have to set the UniqueId for the existing rows:
UPDATE PrestigeCars.Reference.Staff
SET UniqueId = NEWID()
WHERE UniqueId IS NULL
Last but not least you should set the column to not null:
ALTER TABLE PrestigeCars.Reference.Staff
ALTER COLUMN UniqueId NOT NULL
You could add an Unique-Index, if you want to, but this should not be necessary.

Related

What is the uniqueidentifier (default newid()) was inserted?

I have a table with a uniqueidentifier and NEWID() default for new records. Executed the insert script. How do I know what uniqueidentifier was generated for the Id column since the last insert?
Table Script
CREATE TABLE [dbo].[MyData](
[Id] [uniqueidentifier] NOT NULL,
[Data] [varbinary](max) NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
ALTER TABLE [dbo].[MyData] ADD CONSTRAINT [DF_MyData_Id] DEFAULT (newid()) FOR [Id]
GO
Insert Script
INSERT INTO dbo.MyData (Data)
VALUES (NULL)
GO
What is the uniqueidentifier was inserted?
Use an OUTPUT clause. I INSERT the data into a table variable so that is it can consumed by other statements afterwards:
DECLARE #IDs table (ID uniqueidentifier);
INSERT INTO dbo.MyData
OUTPUT inserted.Id
INTO #IDs
DEFAULT VALUES;
SELECT *
FROM #IDs;
There is no particular value in relying on the default in your example.
Just create a scalar variable of type uniqueidentifier and assign it the result of NEWID yourself
DECLARE #Id UNIQUEIDENTIFIER = NEWID();
INSERT INTO dbo.MyData (Id)
VALUES (#Id);
This is more concise than having to insert into a table variable and subsequently select from it.

How to automatically create rows and pass values to other tables

There are three tables in database:
"BusinessEntity " which has the identity column "BusinessEntityID" as Primary Key (as well as rowguid and ModifiedDate columns).
"Firm" which has similarly the identity column "BusinessEntityID" as Primary Key, which is also a Foreign Key to BusinessEntity.BusinessEntityID (it has a 1-to-1 relationship with "BusinessEntity" table, FirmName, rowguid and ModifiedDate columns ).
"Customer" which has the identity column "CustomerID" as Primary Key and column "FirmID" as Foreign Key to Firm .BusinessEntityID (plus CustomerName, rowguid and ModifiedDate columns).
i.e. (also see image)
tables: BusinessEntity Firm Customer
columns: CustomerID (PK)
BusinessEntityID(PK) --> BusinessEntityID (PK/FK) --> FirmID (FK)
What I'm trying to do is whenever a new Customer row is to be created:
A new BusinessEntity row to be created automatically and then pass its BusinessEntityID value to an (automatically) newly created Firm row which it turn would pass its own BusinessEntityID to Customer table as FirmID column.
As you can see a BusinessEntity row was no meaning unless it corresponds to a Firm (or other entities) and a Customer must include a Firm.
I created a view containing all three tables along with a trigger to do the job without success. Any suggestions?
The tables:
BusinessEntity
CREATE TABLE [dbo ].[BusinessEntity](
[BusinessEntityID] [int] IDENTITY(1,1) NOT NULL,
[rowguid] [uniqueidentifier] NOT NULL,
[ModifiedDate] [datetime] NOT NULL,
CONSTRAINT [PK_BusinessEntity_BusinessEntityID] PRIMARY KEY CLUSTERED
(
[BusinessEntityID] ASC
)
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[BusinessEntity] ADD CONSTRAINT [DF_BusinessEntity_rowguid]
DEFAULT (newid()) FOR [rowguid]
GO
ALTER TABLE [dbo ].[BusinessEntity] ADD CONSTRAINT [DF_BusinessEntity_ModifiedDate]
DEFAULT (getdate()) FOR [ModifiedDate]
GO
Firm
CREATE TABLE [dbo].[Firm](
[BusinessEntityID] [int] IDENTITY(1,1) NOT NULL,
[FirmName] [nvarchar](30) NULL,
[rowguid] [uniqueidentifier] NOT NULL,
[ModifiedDate] [datetime] NOT NULL,
CONSTRAINT [PK_Firm_BusinessEntityID] PRIMARY KEY CLUSTERED
(
[BusinessEntityID] ASC
)
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[Firm] ADD CONSTRAINT [DF_Firm_rowguid]
DEFAULT (newid()) FOR [rowguid]
GO
ALTER TABLE [dbo].[Firm] ADD CONSTRAINT [DF_Firm_ModifiedDate]
DEFAULT (getdate()) FOR [ModifiedDate]
GO
ALTER TABLE [dbo].[Firm] WITH CHECK ADD CONSTRAINT [FK_Firm_BusinessEntity_BusinessEntityID] FOREIGN KEY([BusinessEntityID])
REFERENCES [dbo].[BusinessEntity] ([BusinessEntityID])
GO
ALTER TABLE [dbo].[Firm] CHECK CONSTRAINT [FK_Firm_BusinessEntity_BusinessEntityID]
GO
Customer
CREATE TABLE [dbo].[Customer](
[CustomerID] [int] IDENTITY(1,1) NOT NULL,
[FirmID] [int] NULL,
[CustomerName] [nvarchar](28) NULL,
[rowguid] [uniqueidentifier] NOT NULL,
[ModifiedDate] [datetime] NOT NULL,
CONSTRAINT [PK_Customer_CustomerID] PRIMARY KEY CLUSTERED
(
[CustomerID] ASC
)
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[Customer] ADD CONSTRAINT [DF_Customer_rowguid]
DEFAULT (newid()) FOR [rowguid]
GO
ALTER TABLE [dbo].[Customer] ADD CONSTRAINT [DF_Customer_ModifiedDate]
DEFAULT (getdate()) FOR [ModifiedDate]
GO
ALTER TABLE [dbo].[Customer] WITH CHECK ADD CONSTRAINT [FK_Customer_Firm_FirmID] FOREIGN KEY([FirmID])
REFERENCES [dbo].[Firm] ([BusinessEntityID])
GO
ALTER TABLE [dbo].[Customer] CHECK CONSTRAINT [FK_Customer_Firm_FirmID]
GO
Something weird happens here. I created this stored procedure:
CREATE PROCEDURE [dbo].[CreateFirmCustomer](#FirmName NVARCHAR(30), #CustomerName NVARCHAR(28)) AS
BEGIN;
SET NOCOUNT ON;
BEGIN TRANSACTION;
INSERT BusinessEntity DEFAULT VALUES;
DECLARE #BusinessEntityID INT = SCOPE_IDENTITY();
SET IDENTITY_INSERT [dbo].[Firm] ON
INSERT Firm(BusinessEntityID, FirmName)
VALUES (#BusinessEntityID, #FirmName);
SET IDENTITY_INSERT [dbo].[Firm] OFF
INSERT Customer(FirmID, CustomerName)
VALUES (#BusinessEntityID, #CustomerName);
DECLARE #CustomerID INT = SCOPE_IDENTITY();
SELECT #BusinessEntityID AS FirmID, #CustomerID AS CustomerID;
COMMIT;
END;
GO
When I run it sometimes the CustomerID column gets the value of BusinessEntityID column when it should really be independently auto-generated. Also the BusinessEntityID column auto-generates weird values e.g. jumped from value 7 to value 1002. (BusinessEntityID is BusinessEntity.BusinessEntityID ) Any clues? (see picture)
Now I created this view to insert Customers as Firms:
CREATE VIEW [dbo].[vBusEntityFirmCustomer]
AS
SELECT dbo.Firm.FirmName, dbo.Customer.CustomerName
FROM dbo.BusinessEntity INNER JOIN
dbo.Firm ON dbo.BusinessEntity.BusinessEntityID = dbo.Firm.BusinessEntityID INNER JOIN
dbo.Customer ON dbo.Firm.BusinessEntityID = dbo.Customer.FirmID
GO
And this trigger on the view:
CREATE TRIGGER [dbo].[trg_FirmCustomer]
ON [dbo].[vBusEntityFirmCustomer]
INSTEAD OF INSERT
AS
exec [dbo].[CreateFirmCustomer]
GO
But every time I enter a new FirmName CustomerName to insert a new row I get this message (see image):
Procedure or function 'CreateFirmCustomer' expects parameter '#FirmName', which was not supplied.
The fact is that I do supply FirmName.
Logically, as designed, you have to create a BusinessEntity first, then a Firm, then a Customer. Across all these tables, the only real information you're storing is the firm name and the customer name -- all the rest is derived and autogenerated by the database. We can encapsulate the operation CreateCustomer in a stored procedure:
CREATE PROCEDURE CreateCustomer(#FirmName NVARCHAR(30), #CustomerName NVARCHAR(28)) AS
BEGIN;
SET NOCOUNT ON;
BEGIN TRANSACTION;
INSERT BusinessEntity DEFAULT VALUES;
DECLARE #BusinessEntityID INT = SCOPE_IDENTITY();
INSERT Firm(BusinessEntityID, FirmName)
VALUES (#BusinessEntityID, #FirmName);
INSERT Customer(FirmID, CustomerName)
VALUES (#BusinessEntityID, #CustomerName);
DECLARE #CustomerID INT = SCOPE_IDENTITY();
-- Return IDs of the newly created rows as the result set
SELECT #BusinessEntityID AS FirmID, #CustomerID AS CustomerID;
COMMIT;
END;
Invoke as (for example) EXEC CreateCustomer 'Firm', 'Customer'. With the table definitions as given, this will fail because Firm.BusinessEntityID is an IDENTITY -- if it is to take its value from BusinessEntity, it shouldn't be. (You can work around this with IDENTITY_INSERT, but in a properly designed database this shouldn't be necessary.)
Another thing that's obviously weird is that we insert no business data at all in BusinessEntity (which is why we need the DEFAULT VALUES syntax) -- it's nothing but a super-general container of IDs, so it's of dubious value. Nevertheless, this demonstrates the general technique of inserting rows in multiple tables that have dependencies.
As written, this stored procedure always creates a new Firm and BusinessEntity to go along with the Customer. Logically, a Firm can have more than one Customer, so you probably want another stored procedure to create a Customer for an existing Firm. This is simpler, as it's just an INSERT in Customer with the appropriate FirmID. You may wish to have a separate CreateFirm stored procedure that you call first, followed by a CreateCustomer to add a customer for that firm.
According to me,
it all depend how and when those 3 tables are populated.
Suppose those three table are populated using single UI, then
I will write them in single proc within one transaction.
Suppose those 3 table will be will populated at diff stage i.e diff UI then i write them in diff proc as you have already define constraint.
BTW what is the purpose of rowguid in all 3 tables.

Is UNIQUEIDENTIFIER an auto-generated number when inserting values in a table?

I have an error when loading a procedure telling me
Cannot insert the value NULL into column 'requestID', table 'MCAST.a01.tbl_enrollmentRequests'; column does not allow nulls. INSERT fails.
Now requestID is a UNIQUEIDENTIFIER type of variable. Is UNIQUEIDENTIFIER an auto generated number or not? Below is a sample of my code where you can see requestID.
CREATE PROCEDURE [a01].[usp_auditAcceptRequest]
(#AccountID UNIQUEIDENTIFIER,
#GroupID UNIQUEIDENTIFIER,
#Reason NVARCHAR(45)
)
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO [a01].[tbl_enrollmentRequests] (requestDate, groupID, accountID)
VALUES (SYSDATETIMEOFFSET(), #GroupID, #AccountID)
DECLARE #RequestID UNIQUEIDENTIFIER
SET #RequestID = (SELECT requestID
FROM [a01].tbl_enrollmentRequests
WHERE groupID = #GroupID AND accountID = #AccountID)
INSERT INTO [a01].[tbl_enrollmentAudits] (entryDate, requestID, groupID, accountID, accepted, reason)
VALUES (SYSDATETIMEOFFSET(), #RequestID, #GroupID, #AccountID, 1, #Reason)
DELETE FROM [a01].[tbl_enrollmentRequests]
WHERE requestID = #RequestID
END;
GO
Here is where I am implementing the above procedure
BEGIN
DECLARE #AccountID UNIQUEIDENTIFIER;
DECLARE #GroupID UNIQUEIDENTIFIER;
(SELECT #AccountID = accountID
FROM [a01].[tbl_userAccounts] WHERE accountUsername='saraht');
(SELECT #GroupID = groupID FROM [a01].[tbl_groups] WHERE groupName LIKE '%Foo%');
EXECUTE [a01].[usp_addRequest] #AccountID, #GroupID;
END;
GO
Thanks for your help !!
A uniqueidentifier is a normal column, and if you want to have a automatically assigned value you need to add a default to the column. Typically the functions used for the default are newid() or newsequentialid().
Edit based on the posted table definition; you could use this:
CREATE TABLE [a01].[tbl_enrollmentRequests](
requestID UNIQUEIDENTIFIER PRIMARY KEY DEFAULT (NEWID()),
requestDate DATETIMEOFFSET NOT NULL,
groupID UNIQUEIDENTIFIER REFERENCES [a01].[tbl_groups] (groupID) NOT NULL,
accountID UNIQUEIDENTIFIER REFERENCES [a01].[tbl_userAccounts] (accountID) NOT NULL
);
That being said, you can also pre-generate a uniqueidentifier and assign that to a variable in the stored procedure prior to insertion, since the generated GUID can be assumed not to collide with any existing GUID. The benefit of this is that you know the id of the inserted row even without retrieving it from an OUTPUT clause.
A notice on performance: a significant number of rows with a clustered primary key of random GUIDs (as generated bynewid()) are a performance issue, since the inserts will cause many page splits to occur due to the randomness. The newsequentialid() function pretty much completely resolves the performance problem, but it makes the generated GUIDs guessable, so that this can only be used when "random" IDs are not required.
Is UNIQUEIDENTIFIER an auto generated number or not?
What do you ask us? You have a look at the table definition and see whether a default that sets a new uniqueidentifier is defined or not.
If it is not - then no.
If you try to insert null, then also not (as your insert overrides the default value).
---Edit:
As per the table definition you posted:
requestID UNIQUEIDENTIFIER PRIMARY KEY
no default value defined that sets it. So no.

Using SCOPE_IDENTITY() in a constraint

I am aware that using IDENT_CURRENT will not always return me the correct identity value (especially true in multi-threaded applications). I wanted to use SCOPE_IDENTITY() instead.
For example this is my Employee table:
create table Employee
(
ID int identity(1,1),
Name varchar(20),
SystemID int,
constraint Employee_PK primary key (ID asc)
)
I have a statement below:
alter table Employee
add constraint Employee_D1 default ident_current('Employee') for SystemID
which I need to modify to use SCOPE_IDENTITY() instead.
I tried the below:
alter table Employee
add constraint Employee_D1 default SCOPE_IDENITITY() for SystemID
This did not give any errors. But upon inserting a row, I did not see this column getting updated with the identity value. What am I doing wrong?
Note that the SystemID must not be readonly, so computed field isn't an option.
My exercise here is to try eliminating entry of wrong IDENTITY value in SystemID in case parallel processes try to insert rows.
Just re-read your answer. SystemID isn't an identity column. I don't think you can use SCOPE_IDENITITY() as it hasn't added the row and retrieved the new Identity value at the point it would need the value to save.
What you will need to do is create a trigger After Insert of the row to update the SystemId value. CREATE TRIGGER
Here's how I'd approach this problem (apologies for the poor field names, I'm not feeling inventive)
CREATE TABLE employees (
id int identity(1,1) NOT NULL
, name varchar(20) NOT NULL
, actual_system_id int NULL
, system_id As Coalesce(actual_system_id, id)
, CONSTRAINT pk_employees PRIMARY KEY (id)
)
;
INSERT INTO employees (name)
VALUES ('John')
, ('Paul')
, ('George')
, ('Ringo')
;
SELECT id
, name
, system_id
FROM employees
;
UPDATE employees
SET actual_system_id = 937
WHERE name = 'George'
;
SELECT id
, name
, system_id
FROM employees
;

Is it possible to associate Unique constraint with a Check constraint?

I have a table access whose schema is as below:
create table access (
access_id int primary key identity,
access_name varchar(50) not null,
access_time datetime2 not null default (getdate()),
access_type varchar(20) check (access_type in ('OUTER_PARTY','INNER_PARTY')),
access_message varchar(100) not null,
)
Access types allowed are only OUTER_PARTY and INNER_PARTY.
What I am trying to achieve is that the INNER_PARTY entry should be only once per day per login (user), but the OUTER_PARTY can be recorded any number of times. So I was wondering if its possible to do it directly or if there is an idiom to create this kind of restriction.
I have checked this question: Combining the UNIQUE and CHECK constraints, but was not able to apply it to my situation as it was aiming for a different thing.
A filtered unique index can be added to the table. This index can be based on a computed column which removes the time component from the access_time column.
create table access (
access_id int primary key identity,
access_name varchar(50) not null,
access_time datetime2 not null default (SYSDATETIME()),
access_type varchar(20) check (access_type in ('OUTER_PARTY','INNER_PARTY')),
access_message varchar(100) not null,
access_date as CAST(access_time as date)
)
go
create unique index IX_access_singleinnerperday on access (access_date,access_name) where access_type='INNER_PARTY'
go
Seems to work:
--these inserts are fine
insert into access (access_name,access_type,access_message)
select 'abc','inner_party','hello' union all
select 'def','outer_party','world'
go
--as are these
insert into access (access_name,access_type,access_message)
select 'abc','outer_party','hello' union all
select 'def','outer_party','world'
go
--but this one fails
insert into access (access_name,access_type,access_message)
select 'abc','inner_party','hello' union all
select 'def','inner_party','world'
go
unfortunately you cant add a "if" on a check constraint. I advise using a trigger:
create trigger myTrigger
on access
instead of insert
as
begin
declare #access_name varchar(50)
declare #access_type varchar(20)
declare #access_time datetime2
select #access_name = access_name, #access_type= access_type, #access_time=access_time from inserted
if exists (select 1 from access where access_name=#access_name and access_type=#access_type and access_time=#access_time) begin
--raise excetion
end else begin
--insert
end
end
you will have to format the #access_time to consider only the date part

Resources