SQL получает n результатов из каждого набора с заданным отклонением - PullRequest
0 голосов
/ 10 октября 2018

Используя Oracle 11g и имея следующую таблицу:

USER  | TIME
----- | --------
User1 | 08:15:50
User1 | 10:42:22
User1 | 10:42:24
User1 | 10:42:35
User1 | 10:50:01
User2 | 13:23:05
User2 | 13:23:34
User2 | 13:24:01
User2 | 13:24:02

Для каждого пользователя мне нужно получить (если доступно) ровно 3 записи с отклонением между первым и последним менее минуты.Если строк больше 3, они не будут соответствовать критериям.Не могли бы вы дать мне подсказку?

Результат должен выглядеть следующим образом:

User1 | 10:42:22
User1 | 10:42:24
User1 | 10:42:35

Ответы [ 2 ]

0 голосов
/ 10 октября 2018

Я бы использовал аналитическое count() с предложением range:

Демонстрация SQL Fiddle

select user_, to_char(time_, 'hh24:mi:ss') time_
  from (
    select user_, time_, 
           count(1) over (partition by user_ order by time_ 
                          range between interval '1' minute preceding 
                                    and interval '1' minute following) cnt 
      from (select user_, to_date(time_, 'hh24:mi:ss') time_ from tbl))
  where cnt = 3

Результат:

USER_ TIME_
----- --------
User1 10:42:22
User1 10:42:24
User1 10:42:35

Редактировать: Как заметил @CaiusJard, первый ответ может показывать неправильные значения при наличии интервалов, таких как 10:52:01, 10:53:00, 10:53:59.Есть несколько способов исправить это.Сначала найдите минимальное и максимальное время в группе и проверьте условие numtodsinterval( max - min, 'day') <= interval '1' minute.Во-вторых, нумерация всех строк, а затем присвоение флага этим строкам, где предыдущий, текущий и ведущий count = 3.Наконец, показать отмеченные строки, объединенные с исходной таблицей:

with t as (
  select row_number() over (order by user_, time_) rn, tbl.*,
         count(1) over (partition by user_ order by time_ 
                        range between interval '1' minute preceding 
                                  and interval '1' minute following) cnt
    from (select user_, to_date(time_, 'hh24:mi:ss') time_ from tbl) tbl),
r as (select rn, 
             case when 3 = lag(cnt) over (partition by user_ order by time_) 
                   and 3 = cnt 
                   and 3 = lead(cnt) over (partition by user_ order by time_) 
                  then 1 
             end flag
        from t )
select * from t 
  join (select rn-1 r1, rn r2, rn+1 r3 from r where flag = 1) r
    on rn in (r1, r2, r3)
0 голосов
/ 10 октября 2018

Вот мой удар в этом.У меня нет живого Oracle, и SQLFiddle не работает, поэтому, пожалуйста, сообщите, как это получается:

CREATE TABLE t (
  u VARCHAR(5),
  t DATETIME
);

INSERT INTO t
  (u, t)
VALUES
  ('User1', '2001-01-01 08:15:50'),
  ('User1', '2001-01-01 10:42:22'),
  ('User1', '2001-01-01 10:42:24'),
  ('User1', '2001-01-01 10:42:35'),
  ('User1', '2001-01-01 10:50:01'),
  ('User2', '2001-01-01 13:23:05'),
  ('User2', '2001-01-01 13:23:34'),
  ('User2', '2001-01-01 13:24:01'),
  ('User2', '2001-01-01 13:24:02');

SELECT
    z.u,
    min(z.t) evt_start,
    max(z.t) evt_end
FROM
(
    SELECT y.*, SUM(prev_or_2prev_not_within) OVER(PARTITION BY u ORDER BY t ROWS UNBOUNDED PRECEDING) as ctr
    FROM
    (
       SELECT 
           t.*, 
           CASE WHEN 
               t - LAG(t) OVER(PARTITION BY u ORDER BY t) < 1.0/1440.0 OR
               t - LAG(t, 2) OVER(PARTITION BY u ORDER BY t) < 1.0/1440.0
               THEN 0 ELSE 1
           END as prev_or_2prev_not_within
        FROM
           t
    ) y
 ) z
GROUP BY
    z.u,
    z.ctr
HAVING COUNT(*) = 3

Я полагаю, что он установит инкрементный счетчик, который не увеличивается, когда предыдущий или предыдущий предыдущийряд находится в пределах минуты текущего ряда.Он делает это, классифицируя строки как 0 или 1, и когда 0 происходит, операция sum-all-previousing-rows генерирует счетчик, который не изменяется.Затем он группирует на этом счетчике ровно 3 вхождения.Раздел заставляет счетчик работать на пользователя

enter image description here

Вы можете увидеть его в действии здесь: https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=018125210ecd071f3d11e3d4b3d3e670

Это SQL Server (как уже отмечалось, у меня нет оракула), но термины, используемые для sqlserver и логики, должны быть в целом схожи для оракула - оракул поддерживает запаздывание, неограниченные суммы, наличие и т. д., и он вычисляет дату в терминах dateA - dateB ->число с плавающей запятой, представляющее целое или части дня (и 1440 минут в день, 1/1440 должно представлять собой плавание в одну минуту).Типы данных, которые использует sqlserver, могут немного отличаться от oracle, и этот запрос зависит от того, как столбец TIME (я назвал его t - не нравиться имена столбцов, которые являются зарезервированными словами / ключевыми словами) - это дата, а не строка, которая выглядит как время.Если ваши данные представляют собой строку, рассортируйте ее так, чтобы ее не было (используйте внутренний подзапрос для создания даты и времени или измените свое хранилище данных так, чтобы оно сохранялось как тип даты и времени)

Вы сказали, что хотите получить результатон сообщает пользователю и время события - самый простой способ сделать это - использовать min и max, чтобы получить диапазон дат.Если вы отчаянно хотите показать все 3 строки, вы можете присоединить выходные данные этого запроса к таблице с датой между evt_start и evt_end, или вы можете использовать какую-то функцию типа string_aggregate, чтобы получить список раз подряд.вне внешней групповой операции

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...