【问题标题】:Retrieve single row as a query result for an EAV model in CodeIgniter在 CodeIgniter 中检索单行作为 EAV 模型的查询结果
【发布时间】:2019-10-20 11:42:19
【问题描述】:

我已经使用 MySQL (phpmyadmin) 为使用 CodeIgniter Framework (PHP) 开发的电子商务网站实现了 EAV 模型。 EAV 模型是这样的:

表格产品

id | name   | description
-------------------------
1  | prod_1 | lorem
2  | prod_2 | ipsum

表格属性

id | name   | description
-------------------------
1  | attr_1 | dolor
2  | attr_2 | sit
3  | attr_3 | amet

表格Product_Attributes

id | prod_id | attr_id
-------------------------
1  | 1       | 1
2  | 1       | 2
3  | 2       | 1
4  | 2       | 2
5  | 2       | 3

我使用多个连接生成了一个结果,如下所示:

product_name | attribute_name | product_description
---------------------------------------------------
prod_1       | attr_1         | lorem
prod_1       | attr_2         | lorem
prod_2       | attr_1         | ipsum
prod_2       | attr_2         | ipsum
prod_2       | attr_3         | ipsum

上述结果使用的查询如下:

function getProductList() {
  return $this->db->select('p.name as product_name, a.name as attribute_name, p.description as product_description')
                  ->from('products as p')
                  ->join('product_attributes as pa', 'pa.prod_id = p.id', 'LEFT')
                  ->join('attributes as a', 'a.id = pa.attr_id', 'LEFT')
                  ->get();
}

但是,我想要的查询结果如下:

product_name | attribute_name           | product_description
-------------------------------------------------------------
prod_1       | (attr_1, attr_2)         | lorem
prod_2       | (attr_1, attr_2, attr_3) | ipsum

当前查询结果的缺点是我必须对结果执行嵌套循环以显示产品及其属性的列表,这会影响性能。我愿意接受任何改进查询性能或其结果的建议。

--编辑--

我还有其他与Products 表链接的表。比如说,有一个额外的表格如下:

表格尺寸

id | name   | value
-----------------
1  | length | 20
2  | breadth| 15
3  | height | 20

表格Product_Dimensions

id | prod_id | dim_id
-------------------------
1  | 1       | 1
2  | 1       | 2
3  | 1       | 3
4  | 2       | 1
5  | 2       | 2

因此,预期的输出修改如下:

product_name | attribute_name           | product_description| dimension_name            | dimension_value
----------------------------------------------------------------------------------------------------------
prod_1       | (attr_1, attr_2)         | lorem              | (length, breadth, height) | (20, 15, 20)*
prod_2       | (attr_1, attr_2, attr_3) | ipsum              | (length, breadth)         | (20, 15)

但是,得到的输出如下:

product_name | attribute_name                                   | product_description| dimension_name                                      | dimension_value
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
prod_1       | (attr_1, attr_2, attr_1, attr_2, attr_1, attr_2) | lorem              | (length, breadth, height, length, breadth, height)  | (20, 15, 20, 20, 15, 20)
prod_2       | (attr_1, attr_2, attr_3, attr_1, attr_2, attr_3) | ipsum              | (length, breadth, length, breadth, length, breadth) | (20, 15, 20, 15, 20, 15)

--编辑--

当在GROUP_BY下使用DISTINCT时,输出修改如下:

product_name | attribute_name           | product_description| dimension_name            | dimension_value
----------------------------------------------------------------------------------------------------------
prod_1       | (attr_1, attr_2)         | lorem              | (length, breadth, height) | (20, 15)*
prod_2       | (attr_1, attr_2, attr_3) | ipsum              | (length, breadth)         | (20, 15)

*您可以看到预期输出和获得的输出之间的差异。使用DISTINCT 也会删除预期的重复项。

SQL Fiddle 来试试here

