The problem is that the command to turn TERMOUT off must precede the SELECT statement that generates the report, so terminal output is off by the time SQL*Plus reads the line containing
Trang 1SQL> @c:\hours_dollars_b
Enter value for employee_id:111
As commands are executed, SQL*Plus constantly looks for the ampersand character, indicating a substitution variable When an ampersand is encountered, the next token in the command is treated as a variable SQL*Plus first looks to see if that variable has been previously defined In this example it hasn't, so SQL*Plus automatically prompts for the value
After prompting for a value and substituting it into the script in place of the corresponding variable, SQL*Plus will display both the old and the new versions of the particular line of script involved During development, this aids you in verifying that your script is executing correctly Here are the before and after versions of the line containing the &employee_id
variable from the current example:
old 13: AND E.EMPLOYEE_ID = &employee_id
new 13: AND E.EMPLOYEE_ID = 111
Next SQL*Plus goes on to tead the remining lines from the script, producing this hours and dollars report for Taras
Shevchenko:
The Fictional Company
I.S.Department Project Hours and Dollars Detail
==========================================================================
Employee: 111 Taras Shevchenko
Dollars
Proj ID Project Name Date Hours Charged
- - - - -
1001 Corporate Web Site 01-Jan-1998 1 $100.00
01_Mar-1998 3 $300.00
01_May-1998 5 $500.00
01-Jul-1988 7 $700.00
01-Sep-1998 1 $100.00
01-Nov-1998 3 $300.00
************************** - -
Project Totals 20 $2,000.00
In addition to being displayed on the screen, the report is also spooled to the file specified in the script
When TERMOUT is off
In the example just shown, the report was both displayed on the screen and spooled to a file In Chapter 3 you saw how the SET TERMOUT OFF command could be used to suppress output to the display while still allowing it to be
Trang 2
spooled, thus making a report run much faster Trying to do the same thing in this case presents a special problem The problem is that the command to turn TERMOUT off must precede the SELECT statement that generates the report, so terminal output is off by the time SQL*Plus reads the line containing the substitution variable SQL*Plus does not handle this situation too well You won't see a prompt for the substitution variable, because terminal output is off, but SQL*Plus will still be waiting for you to type in a value Your session will appear to be hung Here's what you will see:
SQL>@c:\hours_dollars_c
Strangely enough, even if you remember that SQL*Plus needs an employee number and you type one in, it won't be accepted Try running the script like this:
SQL> @c:\hours_dollars_c
111
Even though you entered a value of 111, SQL*Plus will proceed as if you had entered an empty string The end result will be the following error in the spool file:
Enter value for employee_id:
old 13: AND E.EMPLOYEE_ID =&employee_id
new 13: AND E.EMPLOYEE_ID =
ORDER BY E.EMPLOYEE_ID, P.PROJECT_ID, PH.TIME_LOG_DATE
*
ERROR at line 14:
ORA-00936: missing expression
Looking at the before and after versions of the line with the &employee_id variable, which are written to the spool file, you can see that the input of 111 was totally ignored The result was a syntactically incorrect SQL statement, so instead
of a report all you got was an error
There is a solution to this problem The solution is to use the ACCEPT command to explicitly prompt the user for the employee ID prior to issuing the SET TERMOUT OFF command You will see how to do this later in this chapter in the section titled Prompting for Values
Using Double-Ampersand Variables
Using a double ampersand in front of a substitution variable tells SQL*Plus to define that variable for the duration of the session This is useful when you need to reference a variable several times in one script, because you don't usually want to prompt the user separately for each occurrence
Trang 3
An example that prompts twice for the same value
Take a look at the following script, which displays information about a table followed by a list of all indexes defined on the table:
SET HEADING OFF
SET RECSEP OFF
SET NEWPAGE 1
COLUMN index_name FORMAT A30 NEW_VALUE index_name_var NOPRINT
COLUMN uniqueness FORMAT A6 NEW_VALUE uniqueness_var NOPRINT
COLUMN tablespace_name FORMAT A30 NEW_VALUE tablespace_name_var NOPRINT
COLUMN column_name FORMAT A30
BREAK ON index_name SKIP PAGE on column_header NODUPLICATES
TTITLE uniquenes _var INDEX index_name_var -
SKIP 1 TABLESPACE: tablespace_name_var -
SKIP 1
DESCRIBE &table_name
SELECT ui.index_name,
ui.tablespace_name,
DECODE(ui.uniqueness, UNIQUE,UNIQUE, ) uniqueness,
COLUMNS: column_header,
uic.column_name
FROM user_indexes ui,
user_ind_columns uic
WHERE ui.index_name = uic.index_name
AND ui.table_name = UPPER(&table_name)
ORDER BY ui.index_name,uic.column_position;
TTITLE OFF
SET HEADING ON
SET RECSEP WRAPPED
CLEAR BREAKS
CLEAR COLUMNS
This script uses &table_name twice, once in the DESCRIBE command that lists the columns for the table, and once in the SELECT statement that returns information about the tables's indexes When you run this script, SQL*Plus will issue separate prompts for each occurrence of &table_name The first prompt will occur when SQL*plus hits the
DESCRIBE command:
SQL>@c:\list_indexes_d
Enter value for table_name: project_hours
Name Null? Type
- -
PROJECT_ID NOT NULL NUMBER
EMPLOYEE_ID NOT NULL NUMBER
TIME_LOG_DATE NOT NULL DATE
HOURS_LOGGED NUMBER
DOLLARS_CHARGED NUMBER
Trang 4
Since only a single ampersand was used, the value entered by the user was used for that one specific instance It was not saved for future reference The result is that next time SQL*Plus encounters &table_name, it must prompt again This time it prompts for the table name to use in the SELECT statement:
Enter value for table_name: project_hours
old 9: AND ui.table_name = UPPER(&table_name)
new 9: AND ui.table_name = UPPER(project_hours)
Notice that SQL*Plus only displays before and after images of a line containing substitution variables when that line is part of a SQL query When the DESCRIBE command was read, the user was prompted for a table name, and the
substitution was made, but the old and new versions of the command were not shown
The remaining output from the script, showing the indexes defined on the project_ hours table, looks like this:
INDEX: PROJECT_HOURS_BY_DATE
TABLESPACE: USER_DATA
COLUMNS: TIME_LOG_DATE
INDEX: PROJECT_HOURS_EMP_DATE
TABLESPACE: USER_DATA
COLUMNS: EMPLOYEE_ID
TIME_LOG_DATE
UNIQUE INDEX: PROJECT_HOURS_PK
TABLESPACE: USER_DATA
COLUMNS: PROJECT_ID
EMPLOYEE_ID
TIME_LOG_DATE
6 rows selected
Commit complete
A modified example that prompts once
Obviously there's room for improvement here You don't want to type in the same value over and over just because it's used more than once in a script Aside from being inconvenient, doing so introduces the very real possibility that you won't get it the same each time One way to approach this problem is to use a doubleampersand the first time you
reference the table_name variable in the script Thus the DESCRIBE command becomes:
DESCRIBE &&table_name
The only difference between using a double ampersand rather than a single ampersand is that when a double ampersand
is used, SQL*Plus will save the value All subsequent references to the same variable use that same value It doesn't even
Trang 5
matter if subsequent references use a double ampersand or a single Once the table_name variable has been defined this way, any other reference to &table_ name or &&table_name will be replaced with the defined value
Now if you run the LIST_INDEXES script, you will only be prompted once for the table name, as the following output shows:
SQL> @c:\list_indexes_e
Enter value for table_name: project_hours
Name Null? Type
- -
PROJECT_ID NOT NULL NUMBER
EMPLOYEE_ID NOT NULL NUMBER
TIME_LOG_DATE NOT NULL DATE
HOURS_LOGGED NUMBER
DOLLARS_CHARGED NUMBER
old 9: AND ui.table_name = UPPER(&table_name)
new 9: AND ui.table_name = UPPER(project_hours)
INDEX: PROJECT_HOURS_BY_DATE
TABLESPACE: USER_DATA
COLUMNS: TIME_LOG_DATE
INDEX: PROJECT_HOURS_EMP_DATE
TABLESPACE: USER_DATA
COLUMNS: EMPLOYEE_ID
TIME_LOG_DATE
UNIQUE INDEX: PROJECT_HOURS_PK
TABLESPACE: USER_DATA
COLUMNS: PROJECT_ID
EMPLOYEE_ID
TIME_LOG_DATE
6 rows selected
Commit complete
A final caveat
If you run the LIST_INDEXES script again, you won't be prompted for a table name at all Instead, the value entered earlier will be reused, and you will again see information about the project_hours table and its indexes The reason for this is that once you define a variable, that definition sticks around until you either exit SQL*Plus or explicitly undefine the variable
Because variable definitions persist after a script has ended, it's usually best to explicitly prompt a user for input rather than depending on SQL*Plus to do it for you The ACCEPT command is used for this purpose and is described in the next
Trang 6
section At the very least, you should UNDEFINE variables at the end of a script so they won't inadvertently be reused later Prompting for Values
The most reliable and robust method for getting input from the user is to explicitly prompt for values using the ACCEPT and PROMPT commands The ACCEPT command takes input from the user and stores it in a user variable, and also allows you some level of control over what the user enters The PROMPT command may be used to display messages to the user, perhaps supplying a short summary of what your script is going to accomplish.
There are several potential problems that arise when you simply place substitution variables in your scripts and rely on
SQL*Plus's default prompting mechanisms All of these problems can be avoided through the use of the ACCEPT command Table 4-1 provides a list of these problems together with a description of how the ACCEPT and PROMPT commands can be used to overcome them.
Table 4-1 Potential Problems with SQL*Plus's Default Prompting
Using double ampersands to define a variable in a
script results in your not being prompted for a value
the second time you run the script.
Use the ACCEPT command to prompt for a value This works regardless of whether the variable has previously been defined.
Setting terminal output off, such as when spooling a
report to a file, prevents you from seeing the prompts
for substitution variables used in the query.
Use the ACCEPT command to prompt for these values earlier in the script, before the SET TERMOUT OFF command is executed.
The default prompt provided by SQL*Plus consists
of little more than the variable name.
Use the ACCEPT command to specify your own prompt
For longer explanations, the PROMPT command may be used.
This section shows how to enhance the LIST_INDEXES script with the PROMPT and ACCEPT commands The PROMPT command will be used to better explain what the script is doing, while the ACCEPT command will be used to reliably prompt the user for the table name.
The Accept Command
The ACCEPT command is used to obtain input from the user With it, you specify a user variable and text for a prompt The ACCEPT command displays the prompt for the user, waits for the user to respond, and assigns the user's response to the
variable.
Trang 7
Syntax for the ACCEPT command
Here is the syntax for the ACCEPT command:
ACC[EPT] user_variable [NUM[BER] ¦CHAR¦DATE]
[FOR[MAT] format_specification]
[DEF[AULT] default_value]
[PROMPT prompt_text¦NOPR[OMPT]]
[HIDE]
where:
ACC[EPT]
Tells SQL*Plus that you want to prompt the user for a value, and that you want the value stored in the specified user variable The command may be abbreviated to ACC
user_variable
Is the variable you want to define Do not include leading ampersands If your script uses a &table_name for a
substitution variable, you should used table_name here
NUMBER|CHAR \DATE
Is the type of data you are after The default is CHAR, which allows the user to type in anything as a response Use NUMBER to force the user to enter a number and DATE when you want a date
FOR[MAT] format_Specification
This is an optional format specification, which may optionally be enclosed in quotes If this is specified, ACCEPT will reject any input that does not conform to the specification An error message will be displayed, and the prompt reissued Specifying a format makes the most sense when dealing with numeric and date data, and SQL*Plus is actually
somewhat loose in enforcing the format Chapter 7, Advanced Scripting, delves into this aspect of the ACCEPT
command in detail
DEFAULT default_value
Specifies a default value to assign to the variable This is used if the user bypasses the prompt by pressing ENTER without actually entering a response The default value should usually be enclosed within single quotes
PROMPT prompt_text
This is the prompt text displayed to the user before waiting for input
NOPROMPT
Indicates that you do not want the user to see a visible prompt
HIDE
Causes SQL*Plus not to echo the user's response back to the display This is useful if you are prompting for a password
Trang 8The syntax for the ACCEPT command has evolved significantly with the past few releases of
SQL*Plus The syntax shown here is valid for version 8.1 Not all of the clauses are available
when using prior versions Be sensitive to this, and check your documentation if you are
writing scripts that need to work under earlier versions of SQL*Plus
Using Accept to get the table name
You can make the LIST_INDEXES script more reliable by using ACCEPT to get the table name from the user This ensures that the user is prompted for a table name each time the script is run The following ACCEPT command should
do the trick:
ACCEPT table_name CHAR PROMPT Enter the table name >
A good place to add the command would be just prior to the COLUMN commands, so the resulting script would look like this:
SET HEADING OFF
SET RECSEP OFF
SET NEWPAGE 1
Get the table name from the user
ACCEPT table_name CHAR PROMPT Enter the table name >
COLUMN index_name FORMAT A30 NEW_VALUE index_name_var NOPRINT
COLUMN uniqueness FORMAT A6 NEW_VALUE uniqueness_var NOPRINT
COLUMN tablespace_name FORMAT A30 NEW_VALUE tablespace_name_var NOPRINT
COLUMN column_name FORMAT A30
BREAK ON index_name SKIP PAGE on column_header NODUPLICATES
TTITLE uniqueness_var INDEX: index_name_var -
SKIP 1 TABLESPACE: tablespace_name_var -
SKIP 1
DESCRIBE &&table_name
SELECT ui.index_name,
ui.tablespace_name,
DECODE(ui,uniqueness, UNIQUE,UNIQUE, ) uniqueness,
COLUMNS: column_header,
uic.column_name
FROM user_indexes ui,
user_ind_columns uic
WHERE ui.index_name = uic.index_name
AND ui.table_name = UPPER(&table_name)
ORDER BY ui.index_name, uic.column_position;
COMMIT;
TTITLE OFF
SET HEADING ON
SET RECSEP WRAPPED
CLEAR BREAKS
CLEAR COLUMNS
Trang 9
It doesn't really matter now whether the script uses &table_name or &&table_name for the substitution variable Either will work just as well, and the script just shown uses both When you run the script, here's how the prompt will look:
SQL> @C:\jonathan\sql_plus_book\xe_ch_5\list_indexes_f
Enter the table name >
Now you can run this script many times in succession, and you will be prompted for a different table name each time In addition, this prompt is a bit more userfriendly than the default prompt generated by SQL*Plus
The Prompt Command
The PROMPT command is used to print text on the display for the user to read It allows you to provide informative descriptions of what a script is about to do It can be used to provide very long and detailed prompts for information, and it can be used simply to add blank lines to the output in order to space things out a bit better
Syntax for the PROMPT command
PROMPT is a very simple command The syntax looks like this:
PRO[MPT] text_to_be_displayed
where:
PRO[MPT]
Is the command, which may be abbreviated to PRO
text_to_be_displayed
Is whatever text you want displayed for the user to see This should not be a quoted string If you include quotes, they will appear in the output
If you are spooling output to a file when a PROMPT command is executed, the prompt text will also be written to the file Any substitution variables in the prompt text will be replaced by their respective values before the text is displayed
Using prompt to summarize the script
It would be nice to add some messages to the LIST_INDEXES script to make it more self-explanatory to the user You can do that by adding the following PROMPT commands to the beginning of the script:
PROMPT
PROMPT This script will first DESCRIBE a table, then
PROMPT it will list the definitions for all indexes
PROMPT on that table
PROMPT
Trang 10
The first and last PROMPT commands simply space the output a bit better by adding a blank line above and below the description
Using prompt to explain the output
The PROMPT command can also be used to better explain the output of a script In the LIST_INDEXES example, messages could be added prior to the DESCRIBE command, and prior to the SELECT statement, in order to explain the output The resulting script would look like this:
PROMPT
PROMPT &table_name table definition:
PROMPT
DESCRIBE &&table_name
PROMPT
PROMPT Indexes defined on the &table_name table:
PROMPT
SELECT ui.index_name,
Here is the result of executing the script with all the PROMPT commands added The messages not only make the output more clear, but space it out better as well
SQL> @c:\jonathan\sql_plus_book\xe_ch_5\list_indexes_G
This script will first DESCRIBE a table, then
it will list the definitions for all indexes
on that table
Enter the table name >project_hours
project_hours table definition:
Name Null? Type
- -
PROJECT_ID NOT NULL NUMBER
EMPLOYEE_ID NOT NULL NUMBER
TIME_LOG_DATE NOT NULL DATE
HOURS_LOGGED NUMBER
DOLLARS_CHARGED NUMBER
Indexes defined on the project_hours table:
old 9: AND ui.table_name = UPPER(&table_name)
new 9: AND ui.table_name = UPPER(project_hours)
INDEX: PROJECT_HOURS_BY_DATE
TABLESPACE: USER_DATA
COLUMNS: TIME_LOG_DATE