Нумерация GROUP_CONCAT с несколькими столбцами группируется по

У меня проблема с выбором GROUP_CONCAT, который также должен иметь нумерацию строк, аналогичную этому вопросу нумерация GROUP_CONCAT разница в том, что мне нужно группировать по нескольким столбцам.

В качестве примера у меня есть 2 таблицы review и review_detail.
Схема (MySQL v5.5)

create table review (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `submission_id` int(11) NOT NULL,
   PRIMARY KEY (`id`)
);

create table review_detail (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `review_id` int(11),
  `category_id` int(11),
  `rating` varchar(100),
  PRIMARY KEY (`id`)
);

insert into review (`id`, `submission_id`) values (1, 1), (2, 1), (3, 2), (4, 3), (5,1), (6,3), (7,2), (8,3);

insert into review_detail (`review_id`, `category_id`, `rating`)
values 
(1, 1, ' submission 1.1 cat 1'), (1, 2, ' submission 1.1 cat 2'),
(2, 1, ' submission 1.2 cat 1'), (2, 2, ' submission 1.2 cat 2'),
(3, 1, ' submission 2.1 cat 1'), (3, 2, ' submission 2.1 cat 2'),
(4, 1, ' submission 3.1 cat 1'), (4, 2, ' submission 3.1 cat 1'),
(5, 1, ' submission 1.3 cat 1'), (5, 2, ' submission 1.3 cat 2'),
(6, 1, ' submission 3.2 cat 1'), (6, 2, ' submission 3.2 cat 2'),
(7, 1, ' submission 2.2 cat 1'), (7, 2, ' submission 2.2 cat 2'),
(8, 1, ' submission 3.3 cat 1'), (6, 2, ' submission 3.3 cat 2')
;

Запрос №1

SELECT * FROM review;

| id  | submission_id |
| --- | ------------- |
| 1   | 1             |
| 2   | 1             |
| 3   | 2             |
| 4   | 3             |
| 5   | 1             |
| 6   | 3             |
| 7   | 2             |
| 8   | 3             |

Запрос №2

SELECT * FROM review_detail;

| id  | review_id | category_id | rating                |
| --- | --------- | ----------- | --------------------- |
| 1   | 1         | 1           |  submission 1.1 cat 1 |
| 2   | 1         | 2           |  submission 1.1 cat 2 |
| 3   | 2         | 1           |  submission 1.2 cat 1 |
| 4   | 2         | 2           |  submission 1.2 cat 2 |
| 5   | 3         | 1           |  submission 2.1 cat 1 |
| 6   | 3         | 2           |  submission 2.1 cat 2 |
| 7   | 4         | 1           |  submission 3.1 cat 1 |
| 8   | 4         | 2           |  submission 3.1 cat 1 |
| 9   | 5         | 1           |  submission 1.3 cat 1 |
| 10  | 5         | 2           |  submission 1.3 cat 2 |
| 11  | 6         | 1           |  submission 3.2 cat 1 |
| 12  | 6         | 2           |  submission 3.2 cat 2 |
| 13  | 7         | 1           |  submission 2.2 cat 1 |
| 14  | 7         | 2           |  submission 2.2 cat 2 |
| 15  | 8         | 1           |  submission 3.3 cat 1 |
| 16  | 6         | 2           |  submission 3.3 cat 2 |

Каждый отзыв для отправки (внешний ключ = submission_id) имеет несколько записей review_detail с category_id (в моем примере только 2 категории (1,2), которые не имеют отношения к запросу).

Мне нужно создать выбор, в котором я получаю GROUP_CONCAT, сгруппированные по submission_id и category_id.

Строка Concat должна возвращать
Reviewer 1: {rating}, Reviewer 2: {rating}, Reviewer 3: {rating} etc..

например для submission_id = 1 и category_id = 1 группа concat должна возвращать
Reviewer 1: submission 1.1 cat 1, Reviewer 2: submission 1.2 cat 1, Reviewer 3: submission 1.3 cat 1.

Но я не мог правильно указать нумерацию в группе concat.

Я сделал несколько тестов.

Группа только с одним счетчиком столбца (работает):
https://www.db-fiddle.com/f/6hA4Vft1mQGdw2Pew2An2T/3
Reviewer 1: submission 1.1 cat 1 of review 1 / Reviewer 2: submission 3.3 cat 1 of review 8 / Reviewer 3: submission 2.2 cat 1 of review 7 / Reviewer 4: submission 3.2 cat 1 of review 6 / ... etc.

