Sunday, 23 August 2026

Joins in ClickHouse: Inner, Left, Right, Full, and Cross Explained

  

ClickHouse is a powerful columnar database widely used for real-time analytics. While it's very fast for aggregations and filtering, many developers—especially those transitioning from traditional relational databases often find its SQL joins slightly different or confusing.

 

This post helps you to understand Joins in ClickHouse, and I am going to cover following topics.

·      Inner Join

·      Left Join

·      Right Join

·      Full Outer Join (simulated)

·      Cross Join

 

I'll walk through each join type using practical, easy-to-understand examples involving two simple tables employee and address. This helps not only in understanding the syntax and behavior of each join, but also how to apply them in real-world data analytics scenarios.

 

Whether you're just getting started with ClickHouse or looking to deepen your understanding of SQL join operations in a columnar engine, this post will give you a solid foundation.

 

1. Preparing Dataset to demo the Join examples

Let’s dive deep into the different types of joins in ClickHouse, using practical examples. To demo the examples, I am going to use two tables:

 

·      employee: contains employee information.

·      address: contains employee addresses.

 

1.1 Table Definitions

employee table

emp_id

name

department

1

Rahul Sharma

HR

2

Priya Mehra

IT

3

Arjun Verma

Finance

4

Neha Kapoor

Marketing

5

Aman Gupta

Sales

 

address table

emp_id

city

state

1

Mumbai

Maharashtra

2

Bengaluru

Karnataka

3

Delhi

Delhi

6

Hyderabad

Telangana

 

1.2 DDL to create tables

CREATE DATABASE IF NOT EXISTS demo_db;

CREATE TABLE demo_db.employee
(
    emp_id UInt32,
    name String,
    department String
) ENGINE = MergeTree()
ORDER BY emp_id;

CREATE TABLE demo_db.address
(
    emp_id UInt32,
    city String,
    state String
) ENGINE = MergeTree()
ORDER BY emp_id;

   

1.3 Insert statements for sample Data

 

employee Table:

 

INSERT INTO demo_db.employee (emp_id, name, department) VALUES
(1, 'Rahul Sharma', 'HR'),
(2, 'Priya Mehra', 'IT'),
(3, 'Arjun Verma', 'Finance'),
(4, 'Neha Kapoor', 'Marketing'),
(5, 'Aman Gupta', 'Sales');

   

address Table:

 

INSERT INTO demo_db.address (emp_id, city, state) VALUES
(1, 'Mumbai', 'Maharashtra'),
(2, 'Bengaluru', 'Karnataka'),
(3, 'Delhi', 'Delhi'),
(6, 'Hyderabad', 'Telangana');

   

1.4 Print the records of employee and address tables.

 

krishna :) SELECT * FROM demo_db.employee;

SELECT *
FROM demo_db.employee

Query id: d57139f1-b9d0-447e-b1e3-36cb8e34814b

   ┌─emp_id─┬─name─────────┬─department─┐
1. │      1 │ Rahul Sharma │ HR         │
2. │      2 │ Priya Mehra  │ IT         │
3. │      3 │ Arjun Verma  │ Finance    │
4. │      4 │ Neha Kapoor  │ Marketing  │
5. │      5 │ Aman Gupta   │ Sales      │
   └────────┴──────────────┴────────────┘

5 rows in set. Elapsed: 0.013 sec. 

krishna :) ;

Empty query

krishna :) SELECT * FROM demo_db.address;

SELECT *
FROM demo_db.address

Query id: ef83be21-d120-4529-b1c2-fd6e4e842e2c

   ┌─emp_id─┬─city──────┬─state───────┐
1. │      1 │ Mumbai    │ Maharashtra │
2. │      2 │ Bengaluru │ Karnataka   │
3. │      3 │ Delhi     │ Delhi       │
4. │      6 │ Hyderabad │ Telangana   │
   └────────┴───────────┴─────────────┘

4 rows in set. Elapsed: 0.005 sec.

   

2. INNER JOIN

An INNER JOIN returns only the rows where there is a match in both tables based on the condition specified in the ON clause.

 

Key Characteristics

·      Only matching rows from both tables are returned.

