[SQL] Deferrable NOT NULL REFERENCES ... for two-way referential relationship

2008-03-25 Thread Craig Ringer
Hi all

I'm running into an issue with my schema where I'm trying to establish a
mandatory two-way relationship between two entities, and I'm hoping for
some external wisdom. Using "customer" and "address" by way of example:

CREATE TABLE customer (
 id INTEGER PRIMARY KEY,
 address_id INTEGER NOT NULL
 REFERENCES address(id) DEFERRABLE INITIALLY DEFERRED
)

CREATE TABLE address (
 id INTEGER PRIMARY KEY,
 customer_id INTEGER NOT NULL
 REFERENCES customer(id)
)

Every customer must have one or more addresses, ie address_id must be
set to a valid address by commit time. It does not matter which address
it's set to (though in this particular app there is in fact a preferred
address).

If it could be written as a CHECK constraint I'd be trying to enforce:

CONSTRAINT address_id_exists CHECK
  ( EXISTS (SELECT 1 FROM address WHERE address.id = address_id) )
  DEFERRABLE INITIALLY DEFERRED;

... on the customer table.

PostgreSQL supports DEFERRABLE INITIALLY DEFERRED for the foreign key
constraint, but unless I'm missing something it doesn't appear to have
any direct way to make the NOT NULL check deferrable.

There are constraint triggers:

http://www.postgresql.org/docs/8.3/static/sql-createconstraint.html

and I could use one to enforce the NOT NULL on the table as a whole (at
an acceptable cost in this case). However, I've seen some posts like
this one:

http://archives.postgresql.org/pgsql-hackers/2005-01/msg00882.php

that suggest that constraint triggers are or have been deprecated.
However, their removal is no longer on the TODO as far as I can tell.

Are constraint triggers a safe and reasonably future proof way to
implement this, given that I'm quite OK with being tied to postgresql?

Is there some better way that I'm missing?

Is what I'm trying to do crazy for some reason I haven't spotted?

--
Craig Ringer