SELECT
    --review.submission_id,
    review_detail.category_id,
    @i,
    GROUP_CONCAT(
        CONCAT(
            'Reviewer ',
            @i := @i + 1,
            ': ',
            rating,
            ' of review ',  review_id
        )
    SEPARATOR ' / '
    ) concatText,
    @i := 0
FROM
    review_detail
LEFT JOIN review ON review.id = review_detail.review_id,
    (
SELECT
    @i := 0
) init
GROUP BY
    review_detail.category_id
ORDER BY
    review_detail.category_id ASC
;

Протестируйте с помощью if и сравните строку из двух сгруппированных столбцов (не работает):
https://www.db-fiddle.com/f/3woAVSw5hrav15jAmuWVdT/3
Reviewer 1: submission 1.1 cat 1 of review 1 / Reviewer 1: submission 1.2 cat 1 of review 2 / Reviewer 1: submission 1.3 cat 1 of review 5

SELECT
    submission_id,
    category_id,
    @i,
    @grp,
    CONCAT_WS("-", submission_id, category_id) AS catgroup,
    GROUP_CONCAT(
        CONCAT(
            'Reviewer ',
            @i := IF(
                @grp = CONCAT_WS("-", submission_id, category_id),
                @i + 1,
                IF(
                    @grp := CONCAT_WS("-", submission_id, category_id),
                    1,
                    1
                )
            ),
            ': ',
            rating,
            ' of review ',  review_id
        )
    ORDER BY review_id, submission_id, category_id 
    SEPARATOR ' / '
    ) concatText
FROM
    review_detail
LEFT JOIN review ON review.id = review_detail.review_id,
    (
SELECT
    @i := 0,
    @grp := ''
) init
GROUP BY
    review.submission_id,
    review_detail.category_id

Так кто-нибудь знает способ получить правильную нумерацию в вызове GROUP_CONCAT, когда несколько столбцов сгруппированы?


person BHoft    schedule 14.10.2020    source источник
comment
Обновитесь до 8.0 или MariaDB 10.2, чтобы получить ROW_NUMBER().   -  person Rick James    schedule 17.10.2020
comment
спасибо всем за ваши решения. Каждое решение, упомянутое ниже, работает, поэтому мне сложно назначить вознаграждение за конкретное решение. Надеюсь, вас не раздражает, если я выберу какое-нибудь другое решение. Я очень ценю все решения, представленные ниже.   -  person BHoft    schedule 20.10.2020


Ответы (4)


Вам следует избегать использования таких пользовательских переменных в производственном коде.

В руководстве для MySQL 5.6 говорится:

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

И даже в документации для 8.0 говорится:

Порядок оценки выражений, включающих пользовательские переменные, не определен. Например, нет гарантии, что SELECT @a, @a:=@a+1 сначала оценит @a, а затем выполнит присвоение.

В будущих выпусках это может больше не работать:

Предыдущие выпуски MySQL позволяли присваивать значение пользовательской переменной в операторах, отличных от SET. Эта функция поддерживается в MySQL 8.0 для обратной совместимости, но может быть удалена в будущих версиях MySQL.

Итак, вот решение без пользовательских переменных:

SELECT 
r.submission_id,
rd.category_id,
GROUP_CONCAT(CONCAT('Reviewer ', (SELECT COUNT(*) + 1 
                                  FROM review 
                                  JOIN review_detail ON review.id = review_detail.review_id 
                                  WHERE r.submission_id = review.submission_id 
                                  AND review_detail.category_id = rd.category_id 
                                  AND review_detail.id < rd.id
                                 ), ': ', rating, ' of review ', review_id) ORDER BY rating SEPARATOR ' / ') AS shorter_column_name
FROM 
review r 
JOIN review_detail rd ON rd.review_id = r.id
GROUP BY r.submission_id, rd.category_id;

который возвращается

