none
2 Tabellen mit 1:1-Beziehung synchronisieren? RRS feed

  • Frage

  • Hallo,

    ich habe zwei Tabellen:

    • personal
      idx int,fname varchar(50),lname varchar(50),
      primary key(idx)
    • personal_detail
      idx int,detail_1 int,detail_2 int,
      primary key (idx),
      foreign key (idx) references personal(idx) on delete cascade

    Diese beiden Tabellen sollen immer synchron sein, d. h. wenn für jeden Eintrag in der Tabelle personal soll es einen Eintrag in der Tabelle personal_detail geben.

    Das kann ich natürlich auf Anwendungsebene machen, aber ist es auch auf Datenbankebene möglich?

    Wie würde man das realisieren? Falls man es mit einem INSTEAD OF-Trigger lösen könnte, wie würde dieser dann genau aussehen? Wie geht man insbesondere damit um, wenn ein INSERT-Befehl nur einige aber nicht alle Attribute enthielte?

    Danke
    Magnus

    Montag, 21. März 2016 05:38

Antworten

  • Hallo Knorpi,

    Du hast Recht - die Fremdschlüsseleinschränkung bezieht sich nur die Detailtabelle aber nicht auf die Mastertabelle. Wie Benjamin bin ich selbst auch immer etwas skeptisch in Bezug auf Trigger. Sie haben einige Nachteile, die halt abgewogen werden müssen (Dabei lasse ich mal die Probleme aus Sicht eines Programmierers aussen vor!):

    • Trigger erzeugen temporäre Objekte (INSERTED und DELETED) in TEMPDB
    • Trigger sind Bestandteil der Benutzertransaktion und blockieren Ressourcen, die im Trigger angesprochen werden
    • Die Verwendung von Triggern führt häufig zu Deadlock-Situationen, wenn im Trigger weitere Objekte angesprochen werden (wie in Deinem Beispiel)

    Dein Beispiel als Referenz sieht Punkt 2 der "Bedenken" wie folgt aus:

    CREATE TABLE dbo.personal
    (
    	idx		int			NOT NULL	PRIMARY KEY CLUSTERED,
    	fname	char(50)	NOT NULL,
    	lname	char(50)	NOT NULL
    );
    
    CREATE TABLE dbo.personal_detail
    (
    	personal_idx	int	NOT NULL	PRIMARY KEY CLUSTERED,
    	detail_1		int	NOT NULL	DEFAULT (0),
    	detail_2		int	NOT NULL	DEFAULT (0),
    
    	CONSTRAINT fk_personal FOREIGN KEY
    	(personal_idx) REFERENCES dbo.personal (idx)
    	ON DELETE CASCADE
    );
    GO
    
    -- Insert ONE record to avoid system blocked resources
    -- when locks are investigated
    INSERT INTO dbo.personal (idx, fname, lname) VALUES (1, 'Uwe', 'Ricken');
    GO
    
    -- Create the trigger for insertion of data in reference table
    CREATE TRIGGER dbo.trg_personal_io_insert
    ON dbo.personal
    INSTEAD OF INSERT
    AS
    	SET NOCOUNT ON;
    
    	-- Insert data from personal
    	INSERT INTO dbo.personal
    	SELECT * FROM inserted;
    
    	-- Insert data into referencial table
    	INSERT INTO dbo.personal_detail (personal_idx)
    	SELECT	i.idx
    	FROM	inserted AS i LEFT JOIN dbo.personal_detail AS d
    			ON (i.idx = d.personal_idx)
    	WHERE	d.personal_idx IS NULL;
    
    	SET NOCOUNT OFF;
    GO

    Das obige Beispiel habe ich aus Deinen Anforderungen entnommen und den entsprechenden Trigger (hätte auch ein INSERT-Trigger sein können) implementiert. Nun wird ein weiterer Datensatz eingetragen. Dabei wird aber dieser Vorgang in einer expliziten Transaktion ausgeführt. Ich lasse die Transaktion zunächst geöffnet!

    BEGIN TRANSACTION insertdata;
    GO
    
    	INSERT INTO dbo.personal (idx, fname, lname)
    	VALUES (2, 'Beate', 'Ricken');
    
    	SELECT	resource_type,
    			CASE WHEN resource_type = N'OBJECT'
    				 THEN OBJECT_NAME(resource_associated_entity_id)
    				 ELSE CAST(resource_associated_entity_id AS nvarchar(255))
    			END			AS	resource_type,
    			request_mode,
    			request_type,
    			request_status
    	FROM	sys.dm_tran_locks
    	WHERE	request_session_id = @@SPID;

    Der erste Teil der Abfrage fügt in die Tabelle dbo.personal einen neuen Datensatz ein. Im zweiten Statement überprüfe ich, welche Ressourcen durch die Benutzertransaktion blockiert sind.

    An der Abbildung kannst Du sehr schön erkennen, dass Ressourcen beider Tabellen blockiert werden. Solange die Transaktion nicht abgeschlossen ist, kann keine der beiden Ressourcen freigegeben werden und andere Prozesse müssen warten.

    1:1-Beziehungen machen - entgegen der Meinung von Benjamin - in manchen Szenarien sicherlich Sinn. Zum Beispiel sind sie sinnvoll, wenn nur wenige Datensätze in der "Master"-Tabelle zugehörige Detaildaten besitzen; nur dieses Szenario ist in Deinem Modell nicht vorgesehen - also (aus meiner Sicht) kein Vorteil!

    Sie kommen aber eher selten vor.  Tatsächlich - und da bin ich wieder auf der Seite von Benjamin - sind die Informationen der "Detailtabelle" ja vollständig vom Schlüssel der Mastertabelle abhängig; und damit wiederum sollten sie Bestandteil der Mastertabelle sein.

    1:1-Beziehungen haben wir in der Entwicklung in den 90ern verwendet, um z. B. Personendaten von Gehaltsdaten zu trennen. Letztendlich ging das aber immer zu Lasten der Performance, da ja ein JOIN zwischen Master und Detail vorhanden sein musste. Besser ware ein Konzept, in dem Du keinen Zugriff auf die Tabellen selbst sondern auf Views oder Stored Procedures erteiltst, die wiederum die Sicht auf die Daten einschränken.


    MCM - SQL Server 2008
    MCSE - SQL Server 2012
    db Berater GmbH
    SQL Server Blog (german only)

    Dienstag, 22. März 2016 13:25
  • So habe mal ein Beispiel für die gespeicherte Prozedur fertig (nicht schön aber sollte als Beispiel funktionieren)

    use master
    go
    
    create database demodb
    go
    
    use demodb
    go
    
    create table personen_details(
    id int not null,
    abteilung char(20) not null,
    geburtstag date not null,
    CONSTRAINT PK_ID PRIMARY KEY NONCLUSTERED (id));
    go
    
    create table personen(
    id int not null,
    name char(20) not null,
    vorname char(20) not null,
    CONSTRAINT FK_personen_details FOREIGN KEY (id) 
        REFERENCES personen_details (id) 
        ON DELETE CASCADE
        ON UPDATE CASCADE)
    go
    
    
    CREATE PROCEDURE Personeninsert 
        @id int,
    	@Name char(20), 
        @Vorname char(20), 
    	@abteilung char(20),
    	@geburtag date
    AS 
    
    	insert into personen_details values(@id,@abteilung,@geburtag);
    	insert into personen values (@id, @Name,@Vorname);
    GO
    
    exec Personeninsert 2 ,'Name' ,'Vorname' ,'IT' ,'01/01/2000' 

    Gruß Benjamin

    PS. Meine Ablehnung für 1:1 Realtionen stammt noch aus dem Studium wo man für sowas gleich böse Punkte abgezogen bekommen hat. Stichwort Normalisierung


    Benjamin Hoch
    MCSE: Data Platform
    MCSA: Windows Server 2012
    Blog


    Dienstag, 22. März 2016 16:05