【问题讨论】:

  • 您关心的是在 PHP 中运行循环吗?您是否测量过它的性能影响?如果它很重要甚至是可测量的,我会感到惊讶,因为在与数据库对话的任何代码中,查询的执行时间可能比应用程序代码的执行时间长得多。 SQL 产生二维结果,通常的做法是在显示逻辑中将它们重新格式化为更多维度,除非您有明显的性能问题,否则您已将其隔离到循环中,我建议您已经存在的内容是好的和正确的。跨度>
  • @MichaelBerkowski 感谢您分享知识。但是,我想知道是否有更好的方法来查询结果。因为,我所说明的示例包括仅具有有限属性的 3 个表的连接,但是,我正在处理的数据库需要多个此类连接,每个产品至少具有 20 个属性(可能会进一步增加)。因此,如果我必须显示一个包含 10k+ 产品的列表,则查询结果可能包含 20k+ 行,这是循环迭代的次数。
  • 这确实是一个大循环。下面使用 GROUP_CONCAT() 建议的示例将重新格式化查询,但可能仍需要在您的视图中使用循环。它们将对查询性能产生影响,可能等同于仅对 2D 结果使用循环。

标签: php mysql codeigniter entity-attribute-value


【解决方案1】:

如果你想达到你的期望值,你必须使用GROUP BY。对于 CodeIgniter,您可以在这里找到它,您可以在 $this->db->group_by() 这部分找到它Guide page

function getProductList() {
  return $this->db->select('p.name as product_name, concat(\'( \', GROUP_CONCAT(a.name), \' )\') as attribute_name, p.description as product_description')
                  ->from('products as p')
                  ->join('product_attributes as pa', 'pa.prod_id = p.id', 'LEFT')
                  ->join('attributes as a', 'a.id = pa.attr_id', 'LEFT')
                  ->group_by(array('p.name', 'p.description'))
                  ->get();
}

正如您在此处看到的,grouping by 是我希望打印的列名,concat 是您希望检索的列名。希望这会有所帮助,祝你好运。

已编辑

如果您想在多个列上使用 concat 或任何聚合函数,您必须使用 GROUP_CONCAT(a.name) 进行操作,如下所示。