+---------------+-------------+-----------------------------------------------------------------------------------------------------------------------------------------------+
| submission_id | category_id | shorter_column_name                                                                                                                           |
+---------------+-------------+-----------------------------------------------------------------------------------------------------------------------------------------------+
|             1 |           1 | Reviewer 1:  submission 1.1 cat 1 of review 1 / Reviewer 2:  submission 1.2 cat 1 of review 2 / Reviewer 3:  submission 1.3 cat 1 of review 5 |
|             1 |           2 | Reviewer 1:  submission 1.1 cat 2 of review 1 / Reviewer 2:  submission 1.2 cat 2 of review 2 / Reviewer 3:  submission 1.3 cat 2 of review 5 |
|             2 |           1 | Reviewer 1:  submission 2.1 cat 1 of review 3 / Reviewer 2:  submission 2.2 cat 1 of review 7                                                 |
|             2 |           2 | Reviewer 1:  submission 2.1 cat 2 of review 3 / Reviewer 2:  submission 2.2 cat 2 of review 7                                                 |
|             3 |           1 | Reviewer 1:  submission 3.1 cat 1 of review 4 / Reviewer 2:  submission 3.2 cat 1 of review 6 / Reviewer 3:  submission 3.3 cat 1 of review 8 |
|             3 |           2 | Reviewer 1:  submission 3.1 cat 1 of review 4 / Reviewer 2:  submission 3.2 cat 2 of review 6 / Reviewer 3:  submission 3.3 cat 2 of review 6 |
+---------------+-------------+-----------------------------------------------------------------------------------------------------------------------------------------------+
person fancyPants    schedule 16.10.2020
comment
Кстати, в производственном коде я бы все равно не использовал переменные. Они не на 100% безопасны в использовании. В руководстве указано, что вы не должны устанавливать и читать переменные в одном операторе. - person fancyPants; 16.10.2020
comment
это не ошибка, db-fiddle.com/f/3woAVSw5hrav15jAmuWVdT/4 проблема в сортировке, это можно сделать как подзапрос - person nbk; 16.10.2020
comment
Что ж, в более новых версиях MySQL ORDER BY в подзапросах оптимизированы, поскольку они не нужны. - person fancyPants; 16.10.2020
comment
не читайте мой ответ, там все объяснено, как mysql работает с порядком By и подзапросом., и вы можете увидеть в примере, который я опубликовал, как вы можете воплотить его в жизнь. - person nbk; 16.10.2020
comment
спасибо за ваше решение. Я не уверен, действительно ли в руководстве указано, что переменные не должны устанавливаться и считываться в одном и том же выражении. Он просто говорит, что переменные должны быть определены до их использования. Но мне нравится, что ваше решение работает без переменных, и из-за этого (и ваш ответ тоже был первым) я принимаю ваш ответ. - person BHoft; 20.10.2020
comment
@BHoft Спасибо и добро пожаловать. Я обновил свой ответ, включив в него несколько цитат из руководства, почему вы не должны использовать пользовательские переменные в подобных запросах. - person fancyPants; 20.10.2020

чтобы исправить ваш запрос.

Основная проблема заключается в том, что таблицы по своей природе не отсортированы, поэтому оптимизатор MySQL удаляет файл ORDER BY.

В MySQL достаточно поместить все таблицы в предложение FROM, чтобы сделать подзапрос с порядком, mysql сохранит его.

В Мариадбе этого недостаточно. Вы также добавили LIMIT 18446744073709551615, чтобы оптимизатор сохранил его.

Схема (MySQL v5.5)

Запрос №1

SELECT
    submission_id,
    category_id,
    @i,
    @grp,
    CONCAT_WS("-", submission_id, category_id) AS catgroup,
    GROUP_CONCAT(
        CONCAT(
            'Reviewer ',
            @i := IF(
                @grp = CONCAT_WS("-", submission_id, category_id),
                @i := @i + 1,
                IF(
                    @grp := CONCAT_WS("-", submission_id, category_id),
                    1,
                    1
                )
            ),
            ': ',
            rating,
            ' of review ',  review_id
        )
    ORDER BY review_id, submission_id, category_id 
    SEPARATOR ' / '
    ) concatText
FROM
    (SELECT review_id, submission_id, category_id,`rating` FROM review_detail
LEFT JOIN review ON review.id = review_detail.review_id
     ORDER BY review_id, submission_id, category_id ) t1,
    (
SELECT
    @i := 0,
    @grp := ''
) init


GROUP BY
    submission_id,
    category_id;

Результат

