我想我找到了问题的答案,我只是不确定语法,我不断收到SQL错误.
基本上,我想做与IN相反的事情.举个例子:
SELECT *
FROM users INNER JOIN
tags ON tags.user_id = users.id
WHERE tags.name IN ('tag1', 'tag2');
Run Code Online (Sandbox Code Playgroud)
以上将返回任何具有'tag1'OR'tag2'的用户.我希望用户同时拥有.他们必须要返回两个标签.我假设应该使用关键字ALL,但无法使其工作.
谢谢你的帮助.
Lar*_*tig 29
让我们首先谈谈这个问题,然后再详细说明.
在这个问题中,您要做的是从表A中选择行,具体取决于表B中的两个(或一般情况下,两个以上)行中的条件.为了实现这一点,您需要执行以下两项操作之一:
对表B中的不同行执行测试
将表B中感兴趣的行聚合成一行,它以某种方式包含测试表B中原始行所需的信息
我认为,这种问题是人们在VARCHAR字段中创建逗号分隔列表而不是正确规范其数据库的重要原因.
在您的示例中,您希望user根据匹配两个特定条件的行的存在来选择行tags.
有三种方法可以使用技术(1)(测试不同的行).他们使用EXISTS,使用子查询和使用JOIN:
1A.使用EXIST是(在我看来,无论如何)清楚,因为它匹配你想要做的 - 检查行的存在.如果您正在生成动态SQL,那么在编写SQL创建方面可以适度扩展到更多标记,您可以为每个标记添加一个额外的AND EXISTS子句(当然,性能会受到影响):
SELECT * FROM users WHERE
EXISTS (SELECT * FROM tags WHERE user_id = users.id AND name ='tag1') AND
EXISTS (SELECT * FROM tags WHERE user_id = users.id AND name ='tag2')
Run Code Online (Sandbox Code Playgroud)
我认为这清楚地表达了查询的意图.
1B使用子查询也很清楚.由于此技术不涉及相关子查询,因此某些引擎可以更好地优化它(这部分取决于具有任何给定标记的用户数):
SELECT * FROM users WHERE
id IN (SELECT user_id FROM tags WHERE name ='tag1') AND
id IN (SELECT user_id FROM tags WHERE name ='tag2')
Run Code Online (Sandbox Code Playgroud)
这与选项1A的工作方式相同.它(对我来说,无论如何)也很清楚.
1C使用JOIN涉及INNER将tags表连接到users表,每个标签一次.它也不能扩展,因为生成动态SQL更难(但仍然可能):
SELECT u.* FROM users u
INNER JOIN tags t1 ON u.id = t1.user_id
INNER JOIN tags t2 ON u.id = t2.user_id
WHERE t1.name = 'tag1' AND t2.name = 'tag2'
Run Code Online (Sandbox Code Playgroud)
就个人而言,我觉得这比其他两个选项要清晰得多,因为看起来目标是创建一个JOINed记录集而不是过滤用户表.此外,可伸缩性受到影响,因为您需要添加INNER JOIN 并更改WHERE子句.请注意,这种技术跨越了技术1和2,因为它使用JOIN来聚合标记中的两行.
使用COUNT并使用字符串处理有两种主要方法:
2A如果您的标签表被"保护"以防止将相同的标签应用于同一用户两次,则使用COUNTs会容易得多.您可以通过在标记中创建(user_id,name)PRIMARY KEY,或者在这两列上创建UNIQUE INDEX来完成此操作. 如果以这种方式保护行,您可以执行以下操作:
SELECT users.id, users.user_name
FROM users INNER JOIN tags ON users.id = tags.user_id
WHERE tags.name IN ('tag1', 'tag2')
GROUP BY users.id, users.user_name
HAVING COUNT(*) = 2
Run Code Online (Sandbox Code Playgroud)
在这种情况下,您将HAVING COUNT(*)=测试值与IN子句中的标记名称数相匹配.如果每个标记可以多次应用于用户,则这不起作用,因为2的计数可以由两个'tag1'实例生成而没有'tag2'(并且该行符合它不应该的地方)或者'tag1'的两个实例加上 'tag2'的一个实例将创建一个3的计数(并且用户即使它们应该也不符合条件).
请注意,这是性能最具扩展性的技术,因为您可以添加其他标记,而不需要其他查询或JOIN.
如果允许多个标记,则可以执行内部聚合以删除重复项.您可以在我上面显示的相同查询中执行此操作,但为了简单起见,我将打破逻辑到单独的视图中:
CREATE VIEW tags_dedup (user_id, name) AS
SELECT DISTINCT user_id, name FROM tags
Run Code Online (Sandbox Code Playgroud)
然后你回到上面的查询并用tags_dedup替换标签.
2B使用字符串处理是特定于数据库的,因为没有标准的SQL聚合函数可以从多行生成字符串列表.但是,有些数据库提供了扩展功能.在MySQL中,您可以使用GROUP_CONCAT和FIND_IN_SET来执行此操作:
SELECT user.id, users.user_name, GROUP_CONCAT(tags.name) as all_tags
FROM users INNER JOIN tags ON users.id = tags.user_id
GROUP BY users.id, users.user_name
HAVING FIND_IN_SET('tag1', all_tags) > 0 AND
FIND_IN_SET('tag2', all_tags) > 0
Run Code Online (Sandbox Code Playgroud)
注意,这是非常低效的并使用MySQL独特的扩展.
squ*_*man 17
您将再次加入标签表.
SELECT * FROM users
INNER JOIN tags as t1 on t1.user_id = users.id and t1.name='tag1'
INNER JOIN tags as t2 on t2.user_id = users.id and t2.name='tag2'
Run Code Online (Sandbox Code Playgroud)
我会先做你正在做的事情,因为这会得到一个包含'tag1'的所有用户的列表和一个包含'tag2'的所有用户的列表,但显然是在相同的响应中.所以,我们还要补充一些:
做一个group by users(或users.id)然后having count(*) == 2.这将对重复的用户进行分组(这意味着同时包含tag1和tag2的用户),然后有部分将删除只有两个标签之一的用户.
这个解决方案避免添加另一个join语句,但说实话,我不确定哪个更快.人们,随意评论性能部分:)
编辑:只是为了让它更容易尝试,这里是整个事情:
SELECT *
FROM users INNER JOIN
tags ON tags.user_id = users.id
WHERE tags.name = 'tag1' OR tags.name = 'tag2'
GROUP BY users.id
HAVING COUNT(*) = 2
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
13251 次 |
| 最近记录: |