Получить одну строку в качестве результата запроса для модели EAV в CodeIgniter - PullRequest
2 голосов
/ 20 октября 2019

Я реализовал модель EAV с использованием MySQL (phpmyadmin) для веб-сайта электронной коммерции, разработанного с использованием CodeIgniter Framework (PHP). Модель 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

Таблица : Атрибуты_продукта

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

Недостаток текущего результата запроса заключается в том, что мне нужно выполнить вложенный цикл для отображения списка продуктов и их атрибутов, что влияет на производительность. Я открыт для любых предложений по улучшению производительности запроса или его результата.

- EDIT -

У меня также есть другие связанные таблицыс таблицей 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)

- EDIT -

При использовании DISTINCT в GROUP_BY выход изменяется следующим образом:

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)

* Youможно увидеть разницу между ожидаемым и полученным результатом. Предполагаемые дубликаты также удаляются с помощью DISTINCT.

SQL Fiddle , чтобы попробовать свои силы на здесь .

Ответы [ 5 ]

3 голосов
/ 20 октября 2019

Если вы хотите достичь ожидаемого значения, вы должны использовать GROUP BY. Для CodeIgniter вы найдете его здесь, вы найдете его Страница руководства в этой части $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 имя столбца, который вы хотите получить. Надеюсь, это поможет, удачи.

EDITED

Если вы хотите использовать 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.

Эти примеры даст вам хорошее представление об этом, Удачи.

EDITED

Это должно работать.

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

РЕДАКТИРОВАНИЕ

Вы можете использовать distinct внутри group_concat, так что он дает уникальные данные, как вы хотите.

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

EDITED

Как я уже говорил вам в комментариях, вы должны использовать подзапрос, если вы хотите делать то, что вы хотите, и вот как вы можете это сделать.

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

2 голосов
/ 24 октября 2019

Я бы просто выполнил три простых запроса. Получить продукты. Получить атрибуты. Получите размеры. Затем сопоставьте атрибуты и размеры с соответствующими продуктами. Вот как, вероятно, любая 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

1 голос
/ 20 октября 2019

Тебе нужно будет это сделать

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();
}
1 голос
/ 20 октября 2019

Вы можете использовать функцию MySQL GROUP_CONCAT , чтобы получить желаемый результат:

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

0 голосов
/ 25 октября 2019

Попробуйте, используя Sub Query. Codeigniter Code:

$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 Query:

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

Нажмите здесь, чтобы увидеть результат

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...