Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Datenbanken (https://www.delphipraxis.net/15-datenbanken/)
-   -   [PLSQL] Gibt es ein "Select oder Insert"- Befehl? (https://www.delphipraxis.net/152756-%5Bplsql%5D-gibt-es-ein-select-oder-insert-befehl.html)

sirius 5. Jul 2010 13:35

Datenbank: Oracle • Version: 11 • Zugriff über: verschiedenes

[PLSQL] Gibt es ein "Select oder Insert"- Befehl?
 
Wenn man einen Datensatz updaten möchte, aber nicht sicher ist, ob er bereits existiert kann man das Kommando "MERGE INTO" nutzen. Welches in einem Abwasch bspw. eine "Select count.."-Abfrage macht und dann entscheidet, ob ein Insert oder ein update notwendig ist.

Ich brauche dies aber des öftern für einen anderen Konstrukt. Ich möchte eine Entscheidung ob ich ein "Select ID ..." oder ein "Insert ... returning ID ..." brauche in einem Befehl (ID ist bei mir immer der PK) Da ich vermute, dass ich damit nicht der einzige bin (ich im Netz aber nichts finde; bei "Select oder Insert" findet man zu viel), würde ich gern mal wissen, ob es dafür etwas gibt.
Bisher löse ich das folgendermaßen:
SQL-Code:
Select count(ID) into c from ...

if c=0 then
  begin
    insert into dbt_Channel ...
      returning ID into result;  
    commit;
  exception
    when dup_val_on_index then
      c:=1; -- falls zwischenzeitlich ein zweiter User einen Datensatz eingefügt hat?!?
    when others then
      raise;
  end;
end if;

if c>0 then
  Select ID into result ...
end if;

return(result);

himitsu 5. Jul 2010 13:46

AW: [PLSQL] Gibt es ein "Select oder Insert"- Befehl?
 
Ob es bei PLSQL geht, weiß ich nicht, aber die SQL-Syntax kennt da ein "Insert or Update".

Es ist praktisch ein INSERT und falls es Probleme mit doppelten/vorhandenen Keys gibt, dann wird das UPDATE versucht.

SQL-Code:
INSERT INTO `table` ( 'field1', 'field2' ) VALUES ( 'value1', 'value2' )
ON DUPLICATE KEY UPDATE 'field2' = 'value2'
field1 ist hierbei z.B. ein Primary-/Unique-Key und deshalb hab ich ihn beim Update weggelassen, da er sich ja eh nicht ändert
und über diesen schon der zu ändernde Datensatz ausgewählt wurde.

mkinzler 5. Jul 2010 13:48

AW: [PLSQL] Gibt es ein "Select oder Insert"- Befehl?
 
Liefert dir MERGE die ID nicht zuück?

Unter FireBird könnte man
SQL-Code:
Update or insert ... matching ... returning ID;
verwenden

sirius 5. Jul 2010 13:56

AW: [PLSQL] Gibt es ein "Select oder Insert"- Befehl?
 
Oh Sorry,

ich sehe, ich habe die Frage falsch erklärt. Eine "insert-update" Variante gibt es (nennt sich "Merge Into"). Was ich suche ist eine "Select - Insert returning" Variante (wie im Codebesipiel als umständliche Langform gezeigt).

Schonmal Danke für Eure Bemühungen.

Edit: Wobei mir deine Syntax, himi, auch neu ist (schön wenn man mal redet, da lernt man immer was), ich sie aber hier nicht brauche.

HeZa 5. Jul 2010 14:29

AW: [PLSQL] Gibt es ein "Select oder Insert"- Befehl?
 
Irgendwie hast du mehr Code als nötig.

SQL-Code:
begin
  insert into dbt_Channel ...
    returning ID into result;  
  commit;
  return(result);
exception
  when dup_val_on_index then
    return ID; -- falls zwischenzeitlich ein zweiter User einen Datensatz eingefügt hat?!?
  when others then
    raise;
end;
[/QUOTE]

sirius 5. Jul 2010 14:36

AW: [PLSQL] Gibt es ein "Select oder Insert"- Befehl?
 
Hi HeZa,

jo, so kann man es auch machen. Es bestehen aber zwei Nachteile:
1. Exception, die man vorher abfangen kann sind doof (und normalerweise auch sehr performanceraubend, zumal in dem Fall bis auf den ersten Eintrag immer die Exception geworfen wird)
2. Da auf das Insert ein Trigger (inkl. sequence) angelegt ist, welcher die ID setzen soll (=AutoInc bei mySQL), läuft der auch hoch, obwohl das Insert schief gelaufen ist.

shmia 5. Jul 2010 15:30

AW: [PLSQL] Gibt es ein "Select oder Insert"- Befehl?
 
Das Grundproblem ist die ungeeignete Erzeugung des Wertes für den Primärschlüssel.
SQL-Code:
Select count(ID) into c from ...
Wehe es wird ein Datensatz gelöscht - dann gerät das ganze System durcheinander.
Besser ist da:
SQL-Code:
Select Max(ID)+1 into c from ...
Aber auch das ist nicht wasserdicht wenn mehr als ein Prozess/Thread das abarbeitet.
Was man hier wirklich bräuchte wäre ein Generator.
Dann würde man zuerst mit dem Generator eine "neue Nummer ziehen" und könnte dann sicher sein, dass es keine Kollisionen von anderen Prozessen gibt.

himitsu 5. Jul 2010 15:44

AW: [PLSQL] Gibt es ein "Select oder Insert"- Befehl?
 
Zitat:

Zitat von shmia (Beitrag 1033622)
und könnte dann sicher sein, dass es keine Kollisionen von anderen Prozessen gibt.

Kann man Tabellen nicht auch sperren?

Mschmidt 5. Jul 2010 16:28

AW: [PLSQL] Gibt es ein "Select oder Insert"- Befehl?
 
Zitat:

Zitat von himitsu (Beitrag 1033624)
Zitat:

Zitat von shmia (Beitrag 1033622)
und könnte dann sicher sein, dass es keine Kollisionen von anderen Prozessen gibt.

Kann man Tabellen nicht auch sperren?

jeap select ... for Update [nowait];

borwin 5. Jul 2010 16:30

AW: [PLSQL] Gibt es ein "Select oder Insert"- Befehl?
 
Hallo Sirius,

irgendwie passt das nicht zusammen wie Du es beschreibst.
Zitat:

"Select ID ..." oder ein "Insert ... returning ID ..."
Willst Du wissen ob es die ID schon gibt musst Du in der WHERE Bedingung die ID abfragen.

Da gibt es zwei Möglichkeiten um das zu prüfen.

SQL-Code:
begin
  select ID
  into result
  from tabelle
  where ID = übergabeparameter
exception
 when no_data_found then
   -- Neuen Datensatz einfügen
   insert into tabelle .....
end
anderer weg wäre

SQL-Code:
declare
 vlb_found boolean := false;
begin
for rec in (select * from tabelle where ID = parameter) loop
  vlb_found := true;
end loop;
 if not vlb_found then
   -- neuen Datensatz einfügen
   insert into tabelle .....
 end if;
end;
Die ID kommt hoffentlich aus einer Sequenz im Trigger? Sonst einbauen.

Gruß Borwin

sirius 5. Jul 2010 16:42

AW: [PLSQL] Gibt es ein "Select oder Insert"- Befehl?
 
@shmia:
Das ist nicht das Problem. Mit count will ich ja nicht den Wert ermitteln um den neuen Primärschlüssel (ID) zu erzeugen, sondern nur, ob der Datensatz (mit meiner ID) schon vorhanden ist, oder nicht. Zur Erzeugung der ID nutze ich natürlich eine Sequence (was in Oracle wohl dasselbe ist wie ein Generator bei firebird). Und diese wird hier per Trigger (auf before Insert) zugeführt.

@himi: Ja, man kann die Tabelle sperren. Finde ich aber nicht als geeignetes Mittel für so eine kurze Sache.


Ich machs jetzt Mal an einem meiner zahlreichen konkreten Umsetzungen ausführlich

1. Tabelle
SQL-Code:
CREATE TABLE "DBT_ZEIT"
   (   "ID_ZEIT" NUMBER NOT NULL ENABLE,
   "DATUM" DATE NOT NULL ENABLE,
   "STUNDE" NUMBER(2,0) NOT NULL ENABLE,
    CONSTRAINT "DBT_ZEIT_PK" PRIMARY KEY ("ID_ZEIT") ENABLE,
    CONSTRAINT "DBT_ZEIT_UK1" UNIQUE ("DATUM", "STUNDE") ENABLE
   ) ;
2. Sequence und Trigger
SQL-Code:
CREATE SEQUENCE "SEQ_ZEIT" MINVALUE 1 MAXVALUE 999999999999999999999999999 INCREMENT BY 1 START WITH 1 CACHE 20 NOORDER NOCYCLE ;

CREATE OR REPLACE TRIGGER "TRG_ZEIT"
BEFORE INSERT ON DBT_ZEIT
FOR EACH ROW
BEGIN
 SELECT SEQ_Zeit.NEXTVAL INTO :NEW.ID_Zeit FROM DUAL;
END;
Soweit alles ok. Die Sequence und Trigger entsprechen, wie gesagt, dem AutoInc bei mySQL. Erzeugt werden die beiden Sachen beim SQL Developer automatisch mit der Tabellenerzeugung, wenn man in dem Wizard entsprechend die Angaben setzt.

Jetzt kommt die SP mit besagtem unschönen Code:
SQL-Code:
create or replace
PROCEDURE "ADDTRAFFIC"
(aZeit in Date,
 aSrcIP in VarChar2,
 aDestIP in VarChar2,
 aTraffic in Number
) is
nTraffic64 Number(10) := 0;
nDatum Date;
nStunde Number(2);
id Number;
cnt Integer;
begin
  if aTraffic<64 then
    nTraffic64:=64-aTraffic;
  end if;
 
  nDatum:=trunc(aZeit);
  nStunde:=Extract(Hour from to_timestamp(aZeit));
 
   
  -- ****** Hier beginnt der Problembereich ******************************
  select count(id_zeit) into cnt from dbt_Zeit where
        Datum=nDatum and
        Stunde=nStunde;
  if cnt=0 then
    begin
      insert into dbt_Zeit (Datum,Stunde)
        Values (nDatum,nStunde)
        returning ID_Zeit into id;
    exception
      when dup_val_on_index then
        cnt:=1;
      when others then
        raise;
    end;
  end if;
  if cnt>0 then
    select id_zeit into id from dbt_Zeit where
        Datum=nDatum and
        Stunde=nStunde;
  end if;
  -- *** Ende des Problembreiches *************************************
 
  --commit;
 
 
  -- Für einen Insert or update gibt es folgenden Konstrukt:
  merge into dbt_Traffic
    using
      (Select count(Traffic) as cnt from dbt_Traffic
       where ID_Zeit=id
       and (SrcIP=aSRCIP or (SrcIP is null and aSRCIP is null ))
       and (DestIP=aDestIP or (DestIP is null and aDestIP is null))) e
    on (e.cnt>0)  
  WHEN MATCHED THEN
    update set
        Traffic=Traffic+aTraffic,
        Traffic64=Traffic64+nTraffic64,
        Count_=Count_+1
        where ID_Zeit=id
        and (SrcIP=aSRCIP or (SrcIP is null and aSRCIP is null ))
        and (DestIP=aDestIP or (DestIP is null and aDestIP is null))
  WHEN NOT MATCHED THEN
    insert (ID_Zeit,SRCIP,DestIP,Traffic,Traffic64)
      Values(id,aSrcIP,aDestIP,aTraffic,nTraffic64);
 
 
 
 
  dbms_alert.signal('NewTraffic',to_char(nDatum,'dd.mm.yyyy') || ' ' ||
    to_char(nStunde) || ' ' ||
    aSRCIP || ' ' ||
    aDestIP || ' ' ||
    aTraffic || ' ' ||
    nTraffic64 || ' ' ||
    '1');
 
  commit;
 
end;
Also mich interessiert der Block ab "Select count(ID_Zeit) ..." (Problembereich). Kann man den in einen einzigen Befehl fassen (und ohne Exceptions nutzen).

@Borwin (roter Kasten)
Das ist das, was ich derzeit mache (nur von der anderen Seite aus). Nur ich hätte es gern ohne Exceptions gelöst (also ohne, dass ich mich bewusst auf eine Exception verlasse, denn das ist nach meinem Wissen nicht der Sinn einer Exception=Ausnahme). In deinem Beispiel würde noch eine Exception beim Insert geworfen, die du abfangen müsstest, wenn zwischenzeitlich ein anderer User den Eintrag gemacht hat.

borwin 5. Jul 2010 16:53

AW: [PLSQL] Gibt es ein "Select oder Insert"- Befehl?
 
Schaue mal auf die zweite Variante. Die ist ohne Exception.

Zitat:

declare
vlb_found boolean := false;
begin
for rec in (select * from tabelle where ID = parameter) loop
vlb_found := true;
exit;
end loop;
if not vlb_found then
-- neuen Datensatz einfügen
insert into tabelle .....
end if;
end;


Gruß Borwin

sirius 5. Jul 2010 17:00

AW: [PLSQL] Gibt es ein "Select oder Insert"- Befehl?
 
Ok, aber

1. Die eine Exception ist weg. Aber ich bleine noch bei meiner ursprünglichen.
Denn wie ist es denn gesichert, dass 2 User nicht gerade gleichzeitig diese SP ausführen?
Beide haben kurz hintereinander fesgestellt, dass dieser Datensatz noch nicht existiert (und zwar bevor ein User diesen einträgt). Jetzt versuchen beide User den Datensatz einzutragen. Einer wird eine Exception bekommen (dup_val_on_index), die es abzufangen gilt. Letztenendes bin ich dann wieder bei meiner Variante.
2. Dauert das nicht, alle Datensätze erst durchzugehen?

borwin 5. Jul 2010 17:16

AW: [PLSQL] Gibt es ein "Select oder Insert"- Befehl?
 
Zitat:

2. Dauert das nicht, alle Datensätze erst durchzugehen?
Nein. Das ist ggf. Schnelle als ein Count, da er nicht gleich alle Daten fetch. Das muss er für einen Count machen.
Nach dem ersten Datensatz geht er ja mit EXIT aus dem loop.

Gruß Borwin

idefix2 5. Jul 2010 20:52

AW: [PLSQL] Gibt es ein "Select oder Insert"- Befehl?
 
Ich verstehe nicht ganz, was Du eigentlich willst, bzw., wo das problem liegt.

Normalerweise ist der Primärschlüssel id doch ein willkürlicher Wert, der nur zur Identifikation des Datensatzes dient. Wenn Du diesen Wert mittels Trigger und Sequence erzeugst, ist er zwangsläufig eindeutig. Sobald Du den Sequencewert abrufst, ist er schon verbraucht, und wenn der nächste einen Datensatz in die Tabelle schreibt, bekommt er über den Trigger ohnedies eine neue Nummer. Es ist also vom System her gar nicht möglich, dass das Datenbanksysstem versucht, eine id ein zweites mal zu vergeben.

mkinzler 5. Jul 2010 21:34

AW: [PLSQL] Gibt es ein "Select oder Insert"- Befehl?
 
U.U sind aber Lücken nicht erwünscht :wink:

idefix2 5. Jul 2010 22:40

AW: [PLSQL] Gibt es ein "Select oder Insert"- Befehl?
 
Wenn man davon ausgeht, dass praktisch in allen Anwendungen gelegentlich Datensätze auch wieder gelöscht werden, sind Lücken nicht wirklich vermeidbar. Irgendeine Programmlogik auf lückenlos fortlaufende Nummern der Datensätze aufzubauen würde ich als Designfehler einstufen.

HeZa 6. Jul 2010 00:00

AW: [PLSQL] Gibt es ein "Select oder Insert"- Befehl?
 
@sirius

Irgendwie finde ich das ganze Konstrukt nicht so griffig.

Erzeuge doch alle Datensätze der Tabelle DBT_ZEIT im voraus. Das sind 876000 Datensätze einer sehr schmalen Tabelle für die nächsten 100 Jahre.

Damit sparst du dir
  • das Insert
  • die Sequenz
  • den Trigger
  • das abfangen der Exception dup_val_on_index

Das hat für die Robustheit und Performance der SP nur Vorteile und der Code wird auch übersichtlicher und damit auch verständlicher.

Sherlock 6. Jul 2010 08:23

AW: [PLSQL] Gibt es ein "Select oder Insert"- Befehl?
 
Wenn Du Lücken vermeiden willst (das scheint sich als Hintergrund herauszukristallisieren), nutze nicht Sequences und Trigger, sondern vergib selber die ID.

Sherlock

sirius 6. Jul 2010 08:54

AW: [PLSQL] Gibt es ein "Select oder Insert"- Befehl?
 
Danke Euch für die Antworten,

@HeHa, ja in dem Fall könnte man das vielleicht sogar so lösen (weis nicht, ob das so üblich ist). Aber ich sagte ja, dass dies nur ein Beispiel unter vielen ist. Ich schreib halt immer wieder die selbe Befehlssequenz die da besteht aus
  • Ermitteln ob der Datanesatz schon vorhanden ist
  • Wenn ja, dann PK holen
  • Wenn nein dann Insert probieren
    • falls Insert fehl schlägt (Exception abfangen) dann PK von dem inzwischen eigentragenen Datensatz holen
Und das halt nicht nur bei dbt_Zeit, sondern auch für verschiedene andere Tabellen, wo das nicht so geht. (Ich wollt ja erst nicht ein Beispiel posten, weil ich schon erwartete, dass die Antworten dann zu konkret auf das Beispiel bezogen sind) Also nehmt das Beispiel nur als Beispiel. Ich wollt hier nur einmal wissen, wie andere Datenbanknutzer in ihren Projekten damit umgegangen sind (ich bin ja hoffentlich nicht der erste) und ob es zufällig sogar einen Befehl gibt in Oracle.

zu den Lücken: Lücken kann man nicht vermeiden, aber man muss ja nicht schon von vorn herein im Konzept ca. 10000 IDs überspringen.

@idefix2: Ja, der nächste Datensatz hätte einen neuen PK, aber alle anderen Werte wären gleich. Ich hätte somit einen überflüssigen Eintrag. Dies ist nicht gewünscht und wird meistens durch einen unique constraint verhindert.

@borwin: Danke für den Code. Behebt zwar nicht das urspüngliche Problem, aber beschleunigt meine bisherige Lösung.

:thumb: Danke Euch allen für Eure Beiträge. Aber ich befürchte langsam, dass ich der einzige bin, der ständig über dieses Problem stolpert.

Edit: @Sherlock, Selber PK zu vergeben ist aber ziemlich riskant. Aber wie gesagt, ist nicht das eigentliche Problem.

HeZa 6. Jul 2010 09:37

AW: [PLSQL] Gibt es ein "Select oder Insert"- Befehl?
 
Zitat:

Zitat von sirius (Beitrag 1033728)
Ich schreib halt immer wieder die selbe Befehlssequenz die da besteht aus
  • Ermitteln ob der Datanesatz schon vorhanden ist
  • Wenn ja, dann PK holen
  • Wenn nein dann Insert probieren
  • falls Insert fehl schlägt (Exception abfangen) dann PK von dem inzwischen eigentragenen Datensatz holen

...

Ich wollt hier nur einmal wissen, wie andere Datenbanknutzer in ihren Projekten damit umgegangen sind (ich bin ja hoffentlich nicht der erste) und ob es zufällig sogar einen Befehl gibt in Oracle.

...

Danke Euch allen für Eure Beiträge. Aber ich befürchte langsam, dass ich der einzige bin, der ständig über dieses Problem stolpert.

Du bist bestimmt nicht der erste der dieses Muster verwendet. Du scheinst es aber als eine Art Standard Muster zu verwenden und das ist es nicht. Wir können uns natürlich nur auf dieses eine Beispiel beziehen. Ich bin mir fast sicher, das kein anderer hier die Sache so wie du lösen würde.

Die Tabelle DBT_ZEIT macht einen kranken Eindruck. Sie hat einen eindeutigen Schlüssel (Datum, Stunde) und du gibst ihr noch einen künstlichen warum? Warum überhaupt eine Tabelle DBT_ZEIT? Trage Datum und Stunde in DBT_Traffic ein und nimm sie mit in den Primary Key auf. Warum ein Datumsfeld und ein Stundenfeld wenn die Stunde doch mit in das Datumsfeld passt?

Übernehme nur das Datumsfeld in DBT_TRAFFIC (lösche ID_ZEIT) und fülle es mit ROUND(<Datum>, 'HH'). Das würde deine Inserts erheblich beschleunigen und deine SP würde nur aus dem MERGE und dem Signal senden bestehen.

Ich weiß ja nichts über die Update-Häufigkeit und die Datenmenge. Mein erster Ansatz wäre erstmal jeden Traffic-Datensatz mit dem genauen Zeitpunkt zuspeichern. Verdichtung würde ich erst für die Auswertung.

Für mich sieht deine Lösung etwas exotisch aus. Bring doch mal eine der anderen Stellen wo dieses Muster verwendest. Vielleicht kann man ja auch dort aufzeigen wie es einfacher, sicherer und schneller geht.

sirius 6. Jul 2010 10:12

AW: [PLSQL] Gibt es ein "Select oder Insert"- Befehl?
 
Zitat:

Zitat von HeZa (Beitrag 1033740)
Für mich sieht deine Lösung etwas exotisch aus. Bring doch mal eine der anderen Stellen wo dieses Muster verwendest. Vielleicht kann man ja auch dort aufzeigen wie es einfacher, sicherer und schneller geht.

Ok.
SQL-Code:
--  Die Tabelle dbt_Channel führt verschiedene IDs (alles FK) aus anderen Tabellen zusammen zu einem neuen einzigen Key.
-- Diese FKs bilden einen unique index (in anderen Varianten gab es noch zusätzliche Spalten, welche außerhalb jedweden Indexes lagen)
-- der PK (Spaltenname: ID), der hier in dbt_Channel gebildet wird, wird in verschiedenen Messwerttabellen verwendet, welche mit Abstand dann auch die größten Tabellen sind.

    Select count(ID) into c from dbt_channel
     where ID_Characteristic=CharacteristicID
     and ID_BaseIndicator=BaseIndicatorID
     and ID_Indicator=IndicatorID
     and ID_Phase=PhaseID
     and ID_Unit=UnitID
     and ID_Interval=IntervalID
     and Frequency=aFrequency;
    if c>0 then
      if aDoInsert then
        RaiseAlreadyExist('Channel');
      end if;
    else
      if not aDoInsert then
        RaiseNotExist('Channel');
      end if;
      begin
        insert into dbt_Channel (Id_Characteristic,Id_Phase,Id_Indicator,
                               id_Unit,Id_Interval,Frequency,Id_Baseindicator)
            values (CharacteristicID,
                    PhaseID,
                    IndicatorID,
                    UnitID,
                    IntervalID,
                    aFrequency,
                    BaseIndicatorID)

            returning ID into ChannelID;  
        commit;
      exception
        when dup_val_on_index then
          c:=1;
        when others then
          raise;
      end;
    end if;
    if c>0 then
      Select ID into ChannelID from dbt_channel
       where ID_Characteristic=CharacteristicID
       and ID_BaseIndicator=BaseIndicatorID
       and ID_Indicator=IndicatorID
       and ID_Phase=PhaseID
       and ID_Unit=UnitID
       and ID_Interval=IntervalID
       and Frequency=aFrequency;
    end if;
    return(ChannelID);


   -- Die Reaktion auf aDoInsert ist hier nochmal gesondert zu sehen und muss nicht zwingend hier beachtet werden

HeZa 6. Jul 2010 10:26

AW: [PLSQL] Gibt es ein "Select oder Insert"- Befehl?
 
Ok, ich habe es so gewollt :shock:.

Dann bitte aber auch noch die Beschreibung dieser Channel Tabelle und deren ungefähre Update-Häufigkeit (grob 10 pro Stunde oder tausende pro Minute oder gar Sekunde?).

sirius 6. Jul 2010 10:41

AW: [PLSQL] Gibt es ein "Select oder Insert"- Befehl?
 
SQL-Code:
prompt
prompt Creating table DBT_CHANNEL
prompt ==========================
prompt
create table DBT_CHANNEL
(
  ID               NUMBER not null,
  ID_BASINDICATOR  NUMBER not null,
  ID_INDICATOR     NUMBER not null,
  ID_PHASE         NUMBER not null,
  ID_UNIT          NUMBER not null,
  ID_INTERVAL      NUMBER not null,
  FREQUENCY        NUMBER(*,2),
  ID_CHARACTERISTIC NUMBER not null
)
;
alter table DBT_CHANNEL
  add constraint DBT_CHANNEL_PK primary key (ID);
alter table DBT_CHANNEL
  add constraint DBT_CHANNEL_UK1 unique (ID_BASINDICATOR, ID_INDICATOR, ID_PHASE, ID_UNIT, ID_INTERVAL, FREQUENCY, ID_CHARACTERISTIC);
alter table DBT_CHANNEL
  add constraint DBT_CHANNEL_DBT_BASEINDIC_FK1 foreign key (ID_BASINDICATOR)
  references DBT_BASEINDICATOR (ID) on delete cascade;
alter table DBT_CHANNEL
  add constraint DBT_CHANNEL_DBT_CHARACTER_FK1 foreign key (ID_CHARACTERISTIC)
  references DBT_CHARACTERISTIC (ID);
alter table DBT_CHANNEL
  add constraint DBT_CHANNEL_DBT_INDICATOR_FK1 foreign key (ID_INDICATOR)
  references DBT_INDICATOR (ID) on delete cascade;
alter table DBT_CHANNEL
  add constraint DBT_CHANNEL_DBT_INTERVAL_FK1 foreign key (ID_INTERVAL)
  references DBT_INTERVAL (ID) on delete cascade;
alter table DBT_CHANNEL
  add constraint DBT_CHANNEL_DBT_PHASE_FK1 foreign key (ID_PHASE)
  references DBT_PHASE (ID) on delete cascade;
alter table DBT_CHANNEL
  add constraint DBT_CHANNEL_DBT_UNIT_FK1 foreign key (ID_UNIT)
  references DBT_UNIT (ID) on delete cascade;
Auf dem PK ID liegt wieder eine Sequence und ein Trigger (wie bei dbt_Zeit)

Ein neuer Eintrag kommt recht unregelmäßig. Zeit spielt hier nicht die oberste Rolle.


Zitat:

Zitat von HeZa (Beitrag 1033752)
Ok, ich habe es so gewollt :shock:.

Ich hab das auch noch mit dynamischen SQL gelöst, welches mehrere solche Tabellen mit ähnlicher Tabellenstruktur (von den "wichtigen" Spalten her) bedient.

Und bitte wegrationalisiere mir meine ausnormalisierte Tabelle hier nicht weg ;)
Es ist durchaus möglich, dass hier noch Spalten mit Zusatzinformationen ergänz werden

HeZa 6. Jul 2010 13:12

AW: [PLSQL] Gibt es ein "Select oder Insert"- Befehl?
 
Hallo Sirius,

hier ein Entwurf. Da mir der Context fehlt kann ich natürlich nichts testen. Was mir besser gefällt ist, das im Fall, wenn die Channel-Daten schon gespeichert wurden nur einmal die Tabelle dbt_channel abgefragt wird. Der Rest beruht eher darauf, das ich keine Sorgen bei der Verwendung von Exceptions habe, aber vielleicht fehlen mir da auch nur die schlechten Erfahrungen. :)

