<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Nageen Yerramsetty</title>
    <description>The latest articles on Forem by Nageen Yerramsetty (@nageen20).</description>
    <link>https://forem.com/nageen20</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2453139%2Fd217ad13-54c7-4a72-b293-d86e0fabe5fc.png</url>
      <title>Forem: Nageen Yerramsetty</title>
      <link>https://forem.com/nageen20</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/nageen20"/>
    <language>en</language>
    <item>
      <title>DuckDB vs Pandas - Exploring DuckDB's capabilities</title>
      <dc:creator>Nageen Yerramsetty</dc:creator>
      <pubDate>Tue, 26 Nov 2024 08:09:50 +0000</pubDate>
      <link>https://forem.com/nageen20/duckdb-vs-pandas-exploring-duckdbs-capabilities-6kf</link>
      <guid>https://forem.com/nageen20/duckdb-vs-pandas-exploring-duckdbs-capabilities-6kf</guid>
      <description>&lt;p&gt;Ever since I came across DuckDB, I have been fascinated by its capabilities. For those who are not aware of DuckDB, it is a super fast in-process OLAP database. While I started to use it for ad-hoc analysis and noticed that it is blazingly fast, I only happen to use it on small datasets. In this blog post, I experiment with DuckDB using medium-sized datasets. We will see how DuckDB can handle over 100 million rows on my local machine. Note that all the testing is done on my local laptop which is a 4-core 16 GB machine running Windows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Datasets
&lt;/h2&gt;

&lt;p&gt;I used two large datasets that I found on Kaggle and a medium-sized proprietary dataset. Here are the details about the datasets:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;A proprietary dataset that has half a million rows and is hosted on MySQL server. Unfortunately, I won't be able to share details about the dataset, but below I have shared how DuckDB performed on this dataset.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.kaggle.com/datasets/dbdmobile/myanimelist-dataset" rel="noopener noreferrer"&gt;Anime dataset&lt;/a&gt; from Kaggle. This dataset contains the user ratings given to various anime. The dataset has three files. You can find the details below.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.kaggle.com/datasets/tzelal/binance-bitcoin-dataset-1s-timeframe-p1" rel="noopener noreferrer"&gt;Binance 1-second bitcoin dataset&lt;/a&gt; from Kaggle. This dataset contains the bitcoin rates at a second timeframe between 2017-08-17 and 2021-02-23. There is a part 2 to this data which you can find &lt;a href="https://www.kaggle.com/datasets/tzelal/binance-bitcoin-dataset-1s-timeframe-p2" rel="noopener noreferrer"&gt;here&lt;/a&gt;. I have only used the Part 1 for now.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dataset&lt;/th&gt;
&lt;th&gt;File name&lt;/th&gt;
&lt;th&gt;File type&lt;/th&gt;
&lt;th&gt;Rows count&lt;/th&gt;
&lt;th&gt;File size&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Anime dataset&lt;/td&gt;
&lt;td&gt;user_details&lt;/td&gt;
&lt;td&gt;csv&lt;/td&gt;
&lt;td&gt;700k&lt;/td&gt;
&lt;td&gt;73 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anime dataset&lt;/td&gt;
&lt;td&gt;anime_details&lt;/td&gt;
&lt;td&gt;csv&lt;/td&gt;
&lt;td&gt;24k&lt;/td&gt;
&lt;td&gt;15 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anime dataset&lt;/td&gt;
&lt;td&gt;user_ratings&lt;/td&gt;
&lt;td&gt;csv&lt;/td&gt;
&lt;td&gt;24 million&lt;/td&gt;
&lt;td&gt;1.1 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Binance 1-second bitcoin dataset&lt;/td&gt;
&lt;td&gt;half1_BTCUSDT_1s&lt;/td&gt;
&lt;td&gt;csv&lt;/td&gt;
&lt;td&gt;110 million&lt;/td&gt;
&lt;td&gt;12.68 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Table: &lt;em&gt;Anime &amp;amp; Binance 1-second bitcoin datasets -  files and details&lt;/em&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Results - Quick Summary
&lt;/h2&gt;

