Why Clustered Index Scan in this query? - sql-server

This query (SQL2012) execution plan shows me that Clustered Index Scan used in the internal sub-query on PK index:
SELECT n3.id as node_id,x.id as id,
(select xv.value from xv
--with(forceseek)
where xv.id=x.id) as [value]
FROM x
INNER JOIN n3
ON x.[obj_id]=n3.id
AND n3.parent_id = '975422E0-5630-4545-8CF7-062D7DF72B6B'
The tables x and xv are master->details tables.
When I use hint forceseek then it shows Clustered Index Seek and query executes fast.
Why there is Scan instead of Seek?
How to change the query to have Index Seek without the hint FORCESEEK?
UPD:
The full demo script:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
/*
DROP TABLE [dbo].[xv]
DROP TABLE [dbo].[x]
DROP TABLE [dbo].[n3]
*/
CREATE TABLE [dbo].[n3](
[id] [uniqueidentifier] NOT NULL,
[parent_id] [uniqueidentifier] NOT NULL,
CONSTRAINT [PK_n3] PRIMARY KEY CLUSTERED
(
[id] ASC
)
)
GO
CREATE TABLE [dbo].[x](
[obj_id] [uniqueidentifier] NOT NULL,
[id] [int] IDENTITY(1,1) NOT NULL,
CONSTRAINT [PK_x] PRIMARY KEY CLUSTERED
(
[id] ASC
))
GO
ALTER TABLE [dbo].[x] WITH CHECK ADD CONSTRAINT [FK_x_n3] FOREIGN KEY([obj_id])
REFERENCES [dbo].[n3] ([id])
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[x] CHECK CONSTRAINT [FK_x_n3]
GO
CREATE TABLE [dbo].[xv](
[id] [int] NOT NULL,
[value] [sql_variant] NOT NULL,
CONSTRAINT [PK_xv] PRIMARY KEY CLUSTERED
(
[id] ASC
))
GO
ALTER TABLE [dbo].[xv] WITH CHECK ADD CONSTRAINT [FK_xv_x] FOREIGN KEY([id])
REFERENCES [dbo].[x] ([id])
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[xv] CHECK CONSTRAINT [FK_xv_x]
GO
INSERT INTO n3(id,parent_id)
select newid(), '975422E0-5630-4545-8CF7-062D7DF72B6B'
GO 10
INSERT INTO n3(id,parent_id)
select newid(), '805422E0-5630-4545-8CF7-062D7DF72B6B'
GO 5
INSERT INTO x([obj_id])
select id from n3 where parent_id='975422E0-5630-4545-8CF7-062D7DF72B6B';
insert into xv (id, value)
select id, cast(RAND(1) as sql_variant) from x
--select * from x
--select * from n3
SELECT n3.id as node_id,x.id as id,
(select xv.value from dbo.xv
--with(forceseek)
where xv.id=x.id
) as [value]
FROM dbo.x
INNER JOIN dbo.n3
ON x.[obj_id]=n3.id
AND n3.parent_id = '975422E0-5630-4545-8CF7-062D7DF72B6B'
/*
DROP TABLE [dbo].[xv]
DROP TABLE [dbo].[x]
DROP TABLE [dbo].[n3]
*/
--Update statistics xv with fullscan

I suspect the statistics of xv table might be out of date. Update the statistics of xv and try running the query again.
Update statistics xv with fullscan
Update :
After looking at the data setup and query, For the given parent_id input it is very clear that all the records in both x and xv match so it is obvious that optimizer chooses index scan instead of seek because it has fetch all the records from both x and xv table
Also the number of records is less so optimizer will prefer scan instead of seek

Related

Make a "normal" table as temporal table