function getProductList() {
      return $this->db->select('p.name as product_name, 
                GROUP_CONCAT(a.name) as attribute_name, 
                GROUP_CONCAT(a.name) as attribute_name_2, 
                GROUP_CONCAT(a.name) as attribute_name_3, 
                p.description as product_description')
                  ->...
                  ->group_by(array('p.name', 'p.description'))
                  ->get();
}

注意,如果你想concat一个列,你还没有用group by语句设置它。

These examples 会给你一个好主意,祝你好运。

已编辑

这应该可行。

function getProductList() {
      return $this->db->select('p.name as product_name, 
                GROUP_CONCAT(a.name) as attribute_name, 
                p.description as product_description, 
                GROUP_CONCAT(d.name) as dimension_name, 
                GROUP_CONCAT(d.value) as dimension_value')
                  ->...
                  ->group_by(array('p.name', 'p.description'))
                  ->get();
}

已编辑

您可以在group_concat 中使用distinct,这样它就可以根据需要提供唯一的数据。

function getProductList() {
      return $this->db->select('p.name as product_name, 
                GROUP_CONCAT(DISTINCT a.name) as attribute_name, 
                p.description as product_description, 
                GROUP_CONCAT(DISTINCT d.name) as dimension_name, 
                GROUP_CONCAT(DISTINCT d.value) as dimension_value')
                  ->...
                  ->group_by(array('p.name', 'p.description'))
                  ->get();
}

已编辑

正如我在 cmets 部分告诉你的,如果你想做你想做的事,你必须使用子查询,这就是你可以做到的。

function getProductList() {
  return $this->db->select('pname, pdesc, aname, adesc, group_concat(dname), group_concat(dvalue)')
                  ->from('( select
                        p.name as pname,
                        p.description as pdesc,
                        GROUP_CONCAT(DISTINCT a.name) as aname,
                        GROUP_CONCAT(DISTINCT a.description) as adesc,
                        d.name as dname, 
                        d.value as dvalue

                        from products as p
                        left join product_attributes as pa on pa.product_id = p.id
                        left join attributes as a on a.id = pa.attribute_id
                        left join product_dimensions as pd on pd.product_id = p.id
                        left join dimensions as d on d.id = pd.dimension_id

                        group by p.id, p.description, d.name) as d' )
                  ->group_by(array('pname', 'pdesc', 'aname', 'adesc'))
                  ->get();
}

sql fiddle

【讨论】:

  • 感谢您的回答!但是,你也连接了()(圆括号),这背后有什么原因吗?
  • 不客气,因为在您的预期输出中,您有这样的结果。看起来代码对您有用,您可以随意编辑它。祝你好运。
  • 如果有单列连接,您提供的答案效果很好。你能帮我连接多列吗?
  • 是的,您对其他列执行相同操作,GROUP_CONCAT(a.name), GROUP_CONCAT(a.name), GROUP_CONCAT(a.name), GROUP_CONCAT(a.name),但请确保如果您想对列执行 concat,您不必在 group by 语句中设置它,好运气。
  • 您能否按照edit修改您的答案?
【解决方案2】:

我将只执行三个简单的查询。获取产品。获取属性。获取尺寸。 然后将属性和维度映射到相应的产品。 这就是任何 ORM 执行预加载的可能性。

我不熟悉 codeigniter,但我认为它应该看起来像这样:

$query = $this->db->get('products');

$products = [];
foreach ($query->result() as $row) {
    // init empty arrays for attributes & dimensions
    $row->attributes = [];
    $row->dimensions = [];
    // should be indexed by id, so we can find id later to map attributes & dimensions
    $products[$row->id] = $row;
}

$attributes = $this->db
    ->select('pa.product_id, a.name')
    ->from('product_attributes as pa')
    ->join('attributes as a', 'a.id = pa.attribute_id')
    ->get();

$dimensions = $this->db
    ->select('pd.product_id, d.name, d.value')
    ->from('product_dimensions as pd')
    ->join('dimensions as d', 'd.id = pd.dimension_id')
    ->get();

foreach ($attributes as $attr) {
    $products[$attr->product_id]->attributes[] = $attr->name;
}

foreach ($dimensions as $dim) {
    $products[$dim->product_id]->dimensions[] = $dim;
    unset($dim->product_id);
}

现在您可以在一个很好的嵌套结构中获取所有数据。使用echo json_encode($products, JSON_PRETTY_PRINT),您将获得:

{
    "1": {
        "id": 1,
        "name": "Product_1",
        "description": "Lorem",
        "attributes": [
            "Attribute_1",
            "Attribute_2"
        ],
        "dimensions": [
            {
                "name": "length",
                "value": "15"
            },
            {
                "name": "breadth",
                "value": "25"
            },
            {
                "name": "height",
                "value": "15"
            }
        ]
    },
    "2": {
        "id": 2,
        "name": "Product_2",
        "description": "Ipsum",
        "attributes": [
            "Attribute_1",
            "Attribute_2",
            "Attribute_3"
        ],
        "dimensions": [
            {
                "name": "length",
                "value": "15"
            },
            {
                "name": "breadth",
                "value": "25"
            }
        ]
    }
}

您可以在视图层或导出代码中遍历它,并以您需要的任何方式输出。

更新

在 MySQL 5.7+ 中,您可以通过单个查询获得与单个 JSON 字符串相同的结果:

select json_arrayagg(json_object(
  'id', p.id,
  'name', p.name,
  'description', p.description,
  'attributes', (
    select json_arrayagg(a.name)
    from product_attributes pa
    join attributes a on a.id = pa.attribute_id
    where pa.product_id = p.id
  ),
  'dimensions', ( 
    select json_arrayagg(json_object(
      'name',  d.name,
      'value', d.value
    ))
    from product_dimensions pd
    join dimensions d on d.id = pd.dimension_id
    where pd.product_id = p.id
  )
))
from products p;

db-fiddle

或者也许你想要一些像这样“简单”的东西:

select p.*, (
    select group_concat(a.name order by a.id separator ', ')
    from product_attributes pa
    join attributes a on a.id = pa.attribute_id
    where pa.product_id = p.id
  ) as attributes, (
    select group_concat(d.name, ': ', d.value order by d.id separator ', ')
    from product_dimensions pd
    join dimensions d on d.id = pd.dimension_id
    where pd.product_id = p.id
  ) as dimensions
from products p;

将返回:

| id  | name      | description | attributes                            | dimensions                          |
| --- | --------- | ----------- | ------------------------------------- | ----------------------------------- |
| 1   | Product_1 | Lorem       | Attribute_1, Attribute_2              | length: 15, breadth: 25, height: 15 |
| 2   | Product_2 | Ipsum       | Attribute_1, Attribute_2, Attribute_3 | length: 15, breadth: 25             |

db-fiddle

但如果您需要与问题中完全相同的结果,那么您可以试试这个:

select 
  p.name as product_name,
  (
    select concat('(', group_concat(a.name order by a.id separator ', '), ')')
    from product_attributes pa
    join attributes a on a.id = pa.attribute_id
    where pa.product_id = p.id
  ) as attribute_name,
  concat('(', group_concat(d.name order by d.id separator ', '), ')') as dimension_name,
  concat('(', group_concat(d.value order by d.id separator ', '), ')') as dimension_value,
  p.description as product_description
from products p
left join product_dimensions pd on pd.product_id = p.id
left join dimensions d on d.id = pd.dimension_id
group by p.id;

结果:

| product_name | product_description | attribute_name                          | dimension_name            | dimension_value |
| ------------ | ------------------- | --------------------------------------- | ------------------------- | --------------- |
| Product_1    | Lorem               | (Attribute_1, Attribute_2)              | (length, breadth, height) | (15, 25, 15)    |
| Product_2    | Ipsum               | (Attribute_1, Attribute_2, Attribute_3) | (length, breadth)         | (15, 25)        |

db-fiddle

【讨论】:

  • 看起来不错,只是您将 attributesdimensionsfromwhere 子句互换了。 :P 另外,我很好奇,是否有 SQL 这样做的方式?
  • 嗯..奇怪。我不知道这是怎么回事:-)
  • @RahulSoni 请注意,SQL 查询总是返回表。您不能返回嵌套结构。您可以使用 MYSQL 5.7 返回 JSON 字符串。
  • 谢谢!虽然,您的答案有一些语法错误,但我不能全部指出。请务必纠正它们。
【解决方案3】:

你可以使用 MySQL 函数GROUP_CONCAT 来得到想要的结果:

http://sqlfiddle.com/#!9/c51040/3

【讨论】:

  • 感谢亚历克斯,您的贡献! :) 如果它类似于 CodeIgniter 查询结构,您的回答会更有帮助。
【解决方案4】:

你需要这样做

function getProductList() {
  return $this->db->select('p.name as product_name
                     , GROUP_CONCAT(a.name SEPARATOR ",") as attribute_name
                     , min(p.description) as product_description')
                  ->from('products as p')
                  ->join('product_attributes as pa', 'pa.prod_id = p.id', 'LEFT')
                  ->join('attributes as a', 'a.id = pa.attr_id', 'LEFT')
                  ->group_by("p.name");
                  ->get();
}

【讨论】:

  • 感谢您的回答!但是,min() 用于product_description 有什么具体原因吗?
  • 我的服务器有full_group,可能没有它也能正常工作,但是每个显示的列都应该在group_by中或者有一个聚合函数。
【解决方案5】:

使用子查询试试这个。 Codeigniter 代码:

$this->db->select("select p.name as pname, p.description as pdesc, t2.aname, t2.adesc,t3.dname,t3.dvalue");
$this->db->from("products p");
$this->db->join("(select pa.product_id,GROUP_CONCAT(a.name) as aname, GROUP_CONCAT(a.description) as adesc from  product_attributes as pa 
left join attributes as a on a.id = pa.attribute_id
group by pa.product_id) t2","t2.product_id=p.id","left");
$this->db->join("(select group_concat(name) as dname,group_concat(value) as dvalue,pd.product_id from product_dimensions pd
left join dimensions d on d.id = pd.dimension_id
group by pd.product_id) t3","t3 on t3.product_id = p.id","left");
$this->db->group_by("p.id");
$this->db->get()->result_array();

MySql 查询:

select p.name as pname, p.description as pdesc, t2.aname, t2.adesc,t3.dname,t3.dvalue
from products as p
left join (select pa.product_id,GROUP_CONCAT(a.name) as aname, GROUP_CONCAT(a.description) as adesc from  product_attributes as pa 
left join attributes as a on a.id = pa.attribute_id
group by pa.product_id) t2 on t2.product_id=p.id
left join
(select group_concat(name) as dname,group_concat(value) as dvalue,pd.product_id from product_dimensions pd
left join dimensions d on d.id = pd.dimension_id
group by pd.product_id) t3 on t3.product_id = p.id
group by p.id

Click here to see result

【讨论】:

    猜你喜欢
    • 2015-06-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-16
    • 2011-07-14
    • 1970-01-01
    • 2015-08-29
    相关资源
    最近更新 更多