Re: Updatable view and default sequence values

Lists: pgsql-admin
From: Kouber Saparev <postgres(at)saparev(dot)com>
To: pgsql-admin(at)postgresql(dot)org
Subject: Updatable view and default sequence values
Date: 2006-05-16 13:16:55
Message-ID: e4cjbs$29i$1@sea.gmane.org
Views: Raw Message | Whole Thread | Download mbox | Resend email
Lists: pgsql-admin

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


From: "Jim C(dot) Nasby" <jnasby(at)pervasive(dot)com>
To: Kouber Saparev <postgres(at)saparev(dot)com>
Cc: pgsql-admin(at)postgresql(dot)org
Subject: Re: Updatable view and default sequence values
Date: 2006-05-22 16:50:27
Message-ID: 20060522165026.GV64371@pervasive.com
Views: Raw Message | Whole Thread | Download mbox | Resend email
Lists: pgsql-admin

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(at)pervasive(dot)com
Pervasive Software http://pervasive.com work: 512-231-6117
vcard: http://jim.nasby.net/pervasive.vcf cell: 512-569-9461


From: Kouber Saparev <postgres(at)saparev(dot)com>
To: pgsql-admin(at)postgresql(dot)org
Subject: Re: Updatable view and default sequence values
Date: 2006-05-22 19:30:47
Message-ID: e4t3gt$gd7$1@sea.gmane.org
Views: Raw Message | Whole Thread | Download mbox | Resend email
Lists: pgsql-admin

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


From: "Jim C(dot) Nasby" <jnasby(at)pervasive(dot)com>
To: Kouber Saparev <postgres(at)saparev(dot)com>
Cc: pgsql-admin(at)postgresql(dot)org
Subject: Re: Updatable view and default sequence values
Date: 2006-05-22 19:56:55
Message-ID: 20060522195655.GO64371@pervasive.com
Views: Raw Message | Whole Thread | Download mbox | Resend email
Lists: pgsql-admin

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(at)pervasive(dot)com
Pervasive Software http://pervasive.com work: 512-231-6117
vcard: http://jim.nasby.net/pervasive.vcf cell: 512-569-9461


From: Kouber Saparev <postgres(at)saparev(dot)com>
To: pgsql-admin(at)postgresql(dot)org
Subject: Re: Updatable view and default sequence values
Date: 2006-05-23 10:53:50
Message-ID: e4upjj$1nb$1@sea.gmane.org
Views: Raw Message | Whole Thread | Download mbox | Resend email
Lists: pgsql-admin

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