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

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

4

4 回答 4

24

给定样本数据:

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。数据库服务器更容易优化这些,并且避免了NULLs in可能出现的令人困惑的问题NOT IN

我最初喜欢这个OUTER JOIN公式,但至少在 9.1 中,我的测试数据NOT EXISTS表格优化到了相同的计划。

NOT IN当系列很大时,两者都会比下面的公式表现更好,就像你的情况一样。NOT IN过去需要 PgIN对每个被测试的元组进行列表的线性搜索,但对查询计划的检查表明 Pg 可能足够聪明,现在可以散列它。(由查询计划器NOT EXISTS转换为 a )并且工作得更好。JOINJOIN

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 密集型,直到我感到无聊并取消它。

于 2012-09-16T03:10:23.660 回答
8

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

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

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

于 2012-09-16T03:07:24.237 回答
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();

也许它对某人有用。

于 2016-10-28T19:13:38.913 回答
-1

如果您使用的是 AWS redshift,您可能最终需要回答这个问题,因为它不支持generate_series. 你最终会得到这样的东西:

select 
    startpoints.id    gapstart, 
    min(endpoints.id) resume 
from (
     select id+1 id 
     from   yourtable outer_series 
     where not exists 
         (select null 
          from   yourtable inner_series 
          where  inner_series.id = outer_series.id + 1
         )
     order by id
     ) startpoints,   

     yourtable endpoints 
where 
    endpoints.id > startpoints.id 
group by 
    startpoints.id;
于 2017-01-03T05:47:52.347 回答