SQL Server: Wie kann eine Tabelle so eingeschränkt werden, dass sie eine einzelne Zeile enthält?

81

Ich möchte eine einzelne Zeile in einer Konfigurationstabelle für meine Anwendung speichern. Ich möchte erzwingen, dass diese Tabelle nur eine Zeile enthalten kann.

Was ist der einfachste Weg, um die Einschränkung für einzelne Zeilen durchzusetzen?

Martin
quelle
Verwenden Sie eine Tabelle mit Spalten (Name, Value)mit einem Primärschlüssel für Name. Dann können Sie select Value from Table where Name = ?mit Sicherheit feststellen , dass entweder keine oder nur eine Zeile zurückgegeben wird.
Am
2
Ich bin mir nicht sicher, ob SQL hier die beste Lösung ist. Möglicherweise ist eine einfache XML-Datei für die Konfiguration besser geeignet. Ich denke immer, dass Konfiguration! = Daten und SQL für Daten gemacht wurden.
Remi Bourgarel
2
@ar - Ich habe gesehen, dass das schlecht läuft, wenn Sie beispielsweise eine Ganzzahl lesen möchten und in der Wertespalte einen schlecht formatierten Wert erhalten.
Damien_The_Unbeliever
@Damien_The_Unbeliever Warum sollte das passieren? Weil Sie einen nicht vorhandenen Wert für angegeben haben Name?
Noumenon
1
@ Noumenon - beachte, dass mein Kommentar eine Antwort auf ars Kommentar war. Das Problem ist, wenn Sie nur Name / Wert-Paare speichern, muss der Wert ziemlich gut eine Zeichenfolge sein, und Sie haben keine Möglichkeit , die Validierung in der Datenbank zu erzwingen . Wenn Sie eine einzeilige Tabelle mit separaten Spalten für jede Einstellung verwenden (wie vom OP gewünscht), können Sie die Validierung für jede Konfigurationseinstellung einfach über Prüfbeschränkungen erzwingen.
Damien_The_Unbeliever

Antworten:

97

Sie stellen sicher, dass eine der Spalten nur einen Wert enthalten kann, und legen diesen dann als Primärschlüssel fest (oder wenden eine Eindeutigkeitsbeschränkung an).

CREATE TABLE T1(
    Lock char(1) not null,
    /* Other columns */,
    constraint PK_T1 PRIMARY KEY (Lock),
    constraint CK_T1_Locked CHECK (Lock='X')
)

Ich habe eine Reihe dieser Tabellen in verschiedenen Datenbanken, hauptsächlich zum Speichern der Konfiguration. Es ist viel schöner zu wissen, dass Sie, wenn das Konfigurationselement ein int sein sollte, immer nur ein int aus der Datenbank lesen.

Damien_The_Unbeliever
quelle
2
+1. Dies ist der Ansatz, den Celko für eine Hilfskonstantentabelle in "SQL for Smarties" verwendet
Martin Smith
Dies ist so einfach wie es nur geht. Vielen Dank.
Martin
+1: Interessante Lösung. Ich hatte das noch nie gesehen, also danke fürs Teilen. Immer so, einfach, wenn Sie wissen wie ...
John Sansom
Hier ist eine Folgefrage zum Nachdenken. Was ist der Primärschlüssel dieser Tabelle? :)
nvogel
2
@BZ - cdt ist eine Abkürzung für comp.databases.theoryeine Usenet-Gruppe (sichtbar über Google-Gruppen), von der ich zugegebenermaßen in letzter Zeit nicht viel gelesen habe. Es war mehr auf relationale Theorie als auf SQL ausgerichtet - aber ich wusste zufällig, dass dportas / sqlvogel dieselbe Gruppe auch frequentierte. TTM war ein Verweis auf das Dritte Manifest , ein gutes Buch, das (wieder) eher über relationale Theorie als über SQL spricht.
Damien_The_Unbeliever
53

Normalerweise verwende ich Damiens Ansatz, der für mich immer gut funktioniert hat, aber ich füge noch eines hinzu:

CREATE TABLE T1(
    Lock char(1) not null DEFAULT 'X',
    /* Other columns */,
    constraint PK_T1 PRIMARY KEY (Lock),
    constraint CK_T1_Locked CHECK (Lock='X')
)

Wenn Sie das "DEFAULT 'X'" hinzufügen, müssen Sie sich nie mit der Spalte "Sperren" befassen und müssen sich nicht daran erinnern, welcher Sperrwert beim ersten Laden der Tabelle angegeben wurde.

ACB
quelle
4
Die Standardeinschränkung sollte ebenfalls benannt werden, da sie sonst einen automatisch generierten, verwirrenden Namen erhält. Lock char(1) not null CONSTRAINT DF_T1_Lock DEFAULT 'X'
David S.
1
Außerdem sollten Sie Ihren Standardwert anders als 'X' festlegen. Ich habe meine Zeichenfolge länger gemacht, und dies ist jetzt meine Fehlermeldung, wenn ich versuche, eine zweite Zeile einzufügen: Verletzung der PRIMARY KEY-Einschränkung 'PK_RestrictToOneRow'. Doppelter Schlüssel kann nicht in Objekt 'dbo.1ROWTABLE' eingefügt werden. Der doppelte Schlüsselwert ist (Diese Tabelle ist an eine Zeile gebunden).
Geoff Griswald
14

Vielleicht möchten Sie diese Strategie überdenken. In ähnlichen Situationen fand ich es oft von unschätzbarem Wert, die alten Konfigurationszeilen für historische Informationen herumliegen zu lassen.

Zu diesem Zweck verfügen Sie über eine zusätzliche Spalte creation_date_time(Datum / Uhrzeit des Einfügens oder Aktualisierens) und einen Auslöser zum Einfügen oder Einfügen / Aktualisieren, der das aktuelle Datum / die aktuelle Uhrzeit korrekt auffüllt.