&lt;p&gt;Here is the summary of the results in order of the datasets shared in the previous step:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Proprietary dataset&lt;/strong&gt; - DuckDB processed the result of half a million rows in less than a second. If I consider the time it took to read the data from MySQL and the query process time, it came to around 55 seconds. MySQL took around 6 minutes to complete the same query! The query involved calculating active users in the last 6 months on a monthly rolling basis.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Anime dataset&lt;/strong&gt; - DuckDB breezed through the anime dataset. I have tried multiple things which I will discuss in detail later. For now, it was able to get the top-rated animes by calculating the average user ratings across 24 million rows in 18 seconds. There is a surprise query that made DuckDB sweat for 4.5minutes which will be discussed later. Pandas broke down on this last query with a memory allocation error.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Bitcoin dataset&lt;/strong&gt; - On the bitcoin dataset, I calculated the basic 50-second and 200-second moving averages. DuckDB processed this in about 55seconds on over 100 million rows all the while keeping the overall laptop's memory usage under 70% (including other processes that were using the system's memory)&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Experimenting with DuckDB - Anime dataset
&lt;/h2&gt;

&lt;p&gt;As already explained, the anime dataset contains three files. I used both DuckDB and Pandas to do the analysis, so we can understand how DuckDB compares to Pandas. We compare the following analysis between DuckDB and Pandas&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Load times of datasets between DuckDB and Pandas&lt;/li&gt;
&lt;li&gt;Since we have three datasets, we join the datasets to get the user details, anime details and ratings in a single place.&lt;/li&gt;
&lt;li&gt;Next, we calculate the average user rating for each anime.&lt;/li&gt;
&lt;li&gt;Finally, we calculate the genre-level ratings.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  1. Loading Datasets
&lt;/h3&gt;

&lt;p&gt;Let us load the three files using both DuckDB and Pandas. Note that DuckDB load times are in a few milliseconds while Pandas takes a couple of seconds to load the data. This is because DuckDB constructs what are called relations. These relations are symbolic representations of SQL queries. They do not hold any data and nothing is executed until a method that triggers execution is called. This explains why DuckDB loads look instantaneous.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fra6s8xgo1btidaoz23lf.png" alt="Comparing csv file load times of DuckDB vs Pandas." width="800" height="212"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Comparing csv file load times of DuckDB vs Pandas.&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;In the above screenshot we see CPU times and Wall time. Below these are explained briefly.&lt;/em&gt;&lt;br&gt;
 &lt;strong&gt;&lt;em&gt;CPU times&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;- Measures the actual time the CPU spends working on a specific task. This only includes time when the CPU is actively executing the process, so it excludes waiting periods like I/O operations. It’s often used to gauge how much CPU power a task consumes.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Wall time&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;- Also known as Elapsed Time, measures the real-world time from start to finish of a task. This includes all pauses and waiting periods, such as waiting for data from a disk or network, making it a full picture of the user’s wait time.&lt;/em&gt; &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  2. Joining Datasets
&lt;/h3&gt;

&lt;p&gt;Next, we join the three tables. &lt;code&gt;anime_details&lt;/code&gt; is joined with &lt;code&gt;user_ratings&lt;/code&gt; and &lt;code&gt;user_details&lt;/code&gt; tables. We also convert the genres column to a list data type, so it can later be used for ratings calculations across genres. And finally, we only select the required columns. While I was able to achieve this using a single SQL query in DuckDB, it required multiple steps to achieve the same in pandas. Below is a screenshot comparing the both. From the below screenshot, we see that DuckDB is still not processing the query as the processing time is 0 nanoseconds. Pandas processed everything in about 1 minute 36 seconds.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgxhqfyrrp2ljb8c7p601.png" alt="Joining the three tables in the anime dataset" width="800" height="347"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Joining the three tables in the anime dataset&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  3. Best anime by user ratings
&lt;/h3&gt;

&lt;p&gt;Here, we compute the average anime ratings. We compute the average ratings of the anime and sort them in descending order of rating. We only consider animes which received at least 1000 user ratings. We can see DuckDB is winning this round too. Given that it is doing lazy operations, my understanding is that DuckDB has in this step loaded the required data, did the joins on the three tables and then computed the average anime ratings and sorted them in around 18 seconds. Pandas took 23 seconds just for the average computation and sorting the results given that it has already loaded the data and performed the joins.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fed7vwbzpf4iwirz60ak9.png" alt="Computing the average user ratings of anime sorted in order of user rating" width="800" height="387"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Computing the average user ratings of anime sorted in order of user rating&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;In the previous step, notice that CPU time is greater than Wall time for DuckDB. This means the process is running in a multi-threaded or parallel manner, where multiple CPU cores are working on the task simultaneously. Each core's time adds up, resulting in a higher total CPU time (sum of time across all cores) compared to the actual elapsed wall time.&lt;br&gt;
For example, if a process takes 2 seconds of Wall Time to complete but uses four CPU cores simultaneously, each for 2 seconds, the CPU Time would be 2 seconds * 4 cores = 8 seconds.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  4. Best anime genre by user rating
&lt;/h3&gt;

