Skip to content

Commit 45cb26b

Browse files
authored
Merge pull request #18 from motherduckdb/relational-api-2
Relational api description and example. Reduced Ibis example
2 parents 2ed4089 + 12598f9 commit 45cb26b

3 files changed

Lines changed: 266 additions & 114 deletions

File tree

3_sql-and-python.md

Lines changed: 69 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ Read in the birds.csv file using Apache Arrow, then use the DuckDB Python librar
116116
```{admonition} Exercise 3.02
117117
118118
Use the DuckDB Python client to return these results as a Polars dataframe.
119-
119+
```
120120
```sql
121121
SELECT
122122
Species_Common_Name,
@@ -125,18 +125,79 @@ SELECT
125125
AVG(Beak_Length_Culmen) AS Avg_Beak_Length_Culmen
126126
FROM 'birds.csv'
127127
GROUP BY Species_Common_Name
128-
129128
```
129+
130130
```{code-cell}
131131
# Uncomment and run to show solution
132132
# !cat ./answers/answer_3.02.py
133133
```
134134

135-
## 2. Using `ibis` with a DuckDB backend
135+
## 2. Using the DuckDB Relational API
136+
137+
In addition to SQL, you can write DuckDB queries using the built-in relational API.
138+
We'll see how to chain functions together to build up a query.
139+
DuckDB will then combine that function chain together into a single query, so you still get all of DuckDB's query optimization (speed and scalability!) benefits.
140+
141+
The question we will build up towards answering is, "Who were the most prolific people at finding many new species of non-extinct ducks, and when did they get started finding ducks?"
142+
143+
<!-- TODO: Add in a few incremental steps as examples -->
144+
145+
The Relational API has a `read_csv` function that mirrors the Pandas function of the same name.
146+
These methods can be called on DuckDB connection objects or the default connection using the duckb object itself.
147+
148+
As a note, if we wrap our relational expressions in a set of parentheses, we can organize them nicely across multiple lines.
149+
150+
Here, we `filter` using an expression much like we would use in a `where` clause to only non-extinct ducks.
151+
The, we use `aggregate` to execute a `group by` operation, grouping by the `author` column.
152+
Lastly, we `order` by one of the aggregate expression columns.
153+
154+
```{code-cell}
155+
duck_legends = (duckdb
156+
.read_csv("ducks.csv")
157+
.filter("extinct = 0")
158+
.aggregate("author, count(name) as count_name, min(year) as min_year", "author")
159+
.order("count_name desc")
160+
)
161+
duck_legends
162+
```
163+
164+
You can also see the SQL that is generated using with `sql_query()` function:
165+
```{code-cell}
166+
print(duck_legends.sql_query())
167+
```
168+
169+
Much like DuckDB can query DataFrames within SQL as if they were tables, it can also do the same for relation objects.
170+
171+
```{code-cell}
172+
duckdb.sql("select * from duck_legends limit 5")
173+
```
174+
175+
Technically, the `.sql` function returns a relation object as well, so you can also chain relational operations after a SQL one also!
176+
177+
As a silly example, we could run:
178+
179+
```{code-cell}
180+
duckdb.sql("select * from duck_legends limit 5").limit(1)
181+
```
182+
183+
Now we can mix and match SQL and the relational API!
184+
185+
```{admonition} Exercise 3.04
186+
As an exercise, use SQL to initially pull the CSV file, but then chain together the remaining Relational operators from the `duck_legends` example above to return the same result.
187+
```
188+
189+
```{code-cell}
190+
# Uncomment and run to show solution
191+
# !cat ./answers/answer_3.03.py
192+
```
193+
194+
## 3. Using `ibis` with a DuckDB backend
136195

137196
### A. Introduction to Ibis and DuckDB
138197

139-
We'll show you how to leverage the power of DuckDB without even needing to write a single line of SQL. Instead, we'll use Ibis, a powerful Python library that allows you to interact with databases using a DataFrame-like syntax. We'll also show you how to combine the two so you can get the best of both worlds.
198+
Another option for using DuckDB is Ibis, a powerful Python library that allows you to interact with databases using a DataFrame-like syntax. We'll also show you how to combine the SQL and Ibis so you can get the best of both worlds.
199+
200+
Ibis also works across many different databases, so you can write your code once and run it on a variety of database engines.
140201

