Server IP : 103.119.228.120 / Your IP : 3.149.253.73 Web Server : Apache System : Linux v8.techscape8.com 3.10.0-1160.119.1.el7.tuxcare.els2.x86_64 #1 SMP Mon Jul 15 12:09:18 UTC 2024 x86_64 User : nobody ( 99) PHP Version : 5.6.40 Disable Function : shell_exec,symlink,system,exec,proc_get_status,proc_nice,proc_terminate,define_syslog_variables,syslog,openlog,closelog,escapeshellcmd,passthru,ocinum cols,ini_alter,leak,listen,chgrp,apache_note,apache_setenv,debugger_on,debugger_off,ftp_exec,dl,dll,myshellexec,proc_open,socket_bind,proc_close,escapeshellarg,parse_ini_filepopen,fpassthru,exec,passthru,escapeshellarg,escapeshellcmd,proc_close,proc_open,ini_alter,popen,show_source,proc_nice,proc_terminate,proc_get_status,proc_close,pfsockopen,leak,apache_child_terminate,posix_kill,posix_mkfifo,posix_setpgid,posix_setsid,posix_setuid,dl,symlink,shell_exec,system,dl,passthru,escapeshellarg,escapeshellcmd,myshellexec,c99_buff_prepare,c99_sess_put,fpassthru,getdisfunc,fx29exec,fx29exec2,is_windows,disp_freespace,fx29sh_getupdate,fx29_buff_prepare,fx29_sess_put,fx29shexit,fx29fsearch,fx29ftpbrutecheck,fx29sh_tools,fx29sh_about,milw0rm,imagez,sh_name,myshellexec,checkproxyhost,dosyayicek,c99_buff_prepare,c99_sess_put,c99getsource,c99sh_getupdate,c99fsearch,c99shexit,view_perms,posix_getpwuid,posix_getgrgid,posix_kill,parse_perms,parsesort,view_perms_color,set_encoder_input,ls_setcheckboxall,ls_reverse_all,rsg_read,rsg_glob,selfURL,dispsecinfo,unix2DosTime,addFile,system,get_users,view_size,DirFiles,DirFilesWide,DirPrintHTMLHeaders,GetFilesTotal,GetTitles,GetTimeTotal,GetMatchesCount,GetFileMatchesCount,GetResultFiles,fs_copy_dir,fs_copy_obj,fs_move_dir,fs_move_obj,fs_rmdir,SearchText,getmicrotime MySQL : ON | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : ON Directory : /usr/share/doc/postgresql-9.2.24/html/ |
Upload File : |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <HTML ><HEAD ><TITLE >Rules on INSERT, UPDATE, and DELETE</TITLE ><META NAME="GENERATOR" CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK REV="MADE" HREF="mailto:pgsql-docs@postgresql.org"><LINK REL="HOME" TITLE="PostgreSQL 9.2.24 Documentation" HREF="index.html"><LINK REL="UP" TITLE="The Rule System" HREF="rules.html"><LINK REL="PREVIOUS" TITLE="Views and the Rule System" HREF="rules-views.html"><LINK REL="NEXT" TITLE="Rules and Privileges" HREF="rules-privileges.html"><LINK REL="STYLESHEET" TYPE="text/css" HREF="stylesheet.css"><META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=ISO-8859-1"><META NAME="creation" CONTENT="2017-11-06T22:43:11"></HEAD ><BODY CLASS="SECT1" ><DIV CLASS="NAVHEADER" ><TABLE SUMMARY="Header navigation table" WIDTH="100%" BORDER="0" CELLPADDING="0" CELLSPACING="0" ><TR ><TH COLSPAN="5" ALIGN="center" VALIGN="bottom" ><A HREF="index.html" >PostgreSQL 9.2.24 Documentation</A ></TH ></TR ><TR ><TD WIDTH="10%" ALIGN="left" VALIGN="top" ><A TITLE="Views and the Rule System" HREF="rules-views.html" ACCESSKEY="P" >Prev</A ></TD ><TD WIDTH="10%" ALIGN="left" VALIGN="top" ><A HREF="rules.html" ACCESSKEY="U" >Up</A ></TD ><TD WIDTH="60%" ALIGN="center" VALIGN="bottom" >Chapter 37. The Rule System</TD ><TD WIDTH="20%" ALIGN="right" VALIGN="top" ><A TITLE="Rules and Privileges" HREF="rules-privileges.html" ACCESSKEY="N" >Next</A ></TD ></TR ></TABLE ><HR ALIGN="LEFT" WIDTH="100%"></DIV ><DIV CLASS="SECT1" ><H1 CLASS="SECT1" ><A NAME="RULES-UPDATE" >37.3. Rules on <TT CLASS="COMMAND" >INSERT</TT >, <TT CLASS="COMMAND" >UPDATE</TT >, and <TT CLASS="COMMAND" >DELETE</TT ></A ></H1 ><P > Rules that are defined on <TT CLASS="COMMAND" >INSERT</TT >, <TT CLASS="COMMAND" >UPDATE</TT >, and <TT CLASS="COMMAND" >DELETE</TT > are significantly different from the view rules described in the previous section. First, their <TT CLASS="COMMAND" >CREATE RULE</TT > command allows more: <P ></P ></P><UL ><LI ><P > They are allowed to have no action. </P ></LI ><LI ><P > They can have multiple actions. </P ></LI ><LI ><P > They can be <TT CLASS="LITERAL" >INSTEAD</TT > or <TT CLASS="LITERAL" >ALSO</TT > (the default). </P ></LI ><LI ><P > The pseudorelations <TT CLASS="LITERAL" >NEW</TT > and <TT CLASS="LITERAL" >OLD</TT > become useful. </P ></LI ><LI ><P > They can have rule qualifications. </P ></LI ></UL ><P> Second, they don't modify the query tree in place. Instead they create zero or more new query trees and can throw away the original one.</P ><DIV CLASS="SECT2" ><H2 CLASS="SECT2" ><A NAME="AEN55633" >37.3.1. How Update Rules Work</A ></H2 ><P > Keep the syntax: </P><PRE CLASS="PROGRAMLISTING" >CREATE [ OR REPLACE ] RULE <TT CLASS="REPLACEABLE" ><I >name</I ></TT > AS ON <TT CLASS="REPLACEABLE" ><I >event</I ></TT > TO <TT CLASS="REPLACEABLE" ><I >table</I ></TT > [ WHERE <TT CLASS="REPLACEABLE" ><I >condition</I ></TT > ] DO [ ALSO | INSTEAD ] { NOTHING | <TT CLASS="REPLACEABLE" ><I >command</I ></TT > | ( <TT CLASS="REPLACEABLE" ><I >command</I ></TT > ; <TT CLASS="REPLACEABLE" ><I >command</I ></TT > ... ) }</PRE ><P> in mind. In the following, <I CLASS="FIRSTTERM" >update rules</I > means rules that are defined on <TT CLASS="COMMAND" >INSERT</TT >, <TT CLASS="COMMAND" >UPDATE</TT >, or <TT CLASS="COMMAND" >DELETE</TT >.</P ><P > Update rules get applied by the rule system when the result relation and the command type of a query tree are equal to the object and event given in the <TT CLASS="COMMAND" >CREATE RULE</TT > command. For update rules, the rule system creates a list of query trees. Initially the query-tree list is empty. There can be zero (<TT CLASS="LITERAL" >NOTHING</TT > key word), one, or multiple actions. To simplify, we will look at a rule with one action. This rule can have a qualification or not and it can be <TT CLASS="LITERAL" >INSTEAD</TT > or <TT CLASS="LITERAL" >ALSO</TT > (the default).</P ><P > What is a rule qualification? It is a restriction that tells when the actions of the rule should be done and when not. This qualification can only reference the pseudorelations <TT CLASS="LITERAL" >NEW</TT > and/or <TT CLASS="LITERAL" >OLD</TT >, which basically represent the relation that was given as object (but with a special meaning).</P ><P > So we have three cases that produce the following query trees for a one-action rule. <P ></P ></P><DIV CLASS="VARIABLELIST" ><DL ><DT >No qualification, with either <TT CLASS="LITERAL" >ALSO</TT > or <TT CLASS="LITERAL" >INSTEAD</TT ></DT ><DD ><P > the query tree from the rule action with the original query tree's qualification added </P ></DD ><DT >Qualification given and <TT CLASS="LITERAL" >ALSO</TT ></DT ><DD ><P > the query tree from the rule action with the rule qualification and the original query tree's qualification added </P ></DD ><DT >Qualification given and <TT CLASS="LITERAL" >INSTEAD</TT ></DT ><DD ><P > the query tree from the rule action with the rule qualification and the original query tree's qualification; and the original query tree with the negated rule qualification added </P ></DD ></DL ></DIV ><P> Finally, if the rule is <TT CLASS="LITERAL" >ALSO</TT >, the unchanged original query tree is added to the list. Since only qualified <TT CLASS="LITERAL" >INSTEAD</TT > rules already add the original query tree, we end up with either one or two output query trees for a rule with one action.</P ><P > For <TT CLASS="LITERAL" >ON INSERT</TT > rules, the original query (if not suppressed by <TT CLASS="LITERAL" >INSTEAD</TT >) is done before any actions added by rules. This allows the actions to see the inserted row(s). But for <TT CLASS="LITERAL" >ON UPDATE</TT > and <TT CLASS="LITERAL" >ON DELETE</TT > rules, the original query is done after the actions added by rules. This ensures that the actions can see the to-be-updated or to-be-deleted rows; otherwise, the actions might do nothing because they find no rows matching their qualifications.</P ><P > The query trees generated from rule actions are thrown into the rewrite system again, and maybe more rules get applied resulting in more or less query trees. So a rule's actions must have either a different command type or a different result relation than the rule itself is on, otherwise this recursive process will end up in an infinite loop. (Recursive expansion of a rule will be detected and reported as an error.)</P ><P > The query trees found in the actions of the <TT CLASS="STRUCTNAME" >pg_rewrite</TT > system catalog are only templates. Since they can reference the range-table entries for <TT CLASS="LITERAL" >NEW</TT > and <TT CLASS="LITERAL" >OLD</TT >, some substitutions have to be made before they can be used. For any reference to <TT CLASS="LITERAL" >NEW</TT >, the target list of the original query is searched for a corresponding entry. If found, that entry's expression replaces the reference. Otherwise, <TT CLASS="LITERAL" >NEW</TT > means the same as <TT CLASS="LITERAL" >OLD</TT > (for an <TT CLASS="COMMAND" >UPDATE</TT >) or is replaced by a null value (for an <TT CLASS="COMMAND" >INSERT</TT >). Any reference to <TT CLASS="LITERAL" >OLD</TT > is replaced by a reference to the range-table entry that is the result relation.</P ><P > After the system is done applying update rules, it applies view rules to the produced query tree(s). Views cannot insert new update actions so there is no need to apply update rules to the output of view rewriting.</P ><DIV CLASS="SECT3" ><H3 CLASS="SECT3" ><A NAME="AEN55693" >37.3.1.1. A First Rule Step by Step</A ></H3 ><P > Say we want to trace changes to the <TT CLASS="LITERAL" >sl_avail</TT > column in the <TT CLASS="LITERAL" >shoelace_data</TT > relation. So we set up a log table and a rule that conditionally writes a log entry when an <TT CLASS="COMMAND" >UPDATE</TT > is performed on <TT CLASS="LITERAL" >shoelace_data</TT >. </P><PRE CLASS="PROGRAMLISTING" >CREATE TABLE shoelace_log ( sl_name text, -- shoelace changed sl_avail integer, -- new available value log_who text, -- who did it log_when timestamp -- when ); CREATE RULE log_shoelace AS ON UPDATE TO shoelace_data WHERE NEW.sl_avail <> OLD.sl_avail DO INSERT INTO shoelace_log VALUES ( NEW.sl_name, NEW.sl_avail, current_user, current_timestamp );</PRE ><P></P ><P > Now someone does: </P><PRE CLASS="PROGRAMLISTING" >UPDATE shoelace_data SET sl_avail = 6 WHERE sl_name = 'sl7';</PRE ><P> and we look at the log table: </P><PRE CLASS="PROGRAMLISTING" >SELECT * FROM shoelace_log; sl_name | sl_avail | log_who | log_when ---------+----------+---------+---------------------------------- sl7 | 6 | Al | Tue Oct 20 16:14:45 1998 MET DST (1 row)</PRE ><P> </P ><P > That's what we expected. What happened in the background is the following. The parser created the query tree: </P><PRE CLASS="PROGRAMLISTING" >UPDATE shoelace_data SET sl_avail = 6 FROM shoelace_data shoelace_data WHERE shoelace_data.sl_name = 'sl7';</PRE ><P> There is a rule <TT CLASS="LITERAL" >log_shoelace</TT > that is <TT CLASS="LITERAL" >ON UPDATE</TT > with the rule qualification expression: </P><PRE CLASS="PROGRAMLISTING" >NEW.sl_avail <> OLD.sl_avail</PRE ><P> and the action: </P><PRE CLASS="PROGRAMLISTING" >INSERT INTO shoelace_log VALUES ( new.sl_name, new.sl_avail, current_user, current_timestamp ) FROM shoelace_data new, shoelace_data old;</PRE ><P> (This looks a little strange since you cannot normally write <TT CLASS="LITERAL" >INSERT ... VALUES ... FROM</TT >. The <TT CLASS="LITERAL" >FROM</TT > clause here is just to indicate that there are range-table entries in the query tree for <TT CLASS="LITERAL" >new</TT > and <TT CLASS="LITERAL" >old</TT >. These are needed so that they can be referenced by variables in the <TT CLASS="COMMAND" >INSERT</TT > command's query tree.)</P ><P > The rule is a qualified <TT CLASS="LITERAL" >ALSO</TT > rule, so the rule system has to return two query trees: the modified rule action and the original query tree. In step 1, the range table of the original query is incorporated into the rule's action query tree. This results in: </P><PRE CLASS="PROGRAMLISTING" >INSERT INTO shoelace_log VALUES ( new.sl_name, new.sl_avail, current_user, current_timestamp ) FROM shoelace_data new, shoelace_data old, <B CLASS="EMPHASIS" >shoelace_data shoelace_data</B >;</PRE ><P> In step 2, the rule qualification is added to it, so the result set is restricted to rows where <TT CLASS="LITERAL" >sl_avail</TT > changes: </P><PRE CLASS="PROGRAMLISTING" >INSERT INTO shoelace_log VALUES ( new.sl_name, new.sl_avail, current_user, current_timestamp ) FROM shoelace_data new, shoelace_data old, shoelace_data shoelace_data <B CLASS="EMPHASIS" >WHERE new.sl_avail <> old.sl_avail</B >;</PRE ><P> (This looks even stranger, since <TT CLASS="LITERAL" >INSERT ... VALUES</TT > doesn't have a <TT CLASS="LITERAL" >WHERE</TT > clause either, but the planner and executor will have no difficulty with it. They need to support this same functionality anyway for <TT CLASS="LITERAL" >INSERT ... SELECT</TT >.) </P ><P > In step 3, the original query tree's qualification is added, restricting the result set further to only the rows that would have been touched by the original query: </P><PRE CLASS="PROGRAMLISTING" >INSERT INTO shoelace_log VALUES ( new.sl_name, new.sl_avail, current_user, current_timestamp ) FROM shoelace_data new, shoelace_data old, shoelace_data shoelace_data WHERE new.sl_avail <> old.sl_avail <B CLASS="EMPHASIS" >AND shoelace_data.sl_name = 'sl7'</B >;</PRE ><P> </P ><P > Step 4 replaces references to <TT CLASS="LITERAL" >NEW</TT > by the target list entries from the original query tree or by the matching variable references from the result relation: </P><PRE CLASS="PROGRAMLISTING" >INSERT INTO shoelace_log VALUES ( <B CLASS="EMPHASIS" >shoelace_data.sl_name</B >, <B CLASS="EMPHASIS" >6</B >, current_user, current_timestamp ) FROM shoelace_data new, shoelace_data old, shoelace_data shoelace_data WHERE <B CLASS="EMPHASIS" >6</B > <> old.sl_avail AND shoelace_data.sl_name = 'sl7';</PRE ><P> </P ><P > Step 5 changes <TT CLASS="LITERAL" >OLD</TT > references into result relation references: </P><PRE CLASS="PROGRAMLISTING" >INSERT INTO shoelace_log VALUES ( shoelace_data.sl_name, 6, current_user, current_timestamp ) FROM shoelace_data new, shoelace_data old, shoelace_data shoelace_data WHERE 6 <> <B CLASS="EMPHASIS" >shoelace_data.sl_avail</B > AND shoelace_data.sl_name = 'sl7';</PRE ><P> </P ><P > That's it. Since the rule is <TT CLASS="LITERAL" >ALSO</TT >, we also output the original query tree. In short, the output from the rule system is a list of two query trees that correspond to these statements: </P><PRE CLASS="PROGRAMLISTING" >INSERT INTO shoelace_log VALUES ( shoelace_data.sl_name, 6, current_user, current_timestamp ) FROM shoelace_data WHERE 6 <> shoelace_data.sl_avail AND shoelace_data.sl_name = 'sl7'; UPDATE shoelace_data SET sl_avail = 6 WHERE sl_name = 'sl7';</PRE ><P> These are executed in this order, and that is exactly what the rule was meant to do. </P ><P > The substitutions and the added qualifications ensure that, if the original query would be, say: </P><PRE CLASS="PROGRAMLISTING" >UPDATE shoelace_data SET sl_color = 'green' WHERE sl_name = 'sl7';</PRE ><P> no log entry would get written. In that case, the original query tree does not contain a target list entry for <TT CLASS="LITERAL" >sl_avail</TT >, so <TT CLASS="LITERAL" >NEW.sl_avail</TT > will get replaced by <TT CLASS="LITERAL" >shoelace_data.sl_avail</TT >. Thus, the extra command generated by the rule is: </P><PRE CLASS="PROGRAMLISTING" >INSERT INTO shoelace_log VALUES ( shoelace_data.sl_name, <B CLASS="EMPHASIS" >shoelace_data.sl_avail</B >, current_user, current_timestamp ) FROM shoelace_data WHERE <B CLASS="EMPHASIS" >shoelace_data.sl_avail</B > <> shoelace_data.sl_avail AND shoelace_data.sl_name = 'sl7';</PRE ><P> and that qualification will never be true. </P ><P > It will also work if the original query modifies multiple rows. So if someone issued the command: </P><PRE CLASS="PROGRAMLISTING" >UPDATE shoelace_data SET sl_avail = 0 WHERE sl_color = 'black';</PRE ><P> four rows in fact get updated (<TT CLASS="LITERAL" >sl1</TT >, <TT CLASS="LITERAL" >sl2</TT >, <TT CLASS="LITERAL" >sl3</TT >, and <TT CLASS="LITERAL" >sl4</TT >). But <TT CLASS="LITERAL" >sl3</TT > already has <TT CLASS="LITERAL" >sl_avail = 0</TT >. In this case, the original query trees qualification is different and that results in the extra query tree: </P><PRE CLASS="PROGRAMLISTING" >INSERT INTO shoelace_log SELECT shoelace_data.sl_name, 0, current_user, current_timestamp FROM shoelace_data WHERE 0 <> shoelace_data.sl_avail AND <B CLASS="EMPHASIS" >shoelace_data.sl_color = 'black'</B >;</PRE ><P> being generated by the rule. This query tree will surely insert three new log entries. And that's absolutely correct.</P ><P > Here we can see why it is important that the original query tree is executed last. If the <TT CLASS="COMMAND" >UPDATE</TT > had been executed first, all the rows would have already been set to zero, so the logging <TT CLASS="COMMAND" >INSERT</TT > would not find any row where <TT CLASS="LITERAL" >0 <> shoelace_data.sl_avail</TT >.</P ></DIV ></DIV ><DIV CLASS="SECT2" ><H2 CLASS="SECT2" ><A NAME="RULES-UPDATE-VIEWS" >37.3.2. Cooperation with Views</A ></H2 ><P > A simple way to protect view relations from the mentioned possibility that someone can try to run <TT CLASS="COMMAND" >INSERT</TT >, <TT CLASS="COMMAND" >UPDATE</TT >, or <TT CLASS="COMMAND" >DELETE</TT > on them is to let those query trees get thrown away. So we could create the rules: </P><PRE CLASS="PROGRAMLISTING" >CREATE RULE shoe_ins_protect AS ON INSERT TO shoe DO INSTEAD NOTHING; CREATE RULE shoe_upd_protect AS ON UPDATE TO shoe DO INSTEAD NOTHING; CREATE RULE shoe_del_protect AS ON DELETE TO shoe DO INSTEAD NOTHING;</PRE ><P> If someone now tries to do any of these operations on the view relation <TT CLASS="LITERAL" >shoe</TT >, the rule system will apply these rules. Since the rules have no actions and are <TT CLASS="LITERAL" >INSTEAD</TT >, the resulting list of query trees will be empty and the whole query will become nothing because there is nothing left to be optimized or executed after the rule system is done with it.</P ><P > A more sophisticated way to use the rule system is to create rules that rewrite the query tree into one that does the right operation on the real tables. To do that on the <TT CLASS="LITERAL" >shoelace</TT > view, we create the following rules: </P><PRE CLASS="PROGRAMLISTING" >CREATE RULE shoelace_ins AS ON INSERT TO shoelace DO INSTEAD INSERT INTO shoelace_data VALUES ( NEW.sl_name, NEW.sl_avail, NEW.sl_color, NEW.sl_len, NEW.sl_unit ); CREATE RULE shoelace_upd AS ON UPDATE TO shoelace DO INSTEAD UPDATE shoelace_data SET sl_name = NEW.sl_name, sl_avail = NEW.sl_avail, sl_color = NEW.sl_color, sl_len = NEW.sl_len, sl_unit = NEW.sl_unit WHERE sl_name = OLD.sl_name; CREATE RULE shoelace_del AS ON DELETE TO shoelace DO INSTEAD DELETE FROM shoelace_data WHERE sl_name = OLD.sl_name;</PRE ><P> </P ><P > If you want to support <TT CLASS="LITERAL" >RETURNING</TT > queries on the view, you need to make the rules include <TT CLASS="LITERAL" >RETURNING</TT > clauses that compute the view rows. This is usually pretty trivial for views on a single table, but it's a bit tedious for join views such as <TT CLASS="LITERAL" >shoelace</TT >. An example for the insert case is: </P><PRE CLASS="PROGRAMLISTING" >CREATE RULE shoelace_ins AS ON INSERT TO shoelace DO INSTEAD INSERT INTO shoelace_data VALUES ( NEW.sl_name, NEW.sl_avail, NEW.sl_color, NEW.sl_len, NEW.sl_unit ) RETURNING shoelace_data.*, (SELECT shoelace_data.sl_len * u.un_fact FROM unit u WHERE shoelace_data.sl_unit = u.un_name);</PRE ><P> Note that this one rule supports both <TT CLASS="COMMAND" >INSERT</TT > and <TT CLASS="COMMAND" >INSERT RETURNING</TT > queries on the view — the <TT CLASS="LITERAL" >RETURNING</TT > clause is simply ignored for <TT CLASS="COMMAND" >INSERT</TT >. </P ><P > Now assume that once in a while, a pack of shoelaces arrives at the shop and a big parts list along with it. But you don't want to manually update the <TT CLASS="LITERAL" >shoelace</TT > view every time. Instead we setup two little tables: one where you can insert the items from the part list, and one with a special trick. The creation commands for these are: </P><PRE CLASS="PROGRAMLISTING" >CREATE TABLE shoelace_arrive ( arr_name text, arr_quant integer ); CREATE TABLE shoelace_ok ( ok_name text, ok_quant integer ); CREATE RULE shoelace_ok_ins AS ON INSERT TO shoelace_ok DO INSTEAD UPDATE shoelace SET sl_avail = sl_avail + NEW.ok_quant WHERE sl_name = NEW.ok_name;</PRE ><P> Now you can fill the table <TT CLASS="LITERAL" >shoelace_arrive</TT > with the data from the parts list: </P><PRE CLASS="PROGRAMLISTING" >SELECT * FROM shoelace_arrive; arr_name | arr_quant ----------+----------- sl3 | 10 sl6 | 20 sl8 | 20 (3 rows)</PRE ><P> Take a quick look at the current data: </P><PRE CLASS="PROGRAMLISTING" >SELECT * FROM shoelace; sl_name | sl_avail | sl_color | sl_len | sl_unit | sl_len_cm ----------+----------+----------+--------+---------+----------- sl1 | 5 | black | 80 | cm | 80 sl2 | 6 | black | 100 | cm | 100 sl7 | 6 | brown | 60 | cm | 60 sl3 | 0 | black | 35 | inch | 88.9 sl4 | 8 | black | 40 | inch | 101.6 sl8 | 1 | brown | 40 | inch | 101.6 sl5 | 4 | brown | 1 | m | 100 sl6 | 0 | brown | 0.9 | m | 90 (8 rows)</PRE ><P> Now move the arrived shoelaces in: </P><PRE CLASS="PROGRAMLISTING" >INSERT INTO shoelace_ok SELECT * FROM shoelace_arrive;</PRE ><P> and check the results: </P><PRE CLASS="PROGRAMLISTING" >SELECT * FROM shoelace ORDER BY sl_name; sl_name | sl_avail | sl_color | sl_len | sl_unit | sl_len_cm ----------+----------+----------+--------+---------+----------- sl1 | 5 | black | 80 | cm | 80 sl2 | 6 | black | 100 | cm | 100 sl7 | 6 | brown | 60 | cm | 60 sl4 | 8 | black | 40 | inch | 101.6 sl3 | 10 | black | 35 | inch | 88.9 sl8 | 21 | brown | 40 | inch | 101.6 sl5 | 4 | brown | 1 | m | 100 sl6 | 20 | brown | 0.9 | m | 90 (8 rows) SELECT * FROM shoelace_log; sl_name | sl_avail | log_who| log_when ---------+----------+--------+---------------------------------- sl7 | 6 | Al | Tue Oct 20 19:14:45 1998 MET DST sl3 | 10 | Al | Tue Oct 20 19:25:16 1998 MET DST sl6 | 20 | Al | Tue Oct 20 19:25:16 1998 MET DST sl8 | 21 | Al | Tue Oct 20 19:25:16 1998 MET DST (4 rows)</PRE ><P> </P ><P > It's a long way from the one <TT CLASS="LITERAL" >INSERT ... SELECT</TT > to these results. And the description of the query-tree transformation will be the last in this chapter. First, there is the parser's output: </P><PRE CLASS="PROGRAMLISTING" >INSERT INTO shoelace_ok SELECT shoelace_arrive.arr_name, shoelace_arrive.arr_quant FROM shoelace_arrive shoelace_arrive, shoelace_ok shoelace_ok;</PRE ><P> Now the first rule <TT CLASS="LITERAL" >shoelace_ok_ins</TT > is applied and turns this into: </P><PRE CLASS="PROGRAMLISTING" >UPDATE shoelace SET sl_avail = shoelace.sl_avail + shoelace_arrive.arr_quant FROM shoelace_arrive shoelace_arrive, shoelace_ok shoelace_ok, shoelace_ok old, shoelace_ok new, shoelace shoelace WHERE shoelace.sl_name = shoelace_arrive.arr_name;</PRE ><P> and throws away the original <TT CLASS="COMMAND" >INSERT</TT > on <TT CLASS="LITERAL" >shoelace_ok</TT >. This rewritten query is passed to the rule system again, and the second applied rule <TT CLASS="LITERAL" >shoelace_upd</TT > produces: </P><PRE CLASS="PROGRAMLISTING" >UPDATE shoelace_data SET sl_name = shoelace.sl_name, sl_avail = shoelace.sl_avail + shoelace_arrive.arr_quant, sl_color = shoelace.sl_color, sl_len = shoelace.sl_len, sl_unit = shoelace.sl_unit FROM shoelace_arrive shoelace_arrive, shoelace_ok shoelace_ok, shoelace_ok old, shoelace_ok new, shoelace shoelace, shoelace old, shoelace new, shoelace_data shoelace_data WHERE shoelace.sl_name = shoelace_arrive.arr_name AND shoelace_data.sl_name = shoelace.sl_name;</PRE ><P> Again it's an <TT CLASS="LITERAL" >INSTEAD</TT > rule and the previous query tree is trashed. Note that this query still uses the view <TT CLASS="LITERAL" >shoelace</TT >. But the rule system isn't finished with this step, so it continues and applies the <TT CLASS="LITERAL" >_RETURN</TT > rule on it, and we get: </P><PRE CLASS="PROGRAMLISTING" >UPDATE shoelace_data SET sl_name = s.sl_name, sl_avail = s.sl_avail + shoelace_arrive.arr_quant, sl_color = s.sl_color, sl_len = s.sl_len, sl_unit = s.sl_unit FROM shoelace_arrive shoelace_arrive, shoelace_ok shoelace_ok, shoelace_ok old, shoelace_ok new, shoelace shoelace, shoelace old, shoelace new, shoelace_data shoelace_data, shoelace old, shoelace new, shoelace_data s, unit u WHERE s.sl_name = shoelace_arrive.arr_name AND shoelace_data.sl_name = s.sl_name;</PRE ><P> Finally, the rule <TT CLASS="LITERAL" >log_shoelace</TT > gets applied, producing the extra query tree: </P><PRE CLASS="PROGRAMLISTING" >INSERT INTO shoelace_log SELECT s.sl_name, s.sl_avail + shoelace_arrive.arr_quant, current_user, current_timestamp FROM shoelace_arrive shoelace_arrive, shoelace_ok shoelace_ok, shoelace_ok old, shoelace_ok new, shoelace shoelace, shoelace old, shoelace new, shoelace_data shoelace_data, shoelace old, shoelace new, shoelace_data s, unit u, shoelace_data old, shoelace_data new shoelace_log shoelace_log WHERE s.sl_name = shoelace_arrive.arr_name AND shoelace_data.sl_name = s.sl_name AND (s.sl_avail + shoelace_arrive.arr_quant) <> s.sl_avail;</PRE ><P> After that the rule system runs out of rules and returns the generated query trees. </P ><P > So we end up with two final query trees that are equivalent to the <ACRONYM CLASS="ACRONYM" >SQL</ACRONYM > statements: </P><PRE CLASS="PROGRAMLISTING" >INSERT INTO shoelace_log SELECT s.sl_name, s.sl_avail + shoelace_arrive.arr_quant, current_user, current_timestamp FROM shoelace_arrive shoelace_arrive, shoelace_data shoelace_data, shoelace_data s WHERE s.sl_name = shoelace_arrive.arr_name AND shoelace_data.sl_name = s.sl_name AND s.sl_avail + shoelace_arrive.arr_quant <> s.sl_avail; UPDATE shoelace_data SET sl_avail = shoelace_data.sl_avail + shoelace_arrive.arr_quant FROM shoelace_arrive shoelace_arrive, shoelace_data shoelace_data, shoelace_data s WHERE s.sl_name = shoelace_arrive.sl_name AND shoelace_data.sl_name = s.sl_name;</PRE ><P> The result is that data coming from one relation inserted into another, changed into updates on a third, changed into updating a fourth plus logging that final update in a fifth gets reduced into two queries.</P ><P > There is a little detail that's a bit ugly. Looking at the two queries, it turns out that the <TT CLASS="LITERAL" >shoelace_data</TT > relation appears twice in the range table where it could definitely be reduced to one. The planner does not handle it and so the execution plan for the rule systems output of the <TT CLASS="COMMAND" >INSERT</TT > will be </P><PRE CLASS="LITERALLAYOUT" >Nested Loop -> Merge Join -> Seq Scan -> Sort -> Seq Scan on s -> Seq Scan -> Sort -> Seq Scan on shoelace_arrive -> Seq Scan on shoelace_data</PRE ><P> while omitting the extra range table entry would result in a </P><PRE CLASS="LITERALLAYOUT" >Merge Join -> Seq Scan -> Sort -> Seq Scan on s -> Seq Scan -> Sort -> Seq Scan on shoelace_arrive</PRE ><P> which produces exactly the same entries in the log table. Thus, the rule system caused one extra scan on the table <TT CLASS="LITERAL" >shoelace_data</TT > that is absolutely not necessary. And the same redundant scan is done once more in the <TT CLASS="COMMAND" >UPDATE</TT >. But it was a really hard job to make that all possible at all.</P ><P > Now we make a final demonstration of the <SPAN CLASS="PRODUCTNAME" >PostgreSQL</SPAN > rule system and its power. Say you add some shoelaces with extraordinary colors to your database: </P><PRE CLASS="PROGRAMLISTING" >INSERT INTO shoelace VALUES ('sl9', 0, 'pink', 35.0, 'inch', 0.0); INSERT INTO shoelace VALUES ('sl10', 1000, 'magenta', 40.0, 'inch', 0.0);</PRE ><P> We would like to make a view to check which <TT CLASS="LITERAL" >shoelace</TT > entries do not fit any shoe in color. The view for this is: </P><PRE CLASS="PROGRAMLISTING" >CREATE VIEW shoelace_mismatch AS SELECT * FROM shoelace WHERE NOT EXISTS (SELECT shoename FROM shoe WHERE slcolor = sl_color);</PRE ><P> Its output is: </P><PRE CLASS="PROGRAMLISTING" >SELECT * FROM shoelace_mismatch; sl_name | sl_avail | sl_color | sl_len | sl_unit | sl_len_cm ---------+----------+----------+--------+---------+----------- sl9 | 0 | pink | 35 | inch | 88.9 sl10 | 1000 | magenta | 40 | inch | 101.6</PRE ><P> </P ><P > Now we want to set it up so that mismatching shoelaces that are not in stock are deleted from the database. To make it a little harder for <SPAN CLASS="PRODUCTNAME" >PostgreSQL</SPAN >, we don't delete it directly. Instead we create one more view: </P><PRE CLASS="PROGRAMLISTING" >CREATE VIEW shoelace_can_delete AS SELECT * FROM shoelace_mismatch WHERE sl_avail = 0;</PRE ><P> and do it this way: </P><PRE CLASS="PROGRAMLISTING" >DELETE FROM shoelace WHERE EXISTS (SELECT * FROM shoelace_can_delete WHERE sl_name = shoelace.sl_name);</PRE ><P> <I CLASS="FOREIGNPHRASE" >Voilà</I >: </P><PRE CLASS="PROGRAMLISTING" >SELECT * FROM shoelace; sl_name | sl_avail | sl_color | sl_len | sl_unit | sl_len_cm ---------+----------+----------+--------+---------+----------- sl1 | 5 | black | 80 | cm | 80 sl2 | 6 | black | 100 | cm | 100 sl7 | 6 | brown | 60 | cm | 60 sl4 | 8 | black | 40 | inch | 101.6 sl3 | 10 | black | 35 | inch | 88.9 sl8 | 21 | brown | 40 | inch | 101.6 sl10 | 1000 | magenta | 40 | inch | 101.6 sl5 | 4 | brown | 1 | m | 100 sl6 | 20 | brown | 0.9 | m | 90 (9 rows)</PRE ><P> </P ><P > A <TT CLASS="COMMAND" >DELETE</TT > on a view, with a subquery qualification that in total uses 4 nesting/joined views, where one of them itself has a subquery qualification containing a view and where calculated view columns are used, gets rewritten into one single query tree that deletes the requested data from a real table.</P ><P > There are probably only a few situations out in the real world where such a construct is necessary. But it makes you feel comfortable that it works.</P ></DIV ></DIV ><DIV CLASS="NAVFOOTER" ><HR ALIGN="LEFT" WIDTH="100%"><TABLE SUMMARY="Footer navigation table" WIDTH="100%" BORDER="0" CELLPADDING="0" CELLSPACING="0" ><TR ><TD WIDTH="33%" ALIGN="left" VALIGN="top" ><A HREF="rules-views.html" ACCESSKEY="P" >Prev</A ></TD ><TD WIDTH="34%" ALIGN="center" VALIGN="top" ><A HREF="index.html" ACCESSKEY="H" >Home</A ></TD ><TD WIDTH="33%" ALIGN="right" VALIGN="top" ><A HREF="rules-privileges.html" ACCESSKEY="N" >Next</A ></TD ></TR ><TR ><TD WIDTH="33%" ALIGN="left" VALIGN="top" >Views and the Rule System</TD ><TD WIDTH="34%" ALIGN="center" VALIGN="top" ><A HREF="rules.html" ACCESSKEY="U" >Up</A ></TD ><TD WIDTH="33%" ALIGN="right" VALIGN="top" >Rules and Privileges</TD ></TR ></TABLE ></DIV ></BODY ></HTML >