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

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

    @老虎会游泳,我发现sqlite也不比脚本语言慢,生成 2亿行,花了 两分半,是tcc的 7~8 倍耗时

    但是没有srand,所以每次结果都有细微差异

    $ time sqlite3 -csv ':memory:' <<EOF | wc -lc
    WITH
      gen(v) AS (
        SELECT random() % 128 + 128
          FROM generate_series(1, random() % 6 + 20)  -- -5 ~ 5 + 20
      )
    
    SELECT value, v
      FROM generate_series(1, 10000000), gen
    EOF
    

    结果:

    199993358 2293329326
    
    real    2m30.591s
    user    2m32.265s
    sys     0m1.437s
    
  • @Ta / 2022-08-12 / /

    @无名啊,那么你最后测查询了吗

  • @Ta / 2022-08-12 / /

    @老虎会游泳,还没。昨晚导入至数据库时,碰到了下列问题:

    1. 如何生成不重复的 (uid, num)(数据库一直报错说碰到很多重复行,违反唯一索引)
    2. 怎么在 shell 中传递两种数据给不同进程?(比如 tcc 第一阶段传递给导入 A 表的 SQLite,第二阶段传递给导入 B 表的
  • @Ta / 2022-08-12 / /

    @老虎会游泳,关于问题一,我暂时修改成了,有重复的就再生成一次(速度瞬间慢了好多

    for (int uid = 1; uid <= 10000000; ++uid) {
        uint64_t flags[4] = {0};
        for (int num, s, y, i = randint(15, 25); i > 0; --i) {
            while (num = randint(0, 255), flags[s = num >> 6] & (y = 1 << (num & 0x3F)));
            printf("%d,%d\n", uid, num);
            flags[s] |= y;
        }
    }
    
  • @Ta / 2022-08-12 / /

    @老虎会游泳,关于问题二,我尝试了很多写法:

    0. 首先,建表(这里没事儿)

    sqlite3 test.db \
    	'CREATE TABLE a (uid INT, num INT, PRIMARY KEY (uid, num)) WITHOUT ROWID' \
    	'CREATE TABLE b (uid INT, num INT, PRIMARY KEY (uid, num)) WITHOUT ROWID'
    

    1. 进程替换,再重定向stdoutstderr

    C 源码改造成:先往stdout写,写完就fclose(),然后再往stderr写,写完也fclose()

    #define each(p, arr) \
        (typeof(*(arr)) *p = (arr), *_end = (arr) + sizeof(arr) / sizeof(*(arr)); p < _end; ++p)
    
    int main() {
        // 省略...
        
        FILE * outputs[] = {stdout, stderr};
        for each (fp_p, outputs) {
            // 省略...
            fclose(*fp_p);
        }
    }
    

    shell执行是这样:

    tcc -run main.c \
    	 > >(sqlite3 test.db '.import --csv /dev/stdin a') \
    	2> >(sqlite3 test.db '.import --csv /dev/stdin b')
    

    结果是,第一个sqlite3 迟迟不结束读取stdin(即使我在c里写了fclose(xxx),导致第二个sqlite3也去写入,最后数据库损坏。

    改成这样也没用……

    tcc -run main.c \
    	 > >(sqlite3 test.db 'begin exclusive' '.import --csv /dev/stdin a' 'commit') \
    	2> >(sqlite3 test.db 'begin exclusive' '.import --csv /dev/stdin b' 'commit')
    

    2. 进程替换,直接写入至管道

    C 源码改造成:挨个往argv里的所有文件写

    int main(int argc, char *argv[]) {
        // 省略...
        
        for (int argi = 1; argi < argc; ++argi) {
            FILE * fp = fopen(argv[argi], "w");
            // 省略...
            fclose(fp);
        }
    }
    

    shell执行是这样:

    tcc -run main.c \
    	>(sqlite3 test.db '.import --csv /dev/stdin a') \
    	>(sqlite3 test.db '.import --csv /dev/stdin b')
    

    还是不行,结果类似上面的

    3. 每个阶段结束后,写入一行结束标记

    C 源码改造成:每次结束后,写入特殊标记

    int main() {
        // 省略...
        
        for (int times = 0; times < 2; ++times) {
            // 省略...
            puts("END");
        }
    }
    

    shell测试的时候,发现都不行:

    tcc -run main.c | {
    	sed '/^END$/Q' | wc -l
    	sed '/^END$/Q' | wc -l
    }
    

    结果:

    1199869  # 我只测了 3w 个 uid
    0
    

    简化一下问题:

    $ seq 10 | { head -n 3; head -n 3; }
    1
    2
    3
    

    ???

  • @Ta / 2022-08-12 / /

    @无名啊,把数字取值范围扩大,重复概率不就变小了

  • @Ta / 2022-08-12 / /
    赞同老虎的想法,现在我做程序也是,数据库甚至都不连表,能放到程序上做的绝不放到数据库里面去做。
  • @Ta / 2022-08-12 / /

    @爱特,不连表。。是因为分库了嘛?

    否则,你该要连的表,无论如何都要join啊,

    为何还要多一个『取出所有「稍后我程序自己去连表的主键ID」列表』呢

  • @Ta / 2022-08-12 / /

    @老虎会游泳,问题一还不算很要紧。问题二呢?我想尽量通过管道传输,有啥子写法不?

  • @Ta / 2022-08-12 / /

    @无名啊,我会选择直接用python把生成好的SQL语句写入文件。

  • @Ta / 2022-08-12 / /

    @老虎会游泳,生成SQL语句?

    马也,python 生成 csv 我都嫌慢。。SQL 更不敢想了

  • @Ta / 2022-08-12 / /

    @无名啊

    为何还要多一个『取出所有「稍后我程序自己去连表的主键ID」列表』呢

    程序封装的好一点,功能复杂一点,就会发现很多id在你想用它们之前就已经查好放在变量里了,唾手可得,没有必要再专门join。

    只有“没有任何封装,每个需求都单独写一个SQL,每个功能都独立出现,不与其他功能进行组合”的编程模式和项目架构,才会出现“多一个查找ID步骤”的烦恼。

    比如,要显示这个导航条,我需要帖子所在版块的id。那我会“多一个查找ID的步骤”吗?并不会,因为在显示这个导航条之前,我已经显示了帖子标题,在那时候我就已经用select *同时得到版块的id了。

    首页 > 绿虎论坛 > 编程 > 数据库

    所以只要程序复杂一点,界面功能全面一点,那些查询要用的id、名称等数据在实现其他功能时肯定早就提前找好了。

  • @Ta / 2022-08-12 / /

    全靠JOIN解决问题,意味着你需要为每个具体问题写一个单独的SQL。而实际项目是各种需求的组合叠加,“具体问题”的数量以排列组合的数量级增长,写那么多SQL你烦不烦,就看你自己了。

    而且,在组合各种功能时,明明在上一个问题里想要的ID都拿到了,在下一个问题里还要JOIN一下重新查一遍,会不会觉得蠢也看你自己了。

  • @Ta / 2022-08-12 / /

    @无名啊

    python 生成 csv 我都嫌慢

    随着你的挑三拣四,我对结果已经不感兴趣了。

  • @Ta / 2022-08-12 / /

    @无名啊,如果你专注于一种解决方案,就算只用最初的PHP,测试也可能早就完成了。

  • @Ta / 2022-08-12 / /

    @老虎会游泳,会不会他的意思是,连 ORM 里都不用 JOIN,全靠自己手动实现 JOIN,达到所谓的『压力传到应用层服务器上』

  • @Ta / 2022-08-12 / /

    @无名啊,我就是这个意思啊。既然都ORM了,得到表的一行如此简单,那我为何不用一系列的对象访问来获取我想要的数据,为什么我要手动构造一个JOIN呢?

    写一个对象操作链

    a->getXxx()->getYyy()->getZzz()
    

    不比手动构造一个JOIN简单多了?

    而这个操作链实际执行的时候肯定是转换成多次SELECT查询啊。我觉得应该没有人会编程实现把它转换成单一JOIN语句。而且就算真的转了,不见得比多次SELECT快,处于吃力不讨好。

  • @Ta / 2022-08-12 / /

    @老虎会游泳,他的意思可能是,ORM 里都不要搞什么链式操作(会产生 JOIN),应该自己取得 IDs,再去写等同于下列SQL的代码?

    SELECT *
      FROM 用户信息表
     WHERE 用户ID IN (?, ?, ?, ...)
    
  • @Ta / 2022-08-12 / /

    @无名啊,链式操作是多次SELECT查询。我觉得应该没有人会编程实现把它转换成单一JOIN语句。而且就算真的转了,不见得比多次SELECT快,处于吃力不讨好。

  • @Ta / 2022-08-12 / /

    @无名啊,当你进行以下操作时

    a->getXxx()->getYyy()->getZzz()
    

    你进行的实际是

    select xxx_id from table_a where id=${a_id};
    
    select yyy_id from table_xxx where id=${xxx_id};
    
    select zzz_id from table_yyy where id=${yyy_id};
    
    select * from table_zzz where id=${zzz_id};
    

    所以你当然可以进行链式操作。怎么会产生JOIN呢?为什么要产生JOIN呢?一层一层根据id查询不简单吗?

添加新回复
回复需要登录