CREATE TABLE product productid SERIAL, productcode VARCHAR8 NOT NULL UNIQUE, name TEXT NOT NULL, price NUMERIC5,2 NOT NULL, description TEXT NOT NULL, PRIMARY KEYproductid ; Creating a P
Trang 1This is just a sample of what’s possible using the information schema You can also use
it to learn more about all databases residing on the cluster, users, user privileges, views, and much more See the PostgreSQL documentation for a complete breakdown of what’s available within this schema
Applying this to PHP, you can execute the appropriate query and parse the results using your pg_fetch_* function of choice For example:
<?php
$tablename = "product";
$query = "SELECT column_name, data_type FROM information_schema.columns
WHERE table_name= '$tablename'";
This returns the following:
product table structure:
productid: integer
productcode: character
description: character varying
name: character varying
price: numeric
Summary
This chapter introduced PHP’s PostgreSQL extension, offering numerous examples strating its capabilities You’ll definitely become quite familiar with many of its functions as your experience building PHP and PostgreSQL-driven applications progresses
demon-The next chapter shows you how to more efficiently manage PostgreSQL queries using a custom database class
Trang 2■ ■ ■
C H A P T E R 3 1
Practical Database Queries
The previous chapter introduced PHP’s PostgreSQL extension and demonstrated basic
queries involving data selection This chapter expands upon this foundational knowledge,
demonstrating numerous concepts that you’re bound to return to repeatedly while creating
database-driven Web applications using the PHP language In particular, you’ll learn how to
implement the following concepts:
• A PostgreSQL database class: Managing your database queries using a class not only
results in cleaner application code, but also enables you to quickly and easily extend and
modify query capabilities as necessary This chapter presents a PostgreSQL database
class implementation and provides several introductory examples so that you can
famil-iarize yourself with its behavior
• Tabular output: Listing query results in an easily readable format is one of the most
commonplace tasks you’ll implement when building database-driven applications This
chapter shows you how to create these listings by using HTML tables, and demonstrates
how to link each result row to a corresponding detailed view
• Sorting tabular output: Often, query results are ordered in a default fashion, by product
name, for example But what if the user would like to reorder the results using some
other criteria, such as price? You’ll learn how to provide table-sorting mechanisms that
let the user search on any column
• Paged results: Database tables often consist of hundreds, even thousands, of results
When large result sets are retrieved, it often makes sense to separate these results across
several pages, and provide the user with a mechanism to navigate back and forth between
these pages This chapter shows you an easy way to do so
The intent of this chapter isn’t to imply that a single, solitary means exists for carrying out
these tasks; rather, the intent is to provide you with some general insight regarding how you
might go about implementing these features If your mind is racing regarding how you can
build upon these ideas after you’ve finished this chapter, the goal of this chapter has been met
Sample Data
The examples found in this chapter use the product table created in the last chapter, reprinted
here for your convenience:
Trang 3CREATE TABLE product (
productid SERIAL,
productcode VARCHAR(8) NOT NULL UNIQUE,
name TEXT NOT NULL,
price NUMERIC(5,2) NOT NULL,
description TEXT NOT NULL,
PRIMARY KEY(productid)
);
Creating a PostgreSQL Database Class
Although we introduced PHP’s PostgreSQL library in Chapter 30, you probably won’t want to reference these functions directly within your scripts Rather, you should encapsulate them within a class, and then use the class to interact with the database Listing 31-1 offers a base functionality that one would expect to find in such a class
Listing 31-1 A PostgreSQL Data Layer Class (pgsql.class.php)
<?php
class pgsql {
private $linkid; // PostgreSQL link identifier
private $host; // PostgreSQL server host
private $user; // PostgreSQL user
private $passwd; // PostgreSQL password
private $db; // PostgreSQL database
private $result; // Query result
private $querycount; // Total queries executed
/* Class constructor Initializes the $host, $user, $passwd
Trang 4catch (Exception $e) {
Trang 5/* Return total number of queries executed during
lifetime of this object Not required, but
Why Use the PostgreSQL Database Class?
If you’re new to object-oriented programming, you may still be unconvinced that you should use the class-oriented approach, and may be thinking about directly embedding the PostgreSQL functions in the application code In hopes of remedying such reservations, this section illus-trates the advantages of the class-based strategy by providing two examples Both examples implement the simple task of querying the company database for a particular product name and price However, the first example (shown next) does so by calling the PostgreSQL functions directly from the application code, whereas the second example uses the PostgreSQL class library shown in Listing 31-1 in the prior section
<?php
/* Connect to the database server /*
$linkid = @pg_pconnect("host=localhost dbname=company user=rob
password=secret") or
die("Could not connect to the PostgreSQL server.");
/* Execute the query */
$result = @pg_query("SELECT name,price FROM product ORDER BY
productid") or die("The database query failed.");
/* Output the results */
Trang 6$pgsqldb->connect();
/* Execute the query */
$pgsqldb->query("SELECT name, price FROM product ORDER BY productid");
/* Output the results */
while ($row = $pgsqldb->fetchObject())
echo "$row->name (\$$row->price)<br />";
?>
Both examples return output similar to the following:
PHP T-Shirt ($12.99)
PostgreSQL Coffee Cup ($4.99)
The following list summarizes the numerous advantages of using the object-oriented
approach over calling the PostgreSQL functions directly from the application code:
• The code is cleaner Intertwining logic, data queries, and error messages results in
• Any changes to the PostgreSQL API can easily be implemented through the class Imagine
having to manually modify PostgreSQL function calls spread throughout 50 application
scripts! This is not just a theoretical argument; PHP’s PostgreSQL API did change as
recently as the PHP 4.2 release, and developers who made extensive use of the direct
PostgreSQL functions were forced to deal with this problem
Executing a Simple Query
Before delving into somewhat more complex topics involving the PostgreSQL database class,
a few introductory examples should be helpful For starters, the following example shows you
how to connect to the database server and retrieve some data using a simple query:
/* Query the database */
$pgsqldb->query("SELECT name, price FROM product
WHERE productcode = 'tshirt01'");
Trang 7/* Retrieve the query result as an object */
$row = $pgsqldb->fetchObject();
/* Output the data */
echo "$row->name (\$$row->price)";
?>
This example returns:
PHP T-Shirt ($12.99)
Retrieving Multiple Rows
Now consider a slightly more involved example The following script retrieves all rows from the product table, ordering by name:
/* Query the database */
$pgsqldb->query("SELECT name, price FROM product ORDER BY name");
/* Output the data */
while ($row = $pgsqldb->fetchObject())
echo "$row->name (\$$row->price)<br />";
is incremented every time a query is executed, allowing you to keep track of the total number
Trang 8of queries executed throughout the lifetime of an object The following example executes two
queries and then outputs the number of queries executed:
// Execute a few queries
$query = "SELECT name, price FROM product ORDER BY name";
$pgsqldb->query($query);
$query2 = "SELECT name, price FROM product
WHERE productcode='tshirt01'";
$pgsqldb->query($query2);
// Output the total number of queries executed
echo "Total number of queries executed: ".$pgsqldb->numQueries();
?>
The example returns the following:
Total number of queries executed: 2
Tabular Output
Viewing retrieved database data in a coherent, user-friendly fashion is key to the success of a
Web application HTML tables have been used for years to satisfy this need for uniformity, for
better or for worse Because this functionality is so commonplace, it makes sense to
encapsu-late this functionality in a function, and call that function whenever database results should be
formatted in this fashion This section demonstrates one way to accomplish this
For reasons of convenience, we’ll create this function in the format of a method and add it
to the PostgreSQL data class To facilitate this method, we also need to add two more helper
methods, one for determining the number of fields in a result set, and another for determining
each field name:
/* Return the number of fields in a result set */
function numberFields() {
$count = @pg_num_fields($this->result);
return $count;
}
Trang 9/* Return a field name given an integer offset */
function fieldName($offset){
$field = @pg_field_name($this->result, $offset);
return $field;
}
We’ll use these methods along with other methods in the PostgreSQL data class to create
an easy and convenient method named getResultAsTable(), used to output table-encapsulated results This method is highly useful for two reasons in particular First, it automatically converts the field names into the table headers Second, it automatically adjusts to a number of fields found in the query It’s a one size fits all solution for formatting of this sort The method
is presented in Listing 31-2
Listing 31-2 The getResultAsTable() Method
function getResultAsTable() {
if ($this->numrows() > 0) {
// Start the table
$resultHTML = "<table border='1'>\n<tr>\n";
// Output the table headers
// Output the table data
while ($row = $this->fetchRow()){
Trang 10include "pgsql.class.php";
$pgsqldb = new pgsql("localhost","company","rob","secret");
$pgsqldb->connect();
// Execute the query
$pgsqldb->query('SELECT name as "Product",
price as "Price",
description as "Description" FROM product");
// Return the result as a table
echo $pgsqldb->getResultAsTable();
?>
Example output is displayed in Figure 31-1
Figure 31-1 Creating table-formatted results
Linking to a Detailed View
Often a user will want to do more with the results than just view them For example, the user
might want to learn more about a particular product found in the result, or he might want to
add a product to his shopping cart An interface that offers such capabilities is presented in
Figure 31-2
Figure 31-2 Offering actionable options in the table output
As it currently stands, the getResultsAsTable() method doesn’t offer the ability to accompany
each row with actionable options This section shows you how to modify the code to provide
this functionality Before diving into the code, however, a few preliminary points are in order
Trang 11For starters, we want to be able to pass in actions of varying purpose and number That said, we’ll pass the actions in as a string to the function However, because the actions will be included at the end of each line, we won’t know what primary key to tack on to each action until the row has been rendered This is essential, because the destination script needs to know which item is targeted Therefore, run-time replacement on a predefined string must occur when the rows are being formatted for output We’ll use the string VALUE for this purpose An example action string follows:
$actions = '<a href="viewdetail.php?productid=VALUE">View Detailed</a> |
<a href="addtocart.php?productid=VALUE">Add to Cart</a>';
Of course, this also implies that an identifying key must be included in the query The product table’s primary key is productid In addition, this key should be placed first in the query, because the script needs to know which field value to serve as a replacement for VALUE Finally, because you probably don’t want the productid to appear in the formatted table, the counters used to output the table header and data need to be incremented by 1
The updated getResultAsTable() method follows For your convenience, those lines that have either changed or are new appear in bold
function getResultAsTable($actions){
if ($this->numrows() > 0) {
// Start the table
$resultHTML = "<table border='1'>\n<tr>\n";
// Output the table header
// Output the table data
while ($row = $this->fetchRow()) {
$resultHTML = "<tr>\n";
for ($i=1; $i < $fieldCount; $i++)
$resultHTML = "<td>".htmlentities($row[$i])."</td>\n";
// Replace VALUE with the correct primary key
$action = str_replace("VALUE", $row[0], $actions);
// Add the action cell to the end of the row
$resultHTML = "<td nowrap> $action</td>\n</tr>\n";
} // end while
Trang 12// Close the table
The process for executing this method is almost identical to the original The key difference is
that this time you have the option of including actions:
<?php
include "pgsql.class.php";
$pgsqldb = new pgsql("localhost","company","rob","secret");
$pgsqldb->connect();
// Query the database
$pgsqldb->query('SELECT productid, name as "Product",
price as "Price" FROM product ORDER BY name');
When displaying output, it makes sense to order the information using criteria that are most
helpful to the user For example, if the user wants to view a list of all products in the product
table, ordering the products in ascending alphabetical order makes sense However, some
users may want to order the information by other criteria, price for example Often, such
mech-anisms are implemented by linking listing headers, such as the table headers used in the previous
examples Clicking any of these links will cause the table data to be sorted using that header as
criterion This section demonstrates this concept, again modifying the most recent version of
the getResultsAsTable() method However, only one line requires modification, specifically:
Trang 13The executing code looks quite similar to that used in previous examples, except now a dynamic SORT clause must be inserted in the query A ternary operator, introduced in Chapter 3,
is used to determine whether the user has clicked one of the header links:
$sort = (isset($_GET['sort'])) ? $_GET['sort'] : "name";
If a sort parameter has been passed via the URL, that value will be the sorting criteria Otherwise, a default of name is used
$pgsqldb->query("SELECT productid, name as Product,
price as Price FROM product ORDER BY $sort ASC");
The complete executing code follows:
<?php
include "pgsql.class.php";
$pgsqldb = new pgsql("localhost","company","rob","secret");
$pgsqldb->connect();
// Determine what kind of sort request has been submitted
// By default this is set to sort name
$sort = (isset($_GET['sort'])) ? $_GET['sort'] : 'name';
// Query the database
$pgsqldb->query("SELECT productid, name as \"Product\",
price as \"Price\" FROM product ORDER BY $sort ASC");
$actions = '<a href="viewdetail.php?productid=VALUE">View Detailed</a> | <a href="addtocart.php?productid=VALUE">Add
To Cart</a>';
echo $pgsqldb->getResultAsTable($actions);
?>
■ Note This example strives for simplicity over security If this were a real application, you would not want
to directly assign the $sort variable to your SQL statement, but instead would do some type of integrity checking on the value passed in, to make sure your users have not tried to send across malicious data Keep this in mind when viewing the following examples as well as when you are writing your own code
Loading the script for the first time results in the output being sorted by name Example output is shown in Figure 31-3
Trang 14Figure 31-3 The product table output sorted by the default name
Clicking the Price header re-sorts the output This sorted output is shown in Figure 31-4
Figure 31-4 The product table output sorted by price
Creating Paged Output
If you’ve perused any e-commerce sites or search engines, you’re familiar with the concept of
separating output into several pages This feature is convenient not only to enhance readability,
but also to further optimize page loading You might be surprised to learn that adding this
feature to your Web site is a trivial affair This section demonstrates how this is accomplished
This feature depends in part on two SQL clauses: LIMIT and OFFSET The LIMIT clause is
used to specify the number of rows returned, and the OFFSET clause specifies a starting point to
begin counting from In general, the format looks like this:
LIMIT number_rows OFFSET starting_point
For example, if you want to limit returned query results to just the first five rows, you could
construct the following query:
SELECT name, price FROM product ORDER BY name ASC LIMIT 5;
If you intend to start from the very first row, this is the same as:
SELECT name, price FROM product ORDER BY name ASC LIMIT 5 OFFSET 0;
However, to start from the sixth row of the result set, you would construct the following
query:
SELECT name, price FROM product ORDER BY name ASC LIMIT 5 OFFSET 5;
Trang 15Because this syntax is so convenient, you need to determine only three variables to create
a mechanism for paging throughout the results:
• Number of entries per page: This is entirely up to you Alternatively, you could easily
offer the user the ability to customize this variable This value is passed into the number_rows component of the LIMIT clause
• Row offset: This value depends on what page is presently loaded This value is passed by
way of the URL so that it can be passed to the OFFSET clause You’ll see how to calculate this value in the following code
• Total number of rows in the result set: This is required knowledge because the value is
used to determine whether the page needs to contain a next link
Interestingly, no modification to the PostgreSQL database class is required Because this concept seems to cause quite a bit of confusion, we’ll review the code first, and then see the example in its entirety in Listing 31-3 The first section is typical of any script using the PostgreSQL data class:
$recordstart is set to 0:
$recordstart = (isset($_GET['recordstart'])) ? $_GET['recordstart'] : 0;
Next, the database query is executed and the data is displayed Note that the record offset
is set to $recordstart, and the number of entries to retrieve is set to $pagesize:
$pgsqldb->query("SELECT name, price FROM product
ORDER BY name LIMIT $pagesize OFFSET $recordset");
// Output the result set
$actions = '<a href="viewdetail.php?productid=VALUE">View Detailed</a> |
<a href="addtocart.php?productid=VALUE">Add To Cart</a>';
echo $pgsqldb->getResultAsTable($actions);
Next, we need to determine the total number of rows available, accomplished by removing the LIMIT and OFFSET clauses from the original query However, to optimize the query, we use the count(*) function rather than retrieve the complete result set:
Trang 16$pgsqldb->query("SELECT count(*) FROM product");
$row = $pgsqldb->fetchObject();
$totalrows = $row->count;
Finally, the previous and next links are created The previous link is created only if the
record offset, $recordstart, is greater than 0 The next link is created only if some records
remain to be retrieved, meaning $recordstart + $pagesize must be less than $totalrows
// Create the 'previous' link
// Create the 'next' link
if ($totalrows > ($recordstart + $pagesize) {
$next = $recordstart + $pagesize;
$url = $_SERVER['PHP_SELF']."?recordstart=$next";
echo "<a href=\"$url\">Next Page</a>";
}
Sample output is shown in Figure 31-5 The complete code listing is presented in Listing 31-3
Figure 31-5 Creating paged results (two results per page)
Listing 31-3 Paging Database Results
// What is our record offset?
$recordstart = (isset($_GET['recordstart'])) ? $_GET['recordstart'] : 0;
// Execute the SELECT query, including the LIMIT and OFFSET clauses
$pgsqldb->query("SELECT productid, name as \"Product\",
price as \"Price\" FROM product
ORDER BY name LIMIT $pagesize OFFSET $recordstart");
Trang 17// Output the result set
$actions = '<a href="viewdetail.php?productid=VALUE">View
Detailed</a> | <a href="addtocart.php?productid=VALUE">Add To
Cart</a>';
echo $pgsqldb->getResultAsTable($actions);
// Determine whether additional rows are available
$pgsqldb->query("SELECT count(*) FROM product");
// Create the 'next' link
if ($totalrows > ($recordstart + $pagesize) {
$next = $recordstart + $pagesize;
$url = $_SERVER['PHP_SELF']."?recordstart=$next";
echo "<a href=\"$url\">Next Page</a>";
}
?>
Listing Page Numbers
If you have several pages of results, the user might wish to traverse them in nonlinear order For example, the user might choose to jump from page one to page three, then page six, then back
to page one again Thankfully, providing users with a linked list of page numbers is surprisingly easy Building on Listing 31-3, you start by determining the total number of pages, and assigning that value to $totalpages You determine the total number of pages by dividing the total result rows by the chosen page size, and round upwards using the ceil() function:
$totalpages = ceil($totalrows / $pagesize);
Next, you determine the current page number, and assign it to $currentpage You determine the current page number by dividing the present record offset ($recordstart) by the chosen page size ($pagesize), and adding one to account for the fact that LIMIT offsets start with 0:
$currentpage = ($recordstart / $pagesize ) +1;
Next, create a function titled pageLinks() and pass to it the following four parameters:
• $totalpages: The total number of pages, stored in the $totalpages variable
• $currentpage: The current page, stored in the $currentpage variable
Trang 18• $pagesize: The chosen page size, stored in the $pagesize variable.
• $parameter: The name of the parameter used to pass the record offset by way of the URL
Thus far, $recordstart has been used, so we’ll stick with that in the following example
The pageLinks() function follows:
function pageLinks($totalpages, $currentpage, $pagesize, $parameter) {
// Start at page one
while ($page <= $totalpages) {
// Link the page if it isn't the current one
Sample output of the page listing, combined with other components introduced throughout
this chapter, is shown in Figure 31-6
Trang 19Figure 31-6 Generating a numbered list of page results
Summary
This chapter offered insight into some of the most common general tasks you’ll encounter when developing data-driven applications The chapter started by providing a PostgreSQL data class and offering some basic usage examples involving this class Next, you learned a convenient and easy method for outputting data results in a tabular format, and then learned how to add actionable options for each output data row Building upon this material, you saw how to sort output based on a given table field Finally, you learned how to spread query results across several pages and create linked page listings, enabling the user to navigate the results in
a nonlinear fashion
The next chapter introduces PostgreSQL’s implementation of views and rules, which help you to implement and maintain your full data model
Trang 20■ ■ ■
C H A P T E R 3 2
Views and Rules
In Chapter 28 we looked at the basic objects within PostgreSQL that you can use to help design
your project’s data model While schemas, tables, and other items such as domains are very
helpful, they by no means comprise a complete list of the tools at your disposal In this chapter,
we look at PostgreSQL’s support of a more formal object in relational theory, the view, and also
introduce you to PostgreSQL’s powerful rule system By the end of the chapter, we will have
covered the following:
• How to create and manipulate views within PostgreSQL
• PostgreSQL’s rule system, including what types of commands can be used from within
the rule system
• Updateable views and how you can use PostgreSQL’s rule system to implement powerful
versions of this classic relational concept
Working with Views
When working on a large data model, you frequently have to use complex queries to retrieve
information from several joined tables, often with a long list of WHERE conditionals Duplicating
these complex queries in different parts of your application code often can be troublesome,
especially if your database has multiple interfaces to it One minor variation between these
interfaces can lead to trouble when the end results don’t match up
What would be handy here is a way to name the complex query so that it could be stored
in the database, and accessed in a uniform manner by outside applications This is where views
come in A view is defined in PostgreSQL as a stored representation of a given query Once defined,
in many respects a view can be thought of as a virtual table While a view holds no data itself, it
can be queried just like any other table, and you can even create views based on other views
Creating a View
You create a view by using the CREATE VIEW statement When using the CREATE VIEW statement,
you specify both a name for the view and an SQL query that defines the structure of the view:
CREATE VIEW database_books AS SELECT * FROM books WHERE subject = 'PostgreSQL';
Trang 21This would create a view called database_books that would have an equivalent structure to the books table, as far as column names and their types are concerned In older versions of PostgreSQL (7.2 and older), if you needed to change the definition of a view, you had to first drop the view and then re-create it However, in current versions, we can take advantage of the CREATE OR REPLACE VIEW command:
CREATE OR REPLACE VIEW database_books AS SELECT * FROM books
WHERE subject = 'PostgreSQL' OR subject = 'Sqlite';
■ Note The CREATE OR REPLACE VIEW command will work only if you do not change the layout of the
column names or their types If your query produces a different column layout, you will need to drop and then re-create the view
You can create views by using very complex SQL statements, and you do not need to limit the view to columns from existing tables; derived values and constants are also acceptable, as shown in this example:
CREATE VIEW featured_technical_books AS SELECT 'Best Sellers',title,
(copies_sold/months_in_print) AS average_sales FROM books WHERE
current_stock >= 1000 AND genre = 'technical';
Dropping a View
Dropping a view is accomplished by using the DROP VIEW command This command takes an optional keyword of RESTRICT or CASCADE, to determine what behavior to use when dealing with dependent objects, such as different views or functions that query on the view being dropped The RESTRICT keyword prevents the view from being dropped if it has dependent objects The CASCADE keyword, used in the following example, drops the dependent objects:
DROP VIEW database_books CASCADE;
The PostgreSQL Rule System
Many databases use active rules within the database, based on some combination of functions and triggers, that they use to enforce things like data constraints and foreign keys PostgreSQL also offers those features, but it also offers an alternative system for rewriting queries on the fly Using the rule system, you can do such things as enforce data integrity, create read-only tables, and make views interactive These “query-rewrite” rules can be broken down into one of four types; SELECT, INSERT, UPDATE, and DELETE In this section, we look at the syntax for creating rules and introduce the four different types of rules
Working with Rules
This section presents the basic syntax used to create and drop rules
Trang 22Creating a Rule
You can create a rule by using the CREATE RULE command, the complete syntax for which
follows:
CREATE [ OR REPLACE ] RULE rule_name AS ON event_type
TO object_name [ WHERE conditional ] DO [ ALSO | INSTEAD ] COMMAND
The following list describes the various parts of this syntax:
• CREATE [ OR REPLACE ] RULE rule_name: Specifies the name of the rule, and takes an
optional OR REPLACE clause, which tells PostgreSQL to replace an existing rule with the
same name on the same table or view the rule is being created on
• AS ON event_type: Determines what type of event the rule will be carried out on The
event_type can be one of SELECT, INSERT, UPDATE, or DELETE, each of which is described in
more detail in the upcoming “Rule Types” section
• TO object_name [ WHERE conditional ]: Specifies the name of the table or view that the
rule applies to An optional conditional can be specified if you want the rule to be carried
out only in certain cases Any SQL conditional expression can be used provided that it
returns boolean, does not contain aggregate functions, and references no other tables or
views except, optionally, the NEW and OLD pseudo-relations, if appropriate
• DO [ ALSO | INSTEAD ]: Describes whether the rule should be applied in addition to the
action in the original SQL statement against the table, or if the rule should be applied in
place of the original SQL statement If neither ALSO nor INSTEAD is specified, ALSO is used
as the default
• COMMAND: Specifies the desired query to be run for the rule Commands take the form of
SELECT, INSERT, UPDATE, DELETE, or NOTIFY statements SQL queries used in the COMMAND
section of a rule can access the NEW and OLD pseudo-relations, as appropriate The COMMAND
section can also use the NOTHING keyword, if you do not want any action to be executed
for the rule
■ Tip The action specified in the COMMAND section does not have to match that specified in the event_type For
example, you can create a rule that will insert into another table every time someone attempts to update a view
Removing a Rule
To remove a rule, you use the DROP RULE command Compared to the CREATE RULE command,
the syntax is much simpler:
DROP RULE rule_name ON object_name [ CASCADE | RESTRICT]
The key elements to the DROP RULE command are the name of the rule, the name of the
view or table that the rule applies to, and, optionally, either the CASCADE or RESTRICT keyword If
CASCADE is chosen, then all objects dependent on the rule will be dropped; if RESTRICT is specified,
then PostgreSQL will refuse to drop the rule if it has any dependent objects; the default is RESTRICT
Trang 23CREATE VIEW ourbooks AS SELECT * FROM books;
and
CREATE TABLE ourbooks AS SELECT * FROM books;
CREATE RULE "_RETURN" AS ON SELECT TO ourbooks DO INSTEAD SELECT * FROM books;The use of the name "_RETURN" is required by PostgreSQL for ON SELECT rules to help signal
to the internal query rewriter that the relation being queried is a view Views within PostgreSQL use select rules automatically to handle select calls and retrieve data from their base tables, but
in most cases, you will not need to work with select rules directly
Insert Rules
The next type of rule used within PostgreSQL is the insert rule Insert rules can have an action that is either ALSO or INSTEAD, and can have multiple actions or no action, as desired The actions defined in an insert rule can contain conditionals and can also make use of the NEW pseudo-relation An insert rule’s syntax looks like this:
CREATE RULE database_book_insert AS ON INSERT TO database_books DO INSTEAD INSERT INTO books (title, copies_on_hand, genre) VALUES (NEW.title, 1, 'technical'); With this rule in place, any insert directed at the database_books view would instead insert the specified values into the original books table
Update Rules
The next type of rule is the update rule Like insert rules, update rules can have an action that
is either ALSO or INSTEAD, and can have multiple actions or no actions, as desired The actions defined in an update rule can contain conditionals and can make use of both the NEW and OLD pseudo-relations An example update rule might look like this:
CREATE RULE database_books_update AS ON UPDATE TO database_books WHERE
NEW.title <> OLD.title DO INSTEAD UPDATE books SET title = NEW.title;
With this rule, updates directed at our database_books view, where the title had been changed, would instead update the original books table with the new title
Trang 24Delete Rules
The last type of rule is the delete rule It can have an action of either ALSO or INSTEAD, and can
have multiple actions or no actions, as desired The actions defined in a delete rule can contain
conditionals and can make use of the OLD pseudo-relation An example delete rule might look
like this:
CREATE RULE database_books_delete AS ON DELETE TO database_books
DO INSTEAD NOTHING;
Here, we use the NOTHING keyword to prevent any deletions from taking place if someone
attempts to delete from the database_books view This has two effects: We prevent those who
have access on the database_books view from deleting from our main books table, and we allow
DELETE statements against the database_books table to be executed without error Do not
dismiss this second effect too quickly In most normal cases, deleting from a view (and inserting
and updating as well) would raise an error, but with the use of rules, we can handle these errors
if our application calls for it
Making Views Interactive
Many databases these days offer some form of updateable views, allowing you to run UPDATE
statements against a view and have them update the underlying table However, you’ll often
find that there are heavy restrictions placed on the allowed definitions of this type of view, such
as not allowing joined queries or not allowing special formatting of entries in the view definition
PostgreSQL, by way of its rule system, allows you to bypass these restrictions, giving you much
more flexibility in the types of updatable views you can create The next section walks you
through several examples of putting PostgreSQL’s rule system to use
Updatable, Insertable, Deletable Views
The first part of our example creates a simple set of tables that we will be working with:
CREATE TABLE employee (
employee_id INTEGER PRIMARY KEY,
fname TEXT NOT NULL,
lname TEXT NOT NULL
);
CREATE TABLE phone (
employee_id INTEGER REFERENCES employee (employee_id) ON DELETE CASCADE,
npa INTEGER NOT NULL,
nxx INTEGER NOT NULL,
xxxx INTEGER NOT NULL);
This sets up two tables, one for holding our employee names and one for holding their
phone information Of course, while using column names like npa, nxx, and xxxx may follow
the official format of the North American Numbering Plan, they are a little unwieldy to work
with, so let’s go ahead and make our view:
Trang 25CREATE VIEW directory AS (SELECT employee.employee_id,
fname || ' ' || lname AS name,
npa || '-' || nxx || '-' || xxxx AS number
FROM employee JOIN phone USING (employee_id));
This creates a three-column view: one for the employee ID, one for the employee’s full name, and one for the employee’s phone number, formatted a little bit nicer Now that we have the structure, let’s go ahead and add some data:
INSERT INTO employee(employee_id,fname,lname) VALUES
(1,'Amber','Lee');
INSERT INTO phone(employee_id, npa, nxx, xxxx) VALUES
(1,607,555,5210);
And take a look at it through our view:
rob=# SELECT * FROM directory;
employee_id | name | number
1 | Amber Lee | 607-555-5210
(1 row)
This looks nice enough, but suppose we want to add someone new into our directory To
do this, we have to be aware of the underlying tables and insert into both tables:
INSERT INTO employee(employee_id, fname, lname)
CREATE RULE directory_addition AS ON INSERT TO directory DO INSTEAD
Trang 26Since the directory combines the data from the base tables’ columns into single columns,
we must split up this data to insert it back into the underlying tables; in this case, we use the
built-in database function split_part (added in PostgreSQL 7.3), which splits text strings
based on a given delimiter That is an important thing to be aware of; as long as you can derive
a way to deconstruct the formula used in a view, you can push data back into the base table
with the rule system With this rule in place, we can now insert directly into our view:
rob=# INSERT INTO directory VALUES (3,'Emma Jane','352-555-6120');
INSERT 107999 1
The INSERT indicates that our INSERT statement succeeded, but we know that the data did
not go into our directory view Let’s take a look at our base tables:
rob=# SELECT * FROM employee;
employee_id | fname | lname
It worked! We have inserted data into our view, and the rule split up the data and inserted
it into the proper tables as needed Of course, once you can insert into a view, you surely want
to delete from it, and we can use a rule to make this work as well:
CREATE RULE youre_fired AS ON DELETE TO directory DO INSTEAD
DELETE FROM employee WHERE fname=split_part(OLD.name,' ', 1) AND
lname=split_part(OLD.name,' ', 2);
Rules on joined tables do not have to reference all the tables in the view if you don’t want
them to Notice that in this rule, we only reference the employee table, which works fine for our
example because the entries in the phone table will be removed due to the cascading reference
that we defined in our original table Let’s fire an employee:
rob=# DELETE FROM directory WHERE name = 'Amber Lee';
DELETE 1
Trang 27Again, the database indicates that our delete was successful, so let’s take a look at the base tables to verify our data:
rob=# SELECT * FROM employee;
employee_id | fname | lname
appro-be able to update the data that we have as well For this example, we will create a rule that allows someone to modify the phone number information, but not the name information:CREATE RULE modify_employee AS
ON UPDATE TO directory DO INSTEAD
UPDATE phone SET
npa=split_part(NEW.number,'-', 1)::INTEGER,
nxx=split_part(NEW.number,'-', 2)::INTEGER,
xxxx=split_part(NEW.number,'-', 3)::INTEGER
WHERE employee_id = NEW.employee_id;
This rule combines a number of items that we have talked about already It interacts with only one of the base tables in a join, it reverses a complex formula used in the view definition, and it converts the data type on the fly to properly match what was defined in our base table Now we’ll update the directory:
rob=# UPDATE directory SET number='352-555-7120'
Trang 28As you can see, the new number is now stored in the phone table Since we did not need to
update the employee table, let’s take a look at our view again to see if the changes are reflected:
rob=# SELECT * FROM directory;
employee_id | name | number
2 | Dylan Jairus | 813-555-5040
3 | Emma Jane | 352-555-7120
(2 rows)
Of course, the rule system can be used for more than just making interactive views: You
can also use it on tables, and you can do more than just directly reference tables For the next
example, we create a new table called salary and insert some information into the table:
CREATE TABLE salary (employee_id INTEGER REFERENCES
employee(employee_id) ON DELETE CASCADE, salary INTEGER);
INSERT INTO salary VALUES (2,400000);
INSERT INTO salary VALUES (3,200000);
The first thing we decide is that we want to prevent anyone from deleting an employee’s
salary Normally this would be accomplished by using the REVOKE DELETE command, but
REVOKE DELETE will not prevent superusers from deleting data accidentally, so we want to go
the extra step:
CREATE RULE always_pay AS ON DELETE TO salary DO INSTEAD NOTHING;
This is the INSTEAD form of a rule, and it causes the query to be rewritten so as not to be
executed at all Now, even if a superuser tries to delete from the table, deletes will be prevented:
rob=# DELETE FROM salary WHERE employee_id = 2;
DELETE 0
Another thing we might need is a log of any changes to an employee’s salary that might
take place We accomplish this through the combination of a logging table and an update rule:
CREATE TABLE salary_log (employee_id INTEGER REFERENCES
employee(employee_id) ON DELETE CASCADE, salary_change INTEGER,
changed_by TEXT, log_time TIMESTAMP DEFAULT now());
CREATE RULE log_salary_changes AS ON UPDATE TO salary DO ALSO INSERT
INTO salary_log VALUES (NEW.employee_id, NEW.salary - OLD.salary,
CURRENT_USER);
Trang 29Notice that this rule is of the DO ALSO variety, meaning that the original query will be executed along with the actions specified in the rule It uses data from the original query, a mathematical operation, and the internal CURRENT_USER function, which produces the current user logged into the database:
rob=# UPDATE salary SET salary = 250000 WHERE employee_id = 3;
UPDATE 1
rob=# SELECT * FROM salary_log;
employee_id | salary_change | changed_by | log_time
rob=# UPDATE salary SET salary = (salary – salary*.15) ;
UPDATE 2
rob=# select * from salary_log;
employee_id | salary_change | changed_by | log_time
Working with Views from Within PHP
Although views have some different characteristics from tables at the database level, within PHP, querying from a view is no different than querying from a table Listing 32-1 shows a simple PHP page that queries from our directory view and displays the results onscreen You’ll notice that it is very similar to the examples used in Chapter 31 when we were querying against tables
Trang 30Listing 32-1 Querying a View with PHP
// Query the database
$pgsqldb->query("SELECT name, number FROM directory
ORDER BY name");
// Output the data
while ($row = $pgsqldb->fetchObject())
echo "$row->name, $row->number<br />";
?>
When executed in a browser, this code will return the following results:
Dylan Jairus, 813-555-5040
Emma Jane, 352-555-7120
You can see that querying against a view is no different from querying against a table to
either the PHP application developer or the end user For this reason, as a rule of thumb, you
should treat views and tables as if there is no difference when building your applications This
is especially true if you use PostgreSQL rules to make your views fully interactive
Summary
In this chapter we took a good look at how a few of PostgreSQL’s advanced features can be used
to make powerful, interactive relationships within the database We first looked at views, including
how they work within the database and the commands to add and delete them We next explained
the PostgreSQL rule system, by discussing the different types of rules and giving a basic
over-view of how those rules can be defined We concluded with some examples of how you can
combine views, rules, and tables within PostgreSQL to make highly interactive database
schemas The examples were kept simple, but should give you some good ideas on how you
could take advantage of these powerful features
Trang 32■ ■ ■
C H A P T E R 3 3
PostgreSQL Functions
While PostgreSQL has long been known for its support of custom function languages, many
don’t take advantage of its extensive array of built-in functions and its large number of built-in
operators In this chapter, we take a look at both operators and functions, and spend some
time looking at custom functions as well By the end of the chapter, you will be familiar with
the following:
• What an operator is and the most commonly used operators in PostgreSQL
• The different types of internal PostgreSQL functions and the most common examples of
each type
• PostgreSQL’s internal procedural languages, including how to write your own functions
in these languages
• How to extend PostgreSQL with additional custom procedural languages
You should be aware that our objective here is not to offer a comprehensive resource for
every operator and function inside PostgreSQL—quite frankly, there are just far too many of
these available, and many are used only in very narrow fields Instead, we’ll focus on the most
common and useful of the group, so that you’ll have a strong foundation to build from once
you start developing PostgreSQL-based applications
Operators
PostgreSQL provides a large number of built-in operators (at last count, more than 600!) for
doing various comparisons and data conversions In this section, we examine the most commonly
used operators, many of which will probably already be familiar to you
Trang 33One often misunderstood aspect of the AND and OR logical operators is how they interact with NULL values SQL uses a tristate Boolean effect, where a NULL value represents an “unknown” value Table 33-1 breaks down the effects of various logical operators.
Table 33-1 Logical Operators
foo bar foo AND bar foo OR bar
TRUE TRUE TRUE TRUE
TRUE FALSE FALSE TRUE
TRUE NULL NULL TRUE
NULL NULL NULL NULL
FALSE NULL FALSE NULL
FALSE FALSE FALSE FALSE
Table 33-2 Common Comparison Operators
< 12 < 21 Less than
<= 12 <= 21 Less than or equal
> 21 > 12 Greater than
>= 21 >= 12 Greater than or equal
<>, != 21 <> 12 Does not equal
BETWEEN 12 BETWEEN 9 and 21 Construct for 21 >= 12 AND 12 <= 9
NOT BETWEEN 21 NOT BETWEEN 12 and 9 Construct for 21 < 12 OR 21 > 9
Trang 34Mathematical Operators
Mathematical operators in PostgreSQL match those found in most programming languages
The most common mathematical operators are listed in Table 33-4
String Operators
String operators are generally either used for string manipulation or string matching within
PostgreSQL The most common are listed in Table 33-5
Table 33-3 Common Comparison Operator Constructs
Comparison Operator Constructs
expression IS NOT FALSE
Table 33-4 Common Mathematical Operators
Operator Example Explanation
Table 33-5 Common String Operators
Operator Example Explanation
|| 'foo' || 'bar' is 'foobar' String concatenation
~ 'foobar' ~ 'oo.*r' Regular expression matching ^ and $ anchor searches
to the start and end of strings, respectively
~* 'foobar' ~* 'OO.*R' Case-insensitive regular expression
!~ 'foobar !~ 'rr.*o' Does not match regular expression
!~* 'foobar' !~* 'RR.*O' Does not match case-insensitive regular expression
~~ 'foobar' ~~ '%oo%' Synonym for LIKE
!~~ 'foobar' !~~ %RR% Synonym for NOT LIKE
Trang 35Operator Precedence
Working with operators in PostgreSQL is very similar to working with operators within any programming language The operators all have a precedence that determines the order in which the operators should be handled, and that precedence can be changed using parentheses
to group certain operations To illustrate what we mean, take a look at the following two queries:phppg=# SELECT 3*2+1;
One such quirk is that the PostgreSQL operators also have an associative quality with the values to either the left or right of the operator, which determines in what order operators having the same precedence will be processed For example, arithmetic operators for addition and subtraction are left associative, so an expression such as 3 – 2 + 1 is evaluated as (3 – 2) + 1 However, the equality operator is right associative, so a = b = c is evaluated as a = (b = c).Table 33-6 lists the operator precedence in decreasing order, along with the associativity
of the operators Always remember, though, that the use of parentheses can help change and clarify operator precedence, should you need to work with complex operator combinations
Table 33-6 Operator Precedence in PostgreSQL
Operator Associativity Explanation
Left Schema/table/column name separator
:: Left PostgreSQL-specific typecast
- Right Integer negation (unary minus)
* / % Left Multiplication, division, modulo
+ - Left Addition, subtraction
Trang 36Internal Functions
As with the number of operators, the number of built-in PostgreSQL functions is staggering
In this section, we’ll cover some of the most common and useful built-in functions you’re likely
to encounter We can break down these functions into the following groups:
• Functions for handling date and time values
• Functions for working with string values
• Functions for formatting string and time output
• Aggregate functions
• Various conditional expressions
• Subquery expressions
Most built-in PostgreSQL functions can be called in either the SELECT or the WHERE part of a
query, depending on your needs
Date and Time Functions
PostgreSQL provides a number of date- and time-related functions For functions that can take
a time or timestamp argument, times and timestamps with or without a time zone are acceptable
Table 33-7 shows some common date and time functions
IS Test if TRUE, FALSE, UNKNOWN, or NULL
(All others) Left All other built-in and user-defined operators
BETWEEN Test if contained within a range
OVERLAPS Test for time interval overlapping
LIKE ILIKE SIMILAR Test for string pattern matching
> < Greater than, less than
= Right Test for equality
NOT Right Logical negation
AND Left Logical conjunction
OR Left Logical disjunction
Table 33-6 Operator Precedence in PostgreSQL
Operator Associativity Explanation
Trang 37Like most other database systems, PostgreSQL provides a number of functions that allow you
to do counting, averages, and other aggregate operations Some of the more common aggregate functions are listed in Table 33-9
Table 33-7 Common Date- and Time-Related Functions
current_date Returns today’s date
current_time Returns the current time (no date information returned).current_timestamp Returns a timestamp (date and time) of the current time date_part(text,timestamp) Returns a field specified in the text of a given timestamp.now() Returns a timestamp of the current time, frozen at the
lower(string) Return the string in lowercase
position(substring in string) Return the integer position of a substring within
the string
split_part(string,delimiter,field) Split the string using a delimiter and return a
specified field
substring(string, from,[for]) Extract a substring from the string starting at from
for specified digits
replace(string,from,to) Replace from text with to text in a given string.upper(string) Return the string in uppercase
Trang 38When using aggregate functions in PostgreSQL, be aware that PostgreSQL often requires a
full table scan on a table to satisfy the aggregate function, especially in cases where no WHERE
clause is specified in the query, rather than making use of an index scan The main reason for
this is that PostgreSQL stores tuple visibility information within the table, not the index, so it
must look in the table to determine which tuples are relevant to the current transaction (Imagine
trying to provide an accurate count(*) to two concurrent queries, one of which has done a large
delete, and the other a large number of inserts.) This can lead to poor performance if you are
not careful This problem has been solved in version 8.1 of PostgreSQL for the min() and max()
functions, where PostgreSQL will now attempt to make use of available indexes to determine
this information; hopefully we will see other aggregate functions improved in the future
Conditional Expressions
Conditional expressions are more constructs than functions, but they operate in much the same
manner as functions and can be quite useful when working with complex SQL There are three
main conditional expressions within PostgreSQL: CASE, COALESCE, and NULLIF
CASE
The CASE function returns one of several specified values based on one of several matching
conditionals The syntax for a CASE expression is as follows:
CASE
WHEN condition THEN result
[WHEN condition THEN result]
[ELSE result]
END
A CASE expression can have as many WHEN conditions as desired, but it only returns the result
of the first condition to evaluate to true If no WHEN conditions evaluate to true, then the ELSE
result is return if it has been specified; otherwise, NULL is returned Consider the following example:
Table 33-9 Common Aggregate Functions
Function Explanation
avg(expression) The average of all input values Input values must be one of the
integer types
count(*) The number of input values
count(expression) The number of non-null input values
max(expression) The maximum value of expression for all input values
min(expression) The minimum values of expression for all input values
sum(expression) The sum of expression for all non-null input values
Trang 39company=# SELECT name, price, CASE WHEN price < 10.00 THEN 'Hot Deal'
company-# ELSE 'Exceptional Value' END as offer FROM product;
name | price | offer
Linux Hat | 8.99 | Hot Deal
PostgreSQL Hat | 3.99 | Hot Deal
PHP Hat | 16.99 | Exceptional Value
As with CASE, COALESCE can have as many values as desired If no non-null values are found
in the list, COALESCE will return NULL To keep things simple, we’ll use a fairly direct example here:
company=# SELECT name, COALESCE(NULL,price) as price from product;
do not match The syntax for NULLIF is as follows:
NULLIF(VALUE1, VALUE2)
We can view NULLIF in action as:
company=# select NULLIF(1,2) as different, NULLIF(1,1) as same;
different | same
1 |
(1 row)
Trang 40More Functions
As we mentioned at the beginning of the chapter, the built-in functions and operators presented
here are not a complete list, but instead represent a brief look at the most common items
that you might encounter PostgreSQL also provides more-specialized functions, such as
geometric functions and network address functions You will find that PostgreSQL additionally
offers functions that are exact equivalents to the built-in operators, as well as functions for
converting data between the various data types For a deeper look at built-in functions, you
should check out the online documentation at http://www.postgresql.org/docs/
User-Defined Functions
While PostgreSQL offers a large number of built-in functions, there are still times when these
options will not be quite right To solve this problem, PostgreSQL provides an extremely
powerful set of tools to allow developers to write user-defined functions
Actually, PostgreSQL goes one step further and allows users to write their own custom
procedural language This has led to the creation of more than a dozen different procedural
languages, some included within PostgreSQL and some available externally In this section,
we’ll take a look at some of the basic aspects of PostgreSQL’s user-defined functions including
the following:
• How to write basic functions using SQL
• How to create more advanced functions with the PL/pgSQL language
• Where to find more information about external procedural language packages
Create Function Syntax
Before we get into the specifics of user-defined functions, let’s take a look at the syntax for
creating user-defined functions All functions are created using this same syntax
CREATE [ OR REPLACE ] FUNCTION name ( [ [ argmode ] [ argname ]
argtype [, ] ] )
[ RETURNS returntype ]
{ AS 'definition'
| LANGUAGE langname
| IMMUTABLE | STABLE | VOLATILE
| CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT
| [ EXTERNAL ] SECURITY INVOKER | [ EXTERNAL ] SECURITY DEFINER
}
[ WITH ( attribute [, ] ) ]
Calling the command as CREATE FUNCTION will create a new function as long as another
function with the same name and arguments does not exist Using the CREATE OR REPLACE version
of the function will either create a new function or overwrite an existing definition The next
piece of the command specifies a name and an optional number of optionally named arguments
representing different data types Next, the return type is specified, which will define the data