【问题标题】:Doctrine EAV orderBy and searchDoctrine EAV orderBy 和 search
【发布时间】:2021-12-19 12:31:40
【问题描述】:

我正在使用 Symfony 5 和 Doctrine 2.10 My Book 实体没有直接属性,所有字段都是通过 Entity-Attribute-Value 模式生成的,如下所示:

书:

#[ORM\OneToMany(mappedBy: 'book', targetEntity: BookAttributeValue::class)]
private Collection $bookAttributeValues;

BookAttributeValue:

#[ORM\ManyToOne(targetEntity: Book::class, inversedBy: 'bookAttributeValues')]
#[ORM\JoinColumn(nullable: false)]
private Book $book;

#[ORM\ManyToOne(targetEntity: Attribute::class)]
#[ORM\JoinColumn(nullable: false)]
private BookAttribute $bookAttribute;

#[ORM\Column(type: 'string')]
private string $value;

书籍属性

#[ORM\Column(type: 'string', length: 50)]
private string $name;

#[ORM\Column(type: 'string', length: 50)]
private string $handle; // Like: NAME, DESCRIPTION, AUTHOR etc.

要选择我在 BookRepositry 中列出的书籍:


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

        $qb
            ->addSelect([
                'bookAttributeValues',
                'bookAttribute',
            ])
            ->leftJoin('book.attributeValues', 'bookAttributeValues')
            ->leftJoin('bookAttributeValues.bookAttribute', 'bookAttribute')
            ->andWhere(
                $qb->expr()->in('bookAttribute.handle', [
                    BookAttribute::NAME,
                    BookAttribute::DESCRIPTION,
                ]),
            );

然后我可以像这样访问我的属性:

$book = $books[0];
foreach($book->attributeValues as $value) {
   echo $value->attribute->name . ' : ' . $value->value . '<br>';
}

问题是,如何按名称对 SQL 获取状态进行排序?或者如何按名称搜索? Doctrine QB 会是什么样子?

【问题讨论】:

  • 你试过$qb-&gt;....-&gt;orderBy('book.name')或类似的东西吗?
  • 我正在使用 EAV 模式。书桌上没有name 列。

标签: php symfony doctrine-orm


【解决方案1】:

DQL 关系限制

由于 EAV 使用无模式设计模式,因此没有直接的方法来使用 DQL (QueryBuilder) 使用 ORM 完成过滤或排序,因为数据库中的结果值 (Book::$attributeValues) 是不明确的:

[
     ['value' => 'name_value'], 
     ['value' => 'description_value']
]

简单地说,ORM 并不打算用于这种类型的“报告”。

DQL 解决方法

解决上述关系问题 (Book::$attributeValues) 的一种解决方法是手动映射查询构建器,以便隔离 NAME 属性和关联值,然后可以使用这些值进行过滤 (= , IN(), LIKE) 或已排序。

排序NAME属性值

使用AS HIDDEN 添加任意别名连接列,可用于排序。

$qb = $this->createQueryBuilder('book');
$expr = $qb->expr();

$qbS = $this->_em->createQueryBuilder()
    ->select('na.id')
    ->from(BookAttribute::class, 'na')
    ->where($expr->eq('na.handle', ':attribute_name'));

$qb->addSelect([
        'bookAttributeValues',
        'bookAttribute',
        'nav.value AS HIDDEN name_value',
    ])
    ->leftJoin('book.attributeValues', 'bookAttributeValues')
    ->leftJoin('bookAttributeValues.bookAttribute', 'bookAttribute')

    //isolate the associated name attribute value as a separate column
    ->leftJoin(BookAttributeValue::class, 'nav', 'WITH', $expr->andX(
         $expr->eq('book.id', 'IDENTITY(nav.book)'),
         $expr->in('IDENTITY(nav.attribute)', $qbS->getQuery()->getDQL())
    ))
    ->andWhere($expr->in('bookAttribute.handle', ':attributes'))
    ->setParameter('attribute_name', BookAttribute::NAME)
    ->setParameter('attributes', [BookAttribute::NAME, BookAttribute::DESCRIPTION])
    ->addOrderBy('name_value')
    ->addOrderBy('a.name', 'ASC'); //Z-A (Name, Description)

