这是一个非常基本的查询,我无法弄清楚......

假设我有一个像这样的两列表:

userid  |  roleid
--------|--------
   1    |    1
   1    |    2
   1    |    3
   2    |    1

我想获得所有不同的用户 ID roleids 1、2 和 3。使用上面的示例,我想要返回的唯一结果是 userid 1.我该怎么做呢?

有帮助吗?

解决方案

SELECT userid
FROM UserRole
WHERE roleid IN (1, 2, 3)
GROUP BY userid
HAVING COUNT(DISTINCT roleid) = 3;

要任何阅读:我的答案是简单明了的,并得到了“接受”的地位,但请你去阅读的答案通过@cletus给出。它具有更好的性能。


Justing想大声,另一种方式编写的自联接由@cletus描述是:

SELECT t1.userid
FROM userrole t1
JOIN userrole t2 ON t1.userid = t2.userid
JOIN userrole t3 ON t2.userid = t3.userid
WHERE (t1.roleid, t2.roleid, t3.roleid) = (1, 2, 3);

这可能是更容易阅读的你,和MySQL支持类似的元组的比较。 MySQL也知道如何利用覆盖索引智能化此查询。刚刚经历EXPLAIN运行它,请参阅“使用索引”中的笔记全部三个表,这意味着它读取索引,甚至没有触及数据行。

我跑这个查询了我的MacBook上使用MySQL 48年1月5日210万行(用于PostTags堆栈溢出的7月份的数据转储),并在1.08秒返回的结果。在与分配给innodb_buffer_pool_size足够的内存像样的服务器,它应该是更快的。

其他提示

好吧,我对此投了反对票,所以我决定测试一下:

CREATE TABLE userrole (
  userid INT,
  roleid INT,
  PRIMARY KEY (userid, roleid)
);

CREATE INDEX ON userrole (roleid);

运行这个:

<?php
ini_set('max_execution_time', 120); // takes over a minute to insert 500k+ records 

$start = microtime(true);

echo "<pre>\n";
mysql_connect('localhost', 'scratch', 'scratch');
if (mysql_error()) {
    echo "Connect error: " . mysql_error() . "\n";
}
mysql_select_db('scratch');
if (mysql_error()) {
    echo "Selct DB error: " . mysql_error() . "\n";
}

$users = 200000;
$count = 0;
for ($i=1; $i<=$users; $i++) {
    $roles = rand(1, 4);
    $available = range(1, 5);
    for ($j=0; $j<$roles; $j++) {
        $extract = array_splice($available, rand(0, sizeof($available)-1), 1);
        $id = $extract[0];
        query("INSERT INTO userrole (userid, roleid) VALUES ($i, $id)");
        $count++;
    }
}

$stop = microtime(true);
$duration = $stop - $start;
$insert = $duration / $count;

echo "$count users added.\n";
echo "Program ran for $duration seconds.\n";
echo "Insert time $insert seconds.\n";
echo "</pre>\n";

function query($str) {
    mysql_query($str);
    if (mysql_error()) {
        echo "$str: " . mysql_error() . "\n";
    }
}
?>

输出:

499872 users added.
Program ran for 56.5513510704 seconds.
Insert time 0.000113131663847 seconds.

这会增加 500,000 个随机用户角色组合,并且大约有 25,000 个符合所选条件的组合。

第一个查询:

SELECT userid
FROM userrole
WHERE roleid IN (1, 2, 3)
GROUP by userid
HAVING COUNT(1) = 3

查询时间:0.312秒

SELECT t1.userid
FROM userrole t1
JOIN userrole t2 ON t1.userid = t2.userid AND t2.roleid = 2
JOIN userrole t3 ON t2.userid = t3.userid AND t3.roleid = 3
AND t1.roleid = 1

查询时间:0.016秒

这是正确的。我建议的加入版本是 比聚合版本快二十倍。

抱歉,我这样做是为了在现实世界中生活和工作,在现实世界中我们测试 SQL,结果不言而喻。

这其中的原因应该是很清楚的。聚合查询的成本将随着表的大小而变化。每行都经过处理、聚合和过滤(或不通过) HAVING 条款。连接版本将(使用索引)根据给定角色选择用户子集,然后对照第二个角色检查该子集,最后对照第三个角色检查该子集。每个 选择 (在 关系代数 术语)适用于越来越小的子集。由此你可以得出结论:

连接版本的性能变得更好,匹配发生率更低。

如果只有 500 个用户(在上面的 50 万个示例中)具有三个指定的角色,则加入版本将显着加快。聚合版本不会(并且任何性能改进都是传输 500 个用户而不是 25k 个用户的结果,而加入版本显然也得到了 25k)。

我也很好奇真正的数据库(即 Oracle)将如何处理这个问题。因此,我基本上在 Oracle XE 上重复了相同的练习(与上一个示例中的 MySQL 在同一台 Windows XP 桌面计算机上运行),结果几乎相同。

连接似乎不受欢迎,但正如我所演示的,聚合查询可能会慢一个数量级。

更新: 经过一些 广泛的测试, ,情况比较复杂,答案将取决于您的数据、数据库和其他因素。这个故事的寓意是测试、测试、测试。

在经典的方法来做到这一点是把它当作一个关系分割问题。

在英国:选择那些对他们没有期望的角色ID值的缺少用户

我假设你必须其中的UserRole表是指一个用户表,我假设所期望的角色ID的值是在表中:

create table RoleGroup(
  roleid int not null,
  primary key(roleid)
)
insert into RoleGroup values (1);
insert into RoleGroup values (2);
insert into RoleGroup values (3);

我也将承担所有相关的字段不可为空,所以有与没有惊喜或NOT EXISTS。下面是表达上面的英文SQL查询:

select userid from Users as U
where not exists (
  select * from RoleGroup as G
  where not exists (
    select R.roleid from UserRole as R
    where R.roleid = G.roleid
    and R.userid = U.userid
  )
);

把它写的另一种方法是这样的

select userid from Users as U
where not exists (
  select * from RoleGroup as G
  where G.roleid not in (
    select R.roleid from UserRole as R
    where R.userid = U.userid
  )
);

这可能会或可能不会最终被高效,视指标,平台,数据等在网上搜索“关系师”,你会发现很多东西。

假设用户ID,角色ID被包含在唯一索引(意味着不可能有2个记录其中userid = x和角色ID = 1

select count(*), userid from t
where roleid in (1,2,3)
group by userid
having count(*) = 3
select userid from userrole where userid = 1
intersect
select userid from userrole where userid = 2
intersect
select userid from userrole where userid = 3

这会不会解决这个问题?一个解决方案是多么好这个典型的关系型数据块?将查询优化器自动优化呢?

如果您需要任何形式的一般性这里(3不同的角色组合或不同的正作用组合)......我建议你使用一个位屏蔽系统,为您的角色,并使用位运算符来进行查询。 ..

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top