Искать в сгруппированных столбцах в MySQL? - PullRequest
6 голосов
/ 19 октября 2011

Мне нужно создать базу данных парней, у парней может быть один или несколько атрибутов, и атрибут каждого парня имеет определенное значение, звучит легко, а?ну, продолжайте читать, так как проблема вроде как становится невозможной (5 дней на ее решение: s).

Итак, я создаю 3 таблицы:

CREATE TABLE guy (
  id int(11),
  name varchar(255)
);

CREATE TABLE attribute (
  id int(11),
  name varchar(255)
);

-- each value references one guy and one attribute
CREATE TABLE _value (
  id int(11),
  guy_id int(11),
  attribute_id int(11),
  _value varchar(255)
);

с данными этого примера:

INSERT INTO attribute VALUES (1, 'age'), (2, 'dollars'), (3, 'candies');
INSERT INTO guy VALUES (1, 'John'), (2, 'Bob');
INSERT INTO _value VALUES (1, 1, 1, 12), (2, 1, 2, 15), (3, 1, 3, 3);
INSERT INTO _value VALUES (4, 2, 1, 15), (5, 2, 2, 20), (6, 2, 3, 6);

и создайте этот запрос:

SELECT g.name 'guy', a.name 'attribute', v._value 'value' 
FROM guy g 
JOIN _value v ON g.id = v.guy_id 
JOIN attribute a ON a.id = v.attribute_id;

, который даст мне такой результат:

+------+-----------+-------+
| guy  | attribute | value |
+------+-----------+-------+
| John | age       | 12    |
| John | dollars   | 15    |
| John | candies   | 3     |
| Bob  | age       | 15    |
| Bob  | dollars   | 20    |
| Bob  | candies   | 6     |
+------+-----------+-------+

ЭТО НАСТОЯЩАЯ ЗАДАЧА:

Позже, мой босс сказал мне, что он хочет фильтровать данные, используя столько условий, сколько ему нужно, чтобы иметь возможность группировать эти условия с помощью «ands» и «ors», например, он может захотеть выполнить это безумное условие:

Получите парней, возраст которых больше 10, у которых меньше 18 долларов, у которых больше 2 конфет и меньше 10 конфет, но, не смотря ни на что, также включите парней, которым ровно 15 лет.этот фильтр:

-- should return both John and Bob
(age > 10 and dollars < 18 and candies > 2 and candies < 10) or (age = 15)

У меня нет проблем с созданием фильтра (для этого я использую jqgrid), проблема в том, что атрибуты - это не столбцы, а строки вместо , и из-зачто я не знаю, как смешать запрос с фильтром, я пытался счто-то вроде этого:

SELECT g.name 'guy', a.name 'attribute', v._value 'value' 
FROM guy g 
JOIN _value v ON g.id = v.guy_id 
JOIN attribute a ON a.id = v.attribute_id
GROUP BY guy
HAVING (
    (attribute = 'age' and value > 10) AND
    (attribute = 'dollars' and value < 18) AND
    (attribute = 'candies' and value > 2) AND
    (attribute = 'candies' and value < 10)
       )
OR
       (
     (attribute = 'age' and value = 15)
       )

но возвращается только Боб :( и я должен получить и Джона, и Боба.

ТАК, КАК СЛЕДУЕТ СМЕШИВАТЬ ФИЛЬТР И ЗАПРОС?

Имейте в виду, что количество атрибутов, которые есть у каждого парня, одинаково для всех парней, но в любое время можно добавить больше атрибутов и больше парней, например, если я хочу добавить парня «Марио»«Я бы сделал:

-- we insert the guy Mario
INSERT INTO guy VALUES (3, 'Mario');
-- with age = 5, dollars = 100 and candies = 1
INSERT INTO _value VALUES (7, 3, 1, 5), (8, 3, 2, 100), (9, 3, 3, 1);

И если бы я хотел создать атрибут« яблоки », я бы сделал:

-- we insert the attribute apples
INSERT INTO attribute VALUES (4, 'apples');
-- we create a value for each guy's new attribute, John as 7 apples, Bob has 3 and Mario has 8
INSERT INTO _value VALUES (10, 1, 4, 7), (11, 2, 4, 2), (12, 3, 4, 8);

, и теперь я смогу включить условия о яблоках вмой запрос.

Надеюсь, я стал понятен, спасибо за все ваше время:)

Примечание: Может быть, если бы был способ поместить все атрибуты каждого парня в один ряд ?,что-то вроде этого:

+------+-----------+-------+------+------------+--------+------+------------+--------+------+------------+--------+
| guy  | attribute | value | guy  | attribute  | value  | guy  | attribute  | value  | guy  | attribute  | value  |
+------+-----------+-------+------+------------+--------+------+------------+--------+------+------------+--------+
| John | age       |    12 | John | dollars    |     15 | John | candies    |      3 | John | apples     |      7 |
| Bob  | age       |    15 | Bob  | dollars    |     20 | Bob  | candies    |      6 | Bob  | apples     |      2 |
| Mario| age       |    5  | Mario| dollars    |     100| Mario| candies    |      1 | Mario| apples     |      8 |
+------+-----------+-------+------+------------+--------+------+------------+--------+------+------------+--------+

Примечание 2: предложил @iim (В этом вопросе: Как искать в сгруппированных столбцах в MySQL?(также в Hibernate, если это возможно) ), что я мог бы выполнить самостоятельное объединение для каждого атрибута, и да, это может решить проблему, но могут быть проблемы с производительностью, когда у ребят множество атрибутов (например, 30 или более).

