Обсуждение: Way to stop recursion?

Поиск
Список
Период
Сортировка

Way to stop recursion?

От
Jonathan Knopp
Дата:
Been banging my head against the wall for days and starting to think 
there is no way to do what I need. Hoping someone on here can prove me 
wrong.

UPDATE rules work perfectly for what I need to do except I need them to 
only run once, not try and recurse (which of course isn't allowedby 
postgresql anyway). Triggers seem a less efficient way to do the same 
thing, though I understand they would run recursively too. Here's the 
table structure in question:

CREATE TABLE parent (id INT, cola CHAR(1), common CHAR(1));
CREATE TABLE child (id INT, parent_id INT, cola(1), common(1));
INSERT INTO parent VALUES(1, 'adult', 0);
INSERT INTO child VALUES(1, 1, 'kid 1', 0);
INSERT INTO child VALUES(2, 1, 'kid 2', 0);

What I need, is when "common" is changed for a parent, then that new 
value is reflected in "common" for all the children, ie:

UPDATE parent SET cola='something', common=1 WHERE id=1;

That in itself is no problem:

CREATE RULE update_child_common AS ON UPDATE TO parent WHERE 
NEW.common!=OLD.common DO UPDATE child SET common=NEW.common WHERE 
parent_id=OLD.id;

Problem is, when "common" is changed for a child, I need the parent and 
all siblings to reflect that value too, ie:

UPDATE child SET cola='some value',common=2 WHERE id=2;

If I could force recursion off, I could do that with:

CREATE RULE update_common_from_child AS ON UPDATE TO child WHERE 
NEW.common!=OLD.common DO (UPDATE parent SET common=NEW.common WHERE 
id=NEW.parent_id;UPDATE child SET common=NEW.common WHERE 
parent_id=NEW.parent_id)

As it stands, I can not find a way to do that. Any variation I try 
(using "flags", using INSTEAD, triggers) has led to recursion protection 
kicking in and postgresql refusing to run the query. I want to stay away 
from triggers if I can as I imagine they must be significantly less 
efficient when updating large numbers of parents and/or children at once 
(which happens frequently in the application), assuming a trigger could 
be made to do what I need at all.

Hoping I'm missing something obvious...

- Jonathan


Re: Way to stop recursion?

От
Tom Lane
Дата:
Jonathan Knopp <pgsql@delegated.net> writes:
> CREATE TABLE parent (id INT, cola CHAR(1), common CHAR(1));
> CREATE TABLE child (id INT, parent_id INT, cola(1), common(1));

> What I need, is when "common" is changed for a parent, then that new 
> value is reflected in "common" for all the children, ie:
> ...
> Problem is, when "common" is changed for a child, I need the parent and 
> all siblings to reflect that value too, ie:

Seems to me that your real problem is a bogus database layout.  If there
should only be one "common" value for a parent and children, then only
store one value ... that is, "common" should exist only in the parent.

You can if you like make a view that emulates the appearance of a child
table with a common column, viz

create view childview as select child.*, parent.common from child, parent where parent_id = parent.id;

and it would even be possible to make a rule that allows updating this
view.
        regards, tom lane


Re: Way to stop recursion?

От
Andrew Sullivan
Дата:
On Fri, Nov 26, 2004 at 01:03:38PM -0800, Jonathan Knopp wrote:
> UPDATE rules work perfectly for what I need to do except I need them to 
> only run once, not try and recurse (which of course isn't allowedby 
> postgresql anyway). Triggers seem a less efficient way to do the same 
> thing, though I understand they would run recursively too. Here's the 
> table structure in question:

You have to do this with a trigger.  The problem is that the rule is
expanded inline like a macro, so you can't prevent the behaviour
you're seeing.

A

-- 
Andrew Sullivan  | ajs@crankycanuck.ca
The fact that technology doesn't work is no bar to success in the marketplace.    --Philip Greenspun


Re: Way to stop recursion?

От
Andrew Sullivan
Дата:
On Fri, Nov 26, 2004 at 04:31:11PM -0500, Tom Lane wrote:
> 
> Seems to me that your real problem is a bogus database layout.  If there
> should only be one "common" value for a parent and children, then only
> store one value ... that is, "common" should exist only in the parent.

Tom's answers always make me realise that I should think harder
before I talk.  He's right, of course: one common value means store
it once.

A

-- 
Andrew Sullivan  | ajs@crankycanuck.ca
I remember when computers were frustrating because they *did* exactly what 
you told them to.  That actually seems sort of quaint now.    --J.D. Baldwin


Re: Way to stop recursion?

От
Jonathan Knopp
Дата:
Tom Lane wrote:
>>CREATE TABLE parent (id INT, cola CHAR(1), common CHAR(1));
>>CREATE TABLE child (id INT, parent_id INT, cola(1), common(1));
> 
>>What I need, is when "common" is changed for a parent, then that new 
>>value is reflected in "common" for all the children, ie:
>>...
>>Problem is, when "common" is changed for a child, I need the parent and 
>>all siblings to reflect that value too, ie:
> 
> Seems to me that your real problem is a bogus database layout.  If there
> should only be one "common" value for a parent and children, then only
> store one value ... that is, "common" should exist only in the parent.
> ...

