Tải bản đầy đủ - 0 (trang)
Chapter 9. Obtaining and Using Metadata

Chapter 9. Obtaining and Using Metadata

Tải bản đầy đủ - 0trang

9.1 Introduction

Most of the queries used so far have been written to work with the data stored in the

database. That is, after all, what the database is designed to hold. But sometimes you need

more than just data values. You need information that characterizes or describes those

values—that is, the query metadata. Metadata information is used most often in relation to

processing result sets, but also is available for other aspects of your interaction with MySQL.

This chapter describes how to obtain and use the following types of metadata:







Information about the result of queries.

When you delete or update a set of rows, you can determine the number of rows that

were changed. For a SELECT query, you can find out the number of columns in the

result set, as well as information about each column in the result set, such as the

column name and its display width. Such information often is essential for processing

the results. For example, if you're formatting a tabular display, you can determine how

wide to make each column and whether to justify values to the left or right.







Information about tables and databases.

Information pertaining to the structure of tables and databases is useful for

applications that need to enumerate a list of tables in a database or databases hosted

on a server (for example, to present a display allowing the user to select one of the

available choices). You can also use this information to determine whether tables or

databases exist. Another use for table metadata is to determine the legal values for



ENUM or SET columns.





Information about the MySQL server.

Some APIs provide information about the database server or about the status of your

current connection with the server. Knowing the server version can be useful for

determining whether it supports a given feature, which helps you build adaptive

applications. Information about the connection includes such items as the current user

and the current database.



Some APIs try to provide a database-independent interface for types of metadata that tend to

be available across a variety of database engines (such as the names of the columns in a

result set). But in general, metadata information is closely tied to the structure of the

database system, so it tends to be somewhat database-dependent. This means that if you port

an application that uses recipes in this chapter to other databases, it may need some

modification. For example, lists of tables and databases in MySQL are available by issuing



SHOW statements. However, SHOW is a MySQL-specific extension to SQL, so even if you're

using an API like DBI, DB-API, or JDBC that gives you a database-independent way of issuing

queries, the SQL itself is database-specific and will need to be changed to work with other

engines.



The scripts containing the code for the examples shown here are in the metadata directory of

the recipes distribution. (Some of them use utility functions located in the lib directory.) To

create any tables that you need for trying the examples, look in the tables directory.

In several cases, recipes developed here construct queries using a database, table, or column

name that is stored in a variable. For simplicity, generally such names are inserted as is into

the query string. For example:



$query = "SHOW COLUMNS FROM $tbl_name";

This works properly in the majority of cases, but there are some possible complications you

should know about, and may wish to take into account when adapting these recipes for your

own use. As of MySQL 3.23.6, names are allowed to contain almost any character, such as

spaces. If you anticipate a need to deal with such names, surround the name with backticks:



$query = "SHOW COLUMNS FROM `$tbl_name`";

If the server is running in ANSI mode, name quoting should be done with double quotes

instead:



$query = "SHOW COLUMNS FROM \"$tbl_name\"";

To deal with these issues on a general basis, you can query the server to see if it is Version

3.23.6 or later (see Recipe 9.14), and you can also use SHOW VARIABLES to see if it is

running in ANSI mode. The recipes here do not perform all these checks, because doing so

would obscure their main point.



9.2 Obtaining the Number of Rows Affected by a Query

9.2.1 Problem

You want to know how many rows were changed by a query.



9.2.2 Solution

Some APIs provide the count as the return value of the function that issues the query. Others

have a separate function that you call after issuing the query.



9.2.3 Discussion

For queries that affect rows (UPDATE, DELETE, INSERT, REPLACE), each API provides a way

to determine the number of rows involved. For MySQL, "affected by" normally means

"changed by," so rows that are not changed by a query are not counted, even if they match

the conditions specified in the query. For example, the following UPDATE statement would

result in an "affected by" value of zero because it does not change any columns from their

current values, no matter how many rows the WHERE clause matches:



UPDATE limbs SET arms = 0 WHERE arms = 0;

9.2.4 Perl

In DBI scripts, the affected-rows count is returned by do( ) or by execute( ), depending

on how you execute the query:



# execute $query using do( )

my $count = $dbh->do ($query);

# report 0 rows if an error occurred

