Click here to monitor SSC
  • Av rating:
  • Total votes: 31
  • Total comments: 12
Joe Celko

SQL Server SEQUENCE Basics

06 February 2014

The SEQUENCE statement  introduced in SQL Server 2012 brings the ANSI SQL 2003 standard method of generating IDs. This is a great relief to database professionals as it solves some problems what are awkward to solve with the IDENTITY property. Joe Celko explains the basics of using a SEQUENCE

SQL Server now has the ANSI/ISO Standard CREATE SEQUENCE statement. Hooray! The bad news is that most programmers will not be aware of the significance of the underlying math. A SEQUENCE is not an IDENTITY. That propriety feature in T-SQL is a table property inherited from the old Sybase/UNIX days. It counts the number of insertion attempts, not even successes, on one particular machine. This goes back to how files were managed on UNIX systems in the 1970s. In those systems, you needed record numbers to locate the data, so T-SQL exposed this count as IDENTITY. Also, when a record was deleted, a bit flag on each record might be set to signal a garbage collection routine to remove this data during a file utility clean up.

Obviously, IDENTITY increments its value from physical insertion to physical insertion; no big jumps, no gaps. Again, IDENTITY is not a column; it is a table property. That means it cannot be a key by definition. Remember a key is a subset of columns that are unique in every row of a table. It is not an integer; you cannot do meaningful math on it. It is non-deterministic. If you insert a set of more than one row into a table, the rows will be inserted one at time in an order determined by the indexes, internal state of the hardware and number of sessions open at the time.

We have, for a long time, created a table of integers that has all sorts of uses. It starts at 1 and goes to some sufficiently large value. Its name depends on the author of the table. You will see “Numbers”, “Sequence”, “Tally_Table” and “Series” among others. Obviously, we can no longer use “Sequence”; it is a reserved word! I used it myself, and so I’ll have to re-write my books from now on. Darn. I always thought that “Numbers” was too generic; which numbers?? I never liked “Tally”, since a tally is a current score or amount, a running total, or count. It is not a set, but a process. I settled on “Series” for my stuff since a series is a completed (infinite) set, but it is not the right word.

Now, let's do math.

What is the Difference Between a Sequence and a Series?

A sequence is a list of numbers. The order in which the numbers are listed is important, so for instance {1, 2, 3, 4, 5, …} is one sequence, and {2, 1, 4, 3, 6, 5, …} is an entirely different sequence. This is important. It means we can talk about the i-th member of the sequence.

A series is a sum of numbers. A classic example is: 1 + 1/2 + 1/4 + 1/8 + 1/16 + … which converge to 2 (or you can loosely say it will equal 2 at infinite). The order of terms can change the limit to which the series converges or it might not ("absolutely convergent"). Okay, too much math for a database article.

Sequences and series are closely related to each other. But a sequence is not summed.

Tag Numbers versus Sequence

A tag number is string, usually of fixed length, made up of digits. The digits are in sequence to make them easy to sort and to generate. Think of a serial number on a manufactured product. The most common example is a vehicle identification number (VIN). This is a unique code is used by the automotive industry to identify individual motor vehicles,  as defined in ISO 3833. The positions 12 to 17 are a sequence prefixed by codes for the Manufacturer Identifier, some vehicle attributes, a check digit, model year, and plant code.

The check digit is a major reason we like tag numbers. This is a topic in itself, but the idea is that we can take each digit in the tag number and run it thru a formula as an integer. We get a result, usually another digit, and we attach it to the tag number. For example, the Luhn algorithm is a common method

defined by ISO/IEC 7812-1. We like it because it is simple to put in hardware.

1.      Compute the sum of the digits .

2.      Take the units digit from this total.

3.      Subtract it from 10.

It’s not a great check digit, but it catches most of the common input errors – missing digits, extra digits, wrong digits and pairwise transposes.

Now let's into the SQL.

The CREATE SEQUENCE Statement

This statement creates a schema level object that is accessible to any user. It is not part of a table. It is not a procedure that belong to one user. The idea is that a user can invoke the SEQUENCE with a special syntax that is used wherever an integer value of the SEQUENCE data type would work.

If you want a physical model, imagine you are in the butcher store. You walk in and pull a service ticket number from a roll of tickets on the counter. Sequence numbers are generated outside the scope of the current transaction, just like the tickets. The numbers are consumed whether the transaction using the ticket number is actually served (committed) or they walk out (rolled back).

 NEXT VALUE FOR [<database_name>.] [<schema_name>.] <sequence_name>

 [OVER (<over_order_by_clause>)]

