Arquivos Tab Separated Value, ou TSV, são comuns e podem incluir nomes de campos na primeira linha do arquivo. O ClickHouse pode fazer a ingestão de arquivos TSV e também consultar arquivos TSV sem ingestão. Este guia aborda esses dois casos. Se você precisar consultar ou fazer a ingestão de arquivos CSV, as mesmas técnicas funcionam: basta substituir TSV por CSV nos argumentos de formato.
Ao seguir este guia, você vai:
- Investigar: consultar a estrutura e o conteúdo do arquivo TSV.
- Determinar o esquema de destino no ClickHouse: escolher os tipos de dados adequados e mapear os dados existentes para esses tipos.
- Criar uma tabela no ClickHouse.
- Pré-processar e enviar em stream os dados para o ClickHouse.
- Executar algumas consultas no ClickHouse.
O conjunto de dados usado neste guia vem da equipe do NYC Open Data e contém dados sobre "todos os crimes válidos de felony, misdemeanor e violation reportados ao New York City Police Department (NYPD)". No momento da redação, o arquivo de dados tem 166 MB, mas é atualizado regularmente.
Origem: data.cityofnewyork.us Termos de uso: https://www1.nyc.gov/home/terms-of-use.page
Pré-requisitos
- Baixe o conjunto de dados acessando a página NYPD Complaint Data Current (Year To Date), clicando no botão Export e selecionando TSV for Excel.
- Instale o servidor e o cliente do ClickHouse
Uma observação sobre os comandos descritos neste guia
Há dois tipos de comandos neste guia:
- Alguns comandos consultam os arquivos TSV; eles são executados no prompt de comando.
- Os demais comandos consultam o ClickHouse e são executados no
clickhouse-clientou na UI Play.
Conheça o arquivo TSV
Antes de começar a trabalhar com o banco de dados ClickHouse, conheça os dados.
Veja os campos do arquivo TSV de origem
Este é um exemplo de comando para consultar um arquivo TSV, mas não o execute ainda.
clickhouse-local --query \
"describe file('${HOME}/NYPD_Complaint_Data_Current__Year_To_Date_.tsv', 'TSVWithNames')"Resposta de exemplo
CMPLNT_NUM Nullable(Float64)
ADDR_PCT_CD Nullable(Float64)
BORO_NM Nullable(String)
CMPLNT_FR_DT Nullable(String)
CMPLNT_FR_TM Nullable(String)Execute este comando no prompt de comando. Você usará clickhouse-local para consultar os dados no arquivo TSV que baixou.
clickhouse-local --input_format_max_rows_to_read_for_schema_inference=2000 \
--query \
"describe file('${HOME}/NYPD_Complaint_Data_Current__Year_To_Date_.tsv', 'TSVWithNames')"CMPLNT_NUM Nullable(String)
ADDR_PCT_CD Nullable(Float64)
BORO_NM Nullable(String)
CMPLNT_FR_DT Nullable(String)
CMPLNT_FR_TM Nullable(String)
CMPLNT_TO_DT Nullable(String)
CMPLNT_TO_TM Nullable(String)
CRM_ATPT_CPTD_CD Nullable(String)
HADEVELOPT Nullable(String)
HOUSING_PSA Nullable(Float64)
JURISDICTION_CODE Nullable(Float64)
JURIS_DESC Nullable(String)
KY_CD Nullable(Float64)
LAW_CAT_CD Nullable(String)
LOC_OF_OCCUR_DESC Nullable(String)
OFNS_DESC Nullable(String)
PARKS_NM Nullable(String)
PATROL_BORO Nullable(String)
PD_CD Nullable(Float64)
PD_DESC Nullable(String)
PREM_TYP_DESC Nullable(String)
RPT_DT Nullable(String)
STATION_NAME Nullable(String)
SUSP_AGE_GROUP Nullable(String)
SUSP_RACE Nullable(String)
SUSP_SEX Nullable(String)
TRANSIT_DISTRICT Nullable(Float64)
VIC_AGE_GROUP Nullable(String)
VIC_RACE Nullable(String)
VIC_SEX Nullable(String)
X_COORD_CD Nullable(Float64)
Y_COORD_CD Nullable(Float64)
Latitude Nullable(Float64)
Longitude Nullable(Float64)
Lat_Lon Tuple(Nullable(Float64), Nullable(Float64))
New Georeferenced Column Nullable(String)Neste ponto, você deve verificar se as colunas do arquivo TSV correspondem aos nomes e tipos especificados na seção Columns in this Dataset da página do conjunto de dados. Os tipos de dados não são muito específicos: todos os campos numéricos estão definidos como Nullable(Float64), e todos os demais campos como Nullable(String). Ao criar uma tabela no ClickHouse para armazenar os dados, você pode especificar tipos mais adequados e com melhor desempenho.
Determine o esquema adequado
Para determinar quais tipos devem ser usados nos campos, é necessário saber como os dados se apresentam. Por exemplo, o campo JURISDICTION_CODE é numérico: ele deve ser UInt8 ou Enum, ou Float64 seria apropriado?
clickhouse-local --input_format_max_rows_to_read_for_schema_inference=2000 \
--query \
"select JURISDICTION_CODE, count() FROM
file('${HOME}/NYPD_Complaint_Data_Current__Year_To_Date_.tsv', 'TSVWithNames')
GROUP BY JURISDICTION_CODE
ORDER BY JURISDICTION_CODE
FORMAT PrettyCompact"┌─JURISDICTION_CODE─┬─count()─┐
│ 0 │ 188875 │
│ 1 │ 4799 │
│ 2 │ 13833 │
│ 3 │ 656 │
│ 4 │ 51 │
│ 6 │ 5 │
│ 7 │ 2 │
│ 9 │ 13 │
│ 11 │ 14 │
│ 12 │ 5 │
│ 13 │ 2 │
│ 14 │ 70 │
│ 15 │ 20 │
│ 72 │ 159 │
│ 87 │ 9 │
│ 88 │ 75 │
│ 97 │ 405 │
└───────────────────┴─────────┘A resposta da consulta mostra que JURISDICTION_CODE se ajusta bem a um UInt8.
Da mesma forma, examine alguns dos campos String e veja se eles se encaixam bem como campos DateTime ou LowCardinality(String).
Por exemplo, o campo PARKS_NM é descrito como "Nome do parque, playground ou área verde em Nova York onde ocorreu o registro, se aplicável (parques estaduais não estão incluídos)". Os nomes dos parques da cidade de Nova York podem ser um bom candidato para LowCardinality(String):
clickhouse-local --input_format_max_rows_to_read_for_schema_inference=2000 \
--query \
"select count(distinct PARKS_NM) FROM
file('${HOME}/NYPD_Complaint_Data_Current__Year_To_Date_.tsv', 'TSVWithNames')
FORMAT PrettyCompact"┌─uniqExact(PARKS_NM)─┐
│ 319 │
└─────────────────────┘Confira alguns dos nomes dos parques:
clickhouse-local --input_format_max_rows_to_read_for_schema_inference=2000 \
--query \
"select distinct PARKS_NM FROM
file('${HOME}/NYPD_Complaint_Data_Current__Year_To_Date_.tsv', 'TSVWithNames')
LIMIT 10
FORMAT PrettyCompact"┌─PARKS_NM───────────────────┐
│ (null) │
│ ASSER LEVY PARK │
│ JAMES J WALKER PARK │
│ BELT PARKWAY/SHORE PARKWAY │
│ PROSPECT PARK │
│ MONTEFIORE SQUARE │
│ SUTTON PLACE PARK │
│ JOYCE KILMER PARK │
│ ALLEY ATHLETIC PLAYGROUND │
│ ASTORIA PARK │
└────────────────────────────┘O conjunto de dados em uso no momento da redação tem apenas algumas centenas de parques e playgrounds distintos na coluna PARK_NM. Esse é um número baixo, com base na recomendação de LowCardinality de manter menos de 10.000 strings distintas em um campo LowCardinality(String).
Campos DateTime
Com base na seção Columns in this Dataset da página do conjunto de dados, há campos de data e hora para o início e o fim do evento relatado. Observar os valores mínimo e máximo de CMPLNT_FR_DT e CMPLT_TO_DT ajuda a entender se esses campos são sempre preenchidos ou não:
clickhouse-local --input_format_max_rows_to_read_for_schema_inference=2000 \
--query \
"select min(CMPLNT_FR_DT), max(CMPLNT_FR_DT) FROM
file('${HOME}/NYPD_Complaint_Data_Current__Year_To_Date_.tsv', 'TSVWithNames')
FORMAT PrettyCompact"┌─min(CMPLNT_FR_DT)─┬─max(CMPLNT_FR_DT)─┐
│ 01/01/1973 │ 12/31/2021 │
└───────────────────┴───────────────────┘clickhouse-local --input_format_max_rows_to_read_for_schema_inference=2000 \
--query \
"select min(CMPLNT_TO_DT), max(CMPLNT_TO_DT) FROM
file('${HOME}/NYPD_Complaint_Data_Current__Year_To_Date_.tsv', 'TSVWithNames')
FORMAT PrettyCompact"┌─min(CMPLNT_TO_DT)─┬─max(CMPLNT_TO_DT)─┐
│ │ 12/31/2021 │
└───────────────────┴───────────────────┘clickhouse-local --input_format_max_rows_to_read_for_schema_inference=2000 \
--query \
"select min(CMPLNT_FR_TM), max(CMPLNT_FR_TM) FROM
file('${HOME}/NYPD_Complaint_Data_Current__Year_To_Date_.tsv', 'TSVWithNames')
FORMAT PrettyCompact"┌─min(CMPLNT_FR_TM)─┬─max(CMPLNT_FR_TM)─┐
│ 00:00:00 │ 23:59:00 │
└───────────────────┴───────────────────┘clickhouse-local --input_format_max_rows_to_read_for_schema_inference=2000 \
--query \
"select min(CMPLNT_TO_TM), max(CMPLNT_TO_TM) FROM
file('${HOME}/NYPD_Complaint_Data_Current__Year_To_Date_.tsv', 'TSVWithNames')
FORMAT PrettyCompact"┌─min(CMPLNT_TO_TM)─┬─max(CMPLNT_TO_TM)─┐
│ (null) │ 23:59:00 │
└───────────────────┴───────────────────┘Elabore um plano
Com base na investigação acima:
JURISDICTION_CODEdeve ser convertido paraUInt8.PARKS_NMdeve ser convertido paraLowCardinality(String)CMPLNT_FR_DTeCMPLNT_FR_TMestão sempre preenchidos (possivelmente com o horário padrão00:00:00)CMPLNT_TO_DTeCMPLNT_TO_TMpodem estar vazios- As datas e os horários são armazenados em campos separados na origem
- As datas estão no formato
mm/dd/yyyy - Os horários estão no formato
hh:mm:ss - Datas e horários podem ser concatenados em tipos DateTime
- Há algumas datas anteriores a 1º de janeiro de 1970, o que significa que precisamos de um DateTime de 64 bits
Concatene os campos de data e hora
Para concatenar os campos de data e hora CMPLNT_FR_DT e CMPLNT_FR_TM em uma única String que possa ser convertida para DateTime, selecione os dois campos unidos pelo operador de concatenação: CMPLNT_FR_DT || ' ' || CMPLNT_FR_TM. Os campos CMPLNT_TO_DT e CMPLNT_TO_TM são tratados da mesma forma.
clickhouse-local --input_format_max_rows_to_read_for_schema_inference=2000 \
--query \
"select CMPLNT_FR_DT || ' ' || CMPLNT_FR_TM AS complaint_begin FROM
file('${HOME}/NYPD_Complaint_Data_Current__Year_To_Date_.tsv', 'TSVWithNames')
LIMIT 10
FORMAT PrettyCompact"┌─complaint_begin─────┐
│ 07/29/2010 00:01:00 │
│ 12/01/2011 12:00:00 │
│ 04/01/2017 15:00:00 │
│ 03/26/2018 17:20:00 │
│ 01/01/2019 00:00:00 │
│ 06/14/2019 00:00:00 │
│ 11/29/2021 20:00:00 │
│ 12/04/2021 00:35:00 │
│ 12/05/2021 12:50:00 │
│ 12/07/2021 20:30:00 │
└─────────────────────┘Converta a String de data e hora em um tipo DateTime64
Anteriormente, neste guia, vimos que há datas no arquivo TSV anteriores a 1º de janeiro de 1970, o que significa que precisamos de um tipo DateTime de 64 bits para essas datas. As datas também precisam ser convertidas do formato MM/DD/YYYY para YYYY/MM/DD. Ambas as tarefas podem ser feitas com parseDateTime64BestEffort().
clickhouse-local --input_format_max_rows_to_read_for_schema_inference=2000 \
--query \
"WITH (CMPLNT_FR_DT || ' ' || CMPLNT_FR_TM) AS CMPLNT_START,
(CMPLNT_TO_DT || ' ' || CMPLNT_TO_TM) AS CMPLNT_END
select parseDateTime64BestEffort(CMPLNT_START) AS complaint_begin,
parseDateTime64BestEffortOrNull(CMPLNT_END) AS complaint_end
FROM file('${HOME}/NYPD_Complaint_Data_Current__Year_To_Date_.tsv', 'TSVWithNames')
ORDER BY complaint_begin ASC
LIMIT 25
FORMAT PrettyCompact"As linhas 2 e 3 acima contêm a concatenação da etapa anterior, e as linhas 4 e 5 acima fazem o parse das strings para DateTime64. Como não há garantia de que o horário de término da reclamação exista, usa-se parseDateTime64BestEffortOrNull.
┌─────────complaint_begin─┬───────────complaint_end─┐
│ 1925-01-01 10:00:00.000 │ 2021-02-12 09:30:00.000 │
│ 1925-01-01 11:37:00.000 │ 2022-01-16 11:49:00.000 │
│ 1925-01-01 15:00:00.000 │ 2021-12-31 00:00:00.000 │
│ 1925-01-01 15:00:00.000 │ 2022-02-02 22:00:00.000 │
│ 1925-01-01 19:00:00.000 │ 2022-04-14 05:00:00.000 │
│ 1955-09-01 19:55:00.000 │ 2022-08-01 00:45:00.000 │
│ 1972-03-17 11:40:00.000 │ 2022-03-17 11:43:00.000 │
│ 1972-05-23 22:00:00.000 │ 2022-05-24 09:00:00.000 │
│ 1972-05-30 23:37:00.000 │ 2022-05-30 23:50:00.000 │
│ 1972-07-04 02:17:00.000 │ ᴺᵁᴸᴸ │
│ 1973-01-01 00:00:00.000 │ ᴺᵁᴸᴸ │
│ 1975-01-01 00:00:00.000 │ ᴺᵁᴸᴸ │
│ 1976-11-05 00:01:00.000 │ 1988-10-05 23:59:00.000 │
│ 1977-01-01 00:00:00.000 │ 1977-01-01 23:59:00.000 │
│ 1977-12-20 00:01:00.000 │ ᴺᵁᴸᴸ │
│ 1981-01-01 00:01:00.000 │ ᴺᵁᴸᴸ │
│ 1981-08-14 00:00:00.000 │ 1987-08-13 23:59:00.000 │
│ 1983-01-07 00:00:00.000 │ 1990-01-06 00:00:00.000 │
│ 1984-01-01 00:01:00.000 │ 1984-12-31 23:59:00.000 │
│ 1985-01-01 12:00:00.000 │ 1987-12-31 15:00:00.000 │
│ 1985-01-11 09:00:00.000 │ 1985-12-31 12:00:00.000 │
│ 1986-03-16 00:05:00.000 │ 2022-03-16 00:45:00.000 │
│ 1987-01-07 00:00:00.000 │ 1987-01-09 00:00:00.000 │
│ 1988-04-03 18:30:00.000 │ 2022-08-03 09:45:00.000 │
│ 1988-07-29 12:00:00.000 │ 1990-07-27 22:00:00.000 │
└─────────────────────────┴─────────────────────────┘Criar uma tabela
As decisões tomadas acima sobre os tipos de dados usados nas colunas estão refletidas no esquema da tabela
abaixo. Também precisamos definir o ORDER BY e a PRIMARY KEY da tabela. Pelo menos um
entre ORDER BY e PRIMARY KEY deve ser especificado. Aqui estão algumas diretrizes para decidir quais
colunas incluir em ORDER BY; mais informações estão na seção Próximos passos no final
deste documento.
Cláusulas ORDER BY e PRIMARY KEY
- A tupla
ORDER BYdeve incluir campos usados nos filtros da consulta - Para maximizar a compressão em disco, a tupla
ORDER BYdeve ser ordenada por cardinalidade crescente - Se existir, a tupla
PRIMARY KEYdeve ser um subconjunto da tuplaORDER BY - Se apenas
ORDER BYfor especificada, a mesma tupla será usada comoPRIMARY KEY - O índice da chave primária é criado usando a tupla
PRIMARY KEY, se especificada; caso contrário, a tuplaORDER BY - O índice
PRIMARY KEYé mantido na memória principal
Ao analisar o conjunto de dados e as perguntas que poderiam ser respondidas ao consultá-lo, podemos
decidir que gostaríamos de observar os tipos de crimes reportados ao longo do tempo nos cinco distritos da
cidade de Nova York. Esses campos poderiam então ser incluídos no ORDER BY:
| Coluna | Descrição (do dicionário de dados) |
|---|---|
| OFNS_DESC | Descrição da infração correspondente ao código |
| RPT_DT | Data em que o evento foi reportado à polícia |
| BORO_NM | Nome do distrito em que o incidente ocorreu |
Consultando o arquivo TSV para a cardinalidade das três colunas candidatas:
clickhouse-local --input_format_max_rows_to_read_for_schema_inference=2000 \
--query \
"select formatReadableQuantity(uniq(OFNS_DESC)) as cardinality_OFNS_DESC,
formatReadableQuantity(uniq(RPT_DT)) as cardinality_RPT_DT,
formatReadableQuantity(uniq(BORO_NM)) as cardinality_BORO_NM
FROM
file('${HOME}/NYPD_Complaint_Data_Current__Year_To_Date_.tsv', 'TSVWithNames')
FORMAT PrettyCompact"┌─cardinality_OFNS_DESC─┬─cardinality_RPT_DT─┬─cardinality_BORO_NM─┐
│ 60.00 │ 306.00 │ 6.00 │
└───────────────────────┴────────────────────┴─────────────────────┘Ao ordenar por cardinalidade, o ORDER BY fica:
ORDER BY ( BORO_NM, OFNS_DESC, RPT_DT )Ao combinar as alterações nos tipos de dados e a tupla ORDER BY, chega-se a esta estrutura de tabela:
CREATE TABLE NYPD_Complaint (
complaint_number String,
precinct UInt8,
borough LowCardinality(String),
complaint_begin DateTime64(0,'America/New_York'),
complaint_end DateTime64(0,'America/New_York'),
was_crime_completed String,
housing_authority String,
housing_level_code UInt32,
jurisdiction_code UInt8,
jurisdiction LowCardinality(String),
offense_code UInt8,
offense_level LowCardinality(String),
location_descriptor LowCardinality(String),
offense_description LowCardinality(String),
park_name LowCardinality(String),
patrol_borough LowCardinality(String),
PD_CD UInt16,
PD_DESC String,
location_type LowCardinality(String),
date_reported Date,
transit_station LowCardinality(String),
suspect_age_group LowCardinality(String),
suspect_race LowCardinality(String),
suspect_sex LowCardinality(String),
transit_district UInt8,
victim_age_group LowCardinality(String),
victim_race LowCardinality(String),
victim_sex LowCardinality(String),
NY_x_coordinate UInt32,
NY_y_coordinate UInt32,
Latitude Float64,
Longitude Float64
) ENGINE = MergeTree
ORDER BY ( borough, offense_description, date_reported )Encontrando a chave primária de uma tabela
O banco de dados system do ClickHouse, mais especificamente system.table, contém todas as informações sobre a tabela que você
acabou de criar. Esta consulta mostra o ORDER BY (chave de ordenação) e a PRIMARY KEY:
SELECT
partition_key,
sorting_key,
primary_key,
table
FROM system.tables
WHERE table = 'NYPD_Complaint'
FORMAT VerticalResposta
Query id: 6a5b10bf-9333-4090-b36e-c7f08b1d9e01
Linha 1:
──────
partition_key:
sorting_key: borough, offense_description, date_reported
primary_key: borough, offense_description, date_reported
table: NYPD_Complaint
1 row in set. Elapsed: 0.001 sec.Pré-processar e importar dados
Usaremos a ferramenta clickhouse-local para pré-processar os dados e o clickhouse-client para enviá-los.
Argumentos usados no clickhouse-local
cat ${HOME}/NYPD_Complaint_Data_Current__Year_To_Date_.tsv \Valide os dados
SELECT count()
FROM NYPD_Complaint┌─count()─┐
│ 208993 │
└─────────┘
1 row in set. Elapsed: 0.001 sec.O tamanho do conjunto de dados no ClickHouse corresponde a apenas 12% do arquivo TSV original; compare o tamanho do arquivo TSV original com o da tabela:
SELECT formatReadableSize(total_bytes)
FROM system.tables
WHERE name = 'NYPD_Complaint'┌─formatReadableSize(total_bytes)─┐
│ 8.63 MiB │
└─────────────────────────────────┘Execute algumas consultas
Consulta 1. Compare o número de reclamações por mês
SELECT
dateName('month', date_reported) AS month,
count() AS complaints,
bar(complaints, 0, 50000, 80)
FROM NYPD_Complaint
GROUP BY month
ORDER BY complaints DESCQuery id: 7fbd4244-b32a-4acf-b1f3-c3aa198e74d9
┌─month─────┬─complaints─┬─bar(count(), 0, 50000, 80)───────────────────────────────┐
│ March │ 34536 │ ███████████████████████████████████████████████████████▎ │
│ May │ 34250 │ ██████████████████████████████████████████████████████▋ │
│ April │ 32541 │ ████████████████████████████████████████████████████ │
│ January │ 30806 │ █████████████████████████████████████████████████▎ │
│ February │ 28118 │ ████████████████████████████████████████████▊ │
│ November │ 7474 │ ███████████▊ │
│ December │ 7223 │ ███████████▌ │
│ October │ 7070 │ ███████████▎ │
│ September │ 6910 │ ███████████ │
│ August │ 6801 │ ██████████▊ │
│ June │ 6779 │ ██████████▋ │
│ July │ 6485 │ ██████████▍ │
└───────────┴────────────┴──────────────────────────────────────────────────────────┘
12 rows in set. Elapsed: 0.006 sec. Processed 208.99 thousand rows, 417.99 KB (37.48 million rows/s., 74.96 MB/s.)Consulta 2. Compare o número total de reclamações por distrito
SELECT
borough,
count() AS complaints,
bar(complaints, 0, 125000, 60)
FROM NYPD_Complaint
GROUP BY borough
ORDER BY complaints DESCQuery id: 8cdcdfd4-908f-4be0-99e3-265722a2ab8d
┌─borough───────┬─complaints─┬─bar(count(), 0, 125000, 60)──┐
│ BROOKLYN │ 57947 │ ███████████████████████████▋ │
│ MANHATTAN │ 53025 │ █████████████████████████▍ │
│ QUEENS │ 44875 │ █████████████████████▌ │
│ BRONX │ 44260 │ █████████████████████▏ │
│ STATEN ISLAND │ 8503 │ ████ │
│ (null) │ 383 │ ▏ │
└───────────────┴────────────┴──────────────────────────────┘
6 rows in set. Elapsed: 0.008 sec. Processed 208.99 thousand rows, 209.43 KB (27.14 million rows/s., 27.20 MB/s.)Próximos passos
Uma introdução prática aos índices primários esparsos no ClickHouse discute as diferenças de indexação no ClickHouse em comparação com bancos de dados relacionais tradicionais, como o ClickHouse cria e usa um índice primário esparso e as práticas recomendadas de indexação.