I have a table created like this:
CREATE TABLE address_user
(
[username] VARCHAR(13) NOT NULL,
[address] CHAR(58) NOT NULL,
[id] BIGINT NOT NULL,
CONSTRAINT [PK_ address_user]
PRIMARY KEY CLUSTERED ([id] ASC)
);
Now I want to be able to keep the history modification of this table, so I want to make it as temporal table. I know the script to create a temporal table, the final result should be:
CREATE TABLE address_user
(
[username] VARCHAR(13) NOT NULL,
[address] CHAR(58) NOT NULL,
[id] BIGINT NOT NULL,
[sys_start_time] DATETIME2(7)
GENERATED ALWAYS AS ROW START HIDDEN NOT NULL,
[sys_end_time] DATETIME2 (7)
GENERATED ALWAYS AS ROW END HIDDEN NOT NULL,
PERIOD FOR SYSTEM_TIME ([sys_start_time], [sys_end_time]),
CONSTRAINT [PK_ address_user]
PRIMARY KEY CLUSTERED ([id] ASC)
)
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE=[dbo].[address_user_history], DATA_CONSISTENCY_CHECK=ON));
The easy way to do that is just delete the previous table, and recreate the table with the good schema.
However, I have a lot of information in my table, save the data and delete the table, recreate it and re-insert the data make me uncomfortable.
So if you have a solution to transform the first table in temporal table without the need to delete everything and recreate it, it should be a great help!
Create the new table address_user_new, insert the data, then use sp_rename to rename address_user to address_user_old and address_user_new to address_user. This can all be done in a transaction to ensure ensure that the transition is atomic and apparently-instantaneous. eg
if object_id('address_user') is not null
ALTER TABLE address_user SET ( SYSTEM_VERSIONING = OFF)
go
if object_id('address_user_new') is not null
ALTER TABLE address_user_new SET ( SYSTEM_VERSIONING = OFF)
go
drop table if exists address_user
drop table if exists address_user_history
drop table if exists address_user_new
drop table if exists address_user_old
go
CREATE TABLE address_user
(
[username] VARCHAR(13) NOT NULL,
[address] CHAR(58) NOT NULL,
[id] BIGINT NOT NULL,
CONSTRAINT [PK_address_user]
PRIMARY KEY CLUSTERED ([id] ASC)
);
go
CREATE TABLE address_user_new
(
[username] VARCHAR(13) NOT NULL,
[address] CHAR(58) NOT NULL,
[id] BIGINT NOT NULL,
[sys_start_time] DATETIME2(7)
GENERATED ALWAYS AS ROW START HIDDEN NOT NULL,
[sys_end_time] DATETIME2 (7)
GENERATED ALWAYS AS ROW END HIDDEN NOT NULL,
PERIOD FOR SYSTEM_TIME ([sys_start_time], [sys_end_time]),
CONSTRAINT [PK_address_user_new]
PRIMARY KEY CLUSTERED ([id] ASC)
)
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE=[dbo].[address_user_history], DATA_CONSISTENCY_CHECK=ON));
go
set xact_abort on
begin transaction
insert into address_user_new(username,address,id)
select username,address,id
from address_user with (tablockx)
exec sp_rename 'address_user', 'address_user_old', 'OBJECT'
exec sp_rename 'PK_address_user', 'PK_address_user_old', 'OBJECT'
exec sp_rename 'address_user_new', 'address_user', 'OBJECT'
exec sp_rename 'PK_address_user_new', 'PK_address_user', 'OBJECT'
commit transaction

SQL Server temporary tables with a key in different sessions

In a stored procedure, I use a temporary table with a primary key.
CREATE TABLE #tmpTable
(
[RowId] [bigint] IDENTITY(1,1) NOT NULL,
[Id] [numeric](10, 0) NOT NULL
)
ALTER TABLE #tmpTable
ADD CONSTRAINT PK_NamePK PRIMARY KEY CLUSTERED (RowId);
The procedure works, but if I run the same procedure in another session I get an error
'PK_NamePK already exists'
How to use keys or indexes on temporary tables so that they are visible only in their scope?
CREATE TABLE #tmpTable
(
[RowId] [bigint] IDENTITY(1,1) NOT NULL PRIMARY KEY,
[Id] [numeric](10, 0) NOT NULL
)
There is no need to add alter table, you can define by table creation. Hope this work, I have never tried to add primary key on temptable before.

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.

Merge Partition Locking table in sql server