I will explain the OVER() clause later; just keep the simple butcher store model in mind for now. Let's look at the roll of tickets (i.e. CREATE SEQUENCE). It has to be declared with parameters that define behavior.

CREATE SEQUENCE [<schema_name>.] <sequence_name>

 [AS [built_in_integer_type | user-defined_integer_type]]

 [START WITH <constant>]

 [INCREMENT BY <constant>]

 [{MINVALUE [<constant>]} | {NO MINVALUE}]

 [{MAXVALUE [<constant>]} | {NO MAXVALUE}]

 [CYCLE | {NO CYCLE}]

 [{CACHE [<constant>]} | {NO CACHE}]

 [;]

 

Let's go thru the BNF in detail. The <sequence_name> and its qualifiers explains itself. This is pure SQL. Sequence numbers are generated outside the scope of the current transaction. They are consumed whether the transaction using the sequence number is committed or rolled back. Think about the butcher shop ticket that gets dropped on the floor.

A sequence can be defined as any integer type: That means TINYINT, SMALLINT, INTEGER and BIGINT; but it also allows DECIMAL (s, 0) and NUMERIC(s, 0) data types. You can also a user-defined data type that is based on one of the allowed types. Do not do that if you value portability.

If no data type is provided, BIGINT is the default. Do not use this unless you really need  a range of -2^63 (-9,223,372,036,854,775,808) to 2^63-1 (9,223,372,036,854,775,807), which makes 'Big Data' seem like note on the back of the hand. As with most defaults, this is the largest, safest value in the problem space.

START WITH <constant>

This is the first value returned by the sequence. The START value must be a value between the minimum and maximum values. The default start value for a new sequence is the minimum value for an ascending sequence and the maximum value for a descending sequence. Note this is a constant; no function calls.

INCREMENT BY <constant>

Value used to increment (or decrement if negative) the value of the sequence for each call to the NEXT VALUE FOR. If the increment is a negative value, the sequence is descending; otherwise, it is ascending. The increment cannot be 0, obviously. The default increment for a new sequence is 1. Again, this is a constant and not a function call.

[MINVALUE <constant> | NO MINVALUE]

Specifies the bounds for the sequence. The default minimum value for a new sequence is the minimum value of the data type of the sequence. This is zero for the TINYINT data type and a negative number for all other data types.

NO MINVALUE is probably a really bad design choice . You probably wanted zero or one.

