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

Oracle SQL Plus The Definitive Guide- P15 pps

10 331 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 100,38 KB

Nội dung

< previous page page_117 next page > Page 117 Taking Advantage of Unions A union is a SQL construct that allows you to knit together the results of several SQL queries and treat those results as if they had been returned by just one query. I find them invaluable when writing queries, and one of the more creative uses I've discovered involves using unions to produce reports that need to show data grouped by categories, and that may need to show the same records in more than one of those categories. A typical example A good example of this type or report would be one that fulfills the following request: Produce an employee turnover report that lists everyone employed at the beginning of the year, everyone hired during the year, everyone terminated during the year, and everyone still employed at the end of the year. The report should be divided into four sections, one for each of those categories. This is not an unusual type of request, not for me at least. The interesting thing about this request, though, is that every employee will need to be listed in exactly two categories. That means you would need to write a query that returned each employee record twice, in the correct categories. When you are faced with this type of query, it can be helpful to simplify the problem by thinking in terms of separate queries, one for each category. It's fairly easy to conceive of a query to bring back a list of employees that were on board at the beginning of the year. You just need to make sure the first of the year is between the hire and termination dates, and account for the fact that the termination date might be null. Here's the query to return a list of people employed as of January 1, 1998: SELECT employee_id, employee_name, employee_hire_date, employee_termination_date FROM employee WHERE employee_hire_date < TO_DATE(1-Jan-1988, dd-mon-yyyy) AND (employee_termination_date IS NULL OR employee_termination_date >= TO_DATE(1-Jan-1988, dd-mon-yyyy)) This gives you the first section of the reportthose employed at the beginning of the year. Retrieving the data for the remaining sections is a matter of using a different WHERE clause for each section. Table 3-3 shows the selection criteria for each section of the report. < previous page page_117 next page > < previous page page_118 next page > Page 118 Table 3-3. Union Query Selection Criteria Report Section WHERE Clause Employed at beginning of year WHERE employee_hire date < TO_DATE(1-Jan-1998, dd-mon-yyyy) AND (employee_termination_date IS NULL OR employee_termination_date >=TO_DATE(1-Jan-1998, dd-mon-yyyy)) Hired during the year WHERE employee_hire_date >= TO_DATE (1-Jan-1998 dd-mon-yyyy) AND (employee_hire_date < TO_DATE(1-Jan-1999, dd-mon-yyyy)) Terminated during the year WHERE employee_termination_date >= TO_DATE (1-Jan-1998, dd-mm-yyyy) AND (employee_termination_date < TO_DATE(1-Jan-1999, dd-mm-yyyy)) Employed at end of year WHERE employee_hire_date < TO_DATE (1-Jan-1998, dd-mm-yyyy) AND (employee_termination_date IS NULL OR employee_termination_date >= TO_DATE(1-Jan-1999, dd-mm-yyyy)) The UNION query After separately developing the four queries, one for each section of the report, you can use SQL's UNION operator to link those four queries together into one large query. There are four things to consider when doing this: 1. You need to return all the records retrieved by all four queries. 2. You need to be able to group the retrieved records by category. 3. You need to be able to control which category prints first. 4. You need to identify each category on the printed report so the end user knows what's what. To be certain of getting all the records back from the query, use the UNION ALL operator to tie the queries together. Using UNION by itself causes SQL to filter out any duplicate rows in the result set. That's not really an issue with this example there won't be any duplicate rowsbut it's an important point to consider. In order to properly group the records, you can add a numeric constant to each of the four queries. For example, the query to return the list of those employed at the beginning of the year could return an arbitrary value of 1: SELECT 1 sort_column, employee_id, employee_name, The other queries would return values of 2, 3, and 4 in the sort column. Sorting the query results on these arbitrary numeric values serves two purposes. First, the records for each section of the report will be grouped together because they will all have the same constant. Second, the value of the sort column controls the order in which the sections print. Use a value of 1 for the section to be printed first, a value of 2 for the second section, etc. < previous page page_118 next page > < previous page page_119 next page > Page 119 The final thing to worry about is identifying the results to the reader of the report. The values used in the sort column won't mean anything to the reader, so you also need to add a column with some descriptive text. Here's how the final query for people employed at the beginning of the year looks with that text added: SELECT 1 sort_column, Employed at Beginning of Year employee_status_text, employee_id, employee_name, employee_hire_date, employee_termination_date FROM employee WHERE employee_hire_date < TO_DATE(1-Jan-1998,dd-mon-yyyy) AND (employee_termination_date IS NULL OR employee_termination_date >= TO_DATE(1-Jan-1998,dd-mon-yyyy)) The first column returned by this query is used to sort these records to the top of the report, while the second column serves to identify those records for the reader. The full-blown UNION query to produce all four sections of the report looks like this: SELECT 1 sort_column, Employed at Beginning of Year employee_status_text, employee_id, employee_name, employee_hire_date, employee_termination_date FROM employee WHERE employee_hire_date < TO_DATE(1-Jan-1998, dd-mon-yyyy) AND (employee_termination_date IS NULL OR employee_termination_date >= TO_DATE(1-Jan-1998, dd-mon-yyyy)) UNION ALL SELECT 2 as sort_column, Hired During Year as employee_status_text, employee_id, employee_name, employee_hire_date, employee_termination_date FROM employee WHERE employee_hire_date >= TO_DATE(1-Jan-1998, dd-mon-yyyy) AND (employee_hire_date < TO_DATE(1-Jan-1999,dd-mon-yyyy)) UNION ALL SELECT 3 as sort_column, Terminated During Year as employee_status_text, employee_id, employee_name, employee_hire_date, employee_termination_date FROM employee WHERE employee_termination_date >= TO_DATE(1-Jan-1998, dd-mon-yyyy) AND (employee_termination_date < TO_DATE (1-Jan-1999, dd-mon-yyyy)) UNION ALL SELECT 4 as sort_column, < previous page page_119 next page > < previous page page_120 next page > Page 120 Employed at End of Year as employee_status_text, employee_id, employee_name, employee_hire_date, employee_termination_date FROM employee WHERE employee_hire_date < TO_DATE(1-Jan-1999, dd-mon-yyyy) AND (employee_termination_date IS NULL OR employee_termination_date >= TO_DATE (1-Jan-1999, dd-mon-yyyy)) ORDER BY sort_column, employee_id, employee_hire_date; As you can see, the four queries have been unioned together in the same order in which the report is to be printed. That's done for readability, though. It's the ORDER BY clause at the bottom that ensures that the records are returned in the proper order. The final report All that's left now that the query has been worked out is to follow the remaining steps in the report development methodology to format and print the report. To produce a fairly basic, columnar report, precede the query with the following commands: Setup pagesize parameters SET NEWPAGE 0 SET PAGESIZE 55 Set the linesize, which must match the number of equal signs used for the ruling lines in the headers and footers. SET LINESIZE 75 TTITLE CENTER The Fictional Company SKIP 2 - CENTER Employee Turnover Report SKIP 1 - LEFT ================================ - ===================================== - SKIP 3 Format the columns CLEAR COLUMNS COLUMN sort_column NOPRINT COLUMN employee_status_text HEADING Status FORMAT A29 COLUMN employee_name HEADING Employee Name FORMAT A20 COLUMN employee_hire_date HEADING Hire Date FORMAT A11 COLUMN employee_termination_date HEADING Term Date FORMAT A11 Breaks and computations BREAK ON EMPLOYEE_status_text SKIP 2 NODUPLICATES CLEAR COMPUTES COMPUTE NUMBER LABEL Total Count OF employee_name ON employee_status_text Set the date format to use ALTER SESSION SET NLS_DATE_FORMAT = dd-Mon-yyyy; < previous page page_120 next page > < previous page page_121 next page > Page 121 When you execute this report, the output will look like this: The Fictional Company Employee Turnover Report ============================================================================= Status Employee Name Hire Date Term date Employed at beginning of year Jonathan Gennick 15-Nov-1961 Jenny Gennick 16-Sep-1964 05-May-1998 Jeff Gennick 29-Dec-1987 01-Apr-1998 Pavlo Chubynsky 01-Mar-1994 15-Nov-1998 Taras Shevchenko 23-Agu-1976 Hermon Goche 15-Nov-1961 04-Apr-1998 **************************** Total Count 6 Hired During Year Hourace Walker 15-Jun-1998 Bohdan Khmelnytsky 02-Jan-1998 Ivan Mazepa 04-Apr-1998 30-Sep-1998 Jacob Marley 03-Mar-1998 31-oct-1998 **************************** Total Count 4 Terminated During Year Jenny Gennick 16-Sep-1964 05-May-1998 Jeff Gennick 29-Dec-1987 01-Apr-1998 Pavlo Chubynsky 01-Mar-1994 15-Nov-1998 Ivan Mazepa 04-Apr-1998 30-Sep-1998 Hermon Goche 15-Nov-1961 04-Apr-1998 Jacob Marley 03-Mar-1998 31-Oct-1998 **************************** Total Count 6 Employed at End of Year Jonathan Gennick 15-Nov-1961 Horace Walker 15-Jun-1998 Bohdan Khmelnytsky 02-Jan-1998 Taras Shevchenko 23-Aug-1976 **************************** Total Count 4 That's all there is to it. It wouldn't be a big leap to turn this report into a master/ detail report, with each section starting on a new page. Using this technique, you can develop similar reports with any number of sections you need. < previous page page_121 next page > < previous page page_122 next page > Page 122 4 Writing SQL*Plus Scripts In this chapter: Why Write Scripts? Using Substitution Variables Prompting for Values Cleaning Up the Display Packaging Your Script The DEFINE and UNDEFINE Commands Controlling Variable Substitution Commenting Your Scripts In the previous chapter, you saw how to write a script to produce a report. This chapter delves more deeply into the subject of scripting, and shows you how to write interactive scripts. You will learn how to use substitution variables, which allow the user to dynamically supply values to a script at runtime. You will learn how to prompt the user for those values, and how to display other messages for the user to see. Finally, you will learn how to package your script for easy access when you need it. Why Write Scripts? The most compelling reason to write scripts, in my mind, is to encapsulate knowledge. Say, for example, that you have developed a query that returns index definitions for a table. You certainly don't want to have to think through the entire process of developing that query each time you need to see an index. If you have a good script available, you just run it. Likewise, if someone asks you how to see index definitions for a table, just give them a copy of the script. A second reason for developing scripts is that they save time. Look at the script to produce the first report in Chapter 3, Generating Reports with SQL*Plus. It contains 17 separate commands, some quite long. By placing those commands in a script, you save yourself the time and effort involved in retyping all of them each time you run the report. Lastly, scripts can simplify tasks both for you and for others. When you know you have a good, reliable script, you can just run it, answer the questions, then sit back < previous page page_122 next page > < previous page page_123 next page > Page 123 while it does all the work. You don't need to worry, thinking did I enter the correct command?, did I log on as the correct user?, or did I get that query just right? Anytime you find yourself performing a task over and over, think about writing a script to do it for you. You'll save yourself time. You'll save yourself stress. You'll be able to share your knowledge more easily. A good source of ready-to-run scripts for Unix users is Oracle Scripts by Brian Lomasky and David C. Kreines, O'Reilly & Associates, 1998. Using Substitution Variables Substitution variables allow you to write generic SQL*Plus scripts. They allow you to mark places in a script where you want to substitute values at runtime. What Is a Substitution Variable? A substitution variable is the same thing as a user variable. In the previous chapter, you saw how to get the contents of a database column into a user variable and how to place the contents of that user variable into the page header of a report. SQL*Plus also allows you to place user variables in your script to mark places where you want to supply a value at runtime. When you use them this way, they are called substitution variables. A substitution variable is not like a true variable used in a programming language. Instead, a substitution variable marks places in the text where SQL*Plus does the equivalent of a search and replace at runtime, replacing the reference to a substitution variable with its value. Substitution variables are set off in the text of a script by preceding them with either one or two ampersand characters. Say, for example, that you had this query to list all projects to which employee #107 had charged time: SELECT DISTINCT p.project_id, p.project_name FROM project p, project_hours ph WHERE ph.employee_id = 107 AND p.project_id = ph.project_id; As you can see, this query is specific to employee number 107. To run the query for a different employee, you would need to edit your script file, change the ID number, save the file, then execute it. That's a pain. You don't want to do that. < previous page page_123 next page > < previous page page_124 next page > Page 124 Instead, you can generalize the script by rewriting the SELECT statement with a substitution variable in place of the employee ID number. It would look like this: SELECT DISTINCE p.project_id, p.project_name FROM project p, project_hours ph WHERE ph.employee_id = &employee_id AND p.project_id = ph.project_id; The ampersand in front of the word employee_id marks it as a variable. At runtime, when it reads the statement, SQL*Plus will see the substitution variable and replace it with the current value of the specified user variable. If the employee_id user variable contained a value of 104, then &employee_id would be replaced by 104, and the resulting line would look like this: WHERE ph.employee_id = 104 As stated earlier, and as you can see now, SQL*Plus truly does a search and replace operation. The Oracle database does not know that a variable has been used. Nor does SQL*Plus actually compare the contents of the employee_id column against the value of the variable. SQL*Plus simply does the equivalent of a search and replace operation on each statement before that statement is executed. As far as the Oracle database is concerned, you might just as well have included constants in your script. Substitution variables are the workhorse of SQL*Plus scripts. They give you a place to store user input, and they give you a way to use that input in SQL queries, PL/ SQL code blocks, and other SQL*Plus commands. Using Single-Ampersand Variables. The easiest way to generalize a script is to take one you have working for a specific case and modify it by replacing specific values with substitution variables. In this section, we will revisit the Labor Hours and Dollars Detail report shown in Chapter 3. You will see how you can modify the script to print the report for only one employee, and you will see how you can use a substitution variable to generalize that script by making it prompt for the employee ID number at runtime. When SQL*Plus encounters a variable with a single leading ampersand, it always prompts you for a value. This is true even when you use the same variable multiple times in your script. If you use it twice, you will be prompted twice. Doubleampersand variables allow you to prompt a user only once for a given value, and are explained later in this chapter. The report for one specific employee The report in the previous chapter produced detailed hours and dollars information for all employees. To reduce the scope to one employee, you can add this line to the WHERE clause: < previous page page_124 next page > < previous page page_125 next page > Page 125 AND e.employee_id = 107 Since this report is now only for one employee, the grand totals don't make sense, so the COMPUTES to create them can be removed. Finally, a SPOOL command has been added to capture the output in a file to be printed later. The complete script for the report looks like this: Setup pagesize parameters SET NEWPAGE 0 SET PAGESIZE 55 Set the linesize, which must match the number of equal signs used for the ruling lines in the headers and footers. SET LINESIZE 71 Get the date for inclusion in the page footer. SET TERMOUT OFF ALTER SESSION SET NLS_DATE_FORMAT = DD-Mon_YYYY; COLUMN SYSDATE NEW_VALUE report_date SELECT SYSDATE FROM DUAL; SET TERMOUT ON Setup page headings and footings TTITLE CENTER The Ficional Company SKIP 3 - LEFT I.S.Department - RIGHT Project Hours and Dollars Detail SKIP 1 - LEFT ============================================================= - SKIP 2 Employee: FORMAT 9999 emp_id_var emp_name_var SKIP 3 BTITLE LEFT ============================================================= - SKIP 1 - LEFT report_date - RIGHT Page FORMATE 999 SQL.PNO Format the columns COLUMN employee_id NEW_VLUE emp_id_var NOPRINT COLUMN employee_name NEW_VALUE emp_name_var NOPRINT COLUMN project_id HEADING Proj ID FORMAT 9999 COLUMN project_name HEADING Project Name FORMAT A26 WORD_WRAPPED COLUMN time_log_date HEADING Date FORMAT All COLUMN hours_logged HEADING Hours FORMAT 9,999 COLUMN dollars_charged HEADING Dollars¦Charged FORMAT $999,999.99 Breaks and Computations BREAK ON employee_id SKIP PAGE NODUPLICATES - ON employee_name NODUPLICATES - ON project_id SKIP 2 NODUPLICATES - ON project_name NODUPLICATES CLEAR COMPUTES COMPUTE SUM LABEL Project Totals OF hours_logged ON project_name COMPUTE SUM LABEL Project Totals OF dollars_charged ON project_name COMPUTE SUM LABEL Totals OF hours_logged ON employee_id COMPUTE SUM LABEL Totals OF dollars_charged ON employee_id < previous page page_125 next page > < previous page page_126 next page > Page 126 Execute the query to generate the report. SPOOL C:\A\HOURS_DOLLARS SELECT P.PROJECT_ID P.PROJECT_NAME, TO_CHAR(PH.TIME_LOG_DATE, dd-Mon_yyyy) time_log_date, PH.HOURS_LOGGED, PH.DOLLARS_CHARGED, E.EMPLOYEE_ID, E.EMPLOYEE_NAME FROM EMPLOYEE E, PROJECT P, PROJECT_HOURS PH WHERE E.EMPLOYEE_ID = PH.EMPLOYEE_ID AND P.PROJECT_ID=PH.PROJECT_ID AND E.EMPLOYEE_ID = 107 ORDER BY.E.EMPLOYEE_ID, P.PROJECT_ID, PH.TIME_LOG_DATE; SPOOL OFF Reset everything back to the defaults. CLEAR BREAKS CLEAR COMPUTES TTITLE OFF BTITLE OFF SET NEWPAGE 1 SET PAGESIZE 24 SET LINESIZE 80 Running this script as shown will produce a report specifically for employee 107. Generalizing the report with substitution variables You don't want to edit the script file and modify your script every time you need to produce a report for a different employee, and you don't have to. Instead, you can replace the reference to a specific employee number with a substitution variable and let SQL*Plus prompt you for a value at runtime. Here's how the affected line of script looks with a substitution variable instead of a hardcoded value: AND E.EMPLOYEE_ID = &employee_id The variable name should be descriptive, and it needs to serve two purposes. It needs to inform the user and it needs to inform you. First and foremost, the variable name is used in the prompt, and must convey to the user the specific information needed. In this case, for example, using &id for the variable would leave the user wondering whether to enter an employee ID or a project ID. The second thing to keep in mind is that you will need to look at the script again someday, so make sure the name is something that will jog your memory as well. Running the report When you run the report, SQL*Plus will prompt you for the value of the &employee_id substitution variable. Assume that the script is in a file named HOURS_DOLLARS.SQL. Here's how the output will look: < previous page page_126 next page > . variables are the workhorse of SQL* Plus scripts. They give you a place to store user input, and they give you a way to use that input in SQL queries, PL/ SQL code blocks, and other SQL* Plus commands. Using. of the report will be grouped together because they will all have the same constant. Second, the value of the sort column controls the order in which the sections print. Use a value of 1 for the. can see now, SQL* Plus truly does a search and replace operation. The Oracle database does not know that a variable has been used. Nor does SQL* Plus actually compare the contents of the employee_id

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

TỪ KHÓA LIÊN QUAN