2009-03-25 2 views
7

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

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, но я не мог заставить ее работать ... любые идеи?

+0

Является 'somebool' определенный 'NOT NULL'? –

ответ

7

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

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

(к сожалению, не могу проверить это сейчас)

+0

Вам нужно отказаться от предложения «group by someKey». С помощью курсора и сканирования вручную вы можете прекратить сканирование, как только встретится «ложь», но это такое незначительное улучшение, что вряд ли стоит того, что это может быть связано с неудобством использования функции, обертывающей запрос. Я не думаю, что агрегаты могут так быстро остановиться. – araqnid

0

Может быть, подсчет 'всех' элементов с somekey = somevalue и использовать его в логическом сравнении с подсчетом всех 'True' вхождения для 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 
+0

Это начало, но это не касается случая, когда id не существует (возвращается true, так как 0 = 0) – rjohnston

+0

Ну, что-то вроде этого должно сделать трюк, но мне нравится другое решение лучше; -) http://paste.pocoo.org/show/109644/ – ChristopheD

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 

Это позволит получить все ответы сразу, если вы хотите только один ключ просто добавить ИНЕК

+0

Спасибо ... удаление someKey из списка выбора дает правильный результат – rjohnston

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 

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

+0

Точно: если вы найдете какие-либо «фальши», то «и игра» будет вверх. – Roboprog

+0

Изменил мой разум - мне нравится это решение лучше. Очищенная версия в http://paste.pocoo.org/show/109656/ – rjohnston

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 
2

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

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

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

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

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

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

Чтобы вернуть OR, только обратный порядок:

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

Вы также можете использовать 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'); 

 Смежные вопросы

  • Нет связанных вопросов^_^