MySQL通过多个数据透视表查询WHERE

时间:2015-05-01 09:23:27

标签: mysql sql

products 
+----+--------+
| id | title  |
+----+--------+
|  1 | Apple  |
|  2 | Pear   |
|  3 | Banana |
|  4 | Tomato |
+----+--------+

product_variants
+----+------------+------------+
| id | product_id | is_default |
+----+------------+------------+
|  1 |          1 |          0 |
|  2 |          1 |          1 |
|  3 |          2 |          1 |
|  4 |          3 |          1 |
|  5 |          4 |          1 |
+----+------------+------------+

properties
+----+-----------------+-----------+
| id | property_key_id |   value   |
+----+-----------------+-----------+
|  1 |               1 | Yellow    |
|  2 |               1 | Green     |
|  3 |               1 | Red       |
|  4 |               2 | Fruit     |
|  5 |               2 | Vegetable |
|  6 |               1 | Blue      |
+----+-----------------+-----------+

property_keys
+----+-------+
| id | value |
+----+-------+
|  1 | Color |
|  2 | Type  |
+----+-------+

product_has_properties
+----+------------+-------------+
| id | product_id | property_id |
+----+------------+-------------+
|  1 |          1 |           4 |
|  2 |          1 |           3 |
|  3 |          2 |           4 |
|  4 |          3 |           4 |
|  5 |          3 |           4 |
|  6 |          4 |           4 |
|  7 |          4 |           5 |
+----+------------+-------------+

product_variant_has_properties
+----+------------+-------------+
| id | variant_id | property_id |
+----+------------+-------------+
|  1 |          1 |           2 |
|  2 |          1 |           3 |
|  3 |          2 |           6 |
|  4 |          3 |           4 |
|  5 |          4 |           1 |
|  6 |          5 |           1 |
+----+------------+-------------+

我需要查询我的数据库,以便选择products,其中某些properties附加到产品本身将这些属性附加到其相关的{{1} }}。同样应将具有相同properties.property_key_id的product_variants分组为:properties

示例案例:

  • 选择(pkey1='red' OR pkey1='blue') AND (pkey2='fruit' OR pkey2='vegetable')的所有产品。这应该只返回番茄。
  • 选择(color='red' AND type='vegetable')的所有产品应返回Apple和Banana

请注意,在上面的示例中我不需要通过properties.value查询,我可以通过properties.id进行查询。

我使用MySQL查询玩了很多,但我遇到的最大问题是通过两个数据透视表加载的属性。加载它们没有问题,但加载它们并将它们与正确的((color='red' OR color='yellow') AND type='fruit')WHEREAND语句组合起来就是。

5 个答案:

答案 0 :(得分:9)

以下代码应该为您提供您正在寻找的内容,但是您应该注意到您的表格当前有一个黄色和蔬菜列表的番茄。显然你希望番茄是红色的而番茄实际上是一种不是蔬菜的水果:

Select distinct title 
from products p
inner join
product_variants pv on pv.product_id = p.id
inner join
product_variant_has_properties pvp on pvp.variant_id = pv.id
inner join
product_has_properties php on php.product_id = p.id
inner join
properties ps1 on ps1.id = pvp.property_id --Color
inner join
properties ps2 on ps2.id = php.property_id --Type
inner join
property_keys pk on pk.id = ps1.property_key_id or pk.id = ps2.property_key_id

where ps1.value = 'Red' and ps2.value = 'Vegetable'

这是SQL小提琴:http://www.sqlfiddle.com/#!9/309ad/3/0

答案 1 :(得分:3)

这是一个令人费解的答案,有可能以更简单的方式进行。但是,鉴于您似乎希望能够通过color = xxtype = xx进行查询,我们显然需要包含这些名称的列,正如您所暗示的那样,这意味着我们需要转动数据

此外,由于我们希望获得每种产品的颜色和类型的所有组合,我们需要执行一种交叉连接,以组合它们。

这引导我们进行查询 - 首先我们获得产品及其变体的所有类型,然后我们将其加入产品及其变体的所有颜色。我们使用union来合并产品和变体属性,以便将它们全部保存在同一列中,而不是要检查多个列。

当然,所有产品都可能没有指定此信息,因此我们一直使用left joins。如果确保产品始终至少有一种颜色,并且至少有一种颜色 - 它们都可以更改为inner joins

