为什么数字表“无价”?


112

我们的常驻数据库专家告诉我们,数字表非常宝贵。我不太明白为什么。这是一个数字表:

USE Model
GO

CREATE TABLE Numbers
(
    Number INT NOT NULL,
    CONSTRAINT PK_Numbers 
        PRIMARY KEY CLUSTERED (Number)
        WITH FILLFACTOR = 100
)

INSERT INTO Numbers
SELECT
    (a.Number * 256) + b.Number AS Number
FROM 
    (
        SELECT number
        FROM master..spt_values
        WHERE 
            type = 'P'
            AND number <= 255
    ) a (Number),
    (
        SELECT number
        FROM master..spt_values
        WHERE 
            type = 'P'
            AND number <= 255
    ) b (Number)
GO

根据博客文章,给出的理由是

数字表确实是无价之宝。我一直在使用它们进行字符串操作,模拟窗口函数,用大量数据填充测试表,消除游标逻辑以及许多其他任务,如果没有它们,它们将变得异常困难。

但是我不知道这些用途到底是什么—您能否提供一些令人信服的具体示例,说明“数字表”在SQL Server中为您节省了大量的工作—为什么我们应该使用它们?


3
递归CTE可以即时满足您对数字表的许多使用情况,该CTE可以即时生成您所需的数字。但是,CTE方法存在性能损失以及其他一些限制
Nick Chammas 2012年

4
@Nick:我要说的是基于CTE的即时数字表与物理表,这只是如何生成数字表的实现细节。马铃薯大战马铃薯...
Remus Rusanu 2012年

1
@Remus-是的 我只想指出杰夫的另一种选择。
Nick Chammas 2012年

2
我在SO stackoverflow.com/search?q=user%3A27535+%2B%22numbers+table%22上使用数字表有十二个答案。
gbn 2012年

Answers:


82

当您需要投影“丢失数据”时,我已经看到了许多用途。例如。您有一个时间序列(例如访问日志),并且想要显示过去30天每天的点击数(请考虑分析仪表板)。如果执行a,select count(...) from ... group by day您将获得每天的计数,但实际上您至少有一次访问的每一天的结果将只有一行。另一方面,如果您首先从数字表(select dateadd(day, -number, today) as day from numbers)中投影出一个天数表,然后将其与计数(或外层套用,无论您喜欢什么)一起加入,那么您将得到一个结果,该结果的天数为0无法访问。这只是一个例子。当然,有人可能会争辩说,仪表板的表示层可以处理丢失的日期,而只显示0,但是某些工具(例如SSRS)将无法处理此问题。

我见过的其他示例使用类似的时间序列技巧(日期/时间+/-数字)来进行各种窗口计算。通常,每当使用命令式语言时,都应使用具有已知迭代次数的for循环,SQL的声明性和集合性质可以使用基于数字表的技巧。

顺便说一句,我觉得有必要指出一个事实,即使使用数字表感觉像是命令式的程序执行,也不要陷入假设它命令式的谬论。让我举个例子吧:

int x;
for (int i=0;i<1000000;++i)
  x = i;
printf("%d",x);

该程序将输出999999,几乎可以保证。

让我们在SQL Server中使用数字表尝试相同的操作。首先创建一个包含1,000,000个数字的表:

create table numbers (number int not null primary key);
go

declare @i int = 0
    , @j int = 0;

set nocount on;
begin transaction
while @i < 1000
begin
    set @j = 0;
    while @j < 1000
    begin
        insert into numbers (number) 
            values (@j*1000+@i);
        set @j += 1;
    end
    commit;
    raiserror (N'Inserted %d*1000', 0, 0, @i)
    begin transaction;
    set @i += 1;
end
commit
go

现在让我们做“ for循环”:

declare @x int;
select @x = number 
from numbers with(nolock);
select @x as [@x];

结果是:

@x
-----------
88698

如果您现在有一个WTF时刻(毕竟number 集群主键!),那么这个窍门叫做分配顺序扫描,而我并不是@j*1000+@i偶然插入的……您也可以大胆猜测一下,说结果是因为并行性,有时这可能是正确的答案。

在这个桥下有很多巨魔,我在On SQL Server中提到了布尔运算符短路T-SQL函数并不暗示一定的执行顺序


55

我发现数字表在各种情况下都非常有用。

为什么我应该考虑使用辅助编号表?,写于2004年,我展示了一些示例:

  • 解析字符串
  • 寻找身份差距
  • 生成日期范围(例如,填充日历表,这也可能是无价的)
  • 产生时间片
  • 生成IP范围

坏习惯踢:使用循环来填充较大的表,我给一个数字表如何被用来制造插入很多行(而不是使用while循环的下意识的做法)的短期工作。

在“ 处理整数列表:我的方法”“拆分列表的更多内容:自定义定界符,防止重复项和维持顺序”上,我展示了如何使用数字表拆分字符串(例如,一组用逗号分隔的值)并提供性能此方法与其他方法之间的比较。有关拆分和其他字符串处理的更多信息:

“解释的SQL Server数字表-第1部分”中,我提供了有关该概念的一些背景知识,并在以后的文章中详细介绍了特定的应用程序。

还有许多其他用途,这些用途对我来说足够突出,足以为他们撰写文章。

就像@gbn一样,我在堆栈溢出和使用数字表的此站点有一些答案

最后,我有一系列关于不循环生成集的博客文章,部分显示了与大多数其他方法相比,使用数字表的性能优势(除了Remus的古怪离群值):


26

这是我最近从Adam Machanic使用的一个很好的例子

CREATE FUNCTION dbo.GetSubstringCount
(
    @InputString TEXT, 
    @SubString VARCHAR(200),
    @NoisePattern VARCHAR(20)
)
RETURNS INT
WITH SCHEMABINDING
AS
BEGIN
    RETURN 
    (
        SELECT COUNT(*)
        FROM dbo.Numbers N
        WHERE
            SUBSTRING(@InputString, N.Number, LEN(@SubString)) = @SubString
            AND PATINDEX(@NoisePattern, SUBSTRING(@InputString, N.Number + LEN(@SubString), 1)) = 0
            AND 0 = 
                CASE 
                    WHEN @NoisePattern = '' THEN 0
                    ELSE PATINDEX(@NoisePattern, SUBSTRING(@InputString, N.Number - 1, 1))
                END
    )
END

我使用了与a类似的东西CTE来查找子字符串的特定实例(即“在此字符串中查找第三个管道”)来处理相关的定界数据:

declare @TargetStr varchar(8000), 
@SearchedStr varchar(8000), 
@Occurrence int
set @TargetStr='a'
set @SearchedStr='abbabba'
set @Occurrence=3;

WITH Occurrences AS (
SELECT Number,
       ROW_NUMBER() OVER(ORDER BY Number) AS Occurrence
FROM master.dbo.spt_values
WHERE Number BETWEEN 1 AND LEN(@SearchedStr) AND type='P'
  AND SUBSTRING(@SearchedStr,Number,LEN(@TargetStr))=@TargetStr)
SELECT Number
FROM Occurrences
WHERE Occurrence=@Occurrence

如果没有数字表,则替代方法是使用某种循环。基本上,数字表允许您进行基于集合的迭代,而无需使用游标或循环。


5
以及有关在嵌入式TVF中进行字符串处理的潜在危险的强制性警告:T-SQL函数并不意味着一定的执行顺序
Remus

12

每当需要等效于SQL的Enumerable.Range时,我都会使用数字表。例如,我只是在此站点的答案中使用它:计算排列数

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.