2013-08-27 9 views
10

Mam aplikację, w której znajduję sumę() kolumny bazy danych dla zestawu rekordów, a następnie używam tej sumy w oddzielnym zapytaniu, podobnie do następującego (ułożone tabele, ale idea jest taka sama):Dlaczego nie można użyć opcji WYBIERZ ... DO AKTUALIZACJI z funkcjami zagregowanymi?

SELECT Sum(cost) 
INTO v_cost_total 
FROM materials 
WHERE material_id >=0 
AND material_id <= 10; 

[a little bit of interim work] 

SELECT material_id, cost/v_cost_total 
INTO v_material_id_collection, v_pct_collection 
FROM materials 
WHERE material_id >=0 
AND material_id <= 10 
FOR UPDATE; 

Jednak teoretycznie ktoś mógłby zaktualizować kolumnę kosztów w tabeli materiałów między dwoma zapytaniami, w którym to przypadku wyliczone procenty będą wyłączone.

Idealnie, chciałbym po prostu użyć klauzuli FOR UPDATE na pierwszej kwerendy, ale gdy próbuję że pojawia się błąd:

ORA-01786: FOR UPDATE of this query expression is not allowed 

Teraz obejście nie jest problem - po prostu wykonaj dodatkową kwerendę, aby zablokować wiersze przed znalezieniem Sum(), ale zapytanie to nie będzie służyć do innych celów niż blokowanie tabel. Chociaż ten konkretny przykład nie jest czasochłonny, dodatkowe zapytanie może spowodować działanie w określonych sytuacjach i nie jest tak czyste, więc chciałbym tego uniknąć.

Czy ktoś zna konkretny powód, dla którego nie jest to dozwolone? W mojej głowie klauzula FOR UPDATE powinna po prostu blokować wiersze pasujące do klauzuli WHERE - nie widzę powodu, dla którego ma znaczenie to, co robimy z tymi wierszami.

EDYCJA: Wygląda na to, że SELECT ... FOR UPDATE może być używany z funkcjami analitycznymi, jak sugeruje David Aldridge poniżej. Oto skrypt testowy, który udowodniłem, że to działa.

SET serveroutput ON; 

CREATE TABLE materials (
    material_id NUMBER(10,0), 
    cost  NUMBER(10,2) 
); 
ALTER TABLE materials ADD PRIMARY KEY (material_id); 
INSERT INTO materials VALUES (1,10); 
INSERT INTO materials VALUES (2,30); 
INSERT INTO materials VALUES (3,90); 

<<LOCAL>> 
DECLARE 
    l_material_id materials.material_id%TYPE; 
    l_cost  materials.cost%TYPE; 
    l_total_cost materials.cost%TYPE; 

    CURSOR test IS 
     SELECT material_id, 
      cost, 
      Sum(cost) OVER() total_cost 
     FROM materials 
     WHERE material_id BETWEEN 1 AND 3 
     FOR UPDATE OF cost; 
BEGIN 
    OPEN test; 
    FETCH test INTO l_material_id, l_cost, l_total_cost; 
    Dbms_Output.put_line(l_material_id||' '||l_cost||' '||l_total_cost); 
    FETCH test INTO l_material_id, l_cost, l_total_cost; 
    Dbms_Output.put_line(l_material_id||' '||l_cost||' '||l_total_cost); 
    FETCH test INTO l_material_id, l_cost, l_total_cost; 
    Dbms_Output.put_line(l_material_id||' '||l_cost||' '||l_total_cost); 
END LOCAL; 
/

co daje wynik:

1 10 130 
2 30 130 
3 90 130 
+1

Czy można użyć funkcji analitycznej (suma lub ratio_to_report) za pomocą opcji "wybierz ... do aktualizacji"? Nie mam obecnie dostępnej bazy danych, aby to przetestować, więc nie wiem ... –

Odpowiedz

15

Składnia select . . . for update zamki rekordy w tabeli, aby przygotować się do aktualizacji. Podczas wykonywania agregacji zestaw wyników nie odwołuje się już do oryginalnych wierszy.

Innymi słowy, w bazie danych nie ma żadnych wpisów do aktualizacji. Jest tylko tymczasowy zestaw wyników.

+0

Rozumiem, że sam wynik jest tymczasowy, ale integralność zestawu wyników zależy od podstawowych danych, więc wydaje się, że taka funkcja byłaby przydatna. Domyślam się, że Oracle się nie zgadza. No cóż. – BimmerM3

+6

@ BimmerM3. . . Nie. Jest to o wiele trudniejszy problem, niż mogłoby się wydawać. Jak określić rekordy do zablokowania dla 'max()'. Czy byłoby to właśnie z wartością 'max()'? Czy byłby to cały stół, ponieważ nowa wartość mogłaby być dodana lub zaktualizowana większa niż maksymalna? –

+0

Ah, dobra uwaga. Nie myślałem o funkcjach takich jak max() i min(). Dzięki za odpowiedzi. – BimmerM3

3

Można spróbować coś takiego:

<<LOCAL>> 
declare 
    material_id materials.material_id%Type; 
    cost  materials.cost%Type; 
    total_cost materials.cost%Type; 
begin 
    select material_id, 
     cost, 
     sum(cost) over() total_cost 
    into local.material_id, 
     local.cost, 
     local.total_cost 
    from materials 
    where material_id between 1 and 3 
    for update of cost; 

    ... 

end local; 

Pierwszy wiersz daje całkowity koszt, ale wybiera wszystkie wiersze i teoretycznie mogą być zablokowane.

Nie wiem, czy jest to dozwolone, ale uważam, że warto się z nim zapoznać.

+0

Interesujące - to działa! Umieszczę mój skrypt testowy w moim oryginalnym wpisie. – BimmerM3

+0

@ BimmerM3. . . Jest to wnikliwe podejście do problemu (i warte zachowywania). Jednak nie jest to "interesujące". Zestaw wyników to oryginalny zestaw wierszy. Problem z agregacją jest taki, że zestaw wyników to * nie * oryginalne wiersze. –

0

Czy Twój problem "Jednak teoretycznie ktoś mógłby zaktualizować kolumnę kosztów w tabeli materiałów między dwoma zapytaniami, w takim przypadku obliczone procenty będą wyłączone."?

w tym przypadku, prawdopodobnie można po prostu użyć wewnętrzną kwerendę jako:

SELECT material_id, cost/(SELECT Sum(cost) 
    FROM materials 
    WHERE material_id >=0 
    AND material_id <= 10) 
INTO v_material_id_collection, v_pct_collection 
FROM materials 
WHERE material_id >=0 
AND material_id <= 10; 

Dlaczego chcesz zablokować tabeli? Inne aplikacje mogą zawieść, jeśli spróbują zaktualizować tabelę w tym czasie, prawda?

+1

Inne aplikacje będą musiały poczekać, aż zwolnię blokadę, ale nie powinny zawieść, gdy są poprawnie zaprogramowane. – BimmerM3

+0

To zależy od sposobu zaprogramowania innych aplikacji. – Arnab

+0

W rzeczywistej aplikacji, o której tu mówię, wracam i aktualizuję wiersze, więc albo tak będą one zablokowane po aktualizacji. Blokowanie za pomocą klauzuli FOR ... UPDATE gwarantuje, że nikt nie zmieni danych w międzyczasie. W tym przypadku faktyczne prawdopodobieństwo konfliktu jest niewielkie i integralność danych jest na tyle ważna, że ​​warto podjąć ryzyko niepowodzenia kolejnej sesji. – BimmerM3