SQL-Code:
BEGIN
  LOOP
    BEGIN
      -- ChannelID suchen
      select ID into ChannelID from
        dbt_channel
      where
        ID_Characteristic=CharacteristicID
        and ID_BaseIndicator=BaseIndicatorID
        and ID_Indicator=IndicatorID
        and ID_Phase=PhaseID
        and ID_Unit=UnitID
        and ID_Interval=IntervalID
        and Frequency=aFrequency;
       
      if aDoInsert THEN
        -- existiert, sollte aber nicht
        RaiseAlreadyExist('Channel');
      end if;
     
      -- ansonsten sind wir fertig  
      RETURN ChannelID;
    EXCEPTION
      WHEN NO_DATA_FOUND THEN  
        -- Channel-Daten noch nicht angelegt
       
        if not aDoInsert THEN
          -- sollten aber angelegt sein
          RaiseNotExist('Channel');
        end if;
       
        BEGIN
          insert into dbt_Channel (
            Id_Characteristic, Id_Phase, Id_Indicator, id_Unit, Id_Interval, Frequency, Id_Baseindicator
          ) values (
            CharacteristicID, PhaseID, IndicatorID, UnitID, IntervalID, aFrequency, BaseIndicatorID
          ) returning ID into ChannelID;
          commit;
         
          RETURN ChannelID;
        EXCEPTION
          when dup_val_on_index then
            NULL; -- den Datensatz gibt es doch schon, alles von vorne
        END;  
    END;
  END LOOP;
