Query Construction Logic

Looking at the keys provided, question does arise – What does all this mean to me when I have to write my SQL query? Let us take a sample query and look at typical needs / asks -


Assume there is a table EMPLOYEES having the columns EMP_ID, EMP_NAME, EMP_AGE, EMP_DOJ. A typical query for showcasing the Employee Id, Name and Age will be as –follows:


SELECT 
EMP_ID, EMP_NAME, EMP_AGE
FROM
EMPLOYEES



If you require dynamic filters and sorts, the query must be changed –as follows:


SELECT 
EMP_ID, EMP_NAME, EMP_AGE
FROM
EMPLOYEES
WHERE
<COL 1> like '%filter%' –- and so on
ORDER BY
<COL1> ASC, <COL 2> DESC–- and so on


For enabling pagination, the following two inputs are necessary– the start row number and end row number. For the end UI to handle the pagination properly, the output should also include the total number of records in the result after applying filters.


There are multiple approaches to –enable pagination:


Approach 1: Fire separate queries for getting the total number of records and for the actual slice of data corresponding to the start and end row number. An example would be -

Query for Total number of records 
SELECT 
COUNT as TOTAL_COUNT
FROM
EMPLOYEES
WHERE
<COL 1> like '%filter%' –- and so on
ORDER BY
<COL1> ASC, <COL 2> DESC –- and so on 

And the actual query with Paging is done like -

Query with paging 
SELECT 
EMP_ID, EMP_NAME, EMP_AGE
FROM
EMPLOYEES
WHERE
<COL 1> like '%filter%' –- and so on
AND ROWNUM < endRowOfPage and ROWNUM > startRowOfPage
ORDER BY
<COL1> ASC, <COL 2> DESC –- and so on


While this construct will work, it gives rise to multiple inconsistencies, such as:

  • Forces application to fire two queries for every data fetch
  • The query construct is not optimized for huge data sets.
  • Usage of Oracle proprietary ROWNUM which will not work with other Databases


Approach 2: Get the row number as part of a single query

Query with paging 
SELECT * FROM ( SELECT PAGINATED_TBL.*, ROWNUM RNUM FROM (
SELECT 
EMP_ID, EMP_NAME, EMP_AGE
FROM
EMPLOYEES
WHERE
<COL 1> like '%filter%' –- and so on
ORDER BY
<COL1> ASC, <COL 2> DESC –- and so on
) PAGINATED_TBL WHERE ROWNUM < endRowOfPage ) WHERE RNUM > startRowOfPage 


While this construct will work, it gives rise to different set of problems, such as:

  • Query construct is complex
  • The query construct is not optimized for huge data sets and can create performance issues.


Approach 3: Using Analytical functions to get the Row number as well as the target result set in a single go. There are multiple analytical functions provided that work with the result set in memory rather than involve additional physical reads or computation resources that make for best result. The following sample query construct in this approach is provided:

Query with paging using analytical functions 
SELECT * FROM (
SELECT 
EMP_ID, EMP_NAME, EMP_AGE
,COUNT  OVER () AS TOTAL_ROWS
, ROW_NUMBER () OVER (
<COL1> ASC, <COL 2> DESC –- and so on
) ROWNM
FROM
EMPLOYEES
WHERE
<COL 1> like '%filter%' –- and so on
) WHERE ROWNM < endRowOfPage AND ROWNM > startRowOfPage ORDER BY ROWNM 


This approach brings about an interesting option. It enables the total count of records like any other column for usage at no additional query cost.


The third approach is used while framing queries with support for dynamic filters, sort and paging. This means that a typical query can be created as a template as –follows:


<Pagination Begin section>----------------------> Id: NEW_PAGINATION_BEGINS
<Select query with columns>---------------> As defined by you
<Pagination row count section>---------------> Id: TOTALCOUNT_FOR_PAGINATION
<Dynamic Sorts section>----------------------> Id: NEW_DEFAULT_ORDERBY_CLAUSE
<Default From and Where clause for the query> ---> as defined by you
<Dynamic Filters section>----------------------> Id: FILTER_CLAUSE
<Pagination End section>----------------------> Id: NEW_PAGINATION_ENDS 


The following code segment is a sample query configuration using these Maps–IDs:

<select id="FEATURE_1_SELECT_MODULE_1" resultMap="FEATURE_1_RESULT_MAP">
<include refid="NEW_PAGINATION_BEGINS" />
SELECT 
TRAN_REF_NO,
DEBIT_ACC_NO,
BENE_ACC_NO,
<include refid="TOTALROWS_FOR_PAGINATION"/>
<include refid="NEW_DEFAULT_ORDERBY_CLAUSE"/>
FROM (SELECT TRAN_REF_NO, DEBIT_ACC_NO, BENE_ACC_NO
FROM CT_TRAN_SUMMARY_DATA) 
WHERE 1=1
<include refid="FILTER_CLAUSE" />
<include refid="NEW_PAGINATION_ENDS" />
</select> 



This way of organization brings about the following pointers:

  • The result map configured for this Query model should ensure that it provides a mapping for the column TOTAL_COUNT. The following code segment is a sample result mapping for this column. The TOTAL_COUNT mapping is highlighted in yellow for easy identification.


<resultMap id="FEATURE_1_RESULT_MAP" class="java.util.HashMap">
<result property="TRAN_REF_NO" nullValue="" column="TRAN_REF_NO" javaType="java.lang.String" jdbcType="VARCHAR2"/>
<result property="DEBIT_ACC_NO" nullValue="" column="DEBIT_ACC_NO" javaType="java.lang.String" jdbcType="VARCHAR2"/>
<result property="BENE_ACC_NO" nullValue="" column="BENE_ACC_NO" javaType="java.lang.String" jdbcType="VARCHAR2"/>
<result property="TOTAL_COUNT" nullValue="" column="TOTAL_COUNT" javaType="java.lang.Integer" jdbcType="VARCHAR"/>
</resultMap> 


  • It is advised that the Pagination section is always added to ensure that whenever the App is configured pointing to this query model, it automatically gets activated. If the query model has nothing to do with an App, then these SQL template fragments are not necessary.


TOTAL_COUNT is the field that is expected in the first row of the result Map that is provided back to the invoker as part of the App rendering logic. This is critical for the UI components to get the actual number of records as any paging calculation is based on this count.


There is a small difference between the TOTALROWS_FOR_PAGINATION and TOTALCOUNT_FOR_PAGINATION. The difference is in the column name under which the record count is returned. TOTALCOUNT_FOR_PAGINATION returns it under the column TOTAL_COUNT while TOTALROWS_FOR_PAGINATION returns it under the column TOTAL_ROWS. Accordingly you have to adjust the result map for this column.


Now that we have these basics clear, let us look at how to use the framework for simple CRUD operations, advanced batch operations, Stored Procedure based invocations.