# Join Lens
Join lenses can be used to combine multiple relations into one. Additionally, a filter expression can be provided as a join condition.
For this example, we will look at the tables museums
and workers
from the DuckDB database. For each individual of the :Worker
class, we want to set its property :workAddress
to the address of the museum they work at.
The tables have the following schemas:
museums
column | type |
---|---|
museum_id | integer |
name | string |
address | string |
yearly_income | integer |
yearly_spendings | integer |
ratings | array of floats |
workers
column | type |
---|---|
worker_id | integer |
full_name | string |
role | string |
museum_id | integer |
titles | array of strings |
access_level | integer |
Notably, the table workers
has the column museum_id
which references the primary key of the table museums
. In SQL, we can run a JOIN
query over these two tables to combine all rows of worker
with their corresponding museums. In Ontop, we can instead create a join lens, that can be referenced by the mapping.
The join lens has the following structure:
{
"name": [String],
"join": {
"relations": [[String]],
"columnPrefixes": [String]
},
"columns": {
"added": [{
"name": String,
"expression": String
}],
"hidden": [String]
},
"filterExpression": String,
"type": "JoinLens"
}
The join
field takes an object consisting of a list of relation references and a list of column prefixes. For each relation, its corresponding column prefix will be prepended to the names of all of its columns.
The fields columns
and filterExpression
work exactly the way they worked for basic lenses, with the only reference being that now, column names have to be combined with the individual relation's prefix when referencing them in expressions.
In this example, the relations we use are museums
and workers
, and we choose the prefixes m_
and w_
for them respectively. Since we want to perform a JOIN
operation, rather than a cross-product, we also have to supply the filter expression m_museum_id = w_museum_id
. After including these values, the lenses.json
file should look like this:
{
"relations": [
{
"name": ["lenses", "museums_workers"],
"join": {
"relations": [
["museums"],
["workers"]
],
"columnPrefixes": [
"m_",
"w_"
]
},
"columns": ...,
"filterExpression": "m_museum_id = w_museum_id",
"type": "JoinLens"
}
]
}
The only remaining field is columns
. As mentioned earlier, this field is handled analogously to the basic lens columns
field, allowing the user to add and remove specific columns. For this example, we want to set the property :workAddress
for each :Worker
individual. Because of that, we only require the column worker_id
from workers
and address
from museums
- all other columns can be hidden, and no column has to be added:
{
"relations": [
{
"name": ["lenses", "museum_workers"],
"join": {
"relations": [
["museums"],
["workers"]
],
"columnPrefixes": [
"m_",
"w_"
]
},
"columns": {
"added": [],
"hidden": [
"m_museum_id",
"m_name",
"m_yearly_income",
"m_yearly_spendings",
"m_ratings",
"w_full_name",
"w_role",
"w_museum_id",
"w_titles",
"w_access_level"
]
},
"filterExpression": "m_museum_id = w_museum_id",
"type": "JoinLens"
}
]
}
# Mapping
Now that the lens file is created, we can construct our mapping. For this, we once again start from the mapping template file provided with the tutorial files.
We can add a mapping entry to it, referencing the newly created lens.
mappingId MAPID-museum-worker-address
target data:worker/{w_worker_id} :workAddress {m_address} .
source SELECT w_worker_id, m_address FROM lenses.museum_workers;
Now, we can test the lens and mapping, by copying the corresponding files to the Ontop endpoint directory and running the following query:
PREFIX : <http://example.org/museum_kg/>
SELECT ?name ?address WHERE {
?worker a :Worker .
?worker :name ?name .
?worker :workAddress ?address .
}
If everything was prepared correctly, this should result in a list of employee names, together with the address of the museum they work at.
← Basic Lens Union Lens →