Re: [GENERAL] Another perplexity with PG rules

2006-02-26 Thread Ken Winter
Tom ~

Thanks ever so much for - again - helping me get unstuck.  See comments and
results inserted below.

~ Ken

> -Original Message-
> From: Tom Lane [mailto:[EMAIL PROTECTED]
> Sent: Sunday, February 26, 2006 1:47 PM
> To: [EMAIL PROTECTED]
> Cc: PostgreSQL pg-general List
> Subject: Re: [GENERAL] Another perplexity with PG rules
> 
> "Ken Winter" <[EMAIL PROTECTED]> writes:
> > After trying about a million things, I'm wondering about the meaning of
> > "OLD." as the actions in a rule are successively executed.  What I have
> done
> > assumes that:
> > ...
> > (b) The "OLD." values that appear in the second (INSERT) action in the
> rule
> > are not changed by the execution of the first (UPDATE) rule.
> 
> I believe this is mistaken.  OLD is effectively a macro for "the
> existing row(s) satisfying the rule's WHERE clause".  You've got two
> problems here --- one is that the UPDATE may have changed the data in
> those rows, and the other is that the UPDATE may cause them to not
> satisfy the WHERE clause anymore.

I was afraid of this.  Your conclusions do seem to fit my results.
> 
> > (c) Whatever the truth of the above assumptions, the second (INSERT)
> action
> > in the 'on_update_2_preserve_h' rule should insert SOMEthing.
> 
> See above.  If no rows remain satisfying WHERE, nothing will happen.

Yep, that's what was happening.
> 
> > How to make this whole thing do what is required?
> 
> I'd suggest seeing if you can't do the INSERT first then the UPDATE.
> This may require rethinking which of the two resulting rows is the
> "historical" one and which the "updated" one, but it could probably
> be made to work.

Yes, I had already had it working with such a scheme.  It expired the
existing record, and then inserted a new record with the updated values.
However this scheme seemed to be causing troubles with other triggers on the
base tables.  That's why I was trying to recast it into a scheme that
updated the existing record and then inserted a new record containing the
"old" data.
> 
> Also, you might think about keeping the historical info in a separate
> table (possibly it could be an inheritance child of the master table).
> This would make it easier to distinguish the historical and current info
> when you need to.

I've been striving mightily to avoid taking this path, because it threatens
to hopelessly complicate my foreign keys.
> 
> Lastly, I'd advise using triggers not rules wherever you possibly can.
> In particular, generation of the historical-log records would be far
> more reliable if implemented as an AFTER UPDATE trigger on the base
> table.
>
This appears to be the WINNER!  I eliminated the INSERT action from my
UPDATE rule:

CREATE OR REPLACE RULE on_update_2_preserve_h AS
ON UPDATE TO person
...
DO
(
/* Update the current H record and make it effective
as of either now (if no effective date
was provided) or whenever the update query specifies.*/
UPDATE person_h
SET person_id = NEW.person_id,
first_name = NEW.first_name,
middle_names = NEW.middle_names,
last_name_prefix = NEW.last_name_prefix,
last_name = NEW.last_name,
name_suffix = NEW.name_suffix,
preferred_full_name = NEW.preferred_full_name,
preferred_business_name = NEW.preferred_business_name,
user_name = NEW.user_name,
_action = NEW._action,
effective_date_and_time =
CASE
WHEN NEW.effective_date_and_time =
OLD.effective_date_and_time
 THEN CURRENT_TIMESTAMP -- Query assigned no value
ELSE NEW.effective_date_and_time -- Query assigned value
END
WHERE person_id = OLD.person_id
AND effective_date_and_time = OLD.effective_date_and_time
;
/* Copy the old values to a new record.
Expire it either now (if no effective date
was provided) or whenever the update query specifies.*/
INSERT INTO person_h (
person_id,
first_name,
middle_names,
last_name_prefix,
last_name,
name_suffix,
preferred_full_name,
preferred_business_name,
user_name,
_action,
effective_date_and_time,
expiration_date_and_time)
VALUES (
OLD.person_id,
OLD.first_name,
OLD.middle_names,
OLD.last_name_prefix,
OLD.last_name,
OLD.name_suffix,
OLD.preferred_full_name,
OLD.preferre

Re: [GENERAL] Another perplexity with PG rules

2006-02-26 Thread Tom Lane
"Ken Winter" <[EMAIL PROTECTED]> writes:
> After trying about a million things, I'm wondering about the meaning of
> "OLD." as the actions in a rule are successively executed.  What I have done
> assumes that: 
> ...
> (b) The "OLD." values that appear in the second (INSERT) action in the rule
> are not changed by the execution of the first (UPDATE) rule.

I believe this is mistaken.  OLD is effectively a macro for "the
existing row(s) satisfying the rule's WHERE clause".  You've got two
problems here --- one is that the UPDATE may have changed the data in
those rows, and the other is that the UPDATE may cause them to not
satisfy the WHERE clause anymore.

> (c) Whatever the truth of the above assumptions, the second (INSERT) action
> in the 'on_update_2_preserve_h' rule should insert SOMEthing.

See above.  If no rows remain satisfying WHERE, nothing will happen.

> How to make this whole thing do what is required?

I'd suggest seeing if you can't do the INSERT first then the UPDATE.
This may require rethinking which of the two resulting rows is the
"historical" one and which the "updated" one, but it could probably
be made to work.

Also, you might think about keeping the historical info in a separate
table (possibly it could be an inheritance child of the master table).
This would make it easier to distinguish the historical and current info
when you need to.

Lastly, I'd advise using triggers not rules wherever you possibly can.
In particular, generation of the historical-log records would be far
more reliable if implemented as an AFTER UPDATE trigger on the base
table.

(Over the years I've gotten less and less satisfied with Postgres' rules
feature --- it just seems way too hard to make it do what people want
reliably.  I'm afraid there's not much we can do to fix it without
creating an enormous compatibility problem unfortunately :-(.  But by
and large, triggers are a lot easier for people to wrap their brains
around, once they get over the notational hurdle of having to write a
trigger function.  I'd like to see us allow triggers on views, and then
maybe rules could fade into the sunset for any but the most abstruse
applications.)

regards, tom lane

---(end of broadcast)---
TIP 6: explain analyze is your friend


[GENERAL] Another perplexity with PG rules

2006-02-24 Thread Ken Winter
I'm stumped on the following problem.  

Everything between the "---" rows should be executable.

Please advise.

~ TIA
~ Ken

---

-- Here's a table:

CREATE TABLE public.person_h
(
person_id bigint DEFAULT nextval('pop_seq'::text),
effective_date_and_time timestamptz DEFAULT ('now'::text)::timestamp(6) with
time zone,
expiration_date_and_time timestamptz DEFAULT 'infinity'::timestamp with time
zone,
first_name varchar(255),
middle_names varchar(255),
last_name_prefix varchar(255),
last_name varchar(255),
name_suffix varchar(255),
preferred_full_name varchar(255),
preferred_business_name varchar(255),
user_name varchar(255),
_action varchar(32) DEFAULT 'preserve'::character varying,
CONSTRAINT pk_person_h_identifier_2 PRIMARY KEY (person_id,
effective_date_and_time)
);
-- Indexes
CREATE UNIQUE INDEX personal_data_px ON person_h USING btree (person_id,
effective_date_and_time);

-- Here's a view of that table plus a few ALTERs on the view:

CREATE OR REPLACE VIEW person AS
SELECT h.person_id AS person_id,
h.effective_date_and_time AS effective_date_and_time,
h.expiration_date_and_time AS expiration_date_and_time,
h.first_name AS first_name,
h.middle_names AS middle_names,
h.last_name_prefix AS last_name_prefix,
h.last_name AS last_name,
h.name_suffix AS name_suffix,
h.preferred_full_name AS preferred_full_name,
h.preferred_business_name AS preferred_business_name,
h.user_name AS user_name,
h._action AS _action
FROM person_h AS h
WHERE h.effective_date_and_time <= CURRENT_TIMESTAMP
AND h.expiration_date_and_time >= CURRENT_TIMESTAMP

ALTER TABLE person
ALTER COLUMN person_id
SET DEFAULT nextval('pop_seq'::text)
;
ALTER TABLE person
ALTER COLUMN effective_date_and_time
SET DEFAULT ('now'::text)::timestamp(6) with time zone
;
ALTER TABLE person
ALTER COLUMN expiration_date_and_time
SET DEFAULT 'infinity'::timestamp with time zone
;
ALTER TABLE person
ALTER COLUMN _action
SET DEFAULT 'preserve'::character varying

-- Here are a couple of rules on that view:

/*** Rule on_insert inserts the object's first history record into person_h.
***/
CREATE OR REPLACE RULE on_insert AS
ON INSERT TO person
DO INSTEAD (
/* Insert the row into the H table.
Effective and expiration dates take the defaults,
unless query overrides them. */
INSERT INTO person_h
( person_id,
effective_date_and_time,
first_name,
middle_names,
last_name_prefix,
last_name,
name_suffix,
preferred_full_name,
preferred_business_name,
user_name,
_action )
VALUES ( nextval('pop_seq'::text),
NEW.effective_date_and_time,
NEW.first_name,
NEW.middle_names,
NEW.last_name_prefix,
NEW.last_name,
NEW.name_suffix,
NEW.preferred_full_name,
NEW.preferred_business_name,
NEW.user_name,
NEW._action )
)
;

/*** Rule on_update_1_nothing meets the PostgreSQL requirement for one
unconditional UPDATE rule. ***/
CREATE OR REPLACE RULE on_update_1_nothing AS
ON UPDATE TO person
DO INSTEAD NOTHING
;
 
/*** Rule on_update_2_preserve_h inserts a new record with the old data into
history table person_h,
expires this record effective either now or at the effective time given in
the query,
and updates the current record as of the same time. ***/
CREATE OR REPLACE RULE on_update_2_preserve_h AS
ON UPDATE TO person
WHERE (
(OLD.person_id <> NEW.person_id
OR (OLD.person_id IS NULL AND NEW.person_id IS NOT NULL)
OR (OLD.person_id IS NOT NULL AND NEW.person_id IS NULL ))
OR (OLD.effective_date_and_time <> NEW.effective_date_and_time
OR (OLD.effective_date_and_time IS NULL AND
NEW.effective_date_and_time IS NOT NULL)
OR (OLD.effective_date_and_time IS NOT NULL AND
NEW.effective_date_and_time IS NULL ))
OR (OLD.first_name <> NEW.first_name
OR (OLD.first_name IS NULL AND NEW.first_name IS NOT NULL)
OR (OLD.first_name IS NOT NULL AND NEW.first_name IS NULL ))
OR (OLD.middle_names <> NEW.middle_names
OR (OLD.middle_names IS NULL AND NEW.middle_names IS NOT NULL)
OR (OLD.middle_names IS NOT NULL AND NEW.middle_names IS NULL ))
OR (OLD.last_name_prefix <> NEW.last_name_prefix
OR (OLD.last_name_prefix IS NULL AND NEW.last_name_prefix IS NOT
NULL)
OR (OLD.last_name_prefix IS NOT NULL AND NEW.last_name_prefix IS
NULL ))
OR (OLD.last_name <> NEW.last_name
OR (OLD.last_name IS NULL AND NEW.last_name IS NOT NULL)
OR (OLD.last_name IS NOT NULL AND NEW.last_name IS NULL ))
OR (OLD.name_suffix <> NEW.name_suffix
OR (OL