END;
Da ich nicht weiß wie du ChannelID verwendest, bleibt der Verdacht, dass auch diese Tabelle überflüssig seinen könnte. Diese Tabelle scheint mir wie die DBT_ZEIT Tabelle eine Gruppierung darzustellen, die man auch aus den Daten gewinnen könnte wenn man statt der ChannelID die sieben anderen Werte abspeichern würde. Aber ich weiß natürlich nicht wie viele Datensätze anderer Tabellen von der ChannelID abhängen oder wie teuer es ist, die Gruppierung live aus den von ChannelID abhängigen Daten zu ziehen.

sirius 6. Jul 2010 13:40

AW: [PLSQL] Gibt es ein "Select oder Insert"- Befehl?
 
Sorum habe ich das auch schon einmal aufgezogen. Dadurch wird halt im Normalfall nur ein Select gemacht, und in den meisten Fällen ist das auch ausreichend, denn der entsprechende Datensatz existiert ja nur einmal nicht, ab dann existiert er ja immer und wird auch recht häufig verlangt.

Ich hatte irgendwann dann auf die jetzige Variante umgestellt, weil...tja schon lang her. Exceptions brauchen auf jeden Fall viel Zeit, aber wie das im Vergleich zu einer vollständigen Abfrage ist, weiß ich nicht. Letztenendes spielt es hier keine wichtige Rolle, da in diesem Fall relativ selten drauf zugrgriffen wird (für Datenbankverhältnisse "selten").