·      If there is no match, the row is excluded from the result.

·      It is the default join type (i.e., JOIN alone means INNER JOIN).

 

Example

 

SELECT 
    e.emp_id, e.name, e.department,
    a.city, a.state
FROM demo_db.employee AS e
INNER JOIN demo_db.address AS a 
ON e.emp_id = a.emp_id;

   

(or) you can omit INNER

SELECT 
    e.emp_id, e.name, e.department,
    a.city, a.state
FROM demo_db.employee AS e
JOIN demo_db.address AS a 
ON e.emp_id = a.emp_id;  

Above SQL queries return only the records that have matching emp_id in both tables.

krishna :) SELECT 
    e.emp_id, e.name, e.department,
    a.city, a.state
FROM demo_db.employee AS e
JOIN demo_db.address AS a 
ON e.emp_id = a.emp_id;

SELECT
    e.emp_id,
    e.name,
    e.department,
    a.city,
    a.state
FROM demo_db.employee AS e
INNER JOIN demo_db.address AS a ON e.emp_id = a.emp_id

Query id: cbbc8fcd-5e0f-475c-a1ff-89fc959f8ec0

   ┌─emp_id─┬─name─────────┬─department─┬─city──────┬─state───────┐
1. │      1 │ Rahul Sharma │ HR         │ Mumbai    │ Maharashtra │
2. │      2 │ Priya Mehra  │ IT         │ Bengaluru │ Karnataka   │
3. │      3 │ Arjun Verma  │ Finance    │ Delhi     │ Delhi       │
   └────────┴──────────────┴────────────┴───────────┴─────────────┘

3 rows in set. Elapsed: 0.007 sec. 

krishna :) ;

Empty query

krishna :) SELECT 
    e.emp_id, e.name, e.department,
    a.city, a.state
FROM demo_db.employee AS e
INNER JOIN demo_db.address AS a 
ON e.emp_id = a.emp_id;

SELECT
    e.emp_id,
    e.name,
    e.department,
    a.city,
    a.state
FROM demo_db.employee AS e
INNER JOIN demo_db.address AS a ON e.emp_id = a.emp_id

Query id: 6cdbda4f-ed22-4dd8-baab-a3ae735e45d6

   ┌─emp_id─┬─name─────────┬─department─┬─city──────┬─state───────┐
1. │      1 │ Rahul Sharma │ HR         │ Mumbai    │ Maharashtra │
2. │      2 │ Priya Mehra  │ IT         │ Bengaluru │ Karnataka   │
3. │      3 │ Arjun Verma  │ Finance    │ Delhi     │ Delhi       │
   └────────┴──────────────┴────────────┴───────────┴─────────────┘

3 rows in set. Elapsed: 0.007 sec.

   

3. LEFT JOIN

The LEFT JOIN keyword returns all records from the left table (table1), and the matching records from the right table (table2). If there is no match, the result is NULL for columns from the right table.

 

Explanation in detail

·      LEFT JOIN (also called LEFT OUTER JOIN) includes all rows from the left table.

·      For each row in the left table:

o   If a matching row is found in the right table (based on the ON condition), the columns from the right table are included.

o   If no match is found, NULL values are used for the columns from the right table.

·      The number of rows returned is at least as many as in the left table.

 

 

SELECT 
    e.emp_id, e.name, e.department,
    a.city, a.state
FROM demo_db.employee AS e
LEFT JOIN demo_db.address AS a 
ON e.emp_id = a.emp_id;

   

Above statement returns all records from employee, and the matched records from address. If no match, NULL values are shown for address data.

 

krishna :) SELECT 
    e.emp_id, e.name, e.department,
    a.city, a.state
FROM demo_db.employee AS e
LEFT JOIN demo_db.address AS a 
ON e.emp_id = a.emp_id;

SELECT
    e.emp_id,
    e.name,
    e.department,
    a.city,
    a.state
FROM demo_db.employee AS e
LEFT JOIN demo_db.address AS a ON e.emp_id = a.emp_id

Query id: 87231877-dcc8-4c5f-9400-c11ca9cb24fa

   ┌─emp_id─┬─name─────────┬─department─┬─city──────┬─state───────┐