&lt;p&gt;The final one is to compute the average ratings for different genres. But each anime can belong to multiple genres. This is the reason we created the genres as a list in the previous step. We unnest this genre column in DuckDB so the list of genres is split into multiple rows one for each genre. Similarly, we use explode in Pandas to expand the list into multiple rows. Each anime can on average belong to three genres. Given this, the final data can expand to over 72 million rows. Then the average rating is computed for every genre by taking the average of user ratings. While we can argue against this logic, the idea was to push DuckDB to see how it can handle such an explosion of data. This is the step that took 4.5 minutes for DuckDB to compute the average. Pandas has given up at this point with an "Unable to allocate memory" error. Below is the query.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faanzlja7bhwkmte2vgu1.png" alt="Calculating the average user ratings across anime genres. Notice how pandas is unable to process this because of memory issues." width="800" height="486"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Calculating the average user ratings across anime genres. Notice how pandas is unable to process this because of memory issues.&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In conclusion, we see that DuckDB does lazy evaluation delaying the loading of datasets and joining until the average calculation. We also saw that DuckDB handled the explosion of the dataset very well without any memory issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  Experimenting with DuckDB - Bitcoin dataset
&lt;/h2&gt;

&lt;p&gt;Next, we use the bitcoin dataset extracted from Binance. The dataset has about 110 million rows. I tried using pandas but it wasn't even able to load the whole dataset into memory. So, I had to abandon pandas for this dataset and only focused on using DuckDB.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Loading the dataset
&lt;/h3&gt;

&lt;p&gt;First, we loaded the csv file which is about 12 GB. As we discussed in the previous dataset, DuckDB loads it lazily. Hence the loading seems instantaneous. In the next step, we print the dataset. But here also we see that the output is shown instantaneously. This is because DuckDB doesn't yet read the dataset fully and only scans the first 10000 rows to show the output. This is the reason why even the show() method is very quick.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa1hlk5grj5sb2ptu4dhh.png" alt="Loading the bitcoin dataset" width="800" height="636"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Loading the Bitcoin dataset&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  2. Computing the moving averages
&lt;/h3&gt;

&lt;p&gt;Next, we compute the moving averages on the dataset. Moving averages are one of the basic indicators in trading to decide when to buy and sell an asset. In this, we compute the 50-second moving average and 200-second moving average on the entire dataset. As we can see the result is returned in under a minute.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fltj2jhdvcd5dlmwajoqs.png" alt="Computing the 50-seconds and 200-seconds moving averages for the entire dataset" width="800" height="753"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Computing the 50 seconds and 200 seconds moving averages for the entire dataset&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  3. Lazy evaluation on moving averages variable
&lt;/h3&gt;

&lt;p&gt;Next, I tried calculating the moving average and assigned it to the &lt;code&gt;moving_averages&lt;/code&gt; variable rather than directly showing the output. As expected, DuckDB has not evaluated the query at this point. Notice it shows 0 nanoseconds to process in step 1 of the below screenshot. Next, we do two simple calculations on this variable.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;First, we calculate the maximum Open time, minimum Open time and the total rows in the &lt;code&gt;moving_averages&lt;/code&gt; variable.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We again calculate the same maximum and minimum Open time and the total rows in the &lt;code&gt;moving_averages&lt;/code&gt; variables but this time the variable &lt;code&gt;moving_average&lt;/code&gt; is filtered to dates before 2017-08-08. This filtered dataset has only about 79k rows.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The surprising output here is that both the queries took the same time and they took almost double the time compared to the moving averages calculation from the previous step. We can see that in the second calculation, the filtered dataset only has around 79k records to process which is only a fraction of the 110million records. But it still took the same time. This is unclear to me and came as a surprise how DuckDB is planning the query execution in both these scenarios in the backend. Do comment if you know how this works!&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuzem9ui981po00r2ua1u.png" alt="Moving averages" width="800" height="776"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;ol&gt;
&lt;li&gt;&lt;em&gt;Lazily calculate the moving averages.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Get the maximum open time, minimum open time and total rows from this dataset&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Get the maximum open time, minimum open time and total rows from this dataset where open time is before 2017-08-18&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In conclusion, we see that DuckDB is able to handle 110 million rows without any issues on a local laptop. This shows how efficiently DuckDB uses the resources.&lt;/p&gt;

