You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@calcite.apache.org by Julian Hyde <ju...@hydromatic.net> on 2014/08/11 20:19:52 UTC
Semi-join orientation
I am adding rules to make semi-joins more practical [1] [2] [3]. Which side of a semi-join is conventionally the “build” side? Right now it’s the right. For example, the query
select * from dept where deptno in (select deptno from emp where gender =‘F’)
becomes
SemiJoin(Scan(dept), Filter(Scan(emp), gender = ‘F’), 0.deptno = 1.deptno)
Filter(Scan(emp), gender = ‘F’) is the “build” side, and you can imagine implementing it by populating a hash-table with distinct keys before starting to read from the “dept” table.
If we’re generating left-deep trees, we put the smaller input on the left-hand side. So, should the “build” side of a semi-join go on the left also?
Julian
[1] https://issues.apache.org/jira/browse/OPTIQ-367
[2] https://issues.apache.org/jira/browse/OPTIQ-368
[3] https://issues.apache.org/jira/browse/OPTIQ-369
Re: Semi-join orientation
Posted by Vladimir Sitnikov <si...@gmail.com>.
I've logged an issue for alternative implementation of semi-joins as
https://issues.apache.org/jira/browse/OPTIQ-379.
I believe the amount of required memory (see in the issue) should be good
costing for the current enumerable implementation (no spill to disk
materializations, etc).
Vladimir
Re: Semi-join orientation
Posted by Julian Hyde <ju...@gmail.com>.
That makes sense.
In https://issues.apache.org/jira/browse/OPTIQ-369, I have implemented EnumerableSemiJoinRel. An example (from misc.oq):
select * from "hr"."emps"
where exists (
select 1 from "hr"."depts" where "depts"."deptno" = "emps"."deptno");
+-------+--------+-----------+---------+------------+
| empid | deptno | name | salary | commission |
+-------+--------+-----------+---------+------------+
| 100 | 10 | Bill | 10000.0 | 1000 |
| 110 | 10 | Theodore | 11500.0 | 250 |
| 150 | 10 | Sebastian | 7000.0 | |
+-------+--------+-----------+---------+------------+
(3 rows)
!ok
EnumerableSemiJoinRel(condition=[=($1, $5)], joinType=[inner])
EnumerableTableAccessRel(table=[[hr, emps]])
EnumerableCalcRel(expr#0..3=[{inputs}], expr#4=[true], $f01=[$t0], $f0=[$t4])
EnumerableJoinRel(condition=[=($0, $1)], joinType=[inner])
EnumerableAggregateRel(group=[{0}])
EnumerableCalcRel(expr#0..4=[{inputs}], $f0=[$t1])
EnumerableTableAccessRel(table=[[hr, emps]])
EnumerableTableAccessRel(table=[[hr, depts]])
The generated code uses the new Enumerables.semiJoin function, which builds a HashSet of the keys the “inner” side (depts.deptno) then probes using a stream from the “outer” side (emps).
The other use of “depts” in the query plan is as a “value generator”. As https://issues.apache.org/jira/browse/OPTIQ-375 notes, the value generator is often not necessary.
Vladimir,
Can you please log a JIRA case for your proposed “reverse” implementation of semi-join where you build the non-semi-join side? It would help if you add a cost function that would help us decide which implementation to use.
Julian
On Aug 11, 2014, at 1:48 PM, Vladimir Sitnikov <si...@gmail.com> wrote:
>> Filter(Scan(emp), gender = ‘F’) is the “build” side, and you can imagine
> implementing it by populating a hash-table with distinct keys before
> starting to read from the “dept” table.
>
> There is more than one way to skin a hash semi-join.
>
> Imagine the following physical plan:
> 1) Scan(dept) first and build Map<deptno, List<dept.row>>
> 2) Perform Filter(Scan(emp), gender = ‘F’) and check if the join key is in
> memory. If found, remove the entry from the map and return rows. If not
> found, just ignore the row.
>
> This can be more efficient in case dept is smaller than Filter(Scan(emp),
> gender = ‘F’).
>
> I suggest the following:
> 1) Let the right side of the semi join be the one that is semi-joined (as
> in your example above)
> 2) "build" side (in your terms above) is more of a physical plan, thus it
> is irrelevant at the logical level (see below). For example, for enumerable
> convention we could have EnumerableHashJoinSemi (builds table over left
> relation) and EnumerableHashJoinSemiRight (builds table over right
> relation).
>
> Vladimir
Re: Semi-join orientation
Posted by Vladimir Sitnikov <si...@gmail.com>.
>Filter(Scan(emp), gender = ‘F’) is the “build” side, and you can imagine
implementing it by populating a hash-table with distinct keys before
starting to read from the “dept” table.
There is more than one way to skin a hash semi-join.
Imagine the following physical plan:
1) Scan(dept) first and build Map<deptno, List<dept.row>>
2) Perform Filter(Scan(emp), gender = ‘F’) and check if the join key is in
memory. If found, remove the entry from the map and return rows. If not
found, just ignore the row.
This can be more efficient in case dept is smaller than Filter(Scan(emp),
gender = ‘F’).
I suggest the following:
1) Let the right side of the semi join be the one that is semi-joined (as
in your example above)
2) "build" side (in your terms above) is more of a physical plan, thus it
is irrelevant at the logical level (see below). For example, for enumerable
convention we could have EnumerableHashJoinSemi (builds table over left
relation) and EnumerableHashJoinSemiRight (builds table over right
relation).
Vladimir