14 PL/SQL Language Elements

Summarizes the syntax and semantics of PL/SQL language elements and provides links to examples and related topics.

For instructions for reading the syntax diagrams, see Oracle Database SQL Language Reference.

Topics

14.1 Iterator

The iterator specifies an iterand and the iteration controls.

An iterator can appear in the following statements:

Syntax

Semantics

iterator

The iterator specifies an iterand and the iteration controls.

Statements outside the loop cannot reference iterator. Statements inside the loop can reference iterator, but cannot change its value. After the FOR LOOP statement runs, iterator is undefined.

iterand_decl

An iterand type can be implicitly or explicitly declared. You cannot explicitly initialize an iterand.

An iterand type is implicitly declared when no type declaration follows the iterand in the loop header. The implicit type is determined by the first iteration control.

Table 14-1 Iterand Implicit Type Defaults

Iteration Control Implicit Iterand Type
stepped control PLS_INTEGER
single expression PLS_INTEGER
cursor control CURSOR%ROWTYPE
VALUES OF control collection element type
INDICES OF control collection index type
PAIRS OF control The first iterand denotes the index type of collection and the second iterand denotes the element type of collection

pls_identifier

The iterand name for the implicitly declared variable that is local to the FOR LOOP statement.

[ MUTABLE | IMMUTABLE ]

The mutability property of an iterand determines whether or not it can be assigned in the loop body. If all iteration controls specified in an iterator are cursor controls, the iterand is mutable by default. Otherwise, the iterand is immutable. The default mutability property of an iterand can be changed in the iterand declaration by specifying the MUTABLE or IMMUTABLE keyword after the iterand variable. The mutability property keywords are not reserved and could be used as type names. Such usage would be ambiguous. Therefore, you must explicitly specify the mutability property of an iterand in the iterand declaration if its type is named mutable or immutable. Iterand for INDICES OF iteration control and the index iterand for PAIRS OF iteration control cannot be made mutable.

constrained_type

An iterand is explicitly declared when the iterand type is specified in the loop header. Any constraint defined for a type is considered when assigning values to the iterand. The values generated by the iteration controls must be assignment compatible with the iterand type. Usual conversion rules apply. Exceptions are raised for all constraint violations.

iteration_ctl_seq

Multiple iteration controls may be chained together by separating them with commas.

Restriction on iteration_ctl_seq:

Because two iterands are required for the pairs of iterand, pairs of iteration controls may not be mixed with other kinds of iteration controls.

qual_iteration_ctl

The qualified iteration control specifies the REVERSE option and the optional stopping and skipping predicates clauses.

[ REVERSE ]

When the optional keyword REVERSE is specified, the order of values in the sequence is reversed.

You can use this option with a collection vector value expression. In that case, specifying REVERSE generates values from LAST to FIRST rather than from FIRST to LAST.

Restrictions on REVERSE:
  • You cannot use this option when a pipelined function is specified in the iteration control.
  • You cannot use this option with single expression iteration control since it generates a single value and therefore the keyword does not have any sensible meaning for this control.

  • You cannot use this option when the iteration control specifies a SQL statement. This creates a sequence of records returned by the query. You can specify an ORDER BY clause on the SQL statement to sort the rows in the appropriate order.

  • You cannot use this option when the collection is a cursor,cursor variable, dynamic SQL, or is an expression that calls a pipelined table function.

iteration_control

An iteration control provides a sequence of values to the iterand.

pred_clause_seq

An iteration control may be modified with an optional stopping predicate clause followed by an optional skipping predicate clause. The expressions in the predicates must have a BOOLEAN type.

[ WHILE boolean_expression ]

A stopping predicate clause can cause the iteration control to be exhausted. The boolean_expression is evaluated at the beginning of each iteration of the loop. If it fails to evaluate to TRUE, the iteration control is exhausted.

[ WHEN boolean_expression ]

A skipping predicate clause can cause the loop body to be skipped for some values. The boolean_expression is evaluated. If it fails to evaluate to TRUE, the iteration control skips to the next value.

stepped_control

lower_bound .. upper_bound [ BY step ]

Without REVERSE, the value of iterand starts at lower_bound and increases by step with each iteration of the loop until it reaches upper_bound.

With REVERSE, the value of iterand starts at upper_bound and decreases by step with each iteration of the loop until it reaches lower_bound. If upper_bound is less than lower_bound, then the statements never run.

The default value for step is one if this optional BY clause is not specified.

lower_bound and upper_bound must evaluate to numbers (either numeric literals, numeric variables, or numeric expressions). If a bound does not have a numeric value, then PL/SQL raises the predefined exception VALUE_ERROR. PL/SQL evaluates lower_bound and upper_bound once, when the FOR LOOP statement is entered, and stores them as temporary PLS_INTEGER values, rounding them to the nearest integer if necessary.

If lower_bound equals upper_bound, the statements run only once.

The step value must be greater than zero.

single_expression_control

A single expression iteration control generates a single value. If REPEAT is specified, the expression will be evaluated repeatedly generating a sequence of values until a stopping clause causes the iteration control to be exhausted.

Restrictions on single_expression_control:

REVERSE is not allowed for a single expression iteration control.

values_of_control

The element type of a collection must be assignment compatible with the iterand.

indices_of_control

The index type of a collection must be assignment compatible with the iterand.

The iterand used with an INDICES OF iteration control cannot be mutable.

pairs_of_control

The PAIRS OF iteration control requires two iterands. You cannot mix the PAIRS OF iteration control with other kinds of controls. The first iterand is the index iterand and the second is the value iterand. Each iterand may be followed by an explicit type.

The element type of the collection must be assignment compatible with the value iterand. The index type of the collection must be assignment compatible with the index iterand.

The index iterand used with a PAIRS OF iteration control cannot be mutable.

cursor_iteration_control

Cursor iteration controls generate the sequence of records returned by an explicit or implicit cursor. The cursor definition is the controlling expression.

Restrictions on cursor_iteration_control:

You cannot use REVERSE with a cursor iteration control.

cursor_object

A cursor_object is an explicit PL/SQL cursor object.

sql_statement

A sql_statement is an implicit PL/SQL cursor object created for a SQL statement specified directly in the iteration control.

cursor_variable

Name of a previously declared variable of a REF CURSOR object.

dynamic_sql

EXECUTE IMMEDIATE dynamic_sql_stmt [ USING [ IN ] (bind_argument [,] )+]

You can use a dynamic query in place of an implicit cursor definition in a cursor or collection iteration control. Such a construct cannot provide a default type; if it is used as the first iteration control, an explicit type must be specified for the iterand, or for the value iterand for a pairs of control.

The optional USING clause is the only clause allowed with the dynamic SQL. It can only possibly have IN one or more bind variable, each separated by a comma.

dynamic_sql_stmt

String literal, string variable, or string expression that represents a SQL statement. Its type must be either CHAR, VARCHAR2, or CLOB.

Caution:

When using dynamic SQL, beware of SQL injection, a security risk. For more information about SQL injection, see "SQL Injection".

Examples

14.2 Qualified Expression

Using qualified expressions, you can declare and define a complex value in a compact form where the value is needed.

Qualified expressions appear in:

Syntax

Semantics

qualified_expression

Qualified expressions for RECORD types are allowed in any context where an expression of RECORD type is allowed.

Qualified expressions for associative array types are allowed in any context where an expression of associative array type is allowed.

typemark ( aggregate )

Specifies explicitly the type of the aggregate (qualified items).

typemark

type_name

Qualified expressions use an explicit type indication to provide the type of the qualified item. This explicit indication is known as a typemark.

identifier [ . ]

Indicates the type of the qualified item.

aggregate

A qualified expression combines expression elements to create values of a RECORD type, or associative array type.

positional_choice_list

expr [ , ]

Positional association is allowed for qualified expressions of RECORD type.

A positional association may not follow a named association in the same construct (and vice versa).

sequence_iterator_choice

FOR iterator SEQUENCE => expr

The sequence iterator choice association is a positional argument and may be intermixed freely with other positional arguments. All positional arguments must precede any non-positional arguments.

explicit_choice_list

named_choice_list | indexed_choice_list | basic_iterator_choice | index_iterator_choice

Named choices must use names of fields from the qualifying structure type. Index key values must be compatible with the index type for the qualifying vector type.

named_choice_list

A named choice applies only to structured types

identifier => expr [ , ]

Named association is allowed for qualified expressions of RECORD type.

indexed_choice_list

An index choice applies only to vector types.

expr => expr [ , ]

Indexed choices (key-value pairs) is allowed for qualified expressions of associative array types. Both the key and the value may be expressions.

Using NULL as an index key value is not permitted with associative array type constructs.

basic_iterator_choice

FOR iterator => expr

The basic iterator choice association uses the iterand as an index.

Restrictions:

The PAIRS OF iteration control may not be used with the basic iterator choice association.

index_iterator_choice

FOR iterator INDEX expr => expr

The index iterator choice association provides an index expression along with the value expression.

Examples

  • Example 6-11, "Assigning Values to RECORD Type Variables Using Qualified Expressions"
  • Example 6-12, "Assigning Values to Associative Array Type Variables Using Qualified Expressions"
  • Example 6-8, "Basic Iterator Choice Association in Qualified Expressions"
  • Example 6-9, "Index Iterator Choice Association in Qualified Expressions"
  • Example 6-10, "Sequence Iterator Choice Association in Qualified Expressions"
  • Example 5-26, "Using Dynamic SQL As An Iteration In A Qualified Expression"

14.3 SQL_MACRO Clause

The SQL_MACRO clause marks a function as a SQL macro which can be used as either a scalar expression or a table expression.

A TABLE macro is a function annotated as a SQL_MACRO and defined as a TABLE type.

A SCALAR macro is a function annotated as a SQL_MACRO and defined as a SCALAR type.

A SQL macro referenced in a view is always executed with the view owner's privileges.

The AUTHID property cannot be specified. When a SQL macro is invoked, they behave like invoker's right function. The SQL macro owner must grant inherit privileges to the invoking function.

When a macro annotated function is used in PL/SQL, it works like a regular function returning character or CLOB type with no macro expansion.

The SQL_MACRO annotation can appear in the following SQL statement:

SQL Macro Usage Restrictions:
  • A TABLE macro can only appear in FROM clause of a query table expression.
  • A SCALAR macro cannot appear in FROM clause of a query table expression. It can appear in the select list or the WHERE clause.
  • A scalar macro cannot have table arguments.
  • A SQL macro cannot appear in a virtual column expression, functional index, editioning view or materialized view.
  • Type methods cannot be annotated with SQL_MACRO.

Syntax

Semantics

sql_macro_clause

The sql_macro_clause can appear only once in the function. To make a SQL macro function, include the sql_macro_clause in the function definition. If you declare the SQL macro function before defining it, you must specify the sql_macro_clause in the function declaration.

If SCALAR or TABLE is not specified, TABLE is the default.

SCALAR

Specify SCALAR if the macro function can be used in scalar expressions.

TABLE (Default)

Specify TABLE if the macro function can be used in table expressions.

Restrictions on sql_macro_clause

The SQL_MACRO annotation is disallowed with RESULT_CACHE, PARALLEL_ENABLE, and PIPELINED. Although the DETERMINISTIC property cannot be specified, a SQL macro is always implicitly deterministic.

The SQL macro function must have a return type of VARCHAR2, CHAR, or CLOB.

Examples

Example 14-1 Emp_doc: Using a Scalar Macro to Convert Columns into a JSON or XML Document

The emp_doc SQL macro converts employee fields into a document string (JSON or XML).
The macro is implemented as a tree of nested macros with the following call graph structure.
emp_doc()
   ==> emp_json()
          ==> name_string()
          ==> email_string()
                ==> name_string()
          ==> date_string()
   ==> emp_xml 
          ==> name_string()
          ==> email_string()
                ==> name_string()
          ==> date_string()
The date_string function converts a date in a string formatted as a four digits year, month (01-12) and day of the month (1-31).
CREATE FUNCTION date_string(dat DATE) 
                    RETURN VARCHAR2 SQL_MACRO(SCALAR) IS
BEGIN
   RETURN q'{
             TO_CHAR(dat, 'YYYY-MM-DD')
          }';
END;
/
The name_string function sets the first letter of each words in the first_name and last_name in uppercase and all other letters in lowercase. It concatenates the formatted first name with a space and the formatted last name, and removes leading and trailing spaces from the resulting string.
CREATE FUNCTION name_string(first_name VARCHAR2,
                                 last_name VARCHAR2)
                     RETURN VARCHAR2 SQL_MACRO(SCALAR) IS
BEGIN
   RETURN q'{
          TRIM(INITCAP(first_name) || ' ' || INITCAP(last_name))
          }';
END;
/
The email_string sets the email address using the name_string function with the first_name and last_name and replacing all spaces with a period, and appending a default domain name of example.com.
CREATE FUNCTION email_string(first_name VARCHAR2,
                                  last_name VARCHAR2,
                                  host_name VARCHAR2 DEFAULT 'example.com')
                    RETURN VARCHAR2 SQL_MACRO(SCALAR) IS
BEGIN
   RETURN q'{
          REPLACE(LOWER(name_string(first_name, last_name)),' ','.') || '@' || host_name
          }';
END;
/
The emp_json SQL macro returns a JSON document string.
CREATE FUNCTION emp_json(first_name VARCHAR2 DEFAULT NULL,
                              last_name VARCHAR2 DEFAULT NULL,
                              hire_date DATE DEFAULT NULL,
                              phone_num VARCHAR2 DEFAULT NULL)
                    RETURN VARCHAR2 SQL_MACRO(SCALAR) IS
BEGIN
   RETURN q'{
          JSON_OBJECT(
             'name'      : name_string(first_name, last_name),
             'email'     : email_string(first_name, last_name),
             'phone'     : phone_num,
             'hire_date' : date_string(hire_date)
             ABSENT ON NULL)
          }';
END;
/
The emp_xml SQL macro returns an XML document string.
CREATE FUNCTION emp_xml(first_name VARCHAR2 DEFAULT NULL,
                             last_name VARCHAR2 DEFAULT NULL,
                             hire_date DATE DEFAULT NULL,
                             phone_num VARCHAR2 DEFAULT NULL)
                    RETURN VARCHAR2 SQL_MACRO(SCALAR) IS
BEGIN
   RETURN q'{
       XMLELEMENT("xml",
                  CASE WHEN first_name || last_name IS NOT NULL THEN
                     XMLELEMENT("name", name_string(first_name, last_name))
                  END,
                  CASE WHEN first_name || last_name IS NOT NULL THEN
                     XMLELEMENT("email", email_string(first_name, last_name))
                  END,
                  CASE WHEN hire_date IS NOT NULL THEN
                     XMLELEMENT("hire_date", date_string(hire_date))
                  END,
                  CASE WHEN phone_num IS NOT NULL THEN
                     XMLELEMENT("phone", phone_num)
                  END)
           }';
END;
/ 
The emp_doc SQL macro returns employee fields into a JSON (default) or XML document string.
CREATE FUNCTION emp_doc(first_name VARCHAR2 DEFAULT NULL,
                             last_name VARCHAR2 DEFAULT NULL,
                             hire_date DATE DEFAULT NULL,
                             phone_num VARCHAR2 DEFAULT NULL,
                             doc_type VARCHAR2 DEFAULT 'json')
                    RETURN VARCHAR2 SQL_MACRO(SCALAR) IS
BEGIN
   RETURN q'{
     DECODE(LOWER(doc_type),
            'json', emp_json(first_name, last_name, hire_date, phone_num),
            'xml', emp_xml(first_name, last_name, hire_date, phone_num))
         }';
END;
/
This query shows the emp_doc SQL macro used in a scalar expression to list all employees in a JSON document string in department 30.
SELECT department_id,
            emp_doc(first_name => e.first_name, hire_date => e.hire_date) doc
FROM hr.employees e
WHERE department_id = 30
ORDER BY last_name;
Result:
  
     30 {"name":"Shelli","email":"shelli@example.com","hire_date":"2005-12-24"}
     30 {"name":"Karen","email":"karen@example.com","hire_date":"2007-08-10"}
     30 {"name":"Guy","email":"guy@example.com","hire_date":"2006-11-15"}
     30 {"name":"Alexander","email":"alexander@example.com","hire_date":"2003-05-19"} 
     30 {"name":"Den","email":"den@example.com","hire_date":"2002-12-07"}
     30 {"name":"Sigal","email":"sigal@example.com","hire_date":"2005-07-24"}
This query shows the emp_doc SQL macro used in a scalar expression to list all employees in a XML document string.
SELECT deptno,
            emp_doc(first_name => ename, hire_date => hiredate, doc_type => 'xml') doc
FROM scott.emp
ORDER BY ename;
Result:
20 <xml><name>Adams</name><email>adams@example.com</email><hire_date>1987-05-23</hire_date></xml>
30 <xml><name>Allen</name><email>allen@example.com</email><hire_date>1981-02-20</hire_date></xml>
30 <xml><name>Blake</name><email>blake@example.com</email><hire_date>1981-05-01</hire_date></xml> 
10 <xml><name>Clark</name><email>clark@example.com</email><hire_date>1981-06-09</hire_date></xml> 
20 <xml><name>Ford</name><email>ford@example.com</email><hire_date>1981-12-03</hire_date></xml> 
...
30 <xml><name>Ward</name><email>ward@example.com</email><hire_date>1981-02-22</hire_date></xml>
VARIABLE surname VARCHAR2(100)
EXEC :surname := 'ellison'
WITH e AS (SELECT emp.*, :surname lname FROM emp WHERE deptno IN (10,20))
SELECT deptno,
       emp_doc(first_name => ename, last_name => lname, hire_date => hiredate) doc
FROM e
ORDER BY ename;
Result:
10 {"name":"Clark Ellison","email":"clark.ellison@example.com","hire_date":"1981-06-09"} 
20 {"name":"Ford Ellison","email":"ford.ellison@example.com","hire_date":"1981-12-03"}
20 {"name":"Jones Ellison","email":"jones.ellison@example.com","hire_date":"1981-04-02"}
10 {"name":"King Ellison","email":"king.ellison@example.com","hire_date":"1981-11-17"}
10 {"name":"Miller Ellison","email":"miller.ellison@example.com","hire_date":"1982-01-23"}
20 {"name":"Scott Ellison","email":"scott.ellison@example.com","hire_date":"1987-04-19"}
20 {"name":"Smith Ellison","email":"smith.ellison@example.com","hire_date":"1980-12-17"}

Example 14-2 Env: Using a Scalar Macro in a Scalar Expression

The env SQL macro provides a wrapper for the value of the parameter associated with the context namespace USERENV which describes the current session.

CREATE PACKAGE env AS
   FUNCTION current_user RETURN VARCHAR2 SQL_MACRO(SCALAR);
   FUNCTION current_edition_name RETURN VARCHAR2 SQL_MACRO(SCALAR);
   FUNCTION module RETURN VARCHAR2 SQL_MACRO(SCALAR);
   FUNCTION action RETURN VARCHAR2 SQL_MACRO(SCALAR);
END;
/
CREATE PACKAGE BODY env AS
   FUNCTION current_user RETURN VARCHAR2 SQL_MACRO(SCALAR) IS
     BEGIN
        RETURN q'{SYS_CONTEXT('userenv','SESSION_USER')}';
     END;
   FUNCTION current_edition_name RETURN VARCHAR2 SQL_MACRO(SCALAR) IS
     BEGIN
        RETURN q'{SYS_CONTEXT('userenv','CURRENT_EDITION_NAME')}';
     END;
   FUNCTION module RETURN VARCHAR2 SQL_MACRO(SCALAR) IS
     BEGIN
        RETURN q'{SYS_CONTEXT('userenv','MODULE')}';
     END;
   FUNCTION action RETURN VARCHAR2 SQL_MACRO(SCALAR) IS
     BEGIN
        RETURN q'{SYS_CONTEXT('userenv','ACTION')}';
     END;
END;
/

Select the current user info.

SELECT env.current_user, env.module, env.action FROM DUAL;
Result:
SCOTT   SQL*PLUS            

Example 14-3 Budget : Using a Table Macro in a Table Expression

This example shows the SQL macro named budget used in a table expression to return the total salary in each department for employees for a given job title.

CREATE FUNCTION budget(job VARCHAR2) RETURN VARCHAR2 SQL_MACRO IS
BEGIN
   RETURN q'{SELECT deptno, SUM(sal) budget 
             FROM scott.emp
             WHERE job = budget.job
             GROUP BY deptno}';
END;

This query shows the SQL macro budget used in a table expression.

SELECT * FROM budget('MANAGER');

Result:

    
DEPTNO     BUDGET 
---------- ----------   
        20       2975  
        30       2850 
        10       2450  

Example 14-4 Take: Using a Table Macro with a Polymorphic View

This example creates a table macro named take which returns the first n rows from table t.
CREATE FUNCTION take (n NUMBER, t DBMS_TF.table_t) 
                      RETURN VARCHAR2 SQL_MACRO IS
BEGIN
   RETURN 'SELECT * FROM t FETCH FIRST take.n ROWS ONLY';
END;
/
The query returns the first two rows from table dept.
SELECT * FROM take(2, dept);
Result:
    DEPTNO DNAME          LOC   
---------- -------------- -------------   
        10 ACCOUNTING     NEW YORK       
        20 RESEARCH       DALLAS   
VAR row_count NUMBER
EXEC :row_count := 5

WITH t AS (SELECT * FROM emp NATURAL JOIN dept ORDER BY ename)
SELECT ename, dname FROM take(:row_count, t);
Result:
ENAME      DNAME  
---------- --------------    
ADAMS      RESEARCH     
ALLEN      SALES     
BLAKE      SALES   
CLARK      ACCOUNTING   
FORD       RESEARCH    

Example 14-5 Range : Using a Table Macro in a Table Expression

This example creates a SQL macro that generates an arithmetic progression of rows in the range [first, stop]. The first row start with the value first, and each subsequent row's value will be step more than the previous row's value.

The following combination of arguments will produce zero rows:
  • step < 0 and first < stop
  • step = 0
  • step > 0 and first > stop
/*  PACKAGE NAME: GEN
 *  SQL TABLE MACROS:
 *     range(stop  : number to generate starting from zero)
 *     range(first : starting number of the sequence (default=0), 
 *           stop  : generate numbers up to, but not including this number, 
 *           step  : difference between each number in the sequence (default=1) )
*/
CREATE PACKAGE gen IS 
   FUNCTION range(stop NUMBER)
            RETURN VARCHAR2 SQL_MACRO(TABLE);

   FUNCTION range(first NUMBER DEFAULT 0, stop NUMBER, step NUMBER DEFAULT 1)
            RETURN VARCHAR2 SQL_MACRO(TABLE);

   FUNCTION tab(tab TABLE, replication_factor NATURAL)
            RETURN TABLE PIPELINED ROW POLYMORPHIC USING gen;

   FUNCTION describe(tab IN OUT DBMS_TF.TABLE_T, replication_factor NATURAL)
            RETURN DBMS_TF.DESCRIBE_T;

   PROCEDURE fetch_rows(replication_factor NATURALN);
END gen;
/
CREATE PACKAGE BODY gen IS 
   FUNCTION range(stop NUMBER)
            RETURN VARCHAR2 SQL_MACRO(TABLE) IS
   BEGIN 
      RETURN q'{SELECT ROWNUM-1 n FROM gen.tab(DUAL, stop)}'; 
   END;

   FUNCTION range(first NUMBER DEFAULT 0, stop NUMBER, step NUMBER DEFAULT 1)
           RETURN VARCHAR2 SQL_MACRO(TABLE) IS
   BEGIN
      RETURN q'{
             SELECT first+n*step n FROM gen.range(ROUND((stop-first)/NULLIF(step,0)))
             }';
   END;
 
   FUNCTION describe(tab IN OUT DBMS_TF.TABLE_T, replication_factor NATURAL) 
            RETURN DBMS_TF.DESCRIBE_T AS
   BEGIN 
      RETURN DBMS_TF.DESCRIBE_T(row_replication => true);
   END;

  PROCEDURE fetch_rows(replication_factor NATURALN) as
  BEGIN 
    DBMS_TF.ROW_REPLICATION(replication_factor); 
  END;
END gen;
/

The gen.get_range SQL macro is used in table expressions.

This query returns a sequence of 5 rows starting at zero.
SELECT * FROM gen.range(5);
Result:
         0 
         1 
         2 
         3 
         4   
This query returns a sequence starting at 5, stopping at 10 (not included).
SELECT * FROM gen.range(5, 10);
Result:
         5  
         6  
         7  
         8   
         9   
This query returns a sequence starting at 0, stopping at 1, by increment of 0.1.
SELECT * FROM gen.range(0, 1, step=>0.1);
Result:
 
         0  
        .1  
        .2  
        .3  
        .4  
        .5  
        .6  
        .7   
        .8   
        .9   
This query returns a sequence starting at 5, stopping at -6 (not included) by decrement of 2.
SELECT * FROM gen.range(+5,-6,-2);
Result:
      5 
      3   
      1   
     -1 
     -3 
     -5  

Related Topics