найти каждую n-ую дату в непрерывном потоке дат - PullRequest
4 голосов
/ 12 января 2012

Я хотел бы находить / отмечать каждый 4-й день в непрерывном потоке дат, вставленном в мою таблицу для каждого пользователя в заданном диапазоне дат

CREATE TABLE mytable (
  id INTEGER,
  myuser INTEGER,            
  day DATE NOT NULL,
  PRIMARY KEY  (id)      
);

проблема в том, что для каждого пользователя действует только 3 дня подряд, после этого должен быть один день "перерыва"

 id  | myuser |    day     |
-----+--------+------------+
  0  |    200 | 2012-01-12 | }
  1  |    200 | 2012-01-13 | }--> 3 continuous days
  2  |    200 | 2012-01-14 | }
  3  |    200 | 2012-01-15 | <-- not ok, user 200 should get warned and delete this
  4  |    200 | 2012-01-16 | }
  5  |    200 | 2012-01-17 | }--> 3 continuous days
  6  |    200 | 2012-01-18 | }
  7  |    200 | 2012-01-19 | <-- not ok, user 200 should get warned and delete this
  8  |    201 | 2012-01-12 | }
  9  |    201 | 2012-01-13 | }--> 3 continuous days
  10 |    201 | 2012-01-14 | }
  11 |    201 | 2012-01-16 | <-- ok, there is a one day gap here
  12 |    201 | 2012-01-17 | 

Основная цель - посмотреть на заданный диапазон дат (обычно на месяц) и определить дни, которые не разрешены. Также я должен позаботиться о том, чтобы перекрывающиеся даты обрабатывались правильно, например, если я смотрю на диапазон дат с 2012-02-01 по 2012-02-29, 2012-02-01 может быть днем ​​"перерыва", если 2012-01-29 до 2012-01-31 присутствуют в этой таблице для того же пользователя.

Ответы [ 2 ]

5 голосов
/ 12 января 2012

У меня нет доступа к PostgreSQL, но, надеюсь, это работает ...

WITH
  grouped_data AS
(
  SELECT
    ROW_NUMBER() OVER (PARTITION BY myuser ORDER BY day) - (day - start_date) AS user_group_id,
    myuser,
    day
  FROM
    myTable
  WHERE
        day >= start_date - 3
    AND day <= end_date
)
,
  sequenced_data AS
(
  SELECT
    ROW_NUMBER() OVER (PARTITION BY myuser, user_group_id ORDER BY day) AS sequence_id,
    myuser,
    day
  FROM
    grouped_data
)
SELECT
  myuser,
  day,
  CASE WHEN sequence_id % 4 = 0 THEN 1 ELSE 0 END as should_be_a_break_day
FROM
  sequenced_data
WHERE
  day >= start_date

Извините, я не объяснил работу, я должен был перейти на встречу:)

Пример с start_date = '2012-01-14' ...

 id | myuser |    day     | ROW_NUMBER() | day - start_date | user_group_id
----+--------+------------+--------------+------------------+---------------
  0 |    200 | 2012-01-12 |     1        |        -2        |  1 - -2 = 3
  1 |    200 | 2012-01-13 |     2        |        -1        |  2 - -1 = 3
  2 |    200 | 2012-01-14 |     3        |         0        |  3 -  0 = 3
  3 |    200 | 2012-01-15 |     4        |         1        |  4 -  1 = 3
  4 |    200 | 2012-01-16 |     5        |         2        |  5 -  2 = 3
----+--------+------------+--------------+------------------+---------------
  5 |    201 | 2012-01-12 |     1        |        -2        |  1 - -2 = 3
  6 |    201 | 2012-01-13 |     2        |        -1        |  2 - -1 = 3
  7 |    201 | 2012-01-14 |     3        |         0        |  3 - -1 = 3
  8 |    201 | 2012-01-16 |     4        |         2        |  4 -  2 = 2

Любые последовательные датыбудет иметь тот же user_group_id.Каждый «разрыв» в днях приводит к тому, что user_group_id уменьшается на 1 (см. Строку 8, если запись была 17-го, 2-дневный разрыв, id был бы 1) .

Когда у вас есть group_id, row_number () можно легко использовать, чтобы сказать, какой день в последовательности это.Максимум 3 дня соответствует «Каждому 4-му дню должен быть разрыв», а «x% 4 = 0» означает каждый 4-й день.

2 голосов
/ 13 января 2012

Намного проще и быстрее с оконной функцией lag():

SELECT myuser
      ,day
      ,COALESCE(lag(day, 3) OVER (PARTITION BY myuser ORDER BY day) = (day - 3)
                ,FALSE) AS break_overdue
FROM   mytable
WHERE  day BETWEEN ('2012-01-12'::date - 3) AND '2012-01-16'::date;

Результат:

 myuser |    day     | break_overdue
--------+------------+---------------
    200 | 2012-01-12 | f
    200 | 2012-01-13 | f
    200 | 2012-01-14 | f
    200 | 2012-01-15 | t
    200 | 2012-01-16 | t
    201 | 2012-01-12 | f
    201 | 2012-01-13 | f
    201 | 2012-01-14 | f
    201 | 2012-01-16 | f

Основные баллы:

  • Запрос помечает все дни как break_overdue после трех последовательных дней. Неясно, хотите ли вы, чтобы все они были отмечены после того, как правило было нарушено, или только каждые 4 дня.

  • Я включаю 3 дня до даты начала (а не только два), чтобы определить, является ли первый день уже нарушением правила.

  • Тест прост: если 3-я строка перед текущей строкой в ​​разделе равна текущему дню - 3, то правило нарушено. Я обертываю все это в COALESCE, чтобы сложить NULL значения до FALSE только по косметическим причинам. Гарантированно работает до тех пор, пока (myuser, day) является уникальным .
    В PostgreSQL вы можете вычитать целые числа из даты, эффективно вычитая дни.

  • Может быть выполнено в одном уровне запроса , без использования CTE или подзапроса. Должно быть намного быстрее.

  • Вам необходим PostgreSQL 8.4 или новее для оконных функций .

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