How do you add complex constraints on a table? [closed] - sql

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 1 year ago.
Improve this question
In SQL Server (or it could be other SQL language I guess), how do you make sure that a table always verifies some complex constraint.
For instance, imagine a table has only column A and B
The constraint I want to enforce, is that all lines that contain the same value for column A (e.g. 'a'), also have the same value for column B (e.g. 'b'). For instance, this isn't allowed:
A
B
C
a1
b
c1
a1
c
c2
a2
d
c3
But this is allowed:
A
B
C
a1
b
c1
a1
b
c2
a2
d
c3
This is an example of constraint, but there could be more complex (i.e. depends on more columns etc). Here I think that this constraint could be expressed in terms of primary key etc, but let's assume it can't.
What is the way to enforce this?
What I can think about is, each time I want to do an insert, I instead go through a stored procedure.
The stored procedure would do an atomic transaction and check if the table would verify the constraint if updated, and "reject" the update if the constraint would be violated, but this seems quite complex.

Rough definitions, for this answer:
Simple constraint. A constraint that can be, without schema changes beyond the constraint definition, enforced by the straight forward use of one of SQL Server constraints: primary key, unique, foreign key, not null and check against a simple scalar expression. (I would include a unique filtered index as a simple constraint.)
Complex constraint. A constraint that can not be so enforced.
Just as SQL server provides different constraints to handle different situations there are different techniques for enforcing more complex constraints. Which technique is appropriate depends on what one is trying to accomplish and the context one is in.
Broadly, there are two approaches to enforcing complex constraints, either add code to that prevents bad data or change the schema in such a way that SQL Server can enforce the constraint with a simple constraint.
Add code to enforce the constraint.
A common refrain is to use a trigger to enforce a constraint. So one writes an after trigger that checks whether the constraint has been violated by the DML that fired the trigger. If it has been violated the trigger throws an error, rolling back the transaction. Another approach is to take insert, update, delete rights away from all applications and force them through stored procedures that will error on attempts to violate the constraint. Using a UDF in a check constraint is new to me. The mechanism is similar to the after trigger, a DML statement made a change to the data. After that change, but before the DML statement is done, code is ran in the UDF and the result checked to see if the constraint is violated or not.
The big challenge with adding code that checks the constraint is correctly enforcing the constraint in the face of concurrency. Because transactions are isolated, the code verifying that the constraint is met may not see a conflicting change made by another session. See an example "Concurrency can cause trouble" at the end.
Now, some business rules are too complex to enforce with changes to the schema and code is required. I generally don't consider such business rules to be constraints that the database must enforce and place the code to enforce the business rules in either stored procedures or application code, with the other business rules. (Saying that these business rules are not database constraints does not getting correctness in the face of concurrency any easier.)
Change the schema so SQL Server can enforce the constraint.
A strength reduction from a complex constraint to a simple constraint. There is no one technique in this approach. Sometimes the application of a schema change that will allow SQL Server to enforce a constraint is easy to spot. And sometimes one may need to play with SQL Server features with outside the box thinking.Some schema changes that may be helpful:
Increase the normalization. Many reasonable constraints cannot be expressed against denomalized data.
Adding a persisted computed column to a table and using that column in a constraint.
Indexed views can be amazing for constraint enforcement. (The restrictions placed on what views can be indexed is super frustrating.)
Example using another table
(This may count as a normalization.) Lets consider a table, like in your question, but adding one additional column, a primary key that allows update and delete of individual rows.
CREATE TABLE dbo.T (PK INT PRIMARY KEY IDENTITY(1, 1)
, A CHAR(1) NOT NULL
, B INT NOT NULL
/* other columns as needed */);
And the rule is that the following query should never return a row:
SELECT 'Uh oh!' AS "Uh oh!"
FROM dbo.T
GROUP BY A
HAVING COUNT(DISTINCT B) > 1
To enforce this one can add the following table and constraint:
CREATE TABLE dbo.TConstraint (A CHAR(1) NOT NULL PRIMARY KEY /* Only one B is allowed for any A */
, B INT NOT NULL
, UNIQUE (A, B) /* provide a target for a foreign key from dbo.T */);
ALTER TABLE dbo.T ADD FOREIGN KEY (A, B)
REFERENCES dbo.TConstraint (A, B);
Now the application logic or stored procedures that write to dbo.T will have to change to write to dbo.EnforceComplexConstraintOnT first. Updates to dbo.T (B) are going to be messy because one will need to change the values in dbo.TCnstraint before dbo.T but that can't be done with the old values in dbo.T. So an update becomes:
Delete row from dbo.T
Update dbo.TConstraint
Insert "changed" row into dbo.T.
That update logic is incompatible with an IDENTITY column and incompatible with any foreign key referencing dbo.T. So the contexts where this would work are limited.
Note also that this solution includes adding additional code for insert, update, and possibly delete. That extra code is not enforcing the constraint, but manipulating the data so that into a form such that SQL Server can enforce the constraint.
Example using indexed views
Indexed views come with caveats. See the documentation: https://learn.microsoft.com/en-us/sql/relational-databases/views/create-indexed-views?view=sql-server-ver15
Instead of the new table and constraint:
/* SET OPTIONS required by indexed views*/
SET ANSI_NULLS, ANSI_PADDING, ANSI_WARNINGS
, CONCAT_NULL_YIELDS_NULL, QUOTED_IDENTIFIER ON;
SET NUMERIC_ROUNDABORT OFF;
GO
/* view shows all the distiinct A, B in dbo.T */
CREATE VIEW dbo.TConstraint WITH SCHEMABINDING AS
SELECT A
, B
, COUNT_BIG (*) AS MustHaveForGroupingInAnIndexedView
FROM dbo.T
GROUP BY A, B;
GO
CREATE UNIQUE CLUSTERED INDEX A_B ON dbo.TConstraint (A, B);
CREATE UNIQUE NONCLUSTERED INDEX A ON dbo.TConstraint (A);
INSERT INTO dbo.T (A, B)
VALUES ('a', 100), ('a', 200)
Msg 2601, Level 14, State 1, Line 27
Cannot insert duplicate key row in object 'dbo.TConstraint' with unique index 'A'. The duplicate key value is (a).
Pros: 1) No extra code to get the data into a form SQL Server can constrain. 2) Updates to dbo.T (B) can be done without doing the delete dbo.T update dbo.TConstraint insert dbo.T dance, and all the restrictions that creates. Cons 1) The view has to have schemabinding. 2) Deploying schema changes to dbo.T will involve dropping the indexes on the view, dropping the view, altering dbo.T, create the view, create the indexes on the view. Some deployment tools may struggle with the required steps. Large tables with large indexed views will have a longer deployment for changes to dbo.T.
Concurrency can cause trouble.
#Dai's answer will work in at least some cases, but fails in the following case.
Setup:
Create dbo.TableX, dbo.GetCountInvalidGroupsInTableX(), and CK_Validity as detailed in Dai's answer.
Enable snapshot isolation for the database. ALTER DATABASE CURRENT SET ALLOW_SNAPSHOT_ISOLATION ON
Connect to the database with two separate sessions.
In session 1:
/* Read committed was the default in my connection */
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN TRANSACTION;
INSERT INTO TableX VALUES(10, 100);
In session 2:
SET TRANSACTION ISOLATION LEVEL SNAPSHOT
INSERT INTO TableX VALUES(10, 200);
/* (1 row affected) This insert did not block
on the open transaction */
In session 1:
COMMIT;
SELECT * FROM TableX;
/*
A B
10 100
10 200
*/
To add code to enforce a constraint one must know what isolation level may be used and ensure that the DML will block when needed so that when the code checking that the constraint is still satisfied can see everything it needs to.
Summary
Just as SQL Server has different constraint types for different situations there are different techniques for enforcing constraints when using what SQL provides against the current data model is not enough. Prefer schema changes that allows SQL Server to enforce the constraints. SQL Server is far more likely to get concurrency correct that most people writing Transact-SQL.

