Обсуждение: Updatable view and default sequence values

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

Updatable view and default sequence values

От
Kouber Saparev
Дата:
Hi All,

I am currently using PostgreSQL 8.1.3 and am trying to create an
updatable view with two (or more) joined tables and I also would like to
have the ability to indicate implicitly the value of the serial primary
key fields.

I have the following two tables:

CREATE TABLE purchase (
   purchase_sid SERIAL PRIMARY KEY,
   data TEXT
);

CREATE TABLE subscription_purchase (
   subscription_purchase_sid SERIAL PRIMARY KEY,
   purchase_sid INT NOT NULL UNIQUE REFERENCES purchase ON UPDATE
CASCADE ON DELETE CASCADE,
   data TEXT
);


I have also created the following view:

CREATE VIEW
   s_purchase AS
SELECT
   p.purchase_sid,
   p.data AS pdata,
   sp.subscription_purchase_sid,
   sp.data AS sdata
FROM
   purchase p INNER JOIN subscription_purchase sp
ON
   sp.purchase_sid = p.purchase_sid;


Now, in order to make the view updatable I added this rule:

CREATE RULE s_purchase_update AS
   ON UPDATE TO s_purchase DO INSTEAD (
     UPDATE
       purchase
     SET
       purchase_sid = NEW.purchase_sid,
       data = NEW.pdata
     WHERE
       purchase_sid = OLD.purchase_sid;

     UPDATE
       subscription_purchase
     SET
       subscription_purchase_sid = NEW.subscription_purchase_sid,
       purchase_sid = NEW.purchase_sid,
       data = NEW.sdata
     WHERE
       subscription_purchase_sid = OLD.subscription_purchase_sid;
);


The tricky part comes when I try to make my view insertable. Normally
I'd insert without specifying the sequence values, but in some cases I'd
want also to have full control of what's going into the underlying
tables. The thing is that when I try to do it the simple way by
specifying default values in the view itself:

ALTER TABLE s_purchase ALTER COLUMN purchase_sid SET DEFAULT
NEXTVAL('purchase_purchase_sid_seq');
ALTER TABLE s_purchase ALTER COLUMN subscription_purchase_sid SET
DEFAULT NEXTVAL('subscription_purchase_subscription_purchase_sid_seq');

CREATE RULE s_purchase_insert AS
   ON INSERT TO s_purchase DO INSTEAD (
     INSERT INTO purchase
       (purchase_sid, data)
     VALUES
       (NEW.purchase_sid, NEW.pdata);

     INSERT INTO subscription_purchase
       (subscription_purchase_sid, purchase_sid, data)
     VALUES
       (NEW.subscription_purchase_sid, NEW.purchase_sid, NEW.sdata);
);

I get foreign key constraint violation. That's happening because default
values are executed *before* the rule, so NEXTVAL for the sequence
'purchase_purchase_sid_seq' is executed twice - once for each table.

The work around is to remove the default value for this sequence and to
call it in the rule itself with coalesce:

ALTER TABLE s_purchase ALTER COLUMN purchase_sid DROP DEFAULT;

CREATE RULE s_purchase_insert AS
   ON INSERT TO s_purchase DO INSTEAD (
     INSERT INTO purchase
       (purchase_sid, data)
     VALUES
       (COALESCE(NEW.purchase_sid,
NEXTVAL('purchase_purchase_sid_seq')), NEW.pdata);

     INSERT INTO subscription_purchase
       (subscription_purchase_sid, purchase_sid, data)
     VALUES
       (NEW.subscription_purchase_sid, COALESCE(NEW.purchase_sid,
CURRVAL('purchase_purchase_sid_seq')), NEW.sdata);
);

The thing is that in the real case I have multiple tables that have to
be joined so I really want to get rid of all this COALESCE stuff and to
put everything in the view definition.

Any ideas how to suppress multiple invocations of nextval() or how to do
it anyway? :)

--
Kouber Saparev
http://kouber.saparev.com

Re: Updatable view and default sequence values