Alle Antworten

  • Hallo Knorpi,

    wenn ich deine Tabellendefinition richtig lese, dann hast du es bereits richtig umgesetzt. Um dein Ziel zu erreichen muss man eine Fremdschlüsselbeziehung setzt

    https://msdn.microsoft.com/de-de/library/ms189049%28v=sql.120%29.aspx?f=255&MSPPError=-2147217396

    Durch das setzten von "not null" wird erzwungen dass es auch einen Wert in der anderen Tabelle geben muss. SOnst gibt es eine Fremdschlüsselverletzung. Trigger braucht man nur wenn die beiden Tabellen nicht in der selben Datenbank sind.

    Wenn du nur eine 1:1 Beziehung hast, kannst du die zweite Tabelle besser weg lassen und alle Daten in die erste Tabelle schreiben. Eine zweite Tabelle macht eigentlich erst ab einer 1:n Beziehung sinn.

    Wenn ein Atribut immer angegeben werden muss, wird die entsprechende Spalte mit "not Null" definiert. So wird sichergestellt dass ein Wert vorhanden sein muss. Ohne den Wert würde der Einfügevorgang abbrechen.

    Gruß Benjamin


    Benjamin Hoch
    MCSE: Data Platform
    MCSA: Windows Server 2012
    Blog

    Montag, 21. März 2016 06:37
  • Hallo Benjamin,

    danke für Deine Hinweise.

    Die FOREIGN KEY-Klausel bewirkt m. M. nach nur, dass die Werte für idx in der Tabelle personal_detail aus der Tabelle personal kommen müssen.

    Aber wie kann man erreichen, dass bei einem INSERT in der Tabelle personal automatisch eine entsprechende Zeile in in der Tabelle personal_detail angelegt wird?

    Magnus

    Montag, 21. März 2016 07:23
  • Hallo Magnus,

    das ginge bspw. über einen Trigger in "personal". Siehe dazu bspw.:

      http://stackoverflow.com/questions/13897321/t-sql-insert-trigger-to-insert-update-on-if-condition-on-multiple-tables

    Also in etwa so:

    CREATE TRIGGER [dbo].[AddPersonalDetail]
    ON [dbo].[personal]
    AFTER INSERT
    AS 
    BEGIN
    
        SET NOCOUNT ON;
    
        INSERT INTO dbo.personal_detail( detail_1 )
        SELECT idx FROM inserted
    
    END
    
    GO
    

    Beachte aber, dass ein Trigger nicht zwingend für einen einzigen Datensatz ausgeführt wird. Die inserted und deleted Tabellen können, wie jede andere Tabelle auch, x Datensätze gleichzeitig beinhalten. Das sollte man berücksichtigen.


    Gruß, Stefan
    Microsoft MVP - Visual Developer ASP/ASP.NET
    http://www.asp-solutions.de/ - Consulting, Development
    http://www.aspnetzone.de/ - ASP.NET Zone, die ASP.NET Community

    Montag, 21. März 2016 07:36
    Moderator
  • Ein Trigger ist hier keine gute Wahl. Da es sich um eine 1:1 Beziehung handelt frage ich mich aber immer noch warum es zwei getrennte Tabellen gibt. 

    Wenn du dies aber dennoch so umsetzen möchtest empfehle ich dir eine gespeicherte Prozedur welche automatisch einen Insert in beide Tabellen macht. 

    https://msdn.microsoft.com/de-de/library/ms345415%28v=sql.120%29.aspx?f=255&MSPPError=-2147217396


    Benjamin Hoch
    MCSE: Data Platform
    MCSA: Windows Server 2012
    Blog

    Montag, 21. März 2016 09:35
  • Hallo Benjamin,

    warum ist ein Trigger hier keine gute Wahl?

    Die gespeicherte Prozedur kann man umgehen, das INSERT nicht.

    Ansonsten kann es durchaus Gründe für eine separate Tabelle mit 1:1-Beziehung geben. Das möchte ich in diesem Thread aber nicht weiter thematisieren.

    Magnus

    Montag, 21. März 2016 15:16
  • Hallo Magnus,

    Warum ist der Trigger hier keine gut Wahl? Nun der Trigger kann zwar super einen neuen Eintrag in der zweiten Tabelle setzten. Aber was kann den der Trigger dort sinnvoll eintragen? Nicht außer dem Fremdschlüssel den es aus der ersten Tabelle bekommt. Woher soll der Trigger auch die anderen Informationen bekommen. Entweder stehen sie schon in der ersten Tabelle (dann brauche ich sie in der zweiten nicht) oder der Trigger kann hellsehen. Somit würde der Trigger einen Datensatz ohne inhaltlichen nutzen produzieren. Ob einem durch einen Datensatz mit Nullwerten und dem Fremdschüssel geholfen ist? Da kann ich auch alle paar Stunden prüfen ob es neue Datensätze in der einen Tabelle gibt und die fehlenden dann nachträglich erzeugen. Die fehlenden Datensätze kann man dann ganz leicht durch einen Left Join (Right Join ginge auch) herausfinden.

    Eine richtig gesetzte Fremdschlüsselbeziehung kann man nicht umgehen. Entweder man gibt gleich alle nötigen Informationen mit um beide Tabellen sinnvoll zu füllen oder der Insert scheitert. Von daher wäre es auch überhaupt kein Problem die gespeicherte Prozedur zu umgehen, denn auch ein direkter Insert ohne die erforderlichen Werte für beide Tabellen würde scheitern. Die gespeicherte Prozedur würde hier nur als Hilfsmittel dienen alle Daten korrekt zu erfassen und als Paarinsert zu verarbeiten.

    So ich hoffe mal es klar geworden warum ich es nicht über einen Trigger lösen würde.

    Gruß Benjamin


    Benjamin Hoch
    MCSE: Data Platform
    MCSA: Windows Server 2012
    Blog

    Montag, 21. März 2016 18:33
  • Hallo Benjamin,

    Deine Aussage zu der Fremdschlüsselbeziehung würde ich gerne nochmal vertiefen:

    "Eine richtig gesetzte Fremdschlüsselbeziehung kann man nicht umgehen."

    So wie ich die Beziehung definiert habe, stellt diese nur sicher, dass als Werte für das Attribut idx in der Tabelle personal_detail nur existierende Werte aus der Tabelle personal in Frage kommen, mehr nicht. Habe ich hier eventiell nicht alle Möglichkeiten ausgeschöpft?

    Ansonsten kann ich diese Fremdschlüsselbeziehung sehr wohl ignorieren, indem ich zu einem Eintrag in personal den zugehörigen Eintrag in personal_detail einfach nicht erstelle.

    Gruss

    Magnus

    Dienstag, 22. März 2016 07:15
  • Hallo Knorpi,

    Du hast Recht - die Fremdschlüsseleinschränkung bezieht sich nur die Detailtabelle aber nicht auf die Mastertabelle. Wie Benjamin bin ich selbst auch immer etwas skeptisch in Bezug auf Trigger. Sie haben einige Nachteile, die halt abgewogen werden müssen (Dabei lasse ich mal die Probleme aus Sicht eines Programmierers aussen vor!):

    • Trigger erzeugen temporäre Objekte (INSERTED und DELETED) in TEMPDB
    • Trigger sind Bestandteil der Benutzertransaktion und blockieren Ressourcen, die im Trigger angesprochen werden
    • Die Verwendung von Triggern führt häufig zu Deadlock-Situationen, wenn im Trigger weitere Objekte angesprochen werden (wie in Deinem Beispiel)

    Dein Beispiel als Referenz sieht Punkt 2 der "Bedenken" wie folgt aus:

    CREATE TABLE dbo.personal
    (
    	idx		int			NOT NULL	PRIMARY KEY CLUSTERED,
    	fname	char(50)	NOT NULL,
    	lname	char(50)	NOT NULL
    );
    
    CREATE TABLE dbo.personal_detail
    (
    	personal_idx	int	NOT NULL	PRIMARY KEY CLUSTERED,
    	detail_1		int	NOT NULL	DEFAULT (0),
    	detail_2		int	NOT NULL	DEFAULT (0),
    
    	CONSTRAINT fk_personal FOREIGN KEY
    	(personal_idx) REFERENCES dbo.personal (idx)
    	ON DELETE CASCADE
    );
    GO
    
    -- Insert ONE record to avoid system blocked resources
    -- when locks are investigated
    INSERT INTO dbo.personal (idx, fname, lname) VALUES (1, 'Uwe', 'Ricken');
    GO
    
    -- Create the trigger for insertion of data in reference table
    CREATE TRIGGER dbo.trg_personal_io_insert
    ON dbo.personal
    INSTEAD OF INSERT
    AS
    	SET NOCOUNT ON;
    
    	-- Insert data from personal
    	INSERT INTO dbo.personal
    	SELECT * FROM inserted;
    
    	-- Insert data into referencial table
    	INSERT INTO dbo.personal_detail (personal_idx)
    	SELECT	i.idx
    	FROM	inserted AS i LEFT JOIN dbo.personal_detail AS d
    			ON (i.idx = d.personal_idx)
    	WHERE	d.personal_idx IS NULL;
    
    	SET NOCOUNT OFF;
    GO

    Das obige Beispiel habe ich aus Deinen Anforderungen entnommen und den entsprechenden Trigger (hätte auch ein INSERT-Trigger sein können) implementiert. Nun wird ein weiterer Datensatz eingetragen. Dabei wird aber dieser Vorgang in einer expliziten Transaktion ausgeführt. Ich lasse die Transaktion zunächst geöffnet!

    BEGIN TRANSACTION insertdata;
    GO
    
    	INSERT INTO dbo.personal (idx, fname, lname)
    	VALUES (2, 'Beate', 'Ricken');
    
    	SELECT	resource_type,
    			CASE WHEN resource_type = N'OBJECT'
    				 THEN OBJECT_NAME(resource_associated_entity_id)
    				 ELSE CAST(resource_associated_entity_id AS nvarchar(255))
    			END			AS	resource_type,
    			request_mode,
    			request_type,
    			request_status
    	FROM	sys.dm_tran_locks
    	WHERE	request_session_id = @@SPID;

    Der erste Teil der Abfrage fügt in die Tabelle dbo.personal einen neuen Datensatz ein. Im zweiten Statement überprüfe ich, welche Ressourcen durch die Benutzertransaktion blockiert sind.

    An der Abbildung kannst Du sehr schön erkennen, dass Ressourcen beider Tabellen blockiert werden. Solange die Transaktion nicht abgeschlossen ist, kann keine der beiden Ressourcen freigegeben werden und andere Prozesse müssen warten.

    1:1-Beziehungen machen - entgegen der Meinung von Benjamin - in manchen Szenarien sicherlich Sinn. Zum Beispiel sind sie sinnvoll, wenn nur wenige Datensätze in der "Master"-Tabelle zugehörige Detaildaten besitzen; nur dieses Szenario ist in Deinem Modell nicht vorgesehen - also (aus meiner Sicht) kein Vorteil!

    Sie kommen aber eher selten vor.  Tatsächlich - und da bin ich wieder auf der Seite von Benjamin - sind die Informationen der "Detailtabelle" ja vollständig vom Schlüssel der Mastertabelle abhängig; und damit wiederum sollten sie Bestandteil der Mastertabelle sein.

    1:1-Beziehungen haben wir in der Entwicklung in den 90ern verwendet, um z. B. Personendaten von Gehaltsdaten zu trennen. Letztendlich ging das aber immer zu Lasten der Performance, da ja ein JOIN zwischen Master und Detail vorhanden sein musste. Besser ware ein Konzept, in dem Du keinen Zugriff auf die Tabellen selbst sondern auf Views oder Stored Procedures erteiltst, die wiederum die Sicht auf die Daten einschränken.


    MCM - SQL Server 2008
    MCSE - SQL Server 2012
    db Berater GmbH
    SQL Server Blog (german only)

    Dienstag, 22. März 2016 13:25
  • Es gibt schon Fälle, wo 1:1-Beziehungen Sinn machen, z.B. bei Personen und LastLoginID oder LastPageVisited - die häufig aktualisiert werden und daher nicht in der Stammtabelle stehen müssen - manchmal will man daraus evtl. auch eine 1:n-Tabelle machen. Der Unterdatensatz sollte aber immer optional sein, Krücken mit Trigger etc. machen es unnötig kompliziert und können auch die Performance beeinträchtigen.

    Sebastian Leupold (MVP)

    Dienstag, 22. März 2016 15:11
  • So habe mal ein Beispiel für die gespeicherte Prozedur fertig (nicht schön aber sollte als Beispiel funktionieren)

    use master
    go
    
    create database demodb
    go
    
    use demodb
    go
    
    create table personen_details(
    id int not null,
    abteilung char(20) not null,
    geburtstag date not null,
    CONSTRAINT PK_ID PRIMARY KEY NONCLUSTERED (id));
    go
    
    create table personen(
    id int not null,
    name char(20) not null,
    vorname char(20) not null,
    CONSTRAINT FK_personen_details FOREIGN KEY (id) 
        REFERENCES personen_details (id) 
        ON DELETE CASCADE
        ON UPDATE CASCADE)
    go
    
    
    CREATE PROCEDURE Personeninsert 
        @id int,
    	@Name char(20), 
        @Vorname char(20), 
    	@abteilung char(20),
    	@geburtag date
    AS 
    
    	insert into personen_details values(@id,@abteilung,@geburtag);
    	insert into personen values (@id, @Name,@Vorname);
    GO
    
    exec Personeninsert 2 ,'Name' ,'Vorname' ,'IT' ,'01/01/2000' 

    Gruß Benjamin

    PS. Meine Ablehnung für 1:1 Realtionen stammt noch aus dem Studium wo man für sowas gleich böse Punkte abgezogen bekommen hat. Stichwort Normalisierung


    Benjamin Hoch
    MCSE: Data Platform
    MCSA: Windows Server 2012
    Blog


    Dienstag, 22. März 2016 16:05
  • Hallo zusammen,

    zunächst einmal vielen Dank für die ausführlichen Hinweise und sorry für die Verspätung...

    Der Punktabzug bei fehlender Normalisierung war im Studium schon richtig. Aus heutiger Sicht würde ich das aber vorsichtig vergleichen wollen mit der übertriebenen Kopfbewegung bei der Führerscheinprüfung, damit der Prüfer sieht, dass man in den Spiegel geschaut hat. Die grundlegenden Regeln sind natürlich wichtig, aber nicht immer um jeden Preis. Der Vergleich mag weit her geholt sein, passt m. M. zumindest auf die Ablehnung von 1:1-Tabellen ganz gut. Wenn ich bspw. sehr viele Attribute zu einer Person habe, die in einige wenige Kategorien fallen und ich auf der Oberfläche für jede Kategorie einen eigenen Bereich (z. B. "Tab") habe, dann macht es für mich durchaus Sinn, die Attribute auf verschiedene Tabellen aufzuteilen. Wenn ich das nicht tue und trotzdem die Kategorisierung auch im Modell abbilden möchte, muss ich dies über die Attributnamen tun, z. B. durch die Verwendung von Präfixen (cat1_attr1, cat1_attr2, ...), was  das Datenmodell auch nicht gerade kompakter macht. Ich würde daher 1:1-Beziehungen nicht grundsätzlich ablehnen.

    Nichtdestotrotz muss ich aber einräumen, dass ich in diesem Fall eigentlich nur eine optionale 1:1-Beziehung brauche, also eine, bei der die Einträge in der Tabelle personal_detail optional sind. Der Grund, warum ich sicherstellen wollte, dass zu jedem Eintrag in der Tabelle personal auch ein Eintrag in der Tabelle personal_detail existiert, ist schlicht die Vermeidung einer Unsicherheit (s. u.) bei der anschliessenden Abfrage.

    Zur Erklärung muss ich die Tabellen konkretisieren:

    • personal
      idx int,fname varchar(50),lname varchar(50),
      primary key(idx)
    • personal_detail
      idx int,ersthelfer bit,ersthelfer_ausbildung date,
      primary key (idx),
      foreign key (idx) references personal(idx) on delete cascade

    Das boolesche Attribut ersthelfer gibt an, ob eine Person zu den "Ersthelfern" gehört, also im Notfall erste Hilfe leisten kann. Das Attribut ersthelfer_ausbildung enthält ggf. das Datum, an dem die Person dazu ausgebildet wurde.

    Nun soll eine vorhandene, auf der Tabelle personal basierende, Abfrage um diese Attribute erweitert werden. Hier ein vereinfachtes Beispiel mit einem Sub-Select:

    SELECT idx,fname,lname,
    (SELECT ersthelfer FROM personal_detail WHERE (idx = personal.idx)) AS ersthelfer
    FROM personal

    Hier soll das Attribut ersthelfer true sein, wenn es einen passenden Eintrag in der Tabelle personal_detail gibt und das darin enthaltene Attribut ersthelfer true ist. In allen anderen Fällen soll es false sein. Leider nimmt es auch den Wert NULL an, wenn es keinen passenden Eintrag in der Tabelle personal_detail gibt. Aber auch in diesen Fällen soll es false sein.

    Ich habe hier bewusst ein Sub-Select angegeben, da die Abfrage in Wirklichkeit bereits aus mehreren Joins besteht und ich diese nicht unnötig anwachsen lassen möchte, damit der View "wartbar" bleibt. Auch die Möglichkeit einer Skalar-Funktion ist mir bewusst, aber auch diese möchte ich zunächst vermeiden, da es in der Tabelle personal_detail noch viele solcher Attribute gibt. Wenn es nicht anders geht, tendiere ich aber dazu.

    Gibt es eine einfache Möglichkeit, innerhalb des Sub-Selects die NULL-Werte durch false-Werte zu ersetzen? Wie würdet Ihr das ansonsten lösen?

    Ansonsten nochmals danke für die Erläuterungen. Ein Trigger scheidet für mich danach aus.

    Viele Grüße
    Magnus






    • Bearbeitet Knorpi Mittwoch, 30. März 2016 06:35
    Mittwoch, 30. März 2016 06:30
  • Wenn es dir nur darum geht einen Null wert zu maskieren würde ich hier einfach einen anderen Weg nehmen.

    Persönlich wäre mein Weg die allgemeine Tabelle mit zwei zusätzlichen Spalten zu erweitern

    istErsthelfer bit not null default (0)
    Ersthelfer seit date null
     

    solange nichts angebenen wird ist der Status Ersthelfer 0 (false), dieser wird immer vom System automatisch gesetzt. Beim setzten des Datumswertes kann man dann auch automatisch mit ändern lassen.

    Oder man definiert ein Datumwert als false wie 01.01.1900 welche dann dort eingetragen wird und dann in der Abfrage über die Where Bedingung gefiltert wird.

    istErsthelfer date not null default (01/01/1900)
    Gruß Benjamin
     


    Benjamin Hoch
    MCSE: Data Platform
    MCSA: Windows Server 2012
    Blog

    Mittwoch, 30. März 2016 06:56
  • Danke, könnte man auch die obige Abfrage anpassen?
    Mittwoch, 30. März 2016 08:29
  • Ich wäre vorsichtig mit "falschen" Default-Werten und zusätzlichen "Merkfeldern" in Tabellen, die zu Inkonsistenzen führen können. Bei BIT-Feldern ist ein Default oft sinnvoll, allerdings kann man auf "istErsthelfer" verzichten, wenn man Null in "ErsthelferSeit" als "kein Ersthelfer" interpretiert.

    Will man Nullwerte bei der Übergabe an die Anwendung vermeiden, kann man das bequem in einer View erreichen:

    SELECT IsNull(ErsthelferSeit, 1900-01-01T00:00) AS ErsthelferSeit,
    CASE WHEN ErsthelferSeit IS Null THEN 0 ELSE 1 END AS IstErsthelfer
    FROM Personal


    Sebastian Leupold (MVP)

    Mittwoch, 30. März 2016 08:37
  • Hier würde ich einen Join bevorzugen aber sonst

    SELECT A.idx,A.fname,A.lname, D.ersthelfer 
    FROM personal_detail as D, personal as A 
    where  A.idx = D.idx


    Benjamin Hoch
    MCSE: Data Platform
    MCSA: Windows Server 2012
    Blog


    Mittwoch, 30. März 2016 08:39
  • Das liefert aber nur die Datensätze, bei denen es einen Eintrag in personal_detail gibt.


    Viele Grüße
    Magnus

    Mittwoch, 30. März 2016 10:19
  • SELECT A.idx,
           A.fname,
           A.lname, 
           ISNull(D.ersthelfer, 0) as IstErsthelfer 
    FROM personal_detail as D
    LEFT JOIN personal   as A ON A.idx = D.idx


    Sebastian Leupold (MVP)

    Mittwoch, 30. März 2016 10:34
  • Hier ist das beste Beispiel warum eine 1:1 Beziehung nicht zielführend ist. Du versucht hier ein Problem zu lösen was du ohne die 1:1 Beziehung gar nicht hättest. 

    Speicher diese eine Information IstErsthelfer in die normale personal Tabelle und du kannst über ganz simple Select Abfrage alle gewünschten Informationen bekommen und musst nicht auf teure Joins zurückgreifen.


    Benjamin Hoch
    MCSE: Data Platform
    MCSA: Windows Server 2012
    Blog

    Mittwoch, 30. März 2016 10:40
  • Benjamin,

    ich stimme zu, dass es für eine Eigenschaft keinen Sinn macht, aber wenn dann noch etliche Detailinformationen dazu kommen (Kenntnisse, Prüfungen) und nicht jeder Ersthelfer ist, macht es Sinn, diese in einer Untertabelle abzulegen.


    Sebastian Leupold (MVP)

    Mittwoch, 30. März 2016 10:45
  • Wenn es wie du sagst mehre Eigenschaften werden wie Prüfungen sind, wird es ja keine 1:1 Beziehung mehr da man dann ja am besten einen eignen Datensatz pro Prüfung/Fähigkeit anlegen kann/sollte. Man schreibt ja nicht in ein Char Feld dann "1. Prüfung 16.01.13, 2. Prüfung 19.05.14". Somit bekommt man ja eine 1:n Beziehung welche so absolut richtig in einer Untertabelle aufgehoben ist. 

    Benjamin Hoch
    MCSE: Data Platform
    MCSA: Windows Server 2012
    Blog

    Mittwoch, 30. März 2016 10:50
  • Wenn es wie du sagst mehre Eigenschaften werden wie Prüfungen sind, wird es ja keine 1:1 Beziehung mehr da man dann ja am besten einen eignen Datensatz pro Prüfung/Fähigkeit anlegen kann/sollte. Man schreibt ja nicht in ein Char Feld dann "1. Prüfung 16.01.13, 2. Prüfung 19.05.14". Somit bekommt man ja eine 1:n Beziehung welche so absolut richtig in einer Untertabelle aufgehoben ist. 
    Da stimme ich zu, das wäre dann die flexibelste Lösung.

    Sebastian Leupold (MVP)

    Mittwoch, 30. März 2016 10:52