如何在postgresql中对未知数量的布尔值执行AND?

时间:2009-03-25 22:53:45

标签: sql postgresql plpgsql

我有一个带有外键的表和一个布尔值(以及一些其他与此无关的列),如下所示:

CREATE TABLE myTable
(
  someKey integer,
  someBool boolean
);

insert into myTable values (1, 't'),(1, 't'),(2, 'f'),(2, 't');

每个someKey可以有0个或更多条目。对于任何给定的someKey,我需要知道a)所有条目是否为真,或者b)任何条目都是假的(基本上是AND)。

我想出了以下功能:

CREATE FUNCTION do_and(int4) RETURNS boolean AS
$func$
declare
    rec record;
    retVal boolean = 't'; -- necessary, or true is returned as null (it's weird)
begin
    if not exists (select someKey from myTable where someKey = $1) then
        return null; -- and because we had to initialise retVal, if no rows are     found true would be returned
    end if;

    for rec in select someBool from myTable where someKey = $1 loop
        retVal := rec.someBool AND retVal;
    end loop;

    return retVal;
end;
$func$ LANGUAGE 'plpgsql' VOLATILE;

...它给出了正确的结果:

select do_and(1) => t
select do_and(2) => f
select do_and(3) => null

我想知道是否有更好的方法来做到这一点。在这个简单的场景中看起来并不太糟糕,但是一旦你包含了所有支持代码,它就会比我想要的更长。我看一下将someBool列转换为数组并使用ALL构造,但是我无法使它工作......有什么想法吗?

8 个答案:

答案 0 :(得分:7)

无需重新定义PostgreSQL已提供的功能:bool_and()将完成这项工作:

select bool_and(someBool)
  from myTable
  where someKey = $1
  group by someKey;

(抱歉,现在无法测试)

答案 1 :(得分:3)

与前一个类似,但在一个查询中,这将起到作用,但是,它不是干净的,也不易理解的代码:

SELECT someKey, 
  CASE WHEN sum(CASE WHEN someBool THEN 1 ELSE 0 END) = count(*)
                    THEN true 
                    ELSE false END as boolResult
FROM  table
GROUP BY someKey

如果你只想要一个键只添加一个WHERE子句

,这将立即获得所有响应

答案 2 :(得分:2)

本周我第一次安装PostgreSQL,所以你需要清理语法,但这里的一般想法应该有效:

return_value = NULL

IF EXISTS
(
     SELECT
          *
     FROM
          My_Table
     WHERE
          some_key = $1
)
BEGIN
     IF EXISTS
     (
          SELECT
               *
          FROM
               My_Table
          WHERE
               some_key = $1 AND
               some_bool = 'f'
     )
          SELECT return_value = 'f'
     ELSE
          SELECT return_value = 't'
END

这个想法是你只需要查看一行以查看是否存在任何行,如果存在至少一行,则只需要查找,直到找到错误值以确定最终值为false(或者您到最后,这是真的)。假设你有some_key的索引,我认为性能应该是好的。

答案 3 :(得分:2)

(非常小的侧点:我认为你的函数应该被声明为STABLE而不是VOLATILE,因为它只是使用数据库中的数据来确定它的结果。)

正如有人提到的,您可以在遇到“错误”值时立即停止扫描。如果这是一种常见情况,您可以使用光标实际上激发“快速完成”:

CREATE FUNCTION do_and(key int) RETURNS boolean
  STABLE LANGUAGE 'plpgsql' AS $$
DECLARE
  v_selector CURSOR(cv_key int) FOR
    SELECT someBool FROM myTable WHERE someKey = cv_key;
  v_result boolean;
  v_next boolean;
BEGIN
  OPEN v_selector(key);
  LOOP
    FETCH v_selector INTO v_next;
    IF not FOUND THEN
      EXIT;
    END IF;
    IF v_next = false THEN
      v_result := false;
      EXIT;
    END IF;
    v_result := true;
  END LOOP;
  CLOSE v_selector;
  RETURN v_result;
END
$$;

这种方法也意味着您只在myTable上进行一次扫描。请注意,我怀疑你需要加载和加载行才能使差异显而易见。

答案 4 :(得分:1)

您还可以使用every,这只是bool_and的别名:

select every(someBool)
from myTable
where someKey = $1
group by someKey;

使用every使您的查询更具可读性。举个例子,展示每天只吃苹果的人:

select personId
from personDailyDiet
group by personId
having every(fruit = 'apple');

every在语义上与bool_and相同,但很明显everybool_and更具可读性:

select personId
from personDailyDiet
group by personId
having bool_and(fruit = 'apple');

答案 5 :(得分:0)

也许使用somekey = somevalue计算'all'项目,并在布尔比较中使用它与somekey的所有'True'出现的计数?

一些未经测试的伪sql来显示我的意思......

select foo1.count_key_items = foo2.count_key_true_items
from
   (select count(someBool) as count_all_items from myTable where someKey = '1') as foo1,
   (select count(someBool) as count_key_true_items from myTable where someKey = '1' and someBool) as foo2

答案 6 :(得分:0)

CREATE FUNCTION do_and(int4)
  RETURNS boolean AS
$BODY$
  SELECT
    MAX(bar)::bool
  FROM (
    SELECT
      someKey,
      MIN(someBool::int) AS bar
    FROM
      myTable
    WHERE
      someKey=$1
    GROUP BY
      someKey

    UNION

    SELECT
      $1,
      NULL
  ) AS foo;
$BODY$
  LANGUAGE 'sql' STABLE;

如果您不需要NULL值(当没有任何行时),只需使用以下查询:

SELECT
  someKey,
  MIN(someBool::int)::bool AS bar
FROM
  myTable
WHERE
  someKey=$1
GROUP BY
  someKey

答案 7 :(得分:0)

SELECT  DISTINCT ON (someKey) someKey, someBool
FROM    myTable m
ORDER BY
        someKey, someBool NULLS FIRST

这将为每个someKey选择第一个有序的布尔值。

如果只有一个FALSENULL,则会先返回,这意味着AND失败。

如果第一个布尔值是TRUE,则此键的所有其他布尔值也为TRUE

与聚合不同,这将使用(someKey, someBool)上的索引。

要返回OR,只需撤消订购:

SELECT  DISTINCT ON (someKey) someKey, someBool
FROM    myTable m
ORDER BY
        someKey, someBool DESC NULLS FIRST