为什么数字表“无价”?

Jef*_*ood 116 sql-server

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

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
Run Code Online (Sandbox Code Playgroud)

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

数字表真的是无价之宝。我一直使用它们来进行字符串操作、模拟窗口函数、用大量数据填充测试表、消除游标逻辑以及许多其他没有它们将非常困难的任务。

但我不明白这些用途到底是什么——你能提供一些引人注目的具体例子,说明“数字表”在 SQL Server 中为你节省了大量工作——以及为什么我们应该拥有它们?

Rem*_*anu 87

当您需要投影“缺失数据”时,我已经看到了许多用途。例如。您有一个时间序列(例如访问日志),并且您想显示过去 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);
Run Code Online (Sandbox Code Playgroud)

该程序将输出 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
Run Code Online (Sandbox Code Playgroud)

现在让我们做'for循环':

declare @x int;
select @x = number 
from numbers with(nolock);
select @x as [@x];
Run Code Online (Sandbox Code Playgroud)

结果是:

@x
-----------
88698
Run Code Online (Sandbox Code Playgroud)

如果你现在有一个 WTF 时刻(毕竟number 集群主键!),这个技巧叫做分配顺序扫描,我不是@j*1000+@i偶然插入的......你也可以冒险猜测并说结果是因为并行性,有时这可能是正确的答案。

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


Aar*_*and 60

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

为什么我应该考虑使用辅助数字表?,写于 2004 年,我举几个例子:

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

要踢的坏习惯:使用循环填充大表中,我展示了如何使用数字表来完成插入大量行的简短工作(与使用 while 循环的下意识方法相反)。

处理整数列表:我的方法关于拆分列表的更多信息:自定义分隔符、防止重复和维护顺序中,我展示了如何使用数字表拆分字符串(例如,一组逗号分隔值)并提供性能这种方法与其他方法的比较。有关拆分和其他字符串处理的更多信息:

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

还有许多其他用途,这些只是对我来说足够突出的少数用途,足以写下它们。

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

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


JNK*_*JNK 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
Run Code Online (Sandbox Code Playgroud)

我使用与 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
Run Code Online (Sandbox Code Playgroud)

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

  • 以及关于在内联 TVF 中进行字符串操作的潜在危险的强制性警告:[T-SQL 函数并不意味着特定的执行顺序](http://rusanu.com/2011/08/10/t-sql-functions -do-no-imply-a-certain-order-of-execution/) (6认同)

A-K*_*A-K 12

每当我需要 Enumerable.Range 的 SQL 等效项时,我都会使用数字表。例如,我刚刚在本网站的一个答案中使用了它:计算排列数