Я использую MariaDB-10.2.6.

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

CREATE TABLE `daily_report` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `date` date DEFAULT NULL,
  `user_id` varchar(255) NOT NULL,
  CONSTRAINT `daily_report_user_id_fk` FOREIGN KEY (`user_id`) REFERENCES `user` 
  (`id`) ON DELETE SET NULL ON UPDATE CASCADE
)

CREATE TABLE `user` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `shall_create_every_day` tinyint(1) NOT NULL
)

CREATE TABLE `calendar` (
  `datefield` date
)

Правила :

  1. Пользователь с shall_create_every_day установленным 1 должен создавать daily_report каждый день.
  2. У меня есть таблица календаря calendar, которая состоит из каждой даты от 2000-01-01 до 2020-12-31 (так как generate_series недоступен в MySQL / MariaDB)

Я пробовал следующий запрос:

select daily_report.id, daily_report.date, user.id, calendar.datefield
from calendar
left join daily_report on daily_report.date=calendar.datefield
right join user on daily_report.user_id=user.id and user.shall_create_every_day=1
where calendar.datefield='2017-08-02'

Я пытался установить диапазон дат, но для целей тестирования я ограничил выражение where одним днем.

Он должен вернуть всех пользователей с shall_create_every_day, которые не создали daily_report в '2017-08-12'. Но я получаю только комбинацию созданного отчета в тот день.

Есть ли что-то, что я неправильно понял в левых / правых соединениях, возвращающих нули для несопоставленных строк?

1
esfy 5 Сен 2017 в 10:49

4 ответа

Лучший ответ

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

Ваш запрос делает это:

  1. Получить все записи календаря.
  2. Присоединяйтесь к записям daily_report, если они доступны, иначе создайте пустую фиктивную запись для присоединения.
  3. Получить все записи пользователя с must_create_every_day = 1.
  4. Присоедините первый набор данных к пользователям, если он доступен, иначе создайте пустую фиктивную запись для присоединения к пользователю.
  5. Удалите все записи с внешним соединением, созданные в (4), применив предложение where.

Итак, прежде всего, вы должны спросить себя: мне вообще нужно внешнее соединение? Что касается пользователей и календаря, вы не делаете. На самом деле все, что вы хотите от календаря, это проверить, существует ли запись для «2017-08-02». А что касается таблицы пользователей: это тоже просто критерии; Вы хотите все отчеты для определенных пользователей и определенной даты.

Поэтому выберите из таблицы отчетов и укажите критерии, к которым он принадлежит: в вашем предложении WHERE.

select id, date, user_id
from daily_report 
where user_id in (select id from user where shall_create_every_day = 1)
and date = (select datefield from calendar where datefield = '20170802');

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

select r.id, c.datefield, u.id as user_id
from (select id from user where shall_create_every_day = 1) u
cross join (select datefield from calendar where datefield = '20170802') c
left join daily_report r on r.user_id = u.id and r.date = c.datefield;
0
Thorsten Kettner 5 Сен 2017 в 09:20

Вы сформулировали свой запрос несколько запутанным образом. Я бы сформулировал это, оставив присоединение таблицы user ко второй таблице, содержащей записи для отчетов каждого пользователя в каждый день. Отличительной чертой пользователя, который не писал, было бы то, что данная запись пользователя не соответствует чему-либо во второй таблице.

select
    u.id
from user u
left join
(
    select
        c.datefield,
        dr.me_id
    from calendar c
    left join daily_report dr
        on dr.date = c.datefield
    where c.datefield = '2017-08-02'
 ) cal
    on cal.me_id = u.id and
       u.shall_create_every_day = 1
where
    cal.me_id is null;
0
Tim Biegeleisen 5 Сен 2017 в 08:56

Создать серию доступно в MariaDB. См. здесь . , Это что-то вроде

SELECT '2000-01-01' + INTERVAL seq
    FROM seq_0_241

Вы можете JOIN сделать это без фактической материализации таблицы.

Пожалуйста, не смешивайте LEFT JOIN и RIGHT JOIN. Это либо причина проблемы, либо моя голова слишком занята, чтобы ее понять. Имейте в виду, что это может быть интерпретировано как

FROM a LEFT JOIN (b RIGHT JOIN c)

Что, вероятно, не то, что вы хотели. В любом случае, круглые скобки разрешены (и приветствуются при смешивании LEFT и RIGHT).

1
Rick James 5 Сен 2017 в 17:39

Можно сделать таким образом.

SELECT * 
FROM daily_report 
RIGHT JOIN (calendar c,user u) ON user_id=u.id AND date=c.datefield 
WHERE c.datefield='2017-08-02' AND u.shall_create_every_day=1

Постскриптум Вероятно, есть ошибка в структуре таблицы. daily_report.user_id должно быть int вместо varchar.

0
vadim_hr 5 Сен 2017 в 11:38