
![]()
Önceki yazımızda pandas’ın veritabanlarıyla nasıl konuştuğunu (ADBC sürücüleri) gördük. Peki ya tersi? Elimizde zaten bir DataFrame var ve onu doğrudan SQL ile sorgulamak istiyoruz — filtreleme (filtering), dönüştürme (transformation), toplulaştırma (aggregation), birleştirme (join). Bu mümkün mü? Hem de fazlasıyla. Bu yazıda pandas sql kullanımı konusunda mevcut yolları öğreneceğiz, hangisinin hangi durumda en iyi olduğunu göreceğiz ve gerçek örneklerle deneyeceğiz.
Spoiler: 2026’da herkesin konuştuğu cevap DuckDB ama tek seçenek o değil. Pandas’ın kendi query() ve eval() metodları, eski klasik pandasql paketi de hâlâ hayatta. Hepsine sırayla bakalım.
Önce Hızlı Bir Karar Tablosu
| Araç | SQL Tam Desteği | Hız | Kurulum | Ne Zaman? |
|---|---|---|---|---|
df.query() | Hayır (SQL-vari) | Çok hızlı | Yok (pandas içinde) | Hızlı filtreleme |
df.eval() | Hayır (ifade) | Çok hızlı | Yok | Sütun türetme (column derivation) |
pandasql | Evet (SQLite) | Çok yavaş | pip install | Sadece eski projeler |
| DuckDB | Evet (tam) | Çok hızlı | pip install | Modern, tavsiye edilen yol |
Tabloda gördüğümüz gibi DuckDB öne çıkıyor, ama her durumun kendi tatlı noktası var. Hadi tek tek bakalım.
Yol 1: Pandas’ın Kendi query() Metodu
Pandas’ın DataFrame.query() metodu, SQL’in WHERE cümlesine en yakın yerel (native) seçenek. Tam SQL değil ama SQL’e alışkın gözler için çok okunaklı bir filtreleme dili sunuyor [1][2].
import pandas as pd
df = pd.DataFrame({
"product": ["Laptop", "Klavye", "Mouse", "Monitör", "Tablet"],
"price": [25000, 850, 320, 7400, 12500],
"stock": [12, 45, 120, 8, 30],
"category": ["electronics", "accessories", "accessories", "electronics", "electronics"],
})
# SQL'de: SELECT * FROM df WHERE price > 1000 AND stock < 30
result = df.query("price > 1000 and stock < 30")
print(result)
Çıktı:
product price stock category 0 Laptop 25000 12 electronics 3 Monitör 7400 8 electronics
Birkaç güzel detay [3][4]:
- Harici değişken (external variable):
@işaretiyle Python değişkenlerine erişebiliriz.threshold = 1000df.query("price > @threshold") in/not inoperatörleri: SQL’deki gibi çalışıyor.df.query("category in ['electronics', 'books']")- Boşluklu sütun adları: Geri tırnak (backtick) ile sarılır.
df.query("`unit price` > 100")
query(), NumExpr motoru (engine) sayesinde büyük DataFrame’lerde klasik boolean maskelerden (boolean indexing) daha hızlı çalışıyor; çünkü ifadeyi parçalara ayırıp ara değişkenler yaratmak yerine tek seferde değerlendiriyor [5][6].
Kısıtı: query() sadece filtreleme için. Toplulaştırma, join, window function gibi şeyler yok.
Yol 2: eval() ile Hesaplama ve Sütun Türetme
Yeni sütun yaratmak veya hesaplama yapmak istediğimizde DataFrame.eval() sahneye çıkıyor [5][6]:
# SQL'de: SELECT *, price * stock AS total_value FROM df
df_with_value = df.eval("total_value = price * stock", inplace=False)
print(df_with_value)
Çıktı:
product price stock category total_value 0 Laptop 25000 12 electronics 300000 1 Klavye 850 45 accessories 38250 ...
eval() de NumExpr destekli; özellikle büyük veride zincirlenmiş aritmetik ifadelerde belirgin bir hız kazandırıyor [5]. Ama yine SQL’in tamamını değil, sadece bir alt kümesini sunuyor.
Yol 3: pandasql — Klasik Ama Yavaş
pandasql, DataFrame’lere doğrudan SQL yazmamızı sağlayan en eski paketlerden biri [7]. Altta SQLite kullanıyor:
from pandasql import sqldf
query = """
SELECT category, AVG(price) AS avg_price
FROM df
WHERE stock > 10
GROUP BY category
"""
result = sqldf(query, locals())
Çalışıyor, evet — ama bir sorunu var. DuckDB ekibinin yaptığı resmi kıyaslamada (benchmark), aynı sorgu üzerinde pandasql DuckDB’den yaklaşık 1000 kat daha yavaş çıktı [8]. Sebebi mimari: pandasql her sorguda DataFrame’i SQLite’a INSERT INTO ifadeleriyle yüklüyor, sorguyu çalıştırıyor, sonra sonucu geri okuyor. Bu round-trip büyük veride boğucu.
Sonuç: Yeni projelerde pandasql yerine DuckDB’yi seçmemiz lazım. Eski bir kod tabanına dokunuyorsak bilinmesi gerekiyor, hepsi o kadar.
Yol 4: DuckDB — Modern, Hızlı, Tam SQL
İşin yıldızı burada. DuckDB, “analitik için SQLite” olarak adlandırılan, sürecin içinde (in-process) çalışan bir analitik veritabanı [9][10]. Üç süper gücü var:
- Sıfır kopya entegrasyon (zero-copy integration): DuckDB, pandas DataFrame’i Arrow bellek formatı (memory format) üzerinden doğrudan okur — kopyalama, serileştirme (serialization), tip dönüşümü yok [11].
- Sütun temelli vektörlü motor (columnar vectorized engine): Veriyi satır satır değil, bloklar (batches) halinde işler ve CPU çekirdeklerini paralel kullanır [12].
- Tam SQL: Pencere fonksiyonları (window functions), CTE’ler, karmaşık join’ler, alt sorgular — hepsi var.
Kurulum
pip install duckdb pandas pyarrow
Tek satır, harici sunucu (external server) gerekmiyor [13].
En Basit Örnek
import duckdb
import pandas as pd
df = pd.DataFrame({
"product": ["Laptop", "Klavye", "Mouse", "Monitör", "Tablet"],
"price": [25000, 850, 320, 7400, 12500],
"stock": [12, 45, 120, 8, 30],
"category": ["electronics", "accessories", "accessories", "electronics", "electronics"],
})
# DataFrame'in adı doğrudan SQL içinde kullanılabiliyor!
result = duckdb.sql("SELECT * FROM df WHERE price > 1000").df()
print(result)
Burada sihir şu: df isimli yerel değişken (local variable) SQL içinde sanki bir tabloymuş gibi görünüyor. DuckDB bunu replacement scan denen mekanizmayla yapıyor — yani aslında DuckDB’de df diye bir tablo yok, ama sorgu çalıştığında pandas DataFrame otomatik tanınıp tablo olarak sunuluyor [14].
.df() çağrısı sonucu tekrar pandas DataFrame’e çeviriyor. .fetchdf() veya .to_df() da aynı işi yapıyor [15]. Eğer Polars kullanıyorsak .pl() ile Polars DataFrame’i de alabiliyoruz [11].
Filtreleme
result = duckdb.sql("""
SELECT product, price, stock
FROM df
WHERE category = 'electronics' AND stock < 20
ORDER BY price DESC
""").df()
Dönüştürme (Transformation)
result = duckdb.sql("""
SELECT
product,
price,
stock,
price * stock AS total_value,
CASE
WHEN price > 10000 THEN 'premium'
WHEN price > 1000 THEN 'mid-range'
ELSE 'budget'
END AS price_segment
FROM df
""").df()
print(result)
CASE WHEN SQL’in en güzel ifadelerinden biri; pandas tarafında bunu yapmak için ya np.where zincirleri ya da pd.cut gerekiyordu. SQL’de tek blokta hallediyoruz [16].
Toplulaştırma (Aggregation)
result = duckdb.sql("""
SELECT
category,
COUNT(*) AS product_count,
SUM(stock) AS total_stock,
AVG(price) AS avg_price,
MIN(price) AS min_price,
MAX(price) AS max_price
FROM df
GROUP BY category
ORDER BY avg_price DESC
""").df()
print(result)
Çıktı:
category product_count total_stock avg_price min_price max_price 0 electronics 3 50 14966.67 7400 25000 1 accessories 2 165 585.00 320 850
Pandas eşdeğeri (groupby().agg().reset_index() zinciri) de yapılabilir ama SQL hem daha kısa hem de gözle taranması daha kolay [16][17].
Birleştirme (Join)
İki DataFrame’imiz olsun:
orders = pd.DataFrame({
"order_id": [1, 2, 3, 4, 5],
"product": ["Laptop", "Mouse", "Laptop", "Tablet", "Klavye"],
"quantity": [1, 3, 2, 1, 5],
})
# df ve orders'ı join edelim
result = duckdb.sql("""
SELECT
o.order_id,
o.product,
o.quantity,
d.price,
o.quantity * d.price AS line_total
FROM orders o
JOIN df d ON o.product = d.product
ORDER BY line_total DESC
""").df()
print(result)
Birden fazla DataFrame’i aynı sorguda kullanabiliyoruz — hiçbirini önceden DuckDB’ye “kaydetmemiz” gerekmiyor [14][18].
Pencere Fonksiyonu (Window Function)
İşin nimet kısmı. Pandas’ta groupby + rank + transform zincirleri yerine:
result = duckdb.sql("""
SELECT
product,
category,
price,
RANK() OVER (PARTITION BY category ORDER BY price DESC) AS price_rank,
AVG(price) OVER (PARTITION BY category) AS category_avg
FROM df
""").df()
print(result)
Her kategori içinde fiyat sıralaması ve kategori ortalaması — tek sorguda [19].
Kalıcı (Persistent) Bağlantı
duckdb.sql(...) çağrısı her seferinde geçici bir bellek bağlantısı (in-memory connection) yaratıyor. Birden fazla sorgu çalıştıracaksak bir bağlantıyı yeniden kullanmak daha verimli:
con = duckdb.connect() # bellek içi, kalıcı bağlantı
con.register("products", df)
con.register("orders", orders)
# Artık SQL içinde bu isimleri kullanıyoruz
top_sellers = con.execute("""
SELECT p.product, SUM(o.quantity) AS total_sold
FROM products p
JOIN orders o ON p.product = o.product
GROUP BY p.product
ORDER BY total_sold DESC
""").df()
con.close()
Diskte saklamak istersek duckdb.connect("mydb.duckdb") ile dosyaya yazan bir bağlantı da açılabiliyor [13][20].
Relational API: SQL Yazmadan SQL
DuckDB’nin az bilinen ama çok zarif bir özelliği daha var. Relational API ile pandas zinciri gibi metot zincirlemesi (method chaining) yazıp arkasında SQL üretebiliyoruz [21]:
import duckdb
con = duckdb.connect()
rel = con.from_df(df)
result = (rel
.filter("category = 'electronics'")
.project("product, price, price * 1.20 AS price_with_tax")
.order("price_with_tax DESC")
.limit(3)
.df()
)
Bu, arka planda şuna eşdeğer:
SELECT product, price, price * 1.20 AS price_with_tax FROM df WHERE category = 'electronics' ORDER BY price_with_tax DESC LIMIT 3
Tembel değerlendirme (lazy evaluation) yapıldığı için DuckDB son .df() çağrısında tüm zinciri tek seferde optimize edip çalıştırıyor [21].
Performans: Sayılarla Gerçek Fark
DuckDB ekibinin lineitem tablosu üzerinde (TPC-H tarzı veri seti, 6 milyon satır) yaptığı kıyaslamada [8]:
- Basit gruplu toplulaştırma (grouped aggregation) sorgusu — pandas: bir referans noktasında, DuckDB tek iş parçacığı (single-threaded) ile yaklaşık 2 kat daha hızlı, paralel modda ise çok daha hızlı.
pandasqlaynı sorguda DuckDB’den ~1000 kat daha yavaş — kıyaslamayı bitirmek bile sorun oldu.
1 milyon satırlık bağımsız bir kıyaslamada (banka veri seti, filtre + toplulaştırma) DuckDB hem yürütme süresi (execution time) hem de bellek kullanımı (memory usage) açısından pandas’ı belirgin şekilde geçti; özellikle 1 GB üzeri verilerde fark daha da açılıyor [22]. Bir başka karşılaştırmada (anime puanları veri seti) pandas’ın 1 dakika 36 saniye aldığı işlemi DuckDB neredeyse anlık olarak bitirdi [23].
Tam Tur Pratik Örnek: Satış Analizi
Hadi gerçekçi bir senaryoyla birleştirelim. Üç DataFrame’imiz var: ürünler, siparişler, müşteriler.
import duckdb
import pandas as pd
products = pd.DataFrame({
"product_id": [1, 2, 3, 4],
"name": ["Laptop", "Klavye", "Mouse", "Monitör"],
"category": ["electronics", "accessories", "accessories", "electronics"],
"price": [25000.0, 850.0, 320.0, 7400.0],
})
customers = pd.DataFrame({
"customer_id": [101, 102, 103, 104],
"name": ["Ayşe", "Mehmet", "Fatma", "Can"],
"city": ["İstanbul", "Ankara", "İzmir", "İstanbul"],
})
orders = pd.DataFrame({
"order_id": [1001, 1002, 1003, 1004, 1005, 1006],
"customer_id": [101, 102, 101, 103, 104, 101],
"product_id": [1, 3, 2, 1, 4, 3],
"quantity": [1, 2, 3, 1, 1, 5],
"order_date": pd.to_datetime([
"2026-01-15", "2026-01-20", "2026-02-03",
"2026-02-10", "2026-02-15", "2026-03-01"
]),
})
con = duckdb.connect()
# Şehir başına toplam ciro ve top ürün
report = con.sql("""
SELECT
c.city,
COUNT(DISTINCT o.order_id) AS order_count,
SUM(o.quantity * p.price) AS total_revenue,
ROUND(AVG(o.quantity * p.price), 2) AS avg_order_value
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
JOIN products p ON o.product_id = p.product_id
WHERE o.order_date >= '2026-01-01'
GROUP BY c.city
ORDER BY total_revenue DESC
""").df()
print(report)
Bu sorgunun pandas eşdeğerini yazmaya kalksak (üç tane merge, sonra groupby, sonra agg, sonra rename, sonra sort_values) sayfanın yarısı dolardı [16][17].
Hangisini Ne Zaman Kullanalım?
Kabaca bir rehber:
- Sadece bir filtreleme yapacaksak, küçük-orta DataFrame:
df.query()yeterli, ek bir paket bile gerekmiyor [3][4]. - Sütun türetme veya aritmetik ifade:
df.eval()hızlı ve okunaklı [5]. - Toplulaştırma, join, window function, karmaşık SQL: DuckDB [12][16][20].
- Veri MB’den GB’ye geçtiğinde: yine DuckDB; pandas tek iş parçacıklı ve bellekte tutuyor, DuckDB paralel ve dışa taşıyor (out-of-core) [12][24].
- Eski projede
pandasqlvarsa: zamanı varken DuckDB’ye taşıyalım, performans farkı acımasız [8].
DuckDB pandas’ı öldürmüyor — onu tamamlıyor. Satır bazlı Python mantığı, apply ile özel fonksiyon, görselleştirme, ML kütüphanelerine geçiş için pandas hâlâ rakipsiz [10][24]. SQL’in parladığı yerlerde (analitik sorgular, büyük join’ler) DuckDB’ye geçip sonucu pandas’a iade etmek en pragmatik akış oluyor.
Bonus: SQL Sonucu Yine pandas’a Dönerken Tip Kaybı Olmuyor
Önceki yazımızda ADBC’den bahsederken vurguladığımız tip korunması (type preservation) konusu DuckDB’de de geçerli. DuckDB içte Arrow bellek formatı (memory format) kullandığı için, sorgu sonucunu .df() ile aldığımızda tipler temiz geliyor — özellikle dtype_backend="pyarrow" modlu pandas iş akışlarında round-trip neredeyse maliyetsiz [11][24].
con.execute("SELECT * FROM df").fetch_df(date_as_object=False)
veya doğrudan Arrow tablosu alıp pandas’a verme:
arrow_tbl = con.execute("SELECT * FROM df").fetch_arrow_table()
df_back = arrow_tbl.to_pandas(types_mapper=pd.ArrowDtype)
Sonuç
“Pandas DataFrame’lerini SQL ile sorgulayabilir miyim?” sorusunun cevabı: Evet, üstelik birkaç farklı yolla. Bugün için modern cevap DuckDB; hem sözdizimi (syntax) açısından tam SQL, hem performans olarak pandas’ı geride bırakacak kadar hızlı, hem de kurulumu tek satır. df.query() ve df.eval() yerel hız oyuncuları olarak küçük filtreler için cebimizde duruyor, pandasql ise nostaljik bir köşede.
Bir sonraki ETL akışımda büyük bir gruplu toplulaştırma yazacaksam doğrudan duckdb.sql("...").df() ile başlardım. Hem kod kısalıyor, hem makinem daha az terliyor, hem de SQL bilen takım arkadaşlarımla aynı dili konuşuyoruz. Üç kuş, tek taş.
Bir sonraki yazıda büyük olasılıkla DuckDB’yi doğrudan Parquet ve CSV dosyaları üzerinde nasıl kullanacağımıza bakacağız; takipte kalın!
Kaynaklar
[1] pandas.DataFrame.query — pandas documentation: https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.query.html
[2] Queiroz, F. — Pandas Query Examples: SQL-like queries in dataframes: https://queirozf.com/entries/pandas-query-examples-sql-like-syntax-queries-in-dataframes
[3] Kyriakidis, G. — How to Filter Pandas DataFrames Using ‘in’ and ‘not in’, Towards Data Science: https://towardsdatascience.com/pandas-in-notin-ff2415f1e3e1/
[4] SparkCodeHub — Mastering the Query Method in Pandas: https://www.sparkcodehub.com/pandas/data-manipulation/query-guide
[5] VanderPlas, J. — High-Performance Pandas: eval() and query(), Python Data Science Handbook: https://jakevdp.github.io/PythonDataScienceHandbook/03.12-performance-eval-and-query.html
[6] Oguntimehin, K. — Get More Efficient in your Data Analysis with Pandas query() and eval() Methods, Towards Data Science: https://towardsdatascience.com/get-more-efficient-in-your-data-analysis-with-pandas-query-and-eval-methods-3646317e591f/
[7] pandasql — PyPI: https://pypi.org/project/pandasql3
[8] Mühleisen, H. & Raasveldt, M. — Efficient SQL on Pandas with DuckDB, DuckDB Blog: https://duckdb.org/2021/05/14/sql-on-pandas
[9] DigitalOcean — How DuckDB Complements Pandas for Large-Scale Analytics: https://www.digitalocean.com/community/tutorials/duckdb-complements-pandas-for-large-scale-analytics
[10] Bantra, A. — Using DuckDB in Python: A Comprehensive Guide, Medium: https://medium.com/@anshubantra/using-duckdb-in-python-a-comprehensive-guide-d14bc0b06546
[11] MotherDuck — DuckDB Python Quickstart (Part 2): Pandas, Arrow, Polars & Python UDFs: https://motherduck.com/learn/duckdb-python-quickstart-part2/
[12] DZone — DuckDB for Python Developers: https://dzone.com/articles/duckdb-for-python-developers
[13] BetterStack — DuckDB for Python: A beginner’s guide: https://betterstack.com/community/guides/scaling-python/duckdb-python/
[14] DuckDB Documentation — SQL on Pandas: https://duckdb.org/docs/current/guides/python/sql_on_pandas
[15] Bala Priya, C. — How to Query Pandas DataFrames with DuckDB, Statology: https://www.statology.org/how-to-query-pandas-dataframes-with-duckdb/
[16] Rosidi, N. — 7 DuckDB SQL Queries That Save You Hours of Pandas Work, KDnuggets: https://www.kdnuggets.com/7-duckdb-sql-queries-that-save-you-hours-of-pandas-work
[17] DEV Community — DuckDB vs Pandas – Exploring DuckDB’s capabilities: https://dev.to/nageen20/duckdb-vs-pandas-exploring-duckdbs-capabilities-6kf
[18] MotherDuck — DuckDB vs Pandas vs Polars for Python Developers: https://motherduck.com/blog/duckdb-versus-pandas-versus-polars/
[19] DuckDB Documentation — Window Functions: https://duckdb.org/docs/sql/functions/window_functions
[20] DuckDB Documentation — Python API: https://duckdb.org/docs/api/python/overview
[21] DuckDB Documentation — Relational API on Pandas: https://duckdb.org/docs/current/guides/python/relational_api_pandas
[22] KDnuggets — We Benchmarked DuckDB, SQLite, and Pandas on 1M Rows: https://www.kdnuggets.com/we-benchmarked-duckdb-sqlite-and-pandas-on-1m-rows-heres-what-happened
[23] Hash Block — DuckDB vs Spark vs Pandas: The Benchmark Truth, Medium: https://medium.com/@connect.hashblock/duckdb-vs-spark-vs-pandas-the-benchmark-truth-6a9a184bf34a
[24] Codecentric — DuckDB vs DataFrame libraries benchmark: https://www.codecentric.de/en/knowledge-hub/blog/duckdb-vs-dataframe-libraries