Sorry, I should have mentioned that there is a lot more to the design 
that makes this replication necessary, including another two levels to 
the tree plus the ability to have orphaned children.


Re: Way to stop recursion?

От
Pierre-Frédéric Caillaud
Дата:
> You have to do this with a trigger.  The problem is that the rule is
> expanded inline like a macro, so you can't prevent the behaviour
> you're seeing.
True, but you can get out of the hole in another way :

- Change the name of your table to "hidden_table"

- Create a view which is a duplicate of your table :
CREATE VIEW visible_table AS SELECT * FROM hidden_table;

-> Your application now accesses its data without realizing it goes  
through a view.

Now create a rule on this view, to make it update the real hidden_table.  
As the rule does not apply to hidden_table, it won't recurse.

Other solution (this similar to what Tom Lane proposed I think) :

Create a field common_id in your table, with
- an insert trigger which puts a SERIAL default value if there is no  
parent, or copies the parent's value if there is one
- an update trigger to copy the new parent's common_id whenever a child  
changes parent (if this ever occurs in your design)

Now create another table linking common_id to the 'common' value.

Create a view which joins the two, which emulates your current behaviour.
Create an ON UPDATE rule to the view which just changes one row in the  
link table.

If you do a lot of selects, solution #1 will be faster, if you do a lot of  
updates, #2 will win...

Just out of curiosity, what is this for ?


On Fri, 26 Nov 2004 16:34:48 -0500, Andrew Sullivan <ajs@crankycanuck.ca>  
wrote:

> On Fri, Nov 26, 2004 at 01:03:38PM -0800, Jonathan Knopp wrote:
>> UPDATE rules work perfectly for what I need to do except I need them to
>> only run once, not try and recurse (which of course isn't allowedby
>> postgresql anyway). Triggers seem a less efficient way to do the same
>> thing, though I understand they would run recursively too. Here's the
>> table structure in question:
>
> You have to do this with a trigger.  The problem is that the rule is
> expanded inline like a macro, so you can't prevent the behaviour
> you're seeing.
>
> A
>




Re: Way to stop recursion?

От
Jonathan Knopp
Дата:
Pierre-Frédéric Caillaud wrote:
> - Change the name of your table to "hidden_table"
> 
> - Create a view which is a duplicate of your table :
> CREATE VIEW visible_table AS SELECT * FROM hidden_table;
> 
> -> Your application now accesses its data without realizing it goes  
> through a view.
> 
> Now create a rule on this view, to make it update the real 
> hidden_table.  As the rule does not apply to hidden_table, it won't 
> recurse.
> 
> Other solution (this similar to what Tom Lane proposed I think) :
> 
> Create a field common_id in your table, with
> - an insert trigger which puts a SERIAL default value if there is no  
> parent, or copies the parent's value if there is one
> - an update trigger to copy the new parent's common_id whenever a child  
> changes parent (if this ever occurs in your design)
> 
> Now create another table linking common_id to the 'common' value.
> 
> Create a view which joins the two, which emulates your current behaviour.
> Create an ON UPDATE rule to the view which just changes one row in the  
> link table.
> 
> If you do a lot of selects, solution #1 will be faster, if you do a lot 
> of  updates, #2 will win...
The "hidden table" method should work just fine. Ingenius idea, thank you!

> Just out of curiosity, what is this for ?
The actual application has companies instead of parents, employees 
instead of children, then emails as children of employees and/or 
companies, and folders as parents of companies and employees. The 
"common" field (in all 4 layers) are a pair of permissions flags.

May I humbly suggest two possible todo's for postgreSQL: a simple flag 
to suppress recursion (easier/more powerful way of doing the above), 
and/or more direct access to query rewriting. Seems right now rules 
require you to rewrite queries while partially blind to them. Being able 
to rewrite queries in statement triggers similar to what can be done 
with row triggers would be very nice too.


Re: Way to stop recursion?

От
Chris Travers
Дата:
Jonathan Knopp wrote:

>
> Sorry, I should have mentioned that there is a lot more to the design
> that makes this replication necessary, including another two levels to
> the tree plus the ability to have orphaned children.
>
My first thought was "Dude, use a VIEW...."

In database design, the SPOT principle applies.  *Always* enforce a
Single Point Of Truth.  If that doesn't seem to be possible, rethink how
the data is used and look at how to ensure that there is only ONE
authoritative storeage for each piece of transactional data. (Yes,
sometimes we get away from this with OLAP installations but the data is
not generally being updated there.)

In this case, I would create a view (with appropriate rules) which would
automatically populate the common fields from the parent if it exists.
The issue should not be one of storage but of presentation.

Best Wishes,
Chris Travers
Metatron Technology Consulting

> ---------------------------(end of broadcast)---------------------------
> TIP 2: you can get off all lists at once with the unregister command
>    (send "unregister YourEmailAddressHere" to majordomo@postgresql.org)
>
>


Вложения