2013-08-27 9 views
10

Ho un'applicazione in cui trovo una sum() di una colonna del database per un set di record e successivamente la uso in una query separata, simile alla seguente (tabelle composte, ma l'idea è la stessa):Perché non posso usare SELECT ... FOR UPDATE con funzioni di aggregazione?

Tuttavia, in teoria qualcuno potrebbe aggiornare la colonna dei costi nella tabella dei materiali tra le due query, nel qual caso le percentuali calcolate saranno spente.

Idealmente, vorrei solo usare una clausola FOR UPDATE sulla prima query, ma quando provo che, ottengo un errore:

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

Ora, il work-around non è il problema - solo fai una query aggiuntiva per bloccare le righe prima di trovare Sum(), ma quella query non servirebbe altro che il blocco delle tabelle. Anche se questo particolare esempio non richiede molto tempo, la query aggiuntiva potrebbe causare un problema di prestazioni in determinate situazioni e non è così pulita, quindi mi piacerebbe evitare di doverlo fare.

Qualcuno sa di un motivo particolare per cui questo non è consentito? Nella mia testa, la clausola FOR UPDATE dovrebbe semplicemente bloccare le righe che corrispondono alla clausola WHERE - Non vedo perché è importante quello che stiamo facendo con quelle righe.

MODIFICA: Sembra che SELECT ... FOR UPDATE possa essere utilizzato con le funzioni analitiche, come suggerito da David Aldridge di seguito. Ecco lo script di test che ho usato per dimostrare che funziona.

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

che fornisce l'output:

1 10 130 
2 30 130 
3 90 130 
+1

È possibile utilizzare una funzione analitica (somma o rapporto_port_) con 'selezionare ... per l'aggiornamento'? Non ho un database disponibile al momento per testarlo, quindi non so ... –

risposta

15

La sintassi select . . . for update serrature record in una tabella per preparare un aggiornamento. Quando si esegue un'aggregazione, il set di risultati non fa più riferimento alle righe originali.

In altre parole, non ci sono record nel database da aggiornare. C'è solo un set di risultati temporaneo.

+0

Capisco che il risultato impostato sia temporaneo, ma l'integrità del set di risultati dipende dai dati sottostanti, quindi sembra in questo modo sarebbe una caratteristica utile. Immagino che Oracle non sia d'accordo. Oh bene. – BimmerM3

+6

@ BimmerM3. . . No. È un problema molto più difficile di quanto tu possa pensare. Come si dovrebbe specificare i record da bloccare per un 'max()'. Sarebbero quelli solo con il valore 'max()'? Sarebbe l'intera tabella, perché un nuovo valore potrebbe essere aggiunto o aggiornato più grande del massimo? –

+0

Ah, buon punto. Non ho pensato a funzioni come max() e min(). Grazie per la risposta. – BimmerM3

3

Si potrebbe provare qualcosa di simile:

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

La prima fila ti dà il costo totale, ma seleziona tutte le righe e in teoria potrebbero essere bloccati.

Non so se questo è permesso, badate bene - siate interessante sapere se lo è.

+0

Interessante: funziona! Metterò il mio script di prova nel mio post originale. – BimmerM3

+0

@ BimmerM3. . . Questo è un approccio perspicace al problema (e vale la pena fare upvoting). Tuttavia, non è "interessante". Il set di risultati è il set originale di righe. Il problema con l'aggregazione è che il set di risultati è * non * le righe originali. –

0

È il tuo problema "Tuttavia, in teoria qualcuno potrebbe aggiornare la colonna dei costi nella tabella dei materiali tra le due query, nel qual caso le percentuali calcolate saranno disattivate."?

In questo caso, probabilmente, si può semplicemente utilizzare una query interna come:

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; 

Perché si desidera bloccare un tavolo? Altre applicazioni potrebbero fallire se provano ad aggiornare quella tabella durante quel tempo giusto?

+1

Le altre applicazioni dovranno attendere il rilascio del blocco, ma non dovrebbero fallire se programmate correttamente. – BimmerM3

+0

Dipende da come sono programmate le altre applicazioni. – Arnab

+0

Nell'applicazione effettiva di cui sto parlando, torno indietro e aggiorno le righe, quindi in entrambi i casi saranno bloccate dopo l'aggiornamento. Il blocco con la clausola FOR ... UPDATE garantisce che nessun altro cambierà i dati nel frattempo. In questo caso, l'effettiva probabilità di un conflitto è piuttosto ridotta e l'integrità dei dati è abbastanza importante da giustificare il rischio di causare il fallimento di un'altra sessione. – BimmerM3