Расстановка приоритетов по значениям внутри группы - PullRequest
0 голосов
/ 28 февраля 2020

В MySQL, скажем, у меня есть следующая таблица (называемая workers):

| id | specialty | status     | name
| :- | :-------- | :--------- | :--- |
| 1  | Bricks    | Unemployed | Joe
| 2  | Bricks    | Employed   | Eric
| 3  | Bricks    | Contracted | Bob
| 4  | Tiles     | Employed   | Dylan
| 5  | Tiles     | Contracted | James

В моем запросе, скажем, я хочу найти потенциального человека на новую работу. Таким образом, я хотел бы сначала найти, кто является Unemployed, если никто не является Unemployed, то кто является только Contracted, и если никто не является Contracted, то, по крайней мере, кто является Employed.

Это будет GROUP BY specialty. Единственные методы, которые я мог выяснить, это либо сложные подзапросы, либо наборы UNION s (или оба). Я также попытался GROUP_CONCAT, однако это не сработало (или я сделал это неправильно). Поиск в Google не дал никаких результатов.

Другая идея - присвоить значение каждой категории, а затем выполнить подзапрос group-wise max/min. Я пилотировал это, и это работает, однако кажется довольно грязным и определенно не нормализованным:

SELECT
    `id`,
    `name`,
    `status`,
    -- I haven't been able to figure out how to get rid of MIN from the actual select
    -- statement except by wrapping this in another sub-query, which I'm not keen on
    MIN(`priority`) AS `priority`
FROM workers
INNER JOIN (
    SELECT 'Unemployed' AS `status`, 0 AS `priority` FROM dual UNION
    SELECT 'Contracted' AS `status`, 1 AS `priority` FROM dual UNION
    SELECT 'Employed'   AS `status`, 2 AS `priority` FROM dual
) AS priorities USING (`status`)
GROUP BY `specialty`;

Я ищу более стандартный, эффективный, нормализованный или универсальный способ сделать это.

Обновление :

Дополнительным методом, который я мог бы использовать, является выражение CASE в предложении SELECT оператора. Это было бы, если бы мне пришлось нормализовать столбец status, используя отношение внешнего ключа или другую связанную таблицу:

Новая таблица с именем statuses

| id | status         |
| :- | :------------- | 
| 1  | Employed       |
| 2  | Contracted     |
| 3  | Unemployed     |
| 4  | Not contracted |

Различия: 'Not Contracted' - это новый статус, и моя таблица workers теперь сохраняет внешний ключ для новой таблицы статусов.

Тогда мой SQL будет:

SELECT
    `id`,
    `name`,
    statuses.status,
    MIN(`priority`) AS `priority`
FROM workers
INNER JOIN (
    SELECT
        `id`,
        `status`,
        CASE
            -- currently uses text in `status`,
            -- could also explicitly use `id`
            WHEN `status` IN ('Unemployed', 'Not Contracted') THEN 0
            WHEN `status` = 'Contracted' THEN 1
            WHEN `status` = 'Employed' THEN 2
            ELSE 3
        END AS `priority`
    FROM statuses
) AS statuses ON workers.status = statuses.id
GROUP BY `specialty`;

Примечание: Вы можете подумать - почему бы не поместить priority в таблицу статусов? Причина, по которой я этого не делаю, заключается в том, что приоритет меняется в зависимости от необходимых данных / цели создаваемого отчета.

Потенциально это более чистое решение (для тех времен, когда связанные данные расставить приоритеты против находится в другой таблице). Опять же, я ищу более стандартный, эффективный, нормализованный или универсальный способ сделать это. Кроме того, если есть больше способов, это можно настроить для пользовательского ввода / переменных.

1 Ответ

0 голосов
/ 28 февраля 2020

Трудность здесь в основном возникает из-за того, что у вас нет порядкового столбца, который ранжирует различные статусы в некотором порядке. В отсутствие этого мы можем ввести выражение, используя выражение CASE, аналогичное тому, что пытается сделать ваш второй запрос:

SELECT w1.*
FROM workers w1
INNER JOIN
(
    SELECT
        specialty,
        MIN(CASE status WHEN 'Unemployed' THEN 1
                        WHEN 'Contracted' THEN 2
                        ELSE 3 END) AS status_rnk
    FROM workers
    GROUP BY specialty
) w2
    ON w1.specialty = w2.specialty AND
       w2.status_rnk = CASE w1.status WHEN 'Unemployed' THEN 1
                                      WHEN 'Contracted' THEN 2
                                      ELSE 3 END;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...