&lt;h2&gt;
  
  
  Experimenting with DuckDB - Observations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;DuckDB delays the execution of the queries until a method is called that triggers the execution.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Given its columnar in-memory processing engine, it is much faster compared to pandas.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Extremely efficient memory processing. While the datasets are big enough to challenge the RAM on my system, the RAM usage was always under 70% given that even other processes were running on the system.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The above point means DuckDB can handle larger than memory datasets.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Notice that in DuckDB CPU time is always more than the Wall time. This shows that DuckDB is engaging multiple cores/threads to process the data. The default threads that DuckDB uses are set to 8. Notice that this is not the case with Pandas.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Experimenting with DuckDB - Conclusion
&lt;/h2&gt;

&lt;p&gt;We can conclude that DuckDB is capable of handling hundreds of millions of rows of datasets with ease even on a local machine. For professionals like me who love SQL, DuckDB gives the ability to work with large datasets even with minimal infrastructure and using SQL dialect. Whether you're performing complex analytical queries or just need fast results on a budget, DuckDB offers a balance of speed and flexibility that makes it a strong choice in large-scale scenarios.&lt;/p&gt;

&lt;p&gt;You can find all the code used in this blog in my GitHub repository &lt;a href="https://github.com/nageen20/duckdb_experiments" rel="noopener noreferrer"&gt;here&lt;/a&gt;. I hope to do another part on experimenting with DuckDB where I use even larger datasets.&lt;/p&gt;

&lt;p&gt;Hope you enjoyed the read! Do share your valuable feedback and comments.&lt;/p&gt;

</description>
      <category>duckdb</category>
      <category>pandas</category>
      <category>performance</category>
      <category>experimenting</category>
    </item>
    <item>
      <title>Using DuckDB for Ad-Hoc Analysis: A SQL-Lover's Alternative to Pandas</title>
      <dc:creator>Nageen Yerramsetty</dc:creator>
      <pubDate>Fri, 22 Nov 2024 10:16:37 +0000</pubDate>
      <link>https://forem.com/nageen20/using-duckdb-for-ad-hoc-analysis-a-sql-lovers-alternative-to-pandas-13bo</link>
      <guid>https://forem.com/nageen20/using-duckdb-for-ad-hoc-analysis-a-sql-lovers-alternative-to-pandas-13bo</guid>
      <description>&lt;p&gt;Ad-hoc analysis is an integral part of anyone in the data field. We have to on a regular basis combine data from various sources like CSV files, parquet files and databases for some ad-hoc testing or quick reporting. The most common tool at our disposal is Python's Pandas where we can read data from different sources into dataframes and then do the analysis. However, for someone who is more comfortable with SQL than Python, DuckDB is an excellent alternative. It lets you query data using SQL, without needing to load it into a database or convert it into a Pandas dataframe.&lt;/p&gt;

&lt;h2&gt;
  
  
  But first, what is DuckDB?
&lt;/h2&gt;

&lt;p&gt;DuckDB is a modern, in-process analytical database. It supports a feature-rich SQL dialect and thanks to its columnar engine, it is blazingly fast. DuckDB is super quick to install (yes, you can get it up and running in less than a minute). Unlike traditional databases, it doesn't require a server, meaning you can embed it directly into your applications or run it directly on your local machine with minimal setup. It can read and write file formats such as CSV, Parquet, and JSON, to and from the local file system and remote endpoints such as S3 buckets. You can also pull data from databases like MySQL, BigQuery and others.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why use DuckDB for Ad-Hoc Analysis?
&lt;/h2&gt;

&lt;p&gt;If you're comfortable with SQL, DuckDB lets you connect to various sources and process data using SQL dialect. Given its in-memory columnar engine, it is extremely fast (yes, it leaves Pandas in the dust). And it can handle medium-sized data loads (up to a few GBs) comfortably on your local machine. Yes, it can handle larger-than-memory workloads by spilling to disk. I hope I have convinced you how cool DuckDB is.&lt;/p&gt;

