Druid aims to be a more powerful analytical database, and implementing joins is a very common ask from the user community. Druid does support some related features today:
WHERE x IN (SELECT ...) syntax and traditional JOIN syntax. But it only supports one subquery per SQL query, and does not support negation ("NOT IN").Real JOIN support would be more powerful, enabling even more star-schema and subquery-based use cases:
The idea is to add a "join" datasource, expose it through SQL, and add machinery to brokers and data servers to allow hash-based equijoins of zero or one "table" datasource and any number of "lookup", "inline", or "query" datasources. As a side effect, these proposed changes will add the ability to query lookups directly, using a datasource of type "lookup".
I think this subset of functionality is a good start, because it adds meaningful new capabilities and helps unify existing ones. Things that are not in scope for this initial proposal include: joining two "table" datasources to each other, non-equijoins, non-hash-based joins. I think these should all be implemented at some point, as future work.
There are four main areas to this proposal,
The next few sections expand on these areas.
See also https://gist.github.com/gianm/39548daef74f0373b3c87056e3db4627 for an (incomplete) implementation oriented doc.
An example SQL query might be:
SELECT products.value AS product_name, SUM(sales.revenue)
FROM sales
LEFT JOIN lookup.products ON sales.product_id = products.key
GROUP BY products.value
This query takes advantage of the fact that unqualified tables like sales are assumed to be normal datasources. Lookups are referenced as part of the lookup schema, like lookup.products.
Multiple join queries can be specified per SQL query. We will need to guide Calciteās cost-based optimizer towards reordering them optimally. This may require more statistics than we currently possess, so adding these and improving join plans may end up being an area of future work.
The rows coming out of a join datasource would be the result of the join. Any query type could use a join datasource without being aware of the fact that joins exist.
Join datasources can be nested within each other. Unlike SQL, native query evaluation will not reorder joins. It will execute them in the order that the join tree is provided.
Probably will not allow joining on "table" datasources, except as the extreme left-hand side of a join.
In order to protect against column name ambiguity (what if the left and right side have a column of the same name?), I propose adding a "rightPrefix" parameter to the join datasource. This would be prefixed to every column name coming from the right side, and should be chosen by the caller to be something that wonāt conflict with any left-side columns. Druid SQL will choose one automatically when planning a SQL join.
The join datasource used by the earlier SQL query, above, would be:
"dataSource": {
"type": "join",
"left": "sales",
"right": {
"type": "lookup",
"lookupName": "products"
},
"rightPrefix": "foobar",
"joinType": "left",
"condition": {
"leftColumn": "product_id",
"rightColumn": "key"
"rightPrefix": "something"
}
}
The following technique should be implemented by CachingClusteredClient. The idea is to either evaluate the query locally, or else get it into a format that data servers can handle (see next section).
The following technique should be implemented by ServerManager (historical) and SinkQuerySegmentWalker (indexer) when a join datasource is encountered.
Some other alternatives considered were:
None expected.
Add lots of unit tests in the druid-sql and druid-processing modules.
Out of scope for this proposal, but would be nice in the future:
I am planning to do work towards implementing this proposal over the next few months. Feedback is very welcome.
I know everyone will be very excited about JOINs but the proposal to have lookups queryable via SQL will enable amazing stuff in the web console!
Yes that is an exciting direction, but wouldn't it be more efficient to develop a druid connector for presto?
Hi, @kstrempel . IMO, I don't think this is a problem on the same level. Presto does provide more richer SQL queries, but it does not store data itself, so there will be a process to ingest data from Druid, which will inevitably have some performance loss. And if Presto pushes most of the calculations down to Druid, then Druid still needs to have more SQL query capabilities, which is what the proposal is doing. I think this may be one of the reasons why Spark does not directly borrow the SQL features of Impala or Presto, but implement Spark SQL on its own.
Yes that is an exciting direction, but wouldn't it be more efficient to develop a druid connector for presto?
I think a Druid connector for Presto would help get Druid data into Presto and help support 100% of SQL use cases, so it's interesting. But as @asdf2014 mentioned, doing what we can in Druid directly should mean better performance.
@gianm that's an exciting announcement and looking forward to it.
Great to know this news! š can't wait to see this in releases!
@kstrempel we looked into join support through presto couple years ago.
Its possible but presto connector interface did not have API to push down predicates. So Druid would have to forward raw non aggregated data to presto and presto would do all query processing, which compromises all the performance benefits of Druid.
Great to see this initiative.
FYI āĀ I started a design document for implementation at https://gist.github.com/gianm/39548daef74f0373b3c87056e3db4627. I didn't put it into this issue directly, since I think a proposal and design doc have different goals. (The proposal is meant to describe a program for implementing the feature, motivations, considerations, etc; but the design doc is meant to describe what's already implemented.)
This is a good idea ļ¼I look forward to
@jnaous,
- I think what's missing here as well is a bit of discussion on the ingestion side of dimension tables.
I agree. I haven't thought a ton about how it should work. But I think, ideally, it'd use the new InputFormat and InputSource stuff so users don't have to learn new concepts. It would be great if it used tasks too for even more conceptual-weight-reduction.
- You mention "master datasource" under Broker Behavior. First time it's mentioned in this doc. With no prior explanation. Do you mean the base datasource?
I meant the datasource that the user provided in the query. I'll edit to make it clearer.
- Under Test Plan it would be good to see some more comprehensive sets of queries that you expect to be supported and others that won't be, ideally in order of complexity. Perhaps we can use that to stage implementation phases by scoping them to supported query types?
That is a great idea. I will plan to add that when the SQL part starts getting fleshed out more.
@gianm
This sounds great and thanks for writing https://gist.github.com/gianm/39548daef74f0373b3c87056e3db4627 . While I am reading through everything trying to understand details, I am realizing it would be useful to have a doc describing current limitations of this work (e.g. Druid table joins requiring shuffle not supported yet) and examples of SQL and Druid Native queries that showcase use cases which were not possible before but are supported now, or older way was not efficient/user-friendly and new way of writing that query makes things efficient/user-friendly .
@gianm When can we expect a release with Join Support and ANSI SQL compliance?
Very cool stuff. Curious if this is still actively being worked on. I also wonder if the Imply implementation has been looked at and considered. They may have some findings that would inform the implementation:
Most helpful comment
FYI āĀ I started a design document for implementation at https://gist.github.com/gianm/39548daef74f0373b3c87056e3db4627. I didn't put it into this issue directly, since I think a proposal and design doc have different goals. (The proposal is meant to describe a program for implementing the feature, motivations, considerations, etc; but the design doc is meant to describe what's already implemented.)