[ 1 ] [
2 ] [ 3 ] [ 4 ] [
5 ] [ 6 ] [
7 ] [ 8 ] [
9 ]
Auch dass der Object Type "Indexes" nicht angewählt ist, d.h. auch
auf "Nothing" steht, soll uns nicht weiter beunruhigen. Im Script, das
xCase zum SQL Server sendet, sind die Indices nämlich bereits enthalten.
Drücken Sie auf "Generate Script" und das Script unsere Datenbank zu
erstellen wird von xCase generiert.
CREATE TABLE Parent
(
ParentID INT NOT NULL,
ParentDescr CHAR(40) NULL,
ParentValue NUMERIC(6,2) NULL,
CONSTRAINT PK_Parent PRIMARY KEY NONCLUSTERED
(ParentID)
)
go
CREATE TABLE Child
(
ChildID INT NOT NULL,
ParentID INT NOT NULL,
ChildDesc CHAR(40) NULL,
ChildType CHAR(1) NULL,
ItemID INT NOT NULL,
Quantity NUMERIC(6,0) NULL,
CONSTRAINT PK_Child PRIMARY KEY NONCLUSTERED
(ChildID)
)
go
CREATE NONCLUSTERED INDEX Child_Parent ON Child (ParentID)
go
CREATE NONCLUSTERED INDEX Child_Item ON Child (ItemID)
go
CREATE TABLE Item
(
ItemID INT NOT NULL,
ItemDescr CHAR(40) NULL,
ItemPrice NUMERIC(6,2) NULL,
ItemAvailability CHAR(1) NULL,
CONSTRAINT PK_Item PRIMARY KEY NONCLUSTERED
(ItemID)
)
go
ALTER TABLE Child
ADD CONSTRAINT Parent_Child FOREIGN KEY
( ParentID )
REFERENCES Parent
( ParentID )
go
ALTER TABLE Child
ADD CONSTRAINT Item_Child FOREIGN KEY
( ItemID )
REFERENCES Item
( ItemID )
go
/* Update Trigger 'T_U_Parent' for Table 'Parent' */
CREATE TRIGGER T_U_Parent ON Parent FOR UPDATE AS
BEGIN
DECLARE
@row_count INT,
@null_row_count INT,
@error_number INT,
@error_message VARCHAR(255)
SELECT @row_count = @@rowcount
IF @row_count = 0
RETURN
/* The Primary Key of 'Parent' cannot be modified if children exist in 'Child'
*/
IF UPDATE(ParentID)
BEGIN
IF EXISTS
( SELECT 1
FROM Child c, inserted i, deleted d
WHERE c.ParentID = d.ParentID
AND (i.ParentID != d.ParentID)
)
BEGIN
SELECT @error_number=30004,
@error_message='Children exist in "Child". Cannot modify Primary Key in "Parent".'
GOTO error
END
END
RETURN
/* Error Handling */
error:
RAISERROR @error_number @error_message
ROLLBACK TRANSACTION
END
go
/* Delete Trigger 'T_D_Parent' for Table 'Parent' */
CREATE TRIGGER T_D_Parent ON Parent FOR DELETE AS
BEGIN
DECLARE
@row_count INT,
@error_number INT,
@error_message VARCHAR(255)
SELECT @row_count = @@rowcount
IF @row_count = 0
RETURN
/* Parent in 'Parent' cannot be deleted if children exist in 'Child' */
IF EXISTS (
SELECT 1
FROM Child c, deleted d
WHERE c.ParentID = d.ParentID
)
BEGIN
SELECT @error_number=30005,
@error_message='Children exist in "Child". Cannot delete parent "Parent".'
GOTO error
END
RETURN
/* Error Handling */
error:
RAISERROR @error_number @error_message
ROLLBACK TRANSACTION
END
go
/* Insert Trigger 'T_I_Child' for Table 'Child' */
CREATE TRIGGER T_I_Child ON Child FOR INSERT AS
BEGIN
DECLARE
@row_count INT,
@null_row_count INT,
@error_number INT,
@error_message VARCHAR(255)
SELECT @row_count = @@rowcount
IF @row_count = 0
RETURN
/* When inserting a row in child 'Child' ,the Foreign Key must be Null or
exist in Parent 'Parent' */
IF UPDATE(ParentID)
BEGIN
SELECT @null_row_count =
(
SELECT COUNT(*)
FROM inserted
WHERE ParentID is null
)
IF @null_row_count != @row_count
IF (
SELECT COUNT(*)
FROM Parent p, inserted i
WHERE p.ParentID = i.ParentID
)
!= @row_count - @null_row_count
BEGIN
SELECT @error_number=30001,
@error_message='Cannot insert child in "Child" as its Foreign Key does
not exist in "Parent".' GOTO error
END
END
/* When inserting a row in child 'Child' ,the Foreign Key must be Null or
exist in Parent 'Item' */
IF UPDATE(ItemID)
BEGIN
SELECT @null_row_count =
(
SELECT COUNT(*)
FROM inserted
WHERE ItemID is null
)
IF @null_row_count != @row_count
IF (
SELECT COUNT(*)
FROM Item p, inserted i
WHERE p.ItemID = i.ItemID
)
!= @row_count - @null_row_count
BEGIN
SELECT @error_number=30001,
@error_message='Cannot insert child in "Child" as its Foreign Key does
not exist in "Item".'
GOTO error
END
END
RETURN
/* Error Handling */
error:
RAISERROR @error_number @error_message
ROLLBACK TRANSACTION
END
go
/* Update Trigger 'T_U_Item' for Table 'Item' */
CREATE TRIGGER T_U_Item ON Item FOR UPDATE AS
BEGIN
DECLARE
@row_count INT,
@null_row_count INT,
@error_number INT,
@error_message VARCHAR(255)
SELECT @row_count = @@rowcount
IF @row_count = 0
RETURN
/* The Primary Key of 'Item' cannot be modified if children exist in 'Child'
*/
IF UPDATE(ItemID)
BEGIN IF EXISTS (
SELECT 1
FROM Child c, inserted i, deleted d
WHERE c.ItemID = d.ItemID
AND (i.ItemID != d.ItemID)
)
BEGIN
SELECT @error_number=30004,
@error_message='Children exist in "Child". Cannot modify Primary Key in
"Item".'
GOTO error
END
END
RETURN
/* Error Handling */
error:
RAISERROR @error_number @error_message
ROLLBACK TRANSACTION
END
go
/* Delete Trigger 'T_D_Item' for Table 'Item' */
CREATE TRIGGER T_D_Item ON Item FOR DELETE AS
BEGIN
DECLARE
@row_count INT,
@error_number INT,
@error_message VARCHAR(255)
SELECT @row_count = @@rowcount
IF @row_count = 0
RETURN
/* Parent in 'Item' cannot be deleted if children exist in 'Child' */
IF EXISTS (
SELECT 1
FROM Child c, deleted d
WHERE c.ItemID = d.ItemID
)
BEGIN
SELECT @error_number=30005,
@error_message='Children exist in "Child". Cannot delete parent "Item".'
GOTO error
END
RETURN
/* Error Handling */
error:
RAISERROR @error_number @error_message
ROLLBACK TRANSACTION
END
go
Wie Sie sehen, ist dieses Script tatsächlich vollständig, neben den
Tabellen, Indices und Constraints hat xCase sogar die Update und Delete
Triggers erstellt, um sicherzustellen, dass nicht Primärschlüssel in der "Parent"
oder "Item" Tabelle verändert werden können, wenn gleichzeitig abhängige
Daten dazu vorhanden sind.
Applizieren von Strukturänderungen
xCase unterstützt einen grundsätzlich auch darin, seine Strukturänderungen
auf der Datenbank zu applizieren. Hierzu verwendet xCase den Ansatz, eine
neue Tabelle mit der angepassten Struktur zu erstellen, in diese Tabelle die
Daten aus der alten Tabelle einzufügen, die alte Tabelle zu löschen und
schliesslich die neu erstellte Tabelle in den ursprünglichen Namen
umzubenennen. Hier ein Beispiel:
CREATE TABLE TEMP_Item
(
ItemID INT NOT NULL,
ItemDescr CHAR(40) NULL,
ItemPrice NUMERIC(6,2) NULL,
ItemAvailability CHAR(1) NULL,
test CHAR(10) NULL
)
go
INSERT INTO TEMP_Item (ItemID,ItemDescr,ItemPrice,ItemAvailability)
SELECT ITEMID,ITEMDESCR,ITEMPRICE,ITEMAVAILABILITY
FROM Item
go
IF EXISTS (SELECT 1 FROM sysobjects WHERE name = 'Item' AND type = 'U')
DROP TABLE Item
go
EXECUTE sp_rename 'TEMP_Item', 'Item', 'OBJECT'
go
Um diese Schritte jedoch durchführen zu können, müssen zunächst alle
Referenzen zur alten Tabelle entfernt werden und anschliessend wieder
erstellt werden. Obwohl xCase das prinzipiell auch versucht zu tun, gelingt
es je nach Komplexität und Abfoge der Abhängigkeiten nicht immer auf Anhieb.
Das liegt u.a. auch daran, dass xCase die Tabellen nicht in einer logischen
Abhängigkeit abarbeitet, sondern stur alphabetisch, was natürlich füher oder
später zu Problemen führt.
Wird im SQL Server nicht mit dem 6.5 Kompatibilitätsmodus gearbeitet, ist
es manchmal viel einfacher bei simplen Strukturänderungen direkt die T-SQL
Scripts abzusetzen:
alter table adresse add einzug char(1)
um eine Spalte hinzuzufügen, bzw.
alter table bank drop column einzug
um eine Spalte zu entfernen. Bei Feldtypenänderungen, wie eine einfache
Stellenerweiterung, kann auch mit dem Alter Table Script operiert werden.
WICHTIG: Denken Sie daran, dass xCase die
Triggers nicht von sich aus aktualisiert. Sie müssen die Triggers explizit
neu generieren lassen, damit diese nach umfangreichen Datenstrukturänderungen
wieder funktionstüchtig sind.
Constraints, Rules und Triggers
Wir haben in obigem Script gesehen, wie eine Foreign Key Constraint
aussieht und wir haben auch gesehen, wie ein Update oder Delete Trigger
augebaut ist. Es lohnt sich hier näher darauf einzugehen, damit man in der
Praxis genau weiss, wann welches Konstrukt zu verwenden ist.
Constraints sind wenn immer möglich Triggern oder Rules vorzuziehen. Rules
sollten nach Möglichkeit gar nicht mehr verwendet werden. Sie sind nur wegen
Rückwärtskompatibilität vorhanden.
Constraints sind proaktiv, Triggers hingegen reaktiv. Das heisst, dass
Constraints versuchen, etwas sicherzustellen, bevor es in der Datenbank zu
einem unerwünschten Zustand kommt, die Triggers hingegen kommen erst dann zum
Zug, wenn ein womöglich unerwünschter Zustand bereits eingetreten ist, der
dann wieder zurückgestellt werden muss. Das ist natürlich viel aufwendiger,
als bei den Constraints, deshalb sollte wenn immer möglich mit Constraints
gearbeitet werden.
Constraints
Es gibt folgende Arten von Constraints:
Constraint |
Syntax Bsp |
Beschreibung |
PRIMARY KEY |
- CREATE TABLE Child
- (
ChildID INT NOT NULL,
ParentID INT NOT NULL,
ChildDesc CHAR(40) NULL,
ChildType CHAR(1) NULL,
ItemID INT NOT NULL,
Quantity NUMERIC(6,0) NULL,
CONSTRAINT PK_Child PRIMARY KEY NONCLUSTERED
(ChildID))
|
Stell auf Stufe Tabelle sicher, dass in
einer Tabelle nicht zweimal derselbe Primärschlüssel vergeben werden
kann |
FOREIGN KEY |
- ALTER TABLE Child
ADD CONSTRAINT Parent_Child FOREIGN KEY
( ParentID )
REFERENCES Parent
( ParentID )
|
Stellt referentiell sicher, dass nicht ein
nicht existierender Fremdschüssel (inkl. leer Einträge, nicht .null.) in
das entsprechende Feld eingetragen werden kann
|
UNIQUE |
- ALTER TABLE Parent
ADD CONSTRAINT my_unique UNIQUE
( ParentDescr )
|
Stellt sicher, dass es nicht mehrere gleiche
Einträge bzgl. dem entsprechenden Feld oder Ausdruck gibt |
DEFAULT |
- ALTER TABLE Child
ADD CONSTRAINT my_default DEFAULT '
Unknown' FOR CHILDDESC
|
Stellt auf Tabellenebene sicher, dass alle
neu angelegten Datensätze einen bestimmten Standardwert für bestimmte
Felder erhalten, sofern nichtz explizit ein Wert angegeben wird |
CHECK |
- ALTER TABLE Child
ADD CONSTRAINT my_check CHECK (CHILDTYPE='A' or CHILDTYPE='B')
|
Stellt sicher, dass nur bestimmte Werte für
ein bestimmtes Feld in Frage kommen. |
TIP: Um eine Constraint wieder zu entfernen,
können Sie im Query Analyzer folgendes eingeben:
alter table child drop constraint my_check
Triggers
Es gibt folgende Arten von Triggers
Trigger |
Syntax Bsp |
Beschreibung |
UPDATE |
- CREATE TRIGGER T_U_Parent ON Parent FOR UPDATE AS
BEGIN
DECLARE
@row_count INT,
@null_row_count INT,
@error_number INT,
@error_message VARCHAR(255)
SELECT @row_count = @@rowcount
IF @row_count = 0
RETURN
/* The Primary Key of 'Parent' cannot be modified if children exist
in 'Child' */
IF UPDATE(ParentID)
BEGIN
IF EXISTS (
SELECT 1
FROM Child c, inserted i, deleted d WHERE c.ParentID = d.ParentID
)
BEGIN
SELECT @error_number=30004, @error_message='Children exist in "Child".
- Cannot modify Primary Key in "Parent".' GOTO error
- END
END
/* Error Handling */
error:
RAISERROR @error_number @error_message
ROLLBACK TRANSACTION
END
|
Wird dann ausgeführt, nachdem ein Update
stattgefunden hat.
HINWEIS: Sehr häufig verwendet man die Anweisung
UPDATE(FeldName) um im Trigger Code festzustellen, ob ein Feldinhalt
verändert wurde.
Detaillierter UPDATE Ablauf:
1. UPDATE Statement wird ausgeführt und ein UPDATE Trigger existiert
2. Das UPDATE Statement wird geloggt als einzelne INSERT und DELETE
statements.
3. Der Trigger schiesst los und die Trigger Statements werden
abgearbeitet. Hierbei kann der alte Datensatz in der virtuellen Tabelle
Namens "deleted", der neu hinugefügte unter dem Namen "inserted"
angesprochen werden. |
INSERT |
- CREATE TRIGGER T_I_Parent ON Parent FOR INSERT
- …
|
Wird dann ausgeführt, nachdem ein Insert
stattgefunden hat.
Detaillierter INSERT Ablauf:
1. INSERT Statement wird ausgeführt bei einer Tabelle mit einem INSERT
Trigger
2. Das INSERT Statement wird geloggt
3. Der INSERT Trigger schiesst los und die Trigger Statements werden
abgearbeitet. Hierbei kann der neue Datensatz sowohl in der virtuellen
Tabelle Namens "inserted", als auch in der Tabelle selbst angesprochen
werden. |
DELETE |
- CREATE TRIGGER T_D_Parent ON Parent FOR
DELETE AS
BEGIN
|
Wird dann ausgeführt, nachdem ein Delete
stattgefunden hat.
Detaillierter DELETE Ablauf:
1. DELETE Statement wird ausgeführt bei einer Tabelle mit einem DELETE
Trigger
2. Das DELETE Statement wird geloggt
3. Der DELETE Trigger schiesst los und die Trigger Statements werden
abgearbeitet. Hierbei kann der gelöschte Datensatz in der virtuellen
Tabelle Namens "deleted" angesprochen werden. |
HINWEIS: Triggers sind natürlich auch
ausgezeichnet dafür geeignet, Audit Trails zu erstellen. Ich werde in der
Session zeigen, wie Audit Trails implementiert werden können und worauf dabei
zu achten ist.
SQL Server Handling
SQL Server 7.0 und SQL Server 2000 (8.0) sind im Handling grundsätzlich
identisch. SQL Server 2000 hat einige Neuerungen, welche im SQL Server Books
Online nachgelesen werden können. Auf eine Auflistung wird an dieser Stelle
deshalb verzichtet.
Der Query Analyzer
Der Query Analyzer ist der ständige Begleiter eines jeden Client/Server
Entwicklers. Genauso wie der Enterprise Manager und der Profiler gehört er
zum täglich verwendeten Hilfsmittel.
Bevor man mit dem Query Analyzer arbeiten kann, muss man sich mit einem
SQL Server verbinden, das erfolgt über den "Connect" Dialog. Dieser kann
jederzeit aus dem File Menu heraus aufgerufen werden:
Anschliessend erhält man folgenden Dialog, um seine Arbeiten zu erledigen:
[ 1 ] [
2 ] [ 3 ] [ 4 ] [
5 ] [ 6 ] [
7 ] [ 8 ] [
9 ]
|