1. │      1 │ Rahul Sharma │ HR         │ Mumbai    │ Maharashtra │
2. │      2 │ Priya Mehra  │ IT         │ Bengaluru │ Karnataka   │
3. │      3 │ Arjun Verma  │ Finance    │ Delhi     │ Delhi       │
4. │      4 │ Neha Kapoor  │ Marketing  │           │             │
5. │      5 │ Aman Gupta   │ Sales      │           │             │
   └────────┴──────────────┴────────────┴───────────┴─────────────┘

5 rows in set. Elapsed: 0.003 sec.

   

As you see the result, All employees are listed from employees table, even if address is missing for the records 4 and 5.

 

4. RIGHT JOIN

A RIGHT JOIN returns all rows from the right table, and the matching rows from the left table.

If there is no match, the result will have NULLs for columns from the left table.

 

SELECT 
    e.emp_id, e.name, e.department,
    a.city, a.state
FROM demo_db.employee AS e
RIGHT JOIN demo_db.address AS a 
ON e.emp_id = a.emp_id;

krishna :) SELECT 
    e.emp_id, e.name, e.department,
    a.city, a.state
FROM demo_db.employee AS e
RIGHT JOIN demo_db.address AS a 
ON e.emp_id = a.emp_id;

SELECT
    e.emp_id,
    e.name,
    e.department,
    a.city,
    a.state
FROM demo_db.employee AS e
RIGHT JOIN demo_db.address AS a ON e.emp_id = a.emp_id

Query id: 86435bf2-2f1a-4112-bf14-a2647e3a158d

   ┌─emp_id─┬─name─────────┬─department─┬─city──────┬─state───────┐
1. │      1 │ Rahul Sharma │ HR         │ Mumbai    │ Maharashtra │
2. │      2 │ Priya Mehra  │ IT         │ Bengaluru │ Karnataka   │
3. │      3 │ Arjun Verma  │ Finance    │ Delhi     │ Delhi       │
4. │      0 │              │            │ Hyderabad │ Telangana   │
   └────────┴──────────────┴────────────┴───────────┴─────────────┘

4 rows in set. Elapsed: 0.008 sec. 

As you observe the result of RIGHT JOIN query, it returns all records from address, and the matched records from employee, whereover there is no match in employee table, it has NULL values for the columns.

 

5. CROSS JOIN

A CROSS JOIN returns the Cartesian product of the two tables, meaning it joins every row of the first table with every row of the second table, regardless of any condition. If table A has m rows and table B has n rows, the CROSS JOIN will return m × n rows.

 

Example

SELECT 
    e.emp_id AS emp_id_e, e.name, e.department,
    a.emp_id AS emp_id_a, a.city, a.state
FROM demo_db.employee AS e
CROSS JOIN demo_db.address AS a;

krishna :) SELECT 
    e.emp_id AS emp_id_e, e.name, e.department,
    a.emp_id AS emp_id_a, a.city, a.state
FROM demo_db.employee AS e
CROSS JOIN demo_db.address AS a;

SELECT
    e.emp_id AS emp_id_e,
    e.name,
    e.department,
    a.emp_id AS emp_id_a,
    a.city,
    a.state
FROM demo_db.employee AS e
CROSS JOIN demo_db.address AS a

Query id: 9de64cd6-3364-4e7d-838d-2bd524b1dc91

    ┌─emp_id_e─┬─name─────────┬─department─┬─emp_id_a─┬─city──────┬─state───────┐
 1. │        1 │ Rahul Sharma │ HR         │        1 │ Mumbai    │ Maharashtra │
 2. │        1 │ Rahul Sharma │ HR         │        2 │ Bengaluru │ Karnataka   │
 3. │        1 │ Rahul Sharma │ HR         │        3 │ Delhi     │ Delhi       │
 4. │        1 │ Rahul Sharma │ HR         │        6 │ Hyderabad │ Telangana   │
 5. │        2 │ Priya Mehra  │ IT         │        1 │ Mumbai    │ Maharashtra │
 6. │        2 │ Priya Mehra  │ IT         │        2 │ Bengaluru │ Karnataka   │
 7. │        2 │ Priya Mehra  │ IT         │        3 │ Delhi     │ Delhi       │
 8. │        2 │ Priya Mehra  │ IT         │        6 │ Hyderabad │ Telangana   │
 9. │        3 │ Arjun Verma  │ Finance    │        1 │ Mumbai    │ Maharashtra │
