SQL - 连接表上的查询

Posted

技术标签:

【中文标题】SQL - 连接表上的查询【英文标题】:SQL - where queries on join table 【发布时间】:2014-07-25 22:34:47 【问题描述】:

我正在尝试根据与listing_options 的has_many 关系查询一个表listings。列表选项有不同的类型,我想做一个可以将列表与列表选项匹配的 SQL 查询。这是 SQL 查询 atm:

SELECT "listings".* FROM "listings" INNER JOIN "listing_options" ON 
"listing_options"."listing_id" = "listings"."id" WHERE ("listings".state IS NULL) AND
(listing_options.option_id IN ('1','2')) AND
(listing_options.option_id IN ('4','5','7')) LIMIT 12 OFFSET 0

假设我有两个列表,选项值分别为 (1,5) 和 (1,6)。我希望查询返回第一个列表,而不是第二个。问题是查询一次只考虑一个选项值,即选项值不能同时为 1 和 5,因此查询返回空。

ActiveRecord 生成 SQL:

Listing.joins(:listing_options).where("listing_options.option_id IN (?)", [1,2]).where("listing_options.option_id IN (?)", [4,5,7])

注意:如果查询的一部分被省略,这样构造查询会导致返回重复记录,这可能是问题的一部分。

包含包含的相同查询(也没有返回结果):

Listing.includes(:listing_options).where("listing_options.option_id IN (?)", [1,2]).references(:listing_options).where("listing_options.option_id IN (?)", [4,5,7])

类列表 has_many :listing_options has_and_belongs_to_many :options, join_table: :listing_options

类选项 属于_to :option_type, inverse_of: :option

我尝试反转查询无济于事(尽管这样做也会更复杂,因为我需要指定 option_ids 以避免基于选项类型)。

例子:

Listing.includes(:listing_options).where("listing_options.option_id NOT IN (?)", [3,6,8]).references(:listing_options)

此查询返回列表 - 超出应有的范围。对于大量列表和最多四种列表选项(列表选项的附加查询),查询应该相当有效。出于这个原因,我只想进行一次查询,而不是重复查询结果。

编辑 - 解决方案 - 来自 @ErwinBrandstetter 的 SQL 编写为搜索范围

.where('EXISTS (SELECT 1 FROM listing_options o1 JOIN listing_options o2 USING (listing_id) WHERE o1.listing_id = id AND o1.option_id IN (?) AND o2.option_id IN (?))', [1,2], [4,5,7])

【问题讨论】:

您正在尝试查找具有至少一个选项[1,2] 和至少一个选项[4,5,7] 的列表?所以带有选项[1,5] 的东西可以,但[1,2,3][4,7,11] 不行? 这是relational division的特例。 【参考方案1】:

查询没有返回任何结果,因为您指定了 2 个相反的条件:listing_options.option_id IN ('1','2')listing_options.option_id IN ('4','5','7')。当您连接表时,where 子句将单独应用于连接表的每个组合。

要获得您想要的结果(所有列表都有不同的选项),您需要多次加入 listing_options 表,如下所示:

SELECT l.* FROM listings l
    INNER JOIN listing_options o1 ON l.id = o1.listing_id
    INNER JOIN listing_options o2 ON l.id = o2.listing_id
  WHERE l.state IS NULL
    AND o1.option_id IN ('1','2')
    AND o2.option_id IN ('4','5','7')

或者您也可以使用子选择,如下所示:

SELECT * FROM listings
  WHERE state IS NULL
    AND id IN (SELECT listing_id FROM listing_options WHERE option_id IN ('1','2'))
    AND id IN (SELECT listing_id FROM listing_options WHERE option_id IN ('4','5','7'))

如您所见,使用ActiveRecord 更容易生成第二个查询,特别是如果您的选项数量是动态的:

options = [[1,2], [4,5,7]]
query = Listing.where(state: nil)
options.each do |array|
  query = query.where('id IN (SELECT listing_id FROM listing_options WHERE option_id IN (?))', array)
end
query.load

【讨论】:

【参考方案2】:

我建议使用EXISTS 半连接,其中您将listing_options 连接到自身(根据需要多次)。

SELECT *
FROM   listings l
WHERE  state IS NULL
AND    EXISTS (
   SELECT 1
   FROM   listing_options o1
   JOIN   listing_options o2 USING (listing_id)
   WHERE  o1.listing_id = l.id
   AND    o1.option_id IN (1,2)
   AND    o2.option_id IN (4,5,7)
   );

您只能使用多个连接,如@rabusmar's first query(或下面链接答案中的“6)Sean”中演示的那样)如果最多可以有 一个匹配option_id每组。否则,您将增加行数,这将需要重新分组。昂贵的、不被调用的工作。

EXISTS 通常比 IN 快,同时在使用 NULL 值时也不会表现出棘手的行为。

我们在这个相关问题下组装了一系列用于关系划分的技术:

How to filter SQL results in a has-many-through relation

【讨论】:

以上是关于SQL - 连接表上的查询的主要内容,如果未能解决你的问题,请参考以下文章

优化大型表上的 SQL 连接

使用 2GB+ 加速单个表上的 SQL 查询

同一张表上的多个连接,在一个查询中计数

更新查询在同一张表上的 Sql 查询死锁

小表上的简单 SQL 查询执行时间过长

一个表上的简单 SQL 查询