Я программирую офисный график.
У меня есть таблица users, в которой у каждого пользователя есть обычное расписание hreg с понедельника по пятницу.
У меня есть таблица hleave, в которую я ввожу код отпуска для дат, когда пользователь отсутствует на рабочем месте.
Таблица hleave:
hleave_id user dateStart dateEnd leaveCode
int(11)PK int(11)FK date date int(11)FK
1 5 2019-02-11 2019-02-13 1
2 1 2019-02-28 2019-02-28 1
3 3 2019-02-26 2019-02-28 2
Таблица users:
user_id firstName lastName link_team_id link_hreg_id
int(11)PK varchar varchar int(11)FK int(11)FK
1 Bob Smith 1 1
2 Alice Fraser 1 1
3 Jenny Summers 1 1
4 Carl Raisman 1 1
5 Roger Wayne 1 1
Таблица teams:
team_id teamName
int(11)PK varchar
1 team 1
2 team 2
3 team 3
Таблица hreg:
hreg_id weekStart weekEnd
int(11)PK int(11) int(11)
1 1 5
2 1 4
3 2 5
Таблица leave_codes:
code_id codeName
int(11)PK varchar
1 A
2 B
3 C
4 D
Если есть запись hleave с диапазоном дат, который либо начинается на этой неделе, либо заканчивается на этой неделе, либо начинается до этой недели и заканчивается после этой недели, я хочу отобразить код выхода.
Если нет, я хочу отобразить обычное расписание пользователя «рабочее». Например:
Monday Tuesday Wednesday Thursday Friday
BOB working working working A working
ALICE working working working working working
JENNY working B B B working
CARL working working working working working
Вот мой запрос для КОМАНДЫ №1 (для ясности я заменил переменные $monday и $friday реальными датами):
SELECT
users.firstName,
users.lastName,
users.link_team_id,
users.link_hreg_id,
hreg.weekStart,
hreg.weekEnd,
leave_codes.codeName,
hleave.dateStart,
hleave.dateEnd
FROM users
LEFT JOIN hleave ON hleave.user = users.user_id
JOIN teams ON users.link_team_id = teams.team_id
JOIN hreg ON users.link_hreg_id = hreg.hreg_id
LEFT JOIN leave_codes ON hleave.leaveCode = leave_codes.code_id
WHERE
(CASE
WHEN (hleave.dateStart BETWEEN '2019-02-25' AND '2019-03-01')
THEN (users.link_team_id = 1)
WHEN (hleave.dateEnd BETWEEN '2019-02-25' AND '2019-03-01')
THEN (users.link_team_id = 1)
WHEN (hleave.dateStart < '2019-02-25' AND hleave.dateEnd > '2019-03-01')
THEN (users.link_team_id = 1)
WHEN (hleave.dateStart IS NULL AND hleave.dateEnd IS NULL)
THEN (users.link_team_id = 1)
END)
Результаты:
firstName lastName link_team_id link_hreg_id weekStart weekEnd codeName dateStart dateEnd
Bob Smith 1 1 1 5 A 2019-02-28 2019-02-28
Alice Fraser 1 1 1 5 NULL NULL NULL
Jenny Summers 1 1 1 5 B 2019-02-26 2019-02-28
Carl Raisman 1 1 1 5 NULL NULL NULL
Этот запрос работает. Я получаю codeName, dateStart и dateEnd для пользователя, если это произойдет на этой неделе. Если dateStart и dateEnd равны NULL, я все равно получаю пользователя firstName, lastName и hreg, чтобы отображать их на веб-сайте как работающие.
Пока все хорошо, но у меня есть проблема. Когда dateStart < '2019-02-25' и dateEnd < '2019-03-01' (т. е. если отпуск произошел до этой недели), я ничего не получаю для этого пользователя, поскольку диапазон дат проверяется как ЛОЖЬ, и он также не равен NULL. Например, если у Роджера был отпуск с 11 февраля 2019 г. по 13 февраля 2019 г., Роджер не включается в набор результатов.
То же самое касается диапазона дат в будущем: dateStart > '2019-02-25' и dateEnd> '2019-03-01'.
Каков наилучший способ добиться этого? Я попробовал CASE WHEN в предложении SELECT, чтобы указать даты, которые я хочу выбрать, но результат тот же. Мне нужно либо выбрать по-другому, либо превратить записи, проверяемые как FALSE, на NULL?
Я включил образец результатов
Пожалуйста, предоставьте схему и, желательно, несколько примеров данных для каждой из таблиц. Судя по всему, кажется, что вы можете сделать это более сложным, чем должно быть.
Я включил все таблицы с данными
См. meta.stackoverflow.com/questions/333952/… — хотя я бы заметил, что обычный тест на перекрытия — это когда одно событие начинается до окончания другого и заканчивается после начала другого.






