Here’s a little demonstration of the power of a sequence (or sometimes called Tally) table in SQL. The sequence table allows you to perform set logic for things that would normally require looping or procedural code.
Today I was working on a query that produced a set of possible values, and then used an outer join to present all implemented instances. The problem I had was that there were multiple groups, each having a different range of data for possible values. I needed a query presenting all possible values, and then all actual implementations.
Here is a demonstration using a contrived situation. Consider the letters A-Z in the alphabet. In the ANSII English character set these letters are defined by the ASCII values 65 – 91 consecutively. Let’s say I want to produce each letter, and count the number of characters from the beginning of the Alphabet to the current letter. For the first three characters it would look like:
A-1
B-1
B-2
C-1
C-2
C-3
…
Given the string ABCDEFGHIJKLMNOPQRSTUVWXYZ I want to parse each letter, and Count from 1 to the position of that character in the string. I’m going to use a sequence table twice. You may remember that a sequence table consists of one column with a number. It simply has a series of rows with an incrementing number. 1,2,3,4,5…
First, I’m going to use the sequence table to convert the string into a table, with one character per row, and the position in which that character was found in the string. I use a Common Table Expression (CTE) to do this transformation.
WITH Letters (Letter, Position)
AS
(
SELECT SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ', Number, 1) , number
FROM sequence
WHERE NUMBER BETWEEN 1 AND 26
)
SELECT
Letter
,ASCII(Letter) as ASCValue
,'From 1 To ' + CONVERT(VARCHAR(2), Position) As Range
,Number
FROM Letters
JOIN Sequence ON Number BETWEEN 1 AND Position
The second use of the sequence table counts from 1 to the position of the current character in the string.
Here are the first three character results from the query:
Letter | ASCValue | Range | Number |
A | 65 | From 1 To 1 | 1 |
B | 66 | From 1 To 1 | 1 |
B | 66 | From 1 To 2 | 2 |
C | 67 | From 1 To 1 | 1 |
C | 67 | From 1 To 2 | 2 |
C | 67 | From 1 To 3 | 3 |
Since my string contained the alphabet in character order, each new group increments by 1 from the previous group. However, you could easily sort the letters in a different order, resulting in a final output that has a more jagged appearence. The point is, the breakdown of each group is independent of the group before or after.
In this case the sequence table provides the power of a procedural loop without the overhead and complexity of the code.
Cheers,
Ben