&lt;p&gt;If you prefer using an SQL client, DBeaver currently supports DuckDB. You can simply select DuckDB on the connection page on DBeaver and give some location on your local machine if you want persistent storage or run it in-memory completely by adding ":memory:" in the path. More detailed instructions &lt;a href="https://duckdb.org/docs/guides/sql_editors/dbeaver.html#:~:text=To%20use%20an%20in%2Dmemory,alternative%20driver%20installation%20instructions%20below." rel="noopener noreferrer"&gt;here&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Once DBeaver is connected to your DuckDB, you can read from a CSV file using the following (yes, that simple!):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'file_name.csv'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can connect to a MySQL database and read from the database directly using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;INSTALL&lt;/span&gt; &lt;span class="n"&gt;MYSQL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;ATTACH&lt;/span&gt; &lt;span class="s1"&gt;'host=host_name user=user_name port=3306 database=db_name password=password'&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;mysq_db&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;TYPE&lt;/span&gt; &lt;span class="n"&gt;MYSQL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;READ_ONLY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above lines create a connection to your MySQL database in read-only mode. "mysql_db" is like the alias name you can give to the connection.&lt;/p&gt;

&lt;p&gt;To read a table from the MySQL database, simply use the connection alias with your regular SQL syntax (I know, equally simple!).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;mysql_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now to the interesting part. Let us say you have a CSV file that has departments and department codes in a departments.csv file like the below:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;department_code&lt;/th&gt;
&lt;th&gt;department_name&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;FI&lt;/td&gt;
&lt;td&gt;Finance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HR&lt;/td&gt;
&lt;td&gt;Human Resources&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;And let's say you have an employees table in your MySQL database that has the employee name and the department code as below:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;department_code&lt;/th&gt;
&lt;th&gt;employee_name&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;FI&lt;/td&gt;
&lt;td&gt;Raghu&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HR&lt;/td&gt;
&lt;td&gt;Himesh&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;To combine both in DuckDB to get the employee name and department name, we can use the below code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Connect to MySQL DB&lt;/span&gt;
&lt;span class="n"&gt;ATTACH&lt;/span&gt; &lt;span class="s1"&gt;'host=host_name user=user_name port=3306 database=db_name password=password'&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;mysq_db&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;TYPE&lt;/span&gt; &lt;span class="n"&gt;MYSQL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;READ_ONLY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Enable filter pushdown &lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;mysql_experimental_filter_pushdown&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Join the CSV file and MySQL tables&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;department_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;employee_name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'departments.csv'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;mysql_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;employees&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
     &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;department_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;department_code&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Setting "mysql_experimental_filter_pushdown" to true will push down any filters to the database and only read the filtered data out. You can use simple SQL dialects to read from multiple sources and combine them in a single SQL statement using DuckDB. I will leave you here to play with DuckDB yourself and explore what possibilities it offers.&lt;/p&gt;

&lt;p&gt;To leave you with more inspiration, here is a blog post on how the author handled 450Gb of data in DuckDB - &lt;a href="https://towardsdatascience.com/my-first-billion-of-rows-in-duckdb-11873e5edbb5" rel="noopener noreferrer"&gt;https://towardsdatascience.com/my-first-billion-of-rows-in-duckdb-11873e5edbb5&lt;/a&gt;. And the cherry on top is that you can save costs as well by pulling data to your local machine and processing it in DuckDB.&lt;/p&gt;

&lt;p&gt;And you can read more on what is possible in DuckDB from their documentation here - &lt;a href="https://duckdb.org/docs/" rel="noopener noreferrer"&gt;https://duckdb.org/docs/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Do comment and share your thoughts on how you want to use DuckDB in your day-to-day work. Let's wait and watch how this amazing piece of technology will evolve in the coming years.&lt;/p&gt;

&lt;p&gt;Thanks for reading. Do share any comments and feedback!&lt;/p&gt;

