Scenarios of combining tables using JOIN with additional AND and OR conditions
1. Overview
The <ext:join>
operation enables joining tables using different conditions. It is used when creating search conditions inside the <ext:createSearchCondition>
tag to get the necessary data in roll-up tables.
- There are three main join types:
-
-
INNER JOIN: An intersection of data from two tables. For example,
<ext:join type="inner">
. -
LEFT JOIN: Extracts data from the first table (left) and joins data from the second table (right) where possible. For example,
<ext:join type="left">
. -
RIGHT JOIN: The opposite of LEFT JOIN. For example,
<ext:join type="right">
.
-
You can use the <ext:join>
operation with additional AND
and OR
operators, which you can define within the <ext:condition>
tag as the value of the logicOperator
attribute.
2. Usage examples
Let’s consider several examples of using JOIN
in search conditions with additional AND
and OR
operators in the context of the role model and the KATOTTG territorial units codifier.
2.1. Prerequisites
Create two tables to join and use in search conditions.
-
<createTable tableName="katottg">
creates a table with KATOTTG codes.Example of the "katottg" table
<changeSet id="table katottg" author="registry_owner"> <createTable tableName="katottg" ext:historyFlag="true" remarks="katottg"> <column name="katottg_id" type="UUID" defaultValueComputed="uuid_generate_v4()" remarks="katottg ID"> <constraints nullable="false" primaryKey="true" primaryKeyName="pk_katottg_id"/> </column> <column name="code" type="TEXT" remarks="Code"> <constraints nullable="true"/> </column> <column name="name" type="TEXT" remarks="Name"> <constraints nullable="true"/> </column> <column name="category" type="TEXT" remarks="Category"> <constraints nullable="true"/> </column> <column name="level" type="INT"> <constraints nullable="false"/> </column> <column name="katottg_parent_id" type="UUID"> <constraints nullable="true" foreignKeyName="fk_katottg_parent" referencedTableName="katottg" referencedColumnNames="katottg_id"/> </column> </createTable> <addUniqueConstraint tableName="katottg" columnNames="code"/> </changeSet>
-
<createTable tableName="katottg_category"
creates a table with KATOTTG code categories.Example of the "katottg_category" table
<changeSet id="table katottg_category" author="registry_owner"> <createTable tableName="katottg_category" ext:historyFlag="true" remarks="katottg_category"> <column name="katottg_category_id" type="UUID" defaultValueComputed="uuid_generate_v4()" remarks="ID"> <constraints nullable="false" primaryKey="true" primaryKeyName="pk_katottg_category_id"/> </column> <column name="code" type="TEXT" remarks="Code"> <constraints nullable="false"/> </column> <column name="name" type="TEXT" remarks="Name"> <constraints nullable="true"/> </column> </createTable> <addUniqueConstraint tableName="katottg_category" columnNames="code"/> </changeSet>
2.2. Scenarios
Let’s create a search condition called get_regions_or_city_regions
. This search condition creates an endpoint in the data factory, which can return a list of regions, cities with a special status, their KATOTTG codes, and KATOTTG code categories using the following conditions:
-
Joining tables using
JOIN
with anAND
condition:<ext:condition logicOperator="and" columnName="k.category" operator="eq" value="'K'"/>
-
Joining tables using
JOIN
with anOR
condition:<ext:condition logicOperator="or" columnName="k.category" operator="eq" value="cat.code"> <ext:condition logicOperator="and" columnName="k.category" operator="ne" value="'K'"/> <ext:condition logicOperator="and" columnName="k.level" operator="eq" value="'1'"/> </ext:condition>
The result depends on the condition you specify in the request. A data sample is formed from several records joined in a roll-up table.
2.2.1. Using INNER JOIN without additional conditions
<ext:createSearchCondition name="get_regions_or_city_regions">
<ext:table name="katottg" alias="k">
<ext:column name="katottg_id" />
<ext:column name="name" alias="name_region" searchType="startsWith" />
<ext:column name="category" />
</ext:table>
<ext:table name="katottg_category" alias="cat">
<ext:column name="name" alias="name_category" />
<ext:column name="code" />
</ext:table>
<ext:join type="inner">
<ext:left alias="k">
<ext:column name="category" />
</ext:left>
<ext:right alias="cat">
<ext:column name="code" />
</ext:right>
</ext:join>
</ext:createSearchCondition>
- At the output, Liquibase generates the following SQL query:
-
Example 2. SQL query. Using INNER JOIN without additional conditions
CREATE OR REPLACE VIEW registry.get_regions_or_city_regions_v AS SELECT k.katottg_id, k.name AS name_region, cat.name AS name_category, cat.code FROM katottg k JOIN katottg_category cat ON k.category = cat.code
2.2.2. Using INNER JOIN with an AND condition
<ext:createSearchCondition name="get_regions_or_city_regions">
<ext:table name="katottg" alias="k">
<ext:column name="katottg_id" />
<ext:column name="name" alias="name_region" searchType="startsWith" />
<ext:column name="category" />
</ext:table>
<ext:table name="katottg_category" alias="cat">
<ext:column name="name" alias="name_category" />
<ext:column name="code" />
</ext:table>
<ext:join type="inner">
<ext:left alias="k">
<ext:column name="category" />
</ext:left>
<ext:right alias="cat">
<ext:column name="code" />
</ext:right>
<ext:condition logicOperator="and" columnName="k.category" operator="eq" value="'K'"/>
</ext:join>
</ext:createSearchCondition>
- At the output, Liquibase generates the following SQL query:
-
Example 4. SQL query. Using INNER JOIN with an AND condition
CREATE OR REPLACE VIEW registry.get_regions_or_city_regions_v AS SELECT k.katottg_id, k.name AS name_region, cat.name AS name_category, cat.code FROM katottg k JOIN katottg_category cat ON k.category = cat.code AND k.category = 'K' :: text;
- The result of the request is as follows:
-
When joining the tables using
JOIN
with anAND
condition, you will get a sample of 2 records:Figure 1. Request result when using JOIN + AND condition
2.2.3. Using INNER JOIN with an OR condition
<ext:createSearchCondition name="get_regions_or_city_regions">
<ext:table name="katottg" alias="k">
<ext:column name="katottg_id" />
<ext:column name="name" alias="name_region" searchType="startsWith" />
<ext:column name="category" />
</ext:table>
<ext:table name="katottg_category" alias="cat">
<ext:column name="name" alias="name_category" />
<ext:column name="code" />
</ext:table>
<ext:join type="inner">
<ext:left alias="k">
<ext:column name="category" />
</ext:left>
<ext:right alias="cat">
<ext:column name="code" />
</ext:right>
<ext:condition logicOperator="or" columnName="k.category" operator="eq" value="cat.code">
<ext:condition logicOperator="and" columnName="k.category" operator="ne" value="'K'"/>
<ext:condition logicOperator="and" columnName="k.level" operator="eq" value="'1'"/>
</ext:condition>
</ext:join>
</ext:createSearchCondition>
- At the output, Liquibase generates the following SQL query:
-
Example 6. SQL query. Using INNER JOIN with an OR condition
CREATE OR REPLACE VIEW registry.get_regions_or_city_regions_v AS SELECT k.katottg_id, k.name AS name_region, cat.name AS name_category, cat.code FROM katottg k JOIN katottg_category cat ON k.category = cat.code OR k.category = cat.code AND k.category <> 'K'::text AND k.level = 1;
- The result of the request is as follows:
-
When joining the tables using
JOIN
with anOR
condition, you will get a sample of 25 records:Figure 2. Request result when using JOIN + OR condition
2.2.4. Using INNER JOIN with AND and OR conditions
<ext:createSearchCondition name="get_regions_or_city_regions">
<ext:table name="katottg" alias="k">
<ext:column name="katottg_id" />
<ext:column name="name" alias="name_region" searchType="startsWith" />
<ext:column name="category" />
</ext:table>
<ext:table name="katottg_category" alias="cat">
<ext:column name="name" alias="name_category" />
<ext:column name="code" />
</ext:table>
<ext:join type="inner">
<ext:left alias="k">
<ext:column name="category" />
</ext:left>
<ext:right alias="cat">
<ext:column name="code" />
</ext:right>
<ext:condition logicOperator="and" columnName="k.category" operator="eq" value="'K'"/>
<ext:condition logicOperator="or" columnName="k.category" operator="eq" value="cat.code">
<ext:condition logicOperator="and" columnName="k.category" operator="ne" value="'K'"/>
<ext:condition logicOperator="and" columnName="k.level" operator="eq" value="'1'"/>
</ext:condition>
</ext:join>
</ext:createSearchCondition>
- At the output, Liquibase generates the following SQL query:
-
Example 8. SQL query. Using INNER JOIN with AND and OR conditions
CREATE OR REPLACE VIEW registry.get_regions_or_city_regions_v AS SELECT k.katottg_id, k.name AS name_region, cat.name AS name_category, cat.code FROM katottg k JOIN katottg_category cat ON k.category = cat.code AND k.category = 'K' :: text OR k.category = cat.code AND k.category <> 'K'::text AND k.level = 1;
- The result of the request is as follows:
-
When joining the tables using
JOIN
withAND
andOR
conditions, you will get a sample of 27 records:Figure 3. Request result when using JOIN + AND + OR conditions
3. Other usage examples
You can add custom conditions to the primary <ext:join>
conditions using Liquibase’s functional extension.
3.1. Using LEFT JOIN with a single additional OR condition
For example, to join two tables, cities
and katottg_dictionary
, on the condition that katottg
equals either level4
or add_level
, you would use the following conditions combined with an OR
operator:
-
<ext:left>
and<ext:right>
in<ext:join>
to define the main condition,katottg=level4
-
<ext:condition>
to define the additional condition,katottg=add_level
<changeSet author="registry owner" id="or join">
<ext:createSearchCondition name="cities_4_or_5_level">
<ext:table name="cities" alias="ci">
<ext:column name="city_id" />
<ext:column name="name" searchType="startsWith" />
<ext:column name="katottg" searchType="equal" />
</ext:table>
<ext:table name="katottg_dictionary" alias="kd">
<ext:column name="name" alias="name_dict" />
</ext:table>
<ext:join type="left">
<ext:left alias="ci">
<ext:column name="katottg" />
</ext:left>
<ext:right alias="kd">
<ext:column name="level4" />
</ext:right>
<ext:condition logicOperator="or" columnName="ci.katottg" operator="eq" value="kd.add_level" />
</ext:join>
</ext:createSearchCondition>
</changeSet>
- At the output, Liquibase generates the following SQL query:
-
Example 10. SQL query. Using LEFT JOIN with an OR condition
SELECT ci.city_id, ci.name, ci.katottg, kd.name AS name_dict FROM cities AS ci LEFT JOIN katottg_dictionary AS kd ON (ci.katottg = kd.level4) or (ci.katottg = kd.add_level);
3.2. Using LEFT JOIN with several additional AND and OR conditions
You can use <ext:condition>
to model more complex conditions of joining tables.
For example, to join the same two tables, cities
and katottg_dictionary
, on the condition that katottg
equals level4
when category
does not equal 'B'
, or katottg
equals add_level
when category
equals 'B'
, you would use the following search condition:
<changeSet author="registry owner" id="or join by category">
<ext:createSearchCondition name="cities_4_or_5_level_by_category">
<ext:table name="cities" alias="ci">
<ext:column name="city_id" />
<ext:column name="name" searchType="startsWith" />
<ext:column name="katottg" searchType="equal" />
</ext:table>
<ext:table name="katottg_dictionary" alias="kd">
<ext:column name="name" alias="name_dict" />
</ext:table>
<ext:join type="left">
<ext:left alias="ci">
<ext:column name="katottg" />
</ext:left>
<ext:right alias="kd">
<ext:column name="level4" />
</ext:right>
<ext:condition logicOperator="and" columnName="kd.category" operator="ne" value="'B'">
<ext:condition logicOperator="or" columnName="ci.katottg" operator="eq" value="kd.add_level" />
<ext:condition logicOperator="and" columnName="kd.category" operator="eq" value="'B'"/>
</ext:condition>
</ext:join>
</ext:createSearchCondition>
</changeSet>
- At the output, Liquibase generates the following SQL query:
-
Example 12. SQL query. Using LEFT JOIN with several additional AND and OR conditions
SELECT ci.city_id, ci.name, ci.katottg, kd.name AS name_dict FROM cities AS ci LEFT JOIN katottg_dictionary AS kd ON (ci.katottg = kd.level4) and (kd.category = 'B') or ((ci.katottg = kd.add_level) and (kd.category <> 'B'));
Note that the nested conditions in For this example, grouping is optional: without the parentheses, the code will work the same as with the parentheses because the Keep this in mind for other potential use cases. |