SQL Server supports UDFs in CHECK constraints.
While CHECK constraints are ostensibly a row-level-only constraint, a CHECK constraint can still use a UDF which checks the entire table, and I believe that then suits your purposes.
Note that CHECK constraints are evaluated for every row in a table (though only on-demand) and SQL Server isn't smart enough to detect redundant constraint evaluations. So be careful - and be sure to monitor and profile your database's performance.
(The inner-query in the function below returns the A and COUNT(DISTINCT x.B) values so you can easily copy+paste the inner-query into a new SSMS tab to see the invalid data. Whereas the function's actual execution-plan will optimize-away those columns because they aren't used by the outer-query, so there's no harm in having them in the inner-query).
CREATE FUNCTION dbo.GetCountInvalidGroupsInTableX()
RETURNS bit
AS
BEGIN
DECLARE #countInvalidGroups int = (
SELECT
COUNT(*)
FROM
(
SELECT
x.A,
COUNT(DISTINCT x.B) AS CountDistinctXB
FROM
dbo.TableX AS x
GROUP BY
x.A
HAVING
COUNT(DISTINCT x.B) >= 2
);
RETURN #countInvalidGroups;
END
Used like so:
CREATE TABLE TableX (
A int NOT NULL,
B int NOT NULL,
CONSTRAINT PK_TableX PRIMARY KEY ( etc )
);
CREATE FUNCTION dbo.GetCountInvalidGroupsInTableX() RETURNS bit
AS
/* ... */
END;
ALTER TABLE TableX
ADD CHECK CONSTRAINT CK_Validity CHECK ( dbo.GetCountInvalidGroupsInTableX() = 0 );

Related

SQL Foreign Key without constraints the same as ON DELETE RESTRICT?

I found the following two SQL schemas in an old exam. The exam does not seem to stick with one specific SQL syntax, but I hope this shouldn't matter for this question: What is the difference between ON DELETE RESTRICT and having just a Foreign Key without any such trigger clause?
-- Schema B --
CREATE TABLE tab1 (
tab1_key INT
NOT NULL UNIQUE ) ;
CREATE TABLE tab2 (
tab2_ref INT NOT NULL,
FOREIGN KEY ( tab2_ref )
REFERENCES tab1 ( tab1_key ) ) ;
-- Schema C --
CREATE TABLE tab1 (
tab1_key INT
NOT NULL UNIQUE ) ;
CREATE TABLE tab2 (
tab2_ref INT NOT NULL,
FOREIGN KEY ( tab2_ref )
REFERENCES tab1 ( tab1_key )
ON DELETE RESTRICT
ON UPDATE NO ACTION ) ;
I would assume that the only difference between those two schemas is the last line in Schema C, but I cannot find any documentation on using Foreign Key without any additional constraint. So is this assumption correct?
I attempted to try those in pgAdmin, but that program keeps failing (and seems to be known for its bugs) so I figured asking here is less effort than debugging that tool.
Also, please note that while these Schemas are from an old exam, this question is not the same as the exam question. Otherwise I would look at the solutions.
This is not homework.
From Using Foreign Key constraints:
RESTRICT: Rejects the delete or update operation for the parent table. Specifying RESTRICT (or NO ACTION) is the same as omitting the ON DELETE or ON UPDATE clause.
NO ACTION: A keyword from standard SQL. In MySQL, equivalent to RESTRICT... Some database systems have deferred checks, and NO ACTION is a deferred check.
(My emphasis)
So in fact there's no real difference between the two, unless you're using a database system that supports deferred constraints.
In Postgres and every other DBMS I've used, the default ON DELETE action is RESTRICT. However this can differ by DBMS and version, e.g. RESTRICT was not supported by Microsoft SQL Server 2012 and earlier. It can also differ by DBMS configuration, e.g. MySQL has a setting foreign_key_checks that can disable foreign key enforcement actions. So you are wise to verify.
It would be easy enough to create two tables in your DBMS and test its actual behavior.

SQL constraint to check whether value doesn't exist in another table

In my PostgreSQL 9.4 database, I have a table fields with a column name with unique values.
I'm creating a new table fields_new with a similar structure (not important here) and a column name as well. I need a way to constraint name values to be inserted to the fields_new not to be present in fields.name.
For example, if fields.name contains the values 'color' and 'length', I need to prevent fields_new.name from containing 'color' or 'length' values. So, in other words I need to provide that the name columns in both tables do not have any duplicate values between them. And the constraint should go both ways.
Only enforce constraint for new entries in fields_new
CHECK constraints are supposed to be immutable, which generally rules out any kind of reference to other tables, which are not immutable by nature.
To allow some leeway (especially with temporal functions) STABLE functions are tolerated. Obviously, this cannot be completely reliable in a database with concurrent write access. If rows in the referenced table change, they may be in violation of the constraint.
Declare the invalid nature of your constraint by making it NOT VALID (Postgres 9.1+). This way Postgres also won't try to enforce it during a restore (which might be bound to fail). Details here:
Disable all constraints and table checks while restoring a dump
The constraint is only enforced for new rows.
CREATE OR REPLACE FUNCTION f_fields_name_free(_name text)
RETURNS bool AS
$func$
SELECT NOT EXISTS (SELECT 1 FROM fields WHERE name = $1);
$func$ LANGUAGE sql STABLE;
ALTER TABLE fields_new ADD CONSTRAINT fields_new_name_not_in_fields
CHECK (f_fields_name_free(name)) NOT VALID;
Plus, of course, a UNIQUE or PRIMARY KEY constraint on fields_new(name) as well as on fields(name).
Related:
CONSTRAINT to check values from a remotely related table (via join etc.)
Function to update a status flag for validity of other column?
Trigger vs. check constraint
Enforce both ways
You could go one step further and mirror the above CHECK constraint on the 2nd table. Still no guarantees against nasty race conditions when two transactions write to both tables at the same time.
Or you could maintain a "materialized view" manually with triggers: a union of both name columns. Add a UNIQUE constraint there. Not as rock solid as the same constraint on a single table: there might be race conditions for writes to both tables at the same time. But the worst that can happen is a deadlock forcing transactions to be rolled back. No permanent violation can creep in if all write operations are cascaded to the "materialized view".
Similar to the "dark side" in this related answer:
Can PostgreSQL have a uniqueness constraint on array elements?
Just that you need triggers for INSERT / UPDATE / DELETE on both tables.
I had a similar problem where I wanted to maintain a list of items per-company, along with a global list for all companies. If the company number is 0, it is to be treated as global and a new item cannot be inserted for ANY company using that name. The following script (based on the above solution) seems to work:
drop table if exists blech;
CREATE TABLE blech (
company int,
name_key text,
unique (company, name_key)
);
create or replace function f_foobar(new_company int, new_name_key text) returns bool as
$func$
select not exists (
select 1 from blech b
where $1 <> 0
and b.company = 0
and b.name_key = $2);
$func$ language sql stable;
alter table blech add constraint global_unique_name_key
check (f_foobar(company, name_key)) not valid;
insert into blech values(0,'GLOB1');
insert into blech values(0,'GLOB2');
-- should succeed:
insert into blech values(1,'LOCAL1');
insert into blech values(2,'LOCAL1');
-- should fail:
insert into blech values(1,'GLOB1');
-- should fail:
insert into blech values(0,'GLOB1');

Is there a generic way to replace referential constraints with triggers?

Porting application from MSSQL+Oracle to Sybase, and there's an issue with 'on delete cascade' - Sybase doesn't have the option.
Sybase has a link with a trigger to implement cascading delete: http://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.help.ase_15.0.sqlug/html/sqlug/sqlug815.htm
but there is a problem with that solution when put into context of using it as 'on delete cascade'.
The problem, is triggers get executed after any referential constraints are checked.
The issue is illustrated here:
--drop table A
--drop table B
create table A (c int primary key)
create table B (c int primary key)
alter table A
add constraint Ac FOREIGN KEY (c) REFERENCES B(c)
create trigger delBA on B for delete
as delete A from A, deleted where A.c = deleted.c
insert into B values (1)
insert into A values (1)
delete B where c = 1
The 'delete' statement will fail, because of 'Ac' constraint. Had the trigger fired before the check for referential constraints (instead of after), it would have removed the value '1' from table 'A' and there would not be a problem.
For this reason, I'm thinking to implement the referential constraint by using a trigger. So I have to create an Insert and Update trigger, I believe. Is there some template that I can use?
I want to make sure I'm not overlooking anything, at first look at the issue, I missed that update trigger should be written so that it can validate that after update the constraint is still valid. - That is the reason I'm looking for a template, so I won't miss anything similar.
Triggers are often sources of trouble. A common approach would be to set up your data access layer that will create a transaction, delete the 'children' (cascade part) then delete the parent.

Oracle Unique Constraint - Trigger to check value of property in new relation

Hi I'm having trouble getting my sql syntax correct. I want to create a unique constraint that looks at the newly added foreign key, looks at some properties of the newly related entity to decided if the relationship is allowed.
CREATE or replace TRIGGER "New_Trigger"
AFTER INSERT OR UPDATE ON "Table_1"
FOR EACH ROW
BEGIN
Select "Table_2"."number"
(CASE "Table_2"."number" > 0
THEN RAISE_APPLICATION_ERROR(-20000, 'this is not allowed');
END)
from "Table_1"
WHERE "Table_2"."ID" = :new.FK_Table_2_ID
END;
Edit: APC answer is wonderfully comprehensive, however leads me to think im doing it in the wrong way.
The situation is I have a table of people with different privilege levels, and I want to check these privilege levels, e.g. A user, 'Bob', has low level privileges and he tries to become head of department which requires requires high privileges so the system prevents this happening.
There is a follow-up question which poses a related scenario but with a different data model. Find it here.
So the rule you want to enforce is that TABLE_1 can only reference TABLE_2 if some column in TABLE_2 is zero or less. Hmmm.... Let's sort out the trigger logic and then we'll discuss the rule.
The trigger should look like this:
CREATE or replace TRIGGER "New_Trigger"
AFTER INSERT OR UPDATE ON "Table_1"
FOR EACH ROW
declare
n "Table_2"."number".type%;
BEGIN
Select "Table_2"."number"
into n
from "Table_2"
WHERE "Table_2"."ID" = :new.FK_Table_2_ID;
if n > 0
THEN RAISE_APPLICATION_ERROR(-20000, 'this is not allowed');
end if;
END;
Note that your error message should include some helpful information such as the value of the TABLE_1 primary key, for when you are inserting or updating multiple rows on the table.
What you are trying to do here is to enforce a type of constraint known as an ASSERTION. Assertions are specified in the ANSI standard but Oracle has not implemented them. Nor has any other RDBMS, come to that.
Assertions are problematic because they are symmetrical. That is, the rule also needs to be enforced on TABLE_2. At the moment you check the rule when a record is created in TABLE_1. Suppose at some later time a user updates TABLE_2.NUMBER so it is greater than zero: your rule is now broken, but you won't know that it is broken until somebody issues a completely unrelated UPDATE on TABLE_1, which will then fail. Yuck.
So, what to do?
If the rule is actually
TABLE_1 can only reference TABLE_2 if
TABLE_2.NUMBER is zero
then you can enforce it without triggers.
Add a UNIQUE constraint on TABLE_2 for (ID, NUMBER); you need an additional constraint because ID remains the primary key for TABLE_2.
Add a dummy column on TABLE_1 called TABLE_2_NUMBER. Default it to zero and have a check constraint to ensure it is always zero. (If you are on 11g you should consider using a virtual column for this.)
Change the foreign key on TABLE_1 so (FK_Table_2_ID, TABLE_2_NUMBER) references the unique constraint rather than TABLE_2's primary key.
Drop the "New_Trigger" trigger; you don't need it anymore as the foreign key will prevent anybody updating TABLE_2.NUMBER to a value other than zero.
But if the rule is really as I formulated it at the top i.e.
TABLE_1 can only reference TABLE_2 if
TABLE_2.NUMBER is not greater than zero (i.e. negative values are okay)
then you need another trigger, this time on TABLE_2, to enforce it the other side of the rule.
CREATE or replace TRIGGER "Assertion_Trigger"
BEFORE UPDATE of "number" ON "Table_2"
FOR EACH ROW
declare
x pls_integer;
BEGIN
if :new."number" > 0
then
begin
Select 1
into x
from "Table_1"
WHERE "Table_1"."FK_Table_2_ID" = :new.ID
and rownum = 1;
RAISE_APPLICATION_ERROR(-20001, :new.ID
||' has dependent records in Table_1');
exception
when no_data_found then
null; -- this is what we want
end;
END;
This trigger will not allow you to update TABLE_2.NUMBER to a value greater than zero if it is referenced by records in TABLE_2. It only fires if the UPDATE statement touches TABLE_2.NUMBER to minimise the performance impact of executing the lookup.
Don't use a trigger to create a unique constraint or a foreign key constraint. Oracle has declarative support for unique and foreign keys, e.g.:
Add a unique constraint on a column:
ALTER TABLE "Table_1" ADD (
CONSTRAINT table_1_uk UNIQUE (column_name)
);
Add a foreign key relationship:
ALTER TABLE "ChildTable" ADD (
CONSTRAINT my_fk FOREIGN KEY (parent_id)
REFERENCES "ParentTable" (id)
);
I'm not clear on exactly what you're trying to achieve with your trigger - it's a bit of a mess of SQL and PL/SQL munged together which will not work, and seems to refer to a column on "Table_2" which is not actually queried.
A good rule of thumb is, if your trigger is querying the same table that the trigger is on, it's probably wrong.
I'm not sure, but are you after some kind of conditional foreign key relationship? i.e. "only allow child rows where the parent satisfies condition x"? If so, the problem is in the data model and should be fixed there. If you provide more explanation of what you're trying to achieve we should be able to help you.

SQL Delete if the row is not affected by constraints

First note that I have seen this question:TSQL delete with an inner join
I have a large table and several foreign key relations, each of which have data of a given age. We need to remove data older than a given data on a regular basis to stop the DB from growing without bound.
I'm writing a query that will delete from each point on the star if you will by the given parameters (unfortunately these are configurable and different between the tables).
After this first deletion, I have a central table that I'm worried that I'm doing twice the work attempting to delete, as on delete the database checks the conditionals. I have a set of:
AND NOT EXISTS
(SELECT key
FROM table
WHERE table.key = centretable.key)
which TSQL is making into a right anti semi join and doing it nicely on the indexes. The problem is it creates a list of stuff to delete and then does the same checks again as it performs the delete.
I guess my question is whether there is a try delete by row, (I'm not going to do that in a cursor as I know how slow it would be), but you would think that such a keyword would exist, I haven't had any luck finding it though.
In terms of a single command that only checks the relationships once (rather than twice in your example - once for the NOT EXISTS, once for the DELETE), then I expect the answer is a big fat no, sorry.
(off the wall idea):
If this is a major problem, you could try some kind of reference-counting implementation, using triggers to update the counter - but in reality I expect this will be a lot more overhead to maintain than simply checking the keys like you are already.
You could also investigate NOCHECK during the delete (since you are checking it yourself); but you can only do this at the table level (so probably OK for admin scripts, but not for production code) - i.e.:
-- disable
alter table ChildTableName nocheck constraint ForeignKeyName
-- enable
alter table ChildTableName check constraint ForeignKeyName
A quick test shows that with it enabled it does an extra Clustered Index Scan on the foreign key; with it disabled, this is omitted.
Here's a full example; you can look at the query plan of the two DELETE operations... (ideally in isolation from the rest of the code):
create table parent (id int primary key)
create table child (id int primary key, pid int)
alter table child add constraint fk_parent foreign key (pid)
references parent (id)
insert parent values (1)
insert parent values (2)
insert child values (1,1)
insert child values (2,1)
-- ******************* THIS ONE CHECKS THE FOREIGN KEY
delete from parent
where not exists (select 1 from child where pid = parent.id)
-- reset
delete from child
delete from parent
insert parent values (1)
insert parent values (2)
insert child values (1,1)
insert child values (2,1)
-- re-run with check disabled
alter table child nocheck constraint fk_parent
-- ******************* THIS ONE DOESN'T CHECK THE FOREIGN KEY
delete from parent
where not exists (select 1 from child where pid = parent.id)
-- re-enable
alter table child check constraint fk_parent
Again - I stress this should only be run from things like admin scripts.
You could create an Indexed view of your select sentence:
SELECT key FROM table WHERE table.key = centretable.key
The indexed view is a physical copy of the data it would therefore be very fast to check.
You do have the overhead of updating the view, so you would need to test this against your usage pattern.
If you're reusing the same list of stuff to delete then you could consider inserting the keys to delete into a temp table and then using this in the second query.
SELECT Key, ...
INTO #ToDelete
FROM Table T
WHERE ...
Then something like this
...
LEFT OUTER JOIN #ToDelete D
ON T.Key=D.Key
WHERE D.Key IS NULL
DROP #ToDelete
If you specified the foreign key as a constraint when creating the table in the database you can tell the database what to do in case of a delete, by setting the delete rule. This rule specifies what happens if a user tries to delete a row with data that is involved in a foreign key relationship. The "No action" setting tells the user that the deletion is not allowed and the DELETE is rolled back. Implementing it like that would keep you from checking it yourself before deleting it, and thus could be seen as some kind of try.
Well, at least it works like that in MS SQL. http://msdn.microsoft.com/en-us/library/ms177288.aspx
I did find one article that discusses using an outer join in a delete:
http://www.bennadel.com/blog/939-Using-A-SQL-JOIN-In-A-SQL-DELETE-Statement-Thanks-Pinal-Dave-.htm
I hope this works for you!
The short answer to your question is no, there is no standard RDBMS keyword for deleting a master record when all foreign key references to it go away (and certainly none that would account for foreign keys in multiple tables).
Your most efficient option is a second query that is run on an as-needed basis to delete from "centre" based on a series of NOT EXISTS() clauses for each of the tables with foreign keys.
This is based on two statements I believe are both true for your situation:
You will delete more "related" records than "centre" (parent) records. Thus, any operation that attempts to adjust "centre" every time you delete from one of the other tables will result in an instantaneous update to "centre", but will require much wasted querying to delete a "centre" record only occasionally.
Given that there are multiple points on the star from "centre," any "wasted effort" checking for a foreign key in one of them is minimal compared to the whole. For instance, if there are four foreign keys to check before deleting from "centre", you can only save, at best, 25% of the time.