Может ли решение T-SQL для пробелов и островков работать быстрее, чем решение C #, работающее на клиенте?
Чтобы быть точным, давайте предоставим некоторые тестовые данные:
CREATE TABLE dbo.Numbers
(
n INT NOT NULL
PRIMARY KEY
) ;
GO
INSERT INTO dbo.Numbers
( n )
VALUES ( 1 ) ;
GO
DECLARE @i INT ;
SET @i = 0 ;
WHILE @i < 21
BEGIN
INSERT INTO dbo.Numbers
( n
)
SELECT n + POWER(2, @i)
FROM dbo.Numbers ;
SET @i = @i + 1 ;
END ;
GO
CREATE TABLE dbo.Tasks
(
StartedAt SMALLDATETIME NOT NULL ,
FinishedAt SMALLDATETIME NOT NULL ,
CONSTRAINT PK_Tasks PRIMARY KEY ( StartedAt, FinishedAt ) ,
CONSTRAINT UNQ_Tasks UNIQUE ( FinishedAt, StartedAt )
) ;
GO
INSERT INTO dbo.Tasks
( StartedAt ,
FinishedAt
)
SELECT DATEADD(MINUTE, n, '20100101') AS StartedAt ,
DATEADD(MINUTE, n + 2, '20100101') AS FinishedAt
FROM dbo.Numbers
WHERE ( n < 500000
OR n > 500005
)
GO
Этот первый набор тестовых данных имеет ровно один пробел:
SELECT StartedAt ,
FinishedAt
FROM dbo.Tasks
WHERE StartedAt BETWEEN DATEADD(MINUTE, 499999, '20100101')
AND DATEADD(MINUTE, 500006, '20100101')
Второй набор тестовых данных имеет промежутки 2М -1, промежуток между каждыми двумя смежными интервалами:
TRUNCATE TABLE dbo.Tasks;
GO
INSERT INTO dbo.Tasks
( StartedAt ,
FinishedAt
)
SELECT DATEADD(MINUTE, 3*n, '20100101') AS StartedAt ,
DATEADD(MINUTE, 3*n + 2, '20100101') AS FinishedAt
FROM dbo.Numbers
WHERE ( n < 500000
OR n > 500005
)
GO
В настоящее время я использую 2008 R2, но решения 2012 года очень приветствуются. Я отправил свое решение C # в качестве ответа.
Следующий код C # решает проблему:
Этот код вызывает эту хранимую процедуру:
Он находит и печатает один разрыв с интервалами 2M в следующие моменты времени, горячий кэш:
Он находит и печатает промежутки 2М-1 с интервалами 2М в следующее время, горячий кэш:
Это очень простое решение - на его разработку у меня ушло 10 минут. Недавний выпускник колледжа может придумать это. На стороне базы данных план выполнения представляет собой тривиальное объединение слиянием, которое использует очень мало ЦП и памяти.
Изменить: чтобы быть реалистичным, я запускаю клиент и сервер на отдельных полях.
источник
Я думаю, что исчерпал пределы моих знаний в SQL-сервере на этом ....
Чтобы найти пробел в SQL-сервере (что делает код C #), и вам не нужны начальные или конечные пробелы (те, которые находятся до первого начала или после последнего конца), тогда следующий запрос (или варианты) является быстрее всего я смог найти:
Это работает, хотя и незначительно, но для каждого набора начала и конца вы можете рассматривать начало и конец как отдельные последовательности, сдвигать окончание на единицу и показывать промежутки.
например, возьмите (S1, F1), (S2, F2), (S3, F3) и закажите как: {S1, S2, S3, null} и {null, F1, F2, F3} Затем сравните строку n со строкой n в каждом наборе, и пропуски, где значение F набора меньше, чем значение S набора ... проблема, я думаю, в том, что в SQL-сервере нет никакого способа объединить или сравнить два отдельных набора только по порядку значений в набор ... отсюда использование функции row_number, позволяющей нам объединять, основываясь исключительно на номере строки ... но нет никакого способа сообщить SQL-серверу, что эти значения уникальны (без вставки их в таблицу var с индексом на это - что занимает больше времени - я пробовал), так что я думаю, что объединение слиянием менее чем оптимально? (хотя трудно доказать, когда это быстрее, чем что-либо еще, что я мог сделать)
Я смог получить решения, используя функции LAG / LEAD:
(что, кстати, я не гарантирую результатов - похоже, это работает, но я думаю, что полагается на StartedAt в порядке в таблице задач ... и это было медленнее)
Использование изменения суммы:
(не удивительно, также медленнее)
Я даже пытался использовать агрегатную функцию CLR (чтобы заменить сумму - она была медленнее суммы и использовала row_number () для сохранения порядка данных), а CLR - табличную функцию (чтобы открыть два набора результатов и сравнить значения на основе чисто по порядку) ... и это тоже было медленнее. Я много раз ломал голову над ограничениями SQL и CLR, пробуя многие другие методы ...
И для чего?
Работая на одной машине и выплевывая как данные C #, так и данные, отфильтрованные с помощью SQL, в файл (согласно исходному коду C #), время практически одинаковое .... примерно 2 секунды для данных с 1 разрывом (C # обычно быстрее ), 8-10 секунд для набора данных с несколькими промежутками (SQL обычно быстрее).
ПРИМЕЧАНИЕ . Не используйте среду разработки SQL Server для сравнения времени, так как для ее отображения в сетке требуется время. Как протестировано с SQL 2012, VS2010, .net 4.0 Профиль клиента
Я укажу, что оба решения выполняют практически одинаковую сортировку данных на сервере SQL, поэтому нагрузка на сервер для извлечения и выборки будет одинаковой, какое бы решение вы ни использовали, единственная разница заключается в обработке на клиенте (а не на сервере) и передача по сети.
Я не знаю, в чем разница, возможно, при разделении между разными сотрудниками, или когда вам могут понадобиться дополнительные данные с информацией о пропусках (хотя я не могу думать о многом другом, кроме идентификатора персонала), или, конечно, если есть медленное соединение для передачи данных между сервером SQL и клиентской машиной (или медленным клиентом) ... Также я сделал сравнение запираемых раз, или проблемы раздора, или CPU / вопросы сети для нескольких пользователей ... Так что я не знаю, какой из них, скорее всего, будет узким местом в этом случае.
Что я действительно знаю, так это то, что SQL-сервер не подходит для такого рода сравнений множеств, и если вы не напишете запрос правильно, вы заплатите за него дорого.
Это проще или сложнее, чем писать версию на C #? Я не совсем уверен, что решение Change +/- 1, работающее в целом, также не совсем интуитивно понятно, и я, но это не первое решение, к которому придет среднестатистический выпускник ... после этого его достаточно легко скопировать, но Прежде всего, нужно разобраться, чтобы написать ... То же самое можно сказать и о версии SQL. Что сложнее? Что является более надежным для мошеннических данных? Который имеет больший потенциал для параллельных операций? Действительно ли имеет значение, когда разница настолько мала по сравнению с усилиями по программированию?
Одна последняя заметка; существует необъявленное ограничение на данные - значение StartedAt должно быть меньше значения FinishedAt, иначе вы получите плохие результаты.
источник
Вот решение, которое работает за 4 секунды.
источник