От
"Jim C. Nasby"
Дата:
On Tue, May 16, 2006 at 04:16:55PM +0300, Kouber Saparev wrote:
> The tricky part comes when I try to make my view insertable. Normally
> I'd insert without specifying the sequence values, but in some cases I'd
> want also to have full control of what's going into the underlying
> tables. The thing is that when I try to do it the simple way by
> specifying default values in the view itself:
>
> ALTER TABLE s_purchase ALTER COLUMN purchase_sid SET DEFAULT
> NEXTVAL('purchase_purchase_sid_seq');
> ALTER TABLE s_purchase ALTER COLUMN subscription_purchase_sid SET
> DEFAULT NEXTVAL('subscription_purchase_subscription_purchase_sid_seq');

You're doing ALTER TABLE on a view?

> CREATE RULE s_purchase_insert AS
>   ON INSERT TO s_purchase DO INSTEAD (
>     INSERT INTO purchase
>       (purchase_sid, data)
>     VALUES
>       (NEW.purchase_sid, NEW.pdata);
>
>     INSERT INTO subscription_purchase
>       (subscription_purchase_sid, purchase_sid, data)
>     VALUES
>       (NEW.subscription_purchase_sid, NEW.purchase_sid, NEW.sdata);
> );

Why not just use CURRVAL('purchase_purchase_sid_seq') in the rule?
--
Jim C. Nasby, Sr. Engineering Consultant      jnasby@pervasive.com
Pervasive Software      http://pervasive.com    work: 512-231-6117
vcard: http://jim.nasby.net/pervasive.vcf       cell: 512-569-9461

Re: Updatable view and default sequence values

От
Kouber Saparev
Дата:
Jim C. Nasby wrote:
> On Tue, May 16, 2006 at 04:16:55PM +0300, Kouber Saparev wrote:
>> The tricky part comes when I try to make my view insertable. Normally
>> I'd insert without specifying the sequence values, but in some cases I'd
>> want also to have full control of what's going into the underlying
>> tables. The thing is that when I try to do it the simple way by
>> specifying default values in the view itself:
>>
>> ALTER TABLE s_purchase ALTER COLUMN purchase_sid SET DEFAULT
>> NEXTVAL('purchase_purchase_sid_seq');
>> ALTER TABLE s_purchase ALTER COLUMN subscription_purchase_sid SET
>> DEFAULT NEXTVAL('subscription_purchase_subscription_purchase_sid_seq');
>
> You're doing ALTER TABLE on a view?

Exactly, AFAIK there's no ALTER VIEW command.

>> CREATE RULE s_purchase_insert AS
>>   ON INSERT TO s_purchase DO INSTEAD (
>>     INSERT INTO purchase
>>       (purchase_sid, data)
>>     VALUES
>>       (NEW.purchase_sid, NEW.pdata);
>>
>>     INSERT INTO subscription_purchase
>>       (subscription_purchase_sid, purchase_sid, data)
>>     VALUES
>>       (NEW.subscription_purchase_sid, NEW.purchase_sid, NEW.sdata);
>> );
>
> Why not just use CURRVAL('purchase_purchase_sid_seq') in the rule?

Because I would like to be able to insert data both by specifying and
without specifying values for primary keys. For example:

INSERT INTO s_purchase (pdata, sdata) VALUES ('x', 'y');

INSERT INTO s_purchase (purchase_sid, pdata, subscription_purchase_sid,
sdata) VALUES (123, 'x', 456, 'y');

If I specify CURRVAL and not NEW.primary_key, as you're proposing, I
will lose the second way of adding data, cause in the latter case the
values have nothing to do with the sequences, hence CURRVAL will give me
completely useless, or even worse - wrong data. That's why I'm using
default values of a view - if there's a value provided, it will be
entered as is, if not - then the default value (nextval in this case)
will be taken.

However, this solution is not robust enough. That's why I'm looking for
other possible solutions. :)

--
Kouber Saparev
http://kouber.saparev.com

Re: Updatable view and default sequence values

