在 CodeIgniter 中检索单行作为 EAV 模型的查询结果

Retrieve single row as a query result for an EAV model in CodeIgniter

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

Table产品

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

Table: 属性

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

Table: 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

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

--编辑--

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

Table维度

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

Table: 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.

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

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

你需要这样做

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();
}

如果你想达到你的期望值,你必须在这部分使用GROUP BY. For CodeIgniter you find it here, you find it Guide page$this->db->group_by()

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();
}

已编辑

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

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

我只会执行三个简单的查询。获取产品。获取属性。获取维度。 然后将属性和维度映射到相应的产品。 这就是任何 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

或者您可能想要 "simple" 这样的东西:

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;

这将 return:

| 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

使用子查询试试这个。 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