2010-03-29 12 views
2

J'ai une table avec une plage de dates de début, une plage de dates de fin et quelques autres colonnes supplémentaires. Lors de la saisie d'un nouvel enregistrement, je souhaite ajuster automatiquement toutes les plages de dates qui se chevauchent (les réduire, les diviser ou les supprimer pour tenir compte de la nouvelle entrée - voir l'algorithme ci-dessous). Je veux également m'assurer qu'aucun enregistrement qui se chevauche ne peut être accidentellement inséré dans cette table. J'utilise Oracle et Java pour mon code d'application. Comment devrais-je appliquer la prévention des plages de dates qui se chevauchent et permettre également d'ajuster automatiquement les plages de chevauchement? Dois-je créer un déclencheur AFTER INSERT, avec un dbms_lock pour sérialiser l'accès, pour empêcher le chevauchement des données. Puis en Java, appliquer la logique pour tout ajuster automatiquement? Ou cette partie doit-elle être en PL/SQL dans l'appel de procédure stockée? C'est quelque chose dont nous avons besoin pour quelques autres tables, donc ce serait bien de faire abstraction.Table de plage de réglage automatique

Si quelqu'un a quelque chose comme cela déjà écrit, s'il vous plaît partager :)

Je ne trouve cette référence: http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:474221407101

Voici un exemple de savoir comment traiter chacun des 4 cas de chevauchement pour le réglage à l'insertion :

= Example 1 = 
In DB (Start, End, Value): 
(0, 10, 'X') 
**(30, 100, 'Z') 
(200, 500, 'Y') 

Input 
(20, 50, 'A') 
Gives 
(0, 10, 'X') 
**(20, 50, 'A') 
**(51, 100, 'Z') 
(200, 500, 'Y') 


= Example 2 = 
In DB (Start, End, Value): 
(0, 10, 'X') 
**(30, 100, 'Z') 
(200, 500, 'Y') 

Input 
(40, 80, 'A') 
Gives 
(0, 10, 'X') 
**(30, 39, 'Z') 
**(40, 80, 'A') 
**(81, 100, 'Z') 
(200, 500, 'Y') 


= Example 3 = 
In DB (Start, End, Value): 
(0, 10, 'X') 
**(30, 100, 'Z') 
(200, 500, 'Y') 

Input 
(50, 120, 'A') 
Gives 
(0, 10, 'X') 
**(30, 49, 'Z') 
**(50, 120, 'A') 
(200, 500, 'Y') 


= Example 4 = 
In DB (Start, End, Value): 
(0, 10, 'X') 
**(30, 100, 'Z') 
(200, 500, 'Y') 

Input 
(20, 120, 'A') 
Gives 
(0, 10, 'X') 
**(20, 120, 'A') 
(200, 500, 'Y') 

L'algorithme est la suivante:

given range = g; input range = i; output range set = o 

if i.start <= g.start 
    if i.end >= g.end 
    o_1 = i 
    else 
    o_1 = i 
    o_2 = (i.end + 1, g.end) 
else 
    if i.end >= g.end 
    o_1 = (g.start, i.start - 1) 
    o_2 = i 
    else 
    o_1 = (g.start, i.start - 1) 
    o_2 = i 
    o_3 = (i.end + 1, g.end) 

Répondre

2

J'ai généralement vu des modèles de données comme celui ayant le point de départ de la gamme étant le seul à être suivi, où le point de fin est alors implicite. Donc, ce serait

CREATE TABLE MY_TABLE 
(START_AT NUMBER, 
VALUE  NUMBER, 
CONSTRAINT MY_TABLE_PK (START_AT) 
); 

Si vous devez présenter des valeurs dans le format existant, vous pouvez utiliser l'analyse et une vue matérialisée, en utilisant LEAD(START_AT) OVER (ORDER BY START_AT) (je pense qu'il a raison, mais non testé) pour obtenir la valeur finale interprétée.

+0

