< previous page page_127 next page > Page 127 SQL> @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 < previous page page_127 next page > < previous page page_128 next page > Page 128 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. < previous page page_128 next page > < previous page page_129 next page > Page 129 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 < previous page page_129 next page > < previous page page_130 next page > Page 130 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 < previous page page_130 next page > < previous page page_131 next page > Page 131 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 < previous page page_131 next page > < previous page page_132 next page > Page 132 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 Potential Problem Solution 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. < previous page page_132 next page > < previous page page_133 next page > Page 133 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. < previous page page_133 next page > < previous page page_134 next page > Page 134 The 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 < previous page page_134 next page > < previous page page_135 next page > Page 135 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 < previous page page_135 next page > < previous page page_136 next page > Page 136 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 < previous page page_136 next page > . 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. 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,. 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