Сложный SQL-запрос, нигде нет ответа :( - PullRequest
0 голосов
/ 02 июля 2018

Предположим, что у меня есть события в базе данных SQL со свойствами date и user_id. У меня есть 10 записей в таблице event:

1.  user_id=1, date=2018.04.10
2.  user_id=1, date=2018.04.11
3.  user_id=1, date=2018.04.13
4.  user_id=1, date=2018.04.17
5.  user_id=1, date=2018.04.18
6.  user_id=2, date=2018.04.12
7.  user_id=2, date=2018.04.12
8.  user_id=2, date=2018.04.13
9.  user_id=2, date=2018.04.15
10. user_id=2, date=2018.04.16

Можно ли написать запрос с использованием стандартного синтаксиса SQL, который будет показывать мне только записи для каждого пользователя, эта дата по крайней мере с разницей в 2 дня. Итак:

1.  user_id=1, date=2018.04.10   will be in result
2.  user_id=1, date=2018.04.11   not in result, only 1 day difference
3.  user_id=1, date=2018.04.12   will be in result, 2 days dif from record nbr 1.
4.  user_id=1, date=2018.04.17   will be in result, 5 days dif from record nbr 3.
5.  user_id=1, date=2018.04.18   no in result, only 1 day dif from record nbr4.
6.  user_id=2, date=2018.04.12   will be in result
7.  user_id=2, date=2018.04.12   not in result, 0 day difference
8.  user_id=2, date=2018.04.13   not in result, only 1 day dif from record nbr. 6.
9.  user_id=2, date=2018.04.15   will be in result, 2 days dif from record nbr 8.
10. user_id=2, date=2018.04.16   not in result, only 1 day dif from record nbr. 9.

Пожалуйста, помогите, ребята, никто в моем офисе не может помочь мне с этим :( Я буду использовать этот запрос в Google BigQuery

Ответы [ 3 ]

0 голосов
/ 02 июля 2018

Как отметил комментарий Гордона Линоффа: «Таблицы SQL представляют неупорядоченные множества», и крайне важно понять, что при написании запросов для решения такого рода проблем.

Еще один способ подойти к делу - сделать шаг назад и рассмотреть, в чем причина , а не , включая некоторую заданную строку в наборе результатов. Судя по предоставленной вами выборке данных, кажется, что существует другая строка (т.е. отличная от заданной), которая имеет тот же user_id и дату, равную или равную за один день до даты данный ряд. Это тривиально переводит в стандартный коррелированный подзапрос, используя WHERE NOT EXISTS. И это гораздо точнее, чем ваше расплывчатое «эта дата составляет разницу как минимум в 2 дня», что ставит вопрос о «разнице с чем ?» .

SELECT USER_ID,DATE
  FROM EVENT E
 WHERE NOT EXISTS (SELECT * FROM EVENT E2
                    WHERE E2.USER_ID = E.USER_ID
                          AND
                          <appropriate comparison here between E2.DATE and E.DATE>
                          AND
                          <appropriate comparison here to ascertain only distinct rows are processed>);

Использование оконных функций также может правильно решить вашу проблему, но, как отметил Гордон Линофф, нужно быть бдительным в отношении того, как они ведут себя по связям, а также по «первым» и «последним» строкам любой группы.

EDIT

Также кажется, что есть проблема, почему вы говорите для строки 3. «В результате получится, что 2 дня отличаются от записи nbr 1.» Почему вы не сравниваете здесь со строкой 2 ??? Потому что строка 2 не была сохранена для набора результатов, и вы хотите, чтобы сравнение всегда было с "последней строкой сохранено" ??? Это делает проблему / решение по своей природе рекурсивным и делает неприменимым решение как моего, так и Гордона.

0 голосов
/ 02 июля 2018

Ниже для BigQuery Standard SQL

Как Эрвин упомянул в EDIT в своем ответе - problem/solution inherently recursive and makes both mine and Gordon's solution inapplicable, поэтому в приведенном ниже решении рассматривается рекурсивность. Кроме того, он правильно обрабатывает ваши поля даты, анализируя их в тип DATE, и после того, как все вычисления выполнены, форматирует их обратно в нотацию. и т.д.

#standardSQL
CREATE TEMPORARY FUNCTION qualified_entries(arr ARRAY<DATE>)
RETURNS ARRAY<DATE>
LANGUAGE js AS """
  var result = []; prev = null; day = 1000*60*60*24;
  for (i = 0; i < arr.length; i++) {
    if (i == 0 || Math.round((arr[i].getTime() - prev)/day) > 2) {
      result.push(arr[i]);
      prev = arr[i].getTime();
    }
  };
  return result;
""";
SELECT user_id, FORMAT_DATE('%Y.%m.%d', dt) dt FROM (
  SELECT user_id, qualified_entries(ARRAY_AGG(PARSE_DATE('%Y.%m.%d', dt)))dt
  FROM `project.dataset.table`
  GROUP BY user_id
), UNNEST(dt) dt

Вы можете протестировать / поиграть выше, используя фиктивные данные из вашего вопроса, как показано ниже

#standardSQL
CREATE TEMPORARY FUNCTION qualified_entries(arr ARRAY<DATE>)
RETURNS ARRAY<DATE>
LANGUAGE js AS """
  var result = []; prev = null; day = 1000*60*60*24;
  for (i = 0; i < arr.length; i++) {
    if (i == 0 || Math.round((arr[i].getTime() - prev)/day) > 2) {
      result.push(arr[i]);
      prev = arr[i].getTime();
    }
  };
  return result;
""";
WITH `project.dataset.table` AS (
  SELECT 1 user_id, '2018.04.10' dt UNION ALL
  SELECT 1, '2018.04.11' UNION ALL
  SELECT 1, '2018.04.13' UNION ALL
  SELECT 1, '2018.04.17' UNION ALL
  SELECT 1, '2018.04.18' UNION ALL
  SELECT 2, '2018.04.12' UNION ALL
  SELECT 2, '2018.04.12' UNION ALL
  SELECT 2, '2018.04.13' UNION ALL
  SELECT 2, '2018.04.15' UNION ALL
  SELECT 2, '2018.04.16' 
)
SELECT user_id, FORMAT_DATE('%Y.%m.%d', dt) dt FROM (
  SELECT user_id, qualified_entries(ARRAY_AGG(PARSE_DATE('%Y.%m.%d', dt)))dt
  FROM `project.dataset.table`
  GROUP BY user_id
), UNNEST(dt) dt
-- ORDER BY user_id, dt

с результатом как

Row     user_id     dt   
1       1           2018.04.10   
2       1           2018.04.13   
3       1           2018.04.17   
4       2           2018.04.12   
5       2           2018.04.15   
0 голосов
/ 02 июля 2018

Просто используйте lag():

select e.*
from (select e.*,
             lag(prev_date) over (partition by user_id order by date) as prev_date
      from events e
     ) e
where prev_date is null or 
      date > date_add(prev_date, interval 2 day);

Примечание: это может быть немного сложно с привязанными датами. Было бы лучше, если бы вы добавили второй столбец к order by, чтобы порядок был стабильным.

Если у вас есть только эти два столбца, то вы можете сделать отдельный на самом низком уровне для решения этой проблемы:

select e.*
from (select e.*,
             lag(prev_date) over (partition by user_id order by date) as prev_date
      from (select distinct e.* from events e) e
     ) e
where prev_date is null or 
      date > date_add(prev_date, interval 2 day);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...