6.1.7. The PLAN clause

The PLAN clause enables the user to submit a data retrieval plan, thus overriding the plan that the optimizer would have generated automatically.

Syntax

  1. PLAN <plan-expr>
  2. <plan-expr> ::=
  3. (<plan-item> [, <plan-item> ...])
  4. | <sorted-item>
  5. | <joined-item>
  6. | <merged-item>
  7. <sorted-item> ::= SORT (<plan-item>)
  8. <joined-item> ::=
  9. JOIN (<plan-item>, <plan-item> [, <plan-item> ...])
  10. <merged-item> ::=
  11. [SORT] MERGE (<sorted-item>, <sorted-item> [, <sorted-item> ...])
  12. <plan-item> ::= <basic-item> | <plan-expr>
  13. <basic-item> ::=
  14. <relation> { NATURAL
  15. | INDEX (<indexlist>)
  16. | ORDER index [INDEX (<indexlist>)] }
  17. <relation> ::= table | view [table]
  18. <indexlist> ::= index [, index ...]
Table 67. Arguments for the PLAN Clause
ArgumentDescription

table

Table name or its alias

view

View name

index

Index name

Every time a user submits a query to the Firebird engine, the optimizer computes a data retrieval strategy. Most Firebird clients can make this retrieval plan visible to the user. In Firebird’s own isql utility, this is done with the command SET PLAN ON. If you are studying query plans rather than running queries, SET PLANONLY ON will show the plan without executing the query.

In most situations, you can trust that Firebird will select the optimal query plan for you. However, if you have complicated queries that seem to be underperforming, it may very well be worth your while to examine the plan and see if you can improve on it.

Simple plans

The simplest plans consist of just a relation name followed by a retrieval method. For example, for an unsorted single-table select without a WHERE clause:

  1. select * from students
  2. plan (students natural);

If there’s a WHERE or a HAVING clause, you can specify the index to be used for finding matches:

  1. select * from students
  2. where class = '3C'
  3. plan (students index (ix_stud_class));

The INDEX directive is also used for join conditions (to be discussed a little later). It can contain a list of indexes, separated by commas.

ORDER specifies the index for sorting the set if an ORDER BY or GROUP BY clause is present:

  1. select * from students
  2. plan (students order pk_students)
  3. order by id;

ORDER and INDEX can be combined:

  1. select * from students
  2. where class >= '3'
  3. plan (students order pk_students index (ix_stud_class))
  4. order by id;

It is perfectly OK if ORDER and INDEX specify the same index:

  1. select * from students
  2. where class >= '3'
  3. plan (students order ix_stud_class index (ix_stud_class))
  4. order by class;

For sorting sets when there’s no usable index available (or if you want to suppress its use), leave out ORDER and prepend the plan expression with SORT:

  1. select * from students
  2. plan sort (students natural)
  3. order by name;

Or when an index is used for the search:

  1. select * from students
  2. where class >= '3'
  3. plan sort (students index (ix_stud_class))
  4. order by name;

Notice that SORT, unlike ORDER, is outside the parentheses. This reflects the fact that the data rows are retrieved unordered and sorted afterwards by the engine.

When selecting from a view, specify the view and the table involved. For instance, if you have a view FRESHMEN that selects just the first-year students:

  1. select * from freshmen
  2. plan (freshmen students natural);

Or, for instance:

  1. select * from freshmen
  2. where id > 10
  3. plan sort (freshmen students index (pk_students))
  4. order by name desc;

If a table or view has been aliased, it is the alias, not the original name, that must be used in the PLAN clause.

Composite plans

When a join is made, you can specify the index which is to be used for matching. You must also use the JOIN directive on the two streams in the plan:

  1. select s.id, s.name, s.class, c.mentor
  2. from students s
  3. join classes c on c.name = s.class
  4. plan join (s natural, c index (pk_classes));

The same join, sorted on an indexed column:

  1. select s.id, s.name, s.class, c.mentor
  2. from students s
  3. join classes c on c.name = s.class
  4. plan join (s order pk_students, c index (pk_classes))
  5. order by s.id;

And on a non-indexed column:

  1. select s.id, s.name, s.class, c.mentor
  2. from students s
  3. join classes c on c.name = s.class
  4. plan sort (join (s natural, c index (pk_classes)))
  5. order by s.name;

With a search added:

  1. select s.id, s.name, s.class, c.mentor
  2. from students s
  3. join classes c on c.name = s.class
  4. where s.class <= '2'
  5. plan sort (join (s index (fk_student_class), c index (pk_classes)))
  6. order by s.name;

As a left outer join:

  1. select s.id, s.name, s.class, c.mentor
  2. from classes c
  3. left join students s on c.name = s.class
  4. where s.class <= '2'
  5. plan sort (join (c natural, s index (fk_student_class)))
  6. order by s.name;

If there is no index available to match the join criteria (or if you don’t want to use it), the plan must first sort both streams on their join column(s) and then merge them. This is achieved with the SORT directive (which we’ve already met) and MERGE instead of JOIN:

  1. select * from students s
  2. join classes c on c.cookie = s.cookie
  3. plan merge (sort (c natural), sort (s natural));

Adding an ORDER BY clause means the result of the merge must also be sorted:

  1. select * from students s
  2. join classes c on c.cookie = s.cookie
  3. plan sort (merge (sort (c natural), sort (s natural)))
  4. order by c.name, s.id;

Finally, we add a search condition on two indexable colums of table STUDENTS:

  1. select * from students s
  2. join classes c on c.cookie = s.cookie
  3. where s.id < 10 and s.class <= '2'
  4. plan sort (merge (sort (c natural),
  5. sort (s index (pk_students, fk_student_class))))
  6. order by c.name, s.id;

As follows from the formal syntax definition, JOINs and MERGEs in the plan may combine more than two streams. Also, every plan expression may be used as a plan item in an encompassing plan. This means that plans of certain complicated queries may have various nesting levels.

Finally, instead of MERGE you may also write SORT MERGE. As this makes absolutely no difference and may create confusion with “real” SORT directives (the ones that do make a difference), it’s probably best to stick to plain MERGE.

Occasionally, the optimizer will accept a plan and then not follow it, even though it does not reject it as invalid. One such example was

  1. MERGE (unsorted stream, unsorted stream)

It is advisable to treat such as plan as “deprecated”.