</description>
      <category>duckdb</category>
      <category>database</category>
      <category>pandas</category>
      <category>analytics</category>
    </item>
    <item>
      <title>SQL Window functions: Understanding PARTITION BY</title>
      <dc:creator>Nageen Yerramsetty</dc:creator>
      <pubDate>Tue, 19 Nov 2024 10:05:01 +0000</pubDate>
      <link>https://forem.com/nageen20/sql-window-functions-understanding-partition-by-4apf</link>
      <guid>https://forem.com/nageen20/sql-window-functions-understanding-partition-by-4apf</guid>
      <description>&lt;p&gt;Imagine you are analyzing sales transactions and want to see a running total of daily sales. Or you want to find the best-performing product in each region based on the total sales value. These are all common questions from the business when working with data. In both scenarios, notice that we cannot change the granularity of the data but need an aggregate value. For running totals, we still want the data to be at a daily level and also have an aggregate of all the sales that happened until that day. For the best-performing product, we still want the data to be at the product level but need to get which product has the highest sales in a given region. While we might think of using &lt;code&gt;GROUP BY&lt;/code&gt; to achieve these, &lt;code&gt;GROUP BY&lt;/code&gt; aggregates the data losing the granularity. For these scenarios, SQL provides a powerful feature called Window functions, a way to aggregate data (and more) without losing granularity.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are window functions?
&lt;/h2&gt;

&lt;p&gt;Window functions perform operations on a set of table rows that are somehow related to the current row. This set of related rows is called a Window. Unlike aggregate functions like &lt;code&gt;SUM()&lt;/code&gt; with &lt;code&gt;GROUP BY&lt;/code&gt;, which collapses the rows to the group level, window functions retain the original number of rows in the output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding with an example
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Problem definition
&lt;/h3&gt;

&lt;p&gt;Let us break that definition down and understand with an example. Firstly in window functions, we perform operations on every row. This operation which is performed on every row takes into account a set of related rows called windows. Imagine we have a dataset of product sales in each region as shown below. Let us say we want to calculate the percentage contribution of each product to the overall sales in that region. So, for every row in the dataset, if we have the total sales in the region, then we can divide the product sales by the region sales to get the percentage. Below is a sample dataset.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;region&lt;/th&gt;
&lt;th&gt;product&lt;/th&gt;
&lt;th&gt;sales_amount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Bangalore&lt;/td&gt;
&lt;td&gt;Ice cream&lt;/td&gt;
&lt;td&gt;5000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Bangalore&lt;/td&gt;
&lt;td&gt;Chocolate&lt;/td&gt;
&lt;td&gt;10000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Bangalore&lt;/td&gt;
&lt;td&gt;Soft drinks&lt;/td&gt;
&lt;td&gt;2000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Delhi&lt;/td&gt;
&lt;td&gt;Ice cream&lt;/td&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Delhi&lt;/td&gt;
&lt;td&gt;Chocolate&lt;/td&gt;
&lt;td&gt;3000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Delhi&lt;/td&gt;
&lt;td&gt;Soft drinks&lt;/td&gt;
&lt;td&gt;6000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Hyderabad&lt;/td&gt;
&lt;td&gt;Ice cream&lt;/td&gt;
&lt;td&gt;8000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Hyderabad&lt;/td&gt;
&lt;td&gt;Chocolate&lt;/td&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Hyderabad&lt;/td&gt;
&lt;td&gt;Soft drinks&lt;/td&gt;
&lt;td&gt;3500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;Mumbai&lt;/td&gt;
&lt;td&gt;Ice cream&lt;/td&gt;
&lt;td&gt;12000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;Mumbai&lt;/td&gt;
&lt;td&gt;Chocolate&lt;/td&gt;
&lt;td&gt;5800&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;Mumbai&lt;/td&gt;
&lt;td&gt;Soft drinks&lt;/td&gt;
&lt;td&gt;12000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Table: &lt;em&gt;Sample dataset showing sales across cities and products&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution using window function concepts
&lt;/h2&gt;

&lt;p&gt;Let us break this problem into steps as per the definition of the window functions.&lt;/p&gt;

&lt;p&gt;For every row in the dataset, we need to apply an operation. In this scenario, we apply the &lt;code&gt;SUM(sales_amount)&lt;/code&gt; operation to get the total sales of all the products.&lt;/p&gt;

