Skip to main content

Descripción

El formato RowBinary analiza los datos fila por fila en formato binario. Las filas y los valores se muestran consecutivamente, sin separadores. Como los datos están en formato binario, el delimitador después de FORMAT RowBinary se especifica estrictamente de la siguiente manera:
  • Cualquier cantidad de espacios en blanco:
    • ' ' (espacio - código 0x20)
    • '\t' (tabulación - código 0x09)
    • '\f' (salto de página - código 0x0C)
  • Seguido de exactamente una secuencia de salto de línea:
    • estilo Windows "\r\n"
    • o estilo Unix '\n'
  • Seguido inmediatamente de datos binarios.
Este formato es menos eficiente que el formato Native, ya que está basado en filas.

Formato de transmisión de los tipos de datos

La mayoría de las consultas incluidas en los ejemplos pueden ejecutarse con curl y guardar la salida en un archivo.
A continuación, los datos pueden examinarse con un editor hexadecimal.

LEB128 sin signo (Base 128 little-endian)

Una codificación de enteros sin signo de longitud variable en formato little-endian que se usa para codificar la longitud de tipos de datos de tamaño variable, como String, Array y Map. Se puede encontrar una implementación de ejemplo en la página de Wikipedia sobre LEB128.

(U)Int8, (U)Int16, (U)Int32, (U)Int64, (U)Int128, (U)Int256

Todos los tipos enteros se codifican con el número de bytes correspondiente en formato little-endian. Los tipos con signo (Int8 a Int256) usan la representación en complemento a dos. La mayoría de los lenguajes permiten extraer estos enteros de arrays de bytes, ya sea con herramientas integradas o con bibliotecas conocidas. Para Int128/Int256 y UInt128/UInt256, que superan el tamaño de entero nativo de la mayoría de los lenguajes, puede ser necesaria una deserialización personalizada.

Bool

Los valores booleanos se codifican en un solo byte y pueden deserializarse de forma similar a UInt8.
  • 0 es false
  • 1 es true

Float32, Float64

Números de coma flotante en formato little-endian, codificados en 4 bytes para Float32 y en 8 bytes para Float64. Al igual que con los enteros, la mayoría de los lenguajes ofrecen herramientas adecuadas para deserializar estos valores.

BFloat16

BFloat16 (Brain Floating Point) es un formato de punto flotante de 16 bits con el mismo rango que Float32, pero con menor precisión, lo que lo hace útil para cargas de trabajo de aprendizaje automático. El formato de transmisión es, básicamente, los 16 bits más altos de un valor Float32. Si tu lenguaje no lo admite de forma nativa, la forma más sencilla de manejarlo es leerlo y escribirlo como UInt16, convirtiéndolo desde y hacia Float32: Para convertir BFloat16 a Float32 (pseudocódigo):
Para convertir Float32 en BFloat16 (pseudocódigo):
Ejemplos de valores subyacentes para BFloat16:

Decimal32, Decimal64, Decimal128, Decimal256

Los tipos Decimal se representan como enteros little-endian con el ancho en bits correspondiente.
  • Decimal32 - 4 bytes, o Int32.
  • Decimal64 - 8 bytes, o Int64.
  • Decimal128 - 16 bytes, o Int128.
  • Decimal256 - 32 bytes, o Int256.
Al deserializar un valor Decimal, las partes entera y fraccionaria se pueden obtener usando el siguiente pseudocódigo:
Donde trunc realiza el truncado hacia cero (no la división con redondeo hacia abajo, que da un resultado distinto para valores negativos), y scale es el número de dígitos después del punto decimal. Por ejemplo, para Decimal(10, 2) (equivalente a Decimal32(2)), la escala es 2, y el valor 12345 se representará como (123, 45). La serialización requiere la operación inversa:
Consulta más información en la documentación de ClickHouse sobre los tipos Decimal.

String

Las cadenas de ClickHouse son secuencias arbitrarias de bytes. No es necesario que sean UTF-8 válidas. El prefijo de longitud es la longitud en bytes, no el número de caracteres. Se codifican en dos partes:
  1. Un entero de longitud variable (LEB128) que indica la longitud de la cadena en bytes.
  2. Los bytes en bruto de la cadena.
Por ejemplo, una cadena foobar se codificará usando siete bytes de la siguiente manera:

FixedString

A diferencia de String, FixedString tiene una longitud fija, definida en el esquema. Se codifica como una secuencia de bytes, rellenada con bytes cero al final si el valor es más corto que N.
Al leer un FixedString, los bytes cero finales pueden ser relleno o caracteres \0 reales de los datos; no se pueden distinguir en la transmisión. El propio ClickHouse conserva los N bytes tal cual.
Un FixedString(3) vacío contiene únicamente ceros de relleno:
FixedString(3) no vacía que contiene la cadena hi:
FixedString(3) no vacío que contiene la cadena bar:
No se necesita relleno en el último ejemplo, ya que se usan los tres bytes.

Date

Se almacena como UInt16 (dos bytes), que representa el número de días desde 1970-01-01. Rango de valores admitido: [1970-01-01, 2149-06-06]. Valores subyacentes de ejemplo para Date:

Date32

Se almacena como Int32 (cuatro bytes), que representa el número de días antes o después de 1970-01-01. Rango de valores admitido: [0000-01-01, 9999-12-31]. Ejemplos de valores subyacentes para Date32:
Una fecha anterior a la época Unix:

DateTime

Se almacena como UInt32 (cuatro bytes), que representa el número de segundos transcurridos desde 1970-01-01 00:00:00 UTC. Sintaxis:
Por ejemplo, DateTime o DateTime('UTC').
El valor binario siempre es un desplazamiento de época en UTC. La zona horaria no cambia la codificación. Sin embargo, la zona horaria sí afecta a cómo se interpretan los valores de cadena al insertar: insertar '2024-01-15 10:30:00' en una columna DateTime('America/New_York') almacena un valor de época distinto que insertar la misma cadena en una columna DateTime('UTC'), porque la cadena se interpreta como hora local en la zona horaria de la columna. En la transmisión, ambos son simplemente segundos de época UInt32.
Rango de valores admitido: [1970-01-01 00:00:00, 2106-02-07 06:28:15]. Valores subyacentes de ejemplo para DateTime:

DateTime64

Se almacena como Int64 (ocho bytes), que representa el número de ticks anteriores o posteriores a 1970-01-01 00:00:00 UTC. La resolución del tick se define mediante el parámetro precision; consulte la sintaxis a continuación:
Donde precision es un entero de 0 a 9. Por lo general, solo se usan los siguientes: 3 (milisegundos), 6 (microsegundos), 9 (nanosegundos). Ejemplos de definiciones válidas de DateTime64: DateTime64(0), DateTime64(3), DateTime64(6, 'UTC') o DateTime64(9, 'Europe/Amsterdam').
Al igual que con DateTime, el valor binario siempre es un desplazamiento respecto de la época UTC. La zona horaria afecta a cómo se interpretan los valores de cadena al insertarlos (consulta la nota de DateTime), pero la codificación en sí siempre son ticks Int64 desde la época UTC.
El valor Int64 subyacente del tipo DateTime64 puede interpretarse como la cantidad de las siguientes unidades con respecto a la época UNIX, ya sea antes o después de ella:
  • DateTime64(0) - segundos.
  • DateTime64(3) - milisegundos.
  • DateTime64(6) - microsegundos.
  • DateTime64(9) - nanosegundos.
Rango de valores admitido: [0000-01-01 00:00:00, 9999-12-31 23:59:59.999999999] (para precisiones de hasta 7; las precisiones 8 y 9 tienen un rango más limitado; consulta la nota a continuación). Valores subyacentes de ejemplo para DateTime64:
  • DateTime64(3): el valor 1546300800000 representa 2019-01-01 00:00:00 UTC.
  • DateTime64(6): el valor 1705314600123456 representa 2024-01-15 10:30:00.123456 UTC.
  • DateTime64(9): el valor 1705314600123456789 representa 2024-01-15 10:30:00.123456789 UTC.
Debido a que el rango de ticks Int64 subyacente es más limitado a mayor precisión, el valor máximo admitido se reduce: con precisión 8 es 4892-10-07 y con precisión 9 (nanosegundos) es 2262-04-11 23:47:16 en UTC.

Time

Se almacena como Int32 y representa un valor de tiempo en segundos. Los valores negativos son válidos. Rango de valores admitido: [-999:59:59, 999:59:59] (es decir, [-3599999, 3599999] segundos).
Por el momento, la configuración enable_time_time64_type debe establecerse en 1 para usar Time o Time64.
Valores subyacentes de ejemplo para Time:

Time64

Se almacena internamente como un Decimal64 (que a su vez se almacena como Int64) y representa un valor de tiempo con segundos fraccionarios y precisión configurable. Se permiten valores negativos. Sintaxis:
Donde precision es un entero entre 0 y 9. Valores comunes: 3 (milisegundos), 6 (microsegundos), 9 (nanosegundos). Rango de valores admitidos: [-999:59:59.xxxxxxxxx, 999:59:59.xxxxxxxxx].
En este momento, la configuración enable_time_time64_type debe establecerse en 1 para usar Time o Time64.
El valor subyacente de Int64 representa segundos fraccionarios escalados por 10^precision. Valores subyacentes de ejemplo para Time64:

Tipos de intervalos

Todos los tipos de intervalos se almacenan como Int64 (ocho bytes, little-endian). El valor representa la cantidad de la unidad de tiempo correspondiente. Los valores negativos son válidos. Los tipos de intervalos son: IntervalNanosecond, IntervalMicrosecond, IntervalMillisecond, IntervalSecond, IntervalMinute, IntervalHour, IntervalDay, IntervalWeek, IntervalMonth, IntervalQuarter, IntervalYear.
El nombre del tipo de intervalo (por ejemplo, IntervalSecond frente a IntervalDay) determina la unidad del valor almacenado. La codificación transmitida es siempre la misma.
Ejemplos de valores subyacentes:

Enum8, Enum16

Se almacenan como un solo byte (Enum8 == Int8) o dos bytes (Enum16 == Int16) que representan el índice del valor del enum en la definición del enum. Ten en cuenta que el tipo de almacenamiento es con signo: los valores del enum pueden ser negativos (p. ej., Enum8('a' = -128, 'b' = 0)). Un Enum puede definirse de forma sencilla, así:
En el cliente, el Enum8 definido anteriormente tendrá la siguiente asignación de valores:
O, de forma más compleja, así:
El Enum16 definido anteriormente tendrá la siguiente correspondencia de valores en el cliente:
En el analizador de tipos de datos, la principal dificultad consiste en seguir la pista de los caracteres escapados en la definición de enum, como \', y de los símbolos especiales como = que pueden aparecer dentro de cadenas entre comillas.

UUID

Se representa como una secuencia de 16 bytes. El UUID se almacena como dos valores UInt64 little-endian: los primeros 8 bytes de la representación estándar del UUID se almacenan con el orden de bytes invertido, y los segundos 8 bytes se invierten de forma independiente. Por ejemplo, dado el UUID 61f0c404-5cb3-11e7-907b-a6006ad3dba0:
  • Representación estándar de bytes: 61 f0 c4 04 5c b3 11 e7 | 90 7b a6 00 6a d3 db a0
  • Primera mitad invertida (LE UInt64): e7 11 b3 5c 04 c4 f0 61
  • Segunda mitad invertida (LE UInt64): a0 db d3 6a 00 a6 7b 90
Valores subyacentes de ejemplo para UUID:
  • 61f0c404-5cb3-11e7-907b-a6006ad3dba0 se representa como:
  • El UUID predeterminado 00000000-0000-0000-0000-000000000000 se representa como 16 bytes en cero:
Se puede usar cuando se ha insertado un registro nuevo, pero no se ha especificado un valor UUID.

IPv4

Se almacena en cuatro bytes como UInt32 en orden de bytes little-endian. Tenga en cuenta que esto difiere del orden de bytes de red tradicional (big-endian), usado habitualmente para las direcciones IP. Valores internos de ejemplo para IPv4:

IPv6

Se almacena en 16 bytes en orden de bytes big-endian / de red (MSB primero). Valores internos de ejemplo para IPv6:

Nullable

Un tipo de dato Nullable se codifica de la siguiente manera:
  1. Un único byte que indica si el valor es NULL o no:
    • 0x00 significa que el valor no es NULL.
    • 0x01 significa que el valor es NULL.
  2. Si el valor no es NULL, el tipo de dato subyacente se codifica como de costumbre. Si el valor es NULL, no se escriben bytes adicionales para el tipo subyacente.