printf "%d rows were affected\n", (defined ($count) ? $count : 0);

# execute query using prepare( ) plus execute( )

my $sth = $dbh->prepare ($query);

my $count = $sth->execute ( );

printf "%d rows were affected\n", (defined ($count) ? $count : 0);

When you use DBI, you have the option of asking MySQL to return the "matched by" value

rather than the "affected by" value. To do this, specify mysql_client_found_rows=1 in the

options part of the data source name argument of the connect( ) call when you connect to

the MySQL server. Here's an example:



my $dsn =

"DBI:mysql:cookbook:localhost;mysql_client_found_rows=1";

my $dbh = DBI->connect ($dsn, "cbuser", "cbpass",

{ PrintError => 0, RaiseError => 1 });



mysql_client_found_rows changes the row-reporting behavior for the duration of the

connection.



9.2.5 PHP

In PHP, invoke the mysql_affected_rows( ) function to find out how many rows a query

changed:



$result_id = mysql_query ($query, $conn_id);

# report 0 rows if the query failed

$count = ($result_id ? mysql_affected_rows ($conn_id) : 0);

print ("$count rows were affected\n");

The argument to mysql_affected_rows( ) is a connection identifier. If you omit the

argument, the current connection is used.



9.2.6 Python

Python's DB-API makes the row count available as the value of the query cursor's rowcount

attribute:



cursor = conn.cursor ( )

cursor.execute (query)



print "%d rows were affected" % cursor.rowcount

9.2.7 Java

The Java JDBC interface provides row counts two different ways, depending on the method

you invoke to execute the query. If you use executeUpdate( ), it returns the row count

directly:



Statement s = conn.createStatement ( );

int count = s.executeUpdate (query);

s.close ( );

System.out.println (count + " rows were affected");

If you use execute( ), that method returns true or false to indicate whether or not the

statement produces a result set. For statements such as UPDATE or DELETE that return no

result set, the row count is available by calling the getUpdateCount( ) method:



Statement s = conn.createStatement ( );

if (!s.execute (query))

{

// there is no result set, print the row count

System.out.println (s.getUpdateCount ( ) + " rows were affected");

}

s.close ( );

For statements that modify rows, the MySQL Connector/J JDBC driver provides a rowsmatched value for the row count, rather than a rows-affected value.



9.3 Obtaining Result Set Metadata

9.3.1 Problem

You know how to retrieve the rows of a result set, but you want to know things about the

result, such as the column names and data types, or the number of rows and columns there

are.



9.3.2 Solution

Use the appropriate capabilities provided by your API.



9.3.3 Discussion

For queries that generate a result set, you can get a number of kinds of metadata. This

section discusses the information provided by each API, using programs that show how to

display the result set metadata available after issuing a sample query (SELECT name, foods



FROM profile). The section also discusses some applications for this information. One of the

simplest uses is illustrated by several of the example programs: When you retrieve a row of

values from a result set and you want to process them in a loop, the column count stored in

the metadata serves as the upper bound on the loop iterator.



9.3.4 Perl

Using the DBI interface, you can obtain result sets two ways. These differ in the scope of

result set metadata available to your scripts:







Process the query using a statement handle.

In this case, you invoke prepare( ) to get the statement handle, then call its



execute( ) method to generate the result set, then fetch the rows in a loop. With

this approach, access to the metadata is available while the result set is active—that

is, after the call to execute( ) and until the end of the result set is reached. When

the row-fetching method finds that there are no more rows, it invokes finish( )

implicitly, which causes the metadata to become unavailable. (That also happens if

you explicitly call finish( ) yourself.) Thus, normally it's best to access the

metadata immediately after calling execute( ), making a copy of any values that

you'll need to use beyond the end of the fetch loop.







Process the query using a database handle method that returns the result set

in a single operation.

With this method, any metadata generated while processing the query will have been

disposed of by the time the method returns, although you can still determine the

number of rows and columns from the size of the result set.



When you use the statement handle approach to process a query, DBI makes result set

metadata available after you invoke the handle's execute( ) method. This information is

available primarily in the form of references to arrays. There is a separate array for each type

of metadata, and each array has one element per column in the result set. Array references

are accessed as attributes of the statement handle. For example, $sth->{NAME} points to the

