获取与 SQL 查询中的列表匹配的所有行
Posted
技术标签:
【中文标题】获取与 SQL 查询中的列表匹配的所有行【英文标题】:Get all rows matching a list in a SQL query 【发布时间】:2014-02-19 02:35:52 【问题描述】:我有以下 SQL 查询,它选择与列表 (9, 10) 中的任何值匹配的任何行:
SELECT
r.id, r.title,
u.name as 'Created By',
c.name as 'Category',
c.value,
cr.category_id
FROM
category_resource cr
INNER JOIN resource r
ON cr.resource_id = r.id
INNER JOIN user u
ON r.created_by = u.id
INNER JOIN category c
ON cr.category_id = c.id
WHERE cr.category_id IN ('10', '9');
我已经尝试找出相反的方法,我也需要,即选择匹配所有值的行。
我读过关于使用类似这样的语句:
SELECT
r.id, r.title
FROM
resource r
WHERE
id IN (
SELECT
resource_id
FROM
category_resource
WHERE
category_id IN (9, 10)
GROUP BY
resource_id
HAVING
COUNT(DISTINCT category_id) = 2
);
这是我尝试根据我的需要调整这个答案: SQL Server - select rows that match all items in a list
但这并没有像第一个语句那样让我在结果中获得更多信息。那么我怎么能做一些更等价的事情呢?我试图把它放在一起,但我对 SQL 太陌生了,无法弄清楚,我只是得到错误......
长期更新:
Gordon Linoff 指出这是一个奇怪的要求。而且我知道,我也觉得很奇怪,有一个查询会为同一资源返回多行。但是我不知道该怎么做也完全满足不了我的要求……
这是我的整体要求:
首先,我认为db这部分的模型可能会有所帮助。
(顺便说一句,类别与自身也有关系,因为它被存储为层次结构,使用邻接模型,每个类别都存储其父 ID,如果有人想知道那个箭头...)
1:查找类别与列表中的任何值匹配的所有资源。但是(这是不够的)对于这些资源中的每一个,我需要知道资源以及它拥有的所有类别......
让我用一个简单的例子来解释这一点:
如您所见,这是多对多的关系。一个资源(例如标题为“18 世纪新英格兰木工简介”的资源)可以与许多类别相关联(例如 category.name = ”subject” value = ”Carpentry”, category.name=”subject” value = “木头”,类别名称=“主题”值=“新英格兰”,类别名称=“主题”值=“历史”)。请注意,此示例已简化,但您会看到基本思想。
现在,如果用户搜索与“Carpentry”和“Painting”任何类别匹配的资源,则资源“Introduction to carpentry in 18th century New England”应该会出现在结果中,因为其中一个类别匹配.但是,这就是问题所在,为什么 Gordon 觉得我的要求很奇怪:在我想呈现给用户的搜索结果中,我想列出标题“18 世纪新英格兰木工简介”以及一个显示所有即使用户没有搜索它们,也可以对标题进行分类 - 以便更好地了解此资源的完整主题。
那我该怎么做呢?我能想到的唯一方法是我的问题中的第一个陈述,但正如我所说,它没有给我一个资源可能拥有的所有类别,只有那些实际搜索过的类别……
当然,我可以先查询结果,每个结果只得到一行。然后进行第二次查询,在结果中查找每个资源的所有类别。但是,如果第一个查询给了我 1000 个结果(这很常见),那么要获得所有这些的类别,我将不得不做 1000 个查询来获得每个类别的类别……听起来这会给我带来性能问题……
我是不是想错了?还有其他方法可以完成我想做的事情吗?即给我查询选择的资源,以及该资源的所有相关类别...
2:好吧,经过这么长的解释,第二个要求更容易解释:再次为选定的资源获取所有类别,但这次查询中的选择应该只获取匹配 ALL 的那些资源提供的值。但是,仅仅因为我再次提供了查询中的所有值并不意味着我已经拥有所有类别,因为结果中的每个资源实际上可能有更多(和其他)类别,并且在将结果呈现为时我也需要这些类别在第一个 (ANY) 要求中提到。
【问题讨论】:
返回较少的结果是正常的,因为您只得到重叠 是的,但我并不是说我不希望得到更少的结果,我只是说我需要在结果中获得更多的列,就像第一个查询一样。但是,我发现问题有点复杂,请看我刚才的编辑。 【参考方案1】:更新 2 速度问题
速度改进(避免为每一行执行子查询)是创建一个临时表,其资源 id 与子查询匹配,并通过连接它在主查询中使用它。
/*Create a temporary table with the ids we want (the subquery)*/
CREATE TEMPORARY TABLE Matching_Resources (INDEX(resource_id))
AS (
SELECT
resource_id
FROM
category_resource
WHERE
category_id IN (4,1)
GROUP BY
resource_id
HAVING
COUNT(DISTINCT category_id) = 2
);
SELECT
r.id, r.title,
u.name AS 'Created By',
GROUP_CONCAT( CONCAT('[',c.name,',',c.value,',',CAST(c.id as CHAR),']') separator ' // ') AS 'Categories'
FROM
resource r
INNER JOIN Matching_Resources mr
ON r.id = mr.resource_id
INNER JOIN category_resource cr
ON r.id = cr.resource_id
INNER JOIN category c
ON cr.category_id = c.id
INNER JOIN user u
ON r.created_by = u.id
GROUP BY r.id
更新 1 一些 cmets
在这两种情况下,您都希望类别过滤仅充当匹配资源 ID 的过滤器。因此需要将其设为子查询,以免影响主查询,主查询只需要限制资源,返回所有匹配的类别。
所以WHERE r.id IN (..)
部分必须存在于两种解决方案中。您已经知道如何在其中进行过滤(因为我只使用您提供的相同代码)
对于匹配任何提供的类别的要求
SELECT
r.id, r.title,
u.name as 'Created By',
c.name as 'Category',
c.value,
cr.category_id
FROM
resource r
INNER JOIN category_resource cr
ON r.id = cr.resource_id
INNER JOIN category c
ON cr.category_id = c.id
INNER JOIN user u
ON r.created_by = u.id
WHERE
r.id IN
(
SELECT
resource_id
FROM
category_resource
WHERE
category_id IN (6,1)
)
http://sqlfiddle.com/#!3/d9486/8/0的演示
对于匹配所有提供的类别的要求
SELECT
r.id, r.title,
u.name as 'Created By',
c.name as 'Category',
c.value,
cr.category_id
FROM
resource r
INNER JOIN category_resource cr
ON r.id = cr.resource_id
INNER JOIN category c
ON cr.category_id = c.id
INNER JOIN user u
ON r.created_by = u.id
WHERE
r.id IN
(
SELECT
resource_id
FROM
category_resource
WHERE
category_id IN (1,4)
GROUP BY
resource_id
HAVING
COUNT(DISTINCT category_id) = 2
)
http://sqlfiddle.com/#!3/d9486/10/0的演示
【讨论】:
漂亮!像魅力一样工作(除了我必须删除方括号,也许是不同的 SQL 语法,我使用的是 mysql)。如果您只能对它的作用提供一些小小的解释,我将不胜感激......另外,请告诉我,这是做我想要完成的事情的正确方法吗?我知道我得到了多个结果,然后在我的 Java 代码中我将不得不“清除”重复的资源,只需在循环中连接每个的 Category 值。不过这很好,而且速度很快。但只是想确保我没有错过更好的解决方案! 顺便说一句,我现在将其标记为已回答,很好的答案!谢谢!但是,如果你能用一些关于我上面问题的 cmets 来完成它,那就太好了。 @AndersSvensson 您可以使用 GROUP_CONCAT 将多行合并为一个 demo at sqlfiddle.com/#!8/4bab6/9/0 您可以随意格式化它您将从 java 中进行的解析.. @AndersSvensson 还添加了一小段来解释这个概念。 (这实际上只是您的两种解决方案的组合)。您的第二个返回的结果很少,因为您没有将其加入类别.. 好的,谢谢。这有助于理解它是如何工作的。尽管您的答案是正确的并且有效,但发现了一个问题:不幸的是,性能下降到无法使用。而对于 5000 行资源,原始语句花费了 10 毫秒,而这条语句 (ALL) 花费了 66 秒......我猜没有办法解决这个问题?【参考方案2】:您可以将结果重新加入:
SELECT u.name as "Created By", c.name as 'Category', c.value, cr.category_id
FROM resource r join
user u
on r.created_by = u.id join
(SELECT resource_id
FROM category_resource
WHERE category_id IN (9, 10)
GROUP BY resource_id
HAVING COUNT(DISTINCT category_id) = 2
) crr
on r.id = crr.resource_id join
category_resource cr
on cr.resource_id = r.id join
category c
on cr.category_id = c.id;
这似乎是一个奇怪的请求,因为您将获得(至少)每个资源两行,每个类别一个。
另外,不要对列别名使用单引号。这些只能用于字符串(和日期)常量。
【讨论】:
谢谢,这很有帮助。我刚刚发现,虽然我没有充分解释这个问题,如果你能提供帮助,请查看我的编辑...以上是关于获取与 SQL 查询中的列表匹配的所有行的主要内容,如果未能解决你的问题,请参考以下文章
获取 Pandas DataFrame 列中字符串列表中的所有行 - 此模式具有匹配组