PostgreSQL:如何使用generate_series()找出列中缺少的数字?

时间:2012-09-16 03:01:02

标签: postgresql postgresql-8.4 generate-series

SELECT commandid 
FROM results 
WHERE NOT EXISTS (
    SELECT * 
    FROM generate_series(0,119999) 
    WHERE generate_series = results.commandid 
    );

我在results类型int中有一列,但各种测试都失败了,并没有添加到表格中。我想创建一个查询,该查询返回commandid中找不到的results列表。我认为上面的查询会做我想要的。但是,如果我使用超出预期可能范围commandid的范围(如负数),它甚至不起作用。

4 个答案:

答案 0 :(得分:13)

给出样本数据:

create table results ( commandid integer primary key);
insert into results (commandid) select * from generate_series(1,1000);
delete from results where random() < 0.20;

这有效:

SELECT s.i AS missing_cmd
FROM generate_series(0,1000) s(i)
WHERE NOT EXISTS (SELECT 1 FROM results WHERE commandid = s.i);

这个替代配方也是如此:

SELECT s.i AS missing_cmd
FROM generate_series(0,1000) s(i)
LEFT OUTER JOIN results ON (results.commandid = s.i) 
WHERE results.commandid IS NULL;

上述两种情况似乎都会在我的测试中产生相同的查询计划,但您应该使用EXPLAIN ANALYZE与数据库中的数据进行比较,以确定哪种方法最佳。

说明

请注意,我使用NOT IN代替NOT EXISTS,在一个公式中使用子查询,而在另一个公式中使用普通OUTER JOIN。数据库服务器可以更轻松地优化这些,并避免NULLNOT IN可能出现的混乱问题。

我最初赞成OUTER JOIN制定,但至少在9.1中,我的测试数据NOT EXISTS形式优化到同一计划。

当系列很大时,两者都会比下面的NOT IN表现更好,就像你的情况一样。 NOT IN曾经要求Pg对每个被测试的元组进行IN列表的线性搜索,但是对查询计划的检查表明Pg可能足够聪明,现在可以对其进行哈希处理。 NOT EXISTS(由查询规划器转换为JOIN)和JOIN效果更好。

NOT IN公式在存在NULL commandid时会混淆并且效率低下:

SELECT s.i AS missing_cmd
FROM generate_series(0,1000) s(i)
WHERE s.i NOT IN (SELECT commandid FROM results);

所以我会避免它。拥有1,000,000行,其他两个在1.2秒内完成,NOT IN配方运行CPU限制,直到我感到无聊并取消它。

答案 1 :(得分:6)

正如我在评论中提到的,您需要执行与上述查询相反的操作。

SELECT
    generate_series
FROM
    generate_series(0, 119999)
WHERE
    NOT generate_series IN (SELECT commandid FROM results);

此时,您应该找到所选范围内commandid列中不存在的值。

答案 2 :(得分:0)

我不是那么有经验的SQL大师,但我喜欢其他解决问题的方法。 就在今天,我遇到了类似的问题 - 在一个字符列中找到未使用的数字。 我通过使用pl / pgsql解决了我的问题,并且非常感兴趣的是我的程序速度。 我使用@Craig Ringer的方式生成带有串行列的表,添加一百万条记录,然后删除每99条记录。此过程在搜索缺失的数字时大约需要3秒钟:

-- creating table
create table results (commandid character(7) primary key);
-- populating table with serial numbers formatted as characters
insert into results (commandid) select cast(num_id as character(7)) from generate_series(1,1000000) as num_id;
-- delete some records
delete from results where cast(commandid as integer) % 99 = 0;

create or replace function unused_numbers()
  returns setof integer as
$body$
declare
   i integer;
   r record;
begin
   -- looping trough table with sychronized counter:
   i := 1;
   for r in
      (select distinct cast(commandid as integer) as num_value
      from results
      order by num_value asc)
   loop
      if not (i = r.num_value) then
            while true loop
               return next i;

               i = i + 1;
               if (i = r.num_value) then
                     i = i + 1;
                     exit;
                  else
                     continue;
               end if;
            end loop;
         else
            i := i + 1;
      end if;
   end loop;

   return;
end;
$body$
  language plpgsql volatile
  cost 100
  rows 1000;

select * from unused_numbers();

也许它可以用于某人。

答案 3 :(得分:0)

如果您使用的是AWS redshift,则最终可能无法解决问题,因为它不支持<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.1/knockout-min.js"></script> <h1>Knockout Custom Bindings</h1> <h2>Which factors affect your technology choices?</h2> <p>Please distribute <span class="points">10</span> points between the following options.</p> <table> <thead> <tr> <td>Option</td> <td>Importance</td> </tr> </thead> <tbody data-bind="foreach:options"> <tr> <td data-bind="text:option"></td> <td class="select"> <select data-bind="options:$root.range, value:importance"></select> </td> </tr> </tbody> </table> <p>You've got <span class="points" data-bind="text:points()"></span> points left to use.</p> <button data-bind="click:$root.points()">Finished</button>。你最终会得到这样的东西:

generate_series