Чому таблиці чисел «неоцінені»?


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.
Нік Чаммас

4
@ Nick: Я б сказав, що таємна таблиця номерів на основі CTE на основі фізичної таблиці - це лише детальна інформація про те, як ви створюєте таблицю чисел. Картопля проти картоплі ...
Рем Русану

1
@Remus - Так. Я просто хотів вказати на цю альтернативу Джеффу.
Нік Чаммас

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) просто не зможуть впоратися з цим.

Інші приклади, за якими я бачив, використовували подібні трюки часових рядів (дата / час +/- число) для виконання всіх видів розрахунків у вікні. Взагалі, щоразу, коли в обов'язковій мові ви використовуєте цикл із добре відомою кількістю ітерацій, декларативний та заданий характер 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випадково ... Ви також могли задуматися і сказати, що результат полягає в тому, що паралелізм і що іноді може бути правильною відповіддю.

Під цим мостом є багато тролів, і я згадував про деякі на короткому замиканні булевого оператора на SQL Server, а функції T-SQL не передбачають певного порядку виконання


55

Я знайшов таблицю цифр досить корисною в різних ситуаціях.

На Чому я повинен розглянути можливість використання таблиці допоміжних номерів? , написаний у 2004 році, я показую кілька прикладів:

  • Розбір струни
  • Пошук розривів ідентичності
  • Створення діапазонів дат (наприклад, заповнення таблиці календаря, яка також може бути неоціненною)
  • Генерування відрізків часу
  • Генерування діапазонів IP

При поганих звичках бити: використовуючи петлі для заповнення великих таблиць , я показую, як таблиця чисел може бути використана для короткої роботи над тим, щоб вставити багато рядків (на відміну від підходу в коліні, коли використовується цикл while).

Під час обробки списку цілих чисел: мій підхід та докладніше про розщеплення списків: спеціальні роздільники, запобігання дублікатів та підтримка порядку , я показую, як використовувати таблицю чисел для розділення рядка (наприклад, набір значень, розділених комами) та забезпечення продуктивності порівняння цього та інших методів. Детальніше про розщеплення та інші обробці рядків:

І в Таблиці чисел SQL Server, Пояснено - Частина 1 , я навожу деякі відомості про концепцію і майбутні публікації в магазині, щоб деталізувати конкретні програми.

Є багато інших застосувань, це лише декілька, які мені виділилися достатньо, щоб написати про них.

І як @gbn, у мене є кілька відповідей на переповнення стека та на цьому веб-сайті, які також використовують таблицю цифр.

Нарешті, у мене є серія публікацій у блозі про генерування наборів без циклу, які частково показують перевагу продуктивності використання таблиці чисел порівняно з більшістю інших методів (химерний вибух Ремуса вбік):


26

Ось чудовий приклад, який я нещодавно використав від Адама Маханіка:

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
І обов'язкове попередження про загрозливу небезпеку здійснення маніпуляцій з рядком в вбудованих TVF: функції T-SQL не передбачають певного порядку виконання
Рем Русану,

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.