openSAP Hanasql2 Week 3 Transcript EN
openSAP Hanasql2 Week 3 Transcript EN
openSAP
Using the Optimizer for SQLScript Programming in SAP
HANA
00:00:05 Hello, and welcome to the first unit of week three. I'm Jin Yeon Lee and I'm very glad to
present
00:00:12 the content of week three with my colleague Minjeong Lee.
00:00:17 In week one and two, we studied how SQLScript engine was, and how to use its
supportability features.
00:00:27 This week we will study useful methods NO_INLINE hint, BIND_AS_PARAMETER function,
00:00:32 BIND_AS_VALUE function, and SQL hints for SQLScript... By the end of this week, you'll be
familiar
00:00:44 with using these methods to solve performance issues on your SQLScript procedure.
00:00:53 Before explaining the INLINE feature, let me tell about usage of table variables first.
00:01:04 In case we have to make a complex logic in SQL, we usually compose a long and complex
query
00:01:11 with many relational operations like joins and group bys. Sometimes we use subqueries too.
00:01:22 If you have to edit this long complex SQL statement, it is certainly very difficult.
00:01:29 And it sometimes leads to unexpected results. But in SQLScript,
00:01:38 instead of composing very long and complex single SQL statements,
00:01:44 we can make several short SQL statements combined with table variables.
00:01:53 Logically, these are exactly the same, but this way can reduce the complexity of SQL
statements.
00:02:02 Then it is definitely helpful to comprehend the logic, so then you can easily modify them if
needed.
00:02:14 For this reason, using table variables is always recommended. And I'm sure you are already
using table variables a lot
00:02:22 in your SQLScript procedure. But let's think about using table variables
00:02:33 from a performance perspective. What do you think of executing multiple SQL statements
00:02:41 instead of a single long complex query in SQLScript calls? As we learned,
00:02:51 when an SQL statement in SQLScript procedure is executed, SQLScript engine passes that
query to SQL engine,
00:03:00 then gets the result set. The more result sets, the more interactions
00:03:07 between SQLScript engine and SQL engine happen, including memory copy operations and
so on.
00:03:20 And those are expensive from a performance point of view. Instead of multiple short
queries,
00:03:31 if a single long complex query is passed to an SQL engine from an SQLScript engine,
00:03:38 then this query can be executed with the optimal query plan by SQL optimizer.
00:03:44 Then its results set is passed to SQLScript engine one time.
00:03:52 SAP HANA database provides a feature to take both advantages. If you use table variables
to split the long complex query
00:04:03 into multiple short ones, then HANA database executes them
00:04:07 as a single complex one internally. So you don't need to worry about a performance penalty
00:04:14 by multiple short queries with table variables. SQLScript optimizer tries to combine
00:04:25 dependent SQL statements, as many as possible, and make them a single query string.
00:04:34 This is called inlining, one of the optimizations of SQLScript.
00:04:41 It is beneficial to the performance. For example, materialization,
00:04:49 materializing results of table variables are omitted,
00:04:53 and a better join order can be chosen by SQL optimizer. Let me show how the inlining
feature works
00:05:03 with this example procedure. The procedure "get_results" consists of four SQL statements,
00:05:11 and they are linked with table variables. Table variable "final_products" contains the result
00:05:20 of SQL statement one and it is used in SQL statement two.
00:05:28 Table variable "item" is for the result of statement two and used in statement three.
00:05:37 Lastly, the table variable "aggregated_items" comes from statement three,
00:05:43 and it is consumed in statement four. When this procedure is compiled, SQLScript optimizer
00:05:52 checks the dependency of these four SQL statements, then applies the inlining feature.
00:06:02 As I explained before, INLINE features rewrite multiple SQL statements
00:06:07 into an equivalent single SQL statement using WITH clause.
00:06:14 So once these queries are inline, these SQL statements are totally reshaped.
00:06:22 Let's check the result. When you use EXPLAIN PLAN FOR CALL,
00:06:30 you can find which SQL statements are executed, which is the output of SQLScript
optimization,
00:06:37 without executing the procedure. Since we collected EXPLAIN PLAN FOR CALL with the
statement name "INLINE" before,
00:06:47 let's delete existing records having STATEMENT_NAME "INLINE" from
EXPLAIN_CALL_PLANS table first.
00:06:58 Then execute EXPLAIN PLAN FOR CALL for this procedure with input parameters.
00:07:04 Then check the result from EXPLAIN_CALL_PLANS table. As you can see,
OPERATOR_STRING column,
00:07:17 there is only one SQL statement though there are four SQL statements in its definition code.
00:07:26 Now, let's check SQLSCRIPT_PLAN_PROFILER. This shows actual executed SQL
statements,
00:07:38 whereas "EXPLAIN PLAN FOR CALL" shows an SQLScript execution plan, before actual
execution.
00:07:49 Unlike the result of "EXPLAIN PLAN FOR CALL" in the previous slide,
00:07:54 the result of plan profiler for this call statement shows two SQL statements.
00:08:06 To see executed SQL statements, you should check a value in column
OPERATOR_STATEMENT_STRING
00:08:13 where the column operator is Execute SQL Statement. Table Assign Op in column
OPERATOR
00:08:25 is your operation for assigning table variable. And Execute SQL Statement operation
2 / 21
00:08:33 is next to this Table Assign Op. This means a query result from Execute SQL Statement
operation
00:08:43 is stored in given table variables, in this Table Assign Op operation.
00:08:51 Let's check the first SQL statement. The first one starts with a comment.
00:08:58 This comment shows how the inlining feature is applied. It shows which table variables
00:09:07 are used in this WITH clause by showing their name and position of the code.
00:09:15 This information is very important and helpful because you have to modify your SQL script
code.
00:09:24 You can easily find how your change will be reshaped. At the end of this comment,
00:09:34 you can see WITH clause follows. Then let me explain the last SQL statement
00:09:42 which you didn't see in the result of "EXPLAIN PLAN FOR CALL".
00:09:48 This select statement is for fetching the result to output parameter.
00:09:55 "EXPLAIN PLAN FOR CALL" does not execute the procedure, so SQLScript engine does
not need to send the result set
00:10:04 via output parameter. That is why select statement for
00:10:12 this output parameter is not shown,
00:10:16 but SQLScript Plan Profiler shows actual execution of call statement.
00:10:24 When call statement is executed, output parameter is actually selected and returned
00:10:31 as the result of call statement. That's why this last SQL statement is shown here.
00:10:41 Let me show the output of inlining. In the code, four SQL statements are associated
00:10:48 with table variables. After SQLScript optimizer confirms dependency
00:10:56 of these four SQL statements and their table variables, it combines them into a single SQL
statement.
00:11:05 The new single SQL statement starts from WITH clause and three SQL statements
00:11:13 and their table variables are now represented as a subquery in that WITH clause.
00:11:21 When checking the logic of this new SQL statement, you can see its logic is exactly the
same as the one
00:11:27 from four SQL statements in the code. In WITH clause, statement one is merged
00:11:37 as a subquery with query name "_SYS_FINAL_PRODUCT_2".
00:11:44 Statement two is merged with query name "_SYS_ITEMS_2" and statement three is done
with query name
00:11:54 "_SYS_AGGREGATED_ITEMS_2". During the execution of this single statement,
00:12:01 values in table variable, final products, items, and aggregated items are not materialized.
00:12:10 Instead, join and group by operations run on their intermediate result and only the final
results set of aggregated item
00:12:19 is materialized and copied to SQLScript engine.
3 / 21
Week 3 Unit 1(II)
00:00:05 Even though inlining is beneficial to performance in most cases, there may be a rare case
00:00:14 where combined SQL statements lead to a non-optimal query plan.
00:00:20 In this case, blocking a combination of specific SQL statements will help for better
performance.
00:00:28 And you can do that by using the NO_INLINE hint. For example, a procedure has a lot of
dependencies
00:00:40 from many SQL statements. The inlining feature combines them into a very complex query.
00:00:48 SQL optimizer needs more time to optimize the SQL query due to it having so many things
to be checked.
00:00:59 Then it finally results in long compilation time. Normally long compilation is not a problem.
00:01:06 Because once its query plan is stored in SQL plan cache, next time the cached plan is
reused without compilation.
00:01:19 But, in case the long compilation time may affect overall performance,
00:01:25 so you want to reduce the compilation time, then you can use NO_INLINE hint to avoid
inlining.
00:01:34 Let me present an example of this case in the next unit. Another case of blocking inlining
00:01:44 is when a sub-optimal plan is chosen due to a big join size. Let's assume there are two
queries,
00:01:54 called query one and query two inside the procedure. Each query has several joins.
00:02:04 And these two queries are merged into a single query by SQLScript optimizer.
00:02:14 At this point, there may be a situation where joins become very complex and large
00:02:22 after merging joins from two queries, and running into an unexpected out-of-memory
situation.
00:02:30 In this case, we can try to use NO_INLINE hint. Going back to the example, let's try to use
NO_INLINE hint
00:02:42 at the end of the first SQL statement in procedure get_results. As you know, the first SQL
statement is connected
00:02:53 with the second one by table variable final_products. Now, due to the added NO_INLINE
hint,
00:03:05 SQLScript optimizer does not combine the first SQL statement and second one. Even if they
have dependency.
00:03:14 Since the second and third and fourth SQL statement do not have NO_INLINE hint,
00:03:21 they are combined just like before. Then, let's check the result of explain plan for CALL
00:03:28 to see how the execution plan of this procedure is changing. Let's delete the record having
the statement name INLINE
00:03:40 from EXPLAIN_CALL_PLANS table first, because it contains the result of previous explain
plan for CALL.
00:03:52 Then execute explain plan for CALL for this procedure with input parameters again.
00:04:02 Then check the results from the EXPLAIN_CALL_PLANS table. As you can see, two SQL
statements
00:04:13 are in column OPERATOR_STRING now. The first SQL statement shows WITH HINT
NO_INLINE is added.
00:04:25 The other SQL statement is output of the inlining optimization from the second, third, and
fourth SQL statements.
00:04:37 Then now, let's check the result of the SQLScript Plan Profiler. As you expected, it shows
three SQL statements.
4 / 21
00:04:48 The first one is the SQL statement having NO_INLINE hint. The second one is inlined SQL
statement
00:04:58 from the second, third, and fourth SQL statement. This is just the same result as explain
plan for CALL shows.
00:05:12 The last SQL statement is select a statement for output parameters, as you had checked
previously.
00:05:19 As I explained, Table Assign Op is an operation to assign table variables for the next
operation.
00:05:28 Its operator statement string also shows SQL strings that are executed in the next
operation.
00:05:37 But you can see $$_SS_SE_TAB_VAR_FINAL_PRODUCTS_2$$ in FROM clause. The
query looks like it is coming
00:05:52 from the second SQL statement in the code, and
$$_SS_SE_TAB_VAR_FINAL_PRODUCTS_2$$
00:06:03 is in the red box. It looks like the name of
00:06:09 table variable, FINAL_PRODUCTS. It comes from the first SQL statement.
00:06:15 When you check the executed SQL statement,
$$_SS_SE_TAB_VAR_FINAL_PRODUCTS_2$$
00:06:22 in the red box is not shown anymore. Instead, you can see SYS_SS2_TMP_TABLE, some
numbers,
00:06:34 FINAL_PRODUCTS_2 with a long GUID value in the green box here.
00:06:40 Both point the table variable final product, which is assigned from the first SQL statement.
00:06:50 But the name having $$ is a kind of a logical name of a table variable.
00:06:56 And its physical name is the long name having GUID value.
00:07:04 This GUID value is generated newly whenever an SQL script is compiled.
00:07:13 This naming rule for local temp table was explained in unit two of week two before.
00:07:23 And it'll be explained again in the last unit of week three.
00:07:28 So far, we checked what happened when NO_INLINE hint is added at the end of the first
SQL statement.
00:07:39 This time, let's try to break all dependency by adding this hint at the end of the first,
00:07:48 second, and third SQL statements. Let's repeat the step to see the result of explain plan for
CALL.
00:07:58 As you expected, the SQLScript execution plan contains four SQL statements.
00:08:06 Three SQL statements have WITH HINT NO_INLINE. SQLScript Plan Profiler shows the
same result.
00:08:16 You can also see logical temp table with GUID values in their names, from the executed
SQL statement.
00:08:28 It means the results of each SQL statement are stored in corresponding local temp as table
variables.
00:08:39 As I said, in this case values of result sets are materialized. In column OPERATOR
DETAILS, you can see the size
00:08:50 of the table variable where the result of the query is stored. Its unit is bytes.
00:08:58 Okay, so in the previous example, we added NO_INLINE hint
00:09:03 at the end of the first, second, and third SQL statements. So, four SQL statements were
executed separately.
00:09:14 Then, can you guess what will happen if NO_INLINE hint is added to the last SQL
statement?
00:09:22 We learned the procedure flattening optimization rule in week one. It means a procedure A
is called inside another procedure B
00:09:34 by a CALL statement. This CALL statement for procedure A
5 / 21
00:09:39 is replaced with the body of procedure A. Once procedure A is flattened, then all SQL
statements of
00:09:49 procedure A can be inlined with SQL statement of procedure B.
00:09:57 Let's check this example. Procedure get_results_wrapper has a CALL statement
00:10:06 of procedure get_results and one simple SQL statement. Since procedure get_results
already has four SQL statements,
00:10:19 so let's say this simple SQL statement, SELECT * FROM RESULTS_TAB, is statement five.
00:10:28 And all four SQL statements in procedure get_results have NO_INLINE hint.
00:10:38 Here is the result of explain plan for CALL. You can see five SQL statements in the
execution plan,
00:10:49 because NO_INLINE hint at the end of the fourth SQL statement. blocks combining
statement four and statement five.
00:11:00 If you don't add NO_INLINE hint at the end of fourth SQL statement,
00:11:05 then this is explain plan for CALL shows only four SQL statements,
00:11:10 because statements four and five are combined. The last case is adding NO_INLINE hint
00:11:18 at the end of inner CALL statement. Here's an example.
00:11:25 Procedure get_results_wrapper consists of CALL procedure, CALL procedure get_results,
and a simple SELECT statement.
00:11:37 What will happen if NO_INLINE hint is added at the end of the CALL statement of the
procedure get_results?
00:11:46 Let's check the result of explain plan for CALL. As you can see here, CALL statement of
procedure get_results
00:11:56 is executed via operator call. This means procedure get_results is not flattened.
00:12:05 So NO_INLINE hint blocks not only inlining of SQL statement, but also flattening inner CALL
statement.
00:12:17 In week one, we learned four SQL Script optimization rules. Constant propagation,
procedure flattening,
00:12:27 SQL statement inlining, and control flow simplification. You can control inlining optimization
00:12:36 and procedure flattening by using NO_INLINE hint. This hint is very simple to use, but very
powerful.
00:12:48 So that your procedure can be executed in totally different ways. So you always have to
check the result of
00:12:57 explain plan for CALL when you added this NO_INLINE hint. With that, we come to the end
of unit one.
00:13:09 We learned about inline features and NO_INLINE hint. Inlining optimization combines
multiple short queries
00:13:18 associated with table variables into a single query. It can take more advantage of SQL
optimizer,
00:13:27 and lead to less materialization of the result of table variables.
00:13:36 NO_INLINE hint is a way of controlling inlining optimization. It can also control procedure
flattening.
00:13:45 Those are the key takeaways of this unit. In the next unit, I will present a case study of
NO_INLINE hint.
00:13:56 Thank you for your attention. See you.
6 / 21
Week 3 Unit 2
00:00:05 Hello, my name is Jin Yeon Lee. Today, I'm going to explain a case study
00:00:10 of using the INLINE feature. Let us show we have a SQLScript performance issue.
00:00:16 A slow running procedure is observed in the system. It takes about 18 seconds.
00:00:23 That is much longer than expected. The problematic statement is CALL statement
00:00:32 of procedure CL_ABC_READ_DEF_BASIC=>GET_MRP_ELEMENTS3, with the integer
value 717 as an input parameter.
00:00:45 Due to the business application logic, this input parameter value is given
00:00:51 as a constant value, but it keeps changing. In the example, this time it is 717,
00:01:00 or the next time it can be 718, or any other value. When executing the same CALL
statement
00:01:09 with the same input parameter value, it runs quickly, not taking 18 seconds.
00:01:16 But when changing the parameter value, I can see slow response time again.
00:01:23 In this situation, which supportability feature do you think would help to identify
00:01:29 where the most time is spent? Yes, since we know the problematic statement,
00:01:37 let's collect the Plan Visualizer trace first. So the collected Plan Visualizer trace looks like
the following:
00:01:45 The OVERVIEW page shows compilation time is 218 milliseconds and execution time is
about 18 seconds,
00:01:57 under EXECUTION SUMMARY box. DOMINANT PLAN OPERATORS box shows
00:02:03 what is the most expensive operator in this trace. Let's click the most expensive operator
here.
00:02:16 Then you can see the box having number 1 in the blue circle
00:02:20 in the PLAN GRAPH page. And you can also see
00:02:25 its inclusive time is 17.9 seconds. This operator definitely takes a long time,
00:02:35 because the inclusive time of the next operator is just 3.5 seconds. Now let's check the
STATEMENT_STATISTICS page.
00:02:47 The execution time of the CALL statement is about 17,000 milliseconds,
00:02:55 but it's compilation time is 218 milliseconds. When you check which inner SQL statement
takes long,
00:03:04 the second inner SQL statement from the top looks suspicious.
00:03:10 Because each execution time is 423 milliseconds and its compilation time is about 14
seconds.
00:03:22 But here, you might wonder why compilation time of this CALL statement is just 218
milliseconds
00:03:31 though the compilation time of the second inner SQL statement is 14 seconds.
00:03:39 The execution time of the CALL statement is 17,983 milliseconds.
00:03:48 Though the sum of execution time of all inner SQL statements is about only 600
milliseconds.
00:03:58 On the other hand, the compilation time of the CALL statement is 218 milliseconds,
00:04:07 but the sum of the compilation time of inner SQL statement is 17,207 milliseconds.
00:04:16 Where does such a big difference between execution time of CALL statement and the sum
of execution time of all inner statements come from?
00:04:30 In week one, we learned SQLScript engine compiles
00:04:34 SQLScript procedure code. Then it generates its execution plan.
7 / 21
00:04:40 During SQLScript engine executing its execution plan, it passes inner SQL statement string
to SQL engine.
00:04:51 Then SQL engine processes that SQL statement string. So compilation of inner SQL
statements happens
00:05:01 in SQLScript execution step, not SQLScript compilation step.
00:05:09 That is 17,207 milliseconds of the sum of compilation time
00:05:17 of inner SQL statements belong to execution time of CALL statements.
00:05:27 Then we can now see the compilation of the second inner SQL statement is most dominant.
00:05:35 And it is located in line number 295. Timeline view also shows the same information that we
found.
00:05:46 The compilation time of the second inner SQL statement is long. In the STATEMENT
STATISTICS page, you can see the open link
00:05:58 in the Deep Dive field in this inner SQL query. When clicking it, you can get a Plan
Visualizer trace
00:06:09 of this inner SQL statement in a separate window. In that separate Plan Visualizer trace
window
00:06:18 of this inner SQL statement, you can find compilation summary information.
00:06:26 This information is a new feature of SQL analyzer tool, which SAP HANA Studio PlanViz
doesn't have.
00:06:35 Here the rewriting time is 10 seconds. It means this SQL plan is too complex.
00:06:43 So it needs some time to consider many rule-based optimizations.
00:06:48 Let's continue checking more information in STATEMENT STATISTICS page. When inlining
optimization is applied,
00:07:00 a string of inner SQL statement in a SQLScript code is reshaped.
00:07:09 SQLScript engine leaves information about which table variables are used
00:07:14 to combine queries as a comment. Click Show in New View,
00:07:23 after selecting the problematic line. Then you can see comments value
00:07:29 of the long compiled inner query. You can also find the same comment information
00:07:36 in the PROPERTIES tab of the most dominant operator BOX in the PLAN GRAPH page.
00:07:43 According to the comment of the problematic query, it is the output of the inlining
optimization,
00:07:50 by merging 17 inner SQL statements. This inlined query is very complex.
00:07:59 So reducing its complexity looks like a key to solve this performance issue.
00:08:06 Let's add NO_INLINE HINT at the end of these 17 SQL statements
00:08:12 where the comment points. Then, execute this procedure
00:08:18 with a different parameter value to check how fast it is now.
00:08:24 Plan Visualizer trace for the modified procedure shows CALL statement of this procedure
having NO_INLINE HINT
00:08:34 takes now about 2.6 seconds. That is a big improvement
00:08:40 from the previous response time of about 18 seconds. Let's check the STATEMENT
STATISTICS page
00:08:51 of the newly collected Plan Visualizer trace. As you expected,
00:08:58 the long running compiled inner SQL statement is replaced with 17 statement by adding
NO_INLINE hint now.
00:09:10 Previously, the single inlined query took 423 milliseconds for its execution.
00:09:19 And about 14 seconds for its compilation. In the new trace, the sum of execution time
00:09:29 of the new 17 statement is 3,646 milliseconds and the sum of their compilation time is
16,424 milliseconds.
8 / 21
00:09:49 Their total execution and compilation time is actually bigger than ones of
00:09:54 the previous problematic inline inner SQL statement. Then how can this CALL procedure
run faster than before?
00:10:07 To find out the reason why the modified SQLScript procedure is faster,
00:10:12 let's check the TIMELINE view. We see these 17 statements are compiled in parallel,
00:10:20 and they are executed independently. Due to this parallel compilation and execution,
00:10:28 more CPU resources are used than before. But the entire response time
00:10:34 of this CALL statement becomes very short. This is an acceptable trade off,
00:10:42 since the system has enough CPU power. The PLAN GRAPH page also shows 17
statements
00:10:50 are compiled and executed in parallel. In summary, this case shows how we approach a
long
00:11:00 compilation problem of SQLScript procedure. As a solution, we use NO_INLINE hint at the
end of each statement
00:11:11 to block inlining optimization. There is no perfect answer for when NO_INLINE hint must be
used.
00:11:20 Also there are other ways to solve this issue. When you use this NO_INLINE hint in your
production system,
00:11:29 please check how this hint affects your procedure as well as resource and workload of the
system first.
00:11:39 This is the end of unit two. Let me repeat key takeaways.
00:11:47 As I explained, the advantage of inlining feature, it is helpful to run your procedure fast in
most cases.
00:11:58 So if you want to use NO_INLINE hint, please understand the output
00:12:04 of SQLScript optimization first. Then use this hint properly,
00:12:10 only when the output of SQLScript optimization causes an issue. In the next unit
00:12:20 I will explain BIND_AS_PARAMETER and BIND_AS_VALUE SQLScript functions. Thank
you for your attention.
00:12:29 See you.
9 / 21
Week 3 Unit 3
00:00:05 Hello, and welcome to unit three of week three. I am Jin Yeon Lee.
00:00:11 Last time, we learned the inline feature and NO_INLINE hint, which can control statement
inlining and procedure flattening.
00:00:20 Today, we are going to study features that can control constant propagation.
00:00:28 This constant propagation means evaluating SQL expression during compile time,
00:00:35 and if a result can be known, then it is used as a constant value for execution time.
00:00:42 In HANA 2, we can control parameterization behavior of scalar parameters explicitly
00:00:50 by using BIND_AS_PARAMETER and BIND_AS_VALUE SQLScript functions.
00:00:56 In a nutshell, BIND_AS_PARAMETER treats a scalar variable as a query parameter.
00:01:04 On the other hand, BIND_AS_VALUE treats a scalar variable as a constant value.
00:01:12 Here is a procedure get_results having four input parameters and one output parameter.
00:01:20 Out of the four input parameters, please keep your eyes on the input parameters
00:01:27 im_category in the red box, and im_typecode in the blue box. They are used in the first SQL
statement.
00:01:37 And we will observe how they will be changing. Let's collect Explain Plan for CALL of this
procedure
00:01:44 with constant values "Notebooks" and "PR" for im_category and im_typecode.
00:01:53 From the results of Explain Plan, you can see CAST (Notebooks AS NVARCHAR(40))
00:02:00 and CAST (PR AS NVARCHAR(2)) are used instead of im_category and im_typecode.
00:02:09 This means when you use constant values for input parameters, by constant propagation
rule, SQLScript optimizer
00:02:19 changes these input parameters to given constant values. This time, let's use question
marks for input parameters.
00:02:31 When you use a question mark, it means values for input parameters will be given later
00:02:37 after preparing our CALL statement. We call it a parameterized query.
00:02:46 Explain Plan for CALL does not execute our CALL statement. We don't need to bind values
to parameters this time.
00:02:54 The result shows a different SQL statement from the previous result.
00:03:02 Previously im_category and im_typecode were replaced with constant values.
00:03:08 But this time, they were represented as query parameters like __typed_NString_($1, 40).
00:03:19 Here __typed_NString means data type of the parameter, and $1 means parameter
position,
00:03:29 and 40 is the length of the data type. Though it is unknown which value will be given
00:03:37 for im_category at this moment. It comes from the first parameter and it's 40 length
NVARCHAR value.
00:03:50 When a query parameter is given for the input parameter of a procedure, the SQL statement
becomes a parameterized query.
00:04:00 As you can see, this inner SQL statement is changing depending on what is given for input
parameter.
00:04:10 When you use constant value, the constant propagation rule optimizes our query as a literal
one.
00:04:18 If you use query parameters, then these parameters are used in the query
00:04:24 so it becomes a parameterized query. The advantage of using a parameterized query
00:04:31 is that the query plan can be reused, even when parameter values are changing.
10 / 21
00:04:39 A potential disadvantage is that the query plan can be suboptimal for some parameter
values.
00:04:46 Because the query plan is generated with the first given parameter value.
00:04:53 For a literal query when different values are given, the SQL statement is always changing.
00:05:00 So, it needs to compile a new optimal query plan. So when parameter values are changing
frequently,
00:05:10 compilation costs can be bigger than in the case of a parameterized query.
00:05:16 And if a similar query plan having different values are generated a lot in a short time,
00:05:23 SQL plan cache can flood. Which I said, BIND_AS_PARAMETER and BIND_AS_VALUE
00:05:29 SQLScript functions can control parameterization behavior of scalar parameters explicitly.
00:05:38 Let's check how they work. In this example,
00:05:42 BIND_AS_PARAMETER is applied to im_category and BIND_AS_VALUE is applied to
im_typecode.
00:05:51 Let's collect Explain Plan for CALL with constant value 'Notebooks' and 'PR' as parameter
values.
00:06:00 Though constant value 'Notebooks' is given for input parameter im_category. Im_category is
replaced with
00:06:08 query parameter __typed_NString_($1, 40). SQLScript function BIND_AS_PARAMETER
always
00:06:20 uses a query parameter to represent a scalar variable. So constant propagation rule
00:06:28 could not apply to input parameter im_category with constant value 'Notebooks'.
00:06:35 On the other hand, im_typecode is replaced with constant value 'PR'.
00:06:42 Constant propagation rule applies to im_typecode as well. Let me explain
BIND_AS_VALUE further in the next slide.
00:06:54 This time let's check Explain Plan for CALL of this procedure with parameters.
00:07:03 Can you guess how im_category and im_typecode will be changed?
00:07:08 There is no difference in BIND_AS_PARAMETER im_category because a query parameter
is already given.
00:07:17 But BIND_AS_VALUE, im_typecode is replaced with CAST function with some long name.
00:07:24 Since BIND_AS_VALUE always uses a value to represent a scalar variable during
compiling SQL script,
00:07:36 BIND_AS_VALUE im_typecode should become a constant value. But at this moment,
Explain Plan for CALL
00:07:47 does not execute this procedure yet. That is, a value for im_typecode is not yet given.
00:07:56 So SQLScript engine leaves it as a scalar variable name of im_typecode.
00:08:05 Once this call procedure is actually executed, this scalar variable name will be replaced with
00:08:12 a real value again. We will check this part in the next slide again.
00:08:19 Let's check the result of SQLScript Plan Profiler for this case, and check if constant value
'PR'
00:08:27 is used for BIND_AS_VALUE im_typecode. Since Plan Profiler executes a CALL statement,
00:08:36 you have to enter values for parameters. Then the scalar variable name of
00:08:43 im_typecode is replaced with 'PR' now. So you can see CAST (PR AS NVARCHAR(2)).
00:08:53 in the actual executed SQL statement. So far we studied BIND_AS_PARAMETER
00:08:59 and BIND_AS_VALUE SQLScript functions. They can control the parameterization behavior
11 / 21
00:09:13 as a query parameter, so affected SQL statements become parameterized queries.
00:09:23 BIND_AS_VALUE treats scalar variables as a constant value,
00:09:28 so affected SQL statements become literal queries. In the next unit, we will check the
00:09:38 case study of using BIND_AS_VALUE. Looking forward to meeting you there.
00:09:45 Thank you, and goodbye.
12 / 21
Week 3 Unit 4
00:00:05 Hello, welcome to unit four of week three. I'm Jin Yeon Lee.
00:00:10 Let me present a case study of using BIND_AS_VALUE
00:00:15 that shows you one of our SQLScript procedures runs very slow,
00:00:20 very long in the production system. We couldn't see any performance issues
00:00:26 during developing and testing this SQLScript procedure
00:00:30 by executing statements one by one. But now, the application canceled
00:00:38 this very long-running procedure by its timeout logic. To find out the root cause,
00:00:45 we have to narrow down this issue. First of all, we have to find
00:00:50 which procedure is problematic. As a first step, let's collect the SQL Trace
00:00:57 in the production system, when the issue happens. Don't forget to set parameter internal as
true
00:01:04 in the SQL Trace section. Then let's check the collected SQL Trace.
00:01:13 In this scenario, if SQL Trace is collected successfully
00:01:18 while producing the issue, then we can see a trace message
00:01:23 or cancelation of the problematic query. We can find it by using keywords,
00:01:30 "cancelled by request". When scrolling up a little bit,
00:01:34 we can see a CALL statement having an error message. If the statement-execution-id
00:01:41 of that CALL statement is the same as the one in the error message,
00:01:47 then that CALL statement is the problematic one. Then let's find which SQL statement
00:01:54 in that procedure was canceled. When scrolling on SQL Trace
00:02:01 we can find the error query having the same statement-execution-id.
00:02:07 Of course, at this moment we cannot say this canceled SQL query
00:02:12 is our root cause of the timeout issue, because another SQL Trace might take too long
00:02:20 and this query could be canceled as a victim. Since we found the problematic CALL
statement,
00:02:27 then as our next step we have to find out which inner SQL statement takes too long.
00:02:36 Of course, we can also find information of elapsed time of each SQL statement in SQL
Trace.
00:02:44 But this time let's find that information in M_ACTIVE_PROCEDURES monitoring view.
00:02:51 Let's exclude the problematic procedure first. The procedure is not executed
00:03:00 by the application server, but us manually. It will not be canceled by timeout logic.
00:03:10 Instead, we have to check M_ACTIVE_PROCEDURES monitoring view after an amount of
timeout time.
00:03:20 Then the monitoring view will show us information of executed SQL statements
00:03:24 under this CALL procedure. M_ACTIVE_PROCEDURES monitoring view
00:03:30 shows this query takes too long. And this is the canceled query shown in the previous SQL
Trace.
00:03:43 The problematic query is Insert into the select statement on the calc view,
00:03:48 and it is a parameterized query. Parameter values are shown in the SQL Trace file.
00:03:59 Let’s assume we collect a Plan Visualizer Trace for this query,
00:04:05 with those parameter values. Then we find this calc view
00:04:11 has a multi-store table linked with dynamic tiering,
00:04:17 as a base table, and filters are now pushed down
13 / 21
00:04:22 to a remote source in dynamic tiering. Though they are given as parameters.
00:04:29 Since we know each parameter's values are used from an SQL Trace,
00:04:34 let's execute the same query as a literal one. It is executed quickly
00:04:41 and its Plan Visualizer Trace shows filters, given as constant values,
00:04:48 works well for the remote source this time. So it looks like converting
00:04:54 this parameterized query to a literal one can be a solution.
00:05:00 Let's try to use BIND_AS_VALUE. The comment determines which line we should modify.
00:05:10 The problematic SQL statement is located in line 95
00:05:15 of the definition of the procedure. Then we can see three scalar variables are shown
00:05:22 where parameters are located. Apply BIND_AS_VALUE functions
00:05:33 to these scalar variables. Then when you launch this procedure
00:05:39 in the production system, it works very well. Now the performance problem is solved
successfully.
00:05:47 Here are the key takeaways. SQL Trace shows information
00:05:52 about the executed inner SQL statements during execution of the SQLScript procedure.
00:06:01 Don't forget to configure parameter internal as true in the SQL Trace section.
00:06:11 M_ACTIVE_PROCEDURE shows information about all executed inner SQL statements
00:06:16 of an actively running procedure on the fly. Comment in the running SQL statement shows
the position
00:06:26 of its definition code. You can change or parameterize the query
00:06:33 to a literal one or vice versa by using BIND_AS_VALUE
00:06:41 or BIND_AS_PARAMETER SQLScript function. With that,
00:06:48 we've come to the end of this unit. In the next unit,
00:06:56 Minjeong will tell you about using SQL hints in SQLScript.
00:07:02 Thank you for your attention. See you.
14 / 21
Week 3 Unit 5
00:00:05 Hello, and welcome to unit five of week three. I’m Minjeong Lee and I am going to explain
how to use SQL hint in SQLScript.
00:00:15 SQL hint is an instruction for the SAP HANA database and it influences the way an SQL
request is processed.
00:00:25 HANA SQL optimizer determines the access path of a query, but you can override the
optimizer
00:00:32 by specifying hints in the query to enforce a certain access path. Hints are typically used to
optimize performance or memory consumption of a query
00:00:44 without any effect on the query result set. If you find that the query is running slow due to a
sub-optimal plan,
00:00:54 then you can apply SQL hints on that SQL statement to improve its performance. Since the
performance of inner SQL statements
00:01:04 affects the entire performance of SQLScript procedure a lot, using SQL hint for inner SQL
statements
00:01:14 can be a powerful solution to improve your SQLScript procedure performance. So, I am
going to explain how to use SQL hint for SQLScript procedure
00:01:27 during units five and six as the last topic of this course. Like ways to apply SQL hint on SQL
query, using hints for SQLScript is very similar.
00:01:41 You can add an SQL hint at the end of an inner SQL statement of an SQLScript procedure
code directly.
00:01:51 Or you can use statement hint feature or pinning hint feature. Regarding statement hint
feature,
00:02:00 this can be done by executing statement ALTER SYSTEM ADD STATEMENT HINT and
also by using the Statement Hints app of SAP HANA cockpit.
00:02:13 The pinning hint feature can be done by using the statement ALTER SYSTEM PIN SQL
PLAN CACHE ENTRY.
00:02:22 This ALTER statement links SQL hint to a certain execution plan stored in SQL plan cache.
00:02:31 Let’s see one by one. Here is the procedure HINT_TEST, which we are going to apply SQL
hint to.
00:02:44 This procedure has three inner SQL statements and they have dependency with table
variables.
00:02:52 The result of the first statement is stored in table variable RESULT1 and is consumed in the
second statement.
00:03:02 Table variable RESULT2 is used for storing a result of the second statement and it is
consumed in the last SQL statement.
00:03:13 As you learned, these three statements can be merged into a single statement by
SQLScript optimization rule inlining.
00:03:23 You can check the combined statement string via Explain Plan for CALL. Then, in this
procedure,
00:03:35 let’s add USE_HEX_PLAN SQL hint at the end of the third statement. Can you imagine how
this hint will be applied?
00:03:45 To check the answer, let’s collect Explain Plan for CALL. When clicking operator string
column of Explain Plan for CALL result,
00:04:00 you can see where the SQL hint is added. As you can see, in this SQLScript code,
00:04:08 USE_HEX_PLAN hint is added at the end of the third statement. But, SQLScript optimizer
combines the three inner SQL statements into a single one first,
00:04:20 then added SQL hint USE_HEX_PLAN at the end of the combined statement. That means
the added hint works for the combined statement,
15 / 21
00:04:31 not for the third SQL statement. So, it’s important to keep SQLScript optimization rules in
mind
00:04:42 when you are going to add SQL hints somewhere in your SQLScript procedure. Once
adding SQL hints in SQLScript procedure,
00:04:54 you should check how they will be applied by using Explain Plan for CALL, before executing
it. If you miss these two points,
00:05:05 you could be surprised because of unexpected impact of SQL hints. Then, let’s see another
example.
00:05:17 Unlike the previous example, USE_HEX_PLAN hint is added at the end of the second
statement.
00:05:24 Can you guess what will happen? Explain Plan for CALL shows two SQL statements
00:05:33 as the result of SQLScript optimization. The first and second statements are merged into a
single one,
00:05:43 then SQL hint USE_HEX_PLAN is added here. Then, can you expect what the last
statement looks like?
00:05:56 Yes, the last statement comes from the third statement in the code where RESULT2 is
consumed.
00:06:05 Here, what we should know is, adding SQL hint has also the same effect as NO_INLINE
hint
00:06:12 as well as the SQL hint functionality. So, the result of SQLScript optimization is the same
00:06:20 as when NO_INLINE hint is added at the end of the second statement in the code. Simply
speaking, adding SQL hint in SQLScript code
00:06:31 means that SQL hint is added at this position, not for a certain SQL statement only. Then,
inlining optimization stops
00:06:44 between the SQL statement where SQL hint is added and the next statement. I believe you
understand how SQLScript optimizer handles SQL hints.
00:07:00 Then, let me give you another quiz. Going back to the procedure HINT_TEST not having
any SQL hint,
00:07:09 what will happen if USE_HEX_PLAN hint is added at the end of a CALL statement? Where
do you expect this USE_HEX_PLAN hint will is applied?
00:07:26 When checking the Explain Plan for the CALL, you can see this SQL hint is at the CALL
statement itself.
00:07:36 But when checking the operator string of the inner SQL statements, you cannot see this
SQL hint.
00:07:44 This means SQL the hint at CALL statement is not propagated to inner SQL statements.
Another example is SQL hint IGNORE_PLAN_CACHE.
00:08:00 SQL hint IGNORE_PLAN_CACHE is frequently used when you want to ignore the existing
plan cache entry
00:08:08 and enforce the query to compile and execute. In case you need to clean all cached query
plans
00:08:18 including execution plan of CALL statement, you might use this IGNORE_PLAN_CACHE
hint at the end of the CALL statement.
00:08:28 But then, this IGNORE_PLAN_CACHE affects only the CALL statement. I mean that the
Execution Plan of CALL statement is compiled again,
00:08:40 but the cached SQL query plans of inner SQL statements are still used. With this
HINT_TEST procedure,
00:08:50 if you execute “CALL HINT_TEST WITH HINT (IGNORE_PLAN_CACHE)”, then, SQLScript
engine doesn’t look up its execution plan in SQL plan cache,
00:09:04 SQLScript engine compiles the execution plan again for a CALL statement. This means that
this hint works for a CALL statement itself.
00:09:16 But when SQLScript engine passes the inner SQL statement to SQL engine, this
IGNORE_PLAN_CACHE hint isn’t applied for that inner statement.
16 / 21
00:09:30 So, SQL engine will look up SQL plan cache for its query plan first, then reuse it without
query compilation.
00:09:42 But, since HANA 2.0 SPS07 and HANA Cloud QRC 4/2022, a new feature for hint
propagation has been introduced.
00:09:53 If you want to propagate SQL hint from CALL statement to inner SQL statements, you can
use the CASCADE feature,
00:10:03 which allows propagation of a hint into internal SQL statements within a procedure. Here’s
the procedure HINT_TEST again.
00:10:14 This time, it has NO_INLINE hint at the end of the second statement. You know the first two
statements will be combined
00:10:25 and the last statement will be executed separately. For this procedure, what will happen
00:10:34 if you execute this procedure with USE_HEX_PLAN hint with CASCADE option? This
USE_HEX_PLAN hint is propagated
00:10:47 to all statements after SQLScript optimization. As the output of SQLScript optimization,
00:10:57 two statements are generated, as you expected. One of them already has a hint
NO_INLINE.
00:11:05 And USE_HEX_PLAN hint is added additionally for this one. USE_HEX_PLAN is also
added to another statement.
00:11:17 In summary, the hint is applied to both the CALL statement itself and any inner SQL
statements.
00:11:26 When there are nested CALL statements, CASCADE hints are also propagated into the
nested procedures.
00:11:34 This CASCADE is only supported in CALL statements and cannot propagate hints into user-
defined functions.
00:11:47 If CASCADE is used for non-CALL statements, a FEATURE NOT SUPPORTED message
appears.
00:11:58 This is the last page of unit five. Today, we learned about how to use SQL hints in
SQLScript.
00:12:08 As you learned, to apply SQL hints, you can simply modify SQLScript code. But, don’t forget
to check how SQL hints are applied
00:12:21 by checking Explain Plan for CALL before executing it. Because adding hints affects
SQLScript optimization,
00:12:30 for example, inlining optimization stops at the statement with an SQL hint. Also, you learned
that SQL hint at the end of the call statement
00:12:44 is not propagated into inner statements by default. And only the CASCADE feature allows it.
00:12:53 In the next unit, I will continue explaining how to use SQL hint in SQLScript without
modifying SQLScript code.
00:13:03 Thank you for your attention. See you.
17 / 21
Week 3 Unit 6
00:00:05 Hello, and welcome to unit six, the final unit of this week and this course.
00:00:11 I’m Minjeong Lee and today’s topic is pinning SQL hints for SQLScript. As I explained, there
are two way to use SQL hints in SQLScript.
00:00:26 In the previous unit, we learned about using SQL hints by modifying SQLScript code
directly.
00:00:34 In this unit, I would like to talk about how to use SQL hints when you cannot modify
SQLScript code directly.
00:00:44 Without modifying SQL statements, you can add SQL hints by using
00:00:49 ALTER SYSTEM ADD STATEMENT HINT statement or ALTER SYSTEM PIN SQL PLAN
CACHE ENTRY statement.
00:01:01 Statement ALTER SYSTEM ADD STATEMENT HINT is called as "add statement hint"
feature.
00:01:09 This is a feature to add an SQL hint for a certain statement string or its hash value.
00:01:18 Statement ALTER SYSTEM PIN SQL PLAN CACHE ENTRY is called as "pinning
statement" feature.
00:01:28 This feature is originally for pinning a cached query plan in plan cache pool, but it also has
an option to add SQL hints on it.
00:01:40 In this case, Plan ID or its query plan in SQL plan cache is required. Here’s the procedure
HINT_TEST.
00:01:54 As you see, there is no SQL hint in SQLScript code. Let’s check the result of SQLScript
Plan Profiler this time.
00:02:07 It shows a single statement is executed because the three inner SQL statements are in-
lined.
00:02:16 If you’re using HANA Cloud QRC 4/2022, you can find STATEMENT_HASH value of the
executed SQL statements.
00:02:29 With this STATEMENT_HASH value, let’s add an SQL hint on this query.
00:02:35 To add SQL hint USE_HEX_PLAN, you can execute this statement:
00:02:41 ALTER SYSTEM ADD STATEMENT HINT (USE_HEX_PLAN) FOR STATEMENT HASH.
This means the SQL statement having this STATEMENT_HASH value
00:02:52 will be compiled with a given SQL hint when it's executed next time.
00:02:58 So, to do that, already-cached plan for this statement will be evicted.
00:03:10 In case your SQLScript Plan Profiler result doesn’t show STATEMENT_HASH value, then
you need to find it from M_SQL_PLAN_CACHE monitoring view.
00:03:23 Find hash values in M_SQL_PLAN_CACHE by using a proper STATEMENT_STRING
value
00:03:30 as a filter condition. After adding SQL hint,
00:03:39 SQLScript Plan Profiler or Explain plan for CALL result doesn’t show the added hint
00:03:47 since this hint is applied in SQL engine layer, not SQLScript engine.
00:03:55 Then, let’s check sqltrace to confirm if the hint is applied well. Turn on sqltrace with
parameter internal as true
00:04:07 and parameter query_plan_trace as on, then execute CALL HINT_TEST procedure.
00:04:14 Then you can find SQL hint USE_HEX_PLAN is attached well to the query in the collected
sqltrace result.
00:04:29 Here’s an example of pinning SQL hints. Pinning hints feature is a little bit different from
ADD STATEMENT HINT feature
00:04:38 as I explained before. When adding a hint by using statement ALTER SYSTEM ADD
STATEMENT HINT
18 / 21
00:04:47 with a certain statement string or hash value, this command clears the existing cached
query plan of that statement,
00:04:58 then once the same SQL string is requested, SQL engine compiles it with a given SQL hint.
00:05:07 So, if you already know the SQL statement to add the SQL hint, it doesn’t need that query
must be executed first
00:05:16 before executing our ALTER SYSTEM ADD STATEMENT HINT STATEMENT command.
Just before executing it,
00:05:26 you can add the SQL hint by using ALTER SYSTEM ADD STATEMENT HINT. But the
pinning hint works differently.
00:05:35 Pinning hints is using Plan ID of SQL PLAN CACHE ENTRY. This means this SQL
statement must be executed first,
00:05:46 so its query plan must be cached in SQL plan cache. Once executing procedure
HINT_TEST,
00:05:55 you can find the PLAN_ID of query plan of inner SQL statements in
M_SQL_PLAN_CACHE.
00:06:03 Then, you can execute ALTER SYSTEM PIN SQL PLAN CACHE ENTRY with that
PLAN_ID.
00:06:12 In this example, USE_HEX_PLAN hint is added by using the pinning hints feature. You can
use this pinning statement feature to add the SQL hint,
00:06:23 but the original pinning function is pinning a query plan in a plan cache not to be evicted,
00:06:31 until the plan becomes unpinned or the system restarts. Here’s again the procedure
HINT_TEST.
00:06:39 This time, the first statement has NO_INLINE hint. When you check the executed SQL
statement via sqltrace or SQLScript Plan Profiler,
00:06:52 you can see hexadecimal numbers, which is not in the original SQLScript code.
00:06:58 In unit two of week two, we learned it comes from a naming rule
00:07:03 for local temporary table for table variable or table parameter. Since table parameter is also
a table variable,
00:07:12 from now on, I will skip mentioning the table parameter. In this example, two SQL
statements are actually executed in procedure HINT_TEST.
00:07:30 The last one has a hexadecimal number and it is the real name of table variable RESULT1.
00:07:37 As you can see, its alias is RESULT1 This hexadecimal number is a global unique identifier,
GUID.
00:07:50 And it is used for a name of a local temporary table for table variables. The important thing
you must be aware of is
00:08:03 the real name of table variable RESULT1 is generated when the procedure is compiled. So,
if you compile CALL HINT_TEST statement again,
00:08:15 then the real name of table variable RESULT1 will be changed. Here’s an example of a
different name by changing the GUID of table variable RESULT1.
00:08:28 I get it by executing CALL HINT_TEST with hint IGNORE_PLAN_CACHE. You might
wonder why this is important and mentioned here.
00:08:44 If the real name of a table variable is changed with a new GUID value due to recompilation,
then the query strings with that table variable are also changed,
00:08:56 and the corresponding STATEMENT_HASH values too. So, it’s useless the SQL hints
which were added before
00:09:07 by using "add hint statement" feature or "pinning hint" feature
00:09:13 because their SQL statements have changed, so will not be executed anymore.
00:09:21 Instead, new SQL statements with a new actual name of table variable will be executed for
the CALL statement.
00:09:31 So, it is important to keep the real name of the table variable as long as possible
19 / 21
00:09:37 when you are using the "add hint statement" feature or the "pinning hint" feature.
00:09:43 To do that, pinning both a CALL statement and inner SQL statements with hints can be a
solution,
00:09:51 since the real name of a table variable is generated during compilation of a CALL statement.
00:10:02 Here’s an example of pinning both a CALL statement and its inner SQL statement
00:10:08 with SQL hint USE_HEX_PLAN. Check PLAN_ID in M_SQL_PLAN_CACHE after running
this procedure HINT_TEST once.
00:10:20 Then, you can find two Plan ID values, Plan ID of CALL HINT_TEST statement,
00:10:27 and Plan ID of target inner SQL statement,
00:10:36 Then you execute ALTER SYSTEM PIN statement for these two Plan ID values.
00:10:47 For the inner one, with the SQL hint USE_HEX_PLAN clause is added because you want to
add this hint for this inner statement.
00:10:59 For CALL statement, you don’t need any hint clauses because it is just for pinning plan in
SQL plan cache.
00:11:13 Here are the key takeaways of this unit. As I mentioned in the beginning,
00:11:18 adding or pinning statement hints can be a good solution when you cannot modify
SQLScript code directly to inject SQL hints.
00:11:29 When adding or pinning SQL hints, you must apply them on actual executed SQL
statements.
00:11:38 You can use STATEMENT_HASH value or PLAN ID depending on which feature is used.
00:11:45 Lastly, I explained the real names of table parameters or table variables have a GUID,
00:11:53 and this name is changed whenever SQLScript procedure is compiled. So, pinning the
CALL statement itself is very important
00:12:04 to keep SQL hints for inner statements referring to the actual name of a table variable.
00:12:13 And with that, we came to the end of this course unit and also to the end of our course.
00:12:22 During the course, we learned how SQLScript engine works with SQL engine and SQL plan
cache.
00:12:31 Also we learned SQLScript optimization rules. Then, we studied supportability features of
SQLScript,
00:12:42 which can help you to drill down your SQLScript code by yourself. Lastly, we reviewed
useful NO_INLINE hint and bind_as_parameter, bind_as_value functions,
00:12:56 and their usages. We hope you enjoyed this content
00:13:02 and will use tips immediately for your SQLScript codes. Before closing this course,
00:13:10 I would like to thank you, the learners, for your attention and my course expert colleagues
for their contribution to this course.
00:13:20 Good luck for the weekly assignment and the final exam. Goodbye.
20 / 21
© 2023 SAP SE or an SAP affiliate company. All rights reserved.
See Legal Notice on www.sap.com/legal-notice for use terms,
disclaimers, disclosures, or restrictions related to SAP Materials
for general audiences.