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 年,我举几个例子:
在要踢的坏习惯:使用循环填充大表中,我展示了如何使用数字表来完成插入大量行的简短工作(与使用 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)
如果您没有数字表,另一种方法是使用某种循环。基本上,数字表允许您进行基于集合的迭代,而无需游标或循环。