Um Ihre aktuelle Konfiguration zu erhalten, verwenden Sie Folgendes:

select * from config_table order by creation_date_time desc fetch first row only

(abhängig von Ihrem DBMS-Geschmack).

Auf diese Weise können Sie den Verlauf weiterhin für Wiederherstellungszwecke verwalten (Sie können Bereinigungsverfahren einleiten, wenn die Tabelle zu groß wird, dies ist jedoch unwahrscheinlich), und Sie können weiterhin mit der neuesten Konfiguration arbeiten.

paxdiablo
quelle
+1: Ich höre, was Sie sagen, aber ich ziehe es vor, den Änderungsverlauf in separaten Prüftabellen aufzuzeichnen.
Martin
+1: Um die interessante Idee der Implementierung eines Audit-Trails zu teilen.
John Sansom
Nett. JustSELECT TOP 1 ... ORDER BY creation_date_time DESC
Baodad
5

Sie können einen INSTEAD OF- Trigger implementieren , um diese Art von Geschäftslogik in der Datenbank zu erzwingen.

Der Trigger kann eine Logik enthalten, um zu überprüfen, ob bereits ein Datensatz in der Tabelle vorhanden ist, und in diesem Fall die Einfügung rückgängig zu machen.

Wenn Sie nun einen Schritt zurücktreten, um das Gesamtbild zu betrachten, frage ich mich, ob es möglicherweise eine alternative und geeignetere Möglichkeit gibt, diese Informationen zu speichern, beispielsweise in einer Konfigurationsdatei oder einer Umgebungsvariablen.

John Sansom
quelle
+1 - Ich kann sehen, wie der Auslöser funktionieren würde, und vielleicht werde ich auf diesen Ansatz zurückgreifen, aber ich mag Damiens einfache Einschränkung. Es gibt eine interessante Diskussion darüber, welche Art von Konfigurationsdaten in die Konfigurationsdatei und welche in die Datenbank gehören. In diesem Fall denke ich, dass die DB der richtige Ort ist. Kein Zweifel, ich werde es noch bereuen ... :-)
Martin
2

Ich benutze ein Bitfeld für den Primärschlüssel mit dem Namen IsActive. Es können also höchstens 2 Zeilen vorhanden sein, und der SQL-Code zum Abrufen der gültigen Zeile lautet: Wählen Sie * in den Einstellungen aus, wobei IsActive = 1 ist, wenn die Tabelle den Namen Einstellungen hat.

user3382925
quelle
2

Hier ist eine Lösung, die ich für eine Sperrtabelle gefunden habe, die nur eine Zeile enthalten kann und ein Y oder N enthält (z. B. einen Anwendungssperrstatus).

Erstellen Sie die Tabelle mit einer Spalte. Ich habe der einen Spalte eine Prüfbedingung auferlegt, damit nur ein Y oder N eingefügt werden kann. (Oder 1 oder 0 oder was auch immer)

Fügen Sie eine Zeile mit dem "normalen" Zustand in die Tabelle ein (z. B. N bedeutet nicht gesperrt).

Erstellen Sie dann einen INSERT-Trigger für die Tabelle, die nur ein SIGNAL (DB2) oder RAISERROR (SQL Server) oder RAISE_APPLICATION_ERROR (Oracle) enthält. Dadurch kann der Anwendungscode die Tabelle aktualisieren, aber INSERT schlägt fehl.

DB2-Beispiel:

create table PRICE_LIST_LOCK
(
    LOCKED_YN       char(1)   not null  
        constraint PRICE_LIST_LOCK_YN_CK  check (LOCKED_YN in ('Y', 'N') )
);
--- do this insert when creating the table
insert into PRICE_LIST_LOCK
values ('N');

--- once there is one row in the table, create this trigger
CREATE TRIGGER ONLY_ONE_ROW_IN_PRICE_LIST_LOCK
   NO CASCADE 
   BEFORE INSERT ON PRICE_LIST_LOCK
   FOR EACH ROW
   SIGNAL SQLSTATE '81000'  -- arbitrary user-defined value
     SET MESSAGE_TEXT='Only one row is allowed in this table';

Funktioniert bei mir.

David Ein Beamer
quelle
2

Alte Frage, aber wie wäre es mit IDENTITY (MAX, 1) eines kleinen Spaltentyps?

CREATE TABLE [dbo].[Config](
[ID] [tinyint] IDENTITY(255,1) NOT NULL,
[Config1] [nvarchar](max) NOT NULL,
[Config2] [nvarchar](max) NOT NULL
NeutronCode
quelle
Sie können eine weitere Zeile hinzufügen, indem Sie SET IDENTITY_INSERT verwenden.
Razvan Socol
1

Sie können einen Auslöser für die Einfügeaktion in die Tabelle schreiben. Wenn jemand versucht, eine neue Zeile in die Tabelle einzufügen, wird die Logik zum Entfernen der letzten Zeile im Einfüge-Triggercode aufgehoben.

Karan
quelle
-1
IF NOT EXISTS ( select * from table )
BEGIN
    ///Your insert statement
END
Sachin Shanbhag
quelle
-1

Hier können wir auch einen unsichtbaren Wert erstellen, der nach dem ersten Eintrag in der Datenbank identisch ist. Beispiel: Schülertabelle: ID: int Vorname: char Hier im Eingabefeld müssen wir denselben Wert für die ID-Spalte angeben, der einschränkt wie nach dem ersten Eintrag außer dem Schreiben von lock bla bla aufgrund der Primärschlüsseleinschränkung, so dass nur eine Zeile für immer vorhanden ist. Hoffe das hilft!

Yishagerew
quelle