MySQL выбирает записи, когда они истинны, и заменяет их нулем, когда они ложны

Я программирую офисный график.

У меня есть таблица 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?

Предоставьте образцы данных для всех задействованных таблиц: users, hleave, teams, hreg, leave_codes и соответствующий ожидаемый результат. Как бы то ни было, трудно связать ваш запрос с ожидаемыми результатами.

GMB 04.03.2019 00:37

Я включил образец результатов

turboisturbo 04.03.2019 00:52

Пожалуйста, предоставьте схему и, желательно, несколько примеров данных для каждой из таблиц. Судя по всему, кажется, что вы можете сделать это более сложным, чем должно быть.

Will B. 04.03.2019 01:08

Я включил все таблицы с данными

turboisturbo 04.03.2019 01:24

См. meta.stackoverflow.com/questions/333952/… — хотя я бы заметил, что обычный тест на перекрытия — это когда одно событие начинается до окончания другого и заканчивается после начала другого.

Strawberry 04.03.2019 01:43
Освоение архитектуры микросервисов с Laravel: Лучшие практики, преимущества и советы для разработчиков
Освоение архитектуры микросервисов с Laravel: Лучшие практики, преимущества и советы для разработчиков
В последние годы архитектура микросервисов приобрела популярность как способ построения масштабируемых и гибких приложений. Laravel , популярный PHP...
Как построить CRUD-приложение в Laravel
Как построить CRUD-приложение в Laravel
Laravel - это популярный PHP-фреймворк, который позволяет быстро и легко создавать веб-приложения. Одной из наиболее распространенных задач в...
Освоение PHP и управление базами данных: Создание собственной СУБД - часть II
Освоение PHP и управление базами данных: Создание собственной СУБД - часть II
В предыдущем посте мы создали функциональность вставки и чтения для нашей динамической СУБД. В этом посте мы собираемся реализовать функции обновления...
Документирование API с помощью Swagger на Springboot
Документирование API с помощью Swagger на Springboot
В предыдущей статье мы уже узнали, как создать Rest API с помощью Springboot и MySql .
Роли и разрешения пользователей без пакета Laravel 9
Роли и разрешения пользователей без пакета Laravel 9
Этот пост изначально был опубликован на techsolutionstuff.com .
Как установить LAMP Stack - Security 5/5 на виртуальную машину Azure Linux VM
Как установить LAMP Stack - Security 5/5 на виртуальную машину Azure Linux VM
В предыдущей статье мы завершили установку базы данных, для тех, кто не знает.
0
5
78
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Как я отметил в своем комментарии, кажется, что вы делаете запрос немного сложнее, чем нужно.

Основная проблема заключается в 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. Спасибо.

turboisturbo 04.03.2019 04:15

Вот подход к решению вашего вопроса.

Для начала давайте объединим 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 04.03.2019 04:24

@turboisturbo: мой ответ даст описанный вами вывод с по одной колонке на каждый день недели. Насколько мне известно, запрос Фрайри этого не делает.

GMB 04.03.2019 04:38

@turboisturbo : относительно статуса 'not working': для сотрудника, график работы которого начинается во вторник (т.е. hreg.weekStart = 2), что вы хотите отобразить в столбце Monday?

GMB 04.03.2019 04:41

Это учитывается с помощью 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 04.03.2019 04:49

@turboisturbo: хорошо, я вижу, в SQL та же логика (так что меньше работы для уровня представления PHP).

GMB 04.03.2019 04:56

@turboisturbo, если вы можете адаптировать этот запрос для своих целей, лучше всего получать результаты запроса с точки зрения отчетности, поскольку это цель SQL. Кроме того, улучшится производительность на стороне приложения, особенно с помощью методов кэширования запросов. Я писал то же самое, когда GMB отправил свой ответ, поэтому отправил свой.

Will B. 04.03.2019 05:54

Другие вопросы по теме