[MAXVALUE <constant> | NO MAXVALUE

Specifies the bounds for the sequence. The default maximum value for a new sequence is the maximum value of the data type of the sequence.

NO MAXVALUE is probably another bad design. If you are going to use this to create a tag number, you have to worry about overflowing the length of your string.

If you are casting the numeric data type to strings for tag numbers, you will want to be sure that the numbers do not overflow in the conversion. And you probably do not need the upper limit of a data type.

[CYCLE | NO CYCLE]

Property that specifies whether the sequence should restart from the minimum value (or maximum for descending sequences) or throw an exception when its minimum or maximum value is exceeded. The default cycle option for new sequences is NO CYCLE. Note that cycling restarts from the minimum or maximum value, not from the start value.

[CACHE [<constant>] | NO CACHE]

I am not going to explain Cache management. This is an implementation detail and I do programming.

In many financial applications, you cannot afford gaps. For example in Italy, invoice numbers have to be in a strict sequence by law. But you can lose all of the numbers in cache in the event of a major failure, so while it is faster, it is also dangerous. Remember that today “ROI” means “Risk of Incarceration”

OVER (ORDER BY ..) Clause

 Remember earlier in this article when I mentioned that how IDENTITY in assigned to each element of a a set insertion was not deterministic? SEQUENCE has a way to fix this problem; you can order the sequence! If another process is accessing the sequence object at the same time, the numbers returned could have gaps.

An OVER clause applied to the NEXT VALUE FOR function does not support the PARTITION BY or the [ROW | RANGE] subclauses for obvious reasons. The following additional rules apply when using the NEXT VALUE FOR function with the OVER clause:

Multiple calls to the NEXT VALUE FOR function for the same sequence generator in a single statement must all use the same OVER clause definition. Again, this is obvious.

Multiple calls to the NEXT VALUE FOR function that reference different sequence generators in a single statement can have different OVER clause definitions. They are separate schema objects.

If all calls to the NEXT VALUE FOR function in a SELECT statement specifies the OVER clause, an ORDER BY clause may be used in the SELECT statement. Again, the SEQUENCE is a separate schema object.

The OVER clause is allowed with the NEXT VALUE FOR function when used in a SELECT statement or INSERT … SELECT … statement. The NEXT VALUE FOR function is not allowed in UPDATE or MERGE statements.

Programming Examples

To get a feel for how this works, create a simple sequence and play with it. 

 

CREATE SEQUENCE Invoice_Seq

 AS INTEGER

 START WITH 1

 INCREMENT BY 1

 MINVALUE 1

 MAXVALUE 99

 NO CYCLE;

Now, just play with it. Just hit  “SELECT NEXT VALUE FOR Invoice_Seq;” a few times, or in SSMS execute …

SELECT NEXT VALUE FOR Invoice_Seq;

GO 99

SELECT NEXT VALUE FOR Invoice_Seq;

 

After you hit 99 and then invoke the next value, you will get this message.

 

Msg 11728, Level 16, State 1, Line 1

The sequence object 'Invoice_Seq' has reached its minimum or maximum value. Restart the sequence object to allow new values to be generated.

 

We did not talk about this “restart” option yet. It is a straightforward ALTER statement.

ALTER SEQUENCE <sequence name> RESTART [WITH <constant>];

 The WITH option lets you assign the new starting value. If you do not give a specific value, the default is the original starting value. In this example, we can use:

  ALTER SEQUENCE Invoice_Seq RESTART WITH 3;

  SELECT NEXT VALUE FOR Invoice_Seq;

 this will return 3. Most of the situations where you want to re-start a sequence can be done with the CYCLE clause.

 The use of the ORDER BY clause can let you build groupings. Create a sequence groups of ten:

CREATE SEQUENCE Ten_Seq

 AS INTEGER

 START WITH 1

 INCREMENT BY 1

 MINVALUE 1

 MAXVALUE 10

 CYCLE;

Now use it in a query that breaks the students into groups of ten

 SELECT student_id, student_name,

       (NEXT VALUE FOR Ten_Seq OVER(ORDER BY student_id))

       AS student_grouping

  FROM Students;

 Converting the sequence into, say, a four digit invoice serial number is pretty simple. It is important to use VARCHAR(n).

 SELECT 'IN:' + RIGHT ('0000' + CAST (NEXT VALUE FOR Invoice_Seq AS VARCHAR(5)), 4);

 But a fun example is that of using the same sequence in more than one table. Let's go back the original service ticket example. When you come into the shop, you pull a ticket.  

CREATE SEQUENCE Service_Ticket_Seq

 AS INTEGER

 START WITH 1

 INCREMENT BY 1

 MINVALUE 1

 MAXVALUE 100

 CYCLE;

 

We have two departments in this delicatessen, meats and fish. If you have been to  a Kosher market, you know that these two areas are kept separate. A ticket can be used in only one department.

 CREATE TABLE Meats

(ticket_seq INTEGER NOT NULL PRIMARY KEY,

 meat_type VARCHAR(15) NOT NULL);

 

CREATE TABLE Fish

(ticket_seq INTEGER NOT NULL PRIMARY KEY,

 fish_type VARCHAR(15) NOT NULL);

 

CREATE PROCEDURE Ticket_Service

(@in_market_code CHAR(1),

 @in_product_name VARCHAR(15))

AS

BEGIN

DECLARE @local_ticket_seq INTEGER;

SET @local_ticket_seq = NEXT VALUE FOR Service_Ticket_Seq;

IF @in_market_code = 'M'

INSERT INTO Meats

VALUES (@local_ticket_seq, @in_product_name);

ELSE IF @in_market_code = 'F'

   INSERT INTO Fish

   VALUES (@local_ticket_seq, @in_product_name);

END;

 Now, let' call the procedure a few times:

EXEC Ticket_Service 'M', 'Brisket';

EXEC Ticket_Service 'F', 'Lox';

EXEC Ticket_Service 'M', 'Chicken';

EXEC Ticket_Service 'M', 'Brisket';

EXEC Ticket_Service 'F', 'Sturgeon';

EXEC Ticket_Service 'F', 'Haddock';

 And now let's see how this works.

 SELECT * FROM Meats;

1

Brisket

3

Chicken

4

Brisket

 SELECT * FROM Fish;

2

Lox

5

Sturgeon

6

Haddock

 If I UNION the two tables, I get a complete sequence and I am sure their intersection is empty. Doing this without a SEQUENCE is a lot harder. But I did resort to if-then-else flow control because I have two different tables. UGH!

I can put a SEQUENCE in the DEFAULT clause of the DDL for table:

CREATE TABLE Service_Tickets

(ticket_nbr INTEGER DEFAULT NEXT VALUE FOR Service_Ticket_Seq,

 department_code CHAR(1) NOT NULL

   CHECK (department_code IN ('M', 'F')));

 

Now play with this code.

 

INSERT INTO Service_Tickets (department_code)

VALUES ('M');

 

SELECT * FROM Service_Tickets;

 That is cool! But do not stop here. Let's re-do the Meats and Fish tables with this feature, because we can then do inserts without the need for a procedure and still be sure of an empty intersection:

CREATE TABLE Meats

(ticket_seq INTEGER DEFAULT NEXT VALUE FOR Service_Ticket_Seq

       PRIMARY KEY,

 meat_type VARCHAR(15) NOT NULL);

 

CREATE TABLE Fish

(ticket_seq INTEGER DEFAULT NEXT VALUE FOR Service_Ticket_Seq

       PRIMARY KEY,

 fish_type VARCHAR(15) NOT NULL);

Now, to get you started,  try these statements. I dropped the kosher theme :)