I have a BIG table with 400kk rows.
I want to partition this table but I`m having a problem when merging the two older Partition Functions.
I have this table:
CREATE TABLE [dbo].[PartitionDemo](
[Id] [int] IDENTITY(1,1) NOT NULL,
[myDate] [date] NOT NULL,
[variable] [varchar](100) NULL,
CONSTRAINT [pk_PartitionDemo] PRIMARY KEY CLUSTERED
(
[myDate] ASC,
[Id] ASC
)ON [PartitionDemo_PS](mydate)
)
CREATE PARTITION SCHEME [PartitionDemo_PS] AS PARTITION [PartitionDemo_PF] TO ([PartitionDemo_FG_Prev], [PartitionDemo_FG_Historical], [PartitionDemo_FG_201609], [PartitionDemo_FG_201610], [PartitionDemo_FG_201611], [PartitionDemo_FG_201612], [PartitionDemo_FG_201701], [PartitionDemo_FG_201702], [PartitionDemo_FG_201703], [PartitionDemo_FG_201704])
GO
CREATE PARTITION FUNCTION [PartitionDemo_PF](date) AS RANGE RIGHT FOR VALUES (N'2015-03-01T00:00:00.000', N'2016-09-01T00:00:00.000', N'2016-10-01T00:00:00.000', N'2016-11-01T00:00:00.000', N'2016-12-01T00:00:00.000', N'2017-01-01T00:00:00.000', N'2017-02-01T00:00:00.000', N'2017-03-01T00:00:00.000', N'2017-04-01T00:00:00.000')
GO
This is my table with 400kk rows.
What I do to merge partition is:
CREATE TABLE [staging].[PartitionDemo](
[Id] [int] IDENTITY(1,1) NOT NULL,
[myDate] [date] NOT NULL,
[variable] [varchar](100) NULL,
CONSTRAINT [pk_PartitionDemo] PRIMARY KEY CLUSTERED
(
[myDate] ASC,
[Id] ASC
)ON [PartitionDemo_PS](mydate)
)
GO
ALTER TABLE PartitionDemo
SWITCH PARTITION 2 TO [staging].[PartitionDemo] PARTITION 2
ALTER TABLE PartitionDemo
SWITCH PARTITION 3 TO [staging].[PartitionDemo] PARTITION 3
ALTER PARTITION FUNCTION [PartitionDemo_PF]()
MERGE RANGE ('2016-03-01');
The problem is that it locks both tables while merging.
What is the workaround with this problem?
If you remove the first boundary with this function and scheme, all data before 2015-03-01 will be moved to the PartitionDemo_FG_Prev filegroup instead of the PartitionDemo_FG_Historical filegroup as intended. I recommend a NULL partition boundary to ensure the first partition is always empty. This will also allow you to remove files from this unused filegroup and facilitate partition maintenance going forward. See http://www.dbdelta.com/table-partitioning-best-practices/ for more information on this practice.
A brief schema modification lock will be acquired during the SWITCH, MERGE, and SPLIT operations but those should fast meta-data operations because no data movement is needed. The physical data movement is done by the staging table CREATE INDEX...DROP_EXISTING-ON, which also avoids a sort to rebuild the index. This script acquires an exclusive table lock during the SWITCH, MERGE, and SPLIT operations to avoid deadlocking with other activity.
--create staging table exactly like original table
CREATE TABLE [staging].[PartitionDemo](
[Id] [int] IDENTITY(1,1) NOT NULL,
[myDate] [date] NOT NULL,
[variable] [varchar](100) NULL,
CONSTRAINT [pk_PartitionDemo] PRIMARY KEY CLUSTERED
(
[myDate] ASC,
[Id] ASC
) ON [PartitionDemo_PS](mydate)
);
--create temporary partition function and scheme with desired end state
CREATE PARTITION FUNCTION [StagingPartitionDemo_PF](date) AS RANGE RIGHT FOR VALUES (
CAST(NULL AS datetime) --NULL boundary ensures first parttion is always empty
, N'2016-09-01T00:00:00.000' --upper boundary of historical data fg (less than this date)
, N'2016-10-01T00:00:00.000'
, N'2016-11-01T00:00:00.000'
, N'2016-12-01T00:00:00.000'
, N'2017-01-01T00:00:00.000'
, N'2017-02-01T00:00:00.000'
, N'2017-03-01T00:00:00.000'
, N'2017-04-01T00:00:00.000'
);
CREATE PARTITION SCHEME [StagingPartitionDemo_PS] AS PARTITION [StagingPartitionDemo_PF] TO (
[PartitionDemo_FG_Prev]
, [PartitionDemo_FG_Historical]
, [PartitionDemo_FG_201609]
, [PartitionDemo_FG_201610]
, [PartitionDemo_FG_201611]
, [PartitionDemo_FG_201612]
, [PartitionDemo_FG_201701]
, [PartitionDemo_FG_201702]
, [PartitionDemo_FG_201703]
, [PartitionDemo_FG_201704]
);
GO
SET XACT_ABORT ON;
BEGIN TRAN;
--acquire exclusive table lock to prevent deadlocking with concurrent activity
SELECT TOP(0) myDate FROM dboPartitionDemo WITH(TABLOCKX);
--switch first partition into staging (in case data exists before 2015-03-01)
ALTER TABLE dbo.PartitionDemo
SWITCH PARTITION $PARTITION.PartitionDemo_PF(CAST(NULL AS datetime))
TO [staging].[PartitionDemo] PARTITION $PARTITION.PartitionDemo_PF(CAST(NULL AS datetime));
--switch second partition into staging (on or after 2015-03-01 and before 2016-09-01)
ALTER TABLE dbo.PartitionDemo
SWITCH PARTITION $PARTITION.PartitionDemo_PF('2015-03-01T00:00:00.000')
TO [staging].[PartitionDemo] PARTITION $PARTITION.PartitionDemo_PF('2015-03-01T00:00:00.000');
--switch third partition into staging (on or after 2016-09-01 and before 2016-10-01)
ALTER TABLE dbo.PartitionDemo
SWITCH PARTITION $PARTITION.PartitionDemo_PF('2016-09-01T00:00:00.000')
TO [staging].[PartitionDemo] PARTITION $PARTITION.PartitionDemo_PF('2016-09-01T00:00:00.000');
COMMIT;
GO
--rebuild staging table on temporary partition scheme
CREATE UNIQUE CLUSTERED INDEX pk_PartitionDemo ON staging.PartitionDemo(
[myDate] ASC,
[Id] ASC
)
WITH(DROP_EXISTING=ON)
ON [StagingPartitionDemo_PS](mydate);
GO
SET XACT_ABORT ON;
BEGIN TRAN;
--acquire exclusive table lock to prevent deadlocking with concurrent activity
SELECT TOP(0) myDate FROM dboPartitionDemo WITH(TABLOCKX);
--modify original partition scheme to match temporary one
ALTER PARTITION SCHEME PartitionDemo_PS
NEXT USED PartitionDemo_FG_Historical;
ALTER PARTITION FUNCTION PartitionDemo_PF()
SPLIT RANGE(CAST(NULL AS datetime));
ALTER PARTITION FUNCTION PartitionDemo_PF()
MERGE RANGE('2015-03-01T00:00:00.000');
--switch historical data partition partition back to main table
ALTER TABLE staging.PartitionDemo
SWITCH PARTITION $PARTITION.PartitionDemo_PF(NULL)
TO dbo.[PartitionDemo] PARTITION $PARTITION.PartitionDemo_PF(CAST(NULL AS datetime));
--switch 2016-09-01 partition back to main table
ALTER TABLE staging.PartitionDemo
SWITCH PARTITION $PARTITION.PartitionDemo_PF('2016-09-01T00:00:00.000')
TO dbo.[PartitionDemo] PARTITION $PARTITION.PartitionDemo_PF('2016-09-01T00:00:00.000');
COMMIT;
GO

New uniqueidentifier on the go

I want to add a column for a table which would become a PRIMARY KEY and be of type uniqueidentifier. I have this, but I wonder if there is a faster (in fewer code lines) way?
ALTER TABLE [table] ADD [id] [uniqueidentifier]
DEFAULT('00000000-0000-0000-0000-000000000000') NOT NULL
GO
UPDATE [table] SET [id] = NEWID()
GO
ALTER TABLE [table] ADD CONSTRAINT [PK_table_id] PRIMARY KEY CLUSTERED
GO
If you want to keep naming your constraints (and you should), I don't think we can reduce it below 2 statements:
create table T (
Col1 varchar(10) not null
)
go
insert into T (Col1)
values ('abc'),('def')
go
ALTER TABLE T ADD [id] [uniqueidentifier] constraint DF_T_id DEFAULT(NEWID()) NOT NULL
GO
ALTER TABLE T ADD constraint PK_T PRIMARY KEY CLUSTERED (id)
go
drop table T
Note, that I've added a name for the default constraint. Also, this ensures that new rows also have id values assigned. As I said in my comment, it's usually preferable to avoid having columns with values generated by NEWID() clustered - it leads to lots of fragmentation. If you want to avoid that, consider NEWSEQUENTIALID().
If you don't care about constraint names, you can do it as a single query:
ALTER TABLE T ADD [id] [uniqueidentifier] DEFAULT(NEWID()) NOT NULL PRIMARY KEY CLUSTERED

Resources