I have this locations
table:
id | type | slug | parent_location_id |
---|---|---|---|
1 | Country | Germany | nil |
2 | FederalState | Hessen | 1 |
3 | City | Frankfurt | 2 |
4 | Street | Example | 3 |
I have a tags
table which has a belongs_to
logic to the locations
table:
id | name | location_id |
---|---|---|
1 | blue | 1 |
2 | yellow | 2 |
3 | red | 2 |
4 | orange | 3 |
5 | white | 4 |
6 | black | 4 |
All tags of a location are displayed on webpages with this URL schema:
- http://localhost:4000/Germany
- http://localhost:4000/Germany/Hessen
- http://localhost:4000/Germany/Hessen/Frankfurt
- http://localhost:4000/Germany/Hessen/Frankfurt/Example
- http://localhost:4000/:country_slug/:federal_state_slug/:city_slug/:street_slug
To fetch all the tags
for the last URL which would include all the tags
for the Country, the FederalState, the City and the Street I run down the tree and query for the :country_slug
first, then for the :federal_state_slug
, then for the :city_slug
and lastly for the :street_slug
. Always in combination with the parent_location_id
to make sure that it makes sense within the location tree. After that I combine all the location ids
and run a select on tags
. Needless to say that this takes for ever since I run 5 SQL queries in the worst case (for a street) and 2 in the best case (for a country).
I can't help but think that there must be a better data structure (I am happy to restructure the database) or some SQL magic so get this done with one SQL query. Is there?
CodePudding user response:
You can use a recursive cte
, in which you join your tags
table onto each recursive query. The cte
builds a JSON
array of tag id
s, which you can query later on:
with recursive cte(id, url, tag_path) as (
select l.id, 'http://localhost:4000/'||l.slug, ('['||t.id||']')::jsonb
from locations l join tags t on t.location_id = l.id where l.parent_location_id is null
union all
select l.id, c.url||'/'||l.slug, c.tag_path||(t.id::text::jsonb)
from locations l join cte c on c.id = l.parent_location_id
join tags t on t.location_id = l.id
)
select * from cte;