Я использую 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
)
Правила :
- Пользователь с
shall_create_every_day
установленным1
должен создаватьdaily_report
каждый день. - У меня есть таблица календаря
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'. Но я получаю только комбинацию созданного отчета в тот день.
Есть ли что-то, что я неправильно понял в левых / правых соединениях, возвращающих нули для несопоставленных строк?
4 ответа
Никогда не комбинируйте левые и правые соединения; это просто сбивает с толку. (Ну, большинство из нас вообще никогда не используют правые объединения, так как их сложнее читать, чем левые).
Ваш запрос делает это:
- Получить все записи календаря.
- Присоединяйтесь к записям daily_report, если они доступны, иначе создайте пустую фиктивную запись для присоединения.
- Получить все записи пользователя с must_create_every_day = 1.
- Присоедините первый набор данных к пользователям, если он доступен, иначе создайте пустую фиктивную запись для присоединения к пользователю.
- Удалите все записи с внешним соединением, созданные в (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;
Вы сформулировали свой запрос несколько запутанным образом. Я бы сформулировал это, оставив присоединение таблицы 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;
Создать серию доступно в 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
).
Можно сделать таким образом.
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
.
Похожие вопросы
Новые вопросы
sql
Язык структурированных запросов (SQL) - это язык запросов к базам данных. Вопросы должны включать примеры кода, структуру таблицы, примеры данных и тег для используемой реализации СУБД (например, MySQL, PostgreSQL, Oracle, MS SQL Server, IBM DB2 и т. Д.). Если ваш вопрос относится исключительно к конкретной СУБД (использует определенные расширения / функции), используйте вместо этого тег этой СУБД. Ответы на вопросы, помеченные SQL, должны использовать стандарт ISO / IEC SQL.