Computed Columns

On this page Carat arrow pointing down
Warning:
CockroachDB v2.0 is no longer supported. For more details, see the Release Support Policy.

New in v2.0: A computed column stores data generated from other columns by a scalar expression included in the column definition.

Why use computed columns?

Computed columns are especially useful when used with partitioning, JSONB columns, or secondary indexes.

  • Partitioning requires that partitions are defined using columns that are a prefix of the primary key. In the case of geo-partitioning, some applications will want to collapse the number of possible values in this column, to make certain classes of queries more performant. For example, if a users table has a country and state column, then you can make a stored computed column locality with a reduced domain for use in partitioning. For more information, see the partitioning example below.

  • JSONB columns are used for storing semi-structured JSONB data. When the table's primary information is stored in JSONB, it's useful to index a particular field of the JSONB document. In particular, computed columns allow for the following use case: a two-column table with a PRIMARY KEY column and a payload column, whose primary key is computed as some field from the payload column. This alleviates the need to manually separate your primary keys from your JSON blobs. For more information, see the JSONB example below.

  • Secondary indexes can be created on computed columns, which is especially useful when a table is frequently sorted. See the secondary indexes example below.

Considerations

Computed columns:

  • Cannot be added after a table is created. Follow the GitHub issue for updates on this limitation.
  • Cannot be used to generate other computed columns.
  • Cannot be a foreign key reference.
  • Behave like any other column, with the exception that they cannot be written to directly.
  • Are mutually exclusive with DEFAULT.

Creation

Computed columns can only be added at the time of table creation. Use the following syntax:

column_name <type> AS (<expr>) STORED
Parameter Description
column_name The name/identifier of the computed column.
<type> The data type of the computed column.
<expr> The pure scalar expression used to compute column values. Any functions marked as impure, such as now() or nextval() cannot be used.
STORED (Required) The computed column is stored alongside other columns.

Examples

Create a Table with a Computed Column

In this example, let's create a simple table with a computed column:

icon/buttons/copy
> CREATE TABLE names (
    id INT PRIMARY KEY,
    first_name STRING,
    last_name STRING,
    full_name STRING AS (CONCAT(first_name, ' ', last_name)) STORED
  );

Then, insert a few rows a data:

icon/buttons/copy
> INSERT INTO names (id, first_name, last_name) VALUES
    (1, 'Lola', 'McDog'),
    (2, 'Carl', 'Kimball'),
    (3, 'Ernie', 'Narayan');
icon/buttons/copy
> SELECT * FROM names;
+----+------------+-------------+----------------+
| id | first_name |  last_name  |   full_name    |
+----+------------+-------------+----------------+
|  1 | Lola       | McDog       | Lola McDog     |
|  2 | Carl       | Kimball     | Carl Kimball   |
|  3 | Ernie      | Narayan     | Ernie Narayan  |
+----+------------+-------------+----------------+

The full_name column is computed from the first_name and last_name columns without the need to define a view.

Create a Table with Geo-partitions and a Computed Column

Note:
Partioning is an enterprise feature. To request and enable a trial or full enterprise license, see Enterprise Licensing.

In this example, let's create a table with geo-partitioning and a computed column:

icon/buttons/copy
> CREATE TABLE user_locations (
    locality STRING AS (CASE
      WHEN country IN ('ca', 'mx', 'us') THEN 'north_america'
      WHEN country IN ('au', 'nz') THEN 'australia'
    END) STORED,
    id SERIAL,
    name STRING,
    country STRING,
    PRIMARY KEY (locality, id))
    PARTITION BY LIST (locality)
    (PARTITION north_america VALUES IN ('north_america'),
    PARTITION australia VALUES IN ('australia'));

Then, insert a few rows of data:

icon/buttons/copy
> INSERT INTO user_locations (name, country) VALUES
    ('Leonard McCoy', 'us'),
    ('Uhura', 'nz'),
    ('Spock', 'ca'),
    ('James Kirk', 'us'),
    ('Scotty', 'mx'),
    ('Hikaru Sulu', 'us'),
    ('Pavel Chekov', 'au');
icon/buttons/copy
> SELECT * FROM user_locations;
+---------------+--------------------+---------------+---------+
|   locality    |         id         |     name      | country |
+---------------+--------------------+---------------+---------+
| australia     | 333153890100609025 | Uhura         | nz      |
| australia     | 333153890100772865 | Pavel Chekov  | au      |
| north_america | 333153890100576257 | Leonard McCoy | us      |
| north_america | 333153890100641793 | Spock         | ca      |
| north_america | 333153890100674561 | James Kirk    | us      |
| north_america | 333153890100707329 | Scotty        | mx      |
| north_america | 333153890100740097 | Hikaru Sulu   | us      |
+---------------+--------------------+---------------+---------+

