Learn to configure the <relationship>
element for Pull Reports™ Ad Hoc
report and data service software.
Catalog Configuration Java API reference:
JoinColumnsRelationshipConfiguration.Builder, JoinTableRelationshipConfiguration.Builder
The <relationship>
element specifies a SQL join relationship between two <table>
s.
within the Export Report REST API. Nest <relationship>
elements beneath the report's base
table to any depth to join additional information to the report's relational data tree.
<table>
s within <relationship>
s are referenced within the Export Report REST API's
columns
, filter
, and sort
parameters via their table resource path, the unique forward slash
(/
) separated list of table ids which identifies the table within the
join tree.
<relationship>
s SQL joins are always directional from
the parent <table>
to the child <table>
.
This directionality means that the parent <table>
is always included in the
Export Report REST API SQL join if the child <table>
is included.
Additionally, SQL joins from a table to a <relationship>
child table are always
a left outer or inner join. Right outer SQL joins are not supported.
<relationship>
s support three types of SQL joins:
<join_column>
A to-many, <join_column>
configuration is when one or more
foreign key columns on the child table reference one or more
columns on the parent table. This type of join must be cardinality="many"
.
Configure this type of relationship
with one or more <join_column>
elements as direct children of the <relationship>
element.
The <join_column>
columnName
attribute references the child foreign
key column, and the referencedColumnName
references the parent key column.
<join_column>
A to-one, <join_column>
configuration is when one or more
foreign key columns on the parent table reference one or more
columns on the child table. This type of join must be cardinality="one"
.
Configure this type of relationship
with one or more <join_column>
elements as direct children of the <relationship>
element.
The <join_column>
columnName
attribute references the parent foreign
key column, and the referencedColumnName
references the child key column.
<join_table>
A to-many, <join_table>
configuration requires an indirection table between the parent and child tables.
The indirection table must have one or more foreign key columns that reference
columns on each of the parent and child tables. Configure this type of relationship
with a <join_table>
element.
Because of the similarity in purpose between <relationship>
s and object relational mapping (ORM)
technology, the Pull Reports™ Schema and Catalog Configuration Java API relationship vocabulary follows the
Java Persistence API
(JPA) mapping vocabulary.
<relationship>
exampleThis example uses a real estate data model which contains information about home sales, the owners of homes, and the realtors used by an owner. The following diagram represents the six tables of the data model and the foreign key relationships between them.
The data model relationships may be described like this:
A home
may have 0 to many sales
but a sale
is associated with one home
.
A home
may have 0 to many owners
and an owner
may own 0 to many homes
.
A home
has exactly one address
.
An owner
may have 0 to one realtors
but a realtor
may have 0 to many owners
.
An owner
may have many home_expenses
. Each
expense is associated with one home
.
Use the following join techniques to create a <report>
with the the home
table
as the base table and the address
, sale
, owner
,
and owner_realtor
tables as nested <relationship>
s:
Use a to-many, <join_column>
join to left outer join the sale
table to home
with a <join_column>
from sale.home_id
to home.id
.
The <relationship>
is one to many from home
to sale
.
Use a to-many, <join_table>
join to left outer join the owner
table to home
with a <join_table>
element on the home_owner
indirection table.
The <relationship>
is zero to many from home
to owner
.
Use a to-one, <join_column>
join to inner join the address
table to home
with a <join_column>
from home.address_id
to address.id
.
The inner join is appropriate if the home.address_id
column may not be null.
The <relationship>
is one to one from home
to address
.
Use a to-one, <join_column>
join to left outer join the owner_realtor
table to owner
with two <join_column>
s from owner.first_name
to
owner_realtor.first_name
and owner.last_name
to
owner_realtor.last_name
respectively.
The <relationship>
is many to one from owner
to owner_realtor
.
Use a to-many, <join_column>
join to left outer join the home_expense
table to owner
and home
with two <join_column>
s from
owner_expense.owner_id
to owner.id
and owner_expense.home_id
to home.id
. The later join uses the <join_column>
referencedTablePath
attribute to indicate the referencedColumnName
is on the home
grandparent table of the relationship and not the parent table.
The following Pull Reports™ configuration represents the data model within the Pull Reports REST API. See the comments
within the XML for specific <column>
configuration guidance.
<?xml version="1.0" encoding="UTF-8"?> <catalog xmlns="http://www.pullreports.com/catalog-1.7.0" id="realestate" name="Real Estate Reports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.pullreports.com/catalog-1.7.0 https://www.pullreports.com/docs/xsd/pullreports-catalog-1.7.0.xsd"> <report id="home" name="Home Report"> <export_config defaultColumns='id'/> <table id="home" name="realestate.home" displayName="Home" primaryKeyColumns="id"> <column id="id" name="id" paramType="java.lang.Integer"/> <!-- A Home has One-to-Many Past Sales Example REST API URL: /pullreports/catalog/realestate/report/home/export?columns=/home/sale Resultant SQL from statement: from realestate.home left outer join realestate.sale on realestate.sale.home_id = realestate.home.id --> <relationship> <join_column columnName="home_id" referencedColumnName="id" /> <table id="sale" displayName="Sale" name="realestate.sale"> <column id="sale_id" name="id" paramType="java.lang.Integer"/> <column id="home_id" name="home_id" paramType="java.lang.Integer"/> <column id="amount" name="amount" paramType="java.lang.Double"/> </table> </relationship> <!-- A Home has One to One Addresses Example REST API URL: /pullreports/catalog/realestate/report/home/export?columns=/home/address Resultant SQL from statement: from realestate.home inner join realestate.address on realestate.address.id = realestate.home.address_id --> <relationship join="inner" cardinality="one"> <join_column columnName="address_id" referencedColumnName="id" /> <table id="address" displayName="Address" name="realestate.address"> <column id="aid" name="id" paramType="java.lang.Integer"/> <column id="street" name="street" displayName="Street"/> <column id="city" name="city" displayName="City" /> <column id="state" name="state" displayName="State"/> <column id="zipcode" name="zipcode" displayName="Zip Code"/> </table> </relationship> <!-- A Home has Many to Many Owners Example REST API URL: /pullreports/catalog/realestate/report/home/export?columns=/home/owner Resultant SQL from statement: from realestate.home left outer join realestate.home_owner on realestate.home_owner.home_id = realestate.home.id left outer join realestate.owner on realestate.home_owner.owner_id = realestate.owner.id --> <relationship> <join_table name="realestate.home_owner"> <join_columns> <join_column columnName="home_id" referencedColumnName="id" /> </join_columns> <inverse_join_columns> <join_column columnName="owner_id" referencedColumnName="id" /> </inverse_join_columns> </join_table> <table id="owner" displayName="Owner" name="realestate.owner" primaryKeyColumns="oid"> <column id="oid" name="id" paramType="java.lang.Integer"/> <column id="first_name" name="first_name"/> <column id="last_name" name="last_name"/> <!-- An owner has a many-to-one relationship with a realtor. This example demonstrates a compound key with two <join_column>s. Example REST API URL: /pullreports/catalog/realestate/report/home/export?columns=/home/owner/realtor Resultant SQL from statement: from realestate.home left outer join realestate.home_owner on realestate.home_owner.home_id = realestate.home.id left outer join realestate.owner on realestate.home_owner.owner_id = realestate.owner.id left outer join realestate.owner_realtor on realestate.owner.first_name = realestate.owner_realtor.owner_first_name and realestate.owner.last_name = realestate.owner_realtor.owner_last_name --> <relationship cardinality="one"> <join_column columnName="first_name" referencedColumnName="owner_first_name" /> <join_column columnName="last_name" referencedColumnName="owner_last_name" /> <table id="realtor" displayName="Realtor" name="realestate.owner_realtor"> <column id="realtor_name" name="realtor_name"/> </table> </relationship> <!-- An owner has a one-to-many relationship with a home_expense. However, each home_expense is in the context of one home. Use the <join_column> referencedTablePath attribute to indicate the columnName value is on the home table. Example REST API URL: /pullreports/catalog/realestate/report/home/export?columns=/home/owner/expense Resultant SQL from statement: from realestate.home left outer join realestate.home_owner on realestate.home_owner.home_id = realestate.home.id left outer join realestate.owner on realestate.home_owner.owner_id = realestate.owner.id left outer join realestate.owner_expense on realestate.owner.id = realestate.owner_expense.owner_id and realestate.home.id = realestate.owner_expense.home_id --> <relationship> <join_column columnName="owner_id" referencedColumnName="id" /> <join_column columnName="home_id" referencedColumnName="id" referencedTablePath="/home"/> <table id="expense" displayName="Owner Expense" name="realestate.owner_expense"> <column id="amount" name="amount" paramType="java.lang.Double"/> <column id="reason" name="reason" /> </table> </relationship> </table> </relationship> </table> </report> </catalog>
The following example is an identical Catalog Configuration Java API configuration.
package com.pullreports.examples.relationship;
import com.pullreports.model.Cardinality;
import com.pullreports.model.CatalogId;
import com.pullreports.model.ColumnId;
import com.pullreports.model.Join;
import com.pullreports.model.JoinColumn;
import com.pullreports.model.JoinTable;
import com.pullreports.model.ParamType;
import com.pullreports.model.ReportId;
import com.pullreports.model.TableId;
import com.pullreports.model.TablePath;
import com.pullreports.model.config.CatalogConfiguration;
import com.pullreports.model.config.CatalogConfigurationFactory;
import com.pullreports.model.config.ColumnConfiguration;
import com.pullreports.model.config.JoinColumnsRelationshipConfiguration;
import com.pullreports.model.config.JoinTableRelationshipConfiguration;
import com.pullreports.model.config.RelationshipConfiguration;
import com.pullreports.model.config.ReportConfiguration;
import com.pullreports.model.config.TableConfiguration;
import com.pullreports.model.exportconfig.ExportConfiguration;
import jakarta.servlet.ServletContext;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
public class RelationshipCatalogConfigurationFactory implements CatalogConfigurationFactory {
protected final ColumnId idColumn = new ColumnId("id");
@Override
public CatalogConfiguration makeCatalog(ServletContext servletContext) {
ColumnConfiguration idColumnConfiguration = new ColumnConfiguration.Builder(
idColumn,"id")
.setParamType(ParamType.INTEGER).build();
List<ColumnConfiguration> columnConfigurations = Collections.singletonList(idColumnConfiguration);
List<RelationshipConfiguration> relationshipConfigurations = Arrays.asList(
makeSaleRelationshipConfiguration()
,makeAddressRelationshipConfiguration()
,makeOwnerRelationshipConfiguration());
TableConfiguration homeTableConfiguration = new TableConfiguration.Builder(
new TableId("home"),"Home",columnConfigurations)
.setName("realestate.home")
.setPrimaryKeyColumnIds(Collections.singleton(idColumn))
.setRelationshipConfigurations(relationshipConfigurations).build();
ReportConfiguration homeReportConfiguration = new ReportConfiguration.Builder(
new ReportId("home"),"Home Report",homeTableConfiguration)
.setExportConfiguration(
makeExportConfigurationBuilder().build()).build();
List<ReportConfiguration> reportConfigurations = Collections.singletonList(homeReportConfiguration);
return new CatalogConfiguration(new CatalogId("realestate"),"Real Estate Reports",reportConfigurations);
}
/**
* Subclasses may override and add their own ExportConfiguration.Builder
* configuration.
* <h5>Example</h5>
* <code>
* @Override
* protected ExportConfiguration.Builder makeExportConfigurationBuilder() {
* return super.makeExportConfigurationBuilder().setDefaultSortTerms(...)
* }
* </code>
*/
protected ExportConfiguration.Builder makeExportConfigurationBuilder() {
return new ExportConfiguration.Builder().setDefaultColumns(
Collections.singletonList(idColumn));
}
private RelationshipConfiguration makeSaleRelationshipConfiguration() {
ColumnConfiguration saleIdColumnConfiguration = new ColumnConfiguration.Builder(
new ColumnId("sale_id"),"id")
.setParamType(ParamType.INTEGER).build();
ColumnConfiguration homeIdColumnConfiguration = new ColumnConfiguration.Builder(
new ColumnId("home_id"),"home_id")
.setParamType(ParamType.INTEGER).build();
ColumnConfiguration amountColumnConfiguration = new ColumnConfiguration.Builder(
new ColumnId("amount"),"amount")
.setParamType(ParamType.DOUBLE).build();
List<ColumnConfiguration> columnConfigurations = Arrays.asList(
saleIdColumnConfiguration
,homeIdColumnConfiguration
,amountColumnConfiguration);
TableConfiguration tableConfiguration = new TableConfiguration.Builder(
new TableId("sale"),"Sale",columnConfigurations)
.setName("realestate.sale").build();
return new JoinColumnsRelationshipConfiguration.Builder(tableConfiguration
,Collections.singletonList(new JoinColumn("home_id","id"))).build();
}
private RelationshipConfiguration makeAddressRelationshipConfiguration() {
ColumnConfiguration addressIdColumnConfiguration = new ColumnConfiguration.Builder(
new ColumnId("aid"),"id")
.setParamType(ParamType.INTEGER).build();
ColumnConfiguration cityColumnConfiguration = new ColumnConfiguration.Builder(
new ColumnId("city"),"city")
.setDisplayName("City").build();
ColumnConfiguration streetColumnConfiguration = new ColumnConfiguration.Builder(
new ColumnId("street"),"street")
.setDisplayName("Street").build();
ColumnConfiguration stateColumnConfiguration = new ColumnConfiguration.Builder(
new ColumnId("state"),"state")
.setDisplayName("State").build();
ColumnConfiguration zipcodeColumnConfiguration = new ColumnConfiguration.Builder(
new ColumnId("zipcode"),"zipcode")
.setDisplayName("Zip Code").build();
List<ColumnConfiguration> columnConfigurations = Arrays.asList(
addressIdColumnConfiguration
,cityColumnConfiguration
,streetColumnConfiguration
,stateColumnConfiguration
,zipcodeColumnConfiguration);
TableConfiguration tableConfiguration = new TableConfiguration.Builder(
new TableId("address"),"Address",columnConfigurations)
.setName("realestate.address").build();
return new JoinColumnsRelationshipConfiguration.Builder(tableConfiguration
,Collections.singletonList(new JoinColumn("address_id","id")))
.setCardinality(Cardinality.ONE).setJoin(Join.INNER).build();
}
private RelationshipConfiguration makeOwnerRelationshipConfiguration() {
ColumnConfiguration ownerIdColumnConfiguration = new ColumnConfiguration.Builder(
new ColumnId("oid"),"id")
.setParamType(ParamType.INTEGER).build();
ColumnConfiguration firstNameColumnConfiguration = new ColumnConfiguration.Builder(
new ColumnId("first_name"),"first_name").build();
ColumnConfiguration lastNameColumnConfiguration = new ColumnConfiguration.Builder(
new ColumnId("last_name"),"last_name").build();
List<ColumnConfiguration> columnConfigurations = Arrays.asList(
ownerIdColumnConfiguration
,firstNameColumnConfiguration
,lastNameColumnConfiguration);
List<RelationshipConfiguration> relationships = Arrays.asList(
makeOwnerRealtorRelationshipConfiguration()
,makeOwnerExpenseRelationshipConfiguration()
);
TableConfiguration tableConfiguration = new TableConfiguration.Builder(
new TableId("owner"),"Owner",columnConfigurations)
.setName("realestate.owner")
.setPrimaryKeyColumnIds(new HashSet<>(){{add(ownerIdColumnConfiguration.getId());}})
.setRelationshipConfigurations(relationships).build();
JoinTable joinTable = new JoinTable("realestate.home_owner"
,Collections.singletonList(new JoinColumn("home_id","id"))
,Collections.singletonList(new JoinColumn("owner_id","id")));
return new JoinTableRelationshipConfiguration.Builder(tableConfiguration,joinTable).build();
}
private RelationshipConfiguration makeOwnerRealtorRelationshipConfiguration() {
ColumnConfiguration realtorColumnConfiguration = new ColumnConfiguration.Builder(
new ColumnId("realtor_name"),"realtor_name").build();
List<ColumnConfiguration> columnConfigurations = Collections.singletonList(
realtorColumnConfiguration);
TableConfiguration tableConfiguration = new TableConfiguration.Builder(
new TableId("realtor"),"Realtor",columnConfigurations)
.setName("realestate.owner_realtor").build();
return new JoinColumnsRelationshipConfiguration.Builder(tableConfiguration
,Arrays.asList(
new JoinColumn("first_name","owner_first_name")
,new JoinColumn("last_name","owner_last_name")))
.setCardinality(Cardinality.ONE).build();
}
private RelationshipConfiguration makeOwnerExpenseRelationshipConfiguration() {
ColumnConfiguration amountColumnConfiguration = new ColumnConfiguration.Builder(
new ColumnId("amount"),"amount").setParamType(ParamType.DOUBLE).build();
ColumnConfiguration reasonColumnConfiguration = new ColumnConfiguration.Builder(
new ColumnId("reason"),"reason").build();
List<ColumnConfiguration> columnConfigurations = Arrays.asList(
amountColumnConfiguration,reasonColumnConfiguration);
TableConfiguration tableConfiguration = new TableConfiguration.Builder(
new TableId("expense"),"Owner Expense",columnConfigurations)
.setName("realestate.owner_expense").build();
return new JoinColumnsRelationshipConfiguration.Builder(tableConfiguration
,Arrays.asList(new JoinColumn("owner_id","id")
,new JoinColumn("home_id","id",new TablePath("/home"))))
.build();
}
}
<access_control_voter> ? |
(<join_column> + | <join_table> ) |
(<table> | <table_ref> ) |
<table> |
Specifies the cardinality of the join between the parent <table>
of the <relationship>
and the
child <table>
of the <relationship>
. Permitted values are one
or many
.
For to-many and to-one, <join_column>
relationships, the cardinality
attribute determines
the location of the database columns referenced by child <join_column>
columnName
and referencedColumnName
attributes.
The Pull Reports™ Report Creator reflects the cardinality
attribute
via a "stacked" display within the
Columns panel.
Specifies the join type. Permitted values are left
or inner
.
right
outer joins are not supported.