【问题标题】:PostgreSQL join: delete records present in one table, but not anotherPostgreSQL join:删除一个表中存在的记录,但不删除另一个表中的记录
【发布时间】:2011-04-06 08:48:40
【问题描述】:

我有一个嵌入 Flash 游戏的 Drupal 网站。

已注册的网站用户列在 drupal_users 表中 - 这里是一周前注册的用户列表:

# select uid, created from drupal_users where 
      to_timestamp(created) < (now() - interval '7 days') limit 5;
 uid  |  created
------+------------
 9903 | 1300257067
 9904 | 1300259929
 9750 | 1299858284
 9751 | 1299858603
 8083 | 1285514989
(5 rows)

Flash 游戏用户列在另一个表中 - pref_users 并在他们的 ID 前加上“DE”字符串:

# select id from pref_users where id like 'DE%' limit 5;
   id
--------
 DE9054
 DE9055
 DE9056
 DE9057
 DE9058
(5 rows)

我想摆脱一周前在我的网站上注册但仍未玩 Flash 游戏的(可能是垃圾邮件机器人)用户。 IE。我想删除 pref_users 表中不存在的 drupal_users 记录

同时我不想做这样的事情:

# delete from drupal_users where 
    to_timestamp(created) < (now() - interval '7 days') and
    'DE'||uid not in (select id from pref_users where id like 'DE%');

因为我不确定,上面的 select 语句允许有多大(也许有限制?我使用的是 PostgreSQL 8.4.7 和 CentOS 5.5/64 位。在 Drupal7 之前,我使用的是 phpBB3,有时从 phpBB3 管理控制台删除旧论坛帖子时,我看到这种 SQL 语句失败)。

所以我的问题是,上面的语句是否可以改写为some kind of SQL-join

【问题讨论】:

    标签: postgresql join sql-delete


    【解决方案1】:

    在处理具有数百万条记录的表之间的连接时,我无法使用NOT IN 获得可接受的性能。 相反,我写了相当于:

    alter table drupal_users add column dont_delete boolean;
    

    然后

    update drupal_users set dont_delete = true from pref_users 
    where 'DE'||drupal_users.uid = pref_users.id.
    

    一旦创建了新的 drupal_users,这将不再有效,但是因为您只删除超过 7 天的记录,所以没关系。 最后,验证您的记录并发布:

    delete from drupal_users where dont_delete is null
      and to_timestamp(drupal_users.created) < (now() - interval '7 days');
    

    清理:

    alter table drupal_users drop column dont_delete;
    

    【讨论】:

      【解决方案2】:

      无法将删除重写为 SQL 连接,AFAIK。 但是你为什么不喜欢

      delete from drupal_users where 
      to_timestamp(created) < (now() - interval '7 days') and
      'DE'||uid not in (select id from pref_users where id like 'DE%');
      

      此语句的大小是静态的(您不会在此处生成任何动态 SQL),因此这是一种完全有效的方法,并且应该运行得非常快(如果这是您所关心的)。

      【讨论】:

      • 因为我在从 phpBB3 删除旧论坛帖子时使用类似的语句“从 table1 中删除 id in (select id from table2)”达到了一些 PostgreSQL 限制(不记得是哪一个,抱歉) ACP。
      • PostgreSQL 对 DELETE 有一个(非标准)扩展,您可以在其中使用 USING 指定另一个表,但我想这在这里是不可能的(因为 - 如果我正确理解 USING - 它总是执行内部加入)。但是您可能想自己检查一下。
      • 好的,您认为向 (select id from pref_users where id like 'DE%') 添加 distinct 是个好主意吗?
      • 不,因为它是多余的 - IN / EXISTS 子查询永远不需要 DISTINCT。它甚至可能会减慢您的查询速度(因为优化器必须删除重复两次 - 一次用于 DISTINCT,一次用于 IN)。
      【解决方案3】:

      这是另一种方法,使用 EXISTS 子查询:

      delete from drupal_users D
      where to_timestamp(created) < (now() - interval '7 days')
      and not exists (select 1 from pref_users P where P.id = 'DE' || D.uid);
      

      【讨论】:

        【解决方案4】:

        我重新创建了你说有一些 postgresql 限制的场景:

        create table t0 (id int primary key);
        NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "t0_pkey" for table "t0"
        CREATE TABLE
        
        create table t1 (id int primary key);
        NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "t1_pkey" for table "t1"
        CREATE TABLE
        
        insert into t0 (id) 
        select * from generate_series(1, 100000, 2);
        INSERT 0 50000
        
        insert into t1 (id) 
        select * from generate_series(2, 100000, 2);
        INSERT 0 50000
        
        select * from t0 order by id limit 3;
         id 
        ----
          1
          3
          5
        (3 rows)
        
        select * from t1 order by id limit 3;
         id 
        ----
          2
          4
          6
        (3 rows)
        

        现在我从 t0 中删除所有在 t1 中不存在的行(全部):

        delete from t0
        where id not in (select id from t1);
        

        它有效

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-11-29
          • 2012-07-02
          • 2010-09-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多