---
title: "ClickHouse® tips #9: Filling gaps in time-series on ClickHouse®"
excerpt: "This simple trick will teach you how to fill date and datetime gaps in time-series on ClickHouse®. Part 9."
authors: "Xoel Lopez"
categories: "The Data Base"
createdOn: "2023-09-25 00:00:00"
publishedOn: "2021-07-08 00:00:00"
updatedOn: "2022-09-08 00:00:00"
status: "published"
---

<h2 id="filling-gaps-and-resampling-time-series">Filling gaps and resampling time series</h2><p>In the previous tip, we showed you how to <a href="https://blog.tinybird.co/2021/05/18/tips-7-filling-null-values-on-clickhouse/">fill null values on ClickHouse®</a>. Many times, you will group data by a Date or a DateTime column, and there will be rows missing because for those periods there was no data.</p><p>To chart data, you’ll ideally have time series where the intervals between one data point and the next one are constant, so you’d want to fill in the empty periods with a value of 0. This is how you’d do it on ClickHouse®.</p><p>If you have a Data Source like this, with a DataTime column and an Int column counting visits:</p><figure class="kg-card kg-embed-card"><iframe loading="lazy" width="100%" src="https://snippets.tinybird.co/endpoint?code=SELECT%20%2A%20FROM%20visits_missing_some_dts%0A&amp;language=Tinybird&amp;title=Dataset%20with%20values%20missing%20in%20a%20time-series%20column&amp;run=run&amp;token=p.eyJ1IjogIjdmOTIwMmMzLWM1ZjctNDU4Ni1hZDUxLTdmYzUzNTRlMTk5YSIsICJpZCI6ICI3N2NiMDljOS1hYjVjLTQ2MzYtYTA4Yi00ZDYwNDQwMzUyZTYifQ.XhZPypxjhaeJbdJzNozOKmKPbFqM9CWkAil8WiXFaAE"></iframe></figure><h2 id="with-fill">WITH FILL</h2><p>ClickHouse® <a href="https://clickhouse.tech/docs/en/whats-new/changelog/2019/#clickhouse-release-19-14-3-3-2019-09-10">introduced</a> the <code>WITH FILL</code> modifier to <code>ORDER BY</code> clause a couple of years ago. You can read its full documentation <a href="https://clickhouse.tech/docs/en/sql-reference/statements/select/order-by/#orderby-with-fill">here</a>, but with this example you’ll understand what it does at a glance. It solves the problem we described above in one line, and in a very elegant way:</p><figure class="kg-card kg-embed-card"><iframe loading="lazy" width="100%" src="https://snippets.tinybird.co/endpoint?code=SELECT%20%2A%20FROM%20visits_missing_some_dts%0AORDER%20BY%20dt%20ASC%20WITH%20FILL%20STEP%201%0A&amp;language=Tinybird&amp;title=Filling%20gaps%20using%20the%20WITH%20FILL%20clause&amp;run=run&amp;token=p.eyJ1IjogIjdmOTIwMmMzLWM1ZjctNDU4Ni1hZDUxLTdmYzUzNTRlMTk5YSIsICJpZCI6ICI3N2NiMDljOS1hYjVjLTQ2MzYtYTA4Yi00ZDYwNDQwMzUyZTYifQ.XhZPypxjhaeJbdJzNozOKmKPbFqM9CWkAil8WiXFaAE"></iframe></figure><p>This works with Date, DateTime and any numerical column. The step is 1 by default, and on DateTime columns it’s one second and on Date columns it’s one day.</p><figure class="kg-card kg-embed-card"><iframe loading="lazy" width="100%" src="https://snippets.tinybird.co/endpoint?code=SELECT%20%2A%20FROM%20visits_missing_some_dts%0AORDER%20BY%20dt%20ASC%20WITH%20FILL%0A&amp;language=Tinybird&amp;title=Filling%20gaps%20using%20the%20WITH%20FILL%20clause&amp;run=run&amp;token=p.eyJ1IjogIjdmOTIwMmMzLWM1ZjctNDU4Ni1hZDUxLTdmYzUzNTRlMTk5YSIsICJpZCI6ICI3N2NiMDljOS1hYjVjLTQ2MzYtYTA4Yi00ZDYwNDQwMzUyZTYifQ.XhZPypxjhaeJbdJzNozOKmKPbFqM9CWkAil8WiXFaAE"></iframe></figure><h2 id="if-you-can%E2%80%99t-use-with-fill">If you can’t use WITH FILL</h2><p>If you’re running an older version of ClickHouse®, there’s still a way to do this. The way to fill those empty rows would be to generate a time series that contained every second in that time interval, that could be done like this:</p><figure class="kg-card kg-embed-card"><iframe loading="lazy" width="100%" src="https://snippets.tinybird.co/endpoint?code=WITH%0A%20%20%20%20%28SELECT%20min%28dt%29%20from%20visits_missing_some_dts%29%20AS%20start%2C%0A%20%20%20%20%28SELECT%20max%28dt%29%20from%20visits_missing_some_dts%29%20AS%20end%0ASELECT%20arrayJoin%28arrayMap%28x%20-%3E%20toDateTime%28x%29%2C%20range%28toUInt32%28start%29%2C%20toUInt32%28end%29%2C%201%29%29%29%20as%20dt%0A%0A&amp;language=Tinybird&amp;title=timeseries_without_gaps&amp;run=run&amp;token=p.eyJ1IjogIjdmOTIwMmMzLWM1ZjctNDU4Ni1hZDUxLTdmYzUzNTRlMTk5YSIsICJpZCI6ICI3N2NiMDljOS1hYjVjLTQ2MzYtYTA4Yi00ZDYwNDQwMzUyZTYifQ.XhZPypxjhaeJbdJzNozOKmKPbFqM9CWkAil8WiXFaAE"></iframe></figure><p>And then joining that with the original Data Source would give us this, with all the rows present, including those where there are no values present in the original one:</p><figure class="kg-card kg-embed-card"><iframe loading="lazy" width="100%" src="https://snippets.tinybird.co/endpoint?code=SELECT%20%2A%0AFROM%20%0A%20%20%28WITH%0A%20%20%20%20%20%20%28SELECT%20min%28dt%29%20from%20visits_missing_some_dts%29%20AS%20start%2C%0A%20%20%20%20%20%20%28SELECT%20max%28dt%29%20from%20visits_missing_some_dts%29%20AS%20end%0A%20%20%20%20SELECT%20arrayJoin%28arrayMap%28x%20-%3E%20toDateTime%28x%29%2C%20range%28toUInt32%28start%29%2C%20toUInt32%28end%29%2C%201%29%29%29%20as%20dt%29%20as%20full_dt%0ALEFT%20JOIN%20visits_missing_some_dts%20USING%20dt%0A&amp;language=Tinybird&amp;title=Original%20Data%20Source%20without%20gaps%20in%20the%20index&amp;run=run&amp;token=p.eyJ1IjogIjdmOTIwMmMzLWM1ZjctNDU4Ni1hZDUxLTdmYzUzNTRlMTk5YSIsICJpZCI6ICI3N2NiMDljOS1hYjVjLTQ2MzYtYTA4Yi00ZDYwNDQwMzUyZTYifQ.XhZPypxjhaeJbdJzNozOKmKPbFqM9CWkAil8WiXFaAE"></iframe></figure><p>Last: if you were using Tinybird, you could split the query in two nodes, and write the subquery in another node, that you could name <code>full_dt</code> for example (with the same content as the <code>timeseries_without_gaps</code> node from above). Doing that, the previous query could be rewritten like this, in a much more legible way (plus you get to see the results of intermediate subqueries in our notebook-like interface - quite handy).</p><p>If you don’t have a Tinybird account yet, sign up for one <a href="https://tinybird.co/signup">here</a></p>
