【问题标题】:Search in grouped columns in MySQL?在 MySQL 的分组列中搜索?
【发布时间】:2011-10-19 00:33:23
【问题描述】:

我需要创建一个男人的数据库,男人可以有一个或多个属性,每个男人的属性都有一个特定的值,听起来很容易吧?好吧,继续阅读,因为问题有点不可能(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”和“or”进行分组,例如,他可能想做这个疯狂的条件:

获取大于 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)
       )

但只返回 Bob :( 我应该同时得到 John 和 Bob。

那么,我应该如何混合过滤器和查询?

请记住,每个人的属性数量对于所有人都是相同的,但是可以随时添加更多属性和更多人,例如,如果我想添加“马里奥”这个人,我会这样做:

-- 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 建议(在这个问题中:How to search in grouped columns in MySQL? (also in Hibernate if possible))我可以为每个属性进行自连接,是的,这可以解决问题,但是当人们拥有大量属性时可能会出现性能问题(比如 30 个或更多)。

注意 3:我无法更改数据库架构 :(

【问题讨论】:

    标签: mysql sql


    【解决方案1】:

    这样的事情怎么样?

    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)
    

    编辑修复一些愚蠢的错误:

    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 子句中的字段名,只选择了“人”字段,并添加了DISTINCT 以便每个人只获得一行。

    【讨论】:

    • 必须更改您的查询以使其运行,如果我做了正确的更改,请告诉我:更改:codeSELECT g.name 'guy', a.name 'attribute', v ._value 'value'code for:code*code 将每个条件从:codea1 = 'age' and v1 > 10 ...code 更改为:codea1.name = ' age' 和 v1._value > 10 ...code 在运行查询后它给了我 260 行的结果,我应该包括一个 group by 子句吗?没有 where 子句它给了我 3072 行的结果,不会有性能问题吗?,我应该如何解释这个结果?感谢您的快速回复:)
    • @rhinojosa 不需要回答我,它已经在这里了。没有性能问题。 WHERE 语句就像 if 语句一样,它只获取符合这些条件的东西。这不会有任何性能问题,这里的答案只是根据用户想要搜索的内容加入。就是这样。
    • @Nathan,感谢 Nathan 的评论,我仍然对为什么在结果中返回这么多行感到有些困惑,您认为我可以修改 Javier 的查询以使结果更短吗?也许是一个 group by,这样它就只显示男人的名字一次
    • @rhinojosa 是的,肯定的,或者您可以将每个联接更改为 INNER JOIN 以仅创建一行,并且一切都已经存在。尝试这个。或者您可以创建其他变量来保存其他查询,就像一个变量表示苹果的值,一个表示年龄,这样每个人只会显示一行
    • @rhinojosa:查看编辑,现在它只显示男人的身份,所以每个男人应该只有一行。要获取该人的所有数据,我会进行第二次查询,因此您可以优化一个用于搜索,另一个用于显示。我想可以在不破坏结构的情况下添加它;但调试起来真的很难。
    【解决方案2】:

    以下内容将使您的条件或多或少简单明了,尽管我不能保证它对 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
           )
    

    (我在这里假设一个人不能有重复的属性。)

    【讨论】:

    • 这完美!非常简洁和简单的答案,我用各种疯狂的查询对其进行了测试,它似乎在每种情况下都有效,我想我会接受你的回答:)
    【解决方案3】:

    这样的事情可能是一种选择:

    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
    

    您将外部“或”条件转换为 UNION,您的“与”条件可以在通常的“having count(*) 匹配条件数”中处理。

    我不知道这种方法是否适用于你老板希望你做的所有事情,但也许会有所帮助。

    【讨论】:

    • 这看起来很有希望 :),如果人们开始拥有大量属性(例如 30 或更多)而我最终拥有 100,000 或更多的人,您认为会有任何性能问题吗?
    • @rhinojosa:我猜想限制的问题是有多少条件比家伙和属性的数量多(当然假设你有适当的索引)。
    • 我认为你的答案和哈维尔的答案都非常有希望,你认为我们可以混合它们并改进查询吗?,我可能要到明天才能回答,时间不早了,我需要睡觉,对此感到抱歉:s,祝您有美好的一天:)
    【解决方案4】:

    如果问题是“问题在于属性不是列,而是行”,那么视图如何。您无法更改数据库架构,但您可能会考虑以下视图:

    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 ) 
    

    这一切是否最终对你有帮助,你必须判断,但这是我最初阅读问题时想到的。当然看起来可读;(

    【讨论】:

    • 感谢回复 :),我无法运行你的 SQL 语句,你的意思是写这个吗?:codeCREATE VIEW the_attributes as select a.id, v._value from attribute a JOIN _value v ON v.attribute_id = a.idcode,你能告诉我从这里去哪里吗?我仍然将值和属性作为行,我是否需要自行加入视图或类似的东西?
    【解决方案5】:

    试试这个,也许这会有所帮助。

    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'
    

    如果您还需要什么,请告诉我。

    【讨论】:

    • 谢谢回复 :),这有什么区别?:codeWHERE a.ID = v.attribute_ID;code,你能帮我把这个条件包括在你的询问? code(年龄 > 10 和美元 2 和糖果 code,对不起,我自己不这样做,我不知道如何:s
    猜你喜欢
    • 2013-06-17
    • 2011-04-14
    • 2014-04-30
    • 2016-12-02
    • 1970-01-01
    • 2015-02-07
    • 1970-01-01
    • 2011-06-22
    • 1970-01-01
    相关资源
    最近更新 更多