The locality column is computed from the country column. The locality values can then be used for geo-partitioning.

Create a Table with a JSONB Column and a Computed Column

In this example, let's create a table with a JSONB column and a computed column:

icon/buttons/copy
> CREATE TABLE student_profiles (
    id STRING PRIMARY KEY AS (profile->>'id') STORED,
    profile JSONB
);

Then, insert a few rows of data:

icon/buttons/copy
> INSERT INTO student_profiles (profile) VALUES
    ('{"id": "d78236", "name": "Arthur Read", "age": "16", "school": "PVPHS", "credits": 120, "sports": "none"}'),
    ('{"name": "Buster Bunny", "age": "15", "id": "f98112", "school": "THS", "credits": 67, "clubs": "MUN"}'),
    ('{"name": "Ernie Narayan", "school" : "Brooklyn Tech", "id": "t63512", "sports": "Track and Field", "clubs": "Chess"}');
icon/buttons/copy
> SELECT * FROM student_profiles;
+--------+---------------------------------------------------------------------------------------------------------------------+
|   id   |                                                       profile                                                       |
+--------+---------------------------------------------------------------------------------------------------------------------+
| d78236 | {"age": "16", "credits": 120, "id": "d78236", "name": "Arthur Read", "school": "PVPHS", "sports": "none"}           |
| f98112 | {"age": "15", "clubs": "MUN", "credits": 67, "id": "f98112", "name": "Buster Bunny", "school": "THS"}               |
| t63512 | {"clubs": "Chess", "id": "t63512", "name": "Ernie Narayan", "school": "Brooklyn Tech", "sports": "Track and Field"} |
+--------+---------------------------------------------------------------------------------------------------------------------+

The primary key id is computed as a field from the profile column.

Create a Table with a Secondary Index on a Computed Column

In this example, let's create a table with a computed columns and an index on that column:

icon/buttons/copy
> CREATE TABLE gymnastics (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    athlete STRING,
    vault DECIMAL,
    bars DECIMAL,
    beam DECIMAL,
    floor DECIMAL,
    combined_score DECIMAL AS (vault + bars + beam + floor) STORED,
    INDEX total (combined_score DESC)
  );

Then, insert a few rows a data:

icon/buttons/copy
> INSERT INTO gymnastics (athlete, vault, bars, beam, floor) VALUES
    ('Simone Biles', 15.933, 14.800, 15.300, 15.800),
    ('Gabby Douglas', 0, 15.766, 0, 0),
    ('Laurie Hernandez', 15.100, 0, 15.233, 14.833),
    ('Madison Kocian', 0, 15.933, 0, 0),
    ('Aly Raisman', 15.833, 0, 15.000, 15.366);
icon/buttons/copy
> SELECT * FROM gymnastics;
+--------------------------------------+------------------+--------+--------+--------+--------+----------------+
|                  id                  |     athlete      | vault  |  bars  |  beam  | floor  | combined_score |
+--------------------------------------+------------------+--------+--------+--------+--------+----------------+
| 3fe11371-6a6a-49de-bbef-a8dd16560fac | Aly Raisman      | 15.833 |      0 | 15.000 | 15.366 |         46.199 |
| 56055a70-b4c7-4522-909b-8f3674b705e5 | Madison Kocian   |      0 | 15.933 |      0 |      0 |         15.933 |
| 69f73fd1-da34-48bf-aff8-71296ce4c2c7 | Gabby Douglas    |      0 | 15.766 |      0 |      0 |         15.766 |
| 8a7b730b-668d-4845-8d25-48bda25114d6 | Laurie Hernandez | 15.100 |      0 | 15.233 | 14.833 |         45.166 |
| b2c5ca80-21c2-4853-9178-b96ce220ea4d | Simone Biles     | 15.933 | 14.800 | 15.300 | 15.800 |         61.833 |
+--------------------------------------+------------------+--------+--------+--------+--------+----------------+

Now, let's run a query using the secondary index:

icon/buttons/copy
> SELECT athlete, combined_score FROM gymnastics ORDER BY combined_score DESC;
+------------------+----------------+
|     athlete      | combined_score |
+------------------+----------------+
| Simone Biles     |         61.833 |
| Aly Raisman      |         46.199 |
| Laurie Hernandez |         45.166 |
| Madison Kocian   |         15.933 |
| Gabby Douglas    |         15.766 |
+------------------+----------------+

The athlete with the highest combined score of 61.833 is Simone Biles.

See Also


Yes No
On this page

Yes No