column name array. Individual column names are available as elements of this array:



$name = $sth->{NAMES}->[$i];

Or you can access the entire array like this:



@names = @{$sth->{NAMES}};

The following table lists the attribute names through which you access array-based metadata

and the meaning of values in each array. Names that begin with uppercase are standard DBI

attributes and should be available for most database engines. Attribute names that begin with



mysql_ are MySQL-specific and non-portable; the kinds of information they provide may be

available in other databases, but under different attribute names.

Attribute name



Array element meaning



NAME



Column name



NAME_lc



Column name, lowercased



NAME_uc



Column name, uppercased



NULLABLE



1 if column values can be NULL, empty string if not



PRECISION



Column width



SCALE



Number of decimal places (for numeric columns)



TYPE



Numeric column type (DBI value)



mysql_is_blob



True if column has a BLOB (or TEXT) type



mysql_is_key



True if column is part of a non-unique key



mysql_is_num



True if column has a numeric type



mysql_is_pri_key



True if column is part of a primary key



mysql_max_length



Actual maximum length of column values in result set



mysql_table



Name of table the column is part of



mysql_type



Numeric column type (internal MySQL value)



mysql_type_name



Column type name



The exception to array-based metadata is that the number of columns in a result set is

available as a scalar value:



$num_cols = $sth->{NUM_OF_FIELDS};

Here's some example code that shows how to execute a query and display the result set

metadata:



my $query = "SELECT name, foods FROM profile";

printf "Query: %s\n", $query;

my $sth = $dbh->prepare ($query);

$sth->execute( );

# metadata information becomes available at this point ...

printf "NUM_OF_FIELDS: %d\n", $sth->{NUM_OF_FIELDS};

print "Note: query has no result set\n" if $sth->{NUM_OF_FIELDS} == 0;

for my $i (0 .. $sth->{NUM_OF_FIELDS}-1)

{

printf "--- Column %d (%s) ---\n", $i, $sth->{NAME}->[$i];

printf "NAME_lc:

%s\n", $sth->{NAME_lc}->[$i];

printf "NAME_uc:

%s\n", $sth->{NAME_uc}->[$i];

printf "NULLABLE:

%s\n", $sth->{NULLABLE}->[$i];

printf "PRECISION:

%s\n", $sth->{PRECISION}->[$i];

printf "SCALE:

%s\n", $sth->{SCALE}->[$i];

printf "TYPE:

%s\n", $sth->{TYPE}->[$i];

printf "mysql_is_blob:

%s\n", $sth->{mysql_is_blob}->[$i];

printf "mysql_is_key:

%s\n", $sth->{mysql_is_key}->[$i];

printf "mysql_is_num:

%s\n", $sth->{mysql_is_num}->[$i];

printf "mysql_is_pri_key: %s\n", $sth->{mysql_is_pri_key}->[$i];

printf "mysql_max_length: %s\n", $sth->{mysql_max_length}->[$i];

printf "mysql_table:

%s\n", $sth->{mysql_table}->[$i];

printf "mysql_type:

%s\n", $sth->{mysql_type}->[$i];



printf "mysql_type_name: %s\n", $sth->{mysql_type_name}->[$i];

}

$sth->finish ( );

# release result set, since we didn't fetch its rows

If you use the preceding code to execute the query SELECT name, foods FROM profile, the

output looks like this:



Query: SELECT name, foods FROM profile

NUM_OF_FIELDS: 2

--- Column 0 (name) --NAME_lc:

name

NAME_uc:

NAME

NULLABLE:

PRECISION:

20

SCALE:

0

TYPE:

1

mysql_is_blob:

mysql_is_key:

mysql_is_num:

0

mysql_is_pri_key:

mysql_max_length: 7

mysql_table:

profile

mysql_type:

254

mysql_type_name: char

--- Column 1 (foods) --NAME_lc:

foods

NAME_uc:

FOODS

NULLABLE:

1

PRECISION:

42

SCALE:

0

TYPE:

1

mysql_is_blob:

mysql_is_key:

mysql_is_num:

0

mysql_is_pri_key:

mysql_max_length: 21

mysql_table:

profile

mysql_type:

254

mysql_type_name: char

To get a row count from a result set generated by calling execute( ), you must fetch the

