2009-03-25 14 views
7

Mam tabeli z kluczem obcym i wartości logicznej (i kilka innych kolumn, które nie mają znaczenia), jako takich:Jak mogę wykonać operację AND na nieznanej liczbie zmiennych logicznych w postgresql?

CREATE TABLE myTable 
(
    someKey integer, 
    someBool boolean 
); 

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

Każdy someKey może mieć 0 lub więcej wpisów. Dla każdego danego klucza, muszę wiedzieć, czy a) wszystkie wpisy są prawdziwe, lub b) którykolwiek z wpisów jest fałszywy (w zasadzie AND).

Doszedłem z następujących funkcji:

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; 

... co daje poprawne wyniki:

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

Zastanawiam się, czy istnieje milszy sposób, aby to zrobić. W tym prostym scenariuszu nie wygląda to tak źle, ale gdy już weźmiesz pod uwagę cały kod pomocniczy, będzie on dłuższy niż ja. Rzuciłem okiem na rzucanie kolumny someBool do tablicy i używanie konstrukcji ALL, ale nie mogłem jej uruchomić ... żadnych pomysłów?

+0

jest 'somebool' zdefiniowano "NOT NULL"? –

Odpowiedz

7

Nie trzeba przedefiniować funkcje PostgreSQL już przewiduje: bool_and() będzie wykonać zadanie:

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

(Niestety, nie można przetestować go teraz)

+0

Musisz usunąć klauzulę "group by someKey". Korzystanie z kursora i ręcznego skanowania umożliwia zatrzymanie skanowania natychmiast po napotkaniu "false", ale jest to niewielkie ulepszenie, które raczej nie jest warte zachodu w porównaniu z niedogodnością związaną z funkcją zawijania zapytania. Nie sądzę, że agregaty mogą "tak szybko się zatrzymać". – araqnid

0

Może liczyć "wszystkie" przedmioty z somekey = somevalue i używać go w porównaniu boolean z liczbą wszystkich "prawdziwych" wystąpień dla somekey?

Niektórzy nie testowane pseudo-SQL, aby pokazać, co mam na myśli ...

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

To jest początek, ale nie dotyczy to przypadku, w którym identyfikator nie istnieje (prawda jest zwracana, ponieważ 0 = 0) – rjohnston

+0

Coś, coś takiego powinno wystarczyć, ale lubię inne rozwiązanie lepiej; -) http://paste.pocoo.org/show/109644/ – ChristopheD

3

podobne do poprzedniego, ale w jednym zapytaniu, to załatwi, jednak nie jest ani czysty Kod łatwo zrozumiałe:

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 

Pozwoli to uzyskać wszystkie odpowiedzi na raz, jeśli chcą tylko jeden klucz wystarczy dodać WHERE

+0

Dzięki ... usunięcie someKey z listy select daje prawidłowy wynik – rjohnston

2

właśnie zainstalowałem PostgreSQL po raz pierwszy w tym tygodniu, więc „Ll trzeba oczyścić składnię, ale ogólna idea tutaj powinno działać:

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 

Chodzi o to, że trzeba tylko spojrzeć na jednym rzędzie, aby zobaczyć, jeśli takie istnieją, a jeśli co najmniej jeden wiersz istnieje wtedy Wystarczy spojrzeć, aż znajdziesz fałszywą wartość, aby ustalić, że ostateczna wartość jest fałszywa (lub dojdziesz do końca i to prawda). Zakładając, że masz indeks na some_key, wydajność powinna być dobra.

+0

Dokładnie: jeśli znajdziesz jakieś "falses", to "gra" jest już dostępna. – Roboprog

+0

Zmieniono zdanie - lubię to rozwiązanie lepiej. Oczyszczona wersja na 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; 

W przypadku nie trzeba wartość NULL (jeśli nie ma żadnych wierszy), wystarczy skorzystać z poniższego zapytania:

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

(bardzo drobne side-p oint: Myślę, że twoja funkcja powinna być zadeklarowana jako STABLE zamiast VOLATILE, ponieważ używa tylko danych z bazy danych, aby określić jej wynik.)

Jak ktoś wspomniał, możesz zatrzymać skanowanie, gdy tylko pojawi się wartość "false" .Jeśli tak jest to częsty przypadek, można użyć kursora, aby rzeczywiście prowokować „szybkiej wykończenie”:

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 
$$; 

Podejście to oznacza również, że robisz tylko jednego skanowania na myTable. Pamiętaj, podejrzewam, że potrzebujesz ładunków i mnóstwa rzędów, aby różnica była znacząca.

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

Spowoduje to wybranie pierwszej uporządkowanej wartości boolowskiej dla każdego someKey.

Jeśli występuje pojedyncza FALSE lub NULL, zostanie zwrócona jako pierwsza, co oznacza, że ​​AND nie powiodło się.

Jeśli pierwszą wartością logiczną jest TRUE, wszystkie pozostałe wartości logiczne mają również wartość TRUE dla tego klucza.

W przeciwieństwie do agregatu, użyje on indeksu na (someKey, someBool).

Aby zwrócić OR, po prostu odwrócić kolejność:

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

Można również użyć every, który jest po prostu aliasem bool_and:

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

Korzystanie każdy sprawia zapytanie bardziej czytelny. Przykładem, pokaż wszystkie osoby, które po prostu zjeść jabłko codziennie:

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

every jest semantycznie taka sama jak bool_and, ale to z pewnością stwierdzić, że every jest bardziej czytelny niż bool_and:

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