Pipe query syntax is an extension to GoogleSQL that's simpler and more concise than standard query syntax. Pipe syntax supports the same operations as standard syntax, and improves some areas of SQL query functionality and usability.
For more background and details on pipe syntax design, see the research paper SQL Has Problems. We Can Fix Them: Pipe Syntax In SQL.
Pipe syntax
Pipe syntax has the following key characteristics:
- Each pipe operator in pipe syntax consists of the pipe symbol,
|>, an operator name, and any arguments:
|> operator_name argument_list - Pipe operators can be added to the end of any valid query.
- Pipe syntax works anywhere standard syntax is supported: in queries, views, table-valued functions (TVFs), and other contexts.
- Pipe syntax can be mixed with standard syntax in the same query. For example, subqueries can use different syntax from the parent query.
- A pipe operator can see every alias that exists in the table preceding the pipe.
- A query can start with a
FROMclause, and pipe operators can optionally be added after theFROMclause.
Query comparison
Consider the following table called
example_table:
SELECT * FROM example_table;
/*---------------------------------------------------+
| _key | f1 | f2 |
+---------------------------------------------------+
| a#01 | { | { |
| | "c1": "xyz", | "col1": "abc", |
| | "c2": "nop" | "col2": 125 |
| | } | } |
+---------------------------------------------------+
| a#02 | { | { |
| | "c1": "xyz" | "col1": "cba", |
| | } | "col2": 123 |
| | | } |
+---------------------------------------------------+
| b#01 | { | { |
| | "c1": "jkl", | "col1": "qrs", |
| | "c2": "mno" | "col2": 654 |
| | } | } |
+---------------------------------------------------+
| b#02 | { | { |
| | "c1": "xyz", | "col1": "abc" |
| | "c2": "no" | } |
| | } | |
+---------------------------------------------------*/
Compare the following equivalent queries that compute the number and total
amount of sales for each item in the
example_table table:
Standard syntax
SELECT f2['col1'] AS col1, COUNT(*) AS num_items
FROM example_table
WHERE f1['c1'] = 'xyz'
GROUP BY col1
ORDER BY num_items DESC;
/*--------+-----------+
| col1 | num_items |
+--------+-----------+
| abc | 2 |
| cba | 1 |
+--------+-----------*/
Pipe syntax
FROM example_table
|> WHERE f1['c1'] = 'xyz'
|> AGGREGATE COUNT(*) AS num_items GROUP BY f2['col1'] AS col1
|> ORDER BY num_items DESC;
/*--------+-----------+
| col1 | num_items |
+--------+-----------+
| abc | 2 |
| cba | 1 |
+--------+-----------*/
Pipe operator semantics
Pipe operators have the following semantic behavior:
- Each pipe operator performs a self-contained operation.
- A pipe operator consumes the input table passed to it through the pipe
symbol,
|>, and produces a new table as output. - A pipe operator can reference only columns from its immediate input table. Columns from earlier in the same query aren't visible. Inside subqueries, correlated references to outer columns are still allowed.
FROM queries
In pipe syntax, a query can start with a standard FROM clause
and use any standard FROM syntax, including tables, joins, subqueries,
UNNEST operations, and
table-valued functions (TVFs). Table aliases can be
assigned to each input item using the AS alias clause.
A query with only a FROM clause, like FROM table_name, is allowed in pipe
syntax and returns all rows from the table. For tables with columns,
FROM table_name in pipe syntax is similar to
SELECT * FROM table_name in standard syntax.
Example
The following query uses the example_table:
FROM example_table;
/*---------------------------------------------------+
| _key | f1 | f2 |
+---------------------------------------------------+
| a#01 | { | { |
| | "c1": "xyz", | "col1": "abc", |
| | "c2": "nop" | "col2": 125" |
| | } | } |
+---------------------------------------------------+
| a#02 | { | { |
| | "c1": "xyz" | "col1": "cba", |
| | } | "col2": 123 |
| | | } |
+---------------------------------------------------+
| b#01 | { | { |
| | "c1": "jkl", | "col1": "qrs", |
| | "c2": "mno" | "col2": 654 |
| | } | } |
+---------------------------------------------------+
| b#02 | { | { |
| | "c1": "xyz", | "col1": "abc" |
| | "c2": "no" | } |
| | } | |
+---------------------------------------------------*/
Temporal filters and time series unpacking follow the same format as the standard syntax.
Example with temporal filtering:
FROM example_table(as_of => TIMESTAMP('2022-01-10T13:14:00.234Z'));
Example with the UNPACK function:
FROM UNPACK((SELECT * FROM example_table(with_history=>true)));
Pipe operators
GoogleSQL supports the following pipe operators. For operators that correspond or relate to similar operations in standard syntax, the operator descriptions highlight similarities and differences and link to more detailed documentation on the corresponding syntax.
Pipe operator list
| Name | Summary |
|---|---|
SELECT
|
Produces a new table with the listed columns. |
EXTEND
|
Propagates the existing table and adds computed columns. |
SET
|
Replaces the values of columns in the input table. |
DROP
|
Removes listed columns from the input table. |
RENAME
|
Renames specified columns. |
AS
|
Introduces a table alias for the input table. |
WHERE
|
Filters the results of the input table. |
AGGREGATE
|
Performs aggregation on data across groups of rows or the full input table. |
ORDER BY
|
Sorts results by a list of expressions. |
LIMIT
|
Limits the number of rows to return in a query. |
SELECT pipe operator
|> SELECT expression [[AS] alias] [, ...]
Description
Produces a new table with the listed columns, similar to the outermost
SELECT clause in a table subquery in standard syntax. The
SELECT operator supports standard output modifiers like SELECT AS STRUCT and
SELECT DISTINCT.
The SELECT operator doesn't support aggregations or anonymization.
In pipe syntax, the SELECT operator in a query is optional. The SELECT
operator can be used near the end of a query to specify the list of output
columns. The final query result contains the columns returned from the last pipe
operator. If the SELECT operator isn't used to select specific columns, the
output includes the full row, similar to what the
SELECT * statement in standard syntax produces.
In pipe syntax, the SELECT clause doesn't perform aggregation. Use the
AGGREGATE operator instead.
For cases where SELECT would be used in standard syntax to rearrange columns,
pipe syntax supports other operators:
- The
EXTENDoperator adds columns. - The
SEToperator updates the value of an existing column. - The
DROPoperator removes columns. - The
RENAMEoperator renames columns.
Examples
FROM (SELECT 'apples' AS item, 2 AS sales)
|> SELECT item AS fruit_name;
/*------------+
| fruit_name |
+------------+
| apples |
+------------*/
EXTEND pipe operator
|> EXTEND expression [[AS] alias] [, ...]
Description
Propagates the existing table and adds computed columns, similar to
SELECT *, new_column in standard syntax.
Example
(
SELECT 50 AS Fahrenheit
)
|> EXTEND (Fahrenheit - 32) * 5 / 9 AS Celsius;
/*------------+---------+
| Fahrenheit | Celsius |
+------------+---------+
| 50 | 10 |
+------------+---------*/
SET pipe operator
|> SET column = expression [, ...]
Description
Replaces the value of a column in the input table, similar to
SELECT * REPLACE (expression AS column) in standard syntax.
Each referenced column must exist exactly once in the input table.
After a SET operation, the referenced top-level columns (like x) are
updated, but table aliases (like t) still refer to the original row values.
Therefore, t.x will still refer to the original value.
Example
(
SELECT 2 AS x, 22 AS y
)
|> SET x = x * x, y = 3;
/*---+---+
| x | y |
+---+---+
| 4 | 3 |
+---+---*/
FROM (SELECT 2 AS x, 3 AS y) AS t
|> SET x = x * x, y = 8
|> SELECT t.x AS original_x, x, y;
/*------------+---+---+
| original_x | x | y |
+------------+---+---+
| 2 | 4 | 8 |
+------------+---+---*/
DROP pipe operator
|> DROP column [, ...]
Description
Removes listed columns from the input table, similar to
SELECT * EXCEPT (column) in standard syntax. Each
referenced column must exist at least once in the input table.
After a DROP operation, the referenced top-level columns (like x) are
removed, but table aliases (like t) still refer to the original row values.
Therefore, t.x will still refer to the original value.
Example
SELECT 'apples' AS item, 2 AS sales, 'fruit' AS category
|> DROP sales, category;
/*--------+
| item |
+--------+
| apples |
+--------*/
FROM (SELECT 1 AS x, 2 AS y) AS t
|> DROP x
|> SELECT t.x AS original_x, y;
/*------------+---+
| original_x | y |
+------------+---+
| 1 | 2 |
+------------+---*/
RENAME pipe operator
|> RENAME old_column_name [AS] new_column_name [, ...]
Description
Renames specified columns. Each column to be renamed must exist exactly once in
the input table. The RENAME operator can't rename value table fields,
pseudo-columns, range variables, or objects that aren't columns in the input
table.
After a RENAME operation, the referenced top-level columns (like x) are
renamed, but table aliases (like t) still refer to the original row
values. Therefore, t.x will still refer to the original value.
Example
SELECT 1 AS x, 2 AS y, 3 AS z
|> AS t
|> RENAME y AS renamed_y
|> SELECT *, t.y AS t_y;
/*---+-----------+---+-----+
| x | renamed_y | z | t_y |
+---+-----------+---+-----+
| 1 | 2 | 3 | 2 |
+---+-----------+---+-----*/
AS pipe operator
|> AS alias
Description
Introduces a table alias for the input table, similar to applying the
AS alias clause on a table subquery in standard syntax. Any
existing table aliases are removed and the new alias becomes the table alias for
all columns in the row.
The AS operator can be useful after operators like
SELECT, EXTEND, or
AGGREGATE that add columns but can't give table
aliases to them.
Example
FROM UNNEST([1000, 100, 10]) AS sales
|> AGGREGATE SUM(sales) AS total_sales
|> AS t;
/*-------------------------+
| total_sales |
+-------------------------+
| 1110 |
+-------------------------*/
WHERE pipe operator
|> WHERE boolean_expression
Description
Filters the results of the input table. The WHERE operator behaves the same
as the WHERE clause in standard syntax.
In pipe syntax, the WHERE operator also replaces the
HAVING clause in
standard syntax. For example, after performing aggregation with the
AGGREGATE operator, use the WHERE operator
instead of the HAVING clause.
Example
FROM UNNEST([1000, 100, 10]) AS sales
|> WHERE sales > 90;
/*--------+
| sales |
+--------+
| 1000 |
| 100 |
+--------*/
AGGREGATE pipe operator
-- Full-table aggregation |> AGGREGATE aggregate_expression [[AS] alias] [, ...]
-- Aggregation with grouping |> AGGREGATE [aggregate_expression [[AS] alias] [, ...]] GROUP BY groupable_items [[AS] alias] [, ...]
-- Aggregation with grouping and shorthand ordering syntax |> AGGREGATE [aggregate_expression [[AS] alias] [order_suffix] [, ...]] GROUP [AND ORDER] BY groupable_item [[AS] alias] [order_suffix] [, ...] order_suffix: {ASC | DESC}
Description
Performs aggregation on data across grouped rows or an entire table. The
AGGREGATE operator is similar to a query in standard syntax that contains a
GROUP BY clause or a SELECT list with
aggregate functions or both. In pipe syntax, the
GROUP BY clause is part of the AGGREGATE operator. Pipe syntax
doesn't support a standalone GROUP BY operator.
Without the GROUP BY clause, the AGGREGATE operator performs full-table
aggregation and produces one output row.
With the GROUP BY clause, the AGGREGATE operator performs aggregation with
grouping, producing one row for each set of distinct values for the grouping
expressions.
The AGGREGATE expression list corresponds to the aggregated expressions in a
SELECT list in standard syntax. Each expression in the AGGREGATE list must
include an aggregate function. Aggregate expressions can also include scalar
expressions (for example, sqrt(SUM(x*x))). Column aliases can be assigned
using the AS operator.
The GROUP BY clause in the AGGREGATE operator corresponds to the GROUP BY
clause in standard syntax. Unlike in standard syntax, aliases can be assigned to
GROUP BY items.
The output columns from the AGGREGATE operator include all grouping columns
first, followed by all aggregate columns, using their assigned aliases as the
column names.
Unlike in standard syntax, grouping expressions aren't repeated across SELECT
and GROUP BY clauses. In pipe syntax, the grouping expressions are listed
once, in the GROUP BY clause, and are automatically included as output columns
for the AGGREGATE operator.
Because output columns are fully specified by the AGGREGATE operator, the
SELECT operator isn't needed after the AGGREGATE operator unless
you want to produce a list of columns different from the default.
Standard syntax
-- Aggregation in standard syntax SELECT SUM(col1) AS total, col2, col3, col4... FROM table1 GROUP BY col2, col3, col4...
Pipe syntax
-- The same aggregation in pipe syntax FROM table1 |> AGGREGATE SUM(col1) AS total GROUP BY col2, col3, col4...
Examples
-- Full-table aggregation
FROM
UNNEST(
[
STRUCT('apples' AS item, 2 AS sales),
STRUCT('bananas' AS item, 5 AS sales),
STRUCT('apples' AS item, 7 AS sales)]) AS t
|> AGGREGATE COUNT(*) AS num_items, SUM(t.sales) AS total_sales;
/*-----------+-------------+
| num_items | total_sales |
+-----------+-------------+
| 3 | 14 |
+-----------+-------------*/
-- Aggregation with grouping
FROM
UNNEST(
[
STRUCT('apples' AS item, 2 AS sales),
STRUCT('bananas' AS item, 5 AS sales),
STRUCT('apples' AS item, 7 AS sales)]) AS t
|> AGGREGATE COUNT(*) AS num_items, SUM(t.sales) AS total_sales GROUP BY t.item;
/*---------+-----------+-------------+
| item | num_items | total_sales |
+---------+-----------+-------------+
| apples | 2 | 9 |
| bananas | 1 | 5 |
+---------+-----------+-------------*/
Shorthand ordering syntax with AGGREGATE
The AGGREGATE operator supports a shorthand ordering syntax, which is
equivalent to applying the ORDER BY operator as part
of the AGGREGATE operator without repeating the column list:
-- Aggregation with grouping and shorthand ordering syntax |> AGGREGATE [aggregate_expression [[AS] alias] [order_suffix] [, ...]] GROUP [AND ORDER] BY groupable_item [[AS] alias] [order_suffix] [, ...] order_suffix: {ASC | DESC}
The GROUP AND ORDER BY clause is equivalent to an ORDER BY clause on all
groupable_items. By default, each groupable_item is sorted in ascending
order with NULL values first. Other ordering suffixes like DESC can be used for other orders.
Without the GROUP AND ORDER BY clause, the ASC or DESC suffixes can be
added on individual columns in the GROUP BY list or AGGREGATE list or both.
Adding these suffixes is equivalent to adding an ORDER BY clause that includes
all of the suffixed columns with the suffixed grouping columns first, matching
the left-to-right output column order.
Examples
Consider the following table called
example_table:
/*---------------------------------------------------+
| _key | f1 | f2 |
+---------------------------------------------------+
| a#01 | { | { |
| | "c1": "xyz", | "col1": "abc", |
| | "c2": "nop" | "col2": 125" |
| | } | } |
+---------------------------------------------------+
| a#02 | { | { |
| | "c1": "xyz" | "col1": "cba", |
| | } | "col2": 123 |
| | | } |
+---------------------------------------------------+
| b#01 | { | { |
| | "c1": "jkl", | "col1": "qrs", |
| | "c2": "mno" | "col2": 654 |
| | } | } |
+---------------------------------------------------+
| b#02 | { | { |
| | "c1": "xyz", | "col1": "abc" |
| | "c2": "no" | } |
| | } | |
+---------------------------------------------------*/
The following two equivalent examples show you how to order by all grouping
columns using the GROUP AND ORDER BY clause or a separate ORDER BY clause:
-- Order by all grouping columns using GROUP AND ORDER BY.
FROM example_table
|> AGGREGATE COUNT(*) AS num_items
GROUP AND ORDER BY f1['c1'] AS c1, f2['col1'] AS col1 DESC;
/*------------+-----------+
| c1 | col1 | num_items |
+-----------+------------+
| jkl | qrs | 1 |
| xyz | cba | 1 |
| xyz | abc | 2 |
+-----------+------------*/
-- Order by columns using ORDER BY after performing aggregation.
FROM example_table
|> AGGREGATE COUNT(*) AS num_items
GROUP BY f1['c1'] AS c1, f2['col1'] AS col1
|> ORDER BY c1, col1 DESC;
You can add an ordering suffix to a column in the AGGREGATE list. Although the
AGGREGATE list appears before the GROUP BY list in the query, ordering
suffixes on columns in the GROUP BY list are applied first.
FROM example_table
|> AGGREGATE COUNT(*) AS num_items ASC
GROUP BY f2['col1'] AS col1, f1['c1'] AS c1 DESC;
/*------------+-----------+
| col1 | c1 | num_items |
+------------+-----------+
| abc | xyz | 1 |
| cba | xyz | 2 |
| qrs | jkl | 1 |
+-----------+------------*/
The previous query is equivalent to the following:
-- Order by specified grouping and aggregate columns.
FROM example_table
|> AGGREGATE COUNT(*) AS num_items
GROUP BY f1['c1'] AS c1, f2['col1'] AS col1
|> ORDER BY c1 DESC, num_items;
ORDER BY pipe operator
|> ORDER BY expression [sort_options] [, ...]
Description
Sorts results by a list of expressions. The ORDER BY operator behaves the same
as the ORDER BY clause in standard syntax. Suffixes like
DESC are supported for
customizing the ordering for each expression.
In pipe syntax, the AGGREGATE operator also
supports shorthand ordering suffixes to
apply ORDER BY behavior more concisely as part of aggregation.
Example
FROM UNNEST([1000, 100, 10]) AS sales
|> ORDER BY sales DESC;
/*-------+
| sales |
+-------+
| 1000 |
| 100 |
| 10 |
+-------*/
LIMIT pipe operator
|> LIMIT count
Description
Limits the number of rows to return in a query. The LIMIT operator
behaves the same as the LIMIT
clause in standard syntax.
Example
FROM UNNEST([1000, 100, 10]) AS sales
|> ORDER BY sales DESC
|> LIMIT 1;
/*---------+
| sales |
+---------+
| 1000 |
+---------*/