Interpret the execution plan and, based on the results, optimize object queries.
This example of a simple join between two MBOs or tables illustrates the relationship between the SUPQuery/Query, generated SQL statement, and the execution plan. The column names may be different from the attribute names, depending on the version of the client SDK used. The table with the suffix “_os” stores the original state of the MBO and eliminates duplicate rows if the object is in pending state.
Query query = new Query(); query.select("p.PRODUCTID, p.TYPE, t.DESCRIPTION"); query.from("PRODUCTS", "p"); query.join("PRODUCTSDESCRIPTION","t","t.PRODUCTID","p.PRODUCTID");
select p."a",p."b",t."c" from "slowquery_1_0_productsdescription" t,"slowquery_1_0_products" p where p."a"= t."a" and (t."pending" = 1 or not exists (select 1 from "slowquery_1_0_productsdescription_os" t_os where t."d" = t_os."d")) and (p."pending" = 1 or not exists (select 1 from "slowquery_1_0_products_os" p_os where p."p" = p_os."p"))
slowquery_1_0_products_findByPrimaryKeyIndexThe filter and subquery operations work together to limit the number of rows returned by the scan to consist only of the pending (if there is one) or download state. Read-only MBOs do not require filtering since there is only one state – download.
join [ filter[scan(slowquery_1_0_productsdescription)], filter[index-scan(slowquery_1_0_products, slowquery_1_0_products_findByPrimaryKeyIndex)] ] sub-query[index-scan(slowquery_1_0_products_os,primary)] sub-query[index-scan(slowquery_1_0_productsdescription_os,primary)]
Small table scans can be quite efficient; however, avoid large table scans as much as possible due to the linear cost. For attributes in a WHERE clause, avoid a table scan by adding appropriate indexes. UltraLite uses direct page scans when it is more efficient to access information directly from the database page, however, such results are returned without order. The object query must use ORDER BY to guarantee ordering of result sets.
Join operations are common in a relational database. However, too many joins within a query, especially queries involving tables with many records, are very expensive to perform. Because of the limited resources associated with the device database, dividing a query into two or more subqueries provides better performance. Since the database engine is single threaded, allowing another application thread to process the result of a subquery while the application thread in the database engine works on another query in parallel may provide better performance in a multicore environment. This might involve additional work for the application developer, but may also allow complex queries to execute with reasonable performance.
Also, for a join operation, the query optimizer does not use table size information or gather statistics to decide which table to scan. The table order in the SQL statement (derived from SUPQuery/Query) determines the table to scan. Therefore if you are joining a very large table with a small one, ordering is important to limit the amount of work the engine must do.
Create the index by creating an appropriate object query. The new object query results in a small amount of generated code that does not impact application performance. For example, if you create an index for the attribute “zip”, you need only create an object query using “zip” in the WHERE clause.
While a new object query does not impact performance, an additional index may slow down synchronization and update/insert operations. Do not create indexes to prepare for future upgrades that may or may not materialize. Even for a read-only MBO, additional indexes may slow synchronization. If MBO data changes infrequently and is limited in size, then the only concern is initial synchronization time. Faster query performance and synchronization speed is a trade-off. Indexes are an important tuning tool but must be used with caution: they are important for large tables due to the cost of table scans; however, redundant indexes slow down insert/update/delete operations, but provide no benefit.
For complex queries involving large reference tables that change infrequently, it may be beneficial to construct a materialized view to have results return within user experience limits. You can construct such views using a local MBO, and maintain them in the application code. The change log facility notifies the application of changes to the data set after synchronization completes. Based on the changed/new MBO instance, the application code can update the materialized view. As a result, the query can leverage the materialized view without a join operation and spread materialized view maintenance out over time. SAP does not recommend this approach for volatile data, as maintenance processing is complex and time consuming. Additionally, the most recent view is delayed until processing completes so the user may see only partially updated materialized views.