【问题标题】:Select values that meet different conditions on different rows?在不同的行上选择满足不同条件的值?
【发布时间】:2010-10-03 09:12:18
【问题描述】:

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

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

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

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

【问题讨论】:

    标签: mysql sql relational-division sql-match-all


    【解决方案1】:

    好的,我对此投了反对票,所以我决定对其进行测试:

    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.312s

    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.016s

    没错。我提出的加入版本比聚合版本快 20 倍。

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

    这样做的原因应该很清楚。聚合查询的成本会随着表的大小而增加。通过HAVING 子句对每一行进行处理、聚合和过滤(或不过滤)。加入版本将(使用索引)根据给定角色选择用户子集,然后将该子集与第二个角色进行检查,最后将该子集与第三个角色进行检查。每个selection(在relational algebra 术语中)都适用于越来越小的子集。由此你可以得出结论:

    加入版本的性能更好,匹配率更低。

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

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

    似乎不赞成加入,但正如我所展示的,聚合查询可能会慢一个数量级。

    更新:经过extensive testing,情况更加复杂,答案将取决于您的数据、您的数据库和其他因素。故事的寓意是测试、测试、测试。

    【讨论】:

    • 这个 dv 不是来自我...但是说真的...你会把它放在你的系统中吗?
    • 好像少了点什么。 t2 和 t3 是什么?同一张表的别名?如果修改成这样可能会起作用。
    • 联接几乎总是比等效的聚合查询快。但除非有速度要求(由分析决定),否则我不会牺牲清晰度......
    • 如果将 t2 和 t3 的 roleId 测试移到 WHERE 子句中会有什么不同吗?在我看来(至少在概念上)它们属于那里,我希望它不会影响性能 - 但我还没有测试过。
    • 请注意:请注意,当表中有双重条目时,这两个查询是不等价的,并且如果在 role_id 列中存在 NULL,结果也可能会有所不同。虽然我猜该表在两列上都有一个 pk。
    【解决方案2】:

    如果您需要任何类型的通用性(不同的 3 角色组合或不同的 n 角色组合)...我建议您对角色使用位掩码系统并使用位运算符来执行查询。 ..

    【讨论】:

    • -1 糟糕的想法。使用关系数据库作为关系数据库。
    【解决方案3】:
    SELECT userid
    FROM UserRole
    WHERE roleid IN (1, 2, 3)
    GROUP BY userid
    HAVING COUNT(DISTINCT roleid) = 3;
    

    对于阅读本文的任何人:我的回答简单明了,并且获得了“已接受”状态,但请阅读@cletus 给出的answer。它的性能要好得多。


    只是大声思考,另一种编写@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 5.1.48 运行了超过 210 万行的查询(PostTags 的 Stack Overflow 7 月数据转储),它在 1.08 秒内返回了结果。在有足够内存分配给innodb_buffer_pool_size 的体面服务器上,它应该会更快。

    【讨论】:

      【解决方案4】:

      假设 userid,roleid 包含在唯一索引中(意味着不能有 2 个记录,其中 userid = x 和 roleid = 1

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

      【讨论】:

        【解决方案5】:

        执行此操作的经典方法是将其视为关系除法问题。

        英文:选择没有缺少任何所需角色标识值的用户。

        我假设您有一个 UserRole 表所引用的 Users 表,并且我假设所需的 roleid 值在一个表中:

        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);
        

        我还将假设所有相关列都不能为 NULL,因此 IN 或 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 值(最内层SELECT) 丢失(最里面的 NOT)。 (“没有遗漏”与“没有任何不在其中”j
        【解决方案6】:
        select userid from userrole where userid = 1
        intersect
        select userid from userrole where userid = 2
        intersect
        select userid from userrole where userid = 3
        

        这不能解决问题吗?这在典型的关系数据库上的解决方案有多好?查询优化器会自动优化吗?

        【讨论】:

        猜你喜欢
        • 2019-04-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-12-29
        相关资源
        最近更新 更多