&lt;p&gt;But, this operation has to take into account only a set of related rows along with the current row. In this case, these related rows are all the rows belonging to the same region. For example, if we are operating on the row with &lt;code&gt;id=1&lt;/code&gt;, we know this row belongs to the Bangalore region. Now to compute &lt;code&gt;SUM(sales_amount)&lt;/code&gt;, the window function considers rows with IDs 1,2 and 3 since all three belong to the Bangalore window. Now combining the sales amount of all three rows gives the value 17000 which is computed against row id 1. By repeating this logic for every row, we get the &lt;code&gt;total_sales_in_region&lt;/code&gt; for every row. So, in this case, all the rows in a region are considered a window. Try to compute the &lt;code&gt;total_sales_in_region&lt;/code&gt; for &lt;code&gt;row_id=4&lt;/code&gt; using the above explanation.&lt;br&gt;
As a final step, we can simply divide sales_amount by total_sales_in_region to get the percentages. Note that this step is not shown in the screenshot below.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frruirstukp6vy488pvkg.png" alt="Notice how the windows are defined based on the values in the region column. Also, the total_sales_in_region is computed by summing up sales_amount of all the rows in the respective windows." width="800" height="211"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Notice how the windows are defined based on the values in the region column. Also, the total_sales_in_region is computed by summing up sales_amount of all the rows in the respective windows.&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;With this example, we can see how we computed the aggregate of sales at the region level and still maintained the table at region-product granularity. This is how window functions operate in SQL.&lt;/p&gt;
&lt;h2&gt;
  
  
  How is GROUP BY different?
&lt;/h2&gt;

&lt;p&gt;Before we move forward, let us be clear that &lt;code&gt;GROUP BY&lt;/code&gt; is different and we would not be able to achieve the same result with &lt;code&gt;GROUP BY&lt;/code&gt;. If we &lt;code&gt;SUM(total_sales)&lt;/code&gt; using &lt;code&gt;GROUP BY&lt;/code&gt; on the region column, then the output will be at the region level as shown below. Notice how &lt;code&gt;GROUP BY&lt;/code&gt; reduced the total rows in the output to just show totals at the region level. We no longer have access to the product information in this.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F77bnhx57ajffo2o56qxh.png" alt="Output after aggregating the sales_amount at region level using GROUP BY" width="362" height="154"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Output after aggregating the sales_amount at region level using GROUP BY&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Here is the query used to get the total sales at the region level using &lt;code&gt;GROUP BY&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- GROUP BY query to get the total sales amount at the region level&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sales_amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_sales_in_region&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;region_product_sales&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Window functions — Syntax
&lt;/h2&gt;

&lt;p&gt;Now let us understand the basic syntax of a window function in MySQL. Again starting with the definition, we define the aggregate function for each row over a defined window. We define the window by telling SQL which columns it should use to partition the dataset into multiple windows. Here is the syntax for the above problem to get the total sales in the region for every row.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwovvic8cmlg073xxregp.png" alt="Breaking down the syntax of Window functions" width="800" height="334"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Breaking down the syntax of Window functions&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sales_amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_sales_in_region&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All this syntax goes in-line in the SELECT part where we list the columns. So, the full SQL query for the example problem will be as follows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Window functions SQL syntax using PARTITION BY&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;sales_amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sales_amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_sales_in_region&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;region_product_sales&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;With this example, I hope it is clear what window functions are and how they work. We also saw how the window functions output differs from aggregates computed using &lt;code&gt;GROUP BY&lt;/code&gt;. Finally, we saw the syntax to use window functions with &lt;code&gt;PARTITION BY&lt;/code&gt; in MySQL. This is only a simple example and there are a lot more features to explore in window functions in SQL.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s next?
&lt;/h2&gt;

&lt;p&gt;In this blog, we only discussed a very basic example of using window functions in MySQL. Window functions offer several operations that you can do on the rows apart from aggregate functions like &lt;code&gt;SUM&lt;/code&gt;, &lt;code&gt;COUNT&lt;/code&gt;. You can calculate for example the rank of every product based on their sales amount using the &lt;code&gt;RANK&lt;/code&gt; window function. We can also, form more complex windows using multiple columns in the &lt;code&gt;PARTITION BY&lt;/code&gt; clause. You can see the full list of MySQL window functions &lt;a href="https://dev.mysql.com/doc/refman/8.4/en/window-function-descriptions.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;. Find the list of window functions for Postgres &lt;a href="https://www.postgresql.org/docs/current/functions-window.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;. In the upcoming blogs, we will discuss more complex scenarios using window functions and also introduce how to use &lt;code&gt;ORDER BY&lt;/code&gt; in window functions.&lt;/p&gt;

&lt;p&gt;Hope you enjoyed the read. Do share any feedback in the comments.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>windowfunctions</category>
      <category>database</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
