我有以下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
但是这并没有像第一个声明那样在结果中得到尽可能多的信息。那么我怎么能做一些更相同的事情呢?我试过把它放在一起,但我太新了,不能解决它,我只是得到错误...
LONG UPDATE:
戈登·林诺夫指出这是一个奇怪的要求。而且我知道,我觉得这也很奇怪,有一个查询返回同一资源的多行。但我不知道如何以任何其他方式做到这一点,也许我完全错误的轨道,因为它只是打击我原始请求(获得列表中与ANY匹配的类别的资源的所有行)完全不符合我的要求......这是我的整体要求:
首先,我认为db的这一部分的模型可能会有所帮助。
(BTW类别与自身有关系,因为它使用邻接模型存储为层次结构,其中每个类别存储其父ID,如果有人想知道该箭头...)
1:查找类别与列表中的任何值匹配的所有资源。但是(对于这些资源中的每一个来说,这都是不够的),我需要知道资源及其所有类别......
让我用一个简单的例子来解释这一点:
你可以看到这是一对多关系。资源(例如,标题为“18世纪新英格兰的木工简介”)可以与许多类别相关联(例如,category.name =“subject”value =“Carpentry”,category.name =“subject”value = “Wood”,category.name =“subject”value =“New England”,category.name =“subject”value =“History”)。请注意,此示例已简化,但您可以看到基本概念。
现在,如果用户搜索与“木工”和“绘画”类别中的任何一个匹配的资源,则资源“18世纪新英格兰木工简介”应显示在结果中,因为其中一个类别匹配。但是,这就是问题,为什么戈登觉得我的要求很奇怪:在搜索结果中我想向用户展示,我想列出标题“18世纪新英格兰木工简介”以及显示所有标题被分类的类别,即使用户没有搜索它们 - 为了更好地概述该资源的完整主题。
那我怎么能这样做?我能想到的唯一方法就是我的问题中的第一个陈述,但正如我所说,它只是打击了我,它并没有'给我一个资源可能有的所有类别,只有那些实际搜索过的......
当然,我可以先对结果进行查询,但每行只能获得一行。然后执行第二个查询,查找结果中每个资源的所有类别。但是如果第一个查询给了我1000个结果(这将是常见的),那么要获得所有这些的类别,我将不得不做1000个查询来获取每个类别...听起来这样会给我带来性能问题......
我是否以错误的方式思考这个问题?还有另一种方法可以完成我想做的事吗?即,给我查询选择的资源,以及该资源的所有相关类别......
2:嗯,经过那么长的解释之后,第二个要求更容易解释:同样关于为所选资源获取所有类别的事情,但这次查询中的选择应该只获得那些匹配ALL的资源提供的值。但是,仅仅因为我再次提供查询中的所有值并不意味着我已经拥有所有类别,因为结果中的每个资源实际上可能包含更多(和其他)类别,并且在将结果呈现为在第一个(任何)要求中提到。
答案 0 :(得分:2)
更新2 速度问题
速度改进(以避免为每一行执行子查询)是创建一个临时表,其资源ID与子查询匹配,并通过加入它来在主查询中使用它。 / p>
/*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 一些评论
在这两种情况下,您都希望类别过滤仅用作匹配资源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
)
演示
答案 1 :(得分:1)
您可以将结果重新加入:
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;
这似乎是一个奇怪的请求,因为每个资源将获得(至少)两行,每个类别一个。
另外,不要对列别名使用单引号。这些只应用于字符串(和日期)常量。