另外,在您的示例中,您说tomato的颜色应为red,但在您提供的示例数据中,我确保tomato的颜色为{ {1}}。

无论如何,这是查询:

yellow

这是一个演示:http://sqlfiddle.com/#!9/d3ded/76

如果要获得更多类型的属性,除了颜色和类型之外,还需要修改查询 - 抱歉,但这几乎是你所困扰的,试图转入MySQL的

答案 2 :(得分:2)

简短回答

我会对你得到的那些答案略有不同。虽然很可能有一个纯粹的SQL答案,但我会向你提出的问题是:为什么?

该答案将决定您的下一步。

如果你的答案是尝试学习纯粹的SQL方法,那么这里有一些很好的答案,如果不是全部的那样,你可以获得最多的答案。

如果您的答案是为最终应用程序创建可扩展的动态查询,那么您可以通过依靠编程语言来缓解您的工作。

一点个人背景

我需要使用更多表来转移数据。我决心以最好的方式尝试这样做,而且我花了很多时间研究出最适合我应用的方法。完全了解这可能与您的体验不一样,我会在这里分享我的经验以防万一。

我尝试创建纯SQL解决方案,这些解决方案适用于特定用例,但需要对每个其他用例进行大量调整。当我尝试扩展查询时,我首先尝试创建存储过程。这是一场噩梦,在我的开发过程中很早就意识到这将是一个令人头痛的问题。

我继续使用PHP并创建自己的查询生成。虽然这些代码中的一些已经演变成对我来说非常有用的东西,但我了解到,除非我创建服务库,否则维护很多都会很困难。那时,我意识到我基本上将创建一个对象关系映射器(ORM)。除非我的应用程序 SO 特殊且 SO 唯一,否则市场上没有任何ORM可以接近我做的事情我想,然后我需要借此机会探索为我的应用程序使用ORM。尽管我最初的保留让我做了所有事情但是看了一下ORM,我已经开始使用它了,这有助于我的开发速度显着提高。

达到所需的最终结果

Select all products with (color='red' AND type='vegetable'). This should return only Tomato.
Select all products with ((color='red' OR color='yellow') AND type='fruit') should return Apple and Banana

这可以在ORM中实现。您所描述的内容只是在SQL中松散定义,但实际上在OOP中已经完美地概括了。这就是它在PHP中的样子,仅作为一个例子。

<?
Abtract class AbstractProductType {
    public function __construct() {

    }
}

class Color extends AbstractProductType {

}

class Yellow extends Color {

}

class Red extends Color {

}

class Type extends AbstractProductType {

}

class Vegetable extends Type {

}

class Fruit extends Type {

}

class Product {
     public function setColor(Color $color) {
         //
     }
     public function setType(Type $type) {
         //
     }
}

$product = new Product();
$product->setColor(new Red());
$product->setType(new Fruit());
$result = $product->find();
?>

这背后的想法是你可以在面向对象的编程中充分利用SQL。

稍微低一点的版本是创建一个生成SQL片段的类。我个人的经验是,有限的回报是很多工作。如果你的项目将保持相对较小,它可能会很好。但是,如果你反驳说你的项目会增长,那么ORM可能值得探索。

结论

虽然我不确定您将使用什么语言来查询和操纵您的数据,但是有很棒的ORM不应该打折扣。尽管他们有许多缺点(你可以在互联网上找到很多关于这方面的争论),但我不情愿地相信,尽管对于所有情况肯定不是理想的,但是他们应该考虑一些。如果这不适合您,请准备好自己写出很多JOIN个。当引用表n次并且需要引用回表时,我知道添加引用的唯一方法是创建n JOIN s。

当然,我会非常有兴趣看看是否有更好的方法!

答案 3 :(得分:2)

条件聚合

您可以在having子句中使用条件聚合来查看产品是否具有特定属性。例如,查询同时具有“type vegetable”和“color red”属性的所有产品。

您必须按产品ID和产品变体ID进行分组,以确保您搜索的所有属性都存在于同一变体或产品本身。