10. │        3 │ Arjun Verma  │ Finance    │        2 │ Bengaluru │ Karnataka   │
11. │        3 │ Arjun Verma  │ Finance    │        3 │ Delhi     │ Delhi       │
12. │        3 │ Arjun Verma  │ Finance    │        6 │ Hyderabad │ Telangana   │
13. │        4 │ Neha Kapoor  │ Marketing  │        1 │ Mumbai    │ Maharashtra │
14. │        4 │ Neha Kapoor  │ Marketing  │        2 │ Bengaluru │ Karnataka   │
15. │        4 │ Neha Kapoor  │ Marketing  │        3 │ Delhi     │ Delhi       │
16. │        4 │ Neha Kapoor  │ Marketing  │        6 │ Hyderabad │ Telangana   │
17. │        5 │ Aman Gupta   │ Sales      │        1 │ Mumbai    │ Maharashtra │
18. │        5 │ Aman Gupta   │ Sales      │        2 │ Bengaluru │ Karnataka   │
19. │        5 │ Aman Gupta   │ Sales      │        3 │ Delhi     │ Delhi       │
20. │        5 │ Aman Gupta   │ Sales      │        6 │ Hyderabad │ Telangana   │
    └──────────┴──────────────┴────────────┴──────────┴───────────┴─────────────┘

20 rows in set. Elapsed: 0.008 sec.

   

6. FULL JOIN

A FULL JOIN (also called FULL OUTER JOIN) returns all records from both tables, matching where possible, and filling in NULLs where there's no match on either side.

 

In summary, a FULL JOIN combines the results of both a LEFT JOIN and a RIGHT JOIN. It returns:

·      Matching rows from both tables

·      Unmatched rows from the left table (with NULLs for right)

·      Unmatched rows from the right table (with NULLs for left)

 

Example

 

SELECT 
    e.emp_id AS emp_id_e, e.name, e.department,
    a.emp_id AS emp_id_a, a.city, a.state
FROM demo_db.employee AS e
FULL JOIN demo_db.address AS a
ON e.emp_id=a.emp_id;

krishna :) SELECT 
    e.emp_id AS emp_id_e, e.name, e.department,
    a.emp_id AS emp_id_a, a.city, a.state
FROM demo_db.employee AS e
FULL JOIN demo_db.address AS a
ON e.emp_id=a.emp_id;

SELECT
    e.emp_id AS emp_id_e,
    e.name,
    e.department,
    a.emp_id AS emp_id_a,
    a.city,
    a.state
FROM demo_db.employee AS e
FULL OUTER JOIN demo_db.address AS a ON e.emp_id = a.emp_id

Query id: 087fecca-4e1e-487d-a694-a6e7efa99fe4

   ┌─emp_id_e─┬─name─────────┬─department─┬─emp_id_a─┬─city──────┬─state───────┐
1. │        1 │ Rahul Sharma │ HR         │        1 │ Mumbai    │ Maharashtra │
2. │        2 │ Priya Mehra  │ IT         │        2 │ Bengaluru │ Karnataka   │
3. │        3 │ Arjun Verma  │ Finance    │        3 │ Delhi     │ Delhi       │
4. │        4 │ Neha Kapoor  │ Marketing  │        0 │           │             │
5. │        5 │ Aman Gupta   │ Sales      │        0 │           │             │
6. │        0 │              │            │        6 │ Hyderabad │ Telangana   │
   └──────────┴──────────────┴────────────┴──────────┴───────────┴─────────────┘

6 rows in set. Elapsed: 0.007 sec.

   

In summary, we explored the five fundamental types of SQL joins—INNER JOIN, LEFT JOIN, RIGHT JOIN, FULL OUTER JOIN, and CROSS JOIN—in the context of ClickHouse. Using practical examples, I demonstrated how each join type behaves with a real time example.


  

Previous                                                    Next                                                    Home

No comments:

Post a Comment