rows and count them yourself. (The use of $sth->rows( ) to get a count for SELECT

statements is specifically deprecated in the DBI documentation.)

You can also obtain a result set by calling one of the DBI methods that uses a database handle

rather than a statement handle, such as selectall_arrayref( ) or



selectall_hashref( ). For these methods, no access to column metadata is provided.

That information already will have been disposed of by the time the method returns, and is

unavailable to your scripts. However, you can still derive column and row counts by examining

the result set itself. The way you do this depends on the kind of data structure a method

produces. These structures and the way you use them to obtain result set row and column

counts are discussed in Recipe 2.5.



9.3.5 PHP

In PHP, metadata information is available after a successful call to mysql_query( ) and

remains accessible up to the point at which you call mysql_free_result( ). To access the

metadata, pass the result set identifier returned by mysql_query( ) to the function that

returns the information you want. To get a row or column count for a result set, invoke



mysql_num_rows( ) or mysql_num_fields( ). Metadata information for a given column

in a result set is packaged up in a single object. You get the object by passing the result set

identifier and a column index to mysql_fetch_field( ), then access the various metadata

attributes as members of that object. These members are summarized in the following table:

Member name



Member meaning



blob



1 if column has a BLOB (or TEXT) type, 0 otherwise



max_length



Actual maximum length of column values in result set



multiple_key



1 if column is part of a non-unique key, 0 otherwise



name



Column name



not_null



1 if column values cannot be NULL, 0 otherwise



numeric



1 if column has a numeric type, 0 otherwise



primary_key



1 if column is part of a primary key, 0 otherwise



table



Name of table the column is part of



type



Column type name



unique_key



1 if column is part of a unique key, 0 otherwise



unsigned



1 if column has the UNSIGNED attribute, 0 otherwise



zerofill



1 if column has the ZEROFILL attribute, 0 otherwise



The following code shows how to access and display result set metadata:



$query = "SELECT name, foods FROM profile";

print ("Query: $query\n");

$result_id = mysql_query ($query, $conn_id);

if (!$result_id)

die ("Query failed\n");

# metadata information becomes available at this point ...

# @ is used below because mysql_num_rows( ) and mysql_num_fields( ) print

# a message if there is no result set (under PHP 4, at least)

$nrows = @mysql_num_rows ($result_id);

print ("Number of rows: $nrows\n");

$ncols = @mysql_num_fields ($result_id);

print ("Number of columns: $ncols\n");

if ($ncols == 0)

print ("Note: query has no result set\n");

for ($i = 0; $i < $ncols; $i++)

