I'm working on a reporting system that allows the user to arbitrarily query a set of fact tables, constraining on multiple dimension tables for each fact table. I've written a query-builder class that automatically assembles all the correct joins and subqueries based on the constraint parameters, and everything works as designed.
But, I have a feeling that I'm not generating the most efficient queries. On a set of tables with a few million records, these queries take about 10 seconds to run, and I'd like to get them down in the less-than-one-second range. I have a feeling that, if I could get rid of the subqueries, the result would be much more efficient.
Rather than show you my actual schema (which is much more complicated), I'll show you an analogous example that illustrates the point without having to explain my whole application and data model.
Imagine that I have a database of concert information, with artists and venues. Users can arbitrarily tag the artists and the venues. So the schema looks like this:
concert
id
artist_id
venue_id
date
artist
id
name
venue
id
name
tag
id
name
artist_tag
artist_id
tag_id
venue_tag
venue_id
tag_id
Pretty simple.
Now let's say I want to query the database for all concerts happening within one month of today, for all artists with 'techno' and 'trombone' tags, performing at concerts with 'cheap-beer' and 'great-mosh-pits' tag.
The best query I've been able to come up with looks like this:
SELECT
concert.id AS concert_id,
concert.date AS concert_date,
artist.id AS artist_id,
artist.name AS artist_name,
venue.id AS venue_id,
venue.name AS venue_name,
FROM
concert
INNER JOIN (
artist ON artist.id = concert.artist_id
) INNER JOIN (
venue ON venue.id = concert.venue_id
)
WHERE (
artist.id IN (
SELECT artist_id
FROM artist_tag
INNER JOIN tag AS a on (
a.id = artist_tag.tag_id
AND
a.name = 'techno'
) INNER JOIN tag AS b on (
b.id = artist_tag.tag_id
AND
b.name = 'trombone'
)
)
AND
venue.id IN (
SELECT venue_id
FROM venue_tag
INNER JOIN tag AS a on (
a.id = venue_tag.tag_id
AND
a.name = 'cheap-beer'
) INNER JOIN tag AS b on (
b.id = venue_tag.tag_id
AND
b.name = 'great-mosh-pits'
)
)
AND
concert.date BETWEEN NOW() AND (NOW() + INTERVAL 1 MONTH)
)
The query works, but I really don't like having those multiple subqueries. If I could accomplish the same logic purely using JOIN logic, I have a feeling the performance would drastically improve.
In a perfect world, I'd be using a real OLAP server. But my customers will be deploying to MySQL or MSSQL or Postgres, and I can't guarantee that a compatible OLAP engine will be available. So I'm stuck using an ordinary RDBMS with a star schema.
Don't get too hung up on the details of this example (my real application has nothing to do with music, but it has multiple fact tables with an analogous relationship to the ones I've shown here). In this model, the 'artist_tag' and 'venue_tag' tables function as fact tables, and everything else is a dimension.
It's important to note, in this example, that the queries are much simpler to write if I only allow the user to constrain against a single artist_tag or venue_tag value. It only gets really tricky when I allow the queries to include AND logic, requiring multiple distinct tags.
So, my question is: what are the best techniques that you know of for writing efficient queries against multiple fact tables?