У меня есть ситуация, я думаю, может быть решена с помощью оконной функции, но я не уверен.
Представьте себе следующую таблицу
CREATE TABLE tmp
( date timestamp,
id_type integer
) ;
INSERT INTO tmp
( date, id_type )
VALUES
( '2017-01-10 07:19:21.0', 3 ),
( '2017-01-10 07:19:22.0', 3 ),
( '2017-01-10 07:19:23.1', 3 ),
( '2017-01-10 07:19:24.1', 3 ),
( '2017-01-10 07:19:25.0', 3 ),
( '2017-01-10 07:19:26.0', 5 ),
( '2017-01-10 07:19:27.1', 3 ),
( '2017-01-10 07:19:28.0', 5 ),
( '2017-01-10 07:19:29.0', 5 ),
( '2017-01-10 07:19:30.1', 3 ),
( '2017-01-10 07:19:31.0', 5 ),
( '2017-01-10 07:19:32.0', 3 ),
( '2017-01-10 07:19:33.1', 5 ),
( '2017-01-10 07:19:35.0', 5 ),
( '2017-01-10 07:19:36.1', 5 ),
( '2017-01-10 07:19:37.1', 5 )
;
Я хотел бы иметь новую группу при каждом изменении столбца id_type. Например, первая группа с 7:19:21 до 7:19:25, вторая начинается и заканчивается в 7:19:26 и так далее.
После того, как это сработает, я хочу включить больше критериев для определения групп.
На данный момент, используя запрос ниже ...
SELECT distinct
min(min(date)) over w as begin,
max(max(date)) over w as end,
id_type
from tmp
GROUP BY id_type
WINDOW w as (PARTITION BY id_type)
order by begin;
Я получаю следующий результат:
begin end id_type
2017-01-10 07:19:21.0 2017-01-10 07:19:32.0 3
2017-01-10 07:19:26.0 2017-01-10 07:19:37.1 5
Пока я бы хотел:
begin end id_type
2017-01-10 07:19:21.0 2017-01-10 07:19:25.0 3
2017-01-10 07:19:26.0 2017-01-10 07:19:26.0 5
2017-01-10 07:19:27.1 2017-01-10 07:19:27.1 3
2017-01-10 07:19:28.0 2017-01-10 07:19:29.0 5
2017-01-10 07:19:30.1 2017-01-10 07:19:30.1 3
2017-01-10 07:19:31.0 2017-01-10 07:19:31.0 5
2017-01-10 07:19:32.0 2017-01-10 07:19:32.0 3
2017-01-10 07:19:33.1 2017-01-10 07:19:37.1 5
После того, как я решу этот первый шаг, я добавлю больше столбцов, которые будут использоваться в качестве правил для разбиения групп, и эти другие будут обнуляться.
Версия Postgres: 8.4 (У нас есть Postgres с Postgis, поэтому его нелегко обновить. Функции Postgis меняют имена и возникают другие проблемы, но, надеюсь, мы уже все переписываем, и новая версия будет использовать более новую версию 9.X с Postgis 2.x)
Ответы:
На несколько очков
tmp
которая только сбивает с толку..0
)date
. Если у него есть дата и время, это временная метка (и сохраните ее как единое целое)Лучше использовать оконную функцию ..
Выходы
Explaination
Сначала нам нужно перезагрузить .. Мы генерируем их с
lag()
Тогда мы рассчитываем получить группы.
Тогда мы завернуть в подзапрос
GROUP BY
иORDER
и выберите мин макс (диапазон)источник
1. Оконные функции плюс подзапросы
Подсчитайте шаги для формирования групп, похожих на идею Эвана , с изменениями и исправлениями:
Это предполагает участие столбцов
NOT NULL
. Иначе вам нужно сделать больше.Также при условии,
date
что он будет определенUNIQUE
, в противном случае вам нужно добавить прерыватель связей вORDER BY
пункты, чтобы получить детерминированные результаты. Как:ORDER BY date, id
.Подробное объяснение (ответ на очень похожий вопрос):
Обратите внимание, в частности:
В связанных случаях,
lag()
с 3 параметрами может быть важно, чтобы элегантно покрыть угловой случай первой (или последней) строки. (3-й параметр используется по умолчанию, если нет предыдущей (следующей) строки.Поскольку мы заинтересованы только в фактическом изменении в
id_type
(TRUE
), не имеет значения в данном конкретном случае.NULL
иFALSE
оба не считаютсяstep
.count(step OR NULL) OVER (ORDER BY date)
это кратчайший синтаксис, который также работает в Postgres 9.3 или старше.count()
учитывает только ненулевые значения ...В современном Postgres более чистый эквивалентный синтаксис был бы:
Детали:
2. Вычтите две оконные функции, один подзапрос
Аналогично идее Эрика с модификациями:
Если
date
определеноUNIQUE
, как я упоминал выше (вы никогда не уточняли),dense_rank()
было бы бессмысленно, поскольку результат такой же, как для,row_number()
а последний существенно дешевле.Если
date
это не определеноUNIQUE
(и мы не знаем , что только дублированные на(date, id_type)
), все эти вопросы не имеют смысла, так как результат произволен.Кроме того, подзапрос обычно дешевле, чем CTE в Postgres. Используйте CTE только тогда, когда они вам нужны .
Связанные ответы с дополнительным объяснением:
В связанных случаях, когда у нас уже есть порядковый номер в таблице, мы можем обойтись одной оконной функцией:
3. Высочайшая производительность с функцией plpgsql
Поскольку этот вопрос стал неожиданно популярным, я добавлю еще одно решение для демонстрации максимальной производительности.
В SQL есть много сложных инструментов для создания решений с коротким и элегантным синтаксисом. Но декларативный язык имеет свои ограничения для более сложных требований, которые включают процедурные элементы.
Процедурная функция на стороне сервера быстрее для этого , чем что - либо писал до сих пор , потому что это нужно только одного последовательное сканирование над столом и одной операцией сортировки . Если имеется подходящий индекс, даже только одно сканирование только индекса.
Вызов:
Тест с:
Вы можете сделать функцию универсальной с полиморфными типами и передавать имена табличных типов и столбцов. Детали:
Если вы не хотите или не можете сохранить функцию для этого, вам даже стоит создать временную функцию на лету. Стоит несколько мс.
dbfiddle для Postgres 9.6, сравнивающий производительность для всех трех. Изменен тестовый сценарийДжека.
dbfiddle для Postgres 8.4, где различия в производительности еще больше.
источник
count(x or null)
или даже что она там делает. Может быть , вы могли бы показать некоторые образцы , где это необходимо, потому что это здесь не требуется. И что будет ключевым требованием для покрытия этих угловых случаев. Кстати, я изменил свое downvote на upvote только для примера pl / pgsql. Это действительно круто. (Но, как правило, я против ответов, которые суммируют другие ответы или охватывают угловые случаи - хотя мне неприятно говорить, что это угловой случай, потому что я его не понимаю).count(x or null)
делает. Я буду рад задать оба вопроса, если вы предпочитаете.count(x or null)
нужен разрыв на островах?Вы можете сделать это как простое вычитание
ROW_NUMBER()
операций (или, если ваши даты не уникальны, хотя все еще уникальныid_type
, тогда вы можете использоватьDENSE_RANK()
вместо этого, хотя это будет более дорогой запрос):Посмотрите эту работу в БД Fiddle (или посмотрите версию DENSE_RANK )
Результат:
Логически, вы можете думать об этом как простой
DENSE_RANK()
с микросхемойPREORDER BY
, то есть, вы хотите, чтобыDENSE_RANK
все элементы , которые ранжируются вместе, и вы хотите , чтобы они отсортированы по датам, вы просто должны иметь дело с надоедливой проблемой того факта , что при каждом изменении датыDENSE_RANK
будет увеличиваться. Вы делаете это, используя выражение, как я показал вам выше. Представьте, что у вас был такой синтаксис:DENSE_RANK() OVER (PREORDER BY date, ORDER BY id_type)
гдеPREORDER
исключается из расчета рейтинга иORDER BY
учитывается только значение .Обратите внимание, что это важно
GROUP BY
как для сгенерированногоSeq
столбца, так и дляid_type
столбца.Seq
сам по себе НЕ уникален, возможны совпадения - вы также должны группировать поid_type
.Для дальнейшего чтения по этой теме:
Эта первая ссылка дает вам некоторый код, который вы можете использовать, если хотите, чтобы начальная или конечная дата совпадали с конечной / начальной датой предыдущего или следующего периода (чтобы не было пробелов). Плюс другие версии, которые могут помочь вам в вашем запросе. Хотя они должны быть переведены из синтаксиса SQL Server ...
источник
На Postgres 8.4 вы можете использовать функцию RECURSIVE .
Как они это делают
Рекурсивная функция добавляет уровень к каждому отдельному id_type, выбирая даты одну за другой в порядке убывания.
Затем используйте группировку MAX (дата), MIN (дата) по уровню, id_type, чтобы получить желаемый результат.
Проверьте это: http://rextester.com/WCOYFP6623
источник
Вот еще один метод, который похож на метод Эвана и Эрвина в том, что он использует LAG для определения островов. Он отличается от этих решений тем, что использует только один уровень вложенности, без группировки и значительно больше оконных функций:
is_start
Вычисляемый столбец в вложенном SELECT , маркирует начало каждого острова. Кроме того, вложенный SELECT предоставляет предыдущую дату каждой строки и последнюю дату набора данных.Для строк, которые являются началом их соответствующих островов, предыдущая дата фактически является датой окончания предыдущего острова. Вот как его использует основной SELECT. Он выбирает только те строки, которые соответствуют
is_start = 1
условию, и для каждой возвращаемой строки он показывает собственную строкуdate
asbegin
и следующую строкуprev_date
asend
. Поскольку в последней строке нет следующей строки,LEAD(prev_date)
для нее возвращается значение null, для которого функция COALESCE заменяет последнюю дату набора данных.Вы можете поиграть с этим решением на dbfiddle .
При вводе дополнительных столбцов, идентифицирующих острова, вы, вероятно, захотите ввести подпункт PARTITION BY в предложение OVER каждой оконной функции. Например, если вы хотите обнаружить острова в группах, определенных как a
parent_id
, приведенный выше запрос, вероятно, должен выглядеть следующим образом:И если вы решите воспользоваться решением Эрвина или Эвана, я считаю, что к нему следует добавить и аналогичные изменения.
источник
Больше из академического интереса, чем как практическое решение, вы также можете достичь этого с помощью пользовательского агрегата . Как и другие решения, это будет работать даже на Postgres 8.4, но, как прокомментировали другие, обновите, если можете.
Агрегат обрабатывает так,
null
как если бы он был другимfoo_type
, поэтому прогоны с нулевыми значениями будут одинаковымиgrp
- это может или не может быть тем, что вы хотите.dbfiddle здесь
источник
Это можно сделать,
RECURSIVE CTE
чтобы передать «время начала» из одного ряда в другой и некоторые дополнительные (удобные) приготовления.Этот запрос возвращает желаемый результат:
после подготовки ... рекурсивная часть
Вы можете проверить это на http://rextester.com/POYM83542
Этот метод плохо масштабируется. Для таблицы строк 8_641 это занимает 7 с, для таблицы вдвое больше, чем 28 с. Еще несколько примеров показывают время выполнения, похожее на O (n ^ 2).
Метод Эвана Кэррола занимает меньше 1 с (то есть: дерзайте!) И выглядит как O (n). Рекурсивные запросы абсолютно неэффективны и должны рассматриваться как последнее средство.
источник