INSERT INTO Meats (meat_type) VALUES ('pig');

INSERT INTO Fish (fish_type) VALUES ('squid');

SELECT * FROM Meats;

SELECT * FROM Fish;

 There are no guarantees as to how the sequence numbers will be assigned; it is pretty much first-come, first-served in the system. Did you notice that the sequences are the PRIMARY KEY?

INSERT INTO Meats VALUES (8, 'Cat');

will give us:

Msg 2627, Level 14, State 1, Line 1

Violation of PRIMARY KEY constraint 'PK__Meats__467BFD06C5684404'. Cannot insert duplicate key in object 'dbo.Meats'. The duplicate key value is (8).

The statement has been terminated.

And this is all declarative code!

Joe Celko

Author profile:

Joe Celko is one of the most widely read of all writers about SQL, and was the winner of the DBMS Magazine Reader's Choice Award four consecutive years. He is an independent consultant living in Austin, TX. He has taught SQL in the US, UK, the Nordic countries, South America and Africa.
He served 10 years on ANSI/ISO SQL Standards Committee and contributed to the SQL-89 and SQL-92 Standards.
He has written over 800 columns in the computer trade and academic press, mostly dealing with data and databases. He is the author of eight books on SQL for Morgan-Kaufmann, including the best selling SQL FOR SMARTIES.
Joe is a well-known figure on Newsgroups and Forums, and he is famous for his his dry wit. He is also interested in Science Fiction.

Search for other articles by Joe Celko

Rate this article:   Avg rating: from a total of 31 votes.


Poor

OK

Good

Great

Must read
Have Your Say
Do you have an opinion on this article? Then add your comment below:
You must be logged in to post to this forum

Click here to log in.


Subject: Quick Question
Posted by: Laren Hagen (view profile)
Posted on: Thursday, February 06, 2014 at 5:50 PM
Message: Hi,
Just a minor question to make sure I'm not missing something. When you say: "... a query that breaks the students into groups of ten.", do you mean "...a query that breaks the students into ten groups."?

The way I'm reading the SQL is that the student_grouping column would get IDs from 1 to 10, based on the SEQUENCE (since it cycles). Therefore (assuming you have 10 or more students), you'll get a total of ten groups.

P.S. - Excellent article! Integrating SEQUENCE into DDL is something I hadn't considered... looks very powerful (in the right hands!)

Subject: I agree that ten groups is right ..
Posted by: Celko (view profile)
Posted on: Tuesday, February 11, 2014 at 1:42 PM
Message: Ten groups can be of any size, as written. Want to try to do it the other way, where each group has ten or fewer students? We will need a grouping sequence ..

Subject: No triggers!
Posted by: Bruce W Cassidy (view profile)
Posted on: Sunday, February 16, 2014 at 11:58 PM
Message: I always struggled with Oracle and sequences, as to use them for assigning a synthetic key, I also had to define a trigger (or include them in every DML). It seems that life has moved on, and the inclusion of sequences in DDL will make life a lot easier!