Die ID_Channel wird in (aktuell) drei weiteren Datenbanktabellen verwendet. Diese Tabellen werden sehr groß. Derzeit sind dort die Spalten ID_Channel, ID_Measurement, UTCDate + TimeShift, Value (Messwert) enthalten. Man hat also einen Kosten/Nutzenfaktor von ca 1 zu 3 (wenn man mal die Größe der Spalten vernachlässigt). Wenn ich jetzt alle IDs aus dbt_Channel speichern müsste, würde dieser Faktor bedeutend schlechter (siehe auch Normalisierung von Datenbanktabellen). Hinzu kommt, dass ich zu den Channels noch Zusatzinformationen ergänzen werde (zukünftig). Aktuell würde ich sie einfach als neue Spalte in dbt_Channel legen.

Diese IF-Verzweigung mit aDoInsert kannst du weglassen. Das war jetzt nur noch hier mit drinn.


Edit (nochmal zu einem anderen Beitrag):
Zitat:

Zitat von HeZa (Beitrag 1033740)
Übernehme nur das Datumsfeld in DBT_TRAFFIC (lösche ID_ZEIT) und fülle es mit ROUND(<Datum>, 'HH').

Würde das nicht bedeuten, dass jedwede Art der Normalisierung nicht gemacht werden soll. Du löst ja meine untergeordneten Tabellen auf (auch ein Vorschlag bei dbt_Channel). Dann haben wir am Ende immer nur eine Tabelle mit allen Angaben.
Deswegen kann ich es nochmal auf den Punkt bringen: Alle PK-FK-Kombinationen müssen doch dieses Problem haben. Was macht man da?

