Модераторы: skyboy
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Помогите сделать выборку из каталога с отдельной т 
:(
    Опции темы
Reverent
  Дата 1.2.2016, 02:38 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 65
Регистрация: 30.5.2007

Репутация: нет
Всего: нет



Помогите сделать выборку из каталога с отдельной таблицей свойств.
Добрый день, уважаемые форумчани. Уже несколько дней ломаю голову, как можно реализовать следующую задачу, которая стоит в проекте.

Представьте, что у нас есть каталог наподобие такого:

id_catalog
name

А так же у нас есть таблица, которая хранит свойства каждого товара и его значение.

id_catalogParam
id_catalog
label
value

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

Код

SELECT 
catalog.*, catalogParam.* 

FROM 
catalog 
LEFT JOIN catalogParam ON catalog.id_catalog = catalogParam.id_catalog 

WHERE 
    (
    label = 'Мощность' 
    AND
    value = '300'
    )
OR 
    (
    label = 'Цена' 
    AND
    value = '7000'
    );


Но таким образом он находит товары либо с мощностью 300, либо с ценой 7000. А мне нужно полное соответствие, тогда я заменяю OR на AND. Но поиск ничего не дает. И это вполне понятно, нет такой записи где бы value равнялось и тому и другому. Так как решить эту проблему, как провести эту выборку, чтобы она дала товары полного соотевствия.

Это сообщение отредактировал(а) Reverent - 1.2.2016, 02:40
PM MAIL   Вверх
ksnk
Дата 1.2.2016, 08:17 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


прохожий
****


Профиль
Группа: Комодератор
Сообщений: 6855
Регистрация: 13.4.2007
Где: СПб

Репутация: 2
Всего: 386



Есть такая функция - EXISTS

Код

SELECT 
c.*
FROM 
catalog as c
WHERE 
    EXISTS( select .id_catalog from catalogParam 
                 where id_catalog =c.id_catalog and  label = 'Мощность' AND value = '300' 
    ) 
AND
    EXISTS( select .id_catalog from catalogParam 
                 where id_catalog =c.id_catalog and  label = 'Цена' AND value >= 300 and  value <= 1000
    ) 

Тонкость в том, что запрос не сможет выдать все свойства товара за раз, только ID товара. Придется потом отдельным запросом их выковыривать.

Это сообщение отредактировал(а) ksnk - 1.2.2016, 08:18


--------------------
Человеку свойственно ошибаться, программисту свойственно ошибаться профессионально ! user posted image
PM MAIL WWW Skype   Вверх
igorold
Дата 1.2.2016, 09:19 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 557
Регистрация: 22.12.2005
Где: Россия->Урал-& gt;Миасс

Репутация: 5
Всего: 17



Код

SELECT catalog.*, catalogParam.*,count(catalogParam.id_catalog)
FROM catalog
LEFT JOIN catalog.id_catalog = catalogParam.id_catalog
WHERE (label = 'Мощность' AND value = '300')
OR (label = 'Цена' AND value = '7000') group by  catalog.id_catalog having(count(catalogParam.id_catalog) = 2);




Этот ответ добавлен с нового Винграда - http://vingrad.com
PM MAIL   Вверх
Akina
Дата 1.2.2016, 11:33 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Советчик
****


Профиль
Группа: Модератор
Сообщений: 20581
Регистрация: 8.4.2004
Где: Зеленоград

Репутация: 106
Всего: 454



Классический подход - это обработка таблицы параметров в подзапросе с получением только тех объектов, для которых найден необходимый комплекс характеристик.

Т.е. подзапрос выглядит приблизительно так:

Код

SELECT id_catalog
FROM catalogParam 
WHERE 
    (label = 'Мощность' AND value = '300')
OR 
    (label = 'Цена' AND value = '7000')
GROUP BY id_catalog
HAVING COUNT(id_catalogParam) = 2

Все указанные фильтры включаются в секцию WHERE через OR, причём тут допустимы любые типы условий, например:
Код

label = 'Мощность' AND value = '300' -- строго 300
label = 'Мощность' AND value >= '300' -- не менее 300
label = 'Цена' AND value+0 BETWEEN 3000 AND 5000 -- от 3000 до 5000

Количество соответствующих фильтров указывается в секции HAVING, причём там может задаваться и количество, не равное количеству отдельных фильтров - например, если всего указано 5 фильтров, то секция может быть такой:
Код

HAVING COUNT(id_catalogParam) = 5 -- строго все 5 фильтров
HAVING COUNT(id_catalogParam) >= 3 -- не менее 3 из этих 5

Полученный подзапрос даёт все id_catalog, которые соответствуют набору фильтров и требуемому количеству соответствий, и это значение используется для отбора записей основной таблицы.


--------------------
 О(б)суждение моих действий - в соответствующей теме, пожалуйста. Или в РМ. И высшая инстанция - Администрация форума.

PM MAIL WWW ICQ Jabber   Вверх
ksnk
Дата 1.2.2016, 11:49 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


прохожий
****


Профиль
Группа: Комодератор
Сообщений: 6855
Регистрация: 13.4.2007
Где: СПб

Репутация: 2
Всего: 386



Akina, "Классика" - это бОльшее соответствие стандарту SQL? В смысле EXISTS - это такая mysql фича, которая другими системами может и не поддерживаться.
А вот в mysql реальности есть ли заметные преимущества "классики" перед ESISTS подходом? Интерес шкурный smile Переписывать мне систему или и так пойдет?


--------------------
Человеку свойственно ошибаться, программисту свойственно ошибаться профессионально ! user posted image
PM MAIL WWW Skype   Вверх
Akina
Дата 1.2.2016, 12:04 (ссылка) |    (голосов:2) Загрузка ... Загрузка ... Быстрая цитата Цитата


Советчик
****


Профиль
Группа: Модератор
Сообщений: 20581
Регистрация: 8.4.2004
Где: Зеленоград

Репутация: 106
Всего: 454



Цитата(ksnk @  1.2.2016,  12:49 Найти цитируемый пост)
в mysql реальности есть ли заметные преимущества "классики" перед EXISTS подходом?

Если каталог содержит миллион товаров, из которых набору из десятка фильтров соответствует только десяток записей, EXISTS со своими коррелированными подзапросами отправится курить за угол. Ибо описанный подход изрядно "худеет" самую пухлую из участвующих в этом действе таблиц. Однако если набору фильтров соответствует половина товаров, то EXISTS, несомненно, выиграет.

Кроме того, лично я считаю, что для динамического построения запросов EXISTS-шаблон более сложен. Если кто считает иначе - спорить не буду, это очень субъективно. А ещё я считаю, что запрос с EXISTS хуже читаем с точки зрения понимания логики текста запроса.

Ну и EXISTS не очень-то предназначен для построения запроса, когда требуется соответствие не строго всем фильтрам, а не менее указанного количества (как в примере "не менее 3 из этих 5").

Цитата(ksnk @  1.2.2016,  12:49 Найти цитируемый пост)
В смысле EXISTS - это такая mysql фича, которая другими системами может и не поддерживаться.

По-моему, её поддерживают все и вся...


--------------------
 О(б)суждение моих действий - в соответствующей теме, пожалуйста. Или в РМ. И высшая инстанция - Администрация форума.

PM MAIL WWW ICQ Jabber   Вверх
tzirechnoy
Дата 1.2.2016, 21:05 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1173
Регистрация: 30.1.2009

Репутация: 3
Всего: 16



EXISTS есть как миниму в SQL92.
PM MAIL   Вверх
Zloxa
Дата 2.2.2016, 10:25 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Чо?
****


Профиль
Группа: Завсегдатай
Сообщений: 3473
Регистрация: 12.9.2008

Репутация: 33
Всего: 161



Цитата(Akina @  1.2.2016,  13:04 Найти цитируемый пост)
 EXISTS со своими коррелированными подзапросами 

Хотел было поспорить, но походу это платформспецифик маси, покуда он не умеет выполнять exists, траснформируя его в semi-join.
Увы, 
Цитата

It must be an IN (or =ANY) subquery that appears at the top level of the WHERE or ON clause, possibly as a term in an AND expression.


Далее, сорри - smile. Потому как не про MySql, а про единственную боль-мень нормальную СУБД (oracle) smile, которая умеет exists выполнять семиджойном, а не корреляхой.

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

SQL> create table entity(id primary key) as select level from dual connect by level <= 1e6;
Table created
SQL> create table property(entity_id, type, value, primary key (entity_id, type)) as select trunc(level / 4) + 1, mod(level-1, 4) + 1 , level from dual connect by level <= 4e6;
Table created
SQL> create index property_i1 on property(type, value);
SQL> select * from property order by 1 fetch first 10 rows only;
 ENTITY_ID       TYPE      VALUE
---------- ---------- ----------
         1          1          1
         1          2          2
         1          3          3
         2          4          4
         2          1          5
         2          2          6
         2          3          7
         3          4          8
         3          1          9
         3          2         10
10 rows selected
Index created


Код

SQL> set timing on
SQL> select
  2      e.*
  3  from
  4      entity e
  5  where
  6      exists (select null from property p where p.type = 1 and p.value =1 and p.entity_id = e.id) and
  7      exists (select null from property p where p.type = 2 and p.value =2 and p.entity_id = e.id);
        ID
----------
         1
Executed in 0.015 seconds

План

Код

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 938718327
--------------------------------------------------------------------------------
| Id  | Operation                              | Name         | Rows  | Bytes |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                       |              |     1 |    33 |
|   1 |  NESTED LOOPS SEMI                     |              |     1 |    33 |
|   2 |   NESTED LOOPS                         |              |     1 |    19 |
|   3 |    SORT UNIQUE                         |              |     1 |    14 |
|   4 |     TABLE ACCESS BY INDEX ROWID BATCHED| PROPERTY     |     1 |    14 |
|*  5 |      INDEX RANGE SCAN                  | PROPERTY_I1  |     1 |       |
|*  6 |    INDEX UNIQUE SCAN                   | SYS_C0053015 |     1 |     5 |
|*  7 |   TABLE ACCESS BY INDEX ROWID          | PROPERTY     |     1 |    14 |
|*  8 |    INDEX UNIQUE SCAN                   | SYS_C0053016 |     1 |       |
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   5 - access("P"."TYPE"=2 AND "P"."VALUE"=2)
   6 - access("P"."ENTITY_ID"="E"."ID")
   7 - filter("P"."VALUE"=1)
   8 - access("P"."ENTITY_ID"="E"."ID" AND "P"."TYPE"=1)
23 rows selected

Здесь он сначала по индексу находит property по  p.type = 2 and p.value = 2, затем по индексу подтягивает его entity, затем по индексу смотрит для него property по type = 1 и фильтрует по value = 1

Хавингом получается трохан подольше
Код

SQL> select
  2      *
  3  from
  4      entity
  5  where
  6      id in
  7      (
  8         select entity_id from property p where p.type = 1 and p.value = 1 or p.type = 2 and p.value = 2  group by entity_id having count(*) = 2
  9      )
 10  ;
        ID
----------
         1
Executed in 0.063 seconds


При низкой селективности, казалось бы, должно быть наоборот. Ведь джойн потребует двух фулсканов там где having потребует одного. Но практика это не подтверждает
Код

SQL> select sum(id)
  2  from
  3  (
  4      select
  5          e.*
  6      from
  7          entity e
  8      where
  9          exists (select null from property p where p.type = 1 and p.value > 1 and p.entity_id = e.id) and
 10          exists (select null from property p where p.type = 2 and p.value > 2 and p.entity_id = e.id)
 11  );
 
   SUM(ID)
----------
5000004999
Executed in 1.326 seconds

SQL> select sum(id)
  2  from
  3  (
  4      select
  5          *
  6      from
  7          entity
  8      where
  9          id in
 10          (
 11             select entity_id from property p where p.type = 1 and p.value > 1 or p.type = 2 and p.value > 2  group by entity_id having count(*) = 2
 12          )
 13  )
 14  ;
   SUM(ID)
----------
5000004999
Executed in 3.198 seconds



Это сообщение отредактировал(а) Zloxa - 2.2.2016, 11:00


--------------------
Достоверно известно, что 89% людей доверяют статистике взятой с потолка smile
PM   Вверх
Angel666
Дата 2.2.2016, 12:57 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 0
Регистрация: 8.9.2011

Репутация: нет
Всего: 1



Не знаю как вариант.
1. выбрать id товара по одному свойству допустим цене (получили 30 id)
2. следующий запрос по другому свойству мощности но с ограничением id уже полученных товаров.
3. ну и так далее...

Этот ответ добавлен с нового Винграда - http://vingrad.com
PM MAIL   Вверх
Akina
Дата 2.2.2016, 15:46 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Советчик
****


Профиль
Группа: Модератор
Сообщений: 20581
Регистрация: 8.4.2004
Где: Зеленоград

Репутация: 106
Всего: 454



Цитата(Angel666 @  2.2.2016,  13:57 Найти цитируемый пост)
как вариант.

Гм... это вариант, навеянный привычкой вытащить все данные на клиента и там их обрабатывать, вместо того, чтобы поручить это SQL-серверу. 


--------------------
 О(б)суждение моих действий - в соответствующей теме, пожалуйста. Или в РМ. И высшая инстанция - Администрация форума.

PM MAIL WWW ICQ Jabber   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | MySQL | Следующая тема »


 




[ Время генерации скрипта: 0.1036 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.