Thanks Joe, a great introduction to the topic.

Subject: Oracle
Posted by: Anonymous (not signed in)
Posted on: Monday, February 17, 2014 at 1:26 AM
Message: Excellent article. It would have been helpful had you compared this to sequence in Oracle with an example.
It would have highlighted the merits of sequence, as you have clearly illustrated, and weaknesses of Oracle sequences as is currently implemented.

Subject: Summary
Posted by: puzsol (view profile)
Posted on: Monday, February 17, 2014 at 3:45 AM
Message: From MSDN:

Use sequences instead of identity columns in the following scenarios:
a) The application requires a number before the insert into the table is made.
b) The application requires sharing a single series of numbers between multiple tables or multiple columns within a table.
c) The application must restart the number series when a specified number is reached. For example, after assigning values 1 through 10, the application starts assigning values 1 through 10 again.
d) The application requires sequence values to be sorted by another field. The NEXT VALUE FOR function can apply the OVER clause to the function call. The OVER clause guarantees that the values returned are generated in the order of the OVER clause's ORDER BY clause.
e) An application requires multiple numbers to be assigned at the same time. For example, an application needs to reserve five sequential numbers. Requesting identity values could result in gaps in the series if other processes were simultaneously issued numbers. Calling sp_sequence_get_range can retrieve several numbers in the sequence at once.
f) You need to change the specification of the sequence, such as the increment value.

I didn't see a) or e) in the article anywhere, which I think is important to understand why you would use this.... Especially when you consider that using a sequence as a default value for a primary key does not stop someone from inserting or updating a row that does not come from the sequence... eg INSERT INTO Meats VALUES (18, 'Cat'); is fine... until the sequence-primary-key gets to 18... MSDN suggests an update trigger to roll any changes back (ouch!)....identity locks it down much better than that.

Not to mention that the Meats table is currently limited to 100 rows... do we add Tiger or Elephant in the last slot? I think for the most part I'll be sticking with identity or row_number()

Subject: More thoughts on SEQUENCE
Posted by: Celko (view profile)
Posted on: Monday, February 17, 2014 at 12:03 PM
Message: a) The application requires a number before the INSERT INTO the table is made.

Frankly, I never understood this one. If I have a sequence element, it has to be somewhere. It might have a status of “to be issued”, “removed or recalled”, etc. But it has to exist in some sense. Status is another concept that translates “a state of being over a temporal duration”, and other problems (you cannot be “DEAD” before “BORN”, etc).

Think about VIN numbers. They have a sequence subfield in them, which is tied to a particular auto factory (Factory X is expecting to produce 1700 Hupmobile sedans this year, numbered '0001' to '1700'). If they fail to meet their quota, the application has no Factory X sedans to track after a certain number of actual vehicles. Deciding whether to give the unused numbers to Factory Y is easy with a SEQUENCE. In fact they could have been sharing this.

If they meant that I need to do something to sequence element, like use it to add a check digit (I love check digits!), then I can do this in the insertion with an expression that has the “NEXT VALUE FOR MySeq” in it; the expression will be ugly. But one of the goals of declarative programming is to get rid of any local variables.

e) An application requires multiple numbers to be assigned at the same time. For example, an application needs to reserve five sequential numbers. Requesting IDENTITY values could result in gaps in the series if other processes were simultaneously issued numbers. Calling Sp_Sequence_Get_Range can retrieve several numbers in the sequence at once.

This one I should covered! The cache can drop values, so it is dangerous. The better way to allocate a block of sequence elements within a transaction.

>> Especially when you consider that using a sequence as a default value for a primary key does not stop someone from inserting or updating a row that does not come from the sequence... eg INSERT INTO Meats VALUES (18, 'Cat'); is fine... until the sequence-primary-key gets to 18... MSDN suggests an update trigger to roll any changes back (ouch!) … IDENTITY locks it down much better than that. <<

I find the automatic limits and checking for duplicate values to be a huge advantage.

I hate IDENTITY since it is a physical count of insertion attempts, and has nothing whatsoever RDBMS. It is magnetic tape files on disk. UGH!!

I hate triggers, too. More procedural code shoved into my beautiful declarative language for historical reasons.

A sequence can be part of a PRIMARY KEY, but not the key itself. Again, back to the VIN, the sequence is a subfield. The sequence number on my check is meaningless without my account number.

