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

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

    @无名啊,那不一样,如果深究起来,每个括号()都构成一个临时表,你给的语句临时表一点不比我少。但是你的要求技能表必须你自己创建并且填充查询条件,我的可不用,查询条件是内联在SQL中的。根据计算机的一般规律,内联通常比外置快。

  • @Ta / 2022-08-11 / /

    @老虎会游泳,另外,多个条件的『关系除法』,我目前会写成这个样子:

    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 / /

    @老虎会游泳,我这个,应该是有四个临时表?无论有多少个参数

    1. json 生成 要求技能表
    2. json 生成 要求手机表
    3. 除以要求技能表
    4. 除以要求手机表
  • @Ta / 2022-08-11 / /

    @老虎会游泳,然后我就看着这两坨除以要求技能表除以要求手机表很不爽,但又不会高效合并。。

  • @Ta / 2022-08-11 / /

    @无名啊,实践是检验真理的唯一标准。我建议你准备一些测试数据进行实际计时对比。

    注意:测试用例必须考虑实际情况,不能过度优化。比如,要求技能表要求手机表的值应该由外部程序在运行时插入,不能预先存储在MySQL里,否则就不是“根据用户输入进行查询”了。外部程序插入要求技能表要求手机表的时间也必须考虑在总时间之内。

  • @Ta / 2022-08-11 / /

    然后我就看着这两坨除以要求技能表、除以要求手机表很不爽,但又不会高效合并。。

    @无名啊,那你为什么不用它呢?总有一天你会发现拼接SQL才是让一切更简单的正确方法,因为它把复杂的变参处理任务从MySQL转移到了外部程序,MySQL不必自己处理它,得到的SQL自然可以干净整洁。

    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');
    
  • @Ta / 2022-08-11 / /

    @老虎会游泳,我怀疑,你的 SQL 改成这样,可以快点儿(说不定连临时表也没了)

    SELECT *
      FROM 员工表
     WHERE EXISTS (SELECT * FROM 员工技能表 WHERE 员工ID = 员工表.ID AND 技能 = '技能1')
       AND EXISTS (SELECT * FROM 员工技能表 WHERE 员工ID = 员工表.ID AND 技能 = '技能2')
       AND EXISTS (SELECT * FROM 员工手机表 WHERE 员工ID = 员工表.ID AND 手机 = '手机1')
       AND EXISTS (SELECT * FROM 员工手机表 WHERE 员工ID = 员工表.ID AND 手机 = '手机2');
    
  • @Ta / 2022-08-11 / /

    @无名啊,我顺便来猜测一下你的“题目”中为什么会出现“要求技能表”,肯定是因为这是一门数据库课程,要秉承能不涉及外部程序就不涉及外部程序的教学思路。

    那么,没有外部程序,可变参数怎么输入呢?

    放进一个临时表呗!

    这就是“要求技能表”的来源。

    然后,因为参数全存在表里了,然后还要匹配全部条件,那怎么办呢,“这是数据库课,不能依靠外部程序啊”,所以就写了这么个复杂的语句:

    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.技能 = 要求技能表.技能 ))
    

    而在实际的编程中,我肯定不会选择创建一个要求技能表再把参数塞进去。实际编程必然是使用其他编程语言的,所以把一系列AND 参数=?拼接到SQL语句里显然更容易,SQL语句运行起来想必也更快。

  • @Ta / 2022-08-11 / /

    @老虎会游泳,实际我觉得,我会选择 MySQL 8.0.17+ 的多值索引,然后

    SELECT *
      FROM 员工表
     WHERE JSON_CONTAINS(这个员工会的技能, CAST('["技能1","技能2"]' AS JSON))
       AND JSON_CONTAINS(这个员工有的手机, CAST('["手机1","手机2"]' AS JSON))
    
  • @Ta / 2022-08-11 / /

    @无名啊,我写错了,应该是=而不是LIKE,我的LIKE是从虎绿林帖子那个语句复制的,忘记改了。

    我认为

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

    应该快于

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

    因为(SELECT * FROM 员工手机表 WHERE 员工ID = 员工表.ID AND 手机 = '手机2')是一个INNER JOIN(内连接),而(SELECT 员工ID FROM 员工技能表 WHERE 技能 = '技能1')只是一个简单的单表索引查询。我想不到单表索引查询能比内连接慢的场景。

    至于

    说不定连临时表也没了

    这有可能吗?每当我们使用括号(),就会生成一个临时表,这可以避免吗?

  • @Ta / 2022-08-11 / /

    @无名啊,所以,你还是倾向于选择SQL拼接嘛

    SELECT *
      FROM 员工表
     WHERE JSON_CONTAINS(这个员工会的技能, CAST('["技能1","技能2"]' AS JSON))
       AND JSON_CONTAINS(这个员工有的手机, CAST('["手机1","手机2"]' AS JSON))
    

    这个["技能1","技能2"]["手机1","手机2"],属于SQL拼接。

    WHERE 条件1 AND 条件2,属于SQL拼接。

  • @Ta / 2022-08-11 / /

    @老虎会游泳

    这个["技能1","技能2"]和["手机1","手机2"],属于SQL拼接。

    肯定用 ? 绑定参数啊,要不还要自己转义防注入漏洞。。

    只是在论坛,这样表达方便你一眼看出参数应该是放在哪儿的

    另外,这个方法不适用于 MySQL 8.0.17 以下,以及 SQLite(MariaDB 我没留意)

  • @Ta / 2022-08-11 / /

    @老虎会游泳,噢,这个也是每个条件接受一个字符串,一万个参数也是一样

    SELECT *
      FROM 员工表
     WHERE JSON_CONTAINS(这个员工会的技能, CAST(? AS JSON))
       AND JSON_CONTAINS(这个员工有的手机, CAST(? AS JSON))
    
  • @Ta / 2022-08-11 / /

    @老虎会游泳,我在 MariaDB 官网,好像没搜到 multi valued indexes 相关字眼

  • @Ta / 2022-08-11 / /

    @无名啊,那么我问你,你用?绑定的参数是怎么生成的,不还是得拼接吗?你可以说“我用json_encode”啊,那json_encode内部是怎么运行的,不还是字符串拼接吗?你只是把拼接从一个地方转移到了另一个地方而已,这不还是“使用外部程序进行SQL拼接”吗?

  • @Ta / 2022-08-11 / /

    @无名啊,而且,WHERE 条件1 AND 条件2这个拼接你躲不掉吧。

  • @Ta / 2022-08-11 / /

    @老虎会游泳,数据库解析 SQL 应该比 解析 json 复杂些,我偏向于后者

  • @Ta / 2022-08-11 / /

    @无名啊JSON_CONTAINS能利用索引吗?如果不能利用,可能需要全表扫描哦

  • @Ta / 2022-08-11 / /

    @无名啊,文不对题,只是分享 Android 开发中操作数据库的方法:
    使用 LitePal 这样的 SQLite 管理库,然后就可以轻松地增删改查数据了:

    // 获取满足特定名字和时长的歌曲列表,并按时长排序
    val songs = LitePal.where("name like ? and duration < ?", "song%", "200").order("duration").find<Song>()
    
  • @Ta / 2022-08-11 / /

    @老虎会游泳,MySQL 8.0.17+ 有多值索引(就是在数组上建索引,但每个索引最多只能有一个多值字段)

    https://dev.mysql.com/doc/refman/8.0/en/create-index.html#create-index-multi-valued

添加新回复
回复需要登录