HeZa 6. Jul 2010 16:29

AW: [PLSQL] Gibt es ein "Select oder Insert"- Befehl?
 
Zitat:

Zitat von sirius (Beitrag 1033786)
Edit (nochmal zu einem anderen Beitrag):
Zitat:

Zitat von HeZa (Beitrag 1033740)
Übernehme nur das Datumsfeld in DBT_TRAFFIC (lösche ID_ZEIT) und fülle es mit ROUND(<Datum>, 'HH').

Würde das nicht bedeuten, dass jedwede Art der Normalisierung nicht gemacht werden soll. Du löst ja meine untergeordneten Tabellen auf (auch ein Vorschlag bei dbt_Channel). Dann haben wir am Ende immer nur eine Tabelle mit allen Angaben.
Deswegen kann ich es nochmal auf den Punkt bringen: Alle PK-FK-Kombinationen müssen doch dieses Problem haben. Was macht man da?

Eine untergeordnete Tabelle einzuführen ist nicht zwangsläufig mit Normalisierung gleich zu setzen.

DBT_CHANNEL und DBT_ZEIT bestehen nur aus dem Primarschlüssel und haben damit eigentlich keine Daseinsberechtigung. Es gibt keine Spalten in diesen Tabellen die von diesen Daten abhängen. Dies verschleierst du ein wenig in dem du für den natürlichen zusammengesetzten Schlüssel, einen weiteren künstlichen vergibst.

