Reputation: 2046
I use modified preorder tree to store GEO locations in my application in a single table LOC_TABLE. An example of subtree for e.g. Greece looks something like this:
+-------+---------------+-----+-----+------+
| ID | NAME | LFT | RGT | TYPE |
+-------+---------------+-----+-----+------+
| 10 | Greece | 100 | 200 | 3 |
| 20 | Crete Isl. | 120 | 140 | 4 |
| 25 | Crete-Vamos | 121 | 122 | 4 |
| 26 | Crete-Rethymno| 123 | 124 | 4 |
....
+-------+---------------+-----+-----+------+
TYPE
column is used to store type of location (3 - country, 4 - city). As you can see Crete is stored as city, which contains other cities (e.g., Vamos
and Rethymno
) as its children.
I'm required to perform two types of queries:
1) Fetch all locations of specific type under specific parent.
2) Fetch all top locations of specific type under specific parent: for provided example of locations only Crete Isl.
should be returned when querying for cities inside Greece, because Crete Isl.
doesn't have parents of type city, while cities Vamos
and Rethymno
have parent of type city - Crete Isl.
What are the fastest queries to perform in each case?
For the first case I consider either using two queries (first, get LFT and RGT of Greece, second to fetch all locations of type = 4, which have appropriate LFT and RGT) or using some kind of join to get all locations in one step. Which is the best approach?
For the second case I don't have any suitable idea currently. I tried simple sub-select:
select loc.* from LOC_TABLE loc
where 4 not in
(select TYPE from LOC_TABLE p
where p.lft < loc.lft AND p.rgt > loc.rgt)
AND loc.LFT > 100 AND loc.RGT < 200;
But it's too long.
I don't mind adding more columns and populating them with some values which will help to speed up these two types of queries. But I need to fetch the data quickly.
Thanks.
Upvotes: 0
Views: 103
Reputation: 21533
I would try something like these:-
SELECT b.*
FROM LOC_TABLE a
INNER JOIN LOC_TABLE b
ON a.LFT < b.LFT AND a.RGT > b.RGT
WHERE a.ID = 10
AND b.TYPE = 4
Simple JOIN.
For your second question, maybe something like this. Find the parent, then from that find all the records underneath of the right type. The find any parents of the same type of this child. If one is found then ignore.
SELECT b.*
FROM LOC_TABLE a
INNER JOIN LOC_TABLE b ON a.LFT < b.LFT AND a.RGT > b.RGT
LEFT JOIN LOC_TABLE c ON c.LFT > b.LFT AND c.RGT < b.RGT AND b.TYPE = c.TYPE
WHERE a.ID = 10
AND b.TYPE = 4
AND c.ID IS NULL
Upvotes: 1
Reputation: 31685
For all records that are cities and descendants of a specific record with an ID
given by :id
, use
SELECT descendant.ID, descendant.NAME, descendant.TYPE
FROM LOC_TABLE location
INNER JOIN LOC_TABLE descendant
ON descendant.LFT > location.LFT AND descendant.RGT < location.RGT
WHERE location.ID = :id AND TYPE = 4
For all records that are cities and child of a record with an ID
givien by :id
, use
SELECT child.ID, child.NAME, child.TYPE
FROM LOC_TABLE location
INNER JOIN LOC_TABLE child
ON child.LFT > location.LFT AND child.RGT < location.RGT
LEFT OUTER JOIN LOC_TABLE intermediate
ON intermediate.LFT < child.LFT
AND intermediate.RGT > child.RGT
AND intermediate.LFT > location.LFT
AND intermediate.RGT < location.RGT
WHERE location.ID = :id
AND child.TYPE = 4
AND intermediate.ID IS NULL
The LEFT OUTER JOIN
along with the condition intermediate.ID IS NULL
eliminates those child
records, for which there is a decendant of location
that is ancestor of child
.
Upvotes: 1