(Doctrine ORM) Как упорядочить SELECT по отношению «многие к одному»

У меня есть база данных с типы и категории (среди прочего, несвязанных вещей). Типы имеет отношение "многие к одному" с категории. Я хочу выбрать все строки типов, упорядоченные сначала по имени категории, а затем по весу типа и, наконец, по имени типа (все по возрастанию). Критическая часть заключается в том, что я хочу, чтобы все типы с одной и той же категорией были сгруппированы в результате. Будучи новичком в SQL, я думал, что простого оператора соединения, за которым следует соответствующий порядок следования операторов, будет достаточно. Я был неправ. Результат, который я получаю, не имеет для меня смысла.

Фактический результат (имя типа - название категории):

  1. Посадочная стойка самолета - Самолет
  2. Лопасть несущего винта самолета — Самолет
  3. Крыло самолета - Самолет
  4. Велосипедная рама - Ездовые циклы
  5. Лодочный лук — Гидроцикл
  6. Грузовая палуба лодки - Гидроцикл
  7. Валунное дно — Горные породы
  8. Вершина валуна - Горные породы
  9. Изогнутый элемент — Другие
  10. Кирпич 1x1 с принтом на упаковке сока - Кирпичи Печатные
  11. Кирпич 1x1 с принтом на молочном картоне - Кирпичи Печатные
  12. Держатель щетки для автомойки - Другие
  13. [так далее.]

Ожидаемый результат (имя типа - название категории):

  1. Посадочная стойка самолета - Самолет
  2. Лопасть авиационного винта — Самолет
  3. Крыло самолета - Самолет
  4. Кирпич 1x1 с принтом на упаковке сока - Кирпичи Печатные
  5. Кирпич 1x1 с принтом на молочном картоне - Кирпичи Печатные
  6. Изогнутый элемент — Другие
  7. Держатель щетки для автомойки - Другие
  8. Велосипедная рама - Ездовые циклы
  9. Валунное дно — Горные породы
  10. Вершина валуна - Горные породы
  11. Лодочный лук - Гидроцикл
  12. Грузовая палуба лодки - Гидроцикл
  13. [так далее.]

Существует более 2000 строк типов, поэтому приведенные выше списки, очевидно, очень усечены. Ошибок нет. Вероятно, также важно, чтобы результаты были разбиты на страницы (что отлично работает).

Я использую Doctrine 2.5.x с собственной системой управления контентом. В репозитории для сущности типа я использую QueryBuilder для построения запроса следующим образом (максимальный результат является глобальной настройкой, а первый результат вычисляется вне репозитория на основе текущего номера страницы):

$qb = $this->createQueryBuilder('t');

$qb->select('t, c'); // omitting this does not change the result
$qb->join('t.category', 'c');
$qb->addOrderBy('c.name', 'ASC'); // this does not work as expected
$qb->addOrderBy('t.weight', 'ASC');
$qb->addOrderBy('t.name', 'ASC');
$qb->setMaxResults(20);
$qb->setFirstResult(0);

return $qb->getQuery()->getResult();

Результирующий оператор SQL выглядит следующим образом:

SELECT b0_.ID AS ID_0, b0_.weight AS weight_1, b0_.name AS name_2, b0_.number AS number_3, b0_.name_alt AS name_alt_4, b0_.note AS note_5, b1_.ID AS ID_12, b1_.slug AS slug_13, b1_.name AS name_14, b1_.`desc` AS desc_15, b0_.cat_id AS cat_id_16 FROM types b0_ INNER JOIN categories b1_ ON b0_.cat_id = b1_.ID ORDER BY b1_.name ASC, b0_.weight ASC, b0_.name ASC LIMIT 20 OFFSET 0

Сущность типа настроена следующим образом (с опущенными нерелевантными полями):

/**
 * @Entity(repositoryClass = "Nevermind\Repository\TypeRepository")
 * @Table(name = "types", options = {"collate" = "utf8mb4_unicode_ci", "charset" = "utf8mb4"})
 */
class Type {

    /**
     * @Id
     * @Column(type = "integer", name = "ID")
     * @GeneratedValue
     */
    protected $id;

    /**
     * @ManyToOne(targetEntity = "Category", inversedBy = "types", fetch = "EAGER")
     * @JoinColumn(name = "cat_id", referencedColumnName = "ID")
     */
    protected $category;

    /**
     * @Column(type = "integer")
     */
    protected $weight = 0;

    /**
     * @Column(type = "string", unique=true)
     */
    protected $name = '';

    /**
     * @Column(type = "string", unique=true, length=12)
     */
    protected $number = '';

    /**
     * @Column(type = "string", nullable=true)
     */
    protected $name_alt;

    /**
     * @Column(type = "text", nullable=true)
     */
    protected $note;

}