Bei DBT_ZEIT ist das besonders deutlich zu sehen. Datum und Stunde sind der eigentliche Schlüssel der Tabelle. ID_ZEIT wird noch dazu erfunden. Datum und Stunde lassen sich mit Round(<DateTime>, 'HH') einfach in einem Date-Feld speichern. Du hast also eine Tabelle mit 2 Feldern die beide Unique sind und das eine Feld ID_Zeit wird nur gebraucht um auf den anderen Wert zu verweisen.

Speicherst du den gerundeten DateTime-Wert gleich in der Tabelle DBT_TRAFFIC wird alles einfacher.

statt
SQL-Code:
select * from
  dbt_traffic t
  inner join dbt_zeit z on
    z.id_zeit = t.id_zeit and z.datum between date '2010-01-01' and date '2010-03-31'
schreibst du dann
SQL-Code:
select * from
  dbt_traffic t
where
  t.DatumStd between date '2010-01-01' and date '2010-03-31 23:23:59'
und bei einem insert in die DBT_TRAFFIC Tabelle musst du DatumStd auf Round(sysdate, 'HH') setzen.

Du würdest doch auch nicht

SQL-Code:
create table LEUTE
  Nachname varchar(30),
  Vorname varchar(30)
);
in die folgenden Tabellen aufteilen

