diff --git a/docs/cloud/onboard/02_migrate/01_migration_guides/03_bigquery/01_overview.md b/docs/cloud/onboard/02_migrate/01_migration_guides/03_bigquery/01_overview.md index a8d1a0f6821..a2e4031191f 100644 --- a/docs/cloud/onboard/02_migrate/01_migration_guides/03_bigquery/01_overview.md +++ b/docs/cloud/onboard/02_migrate/01_migration_guides/03_bigquery/01_overview.md @@ -55,28 +55,7 @@ ClickHouse Cloud controls user access in two places, via the [cloud console](/cl ## Data types {#data-types} -ClickHouse offers more granular precision with respect to numerics. For example, BigQuery offers the numeric types [`INT64`, `NUMERIC`, `BIGNUMERIC` and `FLOAT64`](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#numeric_types). Contrast these with ClickHouse, which offers multiple precision types for decimals, floats, and integers. With these data types, you can optimize storage and memory overhead, resulting in faster queries and lower resource consumption. Below we map the equivalent ClickHouse type for each BigQuery type: - -| BigQuery | ClickHouse | -|----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [ARRAY](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#array_type) | [Array(t)](/sql-reference/data-types/array) | -| [NUMERIC](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#decimal_types) | [Decimal(P, S), Decimal32(S), Decimal64(S), Decimal128(S)](/sql-reference/data-types/decimal) | -| [BIG NUMERIC](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#decimal_types) | [Decimal256(S)](/sql-reference/data-types/decimal) | -| [BOOL](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#boolean_type) | [Bool](/sql-reference/data-types/boolean) | -| [BYTES](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#bytes_type) | [FixedString](/sql-reference/data-types/fixedstring) | -| [DATE](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#date_type) | [Date32](/sql-reference/data-types/date32) (with narrower range) | -| [DATETIME](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#datetime_type) | [DateTime](/sql-reference/data-types/datetime), [DateTime64](/sql-reference/data-types/datetime64) (narrow range, higher precision) | -| [FLOAT64](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#floating_point_types) | [Float64](/sql-reference/data-types/float) | -| [GEOGRAPHY](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#geography_type) | [Geo Data Types](/sql-reference/data-types/float) | -| [INT64](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#integer_types) | [UInt8, UInt16, UInt32, UInt64, UInt128, UInt256, Int8, Int16, Int32, Int64, Int128, Int256](/sql-reference/data-types/int-uint) | -| [INTERVAL](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#integer_types) | NA - [supported as expression](/sql-reference/data-types/special-data-types/interval#usage-remarks) or [through functions](/sql-reference/functions/date-time-functions#addYears) | -| [JSON](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#json_type) | [JSON](/integrations/data-formats/json/inference) | -| [STRING](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#string_type) | [String (bytes)](/sql-reference/data-types/string) | -| [STRUCT](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#constructing_a_struct) | [Tuple](/sql-reference/data-types/tuple), [Nested](/sql-reference/data-types/nested-data-structures/nested) | -| [TIME](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#time_type) | [DateTime64](/sql-reference/data-types/datetime64) | -| [TIMESTAMP](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#timestamp_type) | [DateTime64](/sql-reference/data-types/datetime64) | - -When presented with multiple options for ClickHouse types, consider the actual range of the data and pick the lowest required. Also, consider utilizing [appropriate codecs](https://clickhouse.com/blog/optimize-clickhouse-codecs-compression-schema) for further compression. +For the full BigQuery-to-ClickHouse type mapping, see the [Data types section](/migrations/bigquery/sql-translation-reference#data-types) of the SQL translation reference. ## Query acceleration techniques {#query-acceleration-techniques} @@ -157,324 +136,4 @@ ClickHouse provides standard SQL with many extensions and improvements that make ## Arrays {#arrays} -Compared to BigQuery's 8 array functions, ClickHouse has over 80 [built-in array functions](/sql-reference/functions/array-functions) for modeling and solving a wide range of problems elegantly and simply. - -A typical design pattern in ClickHouse is to use the [`groupArray`](/sql-reference/aggregate-functions/reference/grouparray) aggregate function to (temporarily) transform specific row values of a table into an array. This then can be conveniently processed via array functions, and the result can be converted back into individual table rows via [`arrayJoin`](/sql-reference/functions/array-join) aggregate function. - -Because ClickHouse SQL supports [higher order lambda functions](/sql-reference/functions/overview#arrow-operator-and-lambda), many advanced array operations can be achieved by simply calling one of the higher order built-in array functions, instead of temporarily converting arrays back to tables, as it is often [required](https://cloud.google.com/bigquery/docs/arrays) in BigQuery, e.g. for [filtering](https://cloud.google.com/bigquery/docs/arrays#filtering_arrays) or [zipping](https://cloud.google.com/bigquery/docs/arrays#zipping_arrays) arrays. In ClickHouse these operations are just a simple function call of the higher order functions [`arrayFilter`](/sql-reference/functions/array-functions#arrayFilter), and [`arrayZip`](/sql-reference/functions/array-functions#arrayZip), respectively. - -In the following, we provide a mapping of array operations from BigQuery to ClickHouse: - -| BigQuery | ClickHouse | -|----------|------------| -| [ARRAY_CONCAT](https://cloud.google.com/bigquery/docs/reference/standard-sql/array_functions#array_concat) | [arrayConcat](/sql-reference/functions/array-functions#arrayConcat) | -| [ARRAY_LENGTH](https://cloud.google.com/bigquery/docs/reference/standard-sql/array_functions#array_length) | [length](/sql-reference/functions/array-functions#length) | -| [ARRAY_REVERSE](https://cloud.google.com/bigquery/docs/reference/standard-sql/array_functions#array_reverse) | [arrayReverse](/sql-reference/functions/array-functions#arrayReverse) | -| [ARRAY_TO_STRING](https://cloud.google.com/bigquery/docs/reference/standard-sql/array_functions#array_to_string) | [arrayStringConcat](/sql-reference/functions/splitting-merging-functions#arrayStringConcat) | -| [GENERATE_ARRAY](https://cloud.google.com/bigquery/docs/reference/standard-sql/array_functions#generate_array) | [range](/sql-reference/functions/array-functions#range) | - -**Create an array with one element for each row in a subquery** - -_BigQuery_ - -[ARRAY function](https://cloud.google.com/bigquery/docs/reference/standard-sql/array_functions#array) - -```sql -SELECT ARRAY - (SELECT 1 UNION ALL - SELECT 2 UNION ALL - SELECT 3) AS new_array; - -/*-----------* - | new_array | - +-----------+ - | [1, 2, 3] | - *-----------*/ -``` - -_ClickHouse_ - -[groupArray](/sql-reference/aggregate-functions/reference/grouparray) aggregate function - -```sql -SELECT groupArray(*) AS new_array -FROM -( - SELECT 1 - UNION ALL - SELECT 2 - UNION ALL - SELECT 3 -) - ┌─new_array─┐ -1. │ [1,2,3] │ - └───────────┘ -``` - -**Convert an array into a set of rows** - -_BigQuery_ - -[`UNNEST`](https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#unnest_operator) operator - -```sql -SELECT * -FROM UNNEST(['foo', 'bar', 'baz', 'qux', 'corge', 'garply', 'waldo', 'fred']) - AS element -WITH OFFSET AS offset -ORDER BY offset; - -/*----------+--------* - | element | offset | - +----------+--------+ - | foo | 0 | - | bar | 1 | - | baz | 2 | - | qux | 3 | - | corge | 4 | - | garply | 5 | - | waldo | 6 | - | fred | 7 | - *----------+--------*/ -``` - -_ClickHouse_ - -[ARRAY JOIN](/sql-reference/statements/select/array-join) clause - -```sql -WITH ['foo', 'bar', 'baz', 'qux', 'corge', 'garply', 'waldo', 'fred'] AS values -SELECT element, num-1 AS offset -FROM (SELECT values AS element) AS subquery -ARRAY JOIN element, arrayEnumerate(element) AS num; - -/*----------+--------* - | element | offset | - +----------+--------+ - | foo | 0 | - | bar | 1 | - | baz | 2 | - | qux | 3 | - | corge | 4 | - | garply | 5 | - | waldo | 6 | - | fred | 7 | - *----------+--------*/ -``` - -**Return an array of dates** - -_BigQuery_ - -[GENERATE_DATE_ARRAY](https://cloud.google.com/bigquery/docs/reference/standard-sql/array_functions#generate_date_array) function - -```sql -SELECT GENERATE_DATE_ARRAY('2016-10-05', '2016-10-08') AS example; - -/*--------------------------------------------------* - | example | - +--------------------------------------------------+ - | [2016-10-05, 2016-10-06, 2016-10-07, 2016-10-08] | - *--------------------------------------------------*/ -``` - -[range](/sql-reference/functions/array-functions#range) + [arrayMap](/sql-reference/functions/array-functions#arrayMap) functions - -_ClickHouse_ - -```sql -SELECT arrayMap(x -> (toDate('2016-10-05') + x), range(toUInt32((toDate('2016-10-08') - toDate('2016-10-05')) + 1))) AS example - - ┌─example───────────────────────────────────────────────┐ -1. │ ['2016-10-05','2016-10-06','2016-10-07','2016-10-08'] │ - └───────────────────────────────────────────────────────┘ -``` - -**Return an array of timestamps** - -_BigQuery_ - -[GENERATE_TIMESTAMP_ARRAY](https://cloud.google.com/bigquery/docs/reference/standard-sql/array_functions#generate_timestamp_array) function - -```sql -SELECT GENERATE_TIMESTAMP_ARRAY('2016-10-05 00:00:00', '2016-10-07 00:00:00', - INTERVAL 1 DAY) AS timestamp_array; - -/*--------------------------------------------------------------------------* - | timestamp_array | - +--------------------------------------------------------------------------+ - | [2016-10-05 00:00:00+00, 2016-10-06 00:00:00+00, 2016-10-07 00:00:00+00] | - *--------------------------------------------------------------------------*/ -``` - -_ClickHouse_ - -[range](/sql-reference/functions/array-functions#range) + [arrayMap](/sql-reference/functions/array-functions#arrayMap) functions - -```sql -SELECT arrayMap(x -> (toDateTime('2016-10-05 00:00:00') + toIntervalDay(x)), range(dateDiff('day', toDateTime('2016-10-05 00:00:00'), toDateTime('2016-10-07 00:00:00')) + 1)) AS timestamp_array - -Query id: b324c11f-655b-479f-9337-f4d34fd02190 - - ┌─timestamp_array─────────────────────────────────────────────────────┐ -1. │ ['2016-10-05 00:00:00','2016-10-06 00:00:00','2016-10-07 00:00:00'] │ - └─────────────────────────────────────────────────────────────────────┘ -``` - -**Filtering arrays** - -_BigQuery_ - -Requires temporarily converting arrays back to tables via [`UNNEST`](https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#unnest_operator) operator - -```sql -WITH Sequences AS - (SELECT [0, 1, 1, 2, 3, 5] AS some_numbers - UNION ALL SELECT [2, 4, 8, 16, 32] AS some_numbers - UNION ALL SELECT [5, 10] AS some_numbers) -SELECT - ARRAY(SELECT x * 2 - FROM UNNEST(some_numbers) AS x - WHERE x < 5) AS doubled_less_than_five -FROM Sequences; - -/*------------------------* - | doubled_less_than_five | - +------------------------+ - | [0, 2, 2, 4, 6] | - | [4, 8] | - | [] | - *------------------------*/ -``` - -_ClickHouse_ - -[arrayFilter](/sql-reference/functions/array-functions#arrayFilter) function - -```sql -WITH Sequences AS - ( - SELECT [0, 1, 1, 2, 3, 5] AS some_numbers - UNION ALL - SELECT [2, 4, 8, 16, 32] AS some_numbers - UNION ALL - SELECT [5, 10] AS some_numbers - ) -SELECT arrayMap(x -> (x * 2), arrayFilter(x -> (x < 5), some_numbers)) AS doubled_less_than_five -FROM Sequences; - ┌─doubled_less_than_five─┐ -1. │ [0,2,2,4,6] │ - └────────────────────────┘ - ┌─doubled_less_than_five─┐ -2. │ [] │ - └────────────────────────┘ - ┌─doubled_less_than_five─┐ -3. │ [4,8] │ - └────────────────────────┘ -``` - -**Zipping arrays** - -_BigQuery_ - -Requires temporarily converting arrays back to tables via [`UNNEST`](https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#unnest_operator) operator - -```sql -WITH - Combinations AS ( - SELECT - ['a', 'b'] AS letters, - [1, 2, 3] AS numbers - ) -SELECT - ARRAY( - SELECT AS STRUCT - letters[SAFE_OFFSET(index)] AS letter, - numbers[SAFE_OFFSET(index)] AS number - FROM Combinations - CROSS JOIN - UNNEST( - GENERATE_ARRAY( - 0, - LEAST(ARRAY_LENGTH(letters), ARRAY_LENGTH(numbers)) - 1)) AS index - ORDER BY index - ); - -/*------------------------------* - | pairs | - +------------------------------+ - | [{ letter: "a", number: 1 }, | - | { letter: "b", number: 2 }] | - *------------------------------*/ -``` - -_ClickHouse_ - -[arrayZip](/sql-reference/functions/array-functions#arrayZip) function - -```sql -WITH Combinations AS - ( - SELECT - ['a', 'b'] AS letters, - [1, 2, 3] AS numbers - ) -SELECT arrayZip(letters, arrayResize(numbers, length(letters))) AS pairs -FROM Combinations; - ┌─pairs─────────────┐ -1. │ [('a',1),('b',2)] │ - └───────────────────┘ -``` - -**Aggregating arrays** - -_BigQuery_ - -Requires converting arrays back to tables via [`UNNEST`](https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#unnest_operator) operator - -```sql -WITH Sequences AS - (SELECT [0, 1, 1, 2, 3, 5] AS some_numbers - UNION ALL SELECT [2, 4, 8, 16, 32] AS some_numbers - UNION ALL SELECT [5, 10] AS some_numbers) -SELECT some_numbers, - (SELECT SUM(x) - FROM UNNEST(s.some_numbers) AS x) AS sums -FROM Sequences AS s; - -/*--------------------+------* - | some_numbers | sums | - +--------------------+------+ - | [0, 1, 1, 2, 3, 5] | 12 | - | [2, 4, 8, 16, 32] | 62 | - | [5, 10] | 15 | - *--------------------+------*/ -``` - -_ClickHouse_ - -[arraySum](/sql-reference/functions/array-functions#arraySum), [arrayAvg](/sql-reference/functions/array-functions#arrayAvg), ... function, or any of the over 90 existing aggregate function names as argument for the [arrayReduce](/sql-reference/functions/array-functions#arrayReduce) function - -```sql -WITH Sequences AS - ( - SELECT [0, 1, 1, 2, 3, 5] AS some_numbers - UNION ALL - SELECT [2, 4, 8, 16, 32] AS some_numbers - UNION ALL - SELECT [5, 10] AS some_numbers - ) -SELECT - some_numbers, - arraySum(some_numbers) AS sums -FROM Sequences; - ┌─some_numbers──┬─sums─┐ -1. │ [0,1,1,2,3,5] │ 12 │ - └───────────────┴──────┘ - ┌─some_numbers──┬─sums─┐ -2. │ [2,4,8,16,32] │ 62 │ - └───────────────┴──────┘ - ┌─some_numbers─┬─sums─┐ -3. │ [5,10] │ 15 │ - └──────────────┴──────┘ -``` +For the ClickHouse equivalents of BigQuery array functions and `UNNEST`-based patterns, see the [Array functions section](/migrations/bigquery/sql-translation-reference#array-functions) of the SQL translation reference. diff --git a/docs/cloud/onboard/02_migrate/01_migration_guides/03_bigquery/04_sql-translation-reference.md b/docs/cloud/onboard/02_migrate/01_migration_guides/03_bigquery/04_sql-translation-reference.md new file mode 100644 index 00000000000..c48f36997b4 --- /dev/null +++ b/docs/cloud/onboard/02_migrate/01_migration_guides/03_bigquery/04_sql-translation-reference.md @@ -0,0 +1,3847 @@ +--- +title: 'BigQuery SQL translation reference' +slug: /migrations/bigquery/sql-translation-reference +description: 'Construct-by-construct mapping from BigQuery GoogleSQL to ClickHouse SQL' +keywords: ['BigQuery', 'migration', 'SQL', 'translation', 'reference'] +sidebar_label: 'SQL translation reference' +doc_type: 'reference' +--- + +This document details the similarities and differences in SQL syntax between BigQuery and ClickHouse. + +## Data types {#data-types} + +ClickHouse offers more granular precision than BigQuery for numerics. +Where BigQuery has [`INT64`, `NUMERIC`, `BIGNUMERIC`, and `FLOAT64`](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#numeric_types), +ClickHouse provides multiple integer, decimal, and float widths so storage +and memory can be tuned to the actual range of the data. + +:::tip +When several +ClickHouse types map to a single BigQuery type, pick the smallest that fits +and consider [appropriate codecs](/sql-reference/statements/create/table#column_compression_codec) +for further compression. +::: + +| BigQuery | ClickHouse | Notes | +|----------|------------|-------| +| [`ARRAY`](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#array_type) | [`Array(t)`](/sql-reference/data-types/array) | | +| [`BIGNUMERIC`](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#decimal_types) | [`Decimal256(S)`](/sql-reference/data-types/decimal) | | +| [`BOOL`](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#boolean_type) | [`Bool`](/sql-reference/data-types/boolean) | | +| [`BYTES`](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#bytes_type) | [`String`](/sql-reference/data-types/string)  or  [`FixedString(N)`](/sql-reference/data-types/fixedstring) | | +| [`DATE`](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#date_type) | [`Date`](/sql-reference/data-types/date)  or  [`Date32`](/sql-reference/data-types/date32) | `Date` (2-byte, 1970-2149) for typical analytical data; `Date32` (4-byte, 1900-2299) to match BigQuery's full range | +| [`DATETIME`](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#datetime_type) | [`DateTime`](/sql-reference/data-types/datetime)  or  [`DateTime64(p)`](/sql-reference/data-types/datetime64) | Use `DateTime64(p)` for sub-second precision | +| [`FLOAT64`](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#floating_point_types) | [`Float64`](/sql-reference/data-types/float) | | +| [`GEOGRAPHY`](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#geography_type) | [Geo data types](/sql-reference/data-types/geo) | | +| [`INT64`](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#integer_types) | [`UInt8` … `UInt256` / `Int8` … `Int256`](/sql-reference/data-types/int-uint) | Pick the smallest signed or unsigned variant that fits the value range | +| [`INTERVAL`](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#interval_type) | No equivalent | Use the [`INTERVAL` expression](/sql-reference/data-types/special-data-types/interval#usage-remarks) or [date/time arithmetic functions](/sql-reference/functions/date-time-functions#addYears) | +| [`JSON`](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#json_type) | [`JSON`](/sql-reference/data-types/newjson)  or  [`String`](/sql-reference/data-types/string) | `JSON` is preferred; `String` with [`JSONExtract*`](/sql-reference/functions/json-functions) accessors works as a fallback | +| [`NUMERIC`](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#decimal_types) | [`Decimal(P, S)`](/sql-reference/data-types/decimal) | Sized variants `Decimal32(S)` / `Decimal64(S)` / `Decimal128(S)` are also available | +| [`RANGE`](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#range_type) | No equivalent | Store `(start, end)` columns or a `Tuple(start, end)` | +| [`STRING`](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#string_type) | [`String`](/sql-reference/data-types/string) | Optionally wrap in [`LowCardinality(String)`](/sql-reference/data-types/lowcardinality) for columns with few distinct values (enums, status codes, country codes). String functions are byte-based; the [String family](/sql-reference/functions/string-functions) has `UTF8` variants where relevant | +| [`STRUCT`](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#constructing_a_struct) | [`Tuple`](/sql-reference/data-types/tuple)  or  [`Nested`](/sql-reference/data-types/nested-data-structures/nested) | Flattened sibling columns are often more performant in ClickHouse; use `Tuple` / `Nested` for named fields when the nested shape is load-bearing | +| [`TIME`](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#time_type) | No equivalent | Carry a [`DateTime64`](/sql-reference/data-types/datetime64) and extract via [`formatDateTime`](/sql-reference/functions/date-time-functions#formatDateTime) | +| [`TIMESTAMP`](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#timestamp_type) | [`DateTime64(6, 'UTC')`](/sql-reference/data-types/datetime64) | Use for microsecond-precision UTC parity | + +## DDL statements {#ddl-statements} + +### Schemas and databases {#ddl-schemas} + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
BigQueryClickHouse
+ +```sql +CREATE SCHEMA mydataset +``` + + + +```sql +CREATE DATABASE mydb +``` + +
+ +BigQuery datasets map to ClickHouse [databases](/sql-reference/statements/create/database). + +
+ +```sql +CREATE SCHEMA IF NOT EXISTS mydataset +OPTIONS (location = 'US') +``` + + + +```sql +CREATE DATABASE IF NOT EXISTS mydb +``` + +
+ +Location is a service-level decision in ClickHouse Cloud. + +
+ +```sql +ALTER SCHEMA mydataset SET OPTIONS (description = 'sales') +``` + + + +```sql +ALTER DATABASE mydb MODIFY COMMENT 'sales' +``` + +
+ +```sql +DROP SCHEMA mydataset +``` + + + +```sql +DROP DATABASE mydb +``` + +
+ +Add `IF EXISTS` for an idempotent drop in either engine. + +
+ +```sql +DROP SCHEMA mydataset CASCADE +``` + + + +```sql +DROP DATABASE mydb +``` + +
+ +ClickHouse drops tables in the database unconditionally; there is no `CASCADE` keyword. + +
+ +### Tables {#ddl-tables} + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
BigQueryClickHouse
+ +```sql +CREATE TABLE mydataset.t ( + id INT64, + name STRING +) +``` + + + +```sql +CREATE TABLE mydb.t ( + id Int64, + name String +) +ENGINE = MergeTree +ORDER BY id +``` + +
+ +An engine and an `ORDER BY` are required for `MergeTree`-family tables; pick the columns that match the query access pattern. See [Sparse primary indexes](/guides/best-practices/sparse-primary-indexes). + +
+ +```sql +CREATE TABLE IF NOT EXISTS mydataset.t (id INT64) +``` + + + +```sql +CREATE TABLE IF NOT EXISTS mydb.t (id Int64) +ENGINE = MergeTree +ORDER BY id +``` + +
+ +```sql +CREATE OR REPLACE TABLE mydataset.t (id INT64) +``` + + + +```sql +CREATE OR REPLACE TABLE mydb.t (id Int64) +ENGINE = MergeTree +ORDER BY id +``` + +
+ +```sql +CREATE TABLE mydataset.t (id INT64) +PARTITION BY DATE(created_at) +``` + + + +```sql +CREATE TABLE mydb.t ( + id Int64, + created_at DateTime +) +ENGINE = MergeTree +PARTITION BY toDate(created_at) +ORDER BY id +``` + +
+ +BigQuery partitioning expects a single date/timestamp column; ClickHouse accepts an arbitrary expression. ClickHouse partitions are a storage-organization feature, not a substitute for `ORDER BY`. + +
+ +```sql +CREATE TABLE mydataset.t ( + id INT64, + name STRING +) +CLUSTER BY id, name +``` + + + +```sql +CREATE TABLE mydb.t ( + id Int64, + name String +) +ENGINE = MergeTree +ORDER BY (id, name) +``` + +
+ +BigQuery clustering colocates data; in ClickHouse the equivalent is the table's `ORDER BY` key, which controls on-disk ordering and the primary index. + +
+ +```sql +CREATE TABLE mydataset.t LIKE mydataset.source +``` + + + +```sql +CREATE TABLE mydb.t AS mydb.source +``` + +
+ +Both copy the schema only; ClickHouse also copies engine and `ORDER BY`. + +
+ +```sql +CREATE TABLE mydataset.t AS +SELECT * FROM mydataset.source +``` + + + +```sql +CREATE TABLE mydb.t +ENGINE = MergeTree +ORDER BY id +AS SELECT * FROM mydb.source +``` + +
+ +Engine and `ORDER BY` are required for the new table. + +
+ +```sql +CREATE TEMP TABLE t AS SELECT 1 AS x +``` + + + +```sql +CREATE TEMPORARY TABLE t (x Int64) ENGINE = Memory; +INSERT INTO t VALUES (1); +``` + +
+ +ClickHouse temporary tables are session-scoped. The engine clause is optional and defaults to [`Memory`](/engines/table-engines/special/memory); specify a different one only if you need non-Memory storage. + +
+ +```sql +CREATE EXTERNAL TABLE mydataset.t +WITH CONNECTION ... +OPTIONS ( + format = 'PARQUET', + uris = ['gs://...'] +) +``` + + + +```sql +CREATE TABLE mydb.t (...) +ENGINE = S3('https://.../*.parquet', 'Parquet') +``` + +
+ +ClickHouse exposes external storage through table engines such as [`S3`](/engines/table-engines/integrations/s3), [`URL`](/engines/table-engines/special/url), and [`HDFS`](/engines/table-engines/integrations/hdfs). + +
+ +```sql +ALTER TABLE mydataset.t ADD COLUMN tag STRING +``` + + + +```sql +ALTER TABLE mydb.t ADD COLUMN tag String +``` + +
+ +```sql +ALTER TABLE mydataset.t DROP COLUMN tag +``` + + + +```sql +ALTER TABLE mydb.t DROP COLUMN tag +``` + +
+ +```sql +ALTER TABLE mydataset.t RENAME COLUMN old TO new +``` + + + +```sql +ALTER TABLE mydb.t RENAME COLUMN old TO new +``` + +
+ +```sql +ALTER TABLE mydataset.t +ALTER COLUMN amount SET DATA TYPE FLOAT64 +``` + + + +```sql +ALTER TABLE mydb.t MODIFY COLUMN amount Float64 +``` + +
+ +```sql +ALTER TABLE mydataset.t SET OPTIONS (description = '...') +``` + + + +```sql +ALTER TABLE mydb.t MODIFY COMMENT '...' +``` + +
+ +```sql +ALTER TABLE mydataset.t RENAME TO t2 +``` + + + +```sql +RENAME TABLE mydb.t TO mydb.t2 +``` + +
+ +```sql +DROP TABLE mydataset.t +``` + + + +```sql +DROP TABLE mydb.t +``` + +
+ +```sql +TRUNCATE TABLE mydataset.t +``` + + + +```sql +TRUNCATE TABLE mydb.t +``` + +
+ +### Views and materialized views {#ddl-views} + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
BigQueryClickHouse
+ +```sql +CREATE VIEW mydataset.v AS +SELECT id, name FROM mydataset.t +``` + + + +```sql +CREATE VIEW mydb.v AS +SELECT id, name FROM mydb.t +``` + +
+ +Standard logical views; no storage in either engine. + +
+ +```sql +CREATE OR REPLACE VIEW mydataset.v AS SELECT ... +``` + + + +```sql +CREATE OR REPLACE VIEW mydb.v AS SELECT ... +``` + +
+ +```sql +DROP VIEW mydataset.v +``` + + + +```sql +DROP VIEW mydb.v +``` + +
+ +```sql +CREATE MATERIALIZED VIEW mydataset.mv AS +SELECT status, count(*) AS c +FROM mydataset.t +GROUP BY status +``` + + + +```sql +CREATE MATERIALIZED VIEW mydb.mv +ENGINE = AggregatingMergeTree +ORDER BY status +AS SELECT status, countState() AS c +FROM mydb.t +GROUP BY status +``` + +
+ +ClickHouse [materialized views](/sql-reference/statements/create/view#materialized-view) are incremental insert-time triggers that write into a target table. For an aggregate MV, use [`AggregatingMergeTree`](/engines/table-engines/mergetree-family/aggregatingmergetree) and the `-State` aggregate-function combinator. Query the MV with `countMerge(c)` to finalize. + +
+ +```sql +CREATE MATERIALIZED VIEW mydataset.mv +OPTIONS (refresh_interval_minutes = 60) +``` + + + +```sql +CREATE MATERIALIZED VIEW mydb.mv +REFRESH EVERY 1 HOUR +ENGINE = MergeTree +ORDER BY id +AS SELECT ... +``` + +
+ +ClickHouse also supports [refreshable materialized views](/sql-reference/statements/create/view#refreshable-materialized-view) for full-refresh semantics. + +
+ +### Indexes, functions, and procedures {#ddl-indexes-functions} + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
BigQueryClickHouse
+ +```sql +CREATE SEARCH INDEX idx ON mydataset.t (name) +``` + + + +```sql +ALTER TABLE mydb.t +ADD INDEX idx name + TYPE text(tokenizer = splitByNonAlpha) + GRANULARITY 1 +``` + +
+ +ClickHouse [full-text indexes](/engines/table-engines/mergetree-family/textindexes) accelerate `LIKE` / token-search predicates. + +
+ +```sql +CREATE VECTOR INDEX idx ON mydataset.t (embedding) +OPTIONS (distance_type = 'COSINE') +``` + + + +```sql +ALTER TABLE mydb.t +ADD INDEX idx embedding + TYPE vector_similarity('hnsw', 'cosineDistance', 1536) + GRANULARITY 1 +``` + +
+ +The third argument is the vector dimension and is required; it must match the length of every value stored in the indexed `Array(Float*)` column. See [Approximate-nearest-neighbor indexes](/engines/table-engines/mergetree-family/annindexes). + +
+ +```sql +CREATE FUNCTION mydataset.add_one(x INT64) AS (x + 1) +``` + + + +```sql +CREATE FUNCTION add_one AS (x) -> x + 1 +``` + +
+ +ClickHouse [UDFs](/sql-reference/statements/create/function) are expression-only. For complex logic, use the SQL-defined-function or executable-UDF patterns. + +
+ +```sql +CREATE FUNCTION mydataset.f(x INT64) +RETURNS INT64 +LANGUAGE js AS 'return x+1;' +``` + + + +No equivalent + +
+ +ClickHouse has no JavaScript or Python UDFs in standard SQL; use executable UDFs at the server level. + +
+ +```sql +DROP FUNCTION mydataset.add_one +``` + + + +```sql +DROP FUNCTION add_one +``` + +
+ +```sql +CREATE PROCEDURE mydataset.p(x INT64) BEGIN ... END +``` + + + +No equivalent + +
+ +ClickHouse has no stored procedures; orchestrate from a client (Python, Go, etc.) or compose into a SQL function. + +
+ +## DML {#dml} + +### Insert operations {#dml-inserts} + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + +
BigQueryClickHouse
+ +```sql +INSERT INTO mydataset.t VALUES (1, 'a'), (2, 'b') +``` + + + +```sql +INSERT INTO mydb.t VALUES (1, 'a'), (2, 'b') +``` + +
+ +```sql +INSERT INTO mydataset.t (id, name) VALUES (1, 'a') +``` + + + +```sql +INSERT INTO mydb.t (id, name) VALUES (1, 'a') +``` + +
+ +```sql +INSERT INTO mydataset.t +SELECT id, name FROM mydataset.source +``` + + + +```sql +INSERT INTO mydb.t +SELECT id, name FROM mydb.source +``` + +
+ +### Update operations {#dml-updates} + + ++++ + + + + + + + + + + + + + + + + + +
BigQueryClickHouse
+ +```sql +UPDATE mydataset.t SET name = 'x' WHERE id = 1 +``` + + + +```sql +ALTER TABLE mydb.t UPDATE name = 'x' WHERE id = 1 +``` + +
+ +ClickHouse mutates asynchronously by default. For synchronous, in-place updates use [lightweight updates](/sql-reference/statements/update): `UPDATE mydb.t SET name = 'x' WHERE id = 1`. + +
+ +### Delete operations {#dml-deletes} + + ++++ + + + + + + + + + + + + + + + + + + + + + + +
BigQueryClickHouse
+ +```sql +DELETE FROM mydataset.t WHERE id = 1 +``` + + + +```sql +DELETE FROM mydb.t WHERE id = 1 +``` + +
+ +ClickHouse's [lightweight delete](/sql-reference/statements/delete) marks rows for removal; physical removal happens at the next merge. For bulk historical cleanup prefer use of [TTL](/engines/table-engines/mergetree-family/mergetree#table_engine-mergetree-ttl). + +
+ +```sql +TRUNCATE TABLE mydataset.t +``` + + + +```sql +TRUNCATE TABLE mydb.t +``` + +
+ +### Merge operations {#dml-merges} + + ++++ + + + + + + + + + + + + + + + + + +
BigQueryClickHouse
+ +```sql +MERGE INTO mydataset.t USING mydataset.staging s + ON t.id = s.id +WHEN MATCHED THEN UPDATE SET ... +WHEN NOT MATCHED THEN INSERT ... +``` + + + +No equivalent + +
+ +ClickHouse has no equivalent `MERGE` statement. The idiomatic pattern is a [`ReplacingMergeTree`](/engines/table-engines/mergetree-family/replacingmergetree) keyed on the natural key plus a version column; an `INSERT` of newer rows "wins" at merge time. Use [`FINAL`](/sql-reference/statements/select/from#final-modifier) for read-time deduplication. + +
+ +## DCL {#dcl} + +### Grants {#dcl-grants} + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
BigQueryClickHouse
+ +```sql +GRANT SELECT ON TABLE mydataset.t +TO 'user:alice@example.com' +``` + + + +```sql +GRANT SELECT ON mydb.t TO alice +``` + +
+ +BigQuery binds to IAM principals (users, groups, service accounts). ClickHouse binds to SQL-level users and roles created with `CREATE USER` / `CREATE ROLE`. + +
+ +```sql +GRANT ROLE foo ON TABLE mydataset.t +TO 'user:alice@example.com' +``` + + + +```sql +GRANT foo TO alice +``` + +
+ +```sql +REVOKE SELECT ON TABLE mydataset.t +FROM 'user:alice@example.com' +``` + + + +```sql +REVOKE SELECT ON mydb.t FROM alice +``` + +
+ +### Roles {#dcl-roles} + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
BigQueryClickHouse
+ +IAM-managed via console / `gcloud` + + + +```sql +CREATE USER alice +IDENTIFIED WITH plaintext_password BY 'pw' +``` + +
+ +See [Access control and roles](/operations/access-rights). + +
+ +IAM-managed via console / `gcloud` + + + +```sql +CREATE ROLE analyst +``` + +
+ +IAM-managed via console / `gcloud` + + + +```sql +DROP USER alice +``` + +
+ +## Syntax {#syntax} + +### Query syntax {#query-syntax} + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
BigQueryClickHouse
+ +```sql +SELECT id, name FROM mydataset.t +``` + + + +```sql +SELECT id, name FROM mydb.t +``` + +
+ +```sql +SELECT * FROM mydataset.t +``` + + + +```sql +SELECT * FROM mydb.t +``` + +
+ +```sql +SELECT * EXCEPT (password) FROM mydataset.t +``` + + + +```sql +SELECT * EXCEPT password FROM mydb.t +``` + +
+ +ClickHouse [`* EXCEPT`](/sql-reference/statements/select/#except-modifier) accepts an unparenthesised single column; multiple columns must be parenthesised. + +
+ +```sql +SELECT * REPLACE (lower(name) AS name) FROM mydataset.t +``` + + + +```sql +SELECT * REPLACE (lower(name) AS name) FROM mydb.t +``` + +
+ +```sql +SELECT id FROM mydataset.t +WHERE created_at > '2024-01-01' +``` + + + +```sql +SELECT id FROM mydb.t +WHERE created_at > '2024-01-01' +``` + +
+ +```sql +SELECT status, count(*) FROM mydataset.t +GROUP BY status +``` + + + +```sql +SELECT status, count() FROM mydb.t +GROUP BY status +``` + +
+ +```sql +SELECT status, count(*) FROM mydataset.t +GROUP BY ROLLUP (status, country) +``` + + + +```sql +SELECT status, country, count() FROM mydb.t +GROUP BY ROLLUP (status, country) +``` + +
+ +ClickHouse also supports `GROUP BY CUBE` and `GROUP BY GROUPING SETS`. + +
+ +```sql +SELECT status, count(*) AS c FROM mydataset.t +GROUP BY status +HAVING c > 10 +``` + + + +```sql +SELECT status, count() AS c FROM mydb.t +GROUP BY status +HAVING c > 10 +``` + +
+ +```sql +SELECT id FROM mydataset.t ORDER BY id +``` + + + +```sql +SELECT id FROM mydb.t ORDER BY id +``` + +
+ +```sql +SELECT id FROM mydataset.t +ORDER BY id DESC NULLS LAST +LIMIT 100 +``` + + + +```sql +SELECT id FROM mydb.t +ORDER BY id DESC NULLS LAST +LIMIT 100 +``` + +
+ +```sql +SELECT id FROM mydataset.t LIMIT 100 OFFSET 50 +``` + + + +```sql +SELECT id FROM mydb.t LIMIT 100 OFFSET 50 +``` + +
+ +```sql +SELECT DISTINCT status FROM mydataset.t +``` + + + +```sql +SELECT DISTINCT status FROM mydb.t +``` + +
+ +```sql +WITH recent AS ( + SELECT * FROM mydataset.t + WHERE created_at > '2024-01-01' +) +SELECT id FROM recent +``` + + + +```sql +WITH recent AS ( + SELECT * FROM mydb.t + WHERE created_at > '2024-01-01' +) +SELECT id FROM recent +``` + +
+ +```sql +SELECT a.id +FROM mydataset.t a +INNER JOIN mydataset.u b ON a.id = b.id +``` + + + +```sql +SELECT a.id +FROM mydb.t AS a +INNER JOIN mydb.u AS b ON a.id = b.id +``` + +
+ +ClickHouse generally requires `AS` for table aliases. + +
+ +```sql +SELECT a.id +FROM mydataset.t a +LEFT JOIN mydataset.u b ON a.id = b.id +``` + + + +```sql +SELECT a.id +FROM mydb.t AS a +LEFT JOIN mydb.u AS b ON a.id = b.id +``` + +
+ +`FULL`, `RIGHT`, `LEFT ANY`, `LEFT SEMI`, `LEFT ANTI` all match between engines. + +
+ +```sql +SELECT a.id +FROM mydataset.t a +RIGHT JOIN mydataset.u b ON a.id = b.id +``` + + + +```sql +SELECT a.id +FROM mydb.t AS a +RIGHT JOIN mydb.u AS b ON a.id = b.id +``` + +
+ +```sql +SELECT a.id +FROM mydataset.t a +FULL OUTER JOIN mydataset.u b ON a.id = b.id +``` + + + +```sql +SELECT a.id +FROM mydb.t AS a +FULL JOIN mydb.u AS b ON a.id = b.id +``` + +
+ +ClickHouse spells it `FULL JOIN` (the `OUTER` keyword is optional and usually omitted). + +
+ +```sql +SELECT a.id +FROM mydataset.t a +CROSS JOIN mydataset.u b +``` + + + +```sql +SELECT a.id +FROM mydb.t AS a +CROSS JOIN mydb.u AS b +``` + +
+ +```sql +SELECT a.id +FROM mydataset.t a +JOIN mydataset.u b USING (id) +``` + + + +```sql +SELECT a.id +FROM mydb.t AS a +JOIN mydb.u AS b USING (id) +``` + +
+ +```sql +SELECT * FROM mydataset.t +WHERE EXISTS ( + SELECT 1 FROM mydataset.u WHERE u.id = t.id +) +``` + + + +```sql +SELECT * FROM mydb.t +WHERE id IN (SELECT id FROM mydb.u) +``` + +
+ +ClickHouse supports `EXISTS (subquery)` as well; `IN (subquery)` is the more idiomatic form for existence checks and is what you'll see in most ClickHouse query examples. + +
+ +```sql +SELECT * FROM mydataset.t +WHERE NOT EXISTS ( + SELECT 1 FROM mydataset.u WHERE u.id = t.id +) +``` + + + +```sql +SELECT * FROM mydb.t +WHERE id NOT IN (SELECT id FROM mydb.u) +``` + +
+ +```sql +SELECT a, b FROM mydataset.t +UNION ALL +SELECT a, b FROM mydataset.u +``` + + + +```sql +SELECT a, b FROM mydb.t +UNION ALL +SELECT a, b FROM mydb.u +``` + +
+ +ClickHouse rejects bare `UNION` by default (controlled by the `union_default_mode` setting); always write `UNION ALL` or `UNION DISTINCT` explicitly to avoid surprises. + +
+ +```sql +SELECT a FROM mydataset.t +INTERSECT DISTINCT +SELECT a FROM mydataset.u +``` + + + +```sql +SELECT a FROM mydb.t +INTERSECT DISTINCT +SELECT a FROM mydb.u +``` + +
+ +```sql +SELECT a FROM mydataset.t +EXCEPT DISTINCT +SELECT a FROM mydataset.u +``` + + + +```sql +SELECT a FROM mydb.t +EXCEPT DISTINCT +SELECT a FROM mydb.u +``` + +
+ +```sql +SELECT element +FROM UNNEST(['a','b','c']) AS element +``` + + + +```sql +SELECT arrayJoin(['a','b','c']) AS element +``` + +
+ +Use [`ARRAY JOIN`](/sql-reference/statements/select/array-join) or [`arrayJoin`](/sql-reference/functions/array-join). + +
+ +```sql +SELECT id FROM mydataset.t +QUALIFY ROW_NUMBER() OVER ( + PARTITION BY user_id + ORDER BY created_at DESC +) = 1 +``` + + + +```sql +SELECT id FROM ( + SELECT *, + row_number() OVER ( + PARTITION BY user_id + ORDER BY created_at DESC + ) AS rn + FROM mydb.t +) +WHERE rn = 1 +``` + +
+ +ClickHouse has no `QUALIFY`; wrap the windowed query in a subquery. + +
+ +```sql +SELECT * FROM mydataset.t TABLESAMPLE SYSTEM (10 PERCENT) +``` + + + +```sql +SELECT * FROM mydb.t SAMPLE 0.1 +``` + +
+ +ClickHouse [`SAMPLE`](/sql-reference/statements/select/sample) requires the table to declare a `SAMPLE BY` clause. + +
+ +```sql +SELECT * FROM mydataset.t +FOR SYSTEM_TIME AS OF TIMESTAMP '2024-03-15 00:00:00+00:00' +``` + + + +No equivalent + +
+ +ClickHouse doesn't provide row-level time travel. Patterns include [`ReplacingMergeTree`](/engines/table-engines/mergetree-family/replacingmergetree) with a version column or per-day backup tables. + +
+ +```sql +SELECT id, sum(amount) OVER w +FROM mydataset.t +WINDOW w AS (PARTITION BY user_id ORDER BY created_at) +``` + + + +```sql +SELECT id, sum(amount) OVER w +FROM mydb.t +WINDOW w AS (PARTITION BY user_id ORDER BY created_at) +``` + +
+ +```sql +SELECT * FROM mydataset.t +PIVOT (count(*) FOR status IN ('open' AS open, 'closed' AS closed)) +``` + + + +```sql +SELECT + countIf(status = 'open') AS open, + countIf(status = 'closed') AS closed +FROM mydb.t +``` + +
+ +ClickHouse has no `PIVOT`; use [`countIf` / `sumIf`](/sql-reference/aggregate-functions/combinators#-if) per output column. + +
+ +```sql +SELECT col, v +FROM (SELECT 1 AS a, 2 AS b) +UNPIVOT (v FOR col IN (a, b)) +``` + + + +```sql +SELECT p.1 AS col, p.2 AS v +FROM ( + SELECT [tuple('a', a), tuple('b', b)] AS pairs FROM mydb.t +) +ARRAY JOIN pairs AS p +``` + +
+ +ClickHouse has no `UNPIVOT`; emit `(name, value)` tuples and `ARRAY JOIN`. + +
+ +### Pipe syntax {#pipe-syntax} + +BigQuery's [pipe syntax](https://docs.cloud.google.com/bigquery/docs/reference/standard-sql/pipe-syntax) +chains transformations with the `|>` operator. ClickHouse has no equivalent; +each pipe operator desugars to a clause in standard SQL. Subqueries or CTEs +are the readable way to chain stages. + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
BigQueryClickHouse
+ +```sql +FROM mydataset.t +|> SELECT id, name +``` + + + +```sql +SELECT id, name FROM mydb.t +``` + +
+ +```sql +FROM mydataset.t +|> WHERE amount > 100 +``` + + + +```sql +SELECT * FROM mydb.t WHERE amount > 100 +``` + +
+ +```sql +FROM mydataset.t +|> EXTEND amount * 0.1 AS tax +``` + + + +```sql +SELECT *, amount * 0.1 AS tax FROM mydb.t +``` + +
+ +```sql +FROM mydataset.t +|> SET amount = amount * 2 +``` + + + +```sql +SELECT * REPLACE (amount * 2 AS amount) FROM mydb.t +``` + +
+ +```sql +FROM mydataset.t +|> DROP password +``` + + + +```sql +SELECT * EXCEPT password FROM mydb.t +``` + +
+ +```sql +FROM mydataset.t +|> RENAME amount AS price +``` + + + +```sql +SELECT * EXCEPT amount, amount AS price FROM mydb.t +``` + +
+ +ClickHouse `* REPLACE` substitutes the value of an existing column but cannot rename it; use `* EXCEPT` plus an aliased column to drop the original name and reintroduce it under the new one. + +
+ +```sql +FROM mydataset.t +|> AGGREGATE sum(amount) AS total + GROUP BY status +``` + + + +```sql +SELECT status, sum(amount) AS total +FROM mydb.t +GROUP BY status +``` + +
+ +```sql +FROM mydataset.t +|> ORDER BY created_at DESC +``` + + + +```sql +SELECT * FROM mydb.t ORDER BY created_at DESC +``` + +
+ +```sql +FROM mydataset.t +|> LIMIT 100 +``` + + + +```sql +SELECT * FROM mydb.t LIMIT 100 +``` + +
+ +```sql +FROM mydataset.t +|> JOIN mydataset.u USING (id) +``` + + + +```sql +SELECT * FROM mydb.t JOIN mydb.u USING (id) +``` + +
+ +```sql +FROM mydataset.t +|> WHERE amount > 100 +|> AGGREGATE count(*) GROUP BY status +``` + + + +```sql +SELECT status, count() +FROM mydb.t +WHERE amount > 100 +GROUP BY status +``` + +
+ +Pipes compose left-to-right; in ClickHouse, layer clauses or use a CTE for readability. + +
+ +### Procedural language {#procedural-language} + +ClickHouse SQL isn't a procedural language. Variables, loops, statement-level +`IF` / `CASE`, and stored procedures have no first-class equivalents; +orchestrate multi-step logic from a client library (Python, Go, JavaScript, +etc.) or use [parameterized views](/sql-reference/statements/create/view#parameterized-view) +for templated queries. + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
BigQueryClickHouse
+ +```sql +DECLARE x INT64 DEFAULT 0 +``` + + + +_client-side variable, or `SET param_x = 0` for [query parameters](/operations/server-configuration-parameters/settings#query-parameters)_ + +
+ +```sql +SET x = 5 +``` + + + +_client-side; `SET param_x = 5`_ + +
+ +```sql +BEGIN ... END +``` + + + +_multi-statement scripts are run by the client, not the server_ + +
+ +```sql +IF cond THEN ... ELSE ... END IF +``` + + + +_expression form `if(cond, a, b)`; for statement-level branching, branch in the client_ + +
+ +```sql +CASE WHEN cond THEN ... ELSE ... END CASE +``` + + + +_expression form `CASE WHEN cond THEN a ELSE b END`; no statement form_ + +
+ +```sql +WHILE cond DO ... END WHILE +``` + + + +_no equivalent; use a client-driven loop_ + +
+ +```sql +LOOP ... END LOOP +``` + + + +_no equivalent_ + +
+ +```sql +FOR row IN (SELECT ...) DO ... END FOR +``` + + + +_iterate over query results client-side_ + +
+ +`BREAK`, `CONTINUE`, `LEAVE`, `ITERATE` + + + +_no equivalent_ + +
+ +```sql +CALL mydataset.p(1) +``` + + + +_no stored procedures_ + +
+ +```sql +EXECUTE IMMEDIATE 'SELECT 1' +``` + + + +_client-side prepared statements_ + +
+ +```sql +RAISE USING MESSAGE = 'bad' +``` + + + +```sql +SELECT throwIf(1, 'bad') +``` + +
+ +```sql +BEGIN TRANSACTION; ...; COMMIT TRANSACTION +``` + + + +_multi-statement transactions are experimental_; see [transactions roadmap](https://github.com/ClickHouse/ClickHouse/issues/58392) + +
+ +### Operators {#operators} + + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
BigQueryClickHouseNotes
+ +`a + b`, `a - b`, `a * b`, `a / b` + + + +`a + b`, `a - b`, `a * b`, `a / b` + + + +`/` is real division in both engines. + +
+ +```sql +DIV(a, b) +``` + + + +```sql +intDiv(a, b) +``` + + + +Integer division. + +
+ +```sql +MOD(a, b) +``` + + + +```sql +a % b -- or modulo(a, b) +``` + +
+ +`a = b`, `a != b`, `a <> b` + + + +`a = b`, `a != b`, `a <> b` + +
+ +`a < b`, `a <= b`, `a > b`, `a >= b` + + + +_same_ + +
+ +`a AND b`, `a OR b`, `NOT a` + + + +_same_ + +
+ +```sql +a IN (1, 2, 3) +``` + + + +```sql +a IN (1, 2, 3) +``` + +
+ +```sql +a NOT IN (1, 2, 3) +``` + + + +```sql +a NOT IN (1, 2, 3) +``` + +
+ +```sql +a BETWEEN x AND y +``` + + + +```sql +a BETWEEN x AND y +``` + + + +Inclusive of both bounds. + +
+ +```sql +a IS NULL -- or a IS NOT NULL +``` + + + +_same_ + +
+ +```sql +a LIKE 'pre%' +``` + + + +```sql +a LIKE 'pre%' +``` + + + +ClickHouse also offers [`ILIKE`](/sql-reference/functions/string-search-functions#ilike) for case-insensitive matching. + +
+ +```sql +CONCAT(a, b) -- or a || b +``` + + + +```sql +concat(a, b) -- or a || b +``` + +
+ +### Conditional expressions {#conditional} + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
BigQueryClickHouse
+ +```sql +CASE WHEN c THEN a ELSE b END +``` + + + +```sql +CASE WHEN c THEN a ELSE b END +``` + +
+ +```sql +CASE x WHEN 1 THEN 'a' ELSE 'b' END +``` + + + +```sql +CASE x WHEN 1 THEN 'a' ELSE 'b' END +``` + +
+ +```sql +IF(cond, a, b) +``` + + + +```sql +if(cond, a, b) +``` + +
+ +```sql +IFNULL(a, b) +``` + + + +```sql +ifNull(a, b) -- or coalesce(a, b) +``` + +
+ +```sql +NULLIF(a, b) +``` + + + +```sql +nullIf(a, b) +``` + +
+ +```sql +COALESCE(a, b, c) +``` + + + +```sql +coalesce(a, b, c) +``` + +
+ +### Conversion {#conversion} + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
BigQueryClickHouse
+ +```sql +CAST(x AS STRING) +``` + + + +```sql +CAST(x AS String) -- or toString(x) +``` + +
+ +```sql +CAST(x AS INT64) +``` + + + +```sql +CAST(x AS Int64) -- or toInt64(x) +``` + +
+ +```sql +CAST(x AS FLOAT64) +``` + + + +```sql +CAST(x AS Float64) -- or toFloat64(x) +``` + +
+ +```sql +CAST(x AS DATE) +``` + + + +```sql +CAST(x AS Date) -- or toDate(x) +``` + +
+ +```sql +CAST(x AS TIMESTAMP) +``` + + + +```sql +CAST(x AS DateTime64(6, 'UTC')) +-- or parseDateTime64BestEffort(x, 6, 'UTC') +``` + +
+ +BigQuery `TIMESTAMP` is microsecond UTC. + +
+ +```sql +SAFE_CAST(x AS INT64) +``` + + + +```sql +toInt64OrNull(x) +``` + +
+ +Each numeric type has `toTypeOrNull` / `toTypeOrZero` / `toTypeOrDefault` variants. + +
+ +```sql +PARSE_NUMERIC('3.14') +``` + + + +```sql +toDecimal64('3.14', 9) +``` + +
+ +BigQuery `NUMERIC` is fixed `(38, 9)`; specify the scale you need in ClickHouse. + +
+ +```sql +PARSE_BIGNUMERIC('3.14') +``` + + + +```sql +toDecimal256('3.14', 38) +``` + +
+ +## Functions {#functions} + +### Array functions {#array-functions} + +Compared to BigQuery's roughly eight array functions, ClickHouse has more +than 80 [built-in array functions](/sql-reference/functions/array-functions). +The idiomatic pattern is to aggregate row values into an array with +[`groupArray`](/sql-reference/aggregate-functions/reference/grouparray), +transform with higher-order lambda functions +([`arrayMap`](/sql-reference/functions/array-functions#arrayMap), +[`arrayFilter`](/sql-reference/functions/array-functions#arrayFilter), +[`arrayZip`](/sql-reference/functions/array-functions#arrayZip)), and +optionally expand back to rows with +[`arrayJoin`](/sql-reference/functions/array-join). Because of this, many +transformations BigQuery expresses by round-tripping through +[`UNNEST`](https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#unnest_operator) +collapse to a single function call in ClickHouse. + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
BigQueryClickHouse
+ +```sql +ARRAY_CONCAT(a, b) +``` + + + +```sql +arrayConcat(a, b) +``` + +
+ +```sql +ARRAY_LENGTH(tags) +``` + + + +```sql +length(tags) +``` + +
+ +```sql +ARRAY_REVERSE(tags) +``` + + + +```sql +arrayReverse(tags) +``` + +
+ +```sql +ARRAY_TO_STRING(tags, ',') +``` + + + +```sql +arrayStringConcat(tags, ',') +``` + +
+ +```sql +GENERATE_ARRAY(1, 5) +``` + + + +```sql +range(1, 6) +``` + +
+ +ClickHouse [`range`](/sql-reference/functions/array-functions#range) excludes the upper bound. + +
+ +### Aggregate functions {#aggregate-functions} + +BigQuery exposes roughly 18 aggregate functions plus a handful of approximate aggregates. ClickHouse ships [more than 150 aggregate functions](/sql-reference/aggregate-functions/reference) and adds [combinators](/sql-reference/aggregate-functions/combinators) (suffixes such as `-If`, `-Array`, `-Map`, `-ForEach`, `-Merge`, and `-State`) that compose with any aggregate to extend its behavior across data shapes or to use it inside materialized views. + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
BigQueryClickHouse
+ +```sql +COUNT(*) -- or COUNT(col) +``` + + + +```sql +count() -- or count(col) +``` + +
+ +```sql +SUM(col), AVG(col), MIN(col), MAX(col) +``` + + + +```sql +sum(col), avg(col), min(col), max(col) +``` + +
+ +```sql +COUNTIF(cond) +``` + + + +```sql +countIf(cond) +``` + +
+ +ClickHouse pairs every aggregate with the [`-If` combinator](/sql-reference/aggregate-functions/combinators#-if): `sumIf`, `avgIf`, etc. + +
+ +```sql +STRING_AGG(name, ',') +``` + + + +```sql +arrayStringConcat(groupArray(name), ',') +``` + +
+ +```sql +ARRAY_AGG(name) +``` + + + +```sql +groupArray(name) +``` + +
+ +```sql +ANY_VALUE(name) +``` + + + +```sql +any(name) +``` + +
+ +```sql +APPROX_COUNT_DISTINCT(id) +``` + + + +```sql +uniq(id) -- or uniqExact(id) for exact, uniqHLL12(id) for HyperLogLog +``` + +
+ +### Window functions {#window-functions} + +`OVER` and `PARTITION BY` work the same in both engines (see [Query syntax](#query-syntax) for the windowed-query and `QUALIFY` examples). + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
BigQueryClickHouse
+ +```sql +ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY ts) +``` + + + +```sql +row_number() OVER (PARTITION BY user_id ORDER BY ts) +``` + +
+ +```sql +RANK() OVER (ORDER BY score DESC) +``` + + + +```sql +rank() OVER (ORDER BY score DESC) +``` + +
+ +```sql +DENSE_RANK() OVER (ORDER BY score DESC) +``` + + + +```sql +dense_rank() OVER (ORDER BY score DESC) +``` + +
+ +```sql +NTILE(4) OVER (ORDER BY amount) +``` + + + +```sql +ntile(4) OVER (ORDER BY amount) +``` + +
+ +```sql +LAG(amount, 1) OVER (PARTITION BY id ORDER BY ts) +``` + + + +```sql +lagInFrame(amount, 1) OVER (PARTITION BY id ORDER BY ts) +``` + +
+ +```sql +LEAD(amount, 1) OVER (PARTITION BY id ORDER BY ts) +``` + + + +```sql +leadInFrame(amount, 1) OVER (PARTITION BY id ORDER BY ts) +``` + +
+ +ClickHouse names its `LAG`/`LEAD` equivalents `lagInFrame` / `leadInFrame`; they're available without enabling experimental settings since version 23.10. + +
+ +```sql +FIRST_VALUE(name) OVER (PARTITION BY id ORDER BY ts) +``` + + + +```sql +first_value(name) OVER (PARTITION BY id ORDER BY ts) +``` + +
+ +```sql +LAST_VALUE(name) OVER (PARTITION BY id ORDER BY ts) +``` + + + +```sql +last_value(name) OVER (PARTITION BY id ORDER BY ts) +``` + +
+ +### Date and time functions {#date-functions} + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
BigQueryClickHouse
+ +```sql +CURRENT_DATE() +``` + + + +```sql +today() -- or toDate(now()) +``` + +
+ +```sql +CURRENT_TIMESTAMP() +``` + + + +```sql +now() -- or now64() for sub-second precision +``` + +
+ +```sql +EXTRACT(YEAR FROM d) -- MONTH, DAY, HOUR, etc. +``` + + + +```sql +toYear(d) -- toMonth(d), toDayOfMonth(d), toHour(d), ... +``` + +
+ +ClickHouse also accepts `EXTRACT(YEAR FROM d)` directly, so EXTRACT-heavy SQL can port as-is; the `toYear(d)` family is just terser. + +
+ +```sql +DATE_ADD(d, INTERVAL 5 DAY) +``` + + + +```sql +d + INTERVAL 5 DAY -- or addDays(d, 5) +``` + +
+ +```sql +DATE_SUB(d, INTERVAL 5 DAY) +``` + + + +```sql +d - INTERVAL 5 DAY -- or subtractDays(d, 5) +``` + +
+ +```sql +DATE_DIFF(end, start, DAY) +``` + + + +```sql +dateDiff('day', start, end) +``` + +
+ +Note the argument order: ClickHouse takes `(unit, start, end)`; BigQuery takes `(end, start, unit)`. + +
+ +```sql +DATE_TRUNC(d, MONTH) +``` + + + +```sql +toStartOfMonth(d) -- toStartOfWeek, toStartOfQuarter, toStartOfYear, ... +``` + +
+ +```sql +FORMAT_DATE('%Y-%m-%d', d) +``` + + + +```sql +formatDateTime(d, '%F') -- '%F' is ISO date; see formatDateTime docs +``` + +
+ +```sql +PARSE_DATE('%Y-%m-%d', s) +``` + + + +```sql +toDate(s) -- or parseDateTimeBestEffort(s) for permissive parsing +``` + +
+ +```sql +TIMESTAMP_SECONDS(n) +``` + + + +```sql +toDateTime(n) +``` + +
+ +### String functions {#string-functions} + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
BigQueryClickHouse
+ +```sql +LENGTH(s) +``` + + + +```sql +lengthUTF8(s) -- length(s) returns bytes +``` + +
+ +```sql +SUBSTR(s, pos, len) +``` + + + +```sql +substring(s, pos, len) +``` + +
+ +```sql +SPLIT(s, ',') +``` + + + +```sql +splitByChar(',', s) -- or splitByString(',', s) for multi-char delimiters +``` + +
+ +```sql +REPLACE(s, from, to) +``` + + + +```sql +replaceAll(s, from, to) -- use replaceOne for first match only +``` + +
+ +```sql +UPPER(s), LOWER(s) +``` + + + +```sql +upper(s), lower(s) -- upperUTF8 / lowerUTF8 for Unicode-aware casing +``` + +
+ +```sql +TRIM(s) +``` + + + +```sql +trimBoth(s) -- alias: trim(s); pass a second arg to trim specific characters +``` + +
+ +```sql +REGEXP_CONTAINS(s, r'^[a-z]') +``` + + + +```sql +match(s, '^[a-z]') +``` + +
+ +```sql +REGEXP_EXTRACT(s, r'([0-9]+)') +``` + + + +```sql +extract(s, '([0-9]+)') +``` + +
+ +```sql +REGEXP_REPLACE(s, r'[0-9]+', 'X') +``` + + + +```sql +replaceRegexpAll(s, '[0-9]+', 'X') +``` + +
+ +### JSON functions {#json-functions} + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
BigQueryClickHouse
+ +```sql +JSON_EXTRACT(j, '$.field') +``` + + + +```sql +JSONExtract(j, 'field', 'String') -- typed extraction; pass the result type +``` + +
+ +```sql +JSON_EXTRACT_SCALAR(j, '$.field') +``` + + + +```sql +JSONExtractString(j, 'field') +``` + +
+ +ClickHouse offers typed extractors per result type: `JSONExtractString`, `JSONExtractInt`, `JSONExtractFloat`, `JSONExtractBool`, `JSONExtractArrayRaw`, etc. See [JSON functions](/sql-reference/functions/json-functions). + +
+ +```sql +PARSE_JSON(s) +``` + + + +```sql +CAST(s AS JSON) -- or store as the native JSON column type +``` + +
diff --git a/docs/cloud/onboard/02_migrate/01_migration_guides/03_bigquery/index.md b/docs/cloud/onboard/02_migrate/01_migration_guides/03_bigquery/index.md index c56b71f9794..f97280910e3 100644 --- a/docs/cloud/onboard/02_migrate/01_migration_guides/03_bigquery/index.md +++ b/docs/cloud/onboard/02_migrate/01_migration_guides/03_bigquery/index.md @@ -15,3 +15,4 @@ In this section of the docs, learn more about the similarities and differences b | [BigQuery vs ClickHouse Cloud](/migrations/bigquery/biquery-vs-clickhouse-cloud) | The way resources are organized in ClickHouse Cloud is similar to BigQuery's resource hierarchy. We describe the specific differences in this article. | | [Migrating from BigQuery to ClickHouse Cloud](/migrations/bigquery/migrating-to-clickhouse-cloud) | Learn about why you might want to migrate from BigQuery to ClickHouse Cloud. | | [Loading Data](/migrations/bigquery/loading-data) | A guide showing you how to migrate data from BigQuery to ClickHouse. | +| [SQL translation reference](/migrations/bigquery/sql-translation-reference) | Construct-by-construct mapping from BigQuery GoogleSQL to ClickHouse SQL. | diff --git a/docs/cloud/onboard/02_migrate/01_migration_guides/04_snowflake/03_sql_translation_reference.md b/docs/cloud/onboard/02_migrate/01_migration_guides/04_snowflake/03_sql_translation_reference.md index 87bf6acaffa..375805700a1 100644 --- a/docs/cloud/onboard/02_migrate/01_migration_guides/04_snowflake/03_sql_translation_reference.md +++ b/docs/cloud/onboard/02_migrate/01_migration_guides/04_snowflake/03_sql_translation_reference.md @@ -1,114 +1,4826 @@ --- -sidebar_label: 'SQL translation reference' +title: 'Snowflake SQL translation reference' slug: /migrations/snowflake-translation-reference -description: 'SQL translation reference' -keywords: ['Snowflake'] -title: 'Migrating from Snowflake to ClickHouse' +description: 'Construct-by-construct mapping from Snowflake SQL to ClickHouse SQL' +keywords: ['Snowflake', 'migration', 'SQL', 'translation', 'reference'] +sidebar_label: 'SQL translation reference' show_related_blogs: true -doc_type: 'guide' +doc_type: 'reference' --- +This document details the similarities and differences in SQL syntax between Snowflake and ClickHouse. ## Data types {#data-types} -### Numerics {#numerics} - -Users moving data between ClickHouse and Snowflake will immediately notice that -ClickHouse offers more granular precision concerning declaring numerics. For example, -Snowflake offers the type Number for numerics. This requires the user to specify a -precision (total number of digits) and scale (digits to the right of the decimal place) -up to a total of 38. Integer declarations are synonymous with Number, and simply -define a fixed precision and scale where the range is the same. This convenience -is possible as modifying the precision (scale is 0 for integers) doesn't impact the -size of data on disk in Snowflake - the minimal required bytes are used for a -numeric range at write time at a micro partition level. The scale does, however, -impact storage space and is offset with compression. A `Float64` type offers a -wider range of values with a loss of precision. - -Contrast this with ClickHouse, which offers multiple signed and unsigned -precision for floats and integers. With these, you can be explicit about -the precision required for integers to optimize storage and memory overhead. A -Decimal type, equivalent to Snowflake’s Number type, also offers twice the -precision and scale at 76 digits. In addition to a similar `Float64` value, -ClickHouse also provides a `Float32` for when precision is less critical and -compression paramount. - -### Strings {#strings} - -ClickHouse and Snowflake take contrasting approaches to the storage of string -data. The `VARCHAR` in Snowflake holds Unicode characters in UTF-8, allowing the -user to specify a maximum length. This length has no impact on storage or -performance, with the minimum number of bytes always used to store a string, and -rather provides only constraints useful for downstream tooling. Other types, such -as `Text` and `NChar`, are simply aliases for this type. ClickHouse conversely -stores all [string data as raw bytes](/sql-reference/data-types/string) with a `String` -type (no length specification required), deferring encoding to the user, with -[query time functions](/sql-reference/functions/string-functions#lengthUTF8) -available for different encodings. We refer the reader to ["Opaque data argument"](https://utf8everywhere.org/#cookie) -for the motivation as to why. The ClickHouse `String` is thus more comparable -to the Snowflake Binary type in its implementation. Both [Snowflake](https://docs.snowflake.com/en/sql-reference/collation) -and [ClickHouse](/sql-reference/statements/select/order-by#collation-support) -support “collation”, allowing users to override how strings are sorted and compared. - -### Semi-structured types {#semi-structured-data} - -Snowflake supports the `VARIANT`, `OBJECT` and `ARRAY` types for semi-structured -data. - -ClickHouse offers the equivalent [`Variant`](/sql-reference/data-types/variant), -`Object` (now deprecated in favor of the native `JSON` type) and [`Array`](/sql-reference/data-types/array) -types. Additionally, ClickHouse has the [`JSON`](/sql-reference/data-types/newjson) -type which replaces the now deprecated `Object('json')` type and is particularly -performant and storage efficient in [comparison to other native JSON types](https://jsonbench.com/). - -ClickHouse also supports named [`Tuple`s](/sql-reference/data-types/tuple) and arrays of Tuples -via the [`Nested`](/sql-reference/data-types/nested-data-structures/nested) type, -allowing users to explicitly map nested structures. This allows codecs and type -optimizations to be applied throughout the hierarchy, unlike Snowflake, which -requires the user to use the `OBJECT`, `VARIANT`, and `ARRAY` types for the outer -object and doesn't allow [explicit internal typing](https://docs.snowflake.com/en/sql-reference/data-types-semistructured#characteristics-of-an-object). -This internal typing also simplifies queries on nested numerics in ClickHouse, -which don't need to be cast and can be used in index definitions. - -In ClickHouse, codecs and optimized types can also be applied to substructures. -This provides an added benefit that compression with nested structures remains -excellent, and comparable, to flattened data. In contrast, as a result of the -inability to apply specific types to substructures, Snowflake recommends [flattening -data to achieve optimal compression](https://docs.snowflake.com/en/user-guide/semistructured-considerations#storing-semi-structured-data-in-a-variant-column-vs-flattening-the-nested-structure). -Snowflake also [imposes size restrictions](https://docs.snowflake.com/en/user-guide/semistructured-considerations#data-size-limitations) -for these data types. - -### Type reference {#type-reference} - -| Snowflake | ClickHouse | Note | -|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [`NUMBER`](https://docs.snowflake.com/en/sql-reference/data-types-numeric) | [`Decimal`](/sql-reference/data-types/decimal) | ClickHouse supports twice the precision and scale than Snowflake - 76 digits vs. 38. | -| [`FLOAT`, `FLOAT4`, `FLOAT8`](https://docs.snowflake.com/en/sql-reference/data-types-numeric#data-types-for-floating-point-numbers) | [`Float32`, `Float64`](/sql-reference/data-types/float) | All floats in Snowflake are 64 bit. | -| [`VARCHAR`](https://docs.snowflake.com/en/sql-reference/data-types-text#varchar) | [`String`](/sql-reference/data-types/string) | | -| [`BINARY`](https://docs.snowflake.com/en/sql-reference/data-types-text#binary) | [`String`](/sql-reference/data-types/string) | | -| [`BOOLEAN`](https://docs.snowflake.com/en/sql-reference/data-types-logical) | [`Bool`](/sql-reference/data-types/boolean) | | -| [`DATE`](https://docs.snowflake.com/en/sql-reference/data-types-datetime#date) | [`Date`](/sql-reference/data-types/date), [`Date32`](/sql-reference/data-types/date32) | `DATE` in Snowflake offers a wider date range than ClickHouse e.g. min for `Date32` is `1900-01-01` and `Date` `1970-01-01`. `Date` in ClickHouse provides more cost efficient (two byte) storage. | -| [`TIME(N)`](https://docs.snowflake.com/en/sql-reference/data-types-datetime#time) | No direct equivalent but can be represented by [`DateTime`](/sql-reference/data-types/datetime) and [`DateTime64(N)`](/sql-reference/data-types/datetime64). | `DateTime64` uses the same concepts of precision. | -| [`TIMESTAMP`](https://docs.snowflake.com/en/sql-reference/data-types-datetime#timestamp) - [`TIMESTAMP_LTZ`](https://docs.snowflake.com/en/sql-reference/data-types-datetime#timestamp-ltz-timestamp-ntz-timestamp-tz), [`TIMESTAMP_NTZ`](https://docs.snowflake.com/en/sql-reference/data-types-datetime#timestamp-ltz-timestamp-ntz-timestamp-tz), [`TIMESTAMP_TZ`](https://docs.snowflake.com/en/sql-reference/data-types-datetime#timestamp-ltz-timestamp-ntz-timestamp-tz) | [`DateTime`](/sql-reference/data-types/datetime) and [`DateTime64`](/sql-reference/data-types/datetime64) | `DateTime` and `DateTime64` can optionally have a TZ parameter defined for the column. If not present, the server's timezone is used. Additionally a `--use_client_time_zone` parameter is available for the client. | -| [`VARIANT`](https://docs.snowflake.com/en/sql-reference/data-types-semistructured#variant) | [`JSON`, `Tuple`, `Nested`](/interfaces/formats) | `JSON` type is experimental in ClickHouse. This type infers the column types at insert time. `Tuple`, `Nested` and `Array` can also be used to build explicitly type structures as an alternative. | -| [`OBJECT`](https://docs.snowflake.com/en/sql-reference/data-types-semistructured#object) | [`Tuple`, `Map`, `JSON`](/interfaces/formats) | Both `OBJECT` and `Map` are analogous to `JSON` type in ClickHouse where the keys are a `String`. ClickHouse requires the value to be consistent and strongly typed whereas Snowflake uses `VARIANT`. This means the values of different keys can be a different type. If this is required in ClickHouse, explicitly define the hierarchy using `Tuple` or rely on `JSON` type. | -| [`ARRAY`](https://docs.snowflake.com/en/sql-reference/data-types-semistructured#array) | [`Array`](/sql-reference/data-types/array), [`Nested`](/sql-reference/data-types/nested-data-structures/nested) | `ARRAY` in Snowflake uses `VARIANT` for the elements - a super type. Conversely these are strongly typed in ClickHouse. | -| [`GEOGRAPHY`](https://docs.snowflake.com/en/sql-reference/data-types-geospatial#geography-data-type) | [`Point`, `Ring`, `Polygon`, `MultiPolygon`](/sql-reference/data-types/geo) | Snowflake imposes a coordinate system (WGS 84) while ClickHouse applies at query time. | -| [`GEOMETRY`](https://docs.snowflake.com/en/sql-reference/data-types-geospatial#geometry-data-type) | [`Point`, `Ring`, `Polygon`, `MultiPolygon`](/sql-reference/data-types/geo) | | | - -| ClickHouse Type | Description | -|-------------------|-----------------------------------------------------------------------------------------------------| -| `IPv4` and `IPv6` | IP-specific types, potentially allowing more efficient storage than Snowflake. | -| `FixedString` | Allows a fixed length of bytes to be used, which is useful for hashes. | -| `LowCardinality` | Allows any type to be dictionary encoded. Useful for when the cardinality is expected to be < 100k. | -| `Enum` | Allows efficient encoding of named values in either 8 or 16-bit ranges. | -| `UUID` | For efficient storage of UUIDs. | -| `Array(Float32)` | Vectors can be represented as an Array of Float32 with supported distance functions. | - -Finally, ClickHouse offers the unique ability to store the intermediate -[state of aggregate functions](/sql-reference/data-types/aggregatefunction). This -state is implementation-specific, but allows the result of an aggregation to be -stored and later queried (with corresponding merge functions). Typically, this -feature is used via a materialized view and, as demonstrated below, offers the -ability to improve performance of specific queries with minimal storage cost by -storing the incremental result of queries over inserted data (more details here). +ClickHouse offers more granular precision than Snowflake for numerics. Snowflake +exposes a single `NUMBER(P, S)` type for fixed-point values (its integer types +are aliases for `NUMBER` with scale 0). ClickHouse instead provides distinct +integer widths plus decimal and float types so storage and memory can be tuned +to the actual range of the data. `Decimal` in ClickHouse goes to 76 digits +versus Snowflake's 38, and `Float32` is available alongside `Float64` when +precision is less critical than compression. + +Strings work differently in the two engines. Snowflake's `VARCHAR(n)` stores +UTF-8 with an optional length limit (storage isn't affected by the limit); +ClickHouse `String` stores raw bytes with no length specification, deferring +encoding to the application. UTF-8-aware variants (`lengthUTF8`, +`upperUTF8`, etc.) exist for the cases where byte semantics aren't what you +want. + +For semi-structured data, Snowflake's `VARIANT`/`OBJECT`/`ARRAY` map to +ClickHouse's [`JSON`](/sql-reference/data-types/newjson), +[`Tuple`](/sql-reference/data-types/tuple), +[`Nested`](/sql-reference/data-types/nested-data-structures/nested), and +[`Array`](/sql-reference/data-types/array). The native `JSON` type is the +default starting point — it's storage-efficient and lets you keep +schema flexibility while still applying codecs to extracted hot keys. When the +shape is known, named `Tuple`s and `Nested` let you apply typed codecs and +indexes throughout the hierarchy, which Snowflake's untyped `VARIANT` can't. + +:::tip +When several ClickHouse types map to a single Snowflake type, pick the smallest +that fits and consider [appropriate codecs](/sql-reference/statements/create/table#column_compression_codec) +for further compression. +::: + +| Snowflake | ClickHouse | Notes | +|-----------|------------|-------| +| [`NUMBER(P, S)`, `DECIMAL`, `NUMERIC`](https://docs.snowflake.com/en/sql-reference/data-types-numeric) | [`Decimal(P, S)`](/sql-reference/data-types/decimal) | Sized variants `Decimal32(S)` / `Decimal64(S)` / `Decimal128(S)` / `Decimal256(S)` are also available; ClickHouse supports up to 76 digits versus Snowflake's 38 | +| [`INT`, `INTEGER`, `BIGINT`, `SMALLINT`, `TINYINT`, `BYTEINT`](https://docs.snowflake.com/en/sql-reference/data-types-numeric) | [`UInt8` … `UInt256` / `Int8` … `Int256`](/sql-reference/data-types/int-uint) | All Snowflake integer types are aliases for `NUMBER(38, 0)`; pick the smallest signed or unsigned variant that fits the value range | +| [`FLOAT`, `FLOAT4`, `FLOAT8`, `DOUBLE`, `REAL`](https://docs.snowflake.com/en/sql-reference/data-types-numeric#data-types-for-floating-point-numbers) | [`Float32`](/sql-reference/data-types/float)  or  [`Float64`](/sql-reference/data-types/float) | All Snowflake floats are 64-bit; `Float32` is available in ClickHouse when precision is less critical and compression matters | +| [`VARCHAR(n)`, `CHAR`, `STRING`, `TEXT`](https://docs.snowflake.com/en/sql-reference/data-types-text#varchar) | [`String`](/sql-reference/data-types/string) | Optionally wrap in [`LowCardinality(String)`](/sql-reference/data-types/lowcardinality) for columns with few distinct values (enums, status codes, country codes). String functions are byte-based; the [String family](/sql-reference/functions/string-functions) has `UTF8` variants where relevant | +| [`BINARY`, `VARBINARY`](https://docs.snowflake.com/en/sql-reference/data-types-text#binary) | [`String`](/sql-reference/data-types/string)  or  [`FixedString(N)`](/sql-reference/data-types/fixedstring) | | +| [`BOOLEAN`](https://docs.snowflake.com/en/sql-reference/data-types-logical) | [`Bool`](/sql-reference/data-types/boolean) | | +| [`DATE`](https://docs.snowflake.com/en/sql-reference/data-types-datetime#date) | [`Date`](/sql-reference/data-types/date)  or  [`Date32`](/sql-reference/data-types/date32) | `Date` (2-byte, 1970-2149) for typical analytical data; `Date32` (4-byte, 1900-2299) for Snowflake's wider range | +| [`TIME(N)`](https://docs.snowflake.com/en/sql-reference/data-types-datetime#time) | No equivalent | Carry a [`DateTime64(N)`](/sql-reference/data-types/datetime64) and extract via [`formatDateTime`](/sql-reference/functions/date-time-functions#formatDateTime) | +| [`TIMESTAMP_NTZ`](https://docs.snowflake.com/en/sql-reference/data-types-datetime#timestamp-ltz-timestamp-ntz-timestamp-tz) | [`DateTime`](/sql-reference/data-types/datetime)  or  [`DateTime64(p)`](/sql-reference/data-types/datetime64) | Use `DateTime64(p)` for sub-second precision | +| [`TIMESTAMP_LTZ`, `TIMESTAMP_TZ`](https://docs.snowflake.com/en/sql-reference/data-types-datetime#timestamp-ltz-timestamp-ntz-timestamp-tz) | [`DateTime64(p, 'UTC')`](/sql-reference/data-types/datetime64) | Normalize to UTC at load time via `CONVERT_TIMEZONE('UTC', col)` | +| [`VARIANT`](https://docs.snowflake.com/en/sql-reference/data-types-semistructured#variant) | [`JSON`](/sql-reference/data-types/newjson)  or  [`Variant`](/sql-reference/data-types/variant) | `JSON` is preferred; extract hot keys to typed columns when the query pattern warrants | +| [`OBJECT`](https://docs.snowflake.com/en/sql-reference/data-types-semistructured#object) | [`JSON`](/sql-reference/data-types/newjson), [`Tuple`](/sql-reference/data-types/tuple),  or  [`Map(K, V)`](/sql-reference/data-types/map) | Use `Tuple` when the keys are known and stable, `Map(K, V)` when keys are dynamic but values are uniformly typed, `JSON` for fully dynamic shapes | +| [`ARRAY`](https://docs.snowflake.com/en/sql-reference/data-types-semistructured#array) | [`Array(T)`](/sql-reference/data-types/array) | Snowflake `ARRAY` elements are untyped `VARIANT`; ClickHouse `Array(T)` requires a single element type. Use `Array(JSON)` if elements really are heterogeneous | +| [`GEOGRAPHY`](https://docs.snowflake.com/en/sql-reference/data-types-geospatial#geography-data-type), [`GEOMETRY`](https://docs.snowflake.com/en/sql-reference/data-types-geospatial#geometry-data-type) | [Geo data types](/sql-reference/data-types/geo) | Snowflake imposes the `WGS 84` coordinate system; ClickHouse applies coordinate systems at query time | +| [`VECTOR(FLOAT, n)`](https://docs.snowflake.com/en/sql-reference/data-types-vector) | [`Array(Float32)`](/sql-reference/data-types/array) | Pair with a [vector similarity index](/engines/table-engines/mergetree-family/annindexes) for approximate-nearest-neighbor search | + +ClickHouse also offers types that have no Snowflake equivalent and are worth +considering during schema design: + +| ClickHouse type | Description | +|-----------------|-------------| +| [`IPv4`, `IPv6`](/sql-reference/data-types/ipv4) | IP-specific types, more storage-efficient than `String` | +| [`FixedString(N)`](/sql-reference/data-types/fixedstring) | Fixed byte width; useful for hashes and fixed-format codes | +| [`LowCardinality(T)`](/sql-reference/data-types/lowcardinality) | Dictionary encoding for low-cardinality columns (< ~100k distinct values) | +| [`Enum8`, `Enum16`](/sql-reference/data-types/enum) | Efficient encoding for a small fixed set of named values | +| [`UUID`](/sql-reference/data-types/uuid) | Native UUID storage | +| [`AggregateFunction(name, T)`](/sql-reference/data-types/aggregatefunction) | Intermediate aggregation state — pairs with `AggregatingMergeTree` for incremental materialised views | + +## DDL statements {#ddl-statements} + +### Schemas and databases {#ddl-schemas} + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SnowflakeClickHouse
+ +```sql +CREATE SCHEMA mydb.myschema +``` + + + +```sql +CREATE DATABASE mydb +``` + +
+ +Snowflake databases and schemas are a two-level namespace; ClickHouse exposes a single [database](/sql-reference/statements/create/database) level. Map each Snowflake `DATABASE.SCHEMA` to a ClickHouse database. + +
+ +```sql +CREATE SCHEMA IF NOT EXISTS mydb.myschema + WITH MANAGED ACCESS + COMMENT = 'sales' +``` + + + +```sql +CREATE DATABASE IF NOT EXISTS mydb +COMMENT 'sales' +``` + +
+ +`MANAGED ACCESS` and other Snowflake-specific schema options have no direct equivalent; access control in ClickHouse is configured via [`GRANT`](/sql-reference/statements/grant) on databases, tables, and rows. + +
+ +```sql +ALTER SCHEMA mydb.myschema SET COMMENT = 'sales' +``` + + + +```sql +ALTER DATABASE mydb MODIFY COMMENT 'sales' +``` + +
+ +```sql +DROP SCHEMA mydb.myschema +``` + + + +```sql +DROP DATABASE mydb +``` + +
+ +Add `IF EXISTS` for an idempotent drop in either engine. + +
+ +```sql +DROP SCHEMA mydb.myschema CASCADE +``` + + + +```sql +DROP DATABASE mydb +``` + +
+ +ClickHouse drops tables in the database unconditionally; there is no `CASCADE` keyword. + +
+ +### Tables {#ddl-tables} + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SnowflakeClickHouse
+ +```sql +CREATE TABLE myschema.t ( + id NUMBER(38, 0), + name VARCHAR +) +``` + + + +```sql +CREATE TABLE mydb.t ( + id Int64, + name String +) +ENGINE = MergeTree +ORDER BY id +``` + +
+ +An engine and an `ORDER BY` are required for `MergeTree`-family tables; pick the columns that match the query access pattern. See [Sparse primary indexes](/guides/best-practices/sparse-primary-indexes). + +
+ +```sql +CREATE TABLE IF NOT EXISTS myschema.t (id NUMBER) +``` + + + +```sql +CREATE TABLE IF NOT EXISTS mydb.t (id Int64) +ENGINE = MergeTree +ORDER BY id +``` + +
+ +```sql +CREATE OR REPLACE TABLE myschema.t (id NUMBER) +``` + + + +```sql +CREATE OR REPLACE TABLE mydb.t (id Int64) +ENGINE = MergeTree +ORDER BY id +``` + +
+ +```sql +CREATE TABLE myschema.t ( + id NUMBER, + l_shipdate DATE +) +CLUSTER BY (id, l_shipdate) +``` + + + +```sql +CREATE TABLE mydb.t ( + id Int64, + l_shipdate Date +) +ENGINE = MergeTree +PARTITION BY toYYYYMM(l_shipdate) +ORDER BY (id, l_shipdate) +``` + +
+ +Snowflake `CLUSTER BY` colocates rows for pruning; in ClickHouse the equivalent is the table's `ORDER BY` key, which controls on-disk ordering and the primary index. Add `PARTITION BY` (e.g. `toYYYYMM(date_col)`) when you also want coarse time-window pruning. + +
+ +```sql +CREATE TABLE myschema.t LIKE myschema.source +``` + + + +```sql +CREATE TABLE mydb.t AS mydb.source +``` + +
+ +Both copy the schema only; ClickHouse also copies engine and `ORDER BY`. + +
+ +```sql +CREATE TABLE myschema.t AS +SELECT * FROM myschema.source +``` + + + +```sql +CREATE TABLE mydb.t +ENGINE = MergeTree +ORDER BY id +AS SELECT * FROM mydb.source +``` + +
+ +Engine and `ORDER BY` are required for the new table. + +
+ +```sql +CREATE TEMPORARY TABLE t AS SELECT 1 AS x +``` + + + +```sql +CREATE TEMPORARY TABLE t (x Int64); +INSERT INTO t VALUES (1); +``` + +
+ +ClickHouse temporary tables are session-scoped. The engine clause is optional and defaults to [`Memory`](/engines/table-engines/special/memory). + +
+ +```sql +CREATE TRANSIENT TABLE myschema.t (id NUMBER) +``` + + + +```sql +CREATE TABLE mydb.t (id Int64) +ENGINE = MergeTree +ORDER BY id +``` + +
+ +`TRANSIENT` is a Snowflake storage-cost optimisation (no Fail-safe). ClickHouse tables don't carry equivalent retention semantics — create a regular table. + +
+ +```sql +CREATE EXTERNAL TABLE myschema.t (...) +LOCATION = @my_stage +FILE_FORMAT = (TYPE = PARQUET) +``` + + + +```sql +CREATE TABLE mydb.t (...) +ENGINE = S3('https://.../*.parquet', 'Parquet') +``` + +
+ +ClickHouse exposes external storage through table engines such as [`S3`](/engines/table-engines/integrations/s3), [`URL`](/engines/table-engines/special/url), and [`HDFS`](/engines/table-engines/integrations/hdfs). + +
+ +```sql +ALTER TABLE myschema.t ADD COLUMN tag VARCHAR +``` + + + +```sql +ALTER TABLE mydb.t ADD COLUMN tag String +``` + +
+ +```sql +ALTER TABLE myschema.t DROP COLUMN tag +``` + + + +```sql +ALTER TABLE mydb.t DROP COLUMN tag +``` + +
+ +```sql +ALTER TABLE myschema.t RENAME COLUMN old TO new +``` + + + +```sql +ALTER TABLE mydb.t RENAME COLUMN old TO new +``` + +
+ +```sql +ALTER TABLE myschema.t ALTER COLUMN amount SET DATA TYPE FLOAT +``` + + + +```sql +ALTER TABLE mydb.t MODIFY COLUMN amount Float64 +``` + +
+ +```sql +ALTER TABLE myschema.t SET COMMENT = '...' +``` + + + +```sql +ALTER TABLE mydb.t MODIFY COMMENT '...' +``` + +
+ +```sql +ALTER TABLE myschema.t RENAME TO t2 +``` + + + +```sql +RENAME TABLE mydb.t TO mydb.t2 +``` + +
+ +```sql +DROP TABLE myschema.t +``` + + + +```sql +DROP TABLE mydb.t +``` + +
+ +```sql +TRUNCATE TABLE myschema.t +``` + + + +```sql +TRUNCATE TABLE mydb.t +``` + +
+ +### Views, materialized views, and dynamic tables {#ddl-views} + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SnowflakeClickHouse
+ +```sql +CREATE VIEW myschema.v AS +SELECT id, name FROM myschema.t +``` + + + +```sql +CREATE VIEW mydb.v AS +SELECT id, name FROM mydb.t +``` + +
+ +Standard logical views; no storage in either engine. + +
+ +```sql +CREATE OR REPLACE VIEW myschema.v AS SELECT ... +``` + + + +```sql +CREATE OR REPLACE VIEW mydb.v AS SELECT ... +``` + +
+ +```sql +DROP VIEW myschema.v +``` + + + +```sql +DROP VIEW mydb.v +``` + +
+ +```sql +CREATE MATERIALIZED VIEW myschema.mv AS +SELECT status, count(*) AS c +FROM myschema.t +GROUP BY status +``` + + + +```sql +CREATE MATERIALIZED VIEW mydb.mv +ENGINE = AggregatingMergeTree +ORDER BY status +AS SELECT status, countState() AS c +FROM mydb.t +GROUP BY status +``` + +
+ +ClickHouse [materialized views](/sql-reference/statements/create/view#materialized-view) are incremental insert-time triggers that write into a target table. For aggregate MVs, use [`AggregatingMergeTree`](/engines/table-engines/mergetree-family/aggregatingmergetree) and the `-State` aggregate-function combinator. Query the MV with `countMerge(c)` to finalize. Snowflake materialized views have stricter restrictions (single-table, limited functions); ClickHouse MVs have no such restrictions but require the target-table pattern for non-trivial aggregations. + +
+ +```sql +CREATE DYNAMIC TABLE myschema.dt +TARGET_LAG = '1 hour' +WAREHOUSE = compute_wh AS +SELECT date_trunc('day', ts) AS d, count(*) AS c +FROM myschema.t +GROUP BY d +``` + + + +```sql +CREATE MATERIALIZED VIEW mydb.dt +REFRESH EVERY 1 HOUR +ENGINE = MergeTree +ORDER BY d +AS SELECT toStartOfDay(ts) AS d, count() AS c +FROM mydb.t +GROUP BY d +``` + +
+ +Snowflake [Dynamic Tables](https://docs.snowflake.com/en/user-guide/dynamic-tables-about) refresh periodically based on `TARGET_LAG`. The closest ClickHouse equivalent is a [refreshable materialized view](/sql-reference/statements/create/view#refreshable-materialized-view) with `REFRESH EVERY`. For inserts that should aggregate incrementally (rather than fully refresh), prefer the standard `AggregatingMergeTree` + MV pattern above. + +
+ +### Streams, tasks, sequences, and stages {#ddl-streams-tasks} + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SnowflakeClickHouse
+ +```sql +CREATE STREAM myschema.s ON TABLE myschema.t +``` + + + +No equivalent + +
+ +Snowflake [Streams](https://docs.snowflake.com/en/user-guide/streams-intro) track CDC changes on a source table. ClickHouse has no built-in CDC primitive; the common replacements are (a) the [`ReplacingMergeTree`](/engines/table-engines/mergetree-family/replacingmergetree) family with a version column to model upserts, (b) an external CDC pipeline (Debezium, ClickPipes) feeding inserts, or (c) ingesting only append-only event streams and using the natural insert order. + +
+ +```sql +CREATE TASK myschema.tk +SCHEDULE = '1 HOUR' +AS INSERT INTO myschema.t SELECT ... +``` + + + +No equivalent + +
+ +ClickHouse has no in-database task scheduler; orchestrate periodic SQL from a client (cron, Airflow, dbt, etc.). For periodic materializations, [refreshable materialized views](/sql-reference/statements/create/view#refreshable-materialized-view) cover many task use cases natively. + +
+ +```sql +CREATE SEQUENCE myschema.seq START = 1 INCREMENT = 1 +``` + + + +No equivalent + +
+ +ClickHouse has no SEQUENCE object. Use [`generateUUIDv4()`](/sql-reference/functions/uuid-functions#generateUUIDv4) for unique identifiers, [`generateSnowflakeID()`](/sql-reference/functions/uuid-functions#generateSnowflakeID) for monotonic 64-bit IDs, or maintain a counter table with `INSERT … VALUES (max+1, …)`. + +
+ +```sql +CREATE STAGE myschema.stg +URL = 's3://bucket/path/' +FILE_FORMAT = (TYPE = PARQUET) +``` + + + +```sql +-- Use the S3 table engine or s3() table function directly: +SELECT * FROM s3('https://bucket.s3.amazonaws.com/path/*.parquet', 'Parquet') +``` + +
+ +ClickHouse references external storage directly via the [`S3`](/engines/table-engines/integrations/s3), [`URL`](/engines/table-engines/special/url), and [`HDFS`](/engines/table-engines/integrations/hdfs) engines and table functions. No persistent stage object is created. + +
+ +### Indexes, functions, and procedures {#ddl-functions-procedures} + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SnowflakeClickHouse
+ +```sql +CREATE SEARCH OPTIMIZATION ON myschema.t +``` + + + +```sql +ALTER TABLE mydb.t +ADD INDEX idx name + TYPE text(tokenizer = splitByNonAlpha) + GRANULARITY 1 +``` + +
+ +ClickHouse [full-text indexes](/engines/table-engines/mergetree-family/textindexes) accelerate `LIKE` / token-search predicates on specific columns. Unlike Snowflake's table-level Search Optimization service, ClickHouse text indexes are defined per column and per tokenizer. + +
+ +```sql +-- Snowflake VECTOR-aware lookups use VECTOR_COSINE_SIMILARITY() +-- without a separate index object. +``` + + + +```sql +ALTER TABLE mydb.t +ADD INDEX idx embedding + TYPE vector_similarity('hnsw', 'cosineDistance', 1536) + GRANULARITY 1 +``` + +
+ +The third argument is the vector dimension and is required; it must match the length of every value stored in the indexed `Array(Float*)` column. See [Approximate-nearest-neighbor indexes](/engines/table-engines/mergetree-family/annindexes). + +
+ +```sql +CREATE FUNCTION myschema.add_one(x NUMBER) +RETURNS NUMBER AS $$ x + 1 $$ +``` + + + +```sql +CREATE FUNCTION add_one AS (x) -> x + 1 +``` + +
+ +ClickHouse [UDFs](/sql-reference/statements/create/function) are expression-only. For complex logic, use the SQL-defined-function or executable-UDF patterns. + +
+ +```sql +CREATE FUNCTION myschema.f(x NUMBER) +RETURNS NUMBER LANGUAGE javascript AS $$ return x+1 $$ +``` + + + +No equivalent + +
+ +ClickHouse has no JavaScript or Python UDFs in standard SQL; use executable UDFs at the server level. + +
+ +```sql +DROP FUNCTION myschema.add_one +``` + + + +```sql +DROP FUNCTION add_one +``` + +
+ +```sql +CREATE PROCEDURE myschema.p(x NUMBER) RETURNS NUMBER +LANGUAGE SQL AS $$ BEGIN ... END $$ +``` + + + +No equivalent + +
+ +ClickHouse has no stored procedures; orchestrate from a client (Python, Go, etc.) or compose into a SQL function. + +
+ +## DML {#dml} + +### Insert operations {#dml-inserts} + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SnowflakeClickHouse
+ +```sql +INSERT INTO myschema.t VALUES (1, 'a'), (2, 'b') +``` + + + +```sql +INSERT INTO mydb.t VALUES (1, 'a'), (2, 'b') +``` + +
+ +```sql +INSERT INTO myschema.t (id, name) VALUES (1, 'a') +``` + + + +```sql +INSERT INTO mydb.t (id, name) VALUES (1, 'a') +``` + +
+ +```sql +INSERT INTO myschema.t +SELECT id, name FROM myschema.source +``` + + + +```sql +INSERT INTO mydb.t +SELECT id, name FROM mydb.source +``` + +
+ +```sql +INSERT ALL + INTO t1 VALUES (1) + INTO t2 VALUES (2) +SELECT 1 +``` + + + +No equivalent + +
+ +ClickHouse has no multi-table `INSERT ALL` / `INSERT FIRST`. Issue separate `INSERT … SELECT` statements, or use [materialized views](/sql-reference/statements/create/view#materialized-view) to fan a single insert out into multiple target tables. + +
+ +### Update operations {#dml-updates} + + ++++ + + + + + + + + + + + + + + + + + +
SnowflakeClickHouse
+ +```sql +UPDATE myschema.t SET name = 'x' WHERE id = 1 +``` + + + +```sql +ALTER TABLE mydb.t UPDATE name = 'x' WHERE id = 1 +``` + +
+ +ClickHouse mutates asynchronously by default. For synchronous, in-place updates use [lightweight updates](/sql-reference/statements/update): `UPDATE mydb.t SET name = 'x' WHERE id = 1`. + +
+ +### Delete operations {#dml-deletes} + + ++++ + + + + + + + + + + + + + + + + + + + + + + +
SnowflakeClickHouse
+ +```sql +DELETE FROM myschema.t WHERE id = 1 +``` + + + +```sql +DELETE FROM mydb.t WHERE id = 1 +``` + +
+ +ClickHouse's [lightweight delete](/sql-reference/statements/delete) marks rows for removal; physical removal happens at the next merge. For bulk historical cleanup prefer use of [TTL](/engines/table-engines/mergetree-family/mergetree#table_engine-mergetree-ttl). + +
+ +```sql +TRUNCATE TABLE myschema.t +``` + + + +```sql +TRUNCATE TABLE mydb.t +``` + +
+ +### Merge operations {#dml-merges} + + ++++ + + + + + + + + + + + + + + + + + +
SnowflakeClickHouse
+ +```sql +MERGE INTO myschema.t USING myschema.staging s + ON t.id = s.id +WHEN MATCHED THEN UPDATE SET ... +WHEN NOT MATCHED THEN INSERT ... +``` + + + +No equivalent + +
+ +ClickHouse has no equivalent `MERGE` statement. The idiomatic pattern is a [`ReplacingMergeTree`](/engines/table-engines/mergetree-family/replacingmergetree) keyed on the natural key plus a version column; an `INSERT` of newer rows "wins" at merge time. Use [`FINAL`](/sql-reference/statements/select/from#final-modifier) for read-time deduplication. + +
+ +## DCL {#dcl} + +### Grants {#dcl-grants} + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SnowflakeClickHouse
+ +```sql +GRANT SELECT ON TABLE myschema.t TO ROLE analyst +``` + + + +```sql +GRANT SELECT ON mydb.t TO analyst +``` + +
+ +Snowflake grants always flow through roles. ClickHouse can grant directly to users or to roles created with `CREATE ROLE`. + +
+ +```sql +GRANT ROLE analyst TO USER alice +``` + + + +```sql +GRANT analyst TO alice +``` + +
+ +```sql +REVOKE SELECT ON TABLE myschema.t FROM ROLE analyst +``` + + + +```sql +REVOKE SELECT ON mydb.t FROM analyst +``` + +
+ +### Roles {#dcl-roles} + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SnowflakeClickHouse
+ +```sql +CREATE USER alice PASSWORD = 'pw' +``` + + + +```sql +CREATE USER alice +IDENTIFIED WITH plaintext_password BY 'pw' +``` + +
+ +See [Access control and roles](/operations/access-rights) for the full ClickHouse authentication options (including SSL, LDAP, Kerberos, and double-SHA1 password hashing). + +
+ +```sql +CREATE ROLE analyst +``` + + + +```sql +CREATE ROLE analyst +``` + +
+ +```sql +DROP USER alice +``` + + + +```sql +DROP USER alice +``` + +
+ +## Syntax {#syntax} + +### Query syntax {#query-syntax} + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SnowflakeClickHouse
+ +```sql +SELECT id, name FROM myschema.t +``` + + + +```sql +SELECT id, name FROM mydb.t +``` + +
+ +```sql +SELECT * FROM myschema.t +``` + + + +```sql +SELECT * FROM mydb.t +``` + +
+ +```sql +SELECT * EXCLUDE (password) FROM myschema.t +``` + + + +```sql +SELECT * EXCEPT password FROM mydb.t +``` + +
+ +ClickHouse [`* EXCEPT`](/sql-reference/statements/select/#except-modifier) accepts an unparenthesised single column; multiple columns must be parenthesised: `SELECT * EXCEPT (a, b) FROM mydb.t`. + +
+ +```sql +SELECT * RENAME (name AS full_name) FROM myschema.t +``` + + + +```sql +SELECT * EXCEPT name, name AS full_name FROM mydb.t +``` + +
+ +Snowflake `* RENAME` renames a column in the projection. ClickHouse has no inline rename modifier; combine `* EXCEPT` with an aliased reintroduction. ClickHouse `* REPLACE` substitutes the value of an existing column but can't rename it. + +
+ +```sql +SELECT id FROM myschema.t +WHERE created_at > '2024-01-01' +``` + + + +```sql +SELECT id FROM mydb.t +WHERE created_at > '2024-01-01' +``` + +
+ +```sql +SELECT status, COUNT(*) FROM myschema.t +GROUP BY status +``` + + + +```sql +SELECT status, count() FROM mydb.t +GROUP BY status +``` + +
+ +```sql +SELECT status, COUNT(*) FROM myschema.t +GROUP BY ROLLUP (status, country) +``` + + + +```sql +SELECT status, country, count() FROM mydb.t +GROUP BY ROLLUP (status, country) +``` + +
+ +ClickHouse also supports `GROUP BY CUBE` and `GROUP BY GROUPING SETS`. + +
+ +```sql +SELECT status, COUNT(*) AS c FROM myschema.t +GROUP BY status +HAVING c > 10 +``` + + + +```sql +SELECT status, count() AS c FROM mydb.t +GROUP BY status +HAVING c > 10 +``` + +
+ +```sql +SELECT id FROM myschema.t ORDER BY id DESC NULLS LAST +LIMIT 100 +``` + + + +```sql +SELECT id FROM mydb.t ORDER BY id DESC NULLS LAST +LIMIT 100 +``` + +
+ +```sql +SELECT id FROM myschema.t LIMIT 100 OFFSET 50 +-- or +SELECT id FROM myschema.t +OFFSET 50 ROWS FETCH NEXT 100 ROWS ONLY +``` + + + +```sql +SELECT id FROM mydb.t LIMIT 100 OFFSET 50 +``` + +
+ +```sql +SELECT DISTINCT status FROM myschema.t +``` + + + +```sql +SELECT DISTINCT status FROM mydb.t +``` + +
+ +```sql +WITH recent AS ( + SELECT * FROM myschema.t + WHERE created_at > '2024-01-01' +) +SELECT id FROM recent +``` + + + +```sql +WITH recent AS ( + SELECT * FROM mydb.t + WHERE created_at > '2024-01-01' +) +SELECT id FROM recent +``` + +
+ +```sql +WITH RECURSIVE r(n) AS ( + SELECT 1 UNION ALL SELECT n+1 FROM r WHERE n < 5 +) +SELECT * FROM r +``` + + + +```sql +WITH RECURSIVE r AS ( + SELECT 1 AS n UNION ALL SELECT n+1 FROM r WHERE n < 5 +) +SELECT * FROM r +``` + +
+ +```sql +SELECT a.id +FROM myschema.t a +INNER JOIN myschema.u b ON a.id = b.id +``` + + + +```sql +SELECT a.id +FROM mydb.t AS a +INNER JOIN mydb.u AS b ON a.id = b.id +``` + +
+ +ClickHouse generally requires `AS` for table aliases. + +
+ +```sql +SELECT a.id +FROM myschema.t a +LEFT JOIN myschema.u b ON a.id = b.id +``` + + + +```sql +SELECT a.id +FROM mydb.t AS a +LEFT JOIN mydb.u AS b ON a.id = b.id +``` + +
+ +`FULL`, `RIGHT`, `LEFT ANY`, `LEFT SEMI`, `LEFT ANTI` all match between engines. + +
+ +```sql +SELECT a.id +FROM myschema.t a +FULL OUTER JOIN myschema.u b ON a.id = b.id +``` + + + +```sql +SELECT a.id +FROM mydb.t AS a +FULL JOIN mydb.u AS b ON a.id = b.id +``` + +
+ +ClickHouse spells it `FULL JOIN` (the `OUTER` keyword is optional and usually omitted). + +
+ +```sql +SELECT a.id +FROM myschema.t a +CROSS JOIN myschema.u b +``` + + + +```sql +SELECT a.id +FROM mydb.t AS a +CROSS JOIN mydb.u AS b +``` + +
+ +```sql +SELECT a.id +FROM myschema.t a +JOIN myschema.u b USING (id) +``` + + + +```sql +SELECT a.id +FROM mydb.t AS a +JOIN mydb.u AS b USING (id) +``` + +
+ +```sql +SELECT trades.symbol, trades.price, quotes.bid +FROM trades ASOF JOIN quotes + MATCH_CONDITION (trades.ts >= quotes.ts) + ON trades.symbol = quotes.symbol +``` + + + +```sql +SELECT trades.symbol, trades.price, quotes.bid +FROM trades ASOF JOIN quotes + ON trades.symbol = quotes.symbol AND trades.ts >= quotes.ts +``` + +
+ +ClickHouse supports [`ASOF JOIN`](/sql-reference/statements/select/join#asof-join-usage) natively. The inequality (e.g. `trades.ts >= quotes.ts`) goes into the `ON` clause as the last predicate, instead of into a separate `MATCH_CONDITION` clause. + +
+ +```sql +SELECT * FROM myschema.t, LATERAL flatten(input => t.tags) f +``` + + + +```sql +SELECT * FROM mydb.t +ARRAY JOIN tags AS f +``` + +
+ +Use [`ARRAY JOIN`](/sql-reference/statements/select/array-join) for tabular expansion of an array column, or [`arrayJoin`](/sql-reference/functions/array-join) in the `SELECT` list to expand an array expression. + +
+ +```sql +SELECT * FROM myschema.t +WHERE EXISTS ( + SELECT 1 FROM myschema.u WHERE u.id = t.id +) +``` + + + +```sql +SELECT * FROM mydb.t +WHERE id IN (SELECT id FROM mydb.u) +``` + +
+ +ClickHouse supports `EXISTS (subquery)` as well; `IN (subquery)` is the more idiomatic form for existence checks and is what you'll see in most ClickHouse query examples. + +
+ +```sql +SELECT a, b FROM myschema.t +UNION ALL +SELECT a, b FROM myschema.u +``` + + + +```sql +SELECT a, b FROM mydb.t +UNION ALL +SELECT a, b FROM mydb.u +``` + +
+ +ClickHouse rejects bare `UNION` by default (controlled by the `union_default_mode` setting); always write `UNION ALL` or `UNION DISTINCT` explicitly to avoid surprises. + +
+ +```sql +SELECT a FROM myschema.t +INTERSECT +SELECT a FROM myschema.u +``` + + + +```sql +SELECT a FROM mydb.t +INTERSECT DISTINCT +SELECT a FROM mydb.u +``` + +
+ +Snowflake `INTERSECT` deduplicates (it's `INTERSECT DISTINCT` semantically). ClickHouse bare `INTERSECT` preserves duplicates — use `INTERSECT DISTINCT` to keep Snowflake's behavior. + +
+ +```sql +SELECT a FROM myschema.t +MINUS +SELECT a FROM myschema.u +``` + + + +```sql +SELECT a FROM mydb.t +EXCEPT DISTINCT +SELECT a FROM mydb.u +``` + +
+ +Snowflake `MINUS` (or `EXCEPT`) deduplicates. ClickHouse bare `EXCEPT` preserves duplicates — use `EXCEPT DISTINCT` for parity. + +
+ +```sql +SELECT id FROM myschema.t +QUALIFY ROW_NUMBER() OVER ( + PARTITION BY user_id + ORDER BY created_at DESC +) = 1 +``` + + + +```sql +SELECT id FROM ( + SELECT *, + row_number() OVER ( + PARTITION BY user_id + ORDER BY created_at DESC + ) AS rn + FROM mydb.t +) +WHERE rn = 1 +``` + +
+ +ClickHouse has no `QUALIFY`; wrap the windowed query in a subquery. + +
+ +```sql +SELECT * FROM myschema.t TABLESAMPLE BERNOULLI (10) +``` + + + +```sql +SELECT * FROM mydb.t SAMPLE 0.1 +``` + +
+ +ClickHouse [`SAMPLE`](/sql-reference/statements/select/sample) requires the table to declare a `SAMPLE BY` clause. For ad-hoc sampling, use `WHERE cityHash64(some_col) % 100 < 10` or `LIMIT n BY 1`. + +
+ +```sql +SELECT * FROM myschema.t +AT (TIMESTAMP => '2024-03-15 00:00:00') +``` + + + +No equivalent + +
+ +ClickHouse doesn't provide row-level time travel. Patterns include [`ReplacingMergeTree`](/engines/table-engines/mergetree-family/replacingmergetree) with a version column, per-day backup tables, or use of [ClickHouse Cloud backup-and-restore](/cloud/manage/backups/configurable-backups) for coarse-grained snapshots. + +
+ +```sql +SELECT * FROM myschema.t +CHANGES (INFORMATION => APPEND_ONLY) +AT (TIMESTAMP => '2024-03-15') +``` + + + +No equivalent + +
+ +Snowflake `CHANGES` exposes the underlying Stream data without creating a Stream object. ClickHouse has no equivalent — see the [Streams row](#ddl-streams-tasks) for CDC alternatives. + +
+ +```sql +SELECT id, sum(amount) OVER w +FROM myschema.t +WINDOW w AS (PARTITION BY user_id ORDER BY created_at) +``` + + + +```sql +SELECT id, sum(amount) OVER w +FROM mydb.t +WINDOW w AS (PARTITION BY user_id ORDER BY created_at) +``` + +
+ +```sql +SELECT * FROM myschema.t +PIVOT (count(*) FOR status IN ('open' AS open, 'closed' AS closed)) +``` + + + +```sql +SELECT + countIf(status = 'open') AS open, + countIf(status = 'closed') AS closed +FROM mydb.t +``` + +
+ +ClickHouse has no `PIVOT`; use [`countIf` / `sumIf`](/sql-reference/aggregate-functions/combinators#-if) per output column. + +
+ +```sql +SELECT col, v +FROM (SELECT 1 AS a, 2 AS b) +UNPIVOT (v FOR col IN (a, b)) +``` + + + +```sql +SELECT p.1 AS col, p.2 AS v +FROM ( + SELECT [tuple('a', a), tuple('b', b)] AS pairs FROM mydb.t +) +ARRAY JOIN pairs AS p +``` + +
+ +ClickHouse has no `UNPIVOT`; emit `(name, value)` tuples and `ARRAY JOIN`. + +
+ +```sql +WITH RECURSIVE org AS ( + SELECT id, manager_id FROM employees WHERE id = 1 + UNION ALL + SELECT e.id, e.manager_id FROM employees e + JOIN org ON e.manager_id = org.id +) +SELECT * FROM org +-- (Snowflake also accepts CONNECT BY for hierarchical queries) +``` + + + +```sql +WITH RECURSIVE org AS ( + SELECT id, manager_id FROM employees WHERE id = 1 + UNION ALL + SELECT e.id, e.manager_id FROM employees e + JOIN org ON e.manager_id = org.id +) +SELECT * FROM org +``` + +
+ +ClickHouse has no `CONNECT BY` syntax; use a recursive CTE, which both engines support identically. + +
+ +```sql +SELECT * +FROM myschema.t +MATCH_RECOGNIZE ( + PARTITION BY user_id + ORDER BY ts + PATTERN (A B+) + DEFINE + A AS event = 'login', + B AS event = 'click' +) +``` + + + +No equivalent + +
+ +ClickHouse has no `MATCH_RECOGNIZE`. Rewrite with window functions plus `sequenceMatch` / `sequenceCount` for event-sequence detection, or with `arrayJoin` over a session-windowed array. + +
+ +### Procedural language (Snowflake Scripting) {#procedural-language} + +ClickHouse SQL isn't a procedural language. Variables, loops, statement-level +`IF` / `CASE`, cursors, and stored procedures have no first-class equivalents; +orchestrate multi-step logic from a client library (Python, Go, JavaScript, +etc.) or use [parameterized views](/sql-reference/statements/create/view#parameterized-view) +for templated queries. + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SnowflakeClickHouse
+ +```sql +DECLARE x NUMBER DEFAULT 0; +LET y := 5; +``` + + + +_client-side variable, or `SET param_x = 0` for [query parameters](/operations/server-configuration-parameters/settings#query-parameters)_ + +
+ +```sql +x := 5; +``` + + + +_client-side; `SET param_x = 5`_ + +
+ +```sql +BEGIN ... END +``` + + + +_multi-statement scripts are run by the client, not the server_ + +
+ +```sql +IF (cond) THEN ... ELSEIF ... ELSE ... END IF +``` + + + +_expression form `if(cond, a, b)` or `multiIf(c1, v1, c2, v2, default)`; for statement-level branching, branch in the client_ + +
+ +```sql +CASE WHEN cond THEN ... ELSE ... END CASE +``` + + + +_expression form `CASE WHEN cond THEN a ELSE b END`; no statement form_ + +
+ +```sql +WHILE cond LOOP ... END LOOP +``` + + + +_no equivalent; use a client-driven loop_ + +
+ +```sql +REPEAT ... UNTIL cond END REPEAT +``` + + + +_no equivalent_ + +
+ +```sql +FOR r IN cursor DO ... END FOR +``` + + + +_iterate over query results client-side_ + +
+ +`BREAK`, `CONTINUE` + + + +_no equivalent_ + +
+ +```sql +DECLARE c CURSOR FOR SELECT ... +OPEN c; FETCH c INTO ...; CLOSE c; +``` + + + +_no cursors; iterate result rows in the client_ + +
+ +```sql +RETURN 42 +``` + + + +_expression-only UDFs return their body; no procedural `RETURN`_ + +
+ +```sql +EXCEPTION + WHEN STATEMENT_ERROR THEN ... +``` + + + +_no exception handlers; catch errors in the client_ + +
+ +```sql +CALL myschema.p(1) +``` + + + +_no stored procedures_ + +
+ +```sql +EXECUTE IMMEDIATE 'SELECT 1' +``` + + + +_client-side prepared statements_ + +
+ +```sql +BEGIN TRANSACTION; ...; COMMIT +``` + + + +_multi-statement transactions are experimental_; see [transactions roadmap](https://github.com/ClickHouse/ClickHouse/issues/58392) + +
+ +### Operators {#operators} + + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SnowflakeClickHouseNotes
+ +`a + b`, `a - b`, `a * b`, `a / b` + + + +`a + b`, `a - b`, `a * b`, `a / b` + + + +`/` is real division in both engines. + +
+ +```sql +DIV0(a, b) +``` + + + +```sql +if(b = 0, 0, intDiv(a, b)) +``` + + + +Integer division returning `0` on zero divisor; ClickHouse `intDiv` errors on zero. + +
+ +```sql +MOD(a, b) +``` + + + +```sql +a % b -- or modulo(a, b) +``` + +
+ +`a = b`, `a != b`, `a <> b` + + + +`a = b`, `a != b`, `a <> b` + +
+ +`a < b`, `a <= b`, `a > b`, `a >= b` + + + +_same_ + +
+ +`a AND b`, `a OR b`, `NOT a` + + + +_same_ + +
+ +```sql +a IN (1, 2, 3) +``` + + + +```sql +a IN (1, 2, 3) +``` + +
+ +```sql +a NOT IN (1, 2, 3) +``` + + + +```sql +a NOT IN (1, 2, 3) +``` + +
+ +```sql +a BETWEEN x AND y +``` + + + +```sql +a BETWEEN x AND y +``` + + + +Inclusive of both bounds. + +
+ +```sql +a IS NULL -- or a IS NOT NULL +``` + + + +_same_ + +
+ +```sql +a LIKE 'pre%' +``` + + + +```sql +a LIKE 'pre%' +``` + +
+ +```sql +a ILIKE 'pre%' +``` + + + +```sql +a ILIKE 'pre%' +``` + + + +Case-insensitive `LIKE`. + +
+ +```sql +a RLIKE '^[a-z]+$' +``` + + + +```sql +match(a, '^[a-z]+$') +``` + + + +`RLIKE` is regex match; ClickHouse uses [`match`](/sql-reference/functions/string-search-functions#match). + +
+ +```sql +CONCAT(a, b) -- or a || b +``` + + + +```sql +concat(a, b) -- or a || b +``` + +
+ +### Conditional expressions {#conditional} + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SnowflakeClickHouse
+ +```sql +CASE WHEN c THEN a ELSE b END +``` + + + +```sql +CASE WHEN c THEN a ELSE b END +``` + +
+ +```sql +CASE x WHEN 1 THEN 'a' ELSE 'b' END +``` + + + +```sql +CASE x WHEN 1 THEN 'a' ELSE 'b' END +``` + +
+ +```sql +IFF(cond, a, b) +``` + + + +```sql +if(cond, a, b) +``` + +
+ +```sql +IFNULL(a, b) +``` + + + +```sql +ifNull(a, b) -- or coalesce(a, b) +``` + +
+ +```sql +NVL(a, b) +``` + + + +```sql +coalesce(a, b) +``` + +
+ +```sql +NVL2(a, b, c) +``` + + + +```sql +if(a IS NOT NULL, b, c) +``` + +
+ +```sql +NULLIF(a, b) +``` + + + +```sql +nullIf(a, b) +``` + +
+ +```sql +COALESCE(a, b, c) +``` + + + +```sql +coalesce(a, b, c) +``` + +
+ +```sql +GREATEST(a, b, c) +LEAST(a, b, c) +``` + + + +```sql +greatest(a, b, c) +least(a, b, c) +``` + +
+ +```sql +BOOLAND(a, b), BOOLOR(a, b), BOOLXOR(a, b), BOOLNOT(a) +``` + + + +```sql +a AND b, a OR b, xor(a, b), NOT a +``` + +
+ +### Conversion {#conversion} + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SnowflakeClickHouse
+ +```sql +CAST(x AS VARCHAR) -- or x::VARCHAR +``` + + + +```sql +CAST(x AS String) -- or toString(x) +``` + +
+ +```sql +CAST(x AS NUMBER) -- or x::NUMBER +``` + + + +```sql +CAST(x AS Int64) -- or toInt64(x) +``` + +
+ +```sql +CAST(x AS FLOAT) +``` + + + +```sql +CAST(x AS Float64) -- or toFloat64(x) +``` + +
+ +```sql +CAST(x AS DATE) +``` + + + +```sql +CAST(x AS Date) -- or toDate(x) +``` + +
+ +```sql +CAST(x AS TIMESTAMP_TZ) +``` + + + +```sql +CAST(x AS DateTime64(6, 'UTC')) +-- or parseDateTime64BestEffort(x, 6, 'UTC') +``` + +
+ +Snowflake `TIMESTAMP_TZ` carries an offset per value; normalize to UTC in ClickHouse via `CONVERT_TIMEZONE('UTC', col)` at the source. + +
+ +```sql +TRY_CAST(x AS NUMBER) +``` + + + +```sql +toInt64OrNull(x) +``` + +
+ +Each numeric type has `toTypeOrNull` / `toTypeOrZero` / `toTypeOrDefault` variants. For decimal targets use [`accurateCastOrNull(x, 'Decimal(P, S)')`](/sql-reference/functions/type-conversion-functions#accuratecastornullx-t). + +
+ +```sql +TO_NUMBER('3.14', 10, 2) +TO_DECIMAL('3.14', 10, 2) +``` + + + +```sql +toDecimal64('3.14', 2) +``` + +
+ +```sql +TO_VARCHAR(d, 'YYYY-MM-DD') +TO_CHAR(d, 'YYYY-MM-DD') +``` + + + +```sql +formatDateTime(d, '%F') +``` + +
+ +```sql +TO_VARIANT(x) +PARSE_JSON(s) +``` + + + +```sql +CAST(x AS JSON) -- or store as the native JSON column type +``` + +
+ +```sql +TO_OBJECT(v) +``` + + + +```sql +CAST(v AS JSON) -- or tuple(...) for known keys +``` + +
+ +```sql +TO_ARRAY(x) +``` + + + +```sql +array(x) -- or [x] +``` + +
+ +## Functions {#functions} + +### Array functions {#array-functions} + +Compared to Snowflake's roughly two dozen array functions, ClickHouse has more +than 80 [built-in array functions](/sql-reference/functions/array-functions). +The idiomatic pattern is to aggregate row values into an array with +[`groupArray`](/sql-reference/aggregate-functions/reference/grouparray), +transform with higher-order lambda functions +([`arrayMap`](/sql-reference/functions/array-functions#arrayMap), +[`arrayFilter`](/sql-reference/functions/array-functions#arrayFilter), +[`arrayZip`](/sql-reference/functions/array-functions#arrayZip)), and +optionally expand back to rows with +[`arrayJoin`](/sql-reference/functions/array-join). Because of this, many +transformations Snowflake expresses by round-tripping through +[`LATERAL FLATTEN`](https://docs.snowflake.com/en/sql-reference/functions/flatten) +collapse to a single function call in ClickHouse. + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SnowflakeClickHouse
+ +```sql +ARRAY_CONSTRUCT(1, 2, 3) +``` + + + +```sql +array(1, 2, 3) -- or [1, 2, 3] +``` + +
+ +```sql +ARRAY_CAT(a, b) +``` + + + +```sql +arrayConcat(a, b) +``` + +
+ +```sql +ARRAY_SIZE(tags) +``` + + + +```sql +length(tags) +``` + +
+ +```sql +ARRAY_APPEND(tags, 'x') +ARRAY_PREPEND(tags, 'x') +``` + + + +```sql +arrayPushBack(tags, 'x') +arrayPushFront(tags, 'x') +``` + +
+ +```sql +ARRAY_INSERT(tags, 2, 'x') +``` + + + +```sql +arrayInsert(tags, 2, 'x') +``` + +
+ +ClickHouse arrays are 1-indexed; Snowflake's `ARRAY_INSERT` index is 0-based, so increment positions by one when porting. + +
+ +```sql +ARRAY_CONTAINS('x'::VARIANT, tags) +``` + + + +```sql +has(tags, 'x') +``` + +
+ +Argument order differs: Snowflake takes `(value, array)`, ClickHouse `(array, value)`. + +
+ +```sql +ARRAY_POSITION('x'::VARIANT, tags) +``` + + + +```sql +indexOf(tags, 'x') +``` + +
+ +`indexOf` returns 0 when not found; Snowflake's `ARRAY_POSITION` returns `NULL`. Use `nullIf(indexOf(tags, 'x'), 0)` for parity. + +
+ +```sql +ARRAY_SLICE(tags, 0, 2) +``` + + + +```sql +arraySlice(tags, 1, 2) +``` + +
+ +```sql +ARRAY_TO_STRING(tags, ',') +``` + + + +```sql +arrayStringConcat(tags, ',') +``` + +
+ +```sql +ARRAY_DISTINCT(tags) +``` + + + +```sql +arrayDistinct(tags) +``` + +
+ +```sql +ARRAY_INTERSECTION(a, b) +``` + + + +```sql +arrayIntersect(a, b) +``` + +
+ +```sql +ARRAY_COMPACT(tags) +``` + + + +```sql +arrayFilter(x -> x IS NOT NULL, tags) +``` + +
+ +```sql +ARRAY_GENERATE_RANGE(1, 5) +``` + + + +```sql +range(1, 5) +``` + +
+ +Both functions exclude the upper bound — direct port. + +
+ +```sql +FLATTEN(input => tags) +``` + + + +```sql +arrayJoin(tags) +``` + +
+ +### Aggregate functions {#aggregate-functions} + +Snowflake exposes a few dozen aggregate functions plus approximate aggregates. +ClickHouse ships [more than 150 aggregate functions](/sql-reference/aggregate-functions/reference) +and adds [combinators](/sql-reference/aggregate-functions/combinators) (suffixes +such as `-If`, `-Array`, `-Map`, `-ForEach`, `-Merge`, and `-State`) that +compose with any aggregate to extend its behavior across data shapes or to use +it inside materialized views. + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SnowflakeClickHouse
+ +```sql +COUNT(*) -- or COUNT(col) +``` + + + +```sql +count() -- or count(col) +``` + +
+ +```sql +SUM(col), AVG(col), MIN(col), MAX(col) +``` + + + +```sql +sum(col), avg(col), min(col), max(col) +``` + +
+ +```sql +COUNT_IF(cond) +``` + + + +```sql +countIf(cond) +``` + +
+ +ClickHouse pairs every aggregate with the [`-If` combinator](/sql-reference/aggregate-functions/combinators#-if): `sumIf`, `avgIf`, etc. + +
+ +```sql +LISTAGG(name, ',') WITHIN GROUP (ORDER BY id) +``` + + + +```sql +arrayStringConcat(groupArray(name), ',') +-- ordered: +arrayStringConcat( + arrayMap(x -> x.2, + arraySort(x -> x.1, + groupArray((id, name)))), + ',') +``` + +
+ +ClickHouse has no `WITHIN GROUP` clause. For ordered concatenation, aggregate `(sort_key, value)` tuples and sort the array before joining; alternatively, the [`-OrderBy` combinator](/sql-reference/aggregate-functions/combinators) (`groupArrayOrderBy`) lets you specify the ordering inside the aggregate. + +
+ +```sql +ARRAY_AGG(name) +``` + + + +```sql +groupArray(name) +``` + +
+ +```sql +OBJECT_AGG(k, v) +``` + + + +```sql +mapFromArrays(groupArray(k), groupArray(v)) +``` + +
+ +```sql +ANY_VALUE(name) +``` + + + +```sql +any(name) +``` + +
+ +```sql +MEDIAN(x) +``` + + + +```sql +median(x) -- or quantileExact(0.5)(x) +``` + +
+ +```sql +MODE(x) +``` + + + +```sql +topK(1)(x)[1] -- or argMax(x, count()) +``` + +
+ +```sql +PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY x) +``` + + + +```sql +quantile(0.5)(x) +``` + +
+ +Use `quantileExact(0.5)(x)` for exact percentiles, `quantileTDigest(0.5)(x)` for approximate at scale. See [quantile functions](/sql-reference/aggregate-functions/reference/quantile). + +
+ +```sql +STDDEV(x), STDDEV_POP(x) +VAR_SAMP(x), VAR_POP(x) +``` + + + +```sql +stddevSamp(x), stddevPop(x) +varSamp(x), varPop(x) +``` + +
+ +```sql +APPROX_COUNT_DISTINCT(id) +HLL(id) +``` + + + +```sql +uniq(id) -- or uniqExact(id) for exact, uniqHLL12(id) for HyperLogLog +``` + +
+ +```sql +MIN_BY(x, y), MAX_BY(x, y) +``` + + + +```sql +argMin(x, y), argMax(x, y) +``` + +
+ +```sql +KURTOSIS(x), SKEW(x) +``` + + + +```sql +kurtSamp(x), skewSamp(x) +-- or kurtPop / skewPop for population variants +``` + +
+ +### Window functions {#window-functions} + +`OVER` and `PARTITION BY` work the same in both engines (see [Query syntax](#query-syntax) for the windowed-query and `QUALIFY` examples). + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SnowflakeClickHouse
+ +```sql +ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY ts) +``` + + + +```sql +row_number() OVER (PARTITION BY user_id ORDER BY ts) +``` + +
+ +```sql +RANK() OVER (ORDER BY score DESC) +``` + + + +```sql +rank() OVER (ORDER BY score DESC) +``` + +
+ +```sql +DENSE_RANK() OVER (ORDER BY score DESC) +``` + + + +```sql +dense_rank() OVER (ORDER BY score DESC) +``` + +
+ +```sql +NTILE(4) OVER (ORDER BY amount) +``` + + + +```sql +ntile(4) OVER (ORDER BY amount) +``` + +
+ +```sql +LAG(amount, 1) OVER (PARTITION BY id ORDER BY ts) +LEAD(amount, 1) OVER (PARTITION BY id ORDER BY ts) +``` + + + +```sql +lagInFrame(amount, 1) OVER (PARTITION BY id ORDER BY ts) +leadInFrame(amount, 1) OVER (PARTITION BY id ORDER BY ts) +``` + +
+ +ClickHouse names its `LAG`/`LEAD` equivalents `lagInFrame` / `leadInFrame`; they're available without enabling experimental settings since version 23.10. + +
+ +```sql +FIRST_VALUE(name) OVER (PARTITION BY id ORDER BY ts) +LAST_VALUE(name) OVER (PARTITION BY id ORDER BY ts) +``` + + + +```sql +first_value(name) OVER (PARTITION BY id ORDER BY ts) +last_value(name) OVER (PARTITION BY id ORDER BY ts) +``` + +
+ +```sql +NTH_VALUE(name, 3) OVER (ORDER BY ts) +``` + + + +```sql +nth_value(name, 3) OVER (ORDER BY ts) +``` + +
+ +```sql +CUME_DIST() OVER (ORDER BY score) +PERCENT_RANK() OVER (ORDER BY score) +``` + + + +```sql +-- No direct equivalents; emulate via rank()/count() arithmetic: +rank() OVER (ORDER BY score) / count() OVER () +``` + +
+ +### Date and time functions {#date-functions} + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SnowflakeClickHouse
+ +```sql +CURRENT_DATE() +``` + + + +```sql +today() -- or toDate(now()) +``` + +
+ +```sql +CURRENT_TIMESTAMP() +GETDATE() +SYSDATE() +``` + + + +```sql +now() -- or now64() for sub-second precision +``` + +
+ +```sql +EXTRACT(YEAR FROM d) -- MONTH, DAY, HOUR, etc. +DATE_PART('year', d) +``` + + + +```sql +toYear(d) -- toMonth(d), toDayOfMonth(d), toHour(d), ... +-- ClickHouse also accepts EXTRACT(YEAR FROM d) directly. +``` + +
+ +```sql +DATEADD('day', 5, d) +TIMESTAMPADD('day', 5, d) +``` + + + +```sql +addDays(d, 5) -- or d + INTERVAL 5 DAY +``` + +
+ +Note argument order: Snowflake `DATEADD(unit, n, d)`, ClickHouse `addDays(d, n)`. + +
+ +```sql +DATEDIFF('day', start, end) +TIMESTAMPDIFF('day', start, end) +``` + + + +```sql +dateDiff('day', start, end) +``` + +
+ +Argument order matches between Snowflake and ClickHouse: `(unit, start, end)`. + +
+ +```sql +DATE_TRUNC('month', d) +``` + + + +```sql +toStartOfMonth(d) -- toStartOfWeek, toStartOfQuarter, toStartOfYear, ... +``` + +
+ +```sql +DATE_FROM_PARTS(2024, 3, 15) +TIMESTAMP_FROM_PARTS(2024, 3, 15, 12, 0, 0) +``` + + + +```sql +makeDate(2024, 3, 15) +makeDateTime64(2024, 3, 15, 12, 0, 0) +``` + +
+ +```sql +CONVERT_TIMEZONE('America/New_York', ts) +-- two-argument form: source tz is the column's tz +``` + + + +```sql +toTimeZone(ts, 'America/New_York') +``` + +
+ +```sql +CONVERT_TIMEZONE('UTC', 'America/New_York', ts) +-- three-argument form +``` + + + +```sql +-- Store in UTC; convert at display time: +toTimeZone(toTimeZone(ts, 'UTC'), 'America/New_York') +``` + +
+ +```sql +MONTHS_BETWEEN(a, b) +``` + + + +```sql +dateDiff('month', b, a) +``` + +
+ +```sql +LAST_DAY(d) +``` + + + +```sql +toLastDayOfMonth(d) +``` + +
+ +```sql +DAYOFWEEK(d), WEEKOFYEAR(d), DAYOFYEAR(d) +``` + + + +```sql +toDayOfWeek(d), toWeek(d), toDayOfYear(d) +``` + +
+ +```sql +TO_DATE(s, 'YYYY-MM-DD') +TO_TIMESTAMP(s, 'YYYY-MM-DD HH24:MI:SS') +``` + + + +```sql +parseDateTimeBestEffort(s) -- permissive +-- or toDate(s) for ISO dates, parseDateTime(s, format) for an explicit format +``` + +
+ +### String functions {#string-functions} + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SnowflakeClickHouse
+ +```sql +LENGTH(s) -- or LEN(s) +``` + + + +```sql +lengthUTF8(s) -- length(s) returns bytes +``` + +
+ +```sql +SUBSTR(s, pos, len) -- or SUBSTRING +``` + + + +```sql +substring(s, pos, len) +``` + +
+ +```sql +SPLIT(s, ',') +``` + + + +```sql +splitByString(',', s) -- or splitByChar(',', s) for single-char delimiters +``` + +
+ +```sql +SPLIT_PART(s, ',', 2) +``` + + + +```sql +splitByString(',', s)[2] +``` + +
+ +Snowflake `SPLIT_PART` is 1-indexed; so is ClickHouse array access — direct port. + +
+ +```sql +REPLACE(s, from, to) +``` + + + +```sql +replaceAll(s, from, to) -- use replaceOne for first match only +``` + +
+ +```sql +UPPER(s), LOWER(s) +``` + + + +```sql +upper(s), lower(s) -- upperUTF8 / lowerUTF8 for Unicode-aware casing +``` + +
+ +```sql +TRIM(s), LTRIM(s), RTRIM(s) +``` + + + +```sql +trimBoth(s), trimLeft(s), trimRight(s) +-- alias: trim(s) for trimBoth(s) +``` + +
+ +```sql +INITCAP(s) +``` + + + +```sql +-- No direct equivalent; combine arrayMap + splitByChar: +arrayStringConcat( + arrayMap(w -> concat(upperUTF8(substring(w, 1, 1)), lowerUTF8(substring(w, 2))), + splitByChar(' ', s)), + ' ') +``` + +
+ +```sql +REGEXP_LIKE(s, '^[a-z]') +``` + + + +```sql +match(s, '^[a-z]') +``` + +
+ +```sql +REGEXP_SUBSTR(s, '([0-9]+)') +``` + + + +```sql +extract(s, '([0-9]+)') +``` + +
+ +Snowflake `REGEXP_SUBSTR` accepts position / occurrence / group arguments; ClickHouse `extract` returns the first match of the first capture group. Use [`extractAll`](/sql-reference/functions/string-search-functions#extractAll) for multiple matches. + +
+ +```sql +REGEXP_REPLACE(s, '[0-9]+', 'X') +``` + + + +```sql +replaceRegexpAll(s, '[0-9]+', 'X') +``` + +
+ +```sql +REGEXP_INSTR(s, '[0-9]+') +``` + + + +```sql +position(s, extract(s, '[0-9]+')) +-- 0 if not found +``` + +
+ +```sql +CONCAT(a, b, c) +CONCAT_WS(',', a, b, c) +``` + + + +```sql +concat(a, b, c) +concatWithSeparator(',', a, b, c) +``` + +
+ +```sql +LPAD(s, 8, '0'), RPAD(s, 8, '0') +``` + + + +```sql +leftPad(s, 8, '0'), rightPad(s, 8, '0') +``` + +
+ +```sql +STARTSWITH(s, 'pre'), ENDSWITH(s, 'fix') +``` + + + +```sql +startsWith(s, 'pre'), endsWith(s, 'fix') +``` + +
+ +```sql +CONTAINS(s, 'sub') +``` + + + +```sql +position(s, 'sub') > 0 +-- or s LIKE '%sub%' +``` + +
+ +```sql +REVERSE(s) +``` + + + +```sql +reverseUTF8(s) -- reverse(s) reverses bytes +``` + +
+ +```sql +MD5(s), SHA1(s), SHA2(s, 256) +``` + + + +```sql +hex(MD5(s)), hex(SHA1(s)), hex(SHA256(s)) +``` + +
+ +ClickHouse hash functions return raw bytes; wrap in `hex()` to match Snowflake's hex-string output. + +
+ +```sql +BASE64_ENCODE(s), BASE64_DECODE_STRING(s) +``` + + + +```sql +base64Encode(s), base64Decode(s) +``` + +
+ +### JSON and VARIANT functions {#json-functions} + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SnowflakeClickHouse
+ +```sql +v:field +v:field::VARCHAR +``` + + + +```sql +JSONExtractString(v, 'field') +-- or v.field if v is the native JSON type +``` + +
+ +Snowflake's colon syntax (`v:field`) navigates `VARIANT` paths; `::TYPE` casts the extracted value. In ClickHouse, use typed JSON extractors against `String`-stored JSON, or the [native `JSON` type](/sql-reference/data-types/newjson) with dot-path access (`v.field`). + +
+ +```sql +GET(v, 'field') +GET_PATH(v, 'a.b.c') +``` + + + +```sql +JSONExtract(v, 'field', 'String') +JSONExtractString(v, 'a', 'b', 'c') +``` + +
+ +ClickHouse offers typed extractors per result type: `JSONExtractString`, `JSONExtractInt`, `JSONExtractFloat`, `JSONExtractBool`, `JSONExtractArrayRaw`, etc. See [JSON functions](/sql-reference/functions/json-functions). + +
+ +```sql +PARSE_JSON(s) +``` + + + +```sql +CAST(s AS JSON) +-- or store the column as the native JSON type +``` + +
+ +```sql +TO_JSON(v) +``` + + + +```sql +toJSONString(v) +``` + +
+ +```sql +OBJECT_CONSTRUCT('a', a, 'b', b) +``` + + + +```sql +toJSONString(map('a', a, 'b', b)) +-- or tuple(a, b) for a positional structure +``` + +
+ +```sql +OBJECT_KEYS(o) +``` + + + +```sql +JSONExtractKeys(toJSONString(o)) +-- or mapKeys(o) if o is a Map +``` + +
+ +```sql +OBJECT_PICK(o, 'a', 'b') +OBJECT_DELETE(o, 'a') +OBJECT_INSERT(o, 'k', v) +``` + + + +```sql +-- No direct equivalents; reconstruct with map() / tuple() or +-- JSONExtract* into a new JSON value. +``` + +
+ +```sql +IS_NULL_VALUE(v) +IS_ARRAY(v), IS_OBJECT(v) +IS_VARCHAR(v), IS_NUMERIC(v) +``` + + + +```sql +-- For native JSON columns: +JSONType(v, 'field') -- returns 'String', 'Array', 'Object', etc. +JSONHas(v, 'field') +``` + +
+ +```sql +CHECK_JSON(s) +``` + + + +```sql +isValidJSON(s) +``` + +
+ +```sql +SELECT f.value +FROM myschema.t, + LATERAL FLATTEN(input => t.tags) f +``` + + + +```sql +SELECT tag +FROM mydb.t +ARRAY JOIN tags AS tag +``` + +
+ +### Numeric and math functions {#numeric-functions} + +Most numeric functions match by name (case-insensitive in Snowflake, lowercase +by convention in ClickHouse) and are direct ports: + +| Snowflake | ClickHouse | +|-----------|------------| +| `ABS(x)`, `SIGN(x)` | `abs(x)`, `sign(x)` | +| `ROUND(x, n)`, `CEIL(x)`, `FLOOR(x)`, `TRUNCATE(x, n)` | `round(x, n)`, `ceil(x)`, `floor(x)`, `truncate(x, n)` | +| `POWER(x, y)`, `EXP(x)`, `LN(x)`, `LOG(b, x)`, `LOG10(x)`, `SQRT(x)` | `pow(x, y)`, `exp(x)`, `log(x)`, `log(x) / log(b)`, `log10(x)`, `sqrt(x)` | +| `SIN(x)`, `COS(x)`, `TAN(x)`, `ASIN(x)`, `ACOS(x)`, `ATAN(x)`, `ATAN2(y, x)` | `sin(x)`, `cos(x)`, `tan(x)`, `asin(x)`, `acos(x)`, `atan(x)`, `atan2(y, x)` | +| `PI()` | `pi()` | +| `RANDOM()` | `rand()` (returns `UInt32`); divide by `4294967295` for `[0, 1)` | +| `UNIFORM(lo, hi, RANDOM())` | `lo + rand() % (hi - lo + 1)` for integers, or `randUniform(lo, hi)` for floats | +| `BITAND(a, b)`, `BITOR(a, b)`, `BITXOR(a, b)`, `BITNOT(a)`, `BITSHIFTLEFT(a, n)`, `BITSHIFTRIGHT(a, n)` | `bitAnd(a, b)`, `bitOr(a, b)`, `bitXor(a, b)`, `bitNot(a)`, `bitShiftLeft(a, n)`, `bitShiftRight(a, n)` | diff --git a/src/css/custom.scss b/src/css/custom.scss index 58efec097ed..6ccd12c28b6 100644 --- a/src/css/custom.scss +++ b/src/css/custom.scss @@ -1566,3 +1566,113 @@ input::-ms-input-placeholder { /* Microsoft Edge */ [data-theme='dark'] .table-category-header { background-color: #474747; } + +/* Side-by-side SQL translation tables (e.g., BigQuery -> ClickHouse). + Locks columns to 50/50, wraps long code, makes code blocks fill cell height, + and styles the spanning notes row as a plain table row. */ +.sql-translation-table { + table-layout: fixed; + width: 100%; + + colgroup col { + width: 50%; + } + + pre { + white-space: pre-wrap; + word-break: break-word; + overflow-wrap: anywhere; + } + + // Code cells: zero padding so the code block fills the cell. + // The custom .code-viewer uses #f5f5f5 in light mode and #282828 in dark + // mode (see src/components/CodeViewer); match the cell bg to whichever is + // active so any vertical slack below shorter code blocks blends in. + tbody td:not([colspan]) { + padding: 0; + vertical-align: top; + + // Stretch any code-block wrapper (direct child div, descendant + // .code-viewer, or bare
) to fill the cell vertically.
+    > div,
+    > pre,
+    .code-viewer {
+      margin: 0;
+      border-radius: 0;
+      box-shadow: none;
+      height: 100%;
+      min-height: 100%;
+    }
+
+    // Text-only cells (e.g., "No equivalent") wrap in a 

; give them + // padding so they don't sit flush against the cell edges. + > p { + margin: 0; + padding: 12px 14px; + } + } + + // Subtle split between the BigQuery and ClickHouse code cells. + // Color is set per-theme below so it's slightly lighter than the cell bg + // in light mode and slightly darker in dark mode. + tbody td:not([colspan]) + td:not([colspan]) { + border-left: 1px solid transparent; + } +} + +// Match the horizontal row-divider color used by the global table style +// (rgb(230,231,233) light / rgb(50,50,50) dark) so vertical and horizontal +// dividers have the same colour and weight. +[data-theme='light'] .sql-translation-table tbody td:not([colspan]) + td:not([colspan]) { + border-left-color: rgb(230, 231, 233); +} + +[data-theme='dark'] .sql-translation-table tbody td:not([colspan]) + td:not([colspan]) { + border-left-color: rgb(50, 50, 50); +} + +.sql-translation-table tbody td[colspan="2"] { + padding: 10px 14px 10px 36px; + background-color: rgba(127, 127, 127, 0.05); + font-size: 0.9rem; + position: relative; +} + +// Visual marker tying the note to the row above it: a "↳" return arrow in +// the gutter, so the reader sees the note belongs to the code pair that +// just rendered above. +.sql-translation-table tbody td[colspan="2"]::before { + content: '↳'; + position: absolute; + left: 14px; + top: 10px; + color: var(--ifm-color-emphasis-500); + font-size: 0.9rem; + line-height: 1.4; +} + +// Drop the (invisible) row divider between code-pair row and its note row. +// Both browsers we care about support :has(). +.sql-translation-table tbody tr:has(> td[colspan="2"]) { + border-top: none; +} +.sql-translation-table tbody tr:has(> td[colspan="2"]) > td { + border-top: none; +} + +// 3-column variant: BigQuery | ClickHouse | Notes inline. +.sql-translation-table.has-notes-col colgroup col:nth-child(1), +.sql-translation-table.has-notes-col colgroup col:nth-child(2) { + width: 35%; +} +.sql-translation-table.has-notes-col colgroup col:nth-child(3) { + width: 30%; +} + +[data-theme='light'] .sql-translation-table tbody td:not([colspan]) { + background-color: #f5f5f5; +} + +[data-theme='dark'] .sql-translation-table tbody td:not([colspan]) { + background-color: #282828; +}