141202
First, let's make sure you have the necessary packages installed. You can install DuckDB and Ibis using pip:
142203

@@ -174,25 +235,14 @@ persistent_ducks = con.create_table('persistent_ducks', obj=ducks_ibis.to_pyarro
174235
persistent_ducks
175236
```
176237

177-
Now that we have a table set up, let's see how we can query this data using Ibis. With Ibis, you can perform operations on your data without writing SQL. Let's see how similar it feels...
238+
Now that we have a table set up, let's see how we can query this data using Ibis. Let's see how similar it feels...
178239

179-
The question we will build up towards answering is, "Who were the most prolific people at finding many new species of non-extinct ducks, and when did they get started finding ducks?"
240+
We will answer the same question as with the relational API: "Who were the most prolific people at finding many new species of non-extinct ducks, and when did they get started finding ducks?"
180241

181242
Use the `filter` function instead of a `where` clause to choose the rows you are interested in. Ibis also uses Python's `==` comparators instead of SQL's single `=`.
182243

183-
```{code-cell}
184-
persistent_ducks.filter(persistent_ducks.extinct == 0)
185-
```
186-
187244
Pick your columns using the conveniently named `select` function!
188245

189-
```{code-cell}
190-
(persistent_ducks
191-
.filter(persistent_ducks.extinct == 0)
192-
.select("name", "author", "year")
193-
)
194-
```
195-
196246
The `group_by` functions matches well with the `group by` clause.
197247

198248
However, Ibis splits the `select` clause into the `select` function and the `aggregate` function when working with a group by. This aligns with the SQL best practice to organize your `select` clause with non-aggregate expressions first, then aggregate expressions.
@@ -247,6 +297,7 @@ duck_legends
247297

248298
And there you go! You've learned:
249299
* How to read and write Pandas, Polars, and Apache Arrow with DuckDB
300+
* How to use DuckDB's Relational API and how to combine it with SQL
250301
* How to use Ibis to run dataframe queries on top of DuckDB
251302
* How to see the SQL that Ibis is running on your behalf
252303
* How to mix and match SQL and Ibis
@@ -255,6 +306,7 @@ And there you go! You've learned:
255306
```{admonition} Exercise 3.04
256307
257308
Convert the SQL query below into an Ibis expression. You are welcome to ignore the column renaming - think of it as a "stretch-goal" if you have time! We did not cover how to do that yet.
309+
```
258310
```sql
259311
SELECT
260312
Species_Common_Name,
@@ -264,7 +316,6 @@ SELECT
264316
FROM 'birds.csv'
265317
GROUP BY Species_Common_Name
266318
```
267-
```
268319

269320
```{admonition} Exercise 3.04
270321

answers/answer_3.03.py

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,11 @@
1-
# First, connect to the same database that Ibis wrote the csv file to
2-
duck_con = duckdb.connect('whats_quackalackin.duckdb')
1+
# Sort to find the authors that found the most ducks (highest number of names)
2+
# and also calculate the first year that they found a duck.
3+
# Start with a SQL query to pull the CSV and then chain together relational operators.
34

4-
# Then, combine all of the Ibis subqueries into a single level SQL statement
5-
duck_con.sql("""
6-
SELECT
7-
author,
8-
count(name) as "Count(name)",
9-
min(year) as "Min(year)"
10-
FROM persistent_ducks
11-
WHERE
12-
extinct = 0
13-
GROUP BY
14-
author
15-
ORDER BY
16-
"Count(name)" desc
17-
""").df()
5+
duck_legends2 = (duckdb
6+
.sql("FROM ducks.csv")
7+
.filter("extinct = 0")
8+
.aggregate("author, count(name) as count_name, min(year) as min_year", "author")
9+
.order("count_name desc")
10+
)
11+
duck_legends2

0 commit comments

Comments
 (0)