เหตุใดตารางตัวเลขจึงประเมินค่าไม่ได้?


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
Nick Chammas

4
@Nick: ฉันจะบอกว่าตารางหมายเลขตาม CTE on-the-fly เทียบกับตารางทางกายภาพเป็นเพียงรายละเอียดการใช้งานของวิธีการที่คุณสร้างตารางตัวเลข มันฝรั่งกับมันฝรั่ง ...
รีมัส Rusanu

1
@Remus - Yup ฉันแค่อยากจะชี้ให้เห็นทางเลือกนี้กับเจฟฟ์
Nick Chammas

2
ฉันมีโหลคำตอบโดยใช้ตารางตัวเลขบน SO stackoverflow.com/search?q=user%3A27535+%2B%22numbers+table%22
GBN

คำตอบ:


82

ฉันเห็นการใช้งานหลายอย่างเมื่อคุณต้องการฉาย 'ข้อมูลที่หายไป' เช่น. คุณมีอนุกรมเวลา (บันทึกการเข้าใช้เป็นต้น) และคุณต้องการแสดงจำนวนครั้งต่อวันสำหรับ 30 วันที่ผ่านมา (คิดว่าแผงควบคุมการวิเคราะห์) ถ้าคุณทำselect count(...) from ... group by dayคุณจะได้รับการนับทุกวัน แต่ผลลัพธ์จะมีแถวสำหรับแต่ละวันที่คุณมีการเข้าถึงอย่างน้อยหนึ่งครั้ง ในทางตรงกันข้ามถ้าคุณฉายตารางวันแรกจากตารางตัวเลขของคุณ ( select dateadd(day, -number, today) as day from numbers) จากนั้นคุณออกจากการเข้าร่วมกับการนับ (หรือการสมัครนอกสิ่งที่คุณจินตนาการ) จากนั้นคุณจะได้รับผลลัพธ์ที่มี 0 สำหรับการนับวันที่คุณ ไม่มีสิทธิ์เข้าถึง นี่เป็นเพียงตัวอย่างเดียว แน่นอนหนึ่งอาจยืนยันว่าเลเยอร์การนำเสนอของแดชบอร์ดของคุณสามารถจัดการกับวันที่หายไปและแสดงเพียง 0 แทน แต่เครื่องมือบางอย่าง (เช่น SSRS) จะไม่สามารถจัดการสิ่งนี้ได้

ตัวอย่างอื่น ๆ ที่ฉันเคยเห็นใช้เทคนิคอนุกรมเวลาที่คล้ายคลึงกัน (วันที่ / เวลา +/- หมายเลข) เพื่อทำการคำนวณหน้าต่างทั้งหมด โดยทั่วไปเมื่อใดก็ตามที่มีความจำเป็นในการใช้ภาษาคุณจะต้องใช้ for for loop ที่มีจำนวนการวนซ้ำที่รู้จักกันดีการประกาศและการกำหนดลักษณะของ SQL สามารถใช้เคล็ดลับตามตารางตัวเลขได้

BTW ผมรู้สึกว่าต้องเรียกความจริงที่ว่าถึงแม้การใช้ตารางตัวเลขมันรู้สึกเหมือนการดำเนินการขั้นตอนความจำเป็นไม่ตกอยู่ในการเข้าใจผิดของสมมติว่ามันเป็นความจำเป็น ขอยกตัวอย่าง:

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 loop':

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

ผลลัพธ์คือ:

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

หากคุณมีช่วงเวลา WTF (หลังจากทั้งหมดnumber เป็นคีย์หลักแบบคลัสเตอร์!) เคล็ดลับนี้เรียกว่าการสแกนคำสั่งการจัดสรรและฉันไม่ได้แทรก@j*1000+@iโดยบังเอิญ ... คุณอาจลองเดาและบอกว่าผลลัพธ์เป็นเพราะความเท่าเทียมและบางครั้งอาจเป็นคำตอบที่ถูกต้อง

มีโทรลล์จำนวนมากภายใต้บริดจ์นี้และฉันได้กล่าวถึงบางอย่างในฟังก์ชั่นลัดวงจรของตัวดำเนินการบูลีนเซิร์ฟเวอร์ SQLและฟังก์ชัน T-SQL ไม่ได้บอกเป็นนัยถึงการดำเนินการบางอย่าง


55

ฉันพบตารางตัวเลขค่อนข้างมีประโยชน์ในหลากหลายสถานการณ์

ที่เหตุใดฉันจึงควรพิจารณาใช้ตารางตัวเลขเสริม เขียนในปี 2004 ฉันแสดงตัวอย่างบางส่วน:

  • การแยกสตริง
  • ค้นหาช่องว่างของตัวตน
  • การสร้างช่วงวันที่ (เช่นการเติมตารางปฏิทินซึ่งสามารถประเมินค่าได้)
  • การสร้างการแบ่งเวลา
  • กำลังสร้างช่วง IP

ที่นิสัยแย่ที่จะเตะ: การใช้ลูปเพื่อเติมตารางขนาดใหญ่ฉันแสดงให้เห็นว่าสามารถใช้ตารางตัวเลขเพื่อทำงานสั้น ๆ ของการแทรกแถวจำนวนมากได้อย่างไร

ที่การประมวลผลรายการจำนวนเต็ม: วิธีการของฉันและอื่น ๆ ในรายการแยก: ตัวคั่นแบบกำหนดเองป้องกันการซ้ำซ้อนและการรักษาลำดับฉันแสดงวิธีใช้ตารางตัวเลขเพื่อแยกสตริง (เช่นชุดของค่าที่คั่นด้วยเครื่องหมายจุลภาค) และให้ประสิทธิภาพ การเปรียบเทียบระหว่างวิธีนี้กับวิธีอื่น ๆ ข้อมูลเพิ่มเติมเกี่ยวกับการแยกและการจัดการสตริงอื่น ๆ :

และในตารางหมายเลขเซิร์ฟเวอร์ SQL อธิบาย - ส่วนที่ 1ฉันให้พื้นหลังบางอย่างเกี่ยวกับแนวคิดและมีการลงรายการบัญชีในอนาคตเพื่อเก็บรายละเอียดโปรแกรมประยุกต์เฉพาะ

มีการใช้งานอื่น ๆ อีกมากมายสิ่งเหล่านี้เป็นเพียงส่วนน้อยที่โดดเด่นพอที่จะเขียนเกี่ยวกับพวกเขา

และเช่นเดียวกับ @gbn ฉันมีคำตอบเล็ก ๆ น้อย ๆ เกี่ยวกับสแต็คล้นและในเว็บไซต์นี้ที่ใช้ตารางตัวเลขเช่นกัน

ในที่สุดฉันมีชุดของโพสต์บล็อกเกี่ยวกับการสร้างชุดโดยไม่ต้องวนซึ่งส่วนหนึ่งแสดงให้เห็นถึงประสิทธิภาพการใช้ตารางตัวเลขเมื่อเทียบกับวิธีอื่น ๆ


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เพื่อค้นหาอินสแตนซ์ย่อยของสตริงย่อย (เช่น "ค้นหาไพพ์ที่ 3 ในสตริงนี้") เพื่อทำงานกับข้อมูลที่คั่นด้วยความสัมพันธ์:

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
และคำเตือนที่บังคับเกี่ยวกับอันตรายที่ซุ่มซ่อนของการจัดการสตริงใน inline TVFs: ฟังก์ชั่น T-SQL ไม่ได้บ่งบอกถึงการดำเนินการบางอย่าง
Remus Rusanu

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.