select p.id, pv.id from products p
left join product_has_properties php on php.product_id = p.id
left join properties pr on pr.id = php.property_id
left join property_keys pk on pk.id = pr.property_key_id
left join product_variants pv on pv.product_id = p.id
left join product_variant_has_properties pvhp on pvhp.variant_id = pv.id
left join properties pr2 on pr2.id = pvhp.property_id
left join property_keys pk2 on pk2.id = pr2.property_id
group by p.id, pv.id
having (
  count(case when pk.value = 'Color' and pr.value = 'Red' then 1 end) > 0
  and count(case when pk.value = 'Type' and pr.value = 'Vegetable' then 1 end) > 0
) or (
  count(case when pk2.value = 'Color' and pr2.value = 'Red' then 1 end) > 0
  and count(case when pk2.value = 'Type' and pr2.value = 'Vegetable' then 1 end) > 0
)

答案 4 :(得分:2)

问题是什么?(我多次阅读该帖子,而且我仍然没有看到任何实际的问题。)这里的很多答案似乎都在回答问题&#34;什么SQL语句会从这些表中返回结果?&#34;我的回答并没有提供一个例子或者&#34;如何&#34;编写SQL的指南。我的回答解决了一个根本不同的问题。

OP正在经历针对&#34;问题&#34;中显示的表编写SQL的困难。是由于(我所说的)阻抗不匹配&#34;在&#34;关系之间&#34;模型和&#34;实体 - 属性 - 值&#34; (EAV)模型。

SQL旨在与&#34;关系&#34;模型。实体的每个实例都表示为元组,在表中存储一行。实体的属性作为值存储在实体行的列中。

EAV模型与Relational模型有很大不同。它将属性值移出实体行,并将它们移动到其他表中的多个单独行中。如果查询试图模仿查询&#34;关系&#34;那么这会使编写查询变得更加复杂。通过转换来自&#34; EAV&#34;表示回到&#34;关系&#34;表示。

有几种针对EAV模型编写SQL查询的方法,它模拟从Relational模型返回的结果(正如其他答案中提供的示例SQL所证明的那样&#34;问题&#34;。

一种方法是在SELECT列表中使用子查询,将属性值作为实体行中的列返回。

另一种方法是在实体表中的行与属性表中的行之间执行连接,并使用GROUP BY折叠行,并在SELECT列表中,使用条件表达式&#34;挑选&#34;要为列返回的值。

这两种方法都有很多例子。并且两者都不比另一种好,每种方法的适用性实际上取决于具体的用例。

虽然可以针对显示的EAV样式表编写SQL查询,但这些查询比对&#34;关系&#34中存储的数据的等效查询的数量级更复杂 ;模型。

关系模型中的普通查询返回的结果,例如

SELECT p.id
  FROM product p
 WHERE p.color = 'red'

要从EAV模型中的数据返回同一组,需要更复杂的SQL查询,涉及多个表和/或子查询的连接。

一旦我们超越了普通的查询,我们想要从多个相关实体返回属性的查询...作为一个简单的例子,返回过去30天内产品的信息。红色&#39;

SELECT c.customer_name
     , c.address
     , o.order_date
     , p.product_name
     , l.qty
  FROM customer c
  JOIN order o ON ...
  JOIN line_item l ON ...
  JOIN product p ON ...
 WHERE p.color = 'red'
   AND o.order_date >= DATE(NOW()) + INTERVAL 30

从EAV模型中使用SQL得到相同的结果 way 更加复杂和令人困惑,并且可能是令人难以忍受的挫败感。

当然,可以编写SQL。一旦我们设法获得可以返回&#34;正确&#34;的SQL语句。结果集,当表中的行数超出平凡演示时,直到我们期望数据库处理的卷类型......这些查询的性能是可怕的(与从传统返回相同结果的查询相比)关系模型)。

(我们甚至没有涉及仅添加和更新实体属性,强制实体之间的参照完整性等的额外复杂性。)

为什么我们想要这样做? 为什么我们是否需要(或想要)针对EAV模型表编写SQL语句,这些模型表模拟从对关系模型表的查询返回的结果?

最重要的是,如果我们打算使用EAV模型,那么试图使用单个SQL语句返回结果,就像我们从查询中返回一样,我们会好得多一个&#34;关系&#34;模型。

从EAV模型中检索信息的问题更适合于面向对象的编程语言,并提供了一个框架。在SQL中完全结合的东西。