{

$col_info = mysql_fetch_field ($result_id, $i);

printf ("--- Column %d (%s) ---\n", $i, $col_info->name);

printf ("blob:

%s\n", $col_info->blob);



printf

printf

printf

printf

printf

printf

printf

printf

printf

printf



("max_length:

("multiple_key:

("not_null:

("numeric:

("primary_key:

("table:

("type:

("unique_key:

("unsigned:

("zerofill:



%s\n",

%s\n",

%s\n",

%s\n",

%s\n",

%s\n",

%s\n",

%s\n",

%s\n",

%s\n",



$col_info->max_length);

$col_info->multiple_key);

$col_info->not_null);

$col_info->numeric);

$col_info->primary_key);

$col_info->table);

$col_info->type);

$col_info->unique_key);

$col_info->unsigned);

$col_info->zerofill);



}

if ($ncols > 0)

# dispose of result set, if there is one

mysql_free_result ($result_id);

The output from the program looks like this:



Query: SELECT name, foods FROM profile

Number of rows: 10

Number of columns: 2

--- Column 0 (name) --blob:

0

max_length:

7

multiple_key: 0

not_null:

1

numeric:

0

primary_key: 0

table:

profile

type:

string

unique_key:

0

unsigned:

0

zerofill:

0

--- Column 1 (foods) --blob:

0

max_length:

21

multiple_key: 0

not_null:

0

numeric:

0

primary_key: 0

table:

profile

type:

string

unique_key:

0

unsigned:

0

zerofill:

0

9.3.6 Python

Python's DB-API is more limited than the other APIs in providing result set metadata. The row

and column counts are available, but the information about individual columns is not as

extensive.

To get the row count for a result set, access the cursor's rowcount attribute. The column

count is not available directly, but after calling fetchone( ) or fetchall( ), you can

determine the count as the length of any result set row tuple. It's also possible to determine

the column count without fetching any rows by using cursor.description. This is a tuple

containing one element per column in the result set, so its length tells you how many columns



are in the set. (However, be aware that if the query generates no result set, such as for an



UPDATE statement, the value of description is None.) Each element of the description

tuple is another tuple that represents the metadata for the corresponding column of the result.

There are seven metadata values per column; the following code shows how to access them

and what they mean:



query = "SELECT name, foods FROM profile"

print "Query: ", query

cursor = conn.cursor ( )

cursor.execute (query)

# metadata information becomes available at this point ...

print "Number of rows:", cursor.rowcount

if cursor.description == None: # no result set

ncols = 0

else:

ncols = len (cursor.description)

print "Number of columns:", ncols

if ncols == 0:

print "Note: query has no result set"

for i in range (ncols):

col_info = cursor.description[i]

# print name, then other information

print "--- Column %d (%s) ---" % (i, col_info[0])

print "Type:

", col_info[1]

print "Display size: ", col_info[2]

print "Internal size:", col_info[3]

print "Precision:

", col_info[4]

print "Scale:

", col_info[5]

print "Nullable:

", col_info[6]

cursor.close

The output from the program looks like this:



Query: SELECT name, foods FROM profile

Number of rows: 10L

Number of columns: 2

--- Column 0 (name) --Type:

254

Display size: 7

Internal size: 20

Precision:

20

Scale:

0

Nullable:

0

--- Column 1 (foods) --Type:

254

Display size: 21

Internal size: 42

Precision:

42

Scale:

0

Nullable:

1

9.3.7 Java

JDBC makes result set metadata available through a ResultSetMetaData object, which you

obtain by calling the getMetaData( ) method of your ResultSet object. The metadata



object provides access to several kinds of information. Its getColumnCount( ) method

returns the number of columns in the result set. Other types of metadata, illustrated by the

following code, provide information about individual columns and take a column index as their

argument. Note that for JDBC, column indexes begin at 1, not 0, which differs from DBI, PHP,

and DB-API.



String query = "SELECT name, foods FROM profile";

System.out.println ("Query: " + query);

Statement s = conn.createStatement ( );

s.executeQuery (query);

ResultSet rs = s.getResultSet ( );

ResultSetMetaData md = rs.getMetaData ( );

// metadata information becomes available at this point ...

int ncols = md.getColumnCount ( );

System.out.println ("Number of columns: " + ncols);

if (ncols == 0)

System.out.println ("Note: query has no result set");

for (int i = 1; i <= ncols; i++)

// column index values are 1-based

{

System.out.println ("--- Column " + i

+ " (" + md.getColumnName (i) + ") ---");

System.out.println ("getColumnDisplaySize: " + md.getColumnDisplaySize

(i));

System.out.println ("getColumnLabel:

" + md.getColumnLabel (i));

System.out.println ("getColumnType:

" + md.getColumnType (i));

System.out.println ("getColumnTypeName:

" + md.getColumnTypeName

(i));

System.out.println ("getPrecision:

" + md.getPrecision (i));

System.out.println ("getScale:

" + md.getScale (i));

System.out.println ("getTableName:

" + md.getTableName (i));

System.out.println ("isAutoIncrement:

" + md.isAutoIncrement (i));

System.out.println ("isNullable:

" + md.isNullable (i));

System.out.println ("isCaseSensitive:

" + md.isCaseSensitive (i));

System.out.println ("isSigned:

" + md.isSigned (i));

}

rs.close ( );

s.close ( );

The output from the program looks like this:



Query: SELECT name, foods FROM profile

Number of columns: 2

--- Column 1 (name) --getColumnDisplaySize: 20

getColumnLabel:

name

getColumnType:

1

getColumnTypeName:

CHAR

getPrecision:

0

getScale:

0

getTableName:

profile

isAutoIncrement:

false

isNullable:

0

isCaseSensitive:

true

isSigned:

false

--- Column 2 (foods) --getColumnDisplaySize: 42

getColumnLabel:

foods



Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Chapter 9. Obtaining and Using Metadata

Tải bản đầy đủ ngay(0 tr)

×