Как я отметил в своем комментарии, кажется, что вы делаете запрос немного сложнее, чем нужно.
Основная проблема заключается в CASE/WHEN в ваших WHERE критериях, ограничивающих users теми, которые соответствуют CASE/WHEN. Поскольку Роджер был в отпуске, но не соответствовал указанным датам в WHERE критериях, он был исключен.
Поскольку вас интересует только конкретная команда, группу пользователей можно отфильтровать непосредственно в вашем основном запросе. Предполагая, что hleave содержит запись только тогда, когда user находится в отпуске для запрашиваемого диапазона дат, условие можно переместить из WHERE в LEFT JOIN, отображая NULL, когда пользователь не в отпуске.
SELECT
users.firstName,
users.lastName,
users.link_team_id,
users.link_hreg_id,
hreg.weekStart,
hreg.weekEnd,
leave_codes.codeName,
hleave.dateStart,
hleave.dateEnd
FROM users
JOIN teams
ON users.link_team_id = teams.team_id
JOIN hreg
ON users.link_hreg_id = hreg.hreg_id
LEFT JOIN hleave
ON hleave.user = users.user_id
AND (
hleave.dateStart BETWEEN '2019-02-25' AND '2019-03-01' #leave starts this week
OR
hleave.dateEnd BETWEEN '2019-02-25' AND '2019-03-01' #leave ends this week
OR
(hleave.dateStart < '2019-02-25' AND hleave.dateEnd > '2019-03-01') #leave started before this week and continues after
)
LEFT JOIN leave_codes
ON hleave.leaveCode = leave_codes.code_id
WHERE users.link_team_id = 1
Результат:
<table>
<thead>
<tr>
<th class = "col0">firstName</th>
<th class = "col1">lastName</th>
<th class = "col2">link_team_id</th>
<th class = "col3">link_hreg_id</th>
<th class = "col4">weekStart</th>
<th class = "col5">weekEnd</th>
<th class = "col6">codeName</th>
<th class = "col7">dateStart</th>
<th class = "col8">dateEnd</th>
</tr>
</thead>
<tbody>
<tr>
<td class = "col0">Bob</td>
<td class = "col1">Smith</td>
<td class = "col2">1</td>
<td class = "col3">1</td>
<td class = "col4">1</td>
<td class = "col5">5</td>
<td class = "col6">A</td>
<td class = "col7">2019-02-28</td>
<td class = "col8">2019-02-28</td>
</tr>
<tr>
<td class = "col0">Jenny</td>
<td class = "col1">Summers</td>
<td class = "col2">1</td>
<td class = "col3">1</td>
<td class = "col4">1</td>
<td class = "col5">5</td>
<td class = "col6">B</td>
<td class = "col7">2019-02-26</td>
<td class = "col8">2019-02-28</td>
</tr>
<tr>
<td class = "col0">Alice</td>
<td class = "col1">Fraser</td>
<td class = "col2">1</td>
<td class = "col3">1</td>
<td class = "col4">1</td>
<td class = "col5">5</td>
<td class = "col6"></td>
<td class = "col7"></td>
<td class = "col8"></td>
</tr>
<tr>
<td class = "col0">Carl</td>
<td class = "col1">Raisman</td>
<td class = "col2">1</td>
<td class = "col3">1</td>
<td class = "col4">1</td>
<td class = "col5">5</td>
<td class = "col6"></td>
<td class = "col7"></td>
<td class = "col8"></td>
</tr>
<tr>
<td class = "col0">Roger</td>
<td class = "col1">Wayne</td>
<td class = "col2">1</td>
<td class = "col3">1</td>
<td class = "col4">1</td>
<td class = "col5">5</td>
<td class = "col6"></td>
<td class = "col7"></td>
<td class = "col8"></td>
</tr>
</tbody>
</table>Я проверил это, и это работает, как задумано! Я знал, что был не так уж далек, но никогда бы не подумал об условиях LEFT JOIN. Спасибо.
Вот подход к решению вашего вопроса.
Для начала давайте объединим 5 таблиц. Хитрость здесь заключается в LEFT JOIN таблице hleaves, проверяя, является ли интервал выхода перекрывается фильтруемой неделей; классический способ — использовать такое выражение, как: l.dateStart < @weekEnd AND l.dateEnd < @weekStart:
SELECT *
FROM
users u
INNER JOIN teams t ON t.team_id = u.link_team_id
INNER JOIN hreg r ON r.hreg_id = u.link_hreg_id
LEFT JOIN hleave l ON l.user = u.user_id AND l.dateStart < @weekEnd AND l.dateEnd < @weekStart
LEFT JOIN leave_codes lc ON lc.code_id = l.leaveCode
Имея этот набор данных, мы теперь можем использовать условная агрегация для получения желаемых результатов. Например, чтобы проверить статус во вторник, мы должны сделать:
MAX(
CASE
-- employee on leave
WHEN DATE_ADD(@weekStart, INTERVAL 1 DAY) BETWEEN l.dateStart AND l.dateEnd THEN lc.codeName
-- employee on schedule for that day
WHEN 2 BETWEEN h.weekStart AND h.weekEnd THEN 'working'
-- employee not on schedule for that day
ELSE 'not working'
END
) AS Tuesday
Вы можете повторять это выражение для каждого дня недели, увеличивая счетчики. Вот запрос с понедельника по среду:
SELECT
u.firstName,
MAX(
CASE
WHEN @weekStart BETWEEN l.dateStart AND l.dateEnd THEN lc.codeName
WHEN 2 BETWEEN h.weekStart AND h.weekEnd THEN 'working'
ELSE 'not working'
END
) AS Monday,
MAX(
CASE
WHEN DATE_ADD(@weekStart, INTERVAL 1 DAY) BETWEEN l.dateStart AND l.dateEnd THEN lc.codeName
WHEN 2 BETWEEN h.weekStart AND h.weekEnd THEN 'working'
ELSE 'not working'
END
) AS Tuesday,
MAX(
CASE
WHEN DATE_ADD(@weekStart, INTERVAL 2 DAY) BETWEEN l.dateStart AND l.dateEnd THEN lc.codeName
WHEN 3 BETWEEN h.weekStart AND h.weekEnd THEN 'working'
ELSE 'not working'
END
) AS Wednesday
-- other days go here
FROM
users u
INNER JOIN teams t ON t.team_id = u.link_team_id
INNER JOIN hreg r ON r.hreg_id = u.link_hreg_id
LEFT JOIN hleave l ON l.user = u.user_id AND l.dateStart < @weekEnd AND l.dateEnd < @weekStart
LEFT JOIN leave_codes lc ON lc.code_id = l.leaveCode
GROUP BY
u.user_id,
u.firstName
Дело в том, что я уже написал весь php-код для отображения либо выходного кода, либо «работы» для результатов. Ваш ответ кажется очень профессиональным, и я попытаюсь его понять, но решение, предоставленное fyrye, мне легче воспроизвести и в конечном итоге отладить. Можете ли вы рассказать мне о преимуществах этого решения по сравнению с Fyrye?
@turboisturbo: мой ответ даст описанный вами вывод с по одной колонке на каждый день недели. Насколько мне известно, запрос Фрайри этого не делает.
@turboisturbo : относительно статуса 'not working': для сотрудника, график работы которого начинается во вторник (т.е. hreg.weekStart = 2), что вы хотите отобразить в столбце Monday?
Это учитывается с помощью php, когда я анализирую результаты: if ($mondayFull >= $dateStart AND $mondayFull <= $dateEnd){ echo '<td><span style = "color: var(--'.$codeColor.')">'.$codeName.'</span></td>'; } else if ($mondayDay >= $weekStart AND $mondayDay <= $weekEnd){ echo '<td>bureau</td>'; } else { echo '<td> - </td>'; }
@turboisturbo: хорошо, я вижу, в SQL та же логика (так что меньше работы для уровня представления PHP).
@turboisturbo, если вы можете адаптировать этот запрос для своих целей, лучше всего получать результаты запроса с точки зрения отчетности, поскольку это цель SQL. Кроме того, улучшится производительность на стороне приложения, особенно с помощью методов кэширования запросов. Я писал то же самое, когда GMB отправил свой ответ, поэтому отправил свой.
Предоставьте образцы данных для всех задействованных таблиц:
users,hleave,teams,hreg,leave_codesи соответствующий ожидаемый результат. Как бы то ни было, трудно связать ваш запрос с ожидаемыми результатами.