1. Trang chủ
  2. » Công Nghệ Thông Tin

Oracle SQL Plus The Definitive Guide- P29 doc

10 212 0

Đang tải... (xem toàn văn)

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 10
Dung lượng 98,47 KB

Nội dung

< previous page page_251 next page > < previous page page_252 next page > Page 252 Now execute the query that we just spooled. @user_security_choice Reset all the settings back to their defaults SET FEEDBACK ON CLEAR COLUMNS TTITLE OFF You have to be very careful when using this technique to turn off anything that could cause extraneous text to be written to the temporary command file. This includes page headings, column headings, and verification. That's why the script in the example included these commands: SET TERMOUT OFF SET PAGESIZE 0 SET HEADING OFF SET VERIFY OFF Terminal output was turned off to prevent the user from seeing the results of the SELECT on the display. One last thing you have to worry about is the filename itself. In the example shown above, the filename is hardwired into the script and does not include a path. Because no path is specified, the file will be written to the current directory. That's why a single ampersand or @ was used to run the intermediate file. Using @ causes SQL*Plus to look in the current directory for the script. Having the filename hardwired into the script can cause problems if multiple users execute the script at the same time and from the same directory. If you are concerned about this, you could write some SQL or PL/SQL code to generate a unique filename based on the Oracle username or perhaps the session identifier (SID) from the V $ SESSION view. Be creative with this technique. You don't need to limit yourself to writing SQL*Plus scripts, either. You can use SQL*Plus to generate shell script files, SQL*PLoader files, DOS batch files, or any other type of text file. Using PL/SQL Always consider the possibility of using PL/SQL to implement any type of complex procedural logic. After all, that's the reason PL/SQL was invented in the first place. If you can manage to prompt the user up front for any needed information, and if you don't need to interact with the user during the operation, PL/SQL is the way to go. < previous page page_252 next page > < previous page page_253 next page > Page 253 The reports menu could not possibly be implemented in Pl/SQL because the menu needs to run another SQL*Plus script corresponding to the user's choice. PL/SQL runs inside the database, and cannot invoke a SQL*Plus script. An ideal candidate for the use of PL/SQL would be the example where we asked the user a simple yes/no question, and then deleted data from the PROJECT_HOURS table if the user responded with a Y. Here's how that script looked: SET VERIFY OFF ACCEPT s_delete_confirm PROMPT Delete project hours data (Y/N)? DELETE FROM project_hours WHERE UPPER(&&s_delete_confirm) = Y; This script works, and because the DELETE statement is so simple, it's not too hard to understand. Still, there are people who would look at it and become very confused. The more complicated the WHERE clause gets, the greater the likelihood of confusion. Wrapping a simple IF statement, which everyone understands, around the DELETE statement would add clarity to the script. The PL/SQL version of the above script looks like this: SET VERIFY OFF ACCEPT s_delete_confirm PROMPT Delete project hours data (Y/N)? SET SERVEROUTPUT ON DECLARE users_yn_response CHAR := UPPER(&&s_delete_confirm); BEGIN IF users_yn_response = Y THEN DELETE FROM project_hours; COMMIT; DBMS_OUTPUT.PUT_LINE(All PROJECT_HOURS data has been deleted.); ELSIF users_yn_response = N THEN DBMS_OUTPUT.PUT_LINE(No data was deleted.); ELSE DBMS_OUTPUT.PUT_LINE(You must answer with a Y or N.); END IF; EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE(The PROJECT_HOURS data could not be deleted. ¦¦ SQLERRM); ROLLBACK; END; / < previous page page_253 next page > < previous page page_254 next page > Page 254 This script is a bit longer, but it's also more robust. The script will roll back the operation if the DELETE fails for any reason. It's also very clear now that the DELETE statement is going to delete all rows from the table. Using a Scripting Language Instead Don't overlook the possibility that you can use your operating-system scripting language to good advantage. Any Unix shell will allow you to write more complex scripts than you could using SQL*Plus alone. Here's an implementation of the user security report menu using the Unix Korn shell: while: do print print 1 - List users print 2 - List users and table privileges print 3 - List users and system privileges print 4 - Quit print print -n Enter your choice (1,2,3,4) > read case $ REPLY in 1 ) sqlplus -s jgennick/beaner @user_security_1 ;; 2 ) sqlplus -s jgennick/beaner @user_security_2 ;; 3 ) sqlplus -s jgennick/beaner @user_security_3 ;; 4 ) exit ;; * ) print Please enter 1 , 2, 3, or 4 ;; esac done Peri is also something you should consider. The Perl scripting language is available for both Unix and Windows. It has the advantages of being widely used, and of not tying you to one specific operating system. Looping in SQL*Plus There is no way to write a real loop using SQL*Plus. Your best option, if you need to do something iteratively, is to use PL/SQL. PL/SQL, however, doesn't allow you < previous page page_254 next page > < previous page page_255 next page > Page 255 any interaction with the user, so it's not always suitable for the task at hand. Your second-best bet is to look into using your operating system's scripting language, if there is one. Having said this, I'll point out that there are a couple of things you can do in SQL*Plus that might get you the same result as writing a loop. These are: Recursive execution Generating a file of commands, and then executing it The first option has some severe limitations, and I don't recommend it too strongly. The second option I use all the time, especially when performing database maintenance tasks. Recursive Execution You can't loop, but you can execute the same script recursively. Say you have a script that displays some useful information, and you want to give the user the option of running it again. You can do that by recursively executing the script. Take a look at the following interaction, in which the user is looking at indexes for various tables. It looks like a loop. Each time through, the user is prompted for another table name, and the indexes on that table are displayed. SQL> @list_indexex employee INDEX_NAME COLUMN_NAME EMPLOYEE_PK EMPLOYEE_ID Next table >project INDEX_NAME COLUMN_NAME PROJECT_PK PROJECT_ID PROJECT_BY_NAME PROJECT_NAME Next table >project_hours INDEX_NAME COLUMN_NAME PROJECT_HOURS_PK PROJECT_ID EMPLOYEE_ID TIME_LOG_DATE Next table > Thank you for using LIST_INDEXES. Goodbye! It sure does look like a loop, but it's not. Here is the LIST_INDEXES script that is being run: COLUMN index_name FORMAT A30 COLUMN column_name FORMAT A30 BREAK ON index_name NODUPLICATES < previous page page_255 next page > < previous page page_256 next page > Page 256 SELECT index_name, column_name FROM user_ind_columns WHERE table_name = UPPER (1); Ask the user if he wants to do this again. PROMPT ACCEPT s_next_table PROMPT Next table > Execute either list_indexes.sql or empty.sql, depending on the user's response. COLUMN next_script NOPRINT NEW_VALUE s_next_script SET TERMOUT OFF SELECT DECODE (&&s_next_table, ,empty.sql, list_indexes ¦¦ UPPER (&&s_next_table)) next_script FROM dual; SET TERMOUT ON @&&s_next_script The key to the looping is in the last part of the script, following the ACCEPT statement. If the user enters another tablename, the SELECT statement will return another call to the LIST_INDEXES script. So when the user types project in response to the prompt, the s_next_script substitution variable ends up being: list_indexes PROJECT The only thing missing is the ampersand sign, and that is supplied by the command at the bottom of the script. In this case, the command: @&&s_next_script will be translated to: @list_indexes PROJECT If the user doesn't enter a table name at the prompt, the s_next_table variable will be null, and the DECODE statement will return empty.sql. EMPTY.SQL is necessary because the @ command must be executed. EMPTY.SQL gives you a clean way out of the recursion. In this case EMPTY.SQL prints a message, and is implemented like this: PROMPT PROMPT Thank you for using LIST_INDEXES. Goodbye! PROMPT Recursive execution is a very limited technique. You can't nest scripts forever. SQL*Plus limits you to nesting only 20 scripts, and on some older versions the limit may be as low as 5. Exceed that limit, and you will get the following message: SQL*Plus command procedures may only be nested to a depth of 20. < previous page page_256 next page > < previous page page_257 next page > Page 257 Still, recursion can be useful. What are the odds that you will want to type in 20 table names in one sitting? In this case, the convenience may outweigh any chance of exceeding that limit on nesting scripts. And if you do exceed the limit, so what? You can just rerun the script. Generating a File of Commands If you need to loop a fixed number of times, say once for each table you own, you can use a SQL query to build a second file of SQL commands, then execute that file. This is known as using SQL to write SQL, and it's a very powerful scripting technique. You've already seen an example of this technique in a previous section, where it was used to implement the equivalent of an IF statement in a SQL*Plus script. This technique can also be used to perform a repetitive operation, if the basis for that operation can be the results of a SQL query. Suppose, for instance, that you want to write a script that will analyze and compute statistics for all your tables. You don't want to hardcode the table names in the script, because then you would need to remember to change the script each time you drop or create a table. Thinking in terms of pseudocode, you might envision a script like this: FOR xxx = FIRST_TABLE TO LAST_TABLE ANALYZE xxx COMPUTE STATISTICS; NEXT XXX Of course, that script would never fly in SQL*Plus. Instead, you need a way to execute the ANALYZE command for each table without really looping. One way to do that is to write a SQL query to create that command. Take a look at the follow ing query: SELECT ANALYZE ¦¦ table_name ¦¦ COMPUTE STATISTICS; FROM user_tables; Since SQL is a set-oriented language, it will return a result set consisting of one instance of the ANALYZE command for each table you own. Running this query against the sample database used for this book will give the following results: ANALYZE EMPLOYEE COMPUTE STATISTICS; ANALYZE PROJECT COMPUTE STATISTICS; ANALYZE PROJECT_HOURS COMPUTE STATISTICS; At this point, if this were just a once-off job, and if you were using a GUI version of SQL*Plus, you could simply copy the output and paste it back in as input. The commands would execute and all your tables would be analyzed. To make a script you can run periodically, all you need to do is to spool the ANALYZE commands to a file and then execute that file. The following script does that. < previous page page_257 next page > < previous page page_258 next page > Page 258 SET ECHO OFF DESCRIPTION Analyze and compute statistics for all tables owned by the user. We only want the results of the query written to the file. Headings, titles, feedback, etc. aren't valid commands, so turn all that stuff off. SET HEADING OFF SET PAGESIZE 0 SET VERIFY OFF SET FEEDBACK OFF SET TERMOUT OFF Create a file of ANALYZE commands, one for each table. PROMPT commands are used to write SET ECHO ON and OFF commands to the spool file. SPOOL analyze_each_table.sql PROMPT SET ECHO ON SELECT ANALYZE TABLE ¦¦ table_name ¦¦ COMPUTE STATISTICS; FROM user_tables; PROMPT SET ECHO OFF SPOOL OFF Execute the ANALYZE commands. SET TERMOUT ON @analyze_each_table Reset settings back to defaults SET HEADING ON SET PAGESIZE 14 SET VERIFY ON SET FEEDBACK ON Most of the commands in the script are there to prevent any extraneous information, such as column headings or page titles, from being written to the spool file. The real work is done by the SPOOL and SELECT commands, and also by the command used to run the resulting script file. The ECHO setting is turned on prior to running the ANALYZE commands so you can watch the ANALYZE commands as they execute. A PROMPT command is used to write a SET ECHO ON command to the output file. Here are the results from running the script: SQL> @analyze_tables SQL> ANALYZE TABLE EMPLOYEE COMPUTE STATISTICS; SQL> ANALYZE TABLE PROJECT COMPUTE STATISTICS; SQL> ANALYZE TABLE PROJECT_HOURS COMPUTE STATISTICS; SQL> SET ECHO OFF All the tables in the schema were analyzed. Once this is set up, you never need to worry about it again. Each time the script is run, ANALYZE commands are generated for all the tables currently in the schema. < previous page page_258 next page > < previous page page_259 next page > Page 259 My experience has been that the technique of using SQL to write SQL proves most useful when you are writing scripts to perform database maintenance tasks such as the one shown in the previous example. Information about all database objects is accessible via the data dictionary views, so it's usually a simple matter to issue queries against those views to generate whatever commands you need. Looping Within PL/SQL You should always consider PL/SQL when you need to implement any type of complex procedural logic, and that includes looping. Because PL/SQL executes in the database, you can't use it for any type of loop that requires user interaction. The table index example shown earlier in this chapter, where the user was continually prompted for another table name, could never be implemented in PL/SQL. It's also impossible to call another SQL*Plus script from PL/SQL. However, if you can get around those two limitations, PL/SQL may be the best choice for the task. The ANALYZE TABLE script revisited As an example of what you can do using PL/SQL, let's revisit the ANALYZE_TABLE script shown earlier. It's very easy, using PL/SQL, to write a loop to iterate through all the tables you own. Here's one way to do that: SQL> SET SERVEROUTPUT ON SQL> BEGIN 2 FOR my_table in ( 3 SELECT table_name 4 FROM user_tables) LOOP 5 5 Print the table name 6 DBMS_OUTPUT.PUT_LINE(my_table.table_name); 7 END LOOP; 8 END; 9 / EMPLOYEE PROJECT PROJECT_HOURS PL?SQL procedure successfully completed This example uses what is called a cursor FOR loop. A cursor FOR loop executes once for each row returned by the query you give it. In this example, that query returns a list of tables you own. You might think you could just put an ANALYZE TABLE command inside the loop and pass it the table name as a parameter, but it's not quite that simple. The ANALYZE command is a DDL command, and prior to version 8.1 of Oracle, PL/SQL did not allow you to embed DDL commands within your code. The following is an example of what will happen if you try. < previous page page_259 next page > . implemented in Pl /SQL because the menu needs to run another SQL* Plus script corresponding to the user's choice. PL /SQL runs inside the database, and cannot invoke a SQL* Plus script. An. writing SQL* Plus scripts, either. You can use SQL* Plus to generate shell script files, SQL* PLoader files, DOS batch files, or any other type of text file. Using PL /SQL Always consider the possibility. prevent the user from seeing the results of the SELECT on the display. One last thing you have to worry about is the filename itself. In the example shown above, the filename is hardwired into the

Ngày đăng: 05/07/2014, 04:20