如何用 SQL 求出「都关注了某100人」和「都收藏了某100帖子」的所有用户?

回复列表(165|隐藏机器人聊天)
  • @Ta / 2022-08-11 / /

    @无名啊,执行优化可能不蠢,但只是可能,调查研究需要时间。所以我何不把可能性掐灭在萌芽中,给它一定不蠢的SQL语句呢?

  • @Ta / 2022-08-11 / /

    @老虎会游泳,唉,可惜 SQL 本就是个声明式编程语言,大部分时候命运就是要交给别人掌控

  • @Ta / 2022-08-11 / /

    @无名啊,而且我觉得单个复杂SQL语句的可读性也没有拼接SQL来的清晰。还有,如果你使用封装好的数据库操作类,那么背后肯定是拼接SQL,不会是预先生成的复杂语句。

    比如

    function 搜索($名称, $手机) {
        if (empty($名称) && empty($手机)) {
            return null;
        }
        $db = new 数据库操作类();
        $table = $db->select('用户信息表');
        if (!empty($名称)) {
            $table->where('名称', '=', $名称);
        }
        if (!empty($手机)) {
            $table->where('手机', '=', $手机);
        }
        return $table->query();
    }
    

    这样的代码背后肯定是拼接SQL+预处理,没有单个复杂SQL的存在。

  • @Ta / 2022-08-11 / /

    @无名啊

    可惜 SQL 本就是个声明式编程语言,大部分时候命运就是要交给别人掌控

    我的原则是,能不交给SQL的,就不交给SQL。绝不把它当做编程语言,能不在它里面做的运算就不在它里面做。

    毕竟运行网站的web服务器可扩展而且很容易扩展,但MySQL很难扩展。

  • @Ta / 2022-08-11 / /

    @无名啊,对了,关于标题

    「都关注了某100人」和「都收藏了某100帖子」的所有用户
    

    不用查了,直接返回null就好了。100个相同,怎么可能?哪有那么巧。

  • @Ta / 2022-08-11 / /

    @老虎会游泳,标题就是个方便理解『抽象概念』的『具体例子』,我刷刷刷写一堆抽象名词,有谁愿意看呐。。

  • @Ta / 2022-08-11 / /

    @老虎会游泳,要写抽象概念,也要先骗进来再上啊

  • @Ta / 2022-08-11 / /

    @无名啊,我只是想指出,实际的搜索词应该不会有100个那么多。所以拼接SQL带来的性能影响应该可以忽略不计。

    然后再看你的限定条件:

    注意:
    尽量不要使用存储过程/函数、其他编程语言,以及 MySQL 8.0.17+ 的多值索引。
    尽量使用 B+ 树索引(好像PostgreSQL有针对json的高效索引)

    好像符合这个条件的也只有

    SELECT * FROM 员工信息表 WHERE
            员工ID IN (SELECT 员工ID FROM 员工技能表 WHERE 技能 = '技能1')
        AND 员工ID IN (SELECT 员工ID FROM 员工技能表 WHERE 技能 = '技能2')
        AND 员工ID IN (SELECT 员工ID FROM 员工手机表 WHERE 手机 = '手机1')
        AND 员工ID IN (SELECT 员工ID FROM 员工手机表 WHERE 手机 = '手机2');
    

    因为JSON_CONTAINS是函数,并且使用多值索引

  • @Ta / 2022-08-11 / /
    @老虎会游泳,我想到的场景是,楼主是 FBI,正在通过某人零碎的信息,在数据库中筛选得出这个人的具体身份
  • @Ta / 2022-08-11 / /

    @老虎会游泳

    我的原则是,能不交给SQL的,就不交给SQL。绝不把它当做编程语言,能不在它里面做的运算就不在它里面做。

    我有所耳闻,特别是涉及分库分表时

    但我希望,我有能力真的分辨清楚咯:压力真的能传到应用层服务器去

    而不是盲目全放在其他服务器干,数据库整体相比原来:

    1. 该干的活儿,一样没少干
    2. 还多了不必要的压力
      • select 出多余数据
      • 传输过多数据
      • ……
  • @Ta / 2022-08-11 / /

    @老虎会游泳,那个『关系除法』没使用JSON_CONTAINS呀,也没有多值索引,能放到SQLite上运行

  • @Ta / 2022-08-11 / /

    @无名啊,嗯确实,但长度太可怕了

    又是JOIN又是GROUP BY的,它真的能高效运行吗?

    WITH
    
      除以要求技能表(员工ID) AS (
        SELECT DISTINCT 员工ID
          FROM 员工技能表 AS ES1
         WHERE NOT EXISTS (
                   SELECT *
                     FROM 要求技能表
                    WHERE NOT EXISTS (
                              SELECT *
                                FROM 员工技能表 AS ES2
                               WHERE ES2.员工ID = ES1.员工ID
                                 AND ES2.技能 = 要求技能表.技能 ))
      ),
      
      除以要求手机表(员工ID) AS (
        SELECT DISTINCT 员工ID
          FROM 员工手机表 AS ES1
         WHERE NOT EXISTS (
                   SELECT *
                     FROM 要求手机表
                    WHERE NOT EXISTS (
                              SELECT *
                                FROM 员工手机表 AS ES2
                               WHERE ES2.员工ID = ES1.员工ID
                                 AND ES2.手机 = 要求手机表.手机 ))
      )
    
    -- 如果要显示员工详细信息的话
    SELECT 员工表.*
      FROM 除以要求技能表
      JOIN 除以要求手机表 USING(员工ID)
      JOIN 员工表 USING(员工ID)
     GROUP BY 员工ID;
    
  • @Ta / 2022-08-11 / /

    @老虎会游泳,生成点儿随机数据试试?

  • @Ta / 2022-08-11 / /

    @无名啊,你来试试呗,我没有测试环境

  • @Ta / 2022-08-11 / /

    @老虎会游泳,我发现,现在我已经把SQLite等同于一般SQL了。。

    SQLite 都能支持,其他数据库凭啥用不了?!

    的态度。。

    (但近一两个月,SQLite支持FULL OUTER JOIN了。。现在压力给到MySQL

  • @Ta / 2022-08-11 / /

    @老虎会游泳,你觉得这两坨除以要求技能表除以要求手机表能优化合并吗?

    (我主要就是想优化合并这个 SQL 的。。)

  • @Ta / 2022-08-11 / /

    @老虎会游泳,《SQL 解惑(第二版)》谜题 21 飞机与飞行员,也提到过类似问题。但他是只有一个条件的:

    有一个表包含飞行员和他们能够驾驶的飞机,还有一个表包含停在飞机棚中的飞机。需要找出能够驾驶飞机棚中每一架飞机的飞行员姓名。

    别人除了用『关系除法』外,还用了另一种简短一些的方法

    SELECT 飞行员
      FROM 飞行员会开啥飞机表 AS PS, 停在飞机棚的飞机表 AS H
     WHERE PS.飞机 = H.飞机
     GROUP BY PS.飞行员
    HAVING COUNT(PS.飞机) = (SELECT COUNT(*) FROM 停在飞机棚的飞机表)
    

    哪个方法快?别人实验总结:

    COUNT(结果) < COUNT(飞行员会开啥飞机表) * 0.25 时,『关系除法』较好,否则上面的方法好

    飞行员会开啥飞机表

    飞行员 飞机
    Celko Piper Cub
    Higgins B-52 Bomber
    Higgins F-14 Fighter
    Higgins Piper Cub
    Jones B-52 Bomber
    Jones F-14 Fighter
    Smith B-1 Bomber
    Smith B-52 Bomber
    Smith F-14 Fighter
    Wilson B-1 Bomber
    Wilson B-52 Bomber
    Wilson F-14 Fighter
    Wilson F-17 Fighter

    停在飞机棚的飞机表

    飞机
    B-1 Bomber
    B-52 Bomber
    F-14 Fighter

    答案

    飞行员
    Smith
    Wilson
  • @Ta / 2022-08-11 / /

    @无名啊,仿写一个:

    公共聊天室文件上传发过言,并且在超级灌水网页插件版块发过帖的用户:

    SELECT c3.uid
    FROM
    (
        SELECT uid FROM
        (
            SELECT uid, count(*) as c FROM 
            (
                SELECT DISTINCT uid, room
                FROM hu60_addin_chat_data
                WHERE room IN ('公共聊天室', '文件上传')
            ) c1
            GROUP BY uid
        ) c2
        WHERE c = 2
    ) c3
    INNER JOIN
    (
        SELECT uid FROM
        (
            SELECT uid, count(*) as c FROM 
            (
                SELECT DISTINCT uid, forum_id
                FROM hu60_bbs_topic_meta
                WHERE forum_id IN (
                    SELECT id
                    FROM hu60_bbs_forum_meta
                    WHERE name IN ('超级灌水', '网页插件')
                )
            ) f1
            GROUP BY uid
        ) f2
        WHERE c = 2
    ) f3
    ON
        c3.uid=f3.uid
    

    截图_选择区域_20220811185938.png(307.73 KB)

  • @Ta / 2022-08-11 / /

    用HAVING子句减少一层嵌套:

    公共聊天室文件上传发过言,并且在超级灌水网页插件版块发过帖的用户:

    SELECT c2.uid
    FROM
    (
        SELECT uid FROM 
        (
            SELECT DISTINCT uid, room
            FROM hu60_addin_chat_data
            WHERE room IN ('公共聊天室', '文件上传')
        ) c1
        GROUP BY uid
        HAVING count(*) = 2
    ) c2
    INNER JOIN
    (
        SELECT uid FROM 
        (
            SELECT DISTINCT uid, forum_id
            FROM hu60_bbs_topic_meta
            WHERE forum_id IN (
                SELECT id
                FROM hu60_bbs_forum_meta
                WHERE name IN ('超级灌水', '网页插件')
            )
        ) f1
        GROUP BY uid
        HAVING count(*) = 2
    ) f2
    ON
        c2.uid=f2.uid
    

    截图_选择区域_20220811190027.png(286.57 KB)

  • @Ta / 2022-08-11 / /

    比这种写法慢很多啊:

    SELECT uid, name FROM hu60_user WHERE
            uid IN (SELECT DISTINCT uid FROM hu60_addin_chat_data WHERE room = '公共聊天室')
        AND uid IN (SELECT DISTINCT uid FROM hu60_addin_chat_data WHERE room = '文件上传')
        AND uid IN (SELECT DISTINCT uid FROM hu60_bbs_topic_meta WHERE forum_id = (
                SELECT id FROM hu60_bbs_forum_meta WHERE name = '超级灌水'
            ))
        AND uid IN (SELECT DISTINCT uid FROM hu60_bbs_topic_meta WHERE forum_id = (
                SELECT id FROM hu60_bbs_forum_meta WHERE name = '网页插件'
            ));
    

    截图_选择区域_20220811185435.png(399.60 KB)

添加新回复
回复需要登录