Примечание 3: я не могу изменить схему базы данных: (

Ответы [ 5 ]

2 голосов
/ 19 октября 2011

что примерно так?

SELECT g.name 'guy', a.name 'attribute', v._value 'value' 
FROM guy g 
JOIN _value v1 ON g.id = v1.guy_id 
  JOIN attribute a1 ON a1.id = v1.attribute_id
JOIN _value v2 ON g.id = v2.guy_id 
  JOIN attribute a2 ON a2.id = v2.attribute_id
JOIN _value v3 ON g.id = v3.guy_id 
  JOIN attribute a3 ON a3.id = v3.attribute_id
JOIN _value v4 ON g.id = v4.guy_id 
  JOIN attribute a4 ON a4.id = v4.attribute_id
JOIN _value v5 ON g.id = v5.guy_id 
  JOIN attribute a5 ON a5.id = v5.attribute_id
WHERE (
    (a1 = 'age' and v1 > 10) AND
    (a2 = 'dollars' and v2 < 18) AND
    (a3 = 'candies' and v3 > 2) AND
    (a4 = 'candies' and v4 < 10)
  ) OR (a5 = 'age' and v5 = 15)

edit исправление нескольких глупых ошибок:

SELECT DISTINCT g.id, g.name 'guy'
FROM guy g 
JOIN _value v1 ON g.id = v1.guy_id 
  JOIN attribute a1 ON a1.id = v1.attribute_id
JOIN _value v2 ON g.id = v2.guy_id 
  JOIN attribute a2 ON a2.id = v2.attribute_id
JOIN _value v3 ON g.id = v3.guy_id 
  JOIN attribute a3 ON a3.id = v3.attribute_id
JOIN _value v4 ON g.id = v4.guy_id 
  JOIN attribute a4 ON a4.id = v4.attribute_id
JOIN _value v5 ON g.id = v5.guy_id 
  JOIN attribute a5 ON a5.id = v5.attribute_id
WHERE (
    (a1.name = 'age' and v1._value > 10) AND
    (a2.name = 'dollars' and v2._value < 18) AND
    (a3.name = 'candies' and v3._value > 2) AND
    (a4.name = 'candies' and v4._value < 10)
  ) OR (a5.name = 'age' and v5._value = 15)

конкретно, я забыл об именах полей в WHERE, выберите только поля 'guy' и добавьте DISTINCT, чтобы получить только одну строку для каждого парня.

1 голос
/ 19 октября 2011

Следующее позволит вам сделать ваши условия более или менее простыми, хотя я не могу обещать, что это будет действительно эффективно с 100 000+ парней с более чем 30 атрибутами.Это вы должны увидеть сами.

SELECT g.name guy, a.name attribute, v._value value
FROM guy g 
JOIN _value v ON g.id = v.guy_id 
JOIN attribute a ON a.id = v.attribute_id
GROUP BY guy
HAVING (
    SUM(a.name = 'age'     and v._value > 10) = 1 AND
    SUM(a.name = 'dollars' and v._value < 18) = 1 AND
    SUM(a.name = 'candies' and v._value > 2 ) = 1 AND
    SUM(a.name = 'candies' and v._value < 10) = 1
       )
OR
       (
    SUM(a.name = 'age'     and v._value = 15) = 1
       )

(здесь я предполагаю, что у парня не может быть повторяющихся атрибутов.)

1 голос
/ 19 октября 2011

Если проблема в том, что «проблема в том, что атрибуты - это не столбцы, а строки», как насчет представления.Вы не можете изменить схему базы данных, но вы можете рассмотреть представление, которое выглядит так:

CREATE VIEW the_attributes as 
  select a.id, a.name as attribute_name, v._value
  from attribute a JOIN value v
  ON v.attribute_id = a.id

Начиная с этого, может работать лучше.

Тогда я думаю, что вы должны быть в состоянии сделать:

select guy.id from guy JOIN the_attributes ON the_attributes.guy_id = guy.id
where 
the_attributes.name = 'age' and _value > 10 and
the_attributes.name = 'dollar' and _value < 18 and
the_attributes.name = 'candies' and _value > 2 and
the_attributes.name = 'candies' and _value <10 ) or
the_attributes.name = 'age' and _value = 15 ) 

Поможет ли вам все это в конечном итоге, вам придется судить, но это то, что мне пришло в голову, когда я прочитал проблему вначале.Конечно, выглядит читабельным; (

1 голос
/ 19 октября 2011

Примерно так может быть вариант:

select g.name as guy
from guy g
join _value v on g.id = v.guy_id
join attribute a on a.id = v.attribute_id
where (a.name = 'age'     and v._value > 10)
   or (a.name = 'dollars' and v._value < 18)
   or (a.name = 'candies' and v._value > 2)
group by g.name
having count(*) = 3

union

select g.name as guy
from guy g
join _value v on g.id = v.guy_id
join attribute a on a.id = v.attribute_id
 where (a.name = 'age' and v._value = 15)
group by g.name       -- These two clauses are not necessary,
having count(*) = 1   -- they're just her for symmetry

Вы превращаете свои внешние "или" условия в UNIONs, а ваши "и" условия могут обрабатываться в обычном режиме "having count(*) соответствует числу условий".

Я не знаю, сработает ли этот подход для всего, что от вас хочет ваш босс, но, возможно, это поможет.

0 голосов
/ 19 октября 2011

попробуйте, может быть, это поможет.

SELECT g.name 'guy', a.name 'attribute', v._value 'value' 
FROM guy g 
JOIN _value v ON g.id = v.guy_id 
JOIN attribute a ON a.id = v.attribute_id
WHERE a.ID = v.attribute_ID
      AND v._value = 'values you want'
      AND  NOT v._value = 'values you don''t want'

дайте мне знать, если вам нужно что-нибудь еще.

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