И объект категории настроен следующим образом:

/**
 * @Entity(repositoryClass = "Nevermind\Repository\DefaultRepository")
 * @Table(name = "categories", options = {"collate" = "utf8mb4_unicode_ci", "charset" = "utf8mb4"})
 */
class Category {

    /**
     * @Id
     * @Column(type = "integer", name = "ID")
     * @GeneratedValue
     */
    protected $id;

    /**
     * @Column(type = "string", unique=true)
     */
    protected $slug = '';

    /**
     * @Column(type = "string", nullable=true)
     */
    protected $name;

    /**
     * @Column(type = "text", name = "`desc`", nullable=true)
     */
    protected $desc;

    /**
     * @OneToMany(targetEntity = "Type", mappedBy = "category", fetch = "EXTRA_LAZY", cascade = {"persist", "remove"})
     * @OrderBy({"weight" = "ASC", "name" = "ASC"})
     */
    protected $types;

}

TypeRepository расширяет DefaultRepository, который расширяет EntityRepository Doctrine.

Для отладки я бы echo $qb->getSqlQuery(); увидел, что фактический оператор SQL отправляется на сервер MySQL, и протестировал его. Это помогло бы нам определить причину неожиданного поведения... это проблема в тексте SQL-запроса или проблема в обработке возврата.

spencer7593 02.04.2019 22:13

Готово, хотя мне пришлось использовать $qb->getQuery()->getSQL(), так как $qb->getSqlQuery() выдает несуществующую ошибку метода.

user11301756 02.04.2019 22:31

Я помещаю SQL непосредственно в phpMyAdmin, и он выглядит точно так же, как и в HTML для моей CMS (в том числе для последующих страниц, если я изменю номер OFFSET)

user11301756 02.04.2019 22:40

Пожалуйста, отредактируйте вопрос, чтобы он включал первые несколько строк вывода оператора SQL при запуске в phpMyAdmin, т. е. включал все столбцы, а также включал первые несколько строк таблиц типов и категорий. Спасибо.

MandyShaw 02.04.2019 23:04

Простите меня, но мне кажется, что я задаю слишком много вопросов (тем более, что мой вопрос и без того очень длинный), поэтому не могли бы вы помочь мне понять, что можно получить, имея всю эту постороннюю информацию? Я надеялся, что это будет относительно распространенный вид оператора select, который должен знать типичный опытный разработчик SQL, а не поиск ошибок.

user11301756 05.04.2019 21:32

Я провел лучший эксперимент, который мог, используя ваш оператор sql и несколько строк данных, и сортировка вышла правильной. Поэтому я задался вопросом, были ли данные каким-то образом проблемой, и поэтому хотел посмотреть на ваши фактические строки. Думаю, подойдет одна строка каждой таблицы. Это вовсе не обычная проблема с SQL (как вы можете судить по количеству голосов по этому вопросу).

MandyShaw 05.04.2019 21:39

Можете попробовать $q = $qb->getQuery(); $q->setHint(Query::HINT_REFRESH, true);? Вы запрашиваете какие-либо типы перед этим кодом построителя запросов?

Jannes Botis 07.04.2019 21:39

@MandyShaw Вы были правы - это были фактические данные. Я совершенно забыл, что я изменил имя категории (при отображении), чтобы оно было получено либо из столбца имени, либо (если столбец имени равен NULL) преобразовано из столбца slug. Таким образом, изменение запроса для использования category.slug устраняет проблему., потому что столбец slug никогда не равен NULL. Спасибо, что указали мне правильное направление!

user11301756 09.04.2019 00:55

Пожалуйста, поместите решение в ответ и примите его, чтобы будущим исследователям было ясно, что произошло (комментарии могут исчезнуть со временем, а также этот вопрос все еще будет появляться как оставшийся без ответа - добавление «решено» в заголовок не является обычной практикой на ТАК). Спасибо.

MandyShaw 14.04.2019 09:59

Ой, извини. Я думаю, вы можете сказать, что я здесь новенький :/

user11301756 19.04.2019 19:43
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
4
10
130
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ты пробовал

$qb->select('t, c');
...
$qb->orderBy('c.name', 'ASC'); // orderBy not addOrderBy for the first clause
$qb->addOrderBy('t.weight', 'ASC');
$qb->addOrderBy('t.name', 'ASC');
...

У меня - не менял вывод.

user11301756 02.04.2019 23:31
Ответ принят как подходящий

Странный порядок результатов был вызван тем, что столбец категории name иногда имеет вид null (при отображении имя возвращается к преобразованному содержимому столбца slug, изменение, о котором я совершенно забыл). Поэтому простое изменение первого оператора порядка на c.slug вместо этого решает проблему. О!

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

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