In order to tune your query, you will often have to get the execution plan for that query, and interpret plan differences as you make modifications. The EXPLAIN command allows you to inspect the query plan at various stages in the query lifecycle.
  1. The EXPLAIN output after the logical optimization phase: EXPLAIN (logical) <query>.
  2. The EXPLAIN output after the physical optimization phase: EXPLAIN (physical) <query>.
  3. The EXPLAIN output annotated with execution statistics: EXPLAIN (analyze) <query>.
You can retrieve the query plans as a JSON document. This makes it easy to work with plans programmatically. Just run EXPLAIN (<options>, FORMAT JSON) <query>.
The following code snippet illustrates the different plans on a simple query.
select
  ss_item_sk,
  sum(ss_net_profit) as total_profit
from
  store_sales
where
  ss_sold_date_sk between 2451666 and 2451696
group by
  ss_item_sk
having 
  sum(ss_net_profit) > 0
order by
  total_profit desc;
The query plans are printed as a tree structure, with the root node at the top left and the leaves at the bottom right. For binary operators (such as joins) the left child of a node is the first input side of the operator, and the right child is the second input side. If no other per-operator information is requested in the EXPLAIN command, each line represents a single operator. An operator is printed as a line that starts with [node_id] [OperatorType]. The [OperatorType] is followed by node expressions specific to the operator type. For example, in
[0] [Projection] store_sales.ss_item_sk, total_profit
the store_sales.ss_item_sk, total_profit expressions represent the expression retained after the projection. Similarly, in
[5] [Filter] (store_sales.ss_sold_date_sk >= 2451666), (store_sales.ss_sold_date_sk <= 2451696)
the two comma-seprated expressions represent the filter conditions that need to hold simultaneously in order to retain the row in the filter output. The node_id is unique within a single query plan, including subqueries. However, the node_id is not necessarily retained across the logical and physical plans for the same query. Moreover, operations that are represented as a single node in the logical plan may be transformed into multiple nodes in the physical plan. For example, the
[6] [StoredTable] Name: "store_sales"
operator in the logical plan represents the logical operation of reading the store_sales table. The corresponding physical plan starts at
[6] [TableFuncScan] store_sales.ss_sold_date_sk: ...
and represents the sequence of physical operations that the runtime will perform when reading the store_sales table. These include listing all tables, filtering out tables that don’t contain any relevant data, and reading the data from the remaining tablets. You can learn more about tablet-level pruning in the Data Pruning and Storage section. Note that despite the tree-shaped EXPLAIN output, the query plan is not necessarily a tree. For example, the plan of the following query references the target_items CTE twice.
with target_items as (
  select * from item where i_category_id = 'Music'
)
select
  *
from
  target_items l, target_items r
where
  l.i_brand_id = r.i_brand_id and l.i_wholesale_cost < r.i_wholesale_cost;
The [5] [Filter] node in the logical plan is referenced twice under the [3] [Join] node. The second reference is marked as a _Recurring Node --> [5] line.