-- 
Sent via pgsql-sql mailing list (pgsql-sql@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-sql


Re: [SQL] Deferrable NOT NULL REFERENCES ... for two-way referential relationship (SOLVED?)

2008-03-25 Thread Craig Ringer

Craig Ringer wrote:

Hi all

I'm running into an issue with my schema where I'm trying to establish a
mandatory two-way relationship between two entities, and I'm hoping for
some external wisdom. Using "customer" and "address" by way of example:

CREATE TABLE customer (
 id INTEGER PRIMARY KEY,
 address_id INTEGER NOT NULL
 REFERENCES address(id) DEFERRABLE INITIALLY DEFERRED
)

CREATE TABLE address (
 id INTEGER PRIMARY KEY,
 customer_id INTEGER NOT NULL
 REFERENCES customer(id)
)


OK, it looks like there are at least two ways to do it, and I'm hoping 
for some comments from the experts on what seems sanest/cleanest/most 
future proof and the best in a concurrent environment I also figure this 
post might be useful for the archives.




It looks like it's possible to avoid the use of a constraint trigger by
making address_id in the above NOT NULL and inserting a dummy value when
inserting the customer record. A DEFAULT(-1) clause on address_id will
have much the same effect of a deferred NOT NULL check when combined
with the deferred REFERENCES check.

An AFTER ... INSERT trigger on address then updates the associated
customer with the address_id, and an AFTER ... DELETE trigger ensures
that if an address is deleted another valid ID (if any exists, otherwise
null) is inserted into customer.address_id.

However, as above the schema will permit customer to reference an ID
that doesn't have a reference back to the same customer, so it'd have to
be something like:

CREATE TABLE customer (
 id SERIAL  PRIMARY KEY,
 address_id INTEGER NOT NULL DEFAULT(-1),
 name   TEXT
);

CREATE TABLE address (
 id SERIAL  PRIMARY KEY,
 CONSTRAINT address_pkey_not_negative CHECK(id >= 0),
 customer_id INTEGER NOT NULL
 REFERENCES customer(id) ON DELETE CASCADE,
 addr   TEXT,
 UNIQUE(id, customer_id)
);

ALTER TABLE customer ADD CONSTRAINT customer_address_fkey
 FOREIGN KEY (address_id, id)
 REFERENCES address(id, customer_id) DEFERRABLE INITIALLY DEFERRED;

CREATE OR REPLACE FUNCTION address_insert_trig_set_cust_address_id ()
RETURNS trigger AS $$
BEGIN
UPDATE customer
SET address_id = new.id
WHERE customer.id = new.customer_id
  AND customer.address_id = -1;
RETURN NULL;
END;
$$ LANGUAGE 'plpgsql' VOLATILE;

CREATE OR REPLACE FUNCTION address_delete_set_cust_address_id ()
RETURNS trigger AS $$
BEGIN
UPDATE customer
SET address_id =
(SELECT id
 FROM address
 WHERE address.customer_id = customer.id
 LIMIT 1)
WHERE customer.id = old.customer_id;
RETURN NULL;
END;
$$ LANGUAGE 'plpgsql' VOLATILE;

CREATE TRIGGER address_insert_cust_address_id AFTER INSERT ON address
FOR EACH ROW EXECUTE PROCEDURE address_insert_trig_set_cust_address_id();

CREATE TRIGGER address_delete_cust_address_id AFTER DELETE ON address
FOR EACH ROW EXECUTE PROCEDURE address_delete_set_cust_address_id();



An UPDATE trigger preventing changes to the id field of address is assumed.

As a result, the whole thing is transparent to users, though it does
mean that inserts/deletes to `address' result in a lock on the
associated customer record. I'm not 100% sure it's free from races or
locking issues, but at least with races in this case the worst outcome
should be an unexpected error on commit, right?


It works, though. The error from a delete removing the last record could 
be nicer, but that's easily fixed in the DELETE trigger.


Does this look like a better idea than just using a deferred constraint
trigger (on both customer and address, to catch customers inserted w/o
an address and to catch deleted addresses) to directly verify that at
commit time a customer has one or more addresses?




I guess a deferred constraint trigger for the same job, again assuming 
an UPDATE trigger is in place to prevent changes to address2.id and 
customer2.id, would look like:


CREATE TABLE customer2 (
   id SERIAL PRIMARY KEY,
   name TEXT
);

CREATE TABLE address2 (
   id SERIAL PRIMARY KEY,
   customer2_id INTEGER REFERENCES customer2(id) ON DELETE CASCADE,
   addr TEXT
);
CREATE INDEX address2_customer2id_idx ON address2(customer2_id);

CREATE OR REPLACE FUNCTION assert_customer_has_address(INTEGER)
RETURNS VOID AS $$
BEGIN
-- Make sure the customer record actually exists; if it does
-- not then consider the assertion to be passed.
IF EXISTS (SELECT 1 FROM customer2 WHERE id = $1) THEN
-- Customer exists. Verify that at least one address entry for
-- it exists.
PERFORM 1 FROM address2 WHERE customer2_id = $1 LIMIT 1;
IF NOT FOUND THEN
RAISE EXCEPTION 'Customer must have one or more related 
address entries';

END IF;
END IF;
END;
$$ LANGUAGE 'plpgsql';

CREATE OR REPLACE FUNCTION customer_inserted_addrcheck_tr()
RETURNS TRIGGER AS $$
BEGIN
PERFORM assert_customer_has_address(new.id);
RETURN NULL;
END;
$$ LANGUAGE 'plpgsql' VOLATILE;

CREATE OR REPLACE FUNCTION address_deleted_addrcheck_tr()
RETURNS TRIGGER A

Re: [SQL] Deferrable NOT NULL REFERENCES ... for two-way referential relationship

2008-03-25 Thread Tom Lane
Craig Ringer <[EMAIL PROTECTED]> writes:
> There are constraint triggers:
> http://www.postgresql.org/docs/8.3/static/sql-createconstraint.html
> and I could use one to enforce the NOT NULL on the table as a whole (at
> an acceptable cost in this case). However, I've seen some posts like
> this one:
> http://archives.postgresql.org/pgsql-hackers/2005-01/msg00882.php
> that suggest that constraint triggers are or have been deprecated.
> However, their removal is no longer on the TODO as far as I can tell.

Yeah, there is no longer any thought of removing them.

regards, tom lane

-- 
Sent via pgsql-sql mailing list (pgsql-sql@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-sql


[SQL] copy command and array of composite types?

2008-03-25 Thread Jon Norman
I'm using PostgreSQL 8.3.1.

I've created the following composite type:

CREATE TYPE expression_event_type AS (
event_id varchar(10),
expr_indices smallint[]
);

which is used with the following table definition:

CREATE TABLE boolean_expr_table(
expr_id smallint PRIMARY KEY,
expr_name varchar(100) NOT NULL,
expression varchar(256) NOT NULL,
event_indices expression_event_type[] NOT NULL,
true_cases smallint[] NOT NULL
);

Question: What is the correct way to load the event_indices column using the 
copy command and a external file?

I've tried: {(1,{1,2,3})} without success. 

Re: [SQL] copy command and array of composite types?

2008-03-25 Thread Guy Fraser


Try to export some data from the table using "copy from ..." to see  
what it looks like.


On 2008-Mar-25, at 12:19, Jon Norman wrote:


I'm using PostgreSQL 8.3.1.

I've created the following composite type:

CREATE TYPE expression_event_type AS (
event_id varchar(10),
expr_indices smallint[]
);

which is used with the following table definition:

CREATE TABLE boolean_expr_table(
expr_id smallint PRIMARY KEY,
expr_name varchar(100) NOT NULL,
expression varchar(256) NOT NULL,
event_indices expression_event_type[] NOT NULL,
true_cases smallint[] NOT NULL
);

Question: What is the correct way to load the event_indices column  
using the copy command and a external file?


I've tried: {(1,{1,2,3})} without success.