От
"Jim C. Nasby"
Дата:
On Mon, May 22, 2006 at 10:30:47PM +0300, Kouber Saparev wrote:
> Jim C. Nasby wrote:
> >On Tue, May 16, 2006 at 04:16:55PM +0300, Kouber Saparev wrote:
> >>The tricky part comes when I try to make my view insertable. Normally
> >>I'd insert without specifying the sequence values, but in some cases I'd
> >>want also to have full control of what's going into the underlying
> >>tables. The thing is that when I try to do it the simple way by
> >>specifying default values in the view itself:
> >>
> >>ALTER TABLE s_purchase ALTER COLUMN purchase_sid SET DEFAULT
> >>NEXTVAL('purchase_purchase_sid_seq');
> >>ALTER TABLE s_purchase ALTER COLUMN subscription_purchase_sid SET
> >>DEFAULT NEXTVAL('subscription_purchase_subscription_purchase_sid_seq');
> >
> >You're doing ALTER TABLE on a view?
>
> Exactly, AFAIK there's no ALTER VIEW command.
>
> >>CREATE RULE s_purchase_insert AS
> >>  ON INSERT TO s_purchase DO INSTEAD (
> >>    INSERT INTO purchase
> >>      (purchase_sid, data)
> >>    VALUES
> >>      (NEW.purchase_sid, NEW.pdata);
> >>
> >>    INSERT INTO subscription_purchase
> >>      (subscription_purchase_sid, purchase_sid, data)
> >>    VALUES
> >>      (NEW.subscription_purchase_sid, NEW.purchase_sid, NEW.sdata);
> >>);
> >
> >Why not just use CURRVAL('purchase_purchase_sid_seq') in the rule?
>
> Because I would like to be able to insert data both by specifying and
> without specifying values for primary keys. For example:
>
> INSERT INTO s_purchase (pdata, sdata) VALUES ('x', 'y');
>
> INSERT INTO s_purchase (purchase_sid, pdata, subscription_purchase_sid,
> sdata) VALUES (123, 'x', 456, 'y');
>
> If I specify CURRVAL and not NEW.primary_key, as you're proposing, I
> will lose the second way of adding data, cause in the latter case the
> values have nothing to do with the sequences, hence CURRVAL will give me
> completely useless, or even worse - wrong data. That's why I'm using
> default values of a view - if there's a value provided, it will be
> entered as is, if not - then the default value (nextval in this case)
> will be taken.
>
> However, this solution is not robust enough. That's why I'm looking for
> other possible solutions. :)

I think you could get away with doing a CASE or COALESCE statement, ie:

INSERT INTO subscription_purchase ... SELECT
COALESCE(currval('purchase_purchase_sid_seq'), NEW.purchase_sid)

BTW, it would be interesting to share whatever you finally come up with;
it's an interesting problem.
--
Jim C. Nasby, Sr. Engineering Consultant      jnasby@pervasive.com
Pervasive Software      http://pervasive.com    work: 512-231-6117
vcard: http://jim.nasby.net/pervasive.vcf       cell: 512-569-9461

Re: Updatable view and default sequence values

От
Kouber Saparev
Дата:
Jim C. Nasby wrote:
> I think you could get away with doing a CASE or COALESCE statement, ie:
>
> INSERT INTO subscription_purchase ... SELECT
> COALESCE(currval('purchase_purchase_sid_seq'), NEW.purchase_sid)
>
> BTW, it would be interesting to share whatever you finally come up with;
> it's an interesting problem.

Yeah, as I already wrote in my first mail, that's the workaround I'm
currently using - COALESCE everywhere. However, it bothers me that I'm
repeating the same expression multiple times.

I was thinking also of writing some stored procedure in order to
determine whether NEXTVAL was already called and in case it was - to
call CURRVAL instead. Something like that:

CREATE FUNCTION nextcurrval(x_sequence regclass)
   RETURNS int8 AS
$BODY$
   BEGIN

   RETURN CURRVAL(x_sequence);

   EXCEPTION
     WHEN others THEN
       RETURN NEXTVAL(x_sequence);
   END;
$BODY$
   LANGUAGE 'plpgsql' VOLATILE;

The thing is that it works only per session and not per SQL statement,
i.e. RULE. So, in case I have two or more inserts in one session it will
not work correctly - it will always return CURRVAL.

BTW, I didn't manage to find out what's the exception error code for the
"CURRVAL sequence not yet defined" error - that's why I used 'others'.

Anyway, I'll write here when I find other interesting solutions.

Regards,
--
Kouber Saparev
http://kouber.saparev.com