#sql #sql-server #tsql
#sql #sql-сервер #tsql
Вопрос:
Прошу прощения за ужасный заголовок, не совсем уверен, как объяснить это без примера:
Я пытаюсь вернуть столбец в запросе, который изменяется между 0 и 1 по мере изменения часа столбца datetime в результирующем наборе. Есть ли у кого-нибудь предложения о хорошем способе сделать это?? Или как это можно было бы сделать с помощью CTE
Я начал изучать использование CTE, но не продвинулся далеко и надеюсь, что есть более простой способ. Смотрите ниже очень простой пример того, чего я надеюсь достичь, с текущим выводом и тем выводом, который я хочу. ОБРАТИТЕ внимание, что я создал столбец [band], который выполняет то, что я хочу, при условии, что все значения времени указаны в один и тот же день. Конечно, это прерывается, когда даты охватывают более одного дня.
declare @test as table(id int, dt datetime, comment varchar(50))
insert into @test values(1, '2011-01-01 07:00', 'one')
insert into @test values(2, '2011-01-01 07:30', 'two')
insert into @test values(3, '2011-01-02 07:50', 'three')
insert into @test values(4, '2011-01-03 08:00', 'four')
insert into @test values(5, '2011-01-03 08:50', 'five')
insert into @test values(6, '2011-01-03 09:00', 'six')
insert into @test values(7, '2011-01-03 10:00', 'seven');
select *, DATEPART(HOUR, dt) % 2 as [band]
from @test
ТЕКУЩИЙ ВЫВОД
1 2011-01-01 07:00:00.000 one 1
2 2011-01-01 07:30:00.000 two 1
3 2011-01-02 07:50:00.000 three 1
4 2011-01-03 08:00:00.000 four 0
5 2011-01-03 08:50:00.000 five 0
6 2011-01-03 09:00:00.000 six 1
7 2011-01-03 10:00:00.000 seven 0
ТРЕБУЕМЫЙ ВЫВОД
1 2011-01-01 07:00:00.000 one 1
2 2011-01-01 07:30:00.000 two 1
3 2011-01-02 07:50:00.000 three 0
4 2011-01-03 08:00:00.000 four 1
5 2011-01-03 08:50:00.000 five 1
6 2011-01-03 09:00:00.000 six 0
7 2011-01-03 10:00:00.000 seven 1
Обратите внимание здесь, что я хочу, чтобы столбец [band] чередовался по мере изменения datetime в строках на новые часы. Являются ли они следующим часом в тот же день или другим часом на следующий день.
Комментарии:
1. Но 7% 2 на самом деле равно 1. Вы не пояснили, каковы критерии. Почему ответ должен быть 0, когда это 7: 50, но 1, когда это 8: 50? Или 1, когда это 8, когда 8% 2 равно нулю? В принципе, ваш исходный вывод выглядит совершенно правильно.
2. Из моего вопроса: «Я пытаюсь вернуть столбец в запросе, который чередуется между 0 и 1 по мере изменения времени столбца datetime в результирующем наборе» — Не совсем уверен, как сделать это более понятным. 7-й час 1 января не совпадает с 7-м часом 2 февраля. Вот где мой текущий запрос неверен — это просто отправная точка, которую я использую для тестирования способов выполнения этого. Мой запрос не соответствует моим требованиям. Просто пример того, что я сейчас пытаюсь.
3. В твоих словах нет никакого смысла. В чем точно разница между этими двумя седьмыми часами? В сутках четное количество часов, поэтому чередование часов должно приводить к тому, что одни и те же часы в разные дни будут иметь одинаковое значение.
4. Разница между этими 7 часами составляет 24.
5. @mutex — Думаю, я понял вопрос. Я не знаю, как это сделать в 1 запросе (если это вообще возможно). Будет ли работать хранимая процедура?
Ответ №1:
Как насчет?
SELECT *,
(dense_rank() over (order by Dateadd(hh,Datediff(hh,0,dt),0))) % 2 as [band]
FROM @test
У меня сейчас нет sql под рукой. Но
- Dateadd / datediff удаляет минуты из часов
- Затем dense_rank упорядочивает их как rownumber, но не нумерует дубликаты
- затем %2 выполняет то, что вы делали в своем исходном запросе.
Комментарии:
1. ДА! Кажется, это помогает! Вау, я думаю, мне нужно кое-что почитать о dense_rank () 🙂 1, и я, вероятно, приму в качестве ответа после еще немного тестирования.
Ответ №2:
Насколько я понимаю вопрос:
Вы хотите сравнить значение datetime каждой записи с предыдущей записью, и если изменился час или любое значение, превышающее час, верните 1, в противном случае верните 0.
Для этого нет отличного способа — вы должны выполнить итерацию. В SQL итерация означает курсоры:
DECLARE timeLoop CURSOR FOR
SELECT ID, DT
FROM @test
ORDER BY ID
DECLARE @Id Int
DECLARE @Current DATETIME
DECLARE @Last DATETIME
DECLARE @Change bit
OPEN timeLoop
FETCH NEXT FROM timeLoop into @ID, @Current
SET @LAST = DATEADD(-1, DY, @Current)
WHILE @FETCH_STATUS = 0
BEGIN
IF ABS(DATEDIFF(h, @Last, @Current) >= 1) SET @Change = 1
ELSE SET @Change = 0
SELECT @Id, @Dt, @Change
SET @Last = @Current
FETCH NEXT FROM timeLoop into @ID, @Current
END
CLOSE timeLoop
DEALLOCATE timeLoop
Приносим извинения за любые синтаксические ошибки выше — у меня нет sql server в этом поле и я не проверял точный синтаксис, но это поможет вам начать.
На самом деле я не думаю, что вам вообще нужен CTE
SELECT a.ID, a.DT, ISNULL(b.DT, '1/1/1970')
CASE WHEN ABS(DATEDIFF(h, a.dt, ISNULL(b.DT, '1/1/1970')) < 1 THEN 1
DEFAULT 1
END AS Change
FROM @Test a
LEFT JOIN @Test b
ON a.ID = b.ID-1
Присоедините таблицу к самой себе по ID к ID-1 (и послушайте крик администратора базы данных) при объединении по левой стороне, и первая строка присоединится к null, каждая последующая строка присоединится к предыдущей строке.
Комментарии:
1. 1 спасибо, да, это то, что я пытаюсь сделать. НО я бы хотел избежать использования курсоров, если это возможно. Я подозреваю, что это можно сделать с помощью CTE, но они немного выходят за рамки моих текущих знаний SQL.
2. Ах, я вижу — ну, по крайней мере, мне удалось определить вопрос. 😉
3. Да, спасибо. Это понятно. Интересно, могли бы вы сделать это с помощью какой-нибудь «группы по» в дате, преобразованной в длинное целое число, затем по модулю 60 * 60 * 1000 … это было бы довольно грубо.
4. @Greebo — соединение с самоссылкой не совсем выполняет это. Он помещает 1 только в первую строку после изменения в час. Я хочу, чтобы он «переключался» при первом изменении, затем отключался при следующем изменении, затем снова включался при следующем изменении и т.д.