Как выбрать всех ПОЛЬЗОВАТЕЛЕЙ, у которых нет POST, сгруппированных по датам? - PullRequest
0 голосов
/ 09 марта 2020

У меня есть таблица USERS и таблица POSTS. Я могу легко выбрать всех пользователей, которые разместили сообщения с 01/01/2020 по 01/04/2020, и сгруппировать результаты по дате:

User::whereDoesntHave('posts', function($query) use($first_date, $last_date) {
    $query->whereBetween('posts.date', [$first_date, $last_date]);
})
->get();

Мне нужна помощь, чтобы получить пользователей, которые не публиковали сообщения с 01 С 01/2020 по 01/04/2020 и сгруппировать его по датам. Это означает, что я хочу, чтобы все пользователи, у которых нет строки в таблице POST в указанном диапазоне дат c. Возможно ли это?

Пример того, как я хочу получить результат:

01/01/2020
- John
- Ana
- Sandra

01/02/2020
- Ana
- Sandra

01/03/2020
- John
- Lucas
- Charlie

01/04/2020
- Charlie
- Ana



users table:

id = (int)
name (varchar)

posts table

id (int)
user_id (int)
msg (text)
date (datetime)

Ответы [ 2 ]

0 голосов
/ 09 марта 2020

Вот решение Laravel, которое должно работать. Не так эффективно, но гораздо проще для кода. Мы oop просматриваем даты в заданном периоде и и проверяем отсутствие сообщений в указанный день.

$users = [];
$dates = Carbon\CarbonPeriod::create('2020-01-01', '2020-01-04');
foreach ($dates as $date) {
    $users[$date] = User::whereDoesntHave('posts', function($q) use($date) {
        $q->whereDate('date', '!=', $date);
    })->get()->toArray();
}
dd($users);
0 голосов
/ 09 марта 2020

мы могли бы использовать шаблон антисоединения

кратко ... запрос, чтобы вернуть все даты и пользователей, с внешним объединением, чтобы включить совпадающие строки из постов, а затем исключить все строки, у которых была совпадающая строка из сообщения, оставляя строки, у которых не было соответствующей строки в сообщениях

В качестве отправной точки мы можем получить декартово произведение (cross join) всех дат со всеми пользователями.

SELECT d.dt
     , u.user_name
  FROM ( SELECT '2018-01-01' + INTERVAL 0 DAY AS dt
         UNION ALL SELECT '2018-01-01' + INTERVAL 1 DAY
         UNION ALL SELECT '2018-01-01' + INTERVAL 2 DAY
         UNION ALL SELECT '2018-01-01' + INTERVAL 3 DAY
       ) d
 CROSS
  JOIN `USERS` u
 ORDER
    BY d.dt
     , u.user_name

(Чтобы вернуть строки из запроса, нам нужен источник для этих строк. Мы не можем получить «пропущенные» строки, возвращаемые только из таблицы POSTS. Нам нужен полный набор строк, затем мы можем сравнить с содержимое таблицы POSTS.)

Мы можем добавить внешнее соединение, чтобы идентифицировать совпадающие строки, а затем исключить строки, у которых есть совпадение.

SELECT d.dt
     , u.user_name
  FROM ( SELECT '2018-01-01' + INTERVAL 0 DAY AS dt
         UNION ALL SELECT '2018-01-01' + INTERVAL 1 DAY
         UNION ALL SELECT '2018-01-01' + INTERVAL 2 DAY
         UNION ALL SELECT '2018-01-01' + INTERVAL 3 DAY
       ) d
 CROSS
  JOIN `USERS` u

    -- anti-join, exclude rows that have a matching row in posts
  LEFT
  JOIN `POSTS` p
    ON p.user_name = u.user_name
   AND p.dt        = d.dt
 WHERE p.dt IS NULL

 ORDER
    BY d.dt
     , u.user_name

Пример запроса возвращает каждую отдельную дату вместе с пользователями, у которых не было публикации на эту отдельную дату. Это удовлетворяет одной возможной интерпретации довольно расплывчатой ​​спецификации "кто не публиковал от ... до ... и сгруппировал ее по датам"

Чтобы этот запрос вернул ожидаемые результаты, мы могли бы создать пример данных :

USERS   
user_name
--------- 
Ana
Charlie
John
Lucas
Sandra

и

POSTS
user_name  dt
---------  ----------
Charlie    2020-01-01
Lucas      2020-01-01
Charlie    2020-01-02
John       2020-01-02
Lucas      2020-01-02
Ana        2020-01-03
Sandra     2020-01-03
John       2020-01-04
Lucas      2020-01-04
Sandra     2020-01-04

Мы можем использовать NOT EXISTS с коррелированным подзапросом вместо анти-объединения, чтобы возвращать эквивалентные результаты ...

SELECT d.dt
     , u.user_name
  FROM ( SELECT '2018-01-01' + INTERVAL 0 DAY AS dt
         UNION ALL SELECT '2018-01-01' + INTERVAL 1 DAY
         UNION ALL SELECT '2018-01-01' + INTERVAL 2 DAY
         UNION ALL SELECT '2018-01-01' + INTERVAL 3 DAY
       ) d
 CROSS
  JOIN `USERS` u

 WHERE NOT EXISTS
       ( SELECT 1
           FROM `POSTS` p
          WHERE p.user_name = u.user_name
            AND p.dt        = d.dt
       )

 ORDER
    BY d.dt
     , u.user_name
...