| submission_id | category_id | @i  | @grp | catgroup | concatText                                                                                                                                    |
| ------------- | ----------- | --- | ---- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| 1             | 1           | 0   |      | 1-1      | Reviewer 3:  submission 1.1 cat 1 of review 1 / Reviewer 2:  submission 1.2 cat 1 of review 2 / Reviewer 1:  submission 1.3 cat 1 of review 5 |
| 1             | 2           | 3   | 1-1  | 1-2      | Reviewer 3:  submission 1.1 cat 2 of review 1 / Reviewer 2:  submission 1.2 cat 2 of review 2 / Reviewer 1:  submission 1.3 cat 2 of review 5 |
| 2             | 1           | 3   | 1-2  | 2-1      | Reviewer 1:  submission 2.1 cat 1 of review 3 / Reviewer 2:  submission 2.2 cat 1 of review 7                                                 |
| 2             | 2           | 2   | 2-1  | 2-2      | Reviewer 2:  submission 2.1 cat 2 of review 3 / Reviewer 1:  submission 2.2 cat 2 of review 7                                                 |
| 3             | 1           | 2   | 2-2  | 3-1      | Reviewer 2:  submission 3.1 cat 1 of review 4 / Reviewer 1:  submission 3.2 cat 1 of review 6 / Reviewer 3:  submission 3.3 cat 1 of review 8 |
| 3             | 2           | 3   | 3-1  | 3-2      | Reviewer 3:  submission 3.1 cat 1 of review 4 / Reviewer 2:  submission 3.3 cat 2 of review 6 / Reviewer 1:  submission 3.2 cat 2 of review 6 |

Просмотр в DB Fiddle

person nbk    schedule 16.10.2020
comment
спасибо за ваше решение. Я уже думал, что причиной этого является недостающий порядок данных таблицы. Я проверил вашу скрипку, но если дает разные результаты для версий mysql 5.5 + 5.7 против 5.6 + 8. Нумерация также обратная в 5.5, 5.7 Reviewer 3,2,1 вместо 1,2,3. Но из-за того, что результат отличается в разных версиях mysql, я бы не стал использовать ваше решение. - person BHoft; 20.10.2020

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

SET @i := 0;
SET @grp := '';
SELECT
    submission_id,
    category_id,
    GROUP_CONCAT(
      CONCAT(
        'Reviewer ',
        i,
        ': ',
        rating,
        ' of review ',  review_id
      )
      ORDER BY i
      SEPARATOR ' / '
    ) concatText
FROM
-- second, add numbering
(
  SELECT *,
    @i := IF(
      @grp = @grp := CONCAT_WS('-',submission_id,category_id),
      @i + 1, 1) i
  FROM
  -- first, sort for numbering
  (
    SELECT
        review_id,
        submission_id,
        category_id,
        rating
    FROM review_detail LEFT JOIN review ON review.id = review_detail.review_id
    ORDER BY
        submission_id,
        category_id,
        review_id
  ) t1
) t2
GROUP BY
    submission_id,
    category_id
;

скрипт базы данных

person etsuhisa    schedule 17.10.2020
comment
спасибо за ваше решение. ваша скрипка делает именно то, что было запрошено, и работает во всех версиях mysql. - person BHoft; 20.10.2020
comment
Но я принял другой ответ, потому что он не использует переменные. Для чего назначение может быть изменено в будущих версиях MySQL, если я правильно понимаю руководство. Надеюсь, вы понимаете мое решение. из руководства mysql 5,7: также можно присвоить значение пользовательской переменной в операторах, отличных от SET. (Эта функция устарела в MySQL 8.0 и будет удалена в следующем выпуске.) - person BHoft; 20.10.2020

Для полноты картины я также добавляю решение, как это можно сделать в Mysql 8.0.

Он работает как с COUNT (*)

with base as (
    
  SELECT
    review_id,
    submission_id,
    category_id,
    rating,
    count(*) over (partition by submission_id,category_id  order by review_id) num
  
    FROM review_detail LEFT JOIN review ON review.id = review_detail.review_id
    ORDER BY
        submission_id,
        category_id,
        review_id
)
select   
  submission_id,
         category_id,
         group_concat(concat('Reviewer', num, ': ', rating, ' of review ',  review_id ) separator ', ') concattext
from     base
group by 
submission_id,
category_id
;

ИЛИ ROW_NUMBER ()

with base as (
        SELECT
            review_id,
            submission_id,
            category_id,
            rating,
            ROW_NUMBER() over (partition by submission_id,category_id  order by review_id) num
        FROM review_detail 
        LEFT JOIN review ON review.id = review_detail.review_id
        ORDER BY
            submission_id,
            category_id,
            review_id
    )
    SELECT   
        submission_id,
        category_id,
        group_concat(concat('Reviewer', num, ': ', rating, ' of review ',  review_id  ) separator ', ') concattext
    from base
    group by 
        submission_id,
        category_id
;

DB Fiddle

person BHoft    schedule 20.10.2020