Thursday, October 22, 2009

Recipe 14.7 Logging Messages Using a Servlet Context Event Listener



[ Team LiB ]






Recipe 14.7 Logging Messages Using a Servlet Context Event Listener




Problem




You
want to log messages using log4j when a servlet
context is created and shut down.





Solution



Use log4j and a servlet context event listener.





Discussion



The servlet API includes a listener interface named
javax.servlet.
ServletContextListener that you can use to notify
a specific Java class when a servlet context is created or shut down.
This notified class may want to log the servlet context creation or
shut down or store an object attribute in the servlet context,
actions that the Java class (the listener) takes when it receives its
notification.



The servlet context listener is an

application event listener, a category
that also includes session event listeners (see Chapter 11 or Recipe 14.8) and
request event listeners. For example, the session event listener
receives notifications when the servlet container creates new HTTP
session objects in order to track a user's progress
through a web application. The servlet container notifies the request
event listener when a user makes a web
application request, so that a listener can take
some kind of action�such as logging the user's
IP address.



A
javax.servlet.ServletContext is used to store attributes or access
context parameters that are common to a web application, get
RequestDispatcher objects for forwarding or
including files (see Chapter 6), or get
information such as an absolute pathname associated with a web
resource. Every web application has one associated servlet context.






There is one servlet context instance per web application (per Java Virtual
Machine (JVM), in the case of distributed web applications) according
to the ServletContext Javadoc: http://java.sun.com/j2ee/1.4/docs/api/javax/servlet/ServletContext.html.





log4j is a good choice for generating
custom-designed log messages from a class that implements the
ServletContextListener interface. Example 14-12 shows the ContextLogger
class, which uses log4j to send messages in its
two methods.




Example 14-12. A servlet context event listener that sends log messages

package com.jspservletcookbook;

import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;


import javax.servlet.*;
import javax.servlet.http.*;

public class ContextLogger implements ServletContextListener {


private Logger log;

public ContextLogger( ){}

public void contextDestroyed(ServletContextEvent sce) {

String name = sce.getServletContext( ).getServletContextName( );

//log request of the INFO level
log.info("ServletContext shut down: " + (name == null ? "" : name ));

//do other necessary work, like clean up any left-over resources
//used by the web app
}

public void contextInitialized(ServletContextEvent sce) {

ServletContext context = sce.getServletContext( );

String realPath = context.getRealPath("/");
String fileSep = System.getProperty("file.separator");

//Make sure the real path ends with a file separator character ('/')
if (realPath != null && (! realPath.endsWith(fileSep))){
realPath = realPath + fileSep;}


//Initialize logger here; the log4j properties filename is specified
//by a context parameter named "logger-config"

PropertyConfigurator.configure(realPath +
"WEB-INF/classes/" + context.getInitParameter("logger-config"));

log = Logger.getLogger(ContextLogger.class);

String name = context.getServletContextName( );

//log request about servlet context being initialized
log.info("ServletContext ready: " + (name == null ? "" : name ));

}

}



Give this class a no-args constructor, place it in
WEB-INF/classes or in a JAR located in
WEB-INF/lib, and register it in
web.xml:



<listener>
<listener-class>
com.jspservletcookbook.ContextLogger
</listener-class>
</listener>


The
ServletContextListener

tracks the lifecycle
of a servlet context with two methods: contextInitialized(
)
and contextDestroyed( ). The servlet
container calls the first method when the servlet context is created
and the web application is ready to receive its first request. The
container notifies the listener class and calls
the contextDestroyed( ) method when the servlet
context is about to be shut down, such as when a web application is
stopped prior to being reloaded.



Tomcat 4.1.24 initializes the servlet context listener
prior to creating servlet instances, even if the
application configures the servlet to be preloaded. Example 14-12 initializes the log4j
system in the contextInitialized( ) method.





The deployment descriptor can instruct the servlet container to load
a servlet instance and call its init( ) method at
startup by including a
load-on-startup element nested in the servlet
element, as in:



<servlet>
<servlet-name>logger</servlet-name>
<servlet-class>
com.jspservletcookbook.LoggerServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>


The value of load-on-startup is an integer
indicating the order in which the container loads the servlet.




In the contextInitialized( ) method, the
listener configures log4j
using the file specified by a context-param
element in web.xml:



<context-param>
<param-name>logger-config</param-name>
<param-value>servletLog.properties</param-value>
</context-param>


This log4j
configuration file (servletLog.properties) is
located in the WEB-INF/classes directory. The
listener then logs its messages to the console
and to a file when the web application starts up or is shut down.
Example 14-13 shows the configuration file the
listener uses for
log4j.




Example 14-13. Log4j configuration file used by the servlet context listener

log4j.rootLogger=DEBUG, cons
log4j.logger.com.jspservletcookbook=, myAppender

log4j.appender.cons=org.apache.log4j.ConsoleAppender

#configure the 'myAppender' appender

log4j.appender.myAppender=org.apache.log4j.RollingFileAppender
log4j.appender.myAppender.File=h:/home/example.log
log4j.appender.myAppender.MaxBackupIndex=1
log4j.appender.myAppender.MaxFileSize=1MB

log4j.appender.cons.layout=org.apache.log4j.SimpleLayout
log4j.appender.myAppender.layout=org.apache.log4j.PatternLayout
log4j.appender.myAppender.layout.ConversionPattern=
%-5p Logger:%c{1} Date: %d{ISO8601} - %m%n



The listener gets a logger with this code:



log = Logger.getLogger(ContextLogger.class);


This names the logger after the class
com.jspservletcookbook.ContextLogger. Therefore,
in the log4j naming scheme, the
listener's logger inherits the
appender that Example 14-13
defines for the logger com.jspservletcokbook. This
is because the configuration does not define a logger for
com.jspservletcookbook.ContextLogger;
consequently, the listener's
logger inherits the next defined logger available:
com.jspservletcookbook. The
com.jspservletcookbook logger has a console
appender and a file appender.



As a result, the servlet context listener sends its log messages to
the console and the h:/home/example.log file.
Example 14-13 has different layouts for the console
and file appenders. The listener's console messages
look like this:



INFO - ServletContext shut down: The home web application
INFO - ServletContext ready: The home web application


The log file messages have a different format:



INFO  Logger:ContextLogger Date: 2003-05-12 16:45:20,398 - ServletContext shut down: 
The home web application
INFO Logger:ContextLogger Date: 2003-05-12 16:45:20,999 - ServletContext ready: The
home web application


The format of these messages consists of the name of the logging
level (e.g., INFO), the logger name, the date of
the log request, and the message itself.





See Also



Recipe 14.2 on downloading and setting up
log4j; Recipe 14.3 on
using a log4j logger without a properties file;
Recipe 14.4 on adding an appender to the root
logger; Recipe 14.5 on using a pattern layout
with a logger's appender; Recipe 14.6 on using a logger with a JSP; Recipe 14.8 on using log4j with
session event listeners; the log4j download
site: http://jakarta.apache.org/log4j/docs/download.html;
the log4j Javadoc page: http://jakarta.apache.org/log4j/docs/api/index.html;
the log4j project documentation page:
http://jakarta.apache.org/log4j/docs/documentation.html.








    [ Team LiB ]



    Referring to a Range













    Referring to a Range



    A macro that needs to work with ranges of differing sizes must be flexible. In this section, you’ll learn various ways to refer to a range. The examples in this section don’t do anything except reference ranges within a list, but these are all techniques you’ll use many times as you work with ranges. Later in the chapter, you’ll use these techniques in more practical contexts.




    Refer to a Range by Using an Address


    The Range property is a useful and flexible way of retrieving a reference to a range. The Range property allows you to specify the address of the range you want. You can use the Object Browser to see how to use the Range property.




    1. In the Chapter04 workbook, right-click a worksheet tab, and then click View Code on the shortcut menu to display the Visual Basic editor.


      Rearrange the Excel and Visual Basic editor windows so that you can see them side by side.




    2. In the Visual Basic editor, click the Object Browser toolbar button.






      Object Browser






      See Also 

      If you want to change the Object Browser into a dockable window, see the sidebar titled “Dockable Views” in Chapter 3, “Explore Workbooks and Worksheets.”





    The Object Browser appears in the space normally held by the code window.


    In essence, the Object Browser consists of two lists. The list on the left is a listof object class names. The list on the right is a list of members-methods and properties-available for the currently selected object class. At the top of the list of classes is a special object class named <globals>. The <globals> object is not a real object class, but it includes in its list of members all the methods and properties you can use without specifying an object. These are the methods and properties you use to start a statement.







    1. In the Classes list, select the <globals> object, click in the Members of '<globals>' list, and press the R key to scroll to the first member that begins with the letter R. Then select the Range property.


      The box at the bottom of the Object Browser displays information about the Range property. This property takes two arguments. The brackets around the second argument indicate that it is optional. The Range property returns a reference to a Range object.







    2. Right-click the Range property name in the Members list, and click Copy on the shortcut menu.




    3. Click the View menu, and click Immediate Window.




    4. Right-click the Immediate window, and click Paste.


      This is equivalent to using the Complete Word command to enter the function name.




    5. After the Range property, type an opening parenthesis (Visual Basic will display the argument list), and then type "B2" followed by a closing parenthesis and a period. Then type Select.


      The complete statement is Range("B2").Select. You need the quotation marks around the range definition because this is the name of the range, not the item number of a member of a collection.




    6. Press Enter to select cell B2 on the active worksheet.




    7. Type Range("B2:H2").Select and press Enter.


      The first argument of the range property can be a multicell range. In fact, it can be anything that Excel recognizes.







    8. Type Range("H14").Select and press Enter to select the lower-right corner of the list of values. Then type Range(Selection, "B2").Select and press Enter.


      This selects the range from cell H14 (the current selection) to cell B2 (the upper left cell of the list). The arguments to the Range property do not have to be strings; they can also be references to range objects. A common use of the two-argument form of the Range property is to select the range that extends from the currently selected range to some fixed point at the top of the worksheet.




    9. Type ?Selection.Count and press Enter.


      The number 91 appears in the Immediate window. There are 91 cells in the currently selected range. If you don’t specify otherwise, Excel treats a range object as a collection of cells. If you want to know the number of rows or columns in the range, you can do that by using specialized properties, as you will learn in the section titled “Refer to a Range as a Collection of Rows or Columns,” later in this chapter.






      Tip 

      As you learned in Chapter 3, typing a question mark before an expression in the Immediate window allows you to display the value of that expression.





    The Range property is a flexible way of establishing a link to an arbitrary Range object. You can use either a single text string that contains any valid reference as an argument to the Range property or two arguments that define the end points of a rectangular range. Once you have the resulting reference to a range object, you can use any of the members that appear in Object Browser for the Range class.





    Refer to a Range as a Collection of Cells


    Multiple worksheets can exist in a workbook, and the Worksheets collection is defined as an object class. A Worksheets object has a list of methods and properties that is separate from a Worksheet object.


    Similarly, multiple cells exist on a worksheet. You might expect that Excel would have a Cells collection object. But a collection of cells is more complicated than a collection of worksheets because cells come in two dimensions-rows and columns. For example, you can think of the range A1:B3 as a collection of six cells, as a collection of three rows, or as a collection of two columns.


    Excel therefore has three properties that look at a range as a collection. The first of these-the Cells property-returns a collection of cells. However, this is not a separate class. The result of the Cells property is still a Range object, and it can use any of the methods or properties of any other Range object. Because Excel thinks of any range, by default, as a collection of cells, you typically use the Cells property as an alternative to the Range property-using numbers, rather than text strings.




    1. In the Object Browser, with the <globals> object selected in the list of classes, select the Cells property from the list of members.


      The description at the bottom of the Object Browser indicates that the Cells property returns a Range object.







    2. In the Immediate window, type Cells.Select and press Enter.


      This selects all the cells on the worksheet. This is equivalent to clicking the box at the upper left corner of the worksheet, between the column A heading and the row 1 heading.







    3. Type Cells.Item(5).Select and press Enter.




    This selects cell E1, the fifth cell in the first row. The Cells property returns the range of all the cells on the worksheet as a collection. An individual item in the collection is a cell.







    1. Type Cells.Item(16383).Select and press Enter.


      This selects cell XFC1, the next to the last cell in the first row. Excel 2007 now allows 16384 cells in a single row.







    2. Type Cells.Item(16385).Select and press Enter.


      This selects cell A2, the first cell in the second row. When you use a single number to select an item in the Cells collection, the number wraps at the end of each row. Since each row of the worksheet contains 16384 cells, cell 16385 is the first cell on the second row.




    3. Type Cells.Item(3,2).Select and press Enter.


      This selects cell B3, the third row and second column in the worksheet. Unlike most other collections, the Cells collection allows you to specify an item by using both the row and column values.









      On The CD-Important 

      In previous versions of Excel, the expression Cells.Item(257) referred to cell A2. In Excel 2007, it now refers to cell IW1, the 257th cell in the first row. In order to write macros that work in multiple versions, you should always use the row and column specification in the Cells function. Another consequence of the larger size of the worksheet is that you cannot use the expression Cells.Count to retrieve the number of cells on the worksheet, because the number is too big. This is unlikely to ever be a problem, but it illustrates the expanded size of the worksheet grid.





    4. Type Cells.Item(1048576,16384).Select and press Enter.


      This selects cell XFD1048576, the bottom right cell in the worksheet. In case you wonder, these bizarre-looking numbers are really simple powers of 2. You could select the same cell by using the expression Cells.Item(2^20,2^14). You could also use the Range property-Range("XFD1048576").







    5. Type Cells(1).Select and press Enter to select cell A1.




    As with other collections, when you use the Cells property to get a collection of cells, you can leave out the Item method, and simply put the argument after the Cells property. The expression Cells(1) is equivalent to Cells.Item(1), which is equivalent to Range("A1"). All these expressions can be used interchangeably.





    Refer to a Range as a Collection of Rows or Columns


    In addition to referring to the worksheet range as a collection of cells, you can also think of it as a collection of rows or as a collection of columns. Analogous to the Cells property, the Rows property returns a collection of rows and the Columns property returns a collection of columns. These properties return collections, do not have their own object classes, and return Range objects.




    1. In the Object Browser, with the <globals> object selected in the list of classes, select the Columns property in the list of Members.


      The description shows that this property, similar to the Range property and the Cells property, returns a Range object.







    2. In the Immediate window, type Columns.Select and press Enter.


      This selects all the cells on the worksheet, exactly the same as Cells.Select. The difference between the two properties appears when you use the Item property to index into a single item in the collection.




    3. Type Columns(3).Select and press Enter.


      This selects column C, the third column on the worksheet.







    4. Type Columns("D").Select and press Enter.


      This selects column D. When you specify a column by letter, you are giving the name of the item and must enclose it in quotation marks.







    5. Type Columns("B:H").Select and press Enter.


      This selects the range of columns from B through H. The only way to specify a range of columns within the collection is by using the column letter names.







    6. Type Rows(2).Select and press Enter.


      This selects row 2. With rows, the name of an item is also a number. The expressions Rows(2) and Rows("2") are functionally equivalent.








    7. Type Rows("3:14").Select and press Enter.


      This selects a range of rows. The only way to specify a range of rows within the collection is by using the row numbers as a name-that is, by enclosing them in quotation marks.







    The <globals> object in the Object Browser includes three properties that return all the cells of a worksheet-Cells, Columns, and Rows. In each case, you get a reference to a Range object, but the properties return that object as a collection of cells, columns, or rows, respectively. There are no object classes for Cells, Columns, and Rows. These are simply different ways of representing the same Range object.





    Refer to a Range Based on the Active Cell


    Many times when writing a macro you want to refer to a range that is somehow related to the active cell or to the current selection. The macro recorder uses the Selection property to refer to the selected range and the ActiveCell property to refer to the one active cell. A Range object has useful properties that can extend the active cell or the selection to include particularly useful ranges.




    1. In the Immediate window, type Range("B2").Select and press Enter.


      This selects the upper left cell of the sample list.




    2. In the Object Browser, with the <globals> object selected in the Classes list, select the ActiveCell property.


      The description at the bottom of the Object Browser shows that this property returns a Range object.




    3. In the Immediate window, click the Edit menu, and then click Complete Word. In the list of members, click ActiveCell.






      Tip 

      When you use the Complete Word command at the beginning of a statement- whether in a macro or in the Immediate window-the Auto List displays all the members of the <globals> object. If you like using the keyboard, you can press Ctrl+Space to display the list of members, type partial words and use arrow keys to select the desired member, and then press the Tab key to insert the member into the statement.








    4. Type a period ( . ). Then type CurrentRegion.Select to create the statement ActiveCell.CurrentRegion.Select, and then press Enter.


      This selects the entire sample list. The CurrentRegion property selects a rectangular range that includes the original cell and is surrounded by either blank cells or the edge of the worksheet. It is hard to overstate the usefulness of the CurrentRegion property.







    5. Type ActiveCell.EntireColumn.Select and press Enter.


      This selects all of column B because the active cell was cell B2. Because the starting range was the active cell-not the entire selection-the EntireColumn property returned a reference to only one column. Because the initial active cell-B2-is still within the selection, it is still the active cell.







    6. In the Object Browser, with the <globals> object selected, select the Selection property in the list of members.


      The description at the bottom indicates that the Selection property returns an object, not a Range. The Selection property returns a Range object only when cells are selected. If shapes or parts of a chart are selected, this global property returns a different object type. Because the Selection object can return a different object type at different times, it does not display an Auto List the way the ActiveCell property does.







    7. In the Immediate window, type Selection.CurrentRegion.Select and press Enter.


      This selects the range B1:H14-the entire sample list plus the one row above it. It’s acting the same as if the current selection were only cell B1. When you use the CurrentRegion property with a multicell range as the starting point, it ignores everything except the top-left cell of the range as it calculates the current region.







    8. Type Range("A2").Activate and press Enter.


      Because the specified cell is outside of the current selection, the Activate method behaves the same as Select.








    9. Type Selection.EntireRow.Select and press Enter.


      This selects all of row 2. Because the selection is a single cell, you would get exactly the same result by using ActiveCell.EntireRow.Select.




    10. Type Range("B2").Activate and press Enter.


      Because the specified cell is within the selected range, this statement does not change the selection, but it does move the active cell to a new location within the range. If you activate a cell that is not within the current selection, the Activate method behaves the same as Select.







    The Selection and ActiveCell properties are useful as starting points for deriving other ranges. The ActiveCell property always returns a reference to a Range object, and therefore displays a convenient Auto List when you are entering a statement. The Selection property returns a reference to a Range object only when a range is actually selected, and thus it does not display an Auto List.





    Refer to Subsets of a Range


    When you reference a range by using a property from the <globals> object-for example, Range, Cells, Columns, or Rows-you get a range based on the entire active worksheet. These same properties also exist as properties of a Range object. The easiest way to work with properties of a Range object is to declare a variable as a Range. Then the Auto List displays the methods and properties as you type a statement, even if you use the Selection property-which does not display Auto Lists-to assign the range to the variable.




    1. In the Visual Basic editor, click Insert, and then click Module.




    2. Type Sub TestRange and press Enter.


      Visual Basic adds parentheses and an End Sub statement.




    3. Type Dim myRange As Range and press F8 twice to initialize the variable.




    4. In the Immediate window, type Set myRange = Range("B2") and press Enter. Then type myRange.Select and press Enter again.


      This selects cell B2, confirming that the variable contains a reference to that cell.




    5. Click the Object Browser button. In the list of classes, select the Range class. Then in the list of members, select the Range property.


      This Range property appears very similar to the Range property of the <globals> object. It behaves, however, relative to a starting range.




    6. In the Immediate window, type myRange.Range("A1:G1").Select and press Enter.


      This does not select the range A1:G1. Rather, it selects the range B2:H2. If you think of cell B2 as the upper left cell of an imaginary worksheet, the range A1:G1 of that imaginary worksheet would correspond to the range B2:H2 of the real worksheet.







    7. Type Set myRange = myRange.CurrentRegion and press Enter. Then type myRange.Select and press Enter again.


      Given that myRange already referred to cell B2, which is inside the sample list, the first statement references the entire sample list, and the second confirms that the variable contains a reference to the appropriate range.




    8. Type myRange.Cells.Item(2,6).Select and press Enter.


      This selects the first data value in the Units column-row 2 and column 6 within the data region.







    9. Type myRange.Rows(2).Select and press Enter.


      This selects the second row of values in the list, even though they exist in row 3 of the worksheet. A single row from the collection referenced by the global Rows property includes the entire row of the worksheet; the Rows property of a Range object includes only the cells within the range.




    10. Type myRange.Rows(myRange.Rows.Count).Select and press Enter.


      This selects the last row of the list. Because the Rows property returns a collection, you can use the Count property to find the number of items in the collection. That count can then serve as an index into the same collection.







    When you use the Range, Cells, Columns, or Rows properties as members of a Range object, the resulting ranges are relative to the upper-left cell of that range. Contrast this to when you use the same functions from the global group-or as members of the Application object or of a Worksheet object. With anything other than a Range object, these functions return ranges that are relative to the upper-left cell of the worksheet.





    Refer to a Relative Range


    Excel has other properties that can calculate a new range based on one or more existing ranges. Two of these properties do not exist in the list of global members; they exist only as members of a Range object: the Offset property references a range shifted down, up, left, or right from a starting range, and the Resize property references a range with a different number of rows or columns from a starting range. An additional property, the Intersect property, does appear in the list of global members. It is particularly valuable when you need to “trim away” part of a range, such as when you want to remove the header row from the current region.




    1. In the Object Browser, select Range in the Classes list. Then, in the Members list, select the Offset property.


      The description indicates that this property has two arguments-RowOffset and ColumnOffset, both of which are optional-and that it returns a Range object.







    2. In the Immediate window, type myRange.Offset(1).Select and press Enter.


      This selects a range identical in size and shape to the range stored in the variable, but shifted down by one cell. The first argument to the Offset property indicates the number of rows down to shift the range; the second argument indicates how many columns to the right to shift the range. Omitting an argument is the same as using zero and does not shift the range in that direction.










      Tip 

      To understand the Offset property, think of yourself as standing on the upper-left cell of the initial range. Face the bottom of the worksheet, and step forward the number of steps specified in the first argument. Zero steps means no movement. Negative steps are backwards. Then face the right side of the worksheet and do the same with the number of steps specified in the second argument. The resulting range is the same size and shape as the original one, but it begins on the cell you end up standing on.





    3. In the Object Browser, select Range in the Classes list. Then, in the list of members, select the Resize property.


      The description indicates that this property has two arguments-RowSize and ColumnSize, both of which are optional-and that it returns a Range object.




    4. In the Immediate window, type myRange.Offset(1).Resize(5).Select and press Enter.


      This selects the first five rows of data. The Offset property shifts the range down to omit the heading row. The Resize function changes the size of the resulting range. The first argument to the Resize property is the number of rows for the result range; the second is the number of columns for the result range. Omitting an argument is the same as keeping the size of the original range for that direction.







    5. Type myRange.Offset(1,5).Resize(1,2).Select and press Enter.


      This selects the range G3:H3, which happens to be the numeric values in the first row of the body of the list.










      Tip 

      The combined functionality of the Offset and Resize properties is equivalent to that of the OFFSET function available on worksheets.





    6. In the Object Browser, with the <globals> object selected in the list of classes, select the Intersect method in the Members list.


      The description shows that this method returns a Range object, but it also shows that it can take up to 30 arguments! In practice, you usually use two arguments, and you can see that the first two arguments are required. The Object Browser shows that the first two arguments must be range objects, but if you use more than two arguments, they do all need to be ranges. You can use the Intersect method in conjunction with the Offset method to remove headings from the current region.







    7. In the Immediate window, type Intersect(myRange, myRange.Offset(1)).Select and press Enter.


      This selects the range B3:H14, which is the entire list except the heading row. You often need to manipulate the body of a list separately from the heading. By using a range as the first argument of the Intersect method, and then an offset version of the range as the second argument, you can trim off portions of the range.








    8. Press F5 to end the macro.




    The Offset and Resize properties, along with the EntireRow, EntireColumn, and CurrentRegion properties and the Intersect method, provide you with flexible tools for calculating new Range objects based on an original starting range. Often, the easiest way to work within a range is to first use the CurrentRegion property to establish the base range, and then use the Offset property and the Intersect method to manipulate the range.















    Understanding XML DTDs













    Understanding XML DTDs


    Because DTD conformance is important for Tiles tags, a brief overview of how XML DTDs work is given here. DTDs specify a set of tags and attributes that make up a specific XML document type. DTDs also specify the order in which tags must be placed in the file, and the relationship between tags. For example, a tag definition defines what other tags can be nested inside of it, how many occurrences can be nested, and in what order the nesting can occur. Additionally, DTDs define which tag attributes are required and which are optional.


    Each tag described in this chapter has a DTD Definition section that lists the tag's definition in the Tiles configuration file DTD. The definitions will be similar to the one shown in the following snippet:



    <!ELEMENT definition (icon?,display-name?,description?,put*,putList*)>


    This example defines a definition tag and the tags that can be nested inside of it. According to the definition, the tag can have nested icon, display-name, description, put, and putList tags. The question mark (?) and asterisk (*) characters following the nested tags' names indicate the number of times the nested tag can be nested. The ? character indicates that the tag can be nested zero or one time. The * character indicates that the tag can be nested zero or more (unlimited) times. A plus (+) character indicates that the tag must be nested at least once and as many times as you'd like. No trailing character means that the tag must be nested exactly once and no more. If no tags can be nested inside the defined tag, EMPTY is used to denote that, as shown next:


    <!ELEMENT set-property EMPTY>













    Chapter 6. Formatting Data with Tags






















     Print  
     E-Mail
     
     Add
    Note    Add
    Bookmark
       














    JSTL:
    JSP Standard Tag Library Kick Start
    By
    Jeff Heaton

    Table
    of Contents









    Chapter 6. Formatting Data with
    Tags


    JSTL provides several tags that greatly
    simplify the formatting and parsing of data. Tags are provided
    that allow you to format numbers, percents, dates, and
    currencies. By using these tags, you can fine-tune the output
    of your data, without the need for customized display
    programming.


    Web applications must frequently process data
    entered by a user. Data from a user always arrives as a
    string. However, this string is often a number, date,
    percentage, or other data type. JSTL provides parsing tags
    that enable you to take the strings a user enters and parse
    them into other data types.


    In this chapter, we examine both of these
    issues. We provide several examples that illustrate practical
    application of data formatting and parsing. We begin by
    examining the process of data formatting.











       Print  
       E-Mail
       
       Add
      Note    Add
      Bookmark
         




      Top

      [0672324504/ch06]






       
       

      Chapter 13 Functions



      [ Team LiB ]





      Chapter 13 Functions


      1)

      Write a stored function called new_student_id that takes in no parameters and returns a student.student_id%TYPE. The value returned will be used when inserting a new student into the CTA application. It will be derived by using the formula: student_id_seq.NEXTVAL.

      A1:

      Answer: Your answer should look similar to the following:



      CREATE OR REPLACE FUNCTION new_student_id
      RETURN student.student_id%TYPE
      AS
      v_student_id student.student_id%TYPE;
      BEGIN
      SELECT student_id_seq.NEXTVAL
      INTO v_student_id
      FROM dual;
      RETURN(v_student_id);
      END;
      2)

      Write a stored function called zip_does_not_exist that takes in a zipcode.zip%TYPE and returns a Boolean. The function will return TRUE if the zipcode passed into it does not exist. It will return a FALSE if the zipcode exists. Hint: An example of how it might be used is as follows:



      DECLARE
      cons_zip CONSTANT zipcode.zip%TYPE := '&sv_zipcode';
      e_zipcode_is_not_valid EXCEPTION;
      BEGIN
      IF zipcode_does_not_exist(cons_zip);
      THEN
      RAISE e_zipcode_is_not_valid;
      ELSE
      -- An insert of an instructor's record which
      -- makes use of the checked zipcode might go here.
      NULL;
      END IF;
      EXCEPTION
      WHEN e_zipcode_is_not_valid THEN
      RAISE_APPLICATION_ERROR
      (-20003, 'Could not find zipcode '||
      cons_zip||'.');
      END;
      A2:

      Answer: Your answer should look similar to the following:



      CREATE OR REPLACE FUNCTION zipcode_does_not_exist
      (i_zipcode IN zipcode.zip%TYPE)
      RETURN BOOLEAN
      AS
      v_dummy char(1);
      BEGIN
      SELECT NULL
      INTO v_dummy
      FROM zipcode
      WHERE zip = i_zipcode;

      -- meaning the zipcode does exits
      RETURN FALSE;
      EXCEPTION
      WHEN OTHERS THEN
      -- the select statement above will cause an exception
      -- to be raised if the zipcode is not in the database.
      RETURN TRUE;
      END zipcode_does_not_exist;
      3)

      Create a new function. For a given instructor, determine how many sections he or she is teaching. If the number is greater or equal to 3, return a message saying the instructor needs a vacation. Otherwise, return a message saying how many sections this instructor is teaching.

      A3:

      Answer: Your answer should look similar to the following:



      CREATE OR REPLACE FUNCTION instructor_status
      (i_first_name IN instructor.first_name%TYPE,
      i_last_name IN instructor.last_name%TYPE)
      RETURN VARCHAR2
      AS
      v_instructor_id instructor.instructor_id%TYPE;
      v_section_count NUMBER;
      v_status VARCHAR2(100);
      BEGIN
      SELECT instructor_id
      INTO v_instructor_id
      FROM instructor
      WHERE first_name = i_first_name
      AND last_name = i_last_name;

      SELECT COUNT(*)
      INTO v_section_count
      FROM section
      WHERE instructor_id = v_instructor_id;

      IF v_section_count >= 3 THEN
      v_status :=
      'The instructor '||i_first_name||' '||
      i_last_name||' is teaching '||v_section_count||
      ' and needs a vaction.';
      ELSE
      v_status :=
      'The instructor '||i_first_name||' '||
      i_last_name||' is teaching '||v_section_count||
      ' courses.';
      END IF;
      RETURN v_status;
      EXCEPTION
      WHEN NO_DATA_FOUND THEN
      -- note that either of the SELECT statements can raise
      -- this exception
      v_status :=
      'The instructor '||i_first_name||' '||
      i_last_name||' is not shown to be teaching'||
      ' any courses.';
      RETURN v_status;
      WHEN OTHERS THEN
      v_status :=
      'There has been in an error in the function.';
      RETURN v_status;
      END;

      Test the function as follows:



      SELECT instructor_status(first_name, last_name)
      FROM instructor;
      \






        [ Team LiB ]



        Getting Started










        Getting Started


        The sample for this chapter is a login/registration page, which we will eventually plug in to our final sample application. This page will do one of two things: log in existing users or register new users. When new users arrive at the page, they will see a Register link at the top of the login form. When they click the Register link, the necessary form elements for registration will appear in the page, above the username and password form elements (see Figure 12.1).




        Figure 12.1. Our sample login form with optional, inline registration.









        While users begin filling out their user information, we will be checking their information against the existing information in the database to let them know if the information is already in use or present them with a visual identifier to inform them if they are in the clear. One way in which we are going to visually represent this information to the users is by changing the background color of the specific form element to red if it is already in use, or green if the element is valid. These visual identifiers obviously do not appear well in black and white, but the next identifiers will. They are simple string messages that state the status of the data next to the form element into which users are entering information, as shown in Figure 12.2.




        Figure 12.2. Validation provides visual feedback to users as they enter information.









        As you can see in Figure 12.2, we are not validating the first and last names. I chose to allow the same person to create a new account, as long as she uses a new email address and username (hence the reason we are validating these two fields). If we wanted to verify the first and last names, we would have to verify them together because there are a lot of people who share the same first name and sometimes the same last name. Another thing to be aware of is that we are running validation on the form elements only when the form is in registration mode. We only want to display feedback to users who are new to the application because we do not want to display verification on sensitive information that we are storing in the database.


        This form may look very simple, but there is quite a bit of logic to make it a smart form. We first have the actual login/registration page, which contains a JavaScript validation object and fires a validation call each time specified form elements are exited. Behind the front-end code is a PHP object that runs validation on a user database when our JavaScript validation object makes an XHR to it. As if that were not enough, we have an instance of the PHP object included in the login/registration page for logging in existing users and registering new users. This chapter will cover each aspect of this process, but focus primarily on where we use Ajax to connect the JavaScript object to the back end. Let's get started by creating the JavaScript validation object.












        Section C.28. Report







        C.28. Report

        If JSLint is able to complete its scan, it generates a function report. It lists the following for each function:

        • The line number on which it starts.

        • Its name. In the case of anonymous functions, JSLint will "guess" the name.

        • The parameters.

        • Closure: the variables and parameters that are declared in the function that are used by its inner functions.

        • Variables: the variables declared in the function that are used only by the function.

        • Unused: the variables that are declared in the function that are not used. This may be an indication of an error.

        • Outer: variables used by this function that are declared in another function.

        • Global: global variables that are used by this function.

        • Label: statement labels that are used by this function.

        The report will also include a list of all of the member names that were used.








        2.2 The Instance and the Database: Starting an Oracle Database












        for Ru-Brd & DownSky

        size=+0>

        2.2 The Instance and the Database: Starting an Oracle Database


        Many people use the terms
        instance and database interchangeably, but the instance and
        the database are actually separate entities. Let's look at what happens
        when the database is started from the Server Manager utility
        (svrmgrl, svrmgrm, or svrmgr30 for Oracle8 on Windows
        NT). The startup process follows:




        • The background processes are started.



        • The System Global Area (SGA) is allocated in memory.



        • The background processes open the various files.



        • The database is ready for use.


        During the startup, messages are displayed that notify you of what is
        happening. Here is a sample startup sequence:

        SVRMGR>  startup
        ORACLE instance started.
        Total System Global Area 11865072 bytes
        Fixed Size 33708 bytes
        Variable Size 10672196 bytes
        Database Buffers 1126400 bytes
        Redo Buffers 32768 bytes
        Database mounted.
        Database opened.

        You can see that the instance is started before the files that
        constitute the database are opened. The instance consists of the
        background processes and the SGA. The SGA totals are listed before the
        database is started but after the "ORACLE instance started" message. The
        database is the collection of logical objects and physical files
        necessary to support the system, and the database
        system
        is the instance, SGA, and files. Figure
        2.1 shows the components of the database system after the database has
        been started.



        Figure 2.1. Components of the database system after
        startup

        Now, while what we've said is technically accurate, you'll find the
        actual usage at Oracle sites to be quite different. Many people use the
        terms "instance," "database," and "database system" interchangeably. In
        this context, they are referring to the entire system: the background
        processes, the System Global Area, and the data, control, and redo log files.








        What the Oracle System Files Really Are


        The Oracle components we discuss in this chapter are all actually
        just operating system files (except for the SGA, which is a
        memory-resident structure). The physical database files must be
        protected at the operating system level from intrusive access by any
        user. These files are never written to directly by any user
        application, though they are written to on behalf of user processes.
        Oracle owns these files and will manage them. Users should never
        have any operating system privileges on these files beyond the
        privileges Oracle instructs you to set in the installation guide for
        your system.


        You name the physical database files when the database is
        initialized and when additional datafiles are added. There are no
        mandatory names for any of these files. You, as the DBA, can select
        their names. The only restrictions are that the names cannot contain
        blanks or special characters, and they must conform to the operating
        system name requirements, and cannot exceed 30 characters in
        length.











        for Ru-Brd & DownSky




        Lab 4.2 Exercise Answers



        [ Team LiB ]





        Lab 4.2 Exercise Answers


        This section gives you some suggested answers to the questions in Lab 4.2, with discussion related to how those answers resulted. The most important thing to realize is whether your answer works. You should figure out the implications of the answers here and what the effects are from any different answers you may come up with.


        4.2.1 Answers


        a)

        If you issue the following command, what would you expect to see? Why?



        SELECT *
        FROM student
        WHERE last_name = 'Norbu';
        A1:

        Answer: You will not be able to see any data because the ROLLBACK to (SAVEPOINT) B has undone the last insert statement where the student 'Norbu' was inserted.

        b)

        Try it. What happened? Why?

        A2:

        Answer: When you issue this command, you will get the message "no rows selected."



        Three students were inserted in this PL/SQL block. First, Sonam in SAVEPOINT A, then Tashi in SAVEPOINT B, and finally Norbu was inserted in SAVEPOINT C. Then when the command ROLLBACK to B was issued, the insert of Norbu was undone.


        Now issue





        ROLLBACK to SAVEPOINT A;

        c)

        What happened?

        A1:

        Answer: The insert in SAVEPOINT B was just undone. This deleted the insert of Tashi who was inserted in SAVEPOINT B.

        d)

        If you issue the following, what do you expect to see?



        SELECT last_name
        FROM student
        WHERE last_name = 'Tashi';
        A2:

        Answer: You will see the data for Tashi.

        e)

        Issue the command and explain your findings.

        A3:

        Answer: You will see one entry for Tashi, as follows:



        LAST_NAME
        -------------------------
        Tashi

        Tashi was the only student that was successfully entered into the database. The ROLLBACK to SAVEPOINT A undid the insert statement for Norbu and Sonam.




        A Single PL/SQL Block Can Contain Multiple Transactions


        For Example:





        Declare
        v_Counter NUMBER;
        BEGIN
        v_counter := 0;
        FOR i IN 1..100
        LOOP
        v_counter := v_counter + 1;
        IF v_counter = 10
        THEN
        COMMIT;
        v_counter := 0;
        END IF;
        END LOOP;
        END;

        In this example, as soon as the value of v_counter becomes equal to 10, the work is committed. So, there will be a total of 10 transactions contained in this one PL/SQL block.






          [ Team LiB ]



          Section 16.4.&nbsp; Dynamic PL/SQL









          16.4. Dynamic PL/SQL









          Dynamic PL/SQL offers some of the most interesting and challenging coding opportunities. Think of it: while a user is running your application, you can take advantage of NDS to do any of the following:


          • Create a program, including a package that contains globally accessible data structures

          • Obtain (and modify) by name the value of global variables

          • Call functions and procedures whose names are not known at compile time


          I have used this technique to build very flexible code generators, softcoded calculation engines for users, and much more. Dynamic PL/SQL allows you to work at a higher level of generality, which can be both challenging and exhilarating.


          There are some rules and tips you need to keep in mind when working with dynamic PL/SQL blocks and NDS:


          • The dynamic string must be a valid PL/SQL block. It must start with the DECLARE or BEGIN keyword, and end with an END statement and semicolon. The string will not be considered PL/SQL code unless it ends with a semicolon.

          • In your dynamic block, you can access only PL/SQL code elements that have global scope (standalone functions and procedures, and elements defined in the specification of a package). Dynamic PL/SQL blocks execute outside the scope of the local enclosing block.

          • Errors raised within a dynamic PL/SQL block can be trapped and handled by the local block in which the string was run with the EXECUTE IMMEDIATE statement.


          Let's explore these rules. First, I will build a little utility to execute dynamic PL/SQL:



          /* File on web: dynplsql.sp */
          CREATE OR REPLACE PROCEDURE dynPLSQL (blk IN VARCHAR2)
          IS
          BEGIN
          EXECUTE IMMEDIATE
          'BEGIN ' || RTRIM (blk, ';') || '; END;';
          END;



          This one program encapsulates many of the rules mentioned previously for PL/SQL execution. By enclosing the string within a BEGIN-END anonymous block, I guarantee that whatever I pass in will be executed as a valid PL/SQL block. For instance, I can execute the calc_totals procedure dynamically as simply as this:



          SQL> exec dynPLSQL ('calc_totals');



          Now let's use this program to examine what kind of data structures you can reference within a dynamic PL/SQL block. In the following anonymous block, I want to use dynamic SQL to assign a value of 5 to the local variable num:



          <<dynamic>>
          DECLARE
          num NUMBER;
          BEGIN
          dynPLSQL ('num := 5');
          END;



          This string is executed within its own BEGIN-END block, which appears to be a nested block within the anonymous block named "dynamic." Yet when I execute this script, I receive the following error:



          PLS-00201: identifier 'NUM' must be declared
          ORA-06512: at "SCOTT.DYNPLSQL", line 4



          The PL/SQL engine is unable to resolve the reference to the variable named num. I get the same error even if I qualify the variable name with its block name:



          <<dynamic>>
          DECLARE
          num NUMBER;
          BEGIN
          /* Also causes a PLS-00302 error! */
          dynPLSQL ('dynamic.num := 5');
          END;



          Now suppose that I define the num variable inside a package called dynamic:



          CREATE OR REPLACE PACKAGE dynamic
          IS
          num NUMBER;
          END;



          I can now successfully execute the dynamic assignment to this newly defined variable :



          BEGIN
          dynPLSQL ('dynamic.num := 5');
          END;



          What's the difference between these two pieces of data? In my first attempt, the variable num is defined locally in the anonymous PL/SQL block. In my second attempt, num is a public global variable defined in the dynamic package. This distinction makes all the difference with dynamic PL/SQL.


          It turns out that a dynamically constructed and executed PL/SQL block is not treated as a nested block; instead, it is handled as if it were a procedure or function called from within the current block. So any variables local to the current or enclosing blocks are not recognized in the dynamic PL/SQL block; you can make references only to globally defined programs and data structures. These PL/SQL elements include standalone functions and procedures and any elements defined in the specification of a package.


          Fortunately, the dynamic block is executed within the context of the calling block. If you have an exception section within the calling block, it will trap exceptions raised in the dynamic block. So if I execute this anonymous block in SQL*Plus:



          BEGIN
          dynPLSQL ('undefined.packagevar := ''abc''');
          EXCEPTION
          WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE (SQLCODE);
          END;



          I will not get an unhandled exception.


          The assignment performed in this anonymous block is an example of indirect referencing
          . I don't reference the variable directly, but instead do so by specifying the name of the variable. The Oracle Forms Builder product (formerly known as SQL*Forms and Oracle Forms) offers an implementation of indirect referencing with the NAME_IN and COPY programs. This feature allows developers to build logic that can be shared across all forms in the application. PL/SQL does not support indirect referencing, but you can implement it with dynamic PL/SQL. See the dynvar.pkg file on the book's web site for an example of such an implementation.



          The following sections offer a few examples of dynamic PL/SQL to spark your interest and, perhaps, inspire your creativity.



          16.4.1. Replace Repetitive Code with Dynamic Block




          This is a true story, I kid you not. During a consulting stint at an insurance company here in Chicago, I was asked to see what I could do about a particularly vexing program. It was very large and continually increased in sizesoon it would be too large to even compile. Much to my amazement, this is what the program looked like:



          CREATE OR REPLACE PROCEDURE process_line (line IN INTEGER)
          IS
          BEGIN
          IF line = 1 THEN process_line1;
          ELSIF line = 2 THEN process_line2;
          ...
          ELSIF line = 514 THEN process_line514;
          ...
          ELSIF line = 2057 THEN process_line2057;
          END IF;
          END;



          Each one of those line numbers represented fine print in an insurance policy that helped the company achieve its primary objective (minimizing the payment of claims). For each line number, there was a "process_line" program that handled those details. And as the insurance company added more and more exceptions to the policy, the program got bigger and bigger. Not a very scalable approach to programming!


          To avoid this kind of mess, a programmer should be on the lookout for repetition of code. If you can detect a pattern, you can either create a reusable program to encapsulate that pattern, or you can explore the possibility of expressing that pattern as a dynamic SQL construction.


          At the time, I fixed the problem using DBMS_SQL, but dynamic SQL would have been a perfect match. Here's the NDS implementation:



          CREATE OR REPLACE PROCEDURE process_line (line IN INTEGER)
          IS
          BEGIN
          EXECUTE IMMEDIATE
          'BEGIN process_line' || line || '; END;';
          END;



          From thousands of lines of code down to one executable statement! Of course, in most cases, identification of the pattern and conversion of that pattern into dynamic SQL will not be so straightforward. Still, the potential gains are enormous.




          16.4.2. Implement Method 4 Dynamic SQL in NDS






          Method 4 dynamic SQL, introduced early in this chapter, means that at compile time, you don't know how many columns you will be querying and/or how many bind variables will need to be set. This level of uncertainty greatly complicates the code you will have to write. This section examines the kind of code you need to write to implement dynamic SQL Method 4 with native dynamic SQL. Later in the chapter, we review how you can implement Method 4 with DBMS_SQL.


          First, let's take a look at an example that will drive home the complexities involved. Suppose that I want to retrieve a single row by a dynamic WHERE clause and from a table whose name is specified at runtime. My program would, at first glance, look something like this:



          CREATE OR REPLACE PROCEDURE get_rows (
          table_in IN VARCHAR2
          , where_in IN VARCHAR2
          )
          IS
          BEGIN
          EXECUTE IMMEDIATE
          'SELECT * FROM ' || table_in || ' WHERE ' || where_in
          INTO l_row;
          END;



          No matter what table name I pass to this procedure, "SELECT *" will retrieve all the columns for all the rows. "So what's the problem?" you might be asking. The problem may be found in the INTO clause. I reference a variable named l_row. Yet I must declare that variable before I can compile and run my code. This variable must be a record, whose structure matches that of the table. Yet I cannot declare a variable based on a dynamic record type.


          OK, so maybe what I should do is avoid hardcoding the record variable and instead select into individual variables, and then just make that list variable, as in:



          BEGIN
          EXECUTE IMMEDIATE
          'SELECT * FROM ' || table_in || ' WHERE ' || where_in
          INTO || l_variable_list;
          END get_rows;



          Conceptually, that may sound fine, but it simply won't work. The bottom line regarding Method 4 and EXECUTE IMMEDIATE is that the INTO and USING clauses of this statement are static, not dynamic. So is it impossible to implement Method 4 requirements with NDS? No, it is, in fact, possible, but we will have to switch gears, and use dynamic PL/SQL.


          Consider the following revised implementation of get_rows:



          CREATE OR REPLACE PROCEDURE get_rows (
          table_in IN VARCHAR2
          , where_in IN VARCHAR2
          )
          IS
          BEGIN
          EXECUTE IMMEDIATE
          'DECLARE
          l_row ' || table_in || '%ROWTYPE;
          BEGIN
          SELECT * INTO l_row

          FROM ' || table_in || ' WHERE ' || where_in || ';
          END;';
          END get_rows;



          Now the EXECUTE IMMEDIATE runs a dynamic PL/SQL block. All the static parts of this block are in bold. The highlights of this implementation include the following:


          • Because I have switched to dynamic PL/SQL, I can now declare a local variable, l_row, as a record based on the table with %ROWTYPE. This means that my SELECT statement will fetch a row from the variable table and deposit it successfully into the record.

          • The SELECT statement is no longer dynamic, within the context of the dynamic block. By making the whole block dynamic, I can actually simplify the SQL.

          • I can insert carriage returns inside my literal strings; as long as the resulting code is valid, Oracle won't care, and it will make code easier to read.


          Shifting from dynamic SQL to dynamic PL/SQL is the key step to implementing Method 4 with NDS. What I have shown you is a very simple example. There are, of course, complexities.


          If you have to deal with a dynamic number of bind variables, you will have to write a parsing routine to ferret out each appearance of the placeholders and replace them with your variable values.


          In addition, if you need to pass the data that you have queried from the dynamic block to the outer, static block, you will run into additional complexities. That outer block will not know the structure of the data you have populated with your query. At this point, however, the implementation becomes very much application-specific.


          For a more complete demonstration of using dynamic PL/SQL to implement Method 4 with NDS, check out the intab9i.sp file available on the book's web site. The intab procedure offers a PL/SQL implementation of "SELECT * FROM table," that is, it queries and displays (via DBMS_OUTPUT) the contents of the specified table.


          While it is possible to implement Method 4 with NDS, it is an awkward and complicated process. This is one dynamic requirement for which DBMS_SQL might provide a better solution. Finally, if you do choose NDS for Method 4, you may still want to take advantage of the DBMS_SQL.DESCRIBE_COLUMNS procedure. Both of these topics are addressed later in this chapter.










            Lab 9.2 Using Cursor for Loops and Nesting Cursors

            Team-Fly
             

             

            Oracle® PL/SQL® Interactive Workbook, Second Edition
            By
            Benjamin Rosenzweig, Elena Silvestrova
            Table of Contents

            Chapter 9. 
            Introduction to Cursors



            Lab 9.2 Using Cursor for Loops and Nesting Cursors



            Lab Objectives


            After this Lab, you will be able to:


            • Use a Cursor FOR Loop

            • Process Nested Cursors


            There is an alternative method of handling cursors. It is called the cursor FOR loop because of the simplified syntax that is used. When using the cursor FOR loop, the process of opening, fetching, and closing is handled implicitly. This makes the blocks much simpler to code and easier to maintain.


            The cursor FOR loop specifies a sequence of statements to be repeated once for each row returned by the cursor. Use the cursor FOR loop if you need to FETCH and PROCESS each and every record from a cursor.





            FOR EXAMPLE


            Assume the existence of a table called log with one column.



            create table table_log
            (description VARCHAR2(250));
            -- ch09_7a.sql
            DECLARE
            CURSOR c_student IS
            SELECT student_id, last_name, first_name
            FROM student
            WHERE student_id < 110;
            BEGIN
            FOR r_student IN c_student
            LOOP
            INSERT INTO table_log
            VALUES(r_student.last_name);
            END LOOP;
            END;


            Lab 9.2 Exercises



            9.2.1 Use a Cursor FOR Loop

            a)

            Write a PL/SQL block that will reduce the cost of all courses by 5% for courses having an enrollment of eight students or more. Use a cursor FOR loop that will update the course table.




            9.2.2 Process Nested Cursors

            Cursors can be nested inside each other. Although this may sound complex, it is really just a loop inside a loop, much like nested loops, which were covered in the previous chapter. If you had one parent cursor and two child cursors, then each time the parent cursor makes a single loop, it will loop through each child cursor once and then begin a second round. In the following two examples, you will encounter a nested cursor with a single child cursor.





            FOR EXAMPLE



            SET SERVEROUTPUT ON
            -- ch09_8a.sql
            1 DECLARE
            2 v_zip zipcode.zip%TYPE;
            3 v_student_flag CHAR;
            4 CURSOR c_zip IS
            5 SELECT zip, city, state
            6 FROM zipcode
            7 WHERE state = 'CT';
            8 CURSOR c_student IS
            9 SELECT first_name, last_name
            10 FROM student
            11 WHERE zip = v_zip;
            12 BEGIN
            13 FOR r_zip IN c_zip
            14 LOOP
            15 v_student_flag := 'N';
            16 v_zip := r_zip.zip;
            17 DBMS_OUTPUT.PUT_LINE(CHR(10));
            18 DBMS_OUTPUT.PUT_LINE('Students living in '||
            19 r_zip.city);
            20 FOR r_student in c_student
            21 LOOP
            22 DBMS_OUTPUT.PUT_LINE(
            23 r_student.first_name||
            24 ' '||r_student.last_name);
            25 v_student_flag := 'Y';
            26 END LOOP;
            27 IF v_student_flag = 'N'
            28 THEN
            29 DBMS_OUTPUT.PUT_LINE
            ('No Students for this zipcode');
            30 END IF;
            31 END LOOP;
            32 END;

            There are two cursors in this example. The first is a cursor of the zipcodes, and the second cursor is a list of students. The variable v_zip is initialized in line 16 to be the zipcode of the current record of the c_zip cursor. The c_ student cursor ties in the c_zip cursor by means of this variable. Thus, when the cursor is processed in lines 2026, it is retrieving students who have the zipcode of the current record for the parent cursor. The parent cursor is processed from lines 1331. Each iteration of the parent cursor will only execute the DBMS_OUTPUT in lines 16 and 17 once. The DBMS_OUTPUT in line 22 will be executed once for each iteration of the child loop, producing a line of output for each student. The DBMS statement in line 29 will only execute if the inner loop did not execute. This was accomplished by setting a variable v_student_flag. The variable is set to N in the beginning of the parent loop. If the child loop executes at least once, the variable will be set to Y. After the child loop has closed, a check is made with an IF statement to determine the value of the variable. If it is still N, then it can be safely concluded that the inner loop did not process. This will then allow the last DBMS statement to execute. Nested cursors are more often parameterized. You will see parameters in cursors explained in depth in Lab 8.3, "Using Parameters in Cursors."


            a)

            Write a PL/SQL block with two cursor FOR loops. The parent cursor will call the student_id, first_name, and last_name from the student table for students with a student_id less than 110 and output one line with this information. For each student, the child cursor will loop through all the courses that the student is enrolled in, outputting the course_no and the description.


            The following is an example of a nested cursor. Review the code.





            FOR EXAMPLE



            SET SERVEROUTPUT ON
            -- ch09_9a.sql
            DECLARE
            v_amount course.cost%TYPE;
            v_instructor_id instructor.instructor_id%TYPE;
            CURSOR c_inst IS
            SELECT first_name, last_name, instructor_id
            FROM instructor;
            CURSOR c_cost IS
            SELECT c.cost
            FROM course c, section s, enrollment e
            WHERE s.instructor_id = v_instructor_id
            AND c.course_no = s.course_no
            AND s.section_id = e.section_id;
            BEGIN
            FOR r_inst IN c_inst
            LOOP
            v_instructor_id := r_inst.instructor_id;
            v_amount := 0;
            DBMS_OUTPUT.PUT_LINE(
            'Amount generated by instructor '||
            r_inst.first_name||' '||r_inst.last_name
            ||' is');
            FOR r_cost IN c_cost
            LOOP
            v_amount := v_amount + NVL(r_cost.cost, 0);
            END LOOP;
            DBMS_OUTPUT.PUT_LINE
            (' '||TO_CHAR(v_amount,'$999,999'));
            END LOOP;
            END;

            b)

            Before you run the preceding code, analyze what it is doing and determine what you think the result would be. Explain what is happening in each phase of the PL/SQL block and what is happening to the variables as control is passing through parent and child cursor.

            c)

            Run the code and see what the result is. Is it what you expected? Explain the difference.



            Lab 9.2 Exercise Answers




            9.2.1 Answers

            a)

            Write a PL/SQL block that will reduce the cost of all courses by 5% for courses having an enrollment of eight students or more. Use a cursor FOR loop that will update the course table.

            A1:

            Answer: Your block should look like this:


            -- ch09_10a.sql
            DECLARE
            CURSOR c_group_discount IS
            SELECT DISTINCT s.course_no
            FROM section s, enrollment e
            WHERE s.section_id = e.section_id
            GROUP BY s.course_no, e.section_id, s.section_id
            HAVING COUNT(*)>=8;
            BEGIN
            FOR r_group_discount IN c_group_discount LOOP
            UPDATE course
            SET cost = cost * .95
            WHERE course_no = r_group_discount.course_no;
            END LOOP;
            COMMIT;
            END;

            The cursor c_group_discount is declared in the declarative section. The proper SQL is used to generate the select statement to answer the question given. The cursor is processed in a FOR loopin each iteration of the loop the SQL update statement will be executed. This means it does not have to be opened, fetched, and closed. Also, it means that a cursor attribute does not have to be used to create an exit condition for the loop that is processing the cursor.





            9.2.2 Answers

            a)

            Write a PL/SQL block with two cursor FOR loops. The parent cursor will call the student_id, first_name, and last_name from the student table for students with a student_id less than 110 and output one line with this information. For each student, the child cursor will loop through all the courses that the student is enrolled in, outputting the course_no and the description.

            A1:

            Answer: Your block should look be similar to this:


            -- ch09_11a.sql
            DECLARE
            v_sid student.student_id%TYPE;
            CURSOR c_student IS
            SELECT student_id, first_name, last_name
            FROM student
            WHERE student_id < 110;
            CURSOR c_course IS
            SELECT c.course_no, c.description
            FROM course c, section s, enrollment e
            WHERE c.course_no = s.course_no
            AND s.section_id = e.section_id
            AND e.student_id = v_sid;
            BEGIN
            FOR r_student IN c_student
            LOOP
            v_sid := r_student.student_id;
            DBMS_OUTPUT.PUT_LINE(chr(10));
            DBMS_OUTPUT.PUT_LINE(' The Student '||
            r_student.student_id||' '||
            r_student.first_name||' '||
            r_student.last_name);
            DBMS_OUTPUT.PUT_LINE(' is enrolled in the '||
            'following courses: ');
            FOR r_course IN c_course
            LOOP
            DBMS_OUTPUT.PUT_LINE(r_course.course_no||
            ' '||r_course.description);
            END LOOP;
            END LOOP;
            END;

            The select statements for the two cursors are defined in the declarative section of the PL/SQL block. A variable to store the student_id from the parent cursor is also declared. The course cursor is the child cursor, and, since it makes use of the variable v_sid, the variable must be declared first. Both cursors are processed with a FOR loop, which eliminates the need for OPEN, FETCH, and CLOSE. When the parent student loop is processed, the first step is to initialize the variable v_sid, and the value is then used when the child loop is processed. DBMS_OUTPUT is used so that display is generated for each cursor loop. The parent cursor will display the student name once, and the child cursor will display the name of each course in which the student is enrolled.


            b)

            Before you run the preceding code, analyze what it is doing and determine what you think the result would be. Explain what is happening in each phase of the PL/SQL block and what is happening to the variables as control is passing through parent and child cursor.

            A1:

            Answer: The declaration section contains a declaration for two variables. The first is v_amount of the datatype matching that of the cost in the course table; the second is the v_instructor_id of the datatype matching the instructor_id in the instructor table. There are also two declarations for two cursors. The first is for c_inst, which is comprised of the first_name, last_name, and instructor_id for an instructor from the instructor table. The second cursor, c_cost, will produce a result set of the cost of the course taken for each student enrolled in a course by the instructor that matches the variable v_instructor_id. These two cursors will be run in nested fashion. First, the cursor c_inst is opened in a FOR loop. The value of the variable v_instructor_id is initialized to match the instructor_id of the current row of the c_inst cursor. The variable v_amount is initialized to 0. The second cursor is open within the loop for the first cursor. This means that for each iteration of the cursor c_inst, the second cursor will be opened, fetched, and closed. The second cursor will loop through all the cost generated by each student enrolled in a course for the instructor, which is current of the c_inst cursor. Each time the nest loop iterates, it will increase the variable v_amount by adding the current cost in the c_cost loop. Prior to opening the c_cost loop, there is a DBMS_OUTPUT to display the instructor name. After the c_cost cursor loop is closed, it will display the total amount generated by all the enrollments of the current instructor.

            c)

            Run the code and see what the result is. Is it what you expected? Explain the difference.

            A2:

            Answer: The result set would be as follows:


            Generated by instructor Fernand Hanks
            $16,915
            Generated by instructor Tom Wojick
            $18,504
            Generated by instructor Nina Schorin
            $30,137
            Generated by instructor Gary Pertez
            $24,044
            Generated by instructor Anita Morris
            $13,389
            Generated by instructor Todd Smythe
            $14,940
            Generated by instructor Rick Chow
            $0
            Generated by instructor Charles Lowry
            $12,175
            Generated by instructor Marilyn Frantzen
            $13,224
            PL/SQL procedure successfully completed.

            In this example, the nested cursor is tied to the current row of the outer cursor by means of the variable v_instructor_id. A more common way of doing this is to pass a parameter to a cursor. You will learn more about how to achieve this in Chapter 15, "Advanced Cursors."






            Lab 9.2 Self-Review Questions


            In order to test your progress, you should be able to answer the following questions.


            Answers appear in Appendix A, Section 9.2.


            1)

            In a cursor FOR loop, cursor and loop handling is carried out implicitly.


            1. _____ True

            2. _____ False

            2)

            In a cursor FOR loop, it is necessary to declare the rowtype for the cursor.


            1. _____ True

            2. _____ False

            3)

            Is it necessary to open, fetch, and close a cursor in a cursor FOR loop?


            1. _____ Yes

            2. _____ No

            4)

            The child loop in a nested cursor is passed through how many times for each cycle of the parent?


            1. _____ Three

            2. _____ One or more

            3. _____ Two

            4. _____ It depends on the individual code.

            5)

            If the SELECT statement of the cursor makes use of a variable, when should the variable be declared?


            1. _____ It is a bind variable and therefore does not need to be declared.

            2. _____ In the declarative section.

            3. _____ Before the cursor that is using it.

            4. _____ It will be self-declared upon initialization.




              Team-Fly
               

               
              Top