NAME 属性值过滤结果

只需将条件添加到您的陈述中。

$qb->andWhere($expr->eq('nav.value', ':attribute_value'))
    ->setParameter('attribute_value', '<desired_name_value>');

SQL 查询替代

由于限制,我建议将 DQL 转换为 SQL 查询,并为属性及其关联值使用单独的 nested JOIN statements。创建关系的数据透视表。然后您可以通过别名name 连接列值进行排序。

名称属性相关值

SELECT nav.value AS name
#...
LEFT JOIN (book_attribute_value AS nav
INNER JOIN book_attribute AS na 
ON na.id = nav.attribute_id
AND na.handle = BookAttribute::NAME)
ON book.id = nav.book_id 

描述属性相关值

SELECT dav.value AS description
#...
LEFT JOIN (book_attribute_value AS dav 
INNER JOIN book_attribute AS da
ON da.id = dav.attribute_id
AND da.handle = BookAttribute::DESCRIPTION)
ON book.id = dav.book_id 

完整示例 DB-Fiddle

嵌套连接将导致关联图书的描述或名称属性值缺失,在该列中返回为NULL,而不是排除整行。

class BookRepository
{

    /*
     * @return array|string[][]
     */
    public function filterBooks()
    {
        $sql = <<<SQL
SELECT 
    book.*,
    nav.value AS name,
    dav.value AS description
FROM book
LEFT JOIN (book_attribute_value AS nav
INNER JOIN book_attribute AS na 
ON na.id = nav.attribute_id
AND na.handle = :attr_name)
ON book.id = nav.book_id 
LEFT JOIN (book_attribute_value AS dav 
INNER JOIN book_attribute AS da
ON da.id = dav.attribute_id
AND da.handle = :attr_descr)
ON book.id = dav.book_id 
ORDER BY name
SQL;

        $stmt = $this->_em->getConnection()->prepare($sql);
        $stmt->bindValue('attr_name', BookAttribute::NAME);
        $stmt->bindValue('attr_descr', BookAttribute::DESCRIPTION);

        return $stmt->executeQuery()->fetchAllAssociative();
    }
}

结果

id name description
1 Book_1_Name Book_1_Description
2 Book_2_Name Book_2_Description
[
   {"id": "1", "name": "Book_1_Name", "description": "Book_1_Description"},
   {"id": "2", "name": "Book_2_Name", "description": "Book_2_Description"}
]

根据需要迭代结果。

$books = $em->getRepository(Book::class)->filterBooks();
foreach ($books as $book) {
    //ksort($book, SORT_NATURAL); #optionally sort by the attribute column
    //printf('Book %s:<br>', $book['id']); #display the book id
    unset($book['id']); //remove the id column
    foreach ($book as $attribute => $value) {
        printf('%s: %s<br>', $attribute, $value);
    }
}

输出

name: Book_1_Name
description: Book_1_Description
name: Book_2_Name
description: Book_2_Description

要限制指定名称值的结果,请将LEFT JOIN nav 更改为INNER JOIN nav 并将所需条件(=IN()LIKE)添加到语句的ON 子句中.

查询示例 DB-Fiddle

SELECT 
    book.*,
    nav.value AS name,
    dav.value AS description
FROM book
INNER JOIN (book_attribute_value AS nav
INNER JOIN book_attribute AS na 
ON na.id = nav.attribute_id
AND na.handle = :attr_name)
ON book.id = nav.book_id 
AND nav.value = :name_value
LEFT JOIN (book_attribute_value AS dav 
INNER JOIN book_attribute AS da
ON da.id = dav.attribute_id
AND da.handle = :attr_descr)
ON book.id = dav.book_id 

确保将值绑定到语句条件。

$stmt->bindValue('name_value', 'Book_1_Name');

【讨论】:

  • 谢谢威尔 B!你一如既往地深入挖掘问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-08-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多