具有复杂和/或条件的规范化表上的高效MySQL查询

时间:2014-06-25 15:16:28

标签: mysql database normalization database-normalization

我有下表:

create table x (
    id integer, 
    property integer
);

我需要有效地运行测试给定id的多个属性值的查询。例如,我可能想要一个查询来获取具有满足条件的属性的所有id: 1而不是(2或3或4或5)

如果我的所有属性都在布尔列中(' t1' =如果id存在属性值1则为true,否则为false等等),那么我可以简单地运行这个非常快速的查询(假设是这样一张桌子):

select * from y where t1 and not (t2 or t3 or t4 or t5);

不幸的是,我有成千上万的房产,所以这不会做。此外,对于查询没有任何押韵或理由,因此虽然我可以将属性组捆绑到概念关系中,但查询边界并不尊重它。此外,这些查询(间接)由用户确定,因此创建视图以期待他们不会有帮助。最后,我们将不断添加具有新属性的数据集,这些属性的关系可能是新的,模糊的或交叉的,这意味着尝试创建关系表可能会成为维护的噩梦。

因此我选择原始架构的原因。

为了尝试完成我的查询,我尝试首先在查询中涉及的字段上创建一个数据透视,然后查询:

create table pivot as (
    select 
        id,
        max(if(property=1,true,false)) as 't1',
        max(if(property=2,true,false)) as 't2',
        max(if(property=3,true,false)) as 't3',
        max(if(property=4,true,false)) as 't4',
        max(if(property=5,true,false)) as 't5'
    from x);
select * from pivot where t1 and not (t2 or t3 or t4 or t5);

但是,这很慢。事实上,它比未经优化的家庭酿造解决方案要慢。

我知道我可以使用子查询生成复杂的查询,但是有限的测试表明性能会更差(除非我错误地构造了查询)。

我该怎么做才能加快查询速度?

3 个答案:

答案 0 :(得分:1)

我认为id不是唯一的,现有记录(some_id, property_id)表示该属性为on

首先,我会注意到I need to efficiently run queries that test multiple property values for a given idI may want a query that gets all ids with a property satisfying the condition: 1 and not (2 or 3 or 4 or 5)可能导致完全不同的查询。

但这是我的想法。更多假设:

  • 总有一个积极的条件可以消除很大一部分ID(否则我会把几个属性加入到一个额外的属性中,这就成了这样一个条件)
  • 任何一对(id, property)都是唯一的(你甚至拥有相应的UNIQUE索引)

现在,如果您在(property, id)上有索引,那么以下查询将从覆盖索引(即快速)获取所有匹配的ids

SELECT id
FROM t1 
WHERE property = 150;

如果此查询导致结果集明显小于整个表,则可以为另一个属性创建另一个快速相关的子查询,从而显着降低结果集。此子查询将需要另一个覆盖索引(id, property),并且相应的UNIQUE索引是它所需要的:

SELECT id
FROM t1 
WHERE property = 150 
  AND NOT EXISTS (
    SELECT 1
    FROM t2
    WHERE t2.id = t1.id AND t2.property = 130
  )
  AND NOT EXISTS (
    SELECT 1
    FROM t2
    WHERE t2.id = t1.id AND t2.property = 90
  );

如果较早的相关子查询结果为false,则不会对该行执行以下所有子查询。这就是订单至关重要的原因。

您需要使用属性顺序,并且可能在执行查询的代码中对其进行硬编码。

UPD:那么,我担心,你没有太多选择。您可以做的最好的事情是在一次通过中遍历索引并计算您需要的内容。然后,查询的速度将主要取决于表中的行数。因此,再次假设您在(id,property)上有UNIQUE索引,您可以编写如下内容:

SELECT id
FROM t1
GROUP BY id
HAVING 
  COUNT(IF(property=150, 1, NULL))
  AND NOT COUNT(IF(property=130, 1, NULL))
  AND NOT COUNT(IF(property=90, 1, NULL));

答案 1 :(得分:0)

如果你有:

P(id,property) -- thing [i] has property #[property]

你想要:

x(i,p1,p2,p3,p4,p5) -- thing [i] has property p1 per [p1] and ...

这是否符合您的需求?

CREATE VIEW x AS
    SELECT id,
        property=p1 AS p1,
        property=p2 AS p2,
        property=p3 AS p3,
        property=p4 AS p4,
        property=p5 AS p5
    FROM P

据推测,你正在做这样的事情:

--  for A(id,...)
SELECT id,... FROM A JOIN x USING(id) WHERE t1 AND NOT (t2 OR t3 OR t4 OR t5)

你只想要带有ids的A行,其中P(id,t1)而不是......。

(如果你做了一个有多个布尔列的表,你想最小化你的storate你可以使用BIT(1)(BIT(m)需要(m + 7)/ 8字节)。你可以使用他们喜欢布尔。

不要自己将多个位编码为值,这会阻止DBMS优化。)

答案 2 :(得分:0)

您是否考虑构建一个由(id,propertybin)组成的表,该表按以下方式填充:

INSERT LookupTable (id, propertybin)
SELECT id, sum(1 << property)
FROM MyTable
GROUP BY id

现在,propertybin为每个ID保存一个二进制值,其中每个位表示MyTable中是否存在0到63之间的属性。我意识到你有超过64个属性,但我认为这个想法可以扩展到保存更多属性 - 因为你现在有1列能够保存有关64个属性的信息,你只需要“100”这样的列就可以了6400个房产的信息。

现在,为了找到满足某个条件的所有id,你可以使用按位逻辑。例如,要查找具有属性0但不是1,2,3或4的所有id,请使用:

SELECT id
FROM LookupTable
WHERE ((propertybin ^ 0x1E) & 0x1F) = 0x1F

这里,我们使用异或位运算符^和值0x1E =二进制11110来过滤非标准。我们使用AND位运算符&,其值为0x1F = binary 11111,表示我们只对前5位感兴趣。

或者,可以使用足够大的VARBINARY类型的列,从而消除每列64个属性的限制。但是,我不确定MySQL提供的可能性来过滤VARBINARY值中的某些位。