2013-08-27 15 views
10

Ich habe eine Anwendung, wo ich eine sum() einer Datenbankspalte für eine Reihe von Datensätzen finden und später diese Summe in einer separaten Abfrage, ähnlich der folgenden (erfundenen Tabellen, aber die Idee ist die gleiche):Warum kann ich SELECT ... FOR UPDATE nicht mit Aggregatfunktionen verwenden?

Theoretisch könnte jedoch jemand die Kostenspalte in der Materialtabelle zwischen den beiden Abfragen aktualisieren, in diesem Fall sind die berechneten Prozente ausgeschalten.

Im Idealfall würde ich nur auf der ersten Abfrage FOR UPDATE-Klausel verwenden, aber wenn ich versuche, dass, erhalte ich eine Fehlermeldung:

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

Nun wird die Work-around ist nicht das Problem - einfach Führen Sie eine zusätzliche Abfrage durch, um die Zeilen zu sperren, bevor Sie die Summe() finden. Diese Abfrage würde jedoch keinen anderen Zweck als das Sperren der Tabellen erfüllen. Während dieses spezielle Beispiel nicht zeitaufwendig ist, könnte die zusätzliche Abfrage in bestimmten Situationen einen Leistungseinbruch verursachen und ist nicht so sauber. Daher möchte ich es vermeiden, dies zu tun.

Kennt jemand einen bestimmten Grund, warum dies nicht erlaubt ist? In meinem Kopf sollte die FOR UPDATE-Klausel nur die Zeilen sperren, die der WHERE-Klausel entsprechen. Ich sehe nicht, warum es wichtig ist, was wir mit diesen Zeilen machen.

EDIT: Es sieht aus wie SELECT ... FOR UPDATE kann mit analytischen Funktionen verwendet werden, wie von David Aldridge unten vorgeschlagen. Hier ist das Testskript, mit dem ich bewiesen habe, dass es funktioniert.

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

, die den Ausgang gibt:

1 10 130 
2 30 130 
3 90 130 
+1

Ist es möglich, eine analytische Funktion (sum oder ratio_to_report) mit 'select ... for update' zu ​​verwenden? Ich habe im Moment keine Datenbank, um das zu testen, weiß also nicht ... –

Antwort

15

Die Syntax select . . . for update Sperren Datensätze in einer Tabelle für ein Update vorzubereiten. Wenn Sie eine Aggregation durchführen, bezieht sich die Ergebnismenge nicht mehr auf die ursprünglichen Zeilen.

Mit anderen Worten, es gibt keine Datensätze in der Datenbank zu aktualisieren. Es gibt nur eine temporäre Ergebnismenge.

+0

Ich verstehe, dass das Resultset selbst temporär ist, aber die Integrität der Ergebnismenge hängt von den zugrunde liegenden Daten ab, so scheint es so wäre das ein nützliches Feature. Ich denke, dass Oracle nicht einverstanden ist. Naja. – BimmerM3

+6

@BimmerM3. . . Nein. Es ist ein viel schwierigeres Problem, als Sie vielleicht denken. Wie würde man die zu blockierenden Datensätze für ein 'max()' angeben? Wären es nur die mit dem 'max()' Wert? Wäre es die ganze Tabelle, weil ein neuer Wert hinzugefügt oder aktualisiert werden könnte, der größer ist als der max? –

+0

Ah, guter Punkt. Ich habe nicht über Funktionen wie max() und min() nachgedacht. Danke für die Antwort. – BimmerM3

3

Sie könnten versuchen, so etwas wie:

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

Die erste Zeile gibt Ihnen die Gesamtkosten, aber es wählt alle Zeilen und in der Theorie könnten sie gesperrt werden.

Ich weiß nicht, ob das erlaubt ist, wohlgemerkt - interessant zu hören, ob es ist.

+0

Interessant - das funktioniert! Ich lege mein Testskript in meinen ursprünglichen Post. – BimmerM3

+0

@BimmerM3. . . Dies ist eine aufschlussreiche Herangehensweise an das Problem (und es lohnt sich, diese zu erhöhen). Es ist jedoch nicht so "interessant". Die Ergebnismenge ist die ursprüngliche Menge von Zeilen. Das Problem mit der Aggregation ist, dass die Ergebnismenge * nicht * die ursprünglichen Zeilen ist. –

0

Ihr Problem "Theoretisch könnte jedoch jemand die Kostenspalte in der Materialtabelle zwischen den beiden Abfragen aktualisieren. In diesem Fall sind die berechneten Prozentwerte deaktiviert."?

In diesem Fall wahrscheinlich können Sie einfach eine innere Abfrage verwenden:

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; 

Warum wollen Sie eine Tabelle sperren? Andere Anwendungen können fehlschlagen, wenn sie versuchen, diese Tabelle während dieser Zeit zu aktualisieren, oder?

+1

Die anderen Anwendungen müssen warten, bis ich die Sperre loslasse, aber sie sollten nicht fehlschlagen, wenn sie richtig programmiert sind. – BimmerM3

+0

Das hängt davon ab, wie die anderen Anwendungen programmiert sind. – Arnab

+0

In der eigentlichen Anwendung, über die ich hier spreche, gehe ich zurück und aktualisiere die Zeilen, so dass sie entweder nach dem Update gesperrt werden. Das Sperren mit der FOR ... UPDATE-Klausel garantiert, dass niemand sonst die Daten in der Zwischenzeit ändert. In diesem Fall ist die tatsächliche Wahrscheinlichkeit eines Konflikts ziemlich gering und die Datenintegrität ist so wichtig, dass es sich lohnt, das Risiko einzugehen, dass eine andere Sitzung fehlschlägt. – BimmerM3

Verwandte Themen