Skip to main content
In this tutorial, you’ll insert 28 million rows of Hacker News data into a ClickHouse table from both CSV and Parquet formats and run some simple queries to explore the data.

CSV

1

Download CSV

A CSV version of the dataset can be downloaded from our public S3 bucket, or by running this command:
At 4.6GB, and 28m rows, this compressed file should take 5-10 minutes to download.
2

Sample the data

clickhouse-local allows you to perform fast processing on local files without having to deploy and configure the ClickHouse server.Before storing any data in ClickHouse, let’s sample the file using clickhouse-local. From the console run:
Next, run the following command to explore the data:
Query
Response
There are a lot of subtle capabilities in this command. The file operator allows you to read the file from a local disk, specifying only the format CSVWithNames. Most importantly, the schema is automatically inferred for you from the file contents. Note also how clickhouse-local is able to read the compressed file, inferring the gzip format from the extension. The Vertical format is used to more easily see the data for each column.
3

Load the data with schema inference

The simplest and most powerful tool for data loading is the clickhouse-client: a feature-rich native command-line client. To load data, you can again exploit schema inference, relying on ClickHouse to determine the types of the columns.Run the following command to create a table and insert the data directly from the remote CSV file, accessing the contents via the url function. The schema is automatically inferred:
This creates an empty table using the schema inferred from the data. The DESCRIBE TABLE command allows us to understand these assigned types.
Query
Response
To insert the data into this table, use the INSERT INTO, SELECT command. Together with the url function, data will be streamed directly from the URL:
You’ve successfully inserted 28 million rows into ClickHouse with a single command!
4

Explore the data

Sample the Hacker News stories and specific columns by running the following query:
Query
Response
While schema inference is a great tool for initial data exploration, it is “best effort” and not a long-term substitute for defining an optimal schema for your data.
5

Define a schema

An obvious immediate optimization is to define a type for each field. In addition to declaring the time field as a DateTime type, we define an appropriate type for each of the fields below after dropping our existing dataset. In ClickHouse the primary key id for the data is defined via the ORDER BY clause.Selecting appropriate types and choosing which columns to include in the ORDER BY clause will help to improve query speed and compression.Run the query below to drop the old schema and create the improved schema:
Query
With an optimized schema, you can now insert the data from the local file system. Again using clickhouse-client, insert the file using the INFILE clause with an explicit INSERT INTO.
Query
6

Run sample queries

Some sample queries are presented below to give you inspiration for writing your own queries.

How pervasive a topic is “ClickHouse” in Hacker News?

The score field provides a metric of popularity for stories, while the id field and || concatenation operator can be used to produce a link to the original post.
Query
Response
Is ClickHouse generating more noise over time? Here the usefulness of defining the time field as a DateTime is shown, as using a proper data type allows you to use the toYYYYMM() function:
Query
Response
It looks like “ClickHouse” is growing in popularity with time.

Who are the top commenters on ClickHouse related articles?

Query
Response

Which comments generate the most interest?

Query
Response

Parquet

One of the strengths of ClickHouse is its ability to handle any number of formats. CSV represents a rather ideal use case, and isn’t the most efficient for data exchange. Next, you’ll load the data from a Parquet file which is an efficient column-oriented format. Parquet has minimal types, which ClickHouse needs to respect, and this type information is encoded in the format itself. Type inference on a Parquet file will invariably lead to a slightly different schema than the one for the CSV file.
1

Insert the data

Run the following query to read the same data in Parquet format, again using the url function to read the remote data:
Null keys with ParquetThe inferred schema makes the columns Nullable, so allow_nullable_key is required even though this dataset contains no null IDs.
Run the following command to view the inferred schema:
Query
Response
The remaining steps use clearer column names such as author and comment, so continue with a manually specified schema. First drop the inferred table, then create the table and insert the data directly from the public S3 bucket:
2

Add a text index to speed up searches

To find out how many comments mention “ClickHouse”, run the following query:
Query
Response
Next, create a text index on the comment column to speed up this query. A text index uses an inverted index that maps tokens to the rows that contain them. The splitByNonAlpha tokenizer splits text on non-alphanumeric characters. The index and queries use lower(comment) with lowercase search terms so matching is case-insensitive. The query expression must match the indexed expression.Run the following commands to create the index:
Materialization builds the index for the existing data. The mutations_sync setting waits for the materialization to finish. You can check the index definition in the system.data_skipping_indices table.Run the same query again once the index has been materialized:
Query
Response
The result remains the same because the index changes how ClickHouse finds matching rows, not which rows match. The indexed query processes substantially less data and completes much faster. Use EXPLAIN to confirm that ClickHouse plans to apply the index:
Query
Response
The comment_idx entry shows that ClickHouse plans to apply the text index. In this example, the plan selects 547 of 3527 granules, substantially reducing the amount of data examined.You can also search for any or all of multiple tokens. These functions match complete tokens produced by the index tokenizer. Use hasAnyTokens when at least one token must match:
Query
Response
Use hasAllTokens when every token must match, in any order:
Query
Response
Last modified on August 19, 2026