Как я могу выполнить AND для неизвестного числа логических значений в postgresql? - PullRequest
7 голосов
/ 26 марта 2009

У меня есть таблица с внешним ключом и логическим значением (и куча других столбцов, которые здесь не релевантны), например:

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 ]

7 голосов
/ 04 мая 2009

Нет необходимости переопределять функции, которые уже предоставляет PostgreSQL: bool_and () выполнит работу:

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

(Извините, не могу проверить это сейчас)

3 голосов
/ 26 марта 2009

Аналогично предыдущему, но в одном запросе это сработает, однако это не чистый и не понятный код:

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 голосов
/ 04 мая 2009

(Очень второстепенный аспект: я думаю, что ваша функция должна быть объявлена ​​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. Имейте в виду, я подозреваю, что вам нужны нагрузки и ряды строк, чтобы разница была заметной.

2 голосов
/ 26 марта 2009

Я только что установил 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

Идея состоит в том, что вам нужно только взглянуть на одну строку, чтобы увидеть, существует ли какая-либо строка, и если хотя бы одна строка существует, вам нужно только смотреть, пока не найдете ложное значение, чтобы определить, является ли окончательное значение ложным (или вы дойти до конца и это правда). Предполагая, что у вас есть индекс для some_key, производительность должна быть хорошей, я думаю.

1 голос
/ 23 мая 2012

Вы также можете использовать every, который является псевдонимом bool_and:

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

Использование каждого делает ваш запрос более читабельным. Например, покажите всем, кто просто ест яблоко каждый день:

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

every семантически совпадает с bool_and, но очевидно, что every более читабельно, чем bool_and:

select personId
from personDailyDiet
group by personId
having bool_and(fruit = 'apple');
0 голосов
/ 31 мая 2009
SELECT  DISTINCT ON (someKey) someKey, someBool
FROM    myTable m
ORDER BY
        someKey, someBool NULLS FIRST

Это выберет первое упорядоченное логическое значение для каждого someKey.

Если есть один FALSE или NULL, он будет возвращен первым, что означает, что AND не удалось.

Если первым логическим значением является TRUE, то все другие логические значения также TRUE для этого ключа.

В отличие от агрегата, здесь будет использоваться индекс (someKey, someBool).

Чтобы вернуть OR, просто измените порядок:

SELECT  DISTINCT ON (someKey) someKey, someBool
FROM    myTable m
ORDER BY
        someKey, someBool DESC NULLS FIRST
0 голосов
/ 26 марта 2009
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
0 голосов
/ 26 марта 2009

Может быть подсчитать «все» предметы с somekey = somevalue и использовать его в логическом сравнении с подсчетом всех «истинных» случаев для somekey?

Некоторые непроверенные псевдо-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
...