Author’s Archive: ЈЦЅГЇП €ΘΘΚ

Home / ЈЦЅГЇП €ΘΘΚ
48 Posts

Using uniqueidentifiers (Guids) as primary/foreign keys in SQL Server has its advantages and disadvantages. There are a lot of good discussions of uniqueidentifiers vs. integers as keys out there. We use uniqueidentifiers and the purpose of this example is to address a problem that some encounter using them with large and active databases.

One problem is that primary keys are typically clustered indexes (they are stored in sort order), so if the key for a new record isn’t sort-wise at the end of the index, then the server must move some number of records down to free a slot for the new key. In an extreme case, the server performance can degrade because it’s spending too much time churning the indexes.

Uniqueidentifiers are longer keys (than integers), requiring a bit more work from the server to sort, but the real issue is that uniqueidentifiers generated with newid() are random and therefore not guaranteed to fall at the end of the index. This can be a problem for large tables that experience frequent inserts.

SQL Server exposes the newsequentialid() function that guarantees that 1) a uniqueidentifier is sort-wise larger than the previously generated uniqueidentifier for that SQL instance, so the server can append to the end of the index and avoid the churn, and 2) it retains the uniqueness characteristics that make uniqueidentifiers desirable. The problem with newsequentialid() is that it’s not entirely convenient to use. SQL Server 2005 thru 2008 (and likely 2012) require that the function only be used as a default value for a column.

So while you can do this (SQL 2008 example)…

[OK]
declare @id uniqueidentifier = newid();

This won’t work…
[NO!]
declare @id uniqueidentifier = newsequentialid();

So, if newsequentialid() can only be used as a default for a column and therefore you can only set it when inserting a new record and *not* including that field in the insert statement, then how can you get the value for that column in the newly inserted record?! Sometimes you need it and sometimes you don’t. The example shows how to get the value for those times when you need it. Here’s the table definition, look at the use of newsequentialguid() in the default constraint in the last statement.

Insert a record like the statement below, and SQL Server sets the default values for the [Id] and [CreateDateTime] fields. The [CreateDateTime] receives a default value of the current SQL Server time. The [Id] receives a default value of a new sequential uniqueidentifier. This works!

insert [MyTable]([Namespace]) values ('MyNamespace');

Insert a record like the statement below, and SQL Server sets the default value for the [CreateDateTime] field and you’re supplying the value for the [Id] field.
This solves the first problem (you know the value of [Id]), but creates another problem… the value for [Id] isn’t a sequential uniqueidentifier.

Insert a record like the statement below, and SQL Server sets the default values for the [Id] and [CreateDateTime] fields (good so far) and the new record’s [Id] can be retrieved by using a table variable

Now you’ve got the best of both worlds! Here’s a stored procedure that sort of side-steps the issue with newsequentialid().
Even though a stored procedure isn’t always convenient, it works.

Wouldn’t it be great to turn this stored procedure into a function?! Yes it would, but it won’t work because of SQL’s side-effecting rules.
If you try something like this…
[NO!]

… you’re rewarded for you efforts with the following message

A C# SQL CLR Solution
Here’s an alternate method, using a C# CLR assembly. This is a “Visual C# SQL CLR Database Project For SQL Server 2005-2008″, these project types have some limitations in terms of what libraries they can reference, they’re restricted to using .Net Framework 3.5 SP1, and they induce registration and versioning headaches outside of those with regular T-SQL schema statements.

I put some info at the end regarding registration of the DLL with SQL Server. It’s a little different with 2005 and 2008, and depending on what you’re doing, you may have to deal with assembly trust issues. If you have the energy and patience to get over the initial hump, then a CLR assembly solution just might be the thing for you.

The C#
The SeqGuid class implements the behavior and is called by the Common class, which is called by a SQL database object. The Common class is the one to which SQL objects are pointed (more on that below).

T-SQL CLR Assembly Registration (arcane incantations)

If that worked, then create database objects that call the CLR function. You should see the assembly in the Programmability->Assemblies under your database in SQL Server Management Studio. Keep in mind that your case may require more or less work for registration. There’s usually at least one wrench in the gears to remove

If all went to plan, the new C# function should be callable from T-SQL
select [dbo].[fNewSequentialGuid]() --as a regular function that returns a scalar
or
select [Id] from [dbo].[tfGetNewSequentialGuid]() --as a table-valued function that returns a single-column, single-row table

Note: In cases where a CLR function is to be called between databases, or even instances (requires a linked server to be defined), you may have to wrap the SQL object
for it to be usable. I don’t know why, and there may be another really easy configuration solution for this, but I never got down to the brass tacks of the issue.

 

 

A Unix timestamp is defined as the number of seconds elapsed between January 1, 1970 00:00:00 and a given date/time expressed in Universal Coordinated Time.
A timestamp can be generated up to the date 2038-1-19 3:14:7 before overflowing a 32-bit integer.
http://en.wikipedia.org/wiki/Unix_time

An alternative, encoding a DateTime into a BigInt and back again.

WinFormInactivity

Tags:

Using setvar in T-SQL

There are many more female names in the census data from which this dataset was generated, so statistically the resultset is likely to be lop-sided. If this matters, then extra steps should be taken to balance the M to F ratio.

random

“Random” SQL database backup (random.zip)

 

I have a query for which the keys must appear distinctly, but they are duplicated in the resultset. I want to keep only one of the duplicated rows. That is, if I find a record with a duplicated key, I want to keep only one of the records and all of its other data. I don’t care which of the rows get dumped. Admittedly, this is a strange and rare situation (all the more reason to document it).

Deleting all but one duplicated row