Subject: Clarification
Posted by: Anonymous (not signed in)
Posted on: Wednesday, February 19, 2014 at 6:25 AM
Message: You said:

"A sequence can be part of a PRIMARY KEY, but not the key itself. Again, back to the VIN, the sequence is a subfield. The sequence number on my check is meaningless without my account number."

But in you code above you have:

"ticket_seq INTEGER DEFAULT NEXT VALUE FOR Service_Ticket_Seq PRIMARY KEY"

That looks like the SEQUENCE *is* the PRIMARY KEY. Am I missing something?

Subject: I agree
Posted by: Celko (view profile)
Posted on: Wednesday, February 19, 2014 at 7:33 AM
Message: I was sloppy and trying to keep the skeleton code short.

We do not do math on a ticket number, so it needs to be cast as a string. This is what the " RIGHT ('0000' + CAST (NEXT VALUE FOR <something>_Seq AS VARCHAR(<n+1>)), <n>)" trick is for.

But the grouping sequence number does do math, like MOD(), division, addition, etc.

Subject: Surrogate Keys
Posted by: Anonymous (not signed in)
Posted on: Wednesday, February 19, 2014 at 8:07 AM
Message: I'm working with a data warehouse and need surrogate keys for a lot of tables. That should be obvious.

These keys 'mean' nothing besides making the record unique within the table. Gaps don't matter, uniqueness across tables doesn't matter, etc.

Where does SEQUENCE give me any benefit over IDENTITY?

Subject: Surrogate keys in dwh
Posted by: Anonymous (not signed in)
Posted on: Wednesday, February 19, 2014 at 11:04 AM
Message: Hi anonymous :)

In a DWH I would use the sequence the same way as with invoices: to guarantee that your audit trail doesn't have gaps in it. So your audit ID's would be sequences and any issues could be spotted quickly.

Subject: Another use for SEQUENCE
Posted by: Anonymous (not signed in)
Posted on: Wednesday, February 19, 2014 at 1:12 PM
Message: An unmentioned and very useful application of the SEQUENCE would be as a surrogate key in a partitioned table, especially if its windowed and the partitions are being swapped in and out.
We have a few instances of this application of SEQUENCE. During implementation we initially tried using this as the default in DDL for the tables and in a high volume application had issues with the sequence number coming back. This was resolved calling the NEXT VALUE FOR in a procedure

Subject: Typo in line 3
Posted by: JNeville (view profile)
Posted on: Sunday, February 23, 2014 at 8:33 AM
Message: Should "propriety" be "proprietary"?

Otherwise it seems to imply that Microsoft's continuted support of IDENTITY is "appropriateness to the purpose or circumstances"*

Which turns the whole article on its head ;-)

John


* the definition of "propriety" from dictionary.com

 

Phil Factor
Searching for Strings in SQL Server Databases

Sometimes, you just want to do a search in a SQL Server database as if you were using a search engine like Google.... Read more...

 View the blog

Top Rated

Continuous Delivery and the Database
 Continuous Delivery is fairly generally understood to be an effective way of tackling the problems of... Read more...

The SQL Server Sqlio Utility
 If, before deployment, you need to push the limits of your disk subsystem in order to determine whether... Read more...

The PoSh DBA - Reading and Filtering Errors
 DBAs regularly need to keep an eye on the error logs of all their SQL Servers, and the event logs of... Read more...

MySQL Compare: The Manual That Time Forgot, Part 1
 Although SQL Compare, for SQL Server, is one of Red Gate's best-known products, there are also 'sister'... Read more...

Highway to Database Recovery
 Discover the best backup and recovery articles on Simple-Talk, all in one place. Read more...

Most Viewed

Beginning SQL Server 2005 Reporting Services Part 1
 Steve Joubert begins an in-depth tour of SQL Server 2005 Reporting Services with a step-by-step guide... Read more...

Ten Common Database Design Mistakes
 If database design is done right, then the development, deployment and subsequent performance in... Read more...

SQL Server Index Basics
 Given the fundamental importance of indexes in databases, it always comes as a surprise how often the... Read more...

Reading and Writing Files in SQL Server using T-SQL
 SQL Server provides several "standard" techniques by which to read and write to files but, just... Read more...

Concatenating Row Values in Transact-SQL
 It is an interesting problem in Transact SQL, for which there are a number of solutions and... Read more...

Why Join

Over 400,000 Microsoft professionals subscribe to the Simple-Talk technical journal. Join today, it's fast, simple, free and secure.