@Adam: +1, bien que je définirais la clé primaire comme '(start_at)' de sorte que vous n'ayez aucune ambiguïté avec deux périodes commençant en même temps. Le compromis d'un tel modèle est également qu'il est plus difficile d'interroger que le modèle avec '(start, end)'. –

+0

Ma compréhension (et limitée) était que chaque clé avait ses propres gammes. Cela semble être correct, et de même, l'OP semble avoir des plages indéfinies. Si tel est le cas, l'OP doit également insérer (START_AT, VALUE) des enregistrements de fencepost (: previous_logical_end + 1, NULL). La suggestion de vue matérialisée était d'aider avec la question de question, bien que je ne pense pas qu'interroger la table de base elle-même est si mauvais, particulièrement pas avec l'analytique. Modification de la réponse –

+0

Adam, merci pour votre réponse. Je n'ai pas pensé à une telle solution. Avant votre suggestion, le modèle de données ressemble à quelque chose comme (product_id, start_date, end_date, discount_value). J'ai besoin d'obtenir le discount_value pour un produit sur une date. L'utilisateur saisit la date de début et de fin de l'exécution de la remise pour un produit, ainsi que le montant de la remise. Mon exemple spécifique est un peu plus compliqué, donc j'espère que l'exemple de produit/rabais est une représentation juste de mon problème. Oui, il existe des plages indéfinies. – Bradford

0

L'article d'AskTom donne un bon exemple de comment cela peut être fait, mais notez que cet exemple verrouille toute la table qui aura un impact sévère sur la concurrence de votre application.

Si la concurrence est une question pour vous, vous devez simplement ajouter une colonne de séquence (avec ORDER option si vous utilisez RAC) et d'écrire une requête comme ceci:

SELECT * 
FROM (
     SELECT *, rownum AS rn 
     FROM mytable 
     WHERE start_date <= :date 
       AND end_date >= :date 
     ORDER BY 
       seq DESC 
     ) 
WHERE rn = 1 

pour connaître la portée effective (et d'autres données) pour une date donnée.

Ceci renverra la dernière plage insérée contenant la date donnée.

Vous pouvez faire cette requête un peu plus efficace en exécutant une procédure de maintenance qui permettrait de se débarrasser des gammes qui se chevauchent en temps opportun (comme décrit dans le poste) et la réécriture de la requête comme ceci:

SELECT * 
FROM (
     SELECT *, rownum AS rn2 
     FROM (
       SELECT * 
       FROM (
         SELECT *, rownum AS rn 
         FROM mytable 
         WHERE seq <= :lseq 
           AND start_date <= :date 
           AND end_date >= :date 
         ORDER BY 
           start_date DESC 
         ) 
       WHERE rn = 1 
       UNION ALL 
       SELECT * 
       FROM (
         SELECT *, rownum AS rn 
         FROM mytable 
         WHERE seq > :lseq 
           AND start_date <= :date 
           AND end_date >= :date 
         ORDER BY 
           seq DESC 
         ) 
       WHERE rn = 1 
       ) 
     ORDER BY 
       seq DESC 
     ) 
WHERE rn2 = 1 

Créez les index sur start_date et seq pour que cela fonctionne rapidement. Cette dernière requête sélectionnera la première plage de correspondance parmi les plages traitées (qui sont connues pour ne pas se chevaucher), la première plage de correspondance parmi les plages non traitées (qui sont peu nombreuses) et des deux enregistrements sélectionnera la plage un avec le plus haut seq.

+0

J'aime ne pas avoir à me soucier du verrouillage, mais j'ai du mal à voir comment la dernière requête nettoiera les données puisque vous avez spécifié la: date. En outre, l'utilisateur doit voir les plages de dates.Il semble qu'il soit difficile pour l'utilisateur de déterminer quelles valeurs sont associées à chaque plage de dates. Cependant, je n'ai pas spécifié cette partie dans ma question. – Bradford