Por ejemplo, un valor Nullable(UInt32):

LowCardinality

En el formato RowBinary, el marcador de baja cardinalidad no afecta al wire format. Por ejemplo, LowCardinality(String) se codifica de la misma manera que un String normal.
Esto solo se aplica a RowBinary. En el formato Native, LowCardinality usa una codificación diferente basada en diccionario.
Una columna puede definirse como LowCardinality(Nullable(T)), pero no es posible definirla como Nullable(LowCardinality(T)): siempre producirá un error del servidor.
Al hacer pruebas, allow_suspicious_low_cardinality_types puede establecerse en 1 para permitir la mayoría de los tipos de datos dentro de LowCardinality y conseguir una mejor cobertura.

Array

Un array se codifica de la siguiente manera:
  1. Un entero de longitud variable (LEB128) que indica el número de elementos del array.
  2. Los elementos del array, codificados de la misma manera que el tipo de datos subyacente.
Por ejemplo, un array con valores UInt32:
Un ejemplo algo más complejo:
Un array puede contener valores Nullable, pero el array en sí no puede ser Nullable.
Lo siguiente es válido:
Y se codificará así:
Un ejemplo de cómo trabajar con arrays multidimensionales se encuentra en la sección Geo.

Tuple

Una Tuple se codifica como todos sus elementos, uno a continuación del otro, en el wire format correspondiente, sin metainformación ni delimitadores adicionales.
La representación en cadena del tipo de datos Tuple presenta desafíos similares a los del tipo Enum, como rastrear los símbolos escapados y los caracteres especiales; ahora, con Tuple, también es necesario rastrear los paréntesis de apertura y cierre. Además, tenga en cuenta que los Tuple más complejos pueden contener otros Tuple anidados, Arrays, Maps e incluso enums. Por ejemplo, en la siguiente tabla, el tuple contiene un enum con un acento grave (`) y un paréntesis en el nombre, lo que puede causar problemas de parsing si no se maneja correctamente:

Map

Un Map puede verse como un Array(Tuple(K, V)), donde K es el tipo de clave y V es el tipo de valor. El Map se codifica de la siguiente manera:
  1. Un entero de longitud variable (LEB128) que indica el número de elementos del Map.
  2. Los elementos del Map como pares clave-valor, codificados según sus tipos correspondientes.
Por ejemplo, un Map con claves String y valores UInt32:
Es posible tener maps con estructuras muy anidadas, como Map(String, Map(Int32, Array(Nullable(String)))), que se codificarán de forma similar a lo descrito anteriormente.

Variant

Este tipo representa una unión de otros tipos de datos. El tipo Variant(T1, T2, ..., TN) significa que cada fila de este tipo tiene un valor de tipo T1, T2, …, TN o ninguno de ellos (valor NULL).
Aunque para el usuario final Variant(T1, T2) significa exactamente lo mismo que Variant(T2, T1), el orden de los tipos en la definición sí importa para el wire format: los tipos de la definición siempre se ordenan alfabéticamente, y esto es importante, ya que la variante exacta se codifica mediante un “discriminante”: el índice del tipo de datos en la definición.
Considere el siguiente ejemplo:
Un valor NULL se codifica con un byte discriminante de 0xFF:
La opción de configuración allow_suspicious_variant_types puede utilizarse para permitir pruebas más exhaustivas del tipo Variant.

Dynamic

El tipo Dynamic puede contener valores de cualquier tipo, determinados en tiempo de ejecución. En el formato RowBinary, cada valor se describe a sí mismo: la primera parte es la especificación del tipo en este formato. A continuación va el contenido, con la codificación del valor tal como se describe en este documento. Por lo tanto, para analizar un valor, solo necesitas usar el índice de tipo para determinar el parser adecuado y luego reutilizar la lógica de análisis de RowBinary que ya tengas en otro lugar.
Donde BinaryTypeIndex es un solo byte que identifica el tipo. Consulte la referencia aquí para ver los índices de tipo y los parámetros. Un valor Dynamic NULL se codifica con BinaryTypeIndex 0x00 (el tipo Nothing), sin bytes adicionales:
Ejemplos:

JSON

El tipo JSON codifica los datos en dos categorías distintas:
  1. Rutas tipadas - Rutas declaradas con tipos explícitos en el esquema (p. ej., JSON(user_id UInt32, name String))
  2. Rutas dinámicas/rutas de desbordamiento cuando se supera el límite de rutas dinámicas - Rutas detectadas en tiempo de ejecución almacenadas con el tipo Dynamic. La codificación del valor está precedida por la definición del tipo.
El formato de transmisión y las reglas son diferentes para estas dos categorías. Las rutas se serializan en tres grupos escritos de forma secuencial: rutas tipadas, rutas dinámicas y rutas de datos compartidos (desbordamiento). Las rutas tipadas y dinámicas se escriben en un orden definido por la implementación (determinado por la iteración interna del mapa hash), mientras que las rutas de datos compartidos se escriben en orden alfabético. Los lectores no deben asumir ningún orden específico de rutas. El deserializador resuelve cada ruta por nombre, no por posición. Cada fila JSON en formato RowBinary se serializa de la siguiente manera:
Ejemplos: 1. JSON simple solo con rutas con tipo: Schema: JSON(user_id UInt32, active Bool) Fila: {"user_id": 42, "active": true} Codificación binaria (hex con anotaciones):
2. JSON simple con rutas tipadas y dinámicas: Schema: JSON(user_id UInt32, active Bool) Fila: {"user_id": 42, "active": true, "name": "Alice"} Codificación binaria (hex con anotaciones):
3. Manejo de valores NULL: Con una columna Nullable tipada se obtiene null: Schema: JSON(score Nullable(Int32)) Fila: {"score": null } Codificación binaria (hex con anotaciones):
Con una columna tipada no nullable, se obtiene el valor predeterminado: Schema: JSON(name String) Fila: {"name": null} Codificación binaria:
Con una ruta dinámica, se ignora: Schema: JSON(id UInt64) Fila: {"id": 100, "metadata": null} Codificación binaria:
Nota: La ruta metadata con valor NULL no se incluye porque las rutas dinámicas solo se serializan cuando no son nulas. Esta es una diferencia clave con respecto a las rutas tipadas. 4. Objetos JSON anidados: Esquema: JSON() Fila: {"user": {"name": "Bob", "age": 30}} Codificación binaria (hexadecimal con anotaciones):
Nota: Los objetos anidados se aplanan en rutas separadas por puntos (p. ej., user.name en lugar de una estructura anidada). Alternativa: modo JSON como String Con la configuración output_format_binary_write_json_as_string=1, las columnas JSON se serializan como una única cadena de texto JSON en lugar de en formato binario estructurado. Existe una configuración correspondiente para escribir en columnas JSON: input_format_binary_read_json_as_string. La elección entre una u otra configuración depende de si desea procesar el JSON en el cliente o en el servidor.

Tipos Geo

Geo es una categoría de tipos de datos que representan datos geográficos. Incluye:
  • Point - como Tuple(Float64, Float64).
  • MultiPoint - como Array(Point), o Array(Tuple(Float64, Float64)).
  • Ring - como Array(Point), o Array(Tuple(Float64, Float64)).
  • Polygon - como Array(Ring), o Array(Array(Tuple(Float64, Float64))).
  • MultiPolygon - como Array(Polygon), o Array(Array(Array(Tuple(Float64, Float64)))).
  • LineString - como Array(Point), o Array(Tuple(Float64, Float64)).
  • MultiLineString - como Array(LineString), o Array(Array(Tuple(Float64, Float64))).
El wire format de los valores Geo es exactamente el mismo que el de Tuple y Array. Los encabezados del formato RowBinaryWithNamesAndTypes contendrán los alias de estos tipos, por ejemplo, Point, MultiPoint, Ring, Polygon, MultiPolygon, LineString y MultiLineString.

Geometry

Geometry es un tipo Variant que puede contener cualquiera de los tipos Geo mencionados anteriormente. En el wire format, se codifica exactamente igual que un Variant, con un byte discriminante que indica qué tipo geo viene a continuación. Los índices discriminantes de Geometry son: Estructura del wire format:
Ejemplo de codificación de un Point como Geometry:
Codificación de ejemplo de un Ring como Geometry:

Nested

El wire format de Nested depende de la configuración flatten_nested.
Todos los arrays de componentes de una misma fila deben tener la misma longitud. Es una restricción impuesta por el servidor. Si las longitudes no coinciden, se producirán errores de inserción.

flatten_nested = 1 (valor predeterminado)

Con la configuración predeterminada, Nested se descompone en arrays independientes. Cada subcolumna se convierte en una columna Array independiente con un nombre separado por puntos:
DESCRIBE TABLE foo muestra las columnas desanidadas:
Cada array se serializa de forma independiente, tal como se describe en la sección Array:

flatten_nested = 0

Con flatten_nested = 0, Nested se mantiene como una única columna del tipo Array(Tuple(...)). El nombre de la columna no se separa con puntos:
DESCRIBE TABLE foo muestra una sola columna:
La codificación es Array(Tuple(String, Int32)): un prefijo con la longitud del array, seguido de los campos de la tupla de cada elemento en orden:
Observa cómo los campos se intercalan por elemento (a₁, b₁, a₂, b₂) en lugar de agruparse por columna (a₁, a₂, b₁, b₂), como en la representación plana.

SimpleAggregateFunction

SimpleAggregateFunction(func, T) se codifica de forma idéntica a su tipo de dato subyacente T. El nombre de la función de agregación no afecta al wire format. Por ejemplo, SimpleAggregateFunction(max, UInt32) se codifica de la misma forma que un UInt32 normal:
El encabezado RowBinaryWithNamesAndTypes muestra el tipo como SimpleAggregateFunction(max, UInt32), pero el valor transmitido es simplemente UInt32:

AggregateFunction

AggregateFunction(func, T) almacena el estado intermedio completo de una función de agregación. A diferencia de SimpleAggregateFunction, que también almacena un estado intermedio pero lo codifica igual que el tipo de dato subyacente, AggregateFunction almacena un blob binario opaco cuyo formato es específico de cada función de agregación.
Los estados de agregación no tienen prefijo de longitud en RowBinary. Un analizador debe comprender el formato de serialización interno de cada función de agregación en particular para saber cuántos bytes debe consumir. En la práctica, la mayoría de los clientes tratan los estados de agregación como opacos y usan los combinadores *State / *Merge para que el servidor se encargue de la serialización.
El formato interno varía según la función. Algunos ejemplos sencillos: countState — almacena el recuento como un VarUInt (LEB128):
sumState — almacena la suma acumulada en un entero de tamaño fijo. El tamaño depende del tipo del argumento (UInt64 para argumentos enteros):
minState / maxState — almacena un byte de indicador seguido del valor en el tipo subyacente. El indicador es 0x00 para un estado vacío (sin valores registrados) o 0x01 cuando hay un valor presente:
Un estado vacío (sin filas agregadas):
Las funciones más complejas, como uniq, quantile o groupArray, utilizan formatos específicos de la implementación. Si necesita leer o escribir estos estados, consulte el código fuente de ClickHouse correspondiente a la función en cuestión.
Con el ajuste aggregate_function_input_format establecido en value o array, una columna AggregateFunction(func, T) se lee como un valor de tipo T (o como una Tuple de los tipos de los argumentos si hay varios), o como un Array de dichos valores, codificado exactamente como se codifica ese tipo en RowBinary. El estado se construye a partir de los valores en el lado del servidor. Por ejemplo, con aggregate_function_input_format = 'array', una celda de AggregateFunction(avg, UInt32) es un Array(UInt32): una longitud LEB128 seguida de los elementos.

QBit

QBit es un tipo de vector para realizar consultas eficientes con distintos niveles de precisión. Internamente, se almacena en un formato transpuesto. A nivel de protocolo, QBit es simplemente un Array del tipo de elemento subyacente (Int8, Float32, Float64 o BFloat16). La optimización de transposición de bits para el almacenamiento se realiza en el servidor, no en el protocolo RowBinary. Sintaxis:
Donde element_type es Int8, Float32, Float64 o BFloat16, y dimension es la dimensión fija del vector. El stride opcional solo controla cómo se agrupan los planos de bits en flujos de almacenamiento en el servidor; no afecta al wire format de RowBinary, que siempre es el array completo de dimension elementos. wire format: idéntico a Array(element_type):
Ejemplo de codificación de QBit(Float32, 4) que contiene [1.0, 2.0, 3.0, 4.0]:

Configuración del formato

Los siguientes ajustes son comunes a todos los formatos de tipo RowBinary.
Última modificación el 26 de septiembre de 2026