SQL-Code:
create table LEUTE
  Nachname varchar(30),
  VornameID number(10)
);

create table VORNAMEN
  VornameID number(10),
  Vorname varchar(30)
);
Oder?

Für DBT_CHANNEL mag es praktische Gründe geben, z.B. ist es immer nervig wenn man, um zwei Tabellen zu joinen immer 7 Felder angeben musst, aber das kann ich gar nicht beurteilen, da ich nicht ersehen kann was mit der ChannelID angestellt wird.

sirius 7. Jul 2010 08:54

AW: [PLSQL] Gibt es ein "Select oder Insert"- Befehl?
 
Was du sagst ist ja richtig, und für dbt_Zeit im jetzigen Zustand auch i.O. Aber stell dir mal vor, wenn ich demnächst zu einer bestimmten Stunde noch einen Eintrag machen will, bspw. ein externes Ereignis, irgendetwas beschreibendes. Dies wäre bei deiner gekürzten Variante nicht möglich bzw. würde IMHO die 2NF verletzen. (das war übrigens ein kleines Projekt nur für meinen Rechner, welches mir nur kurz Informationen über bestimmte Verbindungen sammeln soll; wann welche Verbindung wie viel Traffic pro Stunde erzeugt; das Programm ist quasi im Fließen und eine Erweiterung zu gewissen Informationen je Stunde war mir schon im Kopf)

Bei dbt_Channel wäre deine Variante sehr umständlich (zumal auch wieder das Prblem der Erweiterung steht). Wie gesagt, die ID aus dbt_Channel wird als FK in derzeit 3 weiteren Tabellen verwendet. Dies sieht folgendermaßen aus
  • ID_Channel (FK)
  • OrtsID (FK)
  • Zeitangabe
  • Messwert
  • ... je nach Tabelle
Und diese Tabelle wird sehr groß. Zu etwa 30 verschiedenen (gleichzeitig verwendeten) OrtsID, gibt es ca. 200 verschiedene (und gleichzeitig verwendete) ID_Channel. Zu jeder Kombination werden mindestens jede Minute und zusätzlich noch aller 10 Minuten und, bei einer geringen Teilmenge an ID_Channel, jede Sekunde Messwerte abgespeichert.
Du siehst, ich habe ca. 30 Messgeräte welche ca. 200 verschiedene Messwerte ausspucken (grob geschätzt). Die Messwertbezeichnungen (ID_Channel) können im Laufe der Projektlaufzeit (Jahre) sich immer wieder ändern.
Jetzt alle IDs in die MEsswerttabellen zu legen würde ja die Datenbank sprengen.
Zumal sich das Problem ja nur verschiebt, denn die dbt_Channel fasst ja wieder nur andere IDS zusammen (deren Erstellung das selbe Problem haben). Es wird dann also quasi rekursiv noch größer.


Fazit: Meine Ursprungsidee für den Thread, war es herauszufinden, ob es einen einzigen Befehl für mein Problem gibt (ähnlich Merge into). Dies scheint nicht der Fall zu sein.


Alle Zeitangaben in WEZ +1. Es ist jetzt 12:21 Uhr.

Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz