Sei sulla pagina 1di 40

USING ADVANCED FEATURES OF SQLSCRIPT

DEV360

Exercises / Solutions
Rich Heilman, SAP Labs, LLC.
Katharina Schell, SAP SE
Andreas Bader, SAP SE
DEV360

Contents

Exercise 1- Package & Schema Creation ................................................................... 3


Exercise 2- Using Dynamic SQL vs Dynamic Filtering ............................................... 7
Using EXEC Statement ........................................................................................... 7
Using EXECUTE IMMEDIATE Statement ............................................................. 10
Using APPLY FILTER Statement .......................................................................... 12
Exercise 3 – SQL vs Cursors vs Arrays .................................................................... 15
Using SQL Only .................................................................................................... 15
Using Cursors ....................................................................................................... 18
Using Arrays ......................................................................................................... 23
Exercise 4 – Exception Handling, COMMIT & Autonomous Transactions ................ 26
Using Exception Handling ..................................................................................... 26
Using COMMIT Statement .................................................................................... 34
Using Autonomous Transactions .......................................................................... 37
Exercise 5 – Challenge!! 99 Bottles of Beer ............................................................ 39

2
DEV360

EXERCISE 1- PACKAGE & SCHEMA CREATION


The following steps are prerequisites for the next exercises.
We will …
- Access WEB IDE (steps 1-2)
- Create several packages (steps 3-7 & 13-14)
- Create a database schema (steps 8-11)
- Assign our user the necessary privileges (step 12)
- Access the reference solution (step 15)

Explanation Screenshot

1. Open the SAP HANA Web


Based Development
Workbench by entering the
following URL in the browser.

http://lt5069.wdf.sap.corp:801
0/sap/hana/ide/editor/

2. Enter your user id and


password. Enter your user id
as DEV360_XXX where XXX
is your group number. DO
NOT USE 000. The password
will be provided by the
instructor.

3. Expand the “dev360” package,


then right click on the
“exercises” package and
choose “New”, then
“Package”.

3
DEV360

4. Enter the package name as


gXXX where XXX is your
group number. DO NOT USE
g000. Enter a description and
click “Create”.

5. Next, right click on your group


package and choose “New”,
then “Package”.

6. Enter the name of the


package as “data”, and click
“Create”.

7. Right-click on the “data”


package and choose “New”,
then “File”.

4
DEV360

8. Enter the name of the file as


“HANA_DEV360_XXX.hdbsch
ema” where XXX is your group
number. DO NOT USE 000.
Click “Create”.

9. Once the editor is opened,


click the “Insert Snippet”
button.

10.Enter the name of the schema


as “HANA_DEV360_XXX”
where XXX is your group
number. DO NOT USE 000.

11.Click “Save”.

12.Click the “Assign execution


authorization” button to grant
access to the new schema.

5
DEV360

13.Next, right click on your group


package and choose “New”,
then “Package”.

14.Enter the name of the


package as “procedures”, and
click “Create”.

15. Finally, before we begin


writing code, we want to make
you aware of a solutions
webpage which contains all of
the source solutions for this
exercise document. You can
access this page via the
following URL. If you do not
wish to type the code, you
may cut/paste it from this
page.

http://lt5069.wdf.sap.corp:801
0/workshop/admin/ui/exercise
Master/index.html?workshop=
dev360

Be advised, simply blindly


cutting and pasting code will
not give you the same
experience as typing it by
hand. You will learn a lot
more by typing. However, if
you do fall behind, you are
encouraged to copy/paste, but
make sure you understand the
code before moving forward.

6
DEV360

EXERCISE 2- USING DYNAMIC SQL VS DYNAMIC FILTERING


In this exercise, you will learn the differences between dynamic sql (EXEC, EXECUTE
IMMEDIATE) and applying a dynamic filter.

Using EXEC Statement

We will …
- Create an empty HDB Procedure “get_product_by_filter” (steps 1-5)
- Add scalar input parameter “im_product_filter_string” (step 6)
- Remove read only property from the procedure (step 7)
- Add a dynamic sql statement using EXEC. The statement should return all products
with a pre-defined filter on the product category excluding “Laser printers”. In addition
a user defined filter based on the scalar input parameter “im_product_filter_string”
should be applied as well on that table. (steps 8-9)
- Call the procedure (steps 10-12)

Explanation Screenshot

1. Right click on the procedures


package and choose “New”,
then “HDB Procedure”.

2. Enter the name of the


procedure as
“get_product_by_filter”. Click
the drop down box for
“Schema”.

7
DEV360

3. Enter
“HANA_DEV360_XXX”(where
XXX is your group number) in
the search box. Select your
schema which you created
earlier. DO NOT USE
HANA_DEV360_000. Click
“Ok”.

4. Click “Create”.

5. The editor will then be shown

6. Add an input parameter


named
“im_product_filter_string”, type
varchar with a length of 5000.

7. Because dynamic SQL is not


supported in “Read-only”
procedures, you must remove
the “READS SQL DATA”
keywords as shown here.

8. Between the BEGIN and END PROCEDURE


"HANA_DEV360_<group_number>"."dev360.exercises.g<group_num
statements, insert the EXEC ber>.procedures::get_product_by_filter" (
statements as shown. The IN im_product_filter_string VARCHAR(5000) )
completed code should look LANGUAGE SQLSCRIPT
similar to this. SQL SECURITY INVOKER
--DEFAULT SCHEMA <default_schema_name>
AS
If you do not wish to type this BEGIN

8
DEV360

code, you can reference the


EXEC 'SELECT count(*) FROM
solution web page at "SAP_HANA_EPM_NEXT"."sap.hana.democontent.epmNext.data::MD
http://lt5069.wdf.sap.corp:801 .Products" where CATEGORY NOT IN (''Laser printers'')'
0/workshop/admin/ui/exercise || :im_product_filter_string ;
Master/index.html?workshop=
END
dev360

9. Save the procedure.

10. Invoke the procedure by


selecting the file and choosing
“Invoke Procedure”.

11. A new SQL tab will be


opened. Add the filter string
as 'AND CATEGORY =
''Notebooks'''

12. Click the “Run” button. You


will notice that you get no
results from the call at all.
Also by using the EXEC
statement, there is a
possibility of SQL injection.

9
DEV360

Using EXECUTE IMMEDIATE Statement

In contrast to executing a string using EXEC, executing the string using EXECUTE IMMEDIATE
returns a result set.

We will …
- Use EXECUTE IMMEDIATE instead of EXEC (steps 1-3)
- Call the procedure (steps 4 -12)

Explanation Screenshot

1. Switch back to the procedure


editor.

2. Replace the EXEC keyword


with EXECUTE IMMEDIATE.

3. Click “Save”.

4. Switch back to the SQL tab.

5. Click the “Run” button again.

10
DEV360

6. You will notice the implicit


result set is now returned to
the console. But you still
cannot work further on this
result set.

7. Now change the CALL


statement again, this time
insert the value for the input
parameter as „ „ as shown
here.

8. Click “Run”.

9. You will notice the count is


103, which refers to all
products except for „Laser
printers‟.

10.Now change the CALL


statement. This time insert the
value for the input parameter
as „OR 1 = 1‟ as shown here.

11.Click “Run”.

11
DEV360

12.You will notice the count is


now much higher, 106. This
illustrates the possibility of
SQL injection. The always true
OR-condition (1=1) will
enforce that the complete
where-condition will be
evaluated to true for each
record.

Using APPLY FILTER Statement

In contrast to EXEC and EXECUTE IMMEDIATE, APPLY_FILTER is sql injection save.


Furthermore the result of the APPLY_FILTER will be assigned to a table variable. This allows
further processing of the result by referring to the table variable. As APPLY_FILTER does not
allow executing dynamic DML/DDL statements the procedure can be flagged read only which
allows further optimization.

We will …
- Define an output parameter named “ex_user_filtered_products” that with
"sap.hana.democontent.epmNext.data::MD.Products" as type definition (steps 1-2)
- Mark the procedure as read-only (step 3)
- Remove the dynamic SQL statement and add a query that returns all products except
of category “Laser printers” and assign the result to a new table variable
“PRE_FILTERED_PRODUCTS” (steps 4-6)
- Add an APPLY_FILTER statement that applies the filter condition of the scalar string
based input paameter “im_products_filter_string” on the table variable
“PRE_FILTERED_PRODUCTS” (steps 4-6)
- Call the procedure (steps 7-12)

Explanation Screenshot

1. Return to the procedure


editor.

2. Add an output parameter


called
“ex_user_filtered_products”
and reference the
"SAP_HANA_EPM_NEXT"."sap.han
a.democontent.epmNext.data::
MD.Products" table as the
type.

12
DEV360

3. Now that we are not using the


dynamic SQL keywords, we
no longer need a read/write
procedure, so add the READS
SQL DATA before AS.

4. Remove the EXECUTE


IMMEDIATE statement and
instead insert the following
SELECT statement and
APPLY_FILTER statement
using table variable
assignments. The
APPLY_FILTER needs two
input parameters: table
variable which will used for
filtering and a scalar variable
which contains the string.

5. The completed code should PROCEDURE


"HANA_DEV360_<group_number>"."dev360.exercises.g<group_num
be very similar to this. ber>.procedures::get_product_by_filter" (
IN im_product_filter_string varchar(5000),
If you do not wish to type this OUT ex_user_filtered_products
code, you can reference the "SAP_HANA_EPM_NEXT"."sap.hana.democontent.epmNext.data::MD
.Products" )
solution web page at LANGUAGE SQLSCRIPT
http://lt5069.wdf.sap.corp:801 SQL SECURITY INVOKER
0/workshop/admin/ui/exercise --DEFAULT SCHEMA <default_schema_name>
Master/index.html?workshop= READS SQL DATA AS
BEGIN
dev360
pre_filtered_products =
SELECT * FROM
"SAP_HANA_EPM_NEXT"."sap.hana.democontent.epmNext.data::MD
.Products" WHERE CATEGORY NOT IN ('Laser printers');

ex_user_filtered_products =
APPLY_FILTER(:pre_filtered_products,
:im_product_filter_string ) ;

END

6. Click “Save”.

7. Once again, choose “Invoke


Procedure”.

13
DEV360

8. Click “Format Code”.

9. Enter the filter string for the


input parameter as
'CATEGORY = ''Notebooks'''
and click “Run”. Please note
the result again contains 10
records.

10. Once again, the results are


displayed, but this time they
are passed through a
parameter which you are able
to access for further
processing.

11. Change the input parameter


value to „OR 1 = 1‟ and click
“Run” again.

12. You will notice you now get


an error message when
passing „OR 1 = 1‟ to the
procedure. This happens as
the provided string will no
longer be concatenated to the
predefined filter conditions.
Instead the provided string will
be treated as a stand-alone
filter condition, in this case
having an invalid syntax.

14
DEV360

EXERCISE 3 – SQL VS CURSORS VS ARRAYS


Depending on the nature of the problem to be solved a solution using an imperative
algorithm might be worse to be considered. You will see the different possibilities based on
the example of calculating the cumulative sum of the number of delivered products.

Using SQL Only

We will …
- Remove the existing tabular output parameter (step 1)
- Define a new tabular output parameter named “EX_PRODUCTS” that has the columns
PRODUCTID NVARCHAR(10), DELIVERYDATE DAYDATE,
NUM_DELIVERED_PRODUCTS BIGINT, CUMULATIVE_SUM BIGINT (step 2)
- Rename the variable “EX_USER_FILTERED_PRODUCTS” to
“USER_FILTERED_PROD”CTS" (step 3)
- Add a query that joins the table variable “USER_FILTERED_PRODUCTS” with the table
PO.Item and assign the result to a new table variable “FILTERED_ITEMS” (step 4)
- Add a query that counts the products of the variable “FILTERED_ITEMS” and assigns the
result to a new table variable “AGGREGATED_FILTERED_ITEMS” (step 5)
- Add a query that calculated the cumulative sum of the delivered products per day on the
variable “AGGREGATED_FILTERED_ITEMS” and assign the result to the output parameter
(step 6-8)
- Call the procedure (steps 9-15)

Explanation Screenshot

1. Return the
“get_product_by_filter”
procedure in the editor.
Delete the output parameter.

2. Define a new output


parameter called
EX_PRODUCTS. Instead of
referring to predefined table of
table type we are now using in
place table type definition.

3. Rename
EX_USER_FILTERED_PROD
UCTS to
USER_FILTERED_PRODUC
TS.

15
DEV360

4. Enter another SELECT


statement which does an
INNER JOIN between the
results of the previous
SELECT statement and the
PO.Item table as shown here.
Assign this statement to a
table variable named
filtered_items.

5. Next enter another SELECT


which aggregates the results
of the previous SELECT
statement. Assign this
statement to a table variable
named
aggregated_filtered_items.

6. Finally, add another SELECT


statement which does a self
inner join to calculate the
cumulative sum. Assign this
statement to the ouput table
parameter ex_products.

7. The completed code should PROCEDURE


"HANA_DEV360_<group_number>"."dev360.exercises.g<group_num
look very similar to the ber>.procedures::get_product_by_filter" (
following. IN im_product_filter_string varchar(5000),
OUT EX_PRODUCTS TABLE (
If you do not wish to type this PRODUCTID NVARCHAR(10),
DELIVERYDATE DAYDATE,
code, you can reference the NUM_DELIVERED_PRODUCTS BIGINT,
solution web page at CUMULATIVE_SUM BIGINT ) )
http://lt5069.wdf.sap.corp:801 LANGUAGE SQLSCRIPT
0/workshop/admin/ui/exercise SQL SECURITY INVOKER
--DEFAULT SCHEMA <default_schema_name>
Master/index.html?workshop= READS SQL DATA AS
dev360 BEGIN

pre_filtered_products =
SELECT * FROM
"SAP_HANA_EPM_NEXT"."sap.hana.democontent.epmNext.data::MD
.Products" WHERE CATEGORY NOT IN ('Laser Printer');

user_filtered_products =
APPLY_FILTER(:pre_filtered_products,
:im_product_filter_string ) ;

filtered_items =
SELECT pi."PRODUCT.PRODUCTID" as PRODUCTID,
pi.DELIVERYDATE FROM :user_filtered_products as p
INNER JOIN
"SAP_HANA_EPM_NEXT"."sap.hana.democontent.epmNext.data::PO
.Item" as pi on p.productid = pi."PRODUCT.PRODUCTID" ;

aggregated_filtered_items =
SELECT PRODUCTID, DELIVERYDATE,
COUNT(PRODUCTID) AS
NUM_DELIVERED_PRODUCTS FROM :filtered_items

16
DEV360

GROUP BY PRODUCTID ,DELIVERYDATE


ORDER BY PRODUCTID, DELIVERYDATE;

ex_products =
SELECT p1.PRODUCTID,
p1.DELIVERYDATE,
p1.NUM_DELIVERED_PRODUCTS,
SUM(p2.NUM_DELIVERED_PRODUCTS) AS CUMULATIVE_SUM
FROM :aggregated_filtered_items as p1
INNER JOIN :aggregated_filtered_items as p2
ON p1.PRODUCTID = p2.PRODUCTID
and p1.DELIVERYDATE >= p2.DELIVERYDATE
GROUP BY p1.PRODUCTID,p1.DELIVERYDATE,
p1.NUM_DELIVERED_PRODUCTS
ORDER BY PRODUCTID, DELIVERYDATE ;

END

8. Click “Save”.

9. Click “Invoke Procedure”.

10.Click “Format Code”.

11. You will notice the CALL


statement is slightly more
readable.

12. Enter the value 'CATEGORY


= ''Notebooks'' OR
CATEGORY = ''PC''' for the
input parameter as shown.

13. Click “Run”.

17
DEV360

14. View the results.

15. Take note of the run time.

Using Cursors

This solution leverages a cursor for looping over the rows and calculating the sum and using
UNION ALL for building the result set to solve the exercise. It is the slowest running solution
out of the three shown possibilities.

We will …
- Create a new procedure “calculate_cumulative_sum_of_delivered_products” (step 1)
- Define a tabular input parameter “IM_PRODUCTS” that matches the result structure
of the variable “aggregated_filtered_items” in procedure “get_product_by_filter” and
define a tabular output parameter “EX_PRODUCTS” that has the same structure as
the output parameter of procedure “get_product_by_filter” (step 2)
- Add a default schema HANA_DEV360_<group_number> (step 3)
- DECLARE a cursor to iterate the tabular input parameter “im_products” and some
scalar variables for the productid and the cumulative sum (step 4)
- Use a SELECT from DUMMY statement to initialize the ex_products output
parameter (step 5)
- Use a FOR loop which will iterate over the input parameter table and perform the
calculation and finally update the output table parameter (steps 6-8)
- Replace the cumulate sum query in procedure “get_product_by_filter” with a call to
the newly created procedure “calculate_cumulative_sum_of_delivered_products”
(steps 9-10)
- Order the result by “PRODUCTID” and “DELIVERYDATE” (step 11-13)
- Call the procedure “get_product_by_filter” (steps 14-17)

18
DEV360

Explanation Screenshot

1. Use what you have learned


and create another procedure
called
“calculate_cumulative_sum_of
_delivered_products” in the
procedures folder under your
group package.

2. Enter the input and output


parameters as shown.

3. Uncomment the DEFAULT


SCHEMA line and insert the
name of your schema,
HANA_DEV360_XXX where
XXX is your group number.
Note, DO NOT USE group
000.

4. Enter the following DECLARE


statements.

5. Use a SELECT from DUMMY


statement to initialize the
ex_products output parameter.
This is required later for
executing a UNION within the
cursor loop.

19
DEV360

6. Enter a FOR loop which will


iterate over the input
parameter table and perform
the calculation and finally
update the output table
parameter.

7. The completed code should PROCEDURE


"HANA_DEV360_<group_number>"."dev360.exercises.g<group_num
look very similar to the ber>.procedures::calculate_cumulative_sum_of_delivered_pro
following. ducts" (
IN IM_PRODUCTS TABLE ( PRODUCTID NVARCHAR(10),
DELIVERYDATE DAYDATE,
NUM_DELIVERED_PRODUCTS BIGINT ),
If you do not wish to type this OUT EX_PRODUCTS TABLE ( PRODUCTID NVARCHAR(10),
code, you can reference the DELIVERYDATE DAYDATE,
solution web page at NUM_DELIVERED_PRODUCTS BIGINT,
http://lt5069.wdf.sap.corp:801 CUMULATIVE_SUM BIGINT ) )
LANGUAGE SQLSCRIPT
0/workshop/admin/ui/exercise SQL SECURITY INVOKER
Master/index.html?workshop= DEFAULT SCHEMA "HANA_DEV360_<group_number>"
dev360 READS SQL DATA AS
BEGIN

DECLARE CURSOR c_po_items FOR


SELECT * FROM :im_products;
DECLARE productid NVARCHAR(10) = '';
DECLARE cumulative_sum BIGINT = 0;

ex_products = SELECT
CAST ( '' AS NVARCHAR(10) ) AS PRODUCTID ,
CAST ( '' AS DAYDATE ) AS DELIVERYDATE,
CAST ( 0 AS BIGINT ) AS NUM_DELIVERED_PRODUCTS,
CAST ( 0 AS BIGINT ) AS CUMULATIVE_SUM
FROM DUMMY where 1 <> 1;

FOR cur_row AS c_po_items DO


IF :productid <> cur_row.PRODUCTID THEN
productid = cur_row.PRODUCTID;
cumulative_sum = cur_row.NUM_DELIVERED_PRODUCTS;
ELSE
cumulative_sum = :cumulative_sum +
cur_row.NUM_DELIVERED_PRODUCTS;
END IF;

ex_products = SELECT :productid AS PRODUCTID,


cast(cur_row.DELIVERYDATE AS DAYDATE) AS
DELIVERYDATE, cur_row.NUM_DELIVERED_PRODUCTS AS
NUM_DELIVERED_PRODUCTS,
:cumulative_sum AS CUMULATIVE_SUM
FROM DUMMY UNION ALL
SELECT * FROM :ex_products;
END FOR;

END

8. Click “Save”.

20
DEV360

9. Return to the procedure called


“get_product_by_filter” and
remove the last SELECT
statement.

10.Insert a call to the procedure


called
“calculate_cumulative_sum_of
_delivered_products” and
pass the
aggregated_filtered_items as
the input parameter and set
products as the output
parameter as shown.

11. Finally, add a SELECT


statement at the end to assign
the sorted results to the output
parameter.

12. The completed code should PROCEDURE


"HANA_DEV360_<group_number>"."dev360.exercises.g<group_num
look very similar to this ber>.procedures::get_product_by_filter" (
following. IN im_product_filter_string varchar(5000),
OUT EX_PRODUCTS TABLE ( PRODUCTID NVARCHAR(10),
DELIVERYDATE DAYDATE,
NUM_DELIVERED_PRODUCTS BIGINT,
If you do not wish to type this CUMULATIVE_SUM BIGINT ) )
code, you can reference the LANGUAGE SQLSCRIPT
solution web page at SQL SECURITY INVOKER
http://lt5069.wdf.sap.corp:801 --DEFAULT SCHEMA <default_schema_name>
READS SQL DATA AS
0/workshop/admin/ui/exercise BEGIN
Master/index.html?workshop=
dev360 pre_filtered_products =
SELECT * FROM
"SAP_HANA_EPM_NEXT"."sap.hana.democontent.epmNext.data::MD
.Products" WHERE CATEGORY NOT IN ('Laser Printer');

user_filtered_products =
APPLY_FILTER(:pre_filtered_products,
:im_product_filter_string ) ;

filtered_items =
select pi."PRODUCT.PRODUCTID" as PRODUCTID,
pi.DELIVERYDATE from :user_filtered_products as p
inner join
"SAP_HANA_EPM_NEXT"."sap.hana.democontent.epmNext.data::PO
.Item" as pi on p.productid = pi."PRODUCT.PRODUCTID" ;

aggregated_filtered_items =
SELECT PRODUCTID, DELIVERYDATE,
COUNT(PRODUCTID) AS NUM_DELIVERED_PRODUCTS
FROM :filtered_items
GROUP BY PRODUCTID ,DELIVERYDATE
ORDER BY PRODUCTID, DELIVERYDATE;

CALL
"HANA_DEV360_<group_number>"."dev360.exercises.g<group_num
ber>.procedures::calculate_cumulative_sum_of_delivered_pro
ducts"(
IM_PRODUCTS => :aggregated_filtered_items,
EX_PRODUCTS => :products ) ;

ex_products = select * from :PRODUCTS order by


PRODUCTID, DELIVERYDATE;

END

21
DEV360

13. Click “Save”.

14. Click “Invoke Procedure”

15. Enter the value


'CATEGORY =
''Notebooks'' OR
CATEGORY = ''PC''' for the
input parameter and Click
“Run”. Note: This
procedure call will take a
few seconds, just wait for it
to return results.

16. Eventually, the results will be


shown.

17. You will notice that using


cursors takes quite a bit of
time.

22
DEV360

Using Arrays

This solution shows how to use Arrays data iteration as well as the result creation. This
options is the fastest among the solutions.

We will …
- Remove the existing logic from procedure
“calculate_cumulative_sum_of_delivered_products” (steps 1-2)
- Declare scalar loop variables as well as an ARRAY for each column of the tabular
input parameter “IM_PRODUCTS” and bind the corresponding columns to the
ARRAYS using the ARRAY_AGG(…) function (step 3-4)
- Use a FOR loop to perform similar calculation as before and insert the value into the
array (step 5)
- Use the UNNEST statement to render the arrays into the output table parameter
(steps 6-8)
- Call the procedure “get_product_by_filter” (steps 9-12)

Explanation Screenshot

1. Returned to the procedure


called
“calculate_cumulative_sum_of
_delivered_products”.

2. Delete all of the logic in the


body of the procedure.

3. Enter the following DECLARE


statements. Notice here you
are declaring 4 arrays.

23
DEV360

4. Next, use the ARRAY_AGG


statement to extract a column
of the table into an array.

5. Use a FOR loop to perform


the calculation and insert the
value into the array.

6. Finally, use the UNNEST


statement to render the arrays
into the output table
parameter.

7. The completed code should PROCEDURE


"HANA_DEV360_<group_number>"."dev360.exercises.g<group_num
look like the following. ber>.procedures::calculate_cumulative_sum_of_delivered_pro
ducts" (
If you do not wish to type this IN IM_PRODUCTS TABLE ( PRODUCTID NVARCHAR(10),
code, you can reference the DELIVERYDATE DAYDATE,
NUM_DELIVERED_PRODUCTS BIGINT ),
solution web page at OUT EX_PRODUCTS TABLE ( PRODUCTID NVARCHAR(10),
http://lt5069.wdf.sap.corp:801 DELIVERYDATE DAYDATE,
0/workshop/admin/ui/exercise NUM_DELIVERED_PRODUCTS BIGINT,
Master/index.html?workshop= CUMULATIVE_SUM BIGINT ) )
LANGUAGE SQLSCRIPT
dev360 SQL SECURITY INVOKER
DEFAULT SCHEMA "HANA_DEV360_<group_number>"
READS SQL DATA AS
BEGIN

DECLARE PRODUCTID NVARCHAR(10) ARRAY;


DECLARE DELIVERYDATE DAYDATE ARRAY;
DECLARE NUM_DELIVERED_PRODUCTS BIGINT ARRAY;
DECLARE CUMULATIVE_SUM BIGINT ARRAY;

DECLARE tmp_productid NVARCHAR(10) = '';


DECLARE tmp_cumulated BIGINT = 0;
DECLARE i INTEGER = 1;

PRODUCTID = ARRAY_AGG( :IM_PRODUCTS.PRODUCTID );


DELIVERYDATE = ARRAY_AGG( :IM_PRODUCTS.DELIVERYDATE );
NUM_DELIVERED_PRODUCTS = ARRAY_AGG(
:IM_PRODUCTS.NUM_DELIVERED_PRODUCTS );

FOR i IN 1..CARDINALITY(:PRODUCTID) DO

IF :tmp_productid <> :PRODUCTID[:i] THEN


tmp_productid = :PRODUCTID[:i];
CUMULATIVE_SUM[:i] = :NUM_DELIVERED_PRODUCTS[:i];
ELSE
CUMULATIVE_SUM[:i] = :CUMULATIVE_SUM[:i-1]
+ :NUM_DELIVERED_PRODUCTS[:i];
END IF;

END FOR;

ex_products = UNNEST( :PRODUCTID, :DELIVERYDATE,

24
DEV360

:NUM_DELIVERED_PRODUCTS, :CUMULATIVE_SUM)
AS ( PRODUCTID, DELIVERYDATE,
NUM_DELIVERED_PRODUCTS, CUMULATIVE_SUM );

END

8. Click “Save”.

9. Switch back to the SQL tab.

10. Click “Run” to run the


procedure again.

11. Check the results.

12. Notice the execution time is a


little bit less than when doing
the calculation using SQL, and
a lot less than when doing the
calculation using cursors.

25
DEV360

EXERCISE 4 – EXCEPTION HANDLING, COMMIT & AUTONOMOUS


TRANSACTIONS
Using Exception Handling

In this example we will exchange the dates of the scalar input parameters to prevent the
error in case the start date is larger than the end date. To do so we will rely on exception
handling.

We will …
- Introduce two scalar date input parameters to procdure “get_product_by_filter” which
will be used to filter a range of valid delivery dates (steps 1-5)
- Call the procedure “get_product_by_filter” (steps 6-9)
- Include a check for the two scalar input parameter and throw an error in case the
check fails by signalling a user defined error code and error message (steps 10-11)
- Call the procedure “get_product_by_filter” (steps 12-13)
- Create a logging and messaging table using a .hdbdd file (steps 14-17)
- Remove read only property from procedure “get_product_by_filter” (step 19)
- Remove IF statement from step 10 and declare local scalar variables to enable
modification of the date values as well as a custom condition MYCOND as alias for
error code 10001 (steps 20-21, step 23)
- Declare a custom exit handler for the custom condition within a nested BEGIN/END
block. The exit handler should insert a new record into the log table and switch start
date and end date in case the start date is larger than the end date (steps 22, 24-25)
- Call the procedure “get_product_by_filter” and check te content of the logging table
(steps 27-31)

Explanation Screenshot

1. Return to the
“get_product_by_filter”
procedure.

2. Add two additional input


parameters for start and end
date as shown.

26
DEV360

3. Add a WHERE clause to the


third statement which filters
the data by the start and end
date input parameters.

4. The completed code should PROCEDURE


"HANA_DEV360_<group_number>"."dev360.exercises.g<group_num
look very similar to this. ber>.procedures::get_product_by_filter" (
IN im_product_filter_string varchar(5000),
If you do not wish to type this IN im_start_date DATE,
code, you can reference the IN im_end_date DATE,
OUT EX_PRODUCTS TABLE (
solution web page at PRODUCTID NVARCHAR(10),
http://lt5069.wdf.sap.corp:801 DELIVERYDATE DAYDATE,
0/workshop/admin/ui/exercise NUM_DELIVERED_PRODUCTS BIGINT,
Master/index.html?workshop= CUMULATIVE_SUM BIGINT ) )
LANGUAGE SQLSCRIPT
dev360 SQL SECURITY INVOKER
--DEFAULT SCHEMA <default_schema_name>
READS SQL DATA AS
BEGIN

pre_filtered_products =
SELECT * FROM
"SAP_HANA_EPM_NEXT"."sap.hana.democontent.epmNext.data::MD
.Products" WHERE CATEGORY NOT IN ('Laser Printer');

user_filtered_products =
APPLY_FILTER(:pre_filtered_products,
:im_product_filter_string ) ;

filtered_items =
select pi."PRODUCT.PRODUCTID" as PRODUCTID,
pi.DELIVERYDATE from :user_filtered_products as p
inner join
"SAP_HANA_EPM_NEXT"."sap.hana.democontent.epmNext.data::PO
.Item" as pi on p.productid = pi."PRODUCT.PRODUCTID"
where pi.DELIVERYDATE >= :im_start_date
AND pi.DELIVERYDATE <= :im_end_date;

aggregated_filtered_items =
SELECT PRODUCTID, DELIVERYDATE, COUNT(PRODUCTID) AS
NUM_DELIVERED_PRODUCTS FROM :filtered_items
GROUP BY PRODUCTID ,DELIVERYDATE
ORDER BY PRODUCTID, DELIVERYDATE;

CALL
"HANA_DEV360_<group_number>"."dev360.exercises.g<group_num
ber>.procedures::calculate_cumulative_sum_of_delivered_pro
ducts"(
IM_PRODUCTS => :aggregated_filtered_items,
EX_PRODUCTS => :products );

ex_products = select * from :PRODUCTS order by


PRODUCTID, DELIVERYDATE;

END;

5. Click “Save”.

27
DEV360

6. Click “Invoke Procedure”.

7. Enter the values for the input


parameters as shown.

8. Click “Run”.

9. Results are shown.

10. Return to the procedure


called “get_product_by_filter”.
We will include a check for the
two scalar input parameters
and throw an error in case the
check fails. To do so please
enter the following code after
the BEGIN statement.

If you do not wish to type this


code, you can reference the
solution web page at
http://lt5069.wdf.sap.corp:801
0/workshop/admin/ui/exercise
Master/index.html?workshop=
dev360

11. Click “Save”.

28
DEV360

12. Return to the SQL tab and


change the values of the start
and end date as shown. Click
“Run”.

13. You should see an error


message showing user
defined error 10001. If you
scroll to the right, you can see
the message text as well.

14. We do not want to stop the


procedure execution by
throwing a signal. Instead we
will handle the exception, write
the exception into a log table
and continue with the
procedure execution.
As a prerequisite we need to
create a table that we will use
for logging. Therefore, go to
the data folder and create a
new file by right-clicking on the
data folder and choosing
“New”, then “File”.

15. Enter the name of the file as


log.hdbdd and click “Create”.

16. Enter the code into the editor namespace dev360.exercises.g<group_number>.data;


as shown. Make sure to @Schema: 'HANA_DEV360_<group_number>'
substitute your group number context log {
where appropriate.
@Catalog.tableType : #COLUMN
@nokey
If you do not wish to type this entity errors {
code, you can reference the ERROR_TIMESTAMP: UTCDateTime;
solution web page at PARAMETER: String(256);
http://lt5069.wdf.sap.corp:801 SQL_ERROR_CODE: Integer;
SQL_ERROR_MESSAGE: String(5000);
0/workshop/admin/ui/exercise };
Master/index.html?workshop=
dev360 @Catalog.tableType : #COLUMN
@nokey
entity messages {
ERROR_TIMESTAMP: UTCDateTime;
PARAMETER: String(256);

29
DEV360

SQL_ERROR_CODE: Integer;
SQL_ERROR_MESSAGE: String(5000);
};

};

17. Click “Save”.

18. Switch back to the procedure


called “get_product_by_filter”.

19. Since we will have a DML


statement in the procedure we
cannot flag it read only.
Please remove the “READS
SQL DATA” statement.

20. Remove the IF statement that


you inserted a little earlier.

21. Insert these DECLARE


statements as shown. Notice
the last DECLARE statement
is declaring a custom
condition and assigning error
code 10001 in the user
defined number range.

30
DEV360

22. After the last DECLARE


statement, insert the following
code. This code is declaring a
custom exit handler for your
custom condition. This exit
handler will insert a new
record in to the log table and
will switch start date and end
date accordingly, The
surrounding block, marked by
the BEGIN/END statements
ensures that after the
exception is caught, execution
will continue after the block.
Make sure to substitute your
group number where
appropriate, do NOT use
group 000 as shown in the
screen shot.

23. Change the WHERE clause


of SELECT statement for
“filtered_items” as shown
here. This ensures usage of
the local declared date
variables which contain the
corrected values.

24. The completed code should PROCEDURE


"HANA_DEV360_<group_number>"."dev360.exercises.g<group_num
look very similar to the ber>.procedures::get_product_by_filter" (
following. IN im_product_filter_string varchar(5000),
IN im_start_date DATE,
If you do not wish to type this IN im_end_date DATE,
OUT EX_PRODUCTS TABLE (
code, you can reference the PRODUCTID NVARCHAR(10),
solution web page at DELIVERYDATE DAYDATE,
http://lt5069.wdf.sap.corp:801 NUM_DELIVERED_PRODUCTS BIGINT,
0/workshop/admin/ui/exercise CUMULATIVE_SUM BIGINT ) )
LANGUAGE SQLSCRIPT
Master/index.html?workshop= SQL SECURITY INVOKER
dev360 --DEFAULT SCHEMA <default_schema_name>
AS
BEGIN

DECLARE temp_date DATE;


DECLARE local_start_date DATE = :im_start_date;
DECLARE local_end_date DATE = :im_end_date;

DECLARE MYCOND CONDITION FOR SQL_ERROR_CODE 10001;

BEGIN
DECLARE EXIT HANDLER FOR MYCOND
BEGIN
DECLARE parameter NVARCHAR(256) = 'start_date = '||
:local_start_date ||
' end_date = '||
:local_end_date;
temp_date = :local_start_date;
local_start_date = :local_end_date;
local_end_date = :temp_date;
INSERT INTO
"HANA_DEV360_<group_number>"."dev360.exercises.g<group_num
ber>.data::log.errors"
VALUES (current_timestamp, :parameter ,
::SQL_ERROR_CODE, ::SQL_ERROR_MESSAGE);
END;

if :im_start_date > :im_end_date THEN


SIGNAL MYCOND SET MESSAGE_TEXT = 'Start date

31
DEV360

must be smaller then end date';


END IF;

END;

pre_filtered_products =
SELECT * FROM
"SAP_HANA_EPM_NEXT"."sap.hana.democontent.epmNext.data::MD
.Products" WHERE CATEGORY NOT IN ('Laser Printer');

user_filtered_products =
APPLY_FILTER(:pre_filtered_products,
:im_product_filter_string ) ;

filtered_items =
select pi."PRODUCT.PRODUCTID" as PRODUCTID,
pi.DELIVERYDATE from :user_filtered_products as p
inner join
"SAP_HANA_EPM_NEXT"."sap.hana.democontent.epmNext.data::PO
.Item" as pi on p.productid = pi."PRODUCT.PRODUCTID"
where pi.DELIVERYDATE >= :local_start_date
AND pi.DELIVERYDATE <= :local_end_date;

aggregated_filtered_items =
SELECT PRODUCTID, DELIVERYDATE,
COUNT(PRODUCTID) AS
NUM_DELIVERED_PRODUCTS FROM :filtered_items
GROUP BY PRODUCTID ,DELIVERYDATE
ORDER BY PRODUCTID, DELIVERYDATE;

CALL
"HANA_DEV360_<group_number>"."dev360.exercises.g<group_num
ber>.procedures::calculate_cumulative_sum_of_delivered_pro
ducts"(
IM_PRODUCTS => :aggregated_filtered_items,
EX_PRODUCTS => :products );

ex_products = select * from :PRODUCTS order by


PRODUCTID, DELIVERYDATE;

END;

25. Click “Save”.

26. Switch back to the SQL tab.

27. Click “Run”.

32
DEV360

28. Notice the results of the


procedure are returned.

29. In the SQL tab, enter a


SELECT statements against
your log.errors and
log.messages table as shown.

If you do not wish to type this


code, you can reference the
solution web page at
http://lt5069.wdf.sap.corp:801
0/workshop/admin/ui/exercise
Master/index.html?workshop=
dev360

30. Highlight the SELECT


statement for log.errors, and
click “Run”.

31. You should see an error


message in your table. This
means that the exception was
caught by the handler, and
execution was allowed to
continue since you saw the
results of the procedure call.

33
DEV360

Using COMMIT Statement

In this exercise will show the impact of a runtime error on DML statements and how to prevent it using
COMMIT.

We will…
- Call procedure “get_product_by_filter” with a filter string that contains a non-existing column
as well as a start date that is larger than the end date and check the logging table for the
expected error message (steps 1-6)
- Add another INSERT statement to the procedure that writes an entry in the message table
(step 7)
- Add a COMMIT statement after the INSERT statement In the exception handler to ensure that
the error message gets persisted into the log table in any case (steps 8-9)
- Call procedure “get_product_by_filter” with similar filter conditions as above and check the
content of the logging and messaging table (steps 10-14)

Explanation Screenshot

1. Return to the SQL tab and


change the filter value for the
first input parameter. Here we
are adding a filter on a column
which we know does not
exists in hopes of causing an
error and transaction rollback.

2. Select the entire CALL


statement, and click „Run‟.

3. Of course we get the error


“invalid column name”.

4. Select the SELECT statement


for log.errors again and click
“Run” to check the table
contents.

34
DEV360

5. You will notice that a new row


was not inserted into the log
table due to transaction
rollback.

6. Return to the procedure called


“get_product_by_filter”. To
avoid the deletion of the log
entry in case of transaction
rollback, we will use an explicit
COMMIT.

7. Let‟s insert a DML statement


for the sake of showing the
behavior of COMMIT. Insert
this INSERT statement with
BEGIN and END blocks after
the DECLARE statements as
shown.

If you do not wish to type this


code, you can reference the
solution web page at
http://lt5069.wdf.sap.corp:801
0/workshop/admin/ui/exercise
Master/index.html?workshop=
dev360

8. After the INSERT statement


with in the EXIT HANDLER,
add a COMMIT statement.

9. Click “Save”.

35
DEV360

10.Once again return to the SQL


tab and select the CALL
statement, click “Run”.

11.You will still get the error for


invalid column. Select the
SELECT statement for
log.errors and click “Run” to
execute it.

12. You will now notice that the


new row has been inserted
into the log table even though
there was an error and a
ROLLBACK was executed.

13. Highlight the SELECT


statement for log.messages
and click “Run”.

14. As you can see not only was


the new record inserted into
the log.errors table, but also
“Chuck Norris” found its way
into our log.messages table.
The complete transaction will
be committed, meaning any
modification happened in this
transaction will be persisted.
A better solution for this are
the autonomous transaction

36
DEV360

Using Autonomous Transactions

The autonomous transaction is independent from the main procedure transaction. Changes made and
committed by an autonomous transaction can be stored in persistency regardless of commit/rollback
of the main transaction. The end of the autonomous transaction block has an implicit commit.

We will …
- Remove the COMMIT statement in procedure “get_product_by_filter” and instead wrap the
INSERT statement with an AUTONOMOUS TRANSACTION block (steps 1-3)
- Call procedure “get_product_by_filter” with similar filter conditions as above and check the
content of the logging and messaging table (steps 4-8)

Explanation Screenshot

1. Return to the procedure


called “get_product_by_filter”.

2. Remove the COMMIT


statement, and instead wrap
the INSERT statement with an
AUTONOMOUS
TRANSACTION block as
shown.

3. Click “Save”.

37
DEV360

4. Once again return to the SQL


tab and select the CALL
statement, click “Run”.

5. You will still get the error for


invalid column. Select the
SELECT statement for the
log.errors table and click “Run”
to execute it.

6. You will now notice that a new


row was entered into the
log.errors table.

7. Select the SELECT statement


for the log.messages table
and click “Run” to execute it.

8. Another “Chuck” record was


not inserted. “Chuck Norris‟s”
record was removed by the
rollback (“Is that even
possible…?”) by using
AUTONOMOUS
TRANSACTION blocks, the
code within is isolated from
the rest of the mainline code
and is treated as a separate
transaction.

38
DEV360

EXERCISE 5 – CHALLENGE!! 99 BOTTLES OF BEER


This is an optional exercise and is meant to challenge you to use what you have learned
previously and what you have learned in this class. Create a procedure who‟s output is the
99 Bottle of Beer song. You know the lyrics… If not, here they are.

99 bottles of beer on the wall, 99 bottles of beer.


Take one down and pass it around, 98 bottles of beer on the wall.
98 bottles of beer on the wall, 98 bottles of beer.
Take one down and pass it around, 97 bottles of beer on the wall.
97 bottles of beer on the wall, 97 bottles of beer.
Take one down and pass it around, 96 bottles of beer on the wall.

1 bottle of beer on the wall, 1 bottle of beer.


Take one down and pass it around, no more bottles of beer on the wall.

No more bottles of beer on the wall, no more bottles of beer.


Go to the store and buy some more, 99 bottles of beer on the wall.

You can find some examples of how this was programmed in other languages at
http://www.99-bottles-of-beer.net/

Have fun!

Below is our solution, if you would like to try this solution it can be referenced here.
http://lt5069.wdf.sap.corp:8010/workshop/admin/ui/exerciseMaster/index.html?workshop=dev
360

DO BEGIN
DECLARE bottles INTEGER = 99;
DECLARE bottle_phrase NVARCHAR(50) = '';
DECLARE wall_phrase NVARCHAR(50) = ' of beer on the wall';
DECLARE beer_phrase NVARCHAR(50) = ' of beer.';
DECLARE take_phrase NVARCHAR(50) = 'Take 1 down, pass it around, ';

ex_lyric_table = SELECT 0 AS INDEX, '' AS LINE FROM DUMMY WHERE 1=0;

WHILE :bottles >= 0 DO

bottle_phrase = :bottles || ' bottles';

IF :bottles = 0 THEN
bottle_phrase = 'No more bottles';
take_phrase = 'Go to the store and buy some more, ';
END IF;
IF :bottles = 1 THEN
bottle_phrase = :bottles || ' bottle';
END IF;

ex_lyric_table = SELECT * FROM :ex_lyric_table UNION ALL


SELECT :bottles as INDEX, :bottle_phrase || :wall_phrase || ', '
|| LOWER(:bottle_phrase) || :beer_phrase || CHAR(10) ||
:take_phrase || REPLACE(:bottle_phrase,'No more','99')
|| :wall_phrase || '.' || CHAR(10) as LINE FROM DUMMY;

bottles = :bottles - 1;

END WHILE;

SELECT STRING_AGG(LINE, CHAR(10) ORDER BY INDEX DESC) FROM :ex_lyric_table;


END

39
DEV360

© 2015 SAP SE or an SAP affiliate company. All rights reserved.


No part of this publication may be reproduced or transmitted in any form or for any purpose without the express permission of SAP SE or an SAP
affiliate company. SAP and other SAP products and services mentioned herein as well as their respective logos are trademarks or registered
trademarks of SAP SE (or an SAP affiliate company) in Germany and other countries.
Please see http://www.sap.com/corporate-en/legal/copyright/index.epx#trademark for additional trademark information and notices.

40