Mert Özen Her Satırda Daha İleri
Veritabanı SQL Temelleri — #16

Aynı Sorgu Dört Lehçede: SQL Server, PostgreSQL, MySQL ve SQLite Arasındaki Farklar

Mert Özen 8 Kas 2026 14 dk 4 görüntülenme
Aynı Sorgu Dört Lehçede: SQL Server, PostgreSQL, MySQL ve SQLite Arasındaki Farklar

Aynı Bookstore sorguları SQL Server, PostgreSQL, MySQL ve SQLite'ta farklı syntax ve farklı sonuç verir: MySQL'de || bir OR'dur ve 7 / 2 3.5000 eder, PostgreSQL büyük-küçük harfe duyarlıdır. Sayfalamayı, string birleştirmeyi, identity ve RETURNING'i, upsert'i, tarih function'larını, quoting'i, boolean'ı, NULL sıralamasını ve LATERAL'ı dört engine'de gerçek çıktılarla karşılaştırıyoruz.

Sürüm notu: Bu yazıdaki SQL (Structured Query Language) örnekleri SQL Server 2025 (17.0, CU10 (Cumulative Update 10)), PostgreSQL 18.6, MySQL 8.4.13 ve SQLite 3.54 üzerinde, Bookstore veritabanının aynı verisiyle çalıştırılmıştır.

Önce Tahmin Et

Bookstore'un books, customers, orders, order_items ve stock_items tablolarını aynı veriyle dört veritabanına yükledim: SQL Server, PostgreSQL, MySQL ve SQLite. Sonra üç basit ifadeyi dördünde de çalıştırdım: title = 'dune' koşuluyla kaç kitap bulunur (katalogdaki kitabın adı büyük harfle "Dune"), 7 / 2 kaç eder ve 'a' || 'b' ne döndürür? Sonuçları tahmin et:

İfadeSQL Server 2025PostgreSQL 18MySQL 8.4SQLite 3.54
title = 'dune'????
7 / 2????
'a' || 'b'????

SQL Server'daki gerçek çıktı:

dune_matches int_div pipes plus_null concat_null
------------ ------- ----- --------- -----------
           1       3 ab         NULL a

PostgreSQL:

 dune_matches | int_div | pipes | pipes_null | concat_null 
--------------+---------+-------+------------+-------------
            0 |       3 | ab    |            | a
(1 row)

MySQL:

+--------------+---------+------------+-------+-------------+
| dune_matches | int_div | int_div_op | pipes | concat_null |
+--------------+---------+------------+-------+-------------+
|            1 |  3.5000 |          3 |     0 | NULL        |
+--------------+---------+------------+-------+-------------+

SQLite:

+--------------+-----------+---------+-------+------------+-------------+
| dune_matches | dune_like | int_div | pipes | pipes_null | concat_null |
+--------------+-----------+---------+-------+------------+-------------+
|            0 |         1 |       3 | ab    |            | a           |
+--------------+-----------+---------+-------+------------+-------------+

Büyük-küçük harf: SQL Server ve MySQL "dune"u buldu, PostgreSQL ve SQLite bulmadı (SQLite'ta LIKE ise buldu). Bölme: MySQL 3.5000 dedi, diğerleri 3. Ve 'a' || 'b': üçü "ab" dedi, MySQL 0 dedi, çünkü MySQL'de || varsayılan olarak mantıksal OR'dur ve iki string'i sayıya çevirip karşılaştırır. Bu yazı, serinin başından beri söylediğimiz "önce standart, sonra T-SQL (Transact-SQL), sonra fark notu" kuralının toplu hesabı: aynı Bookstore sorgularını dört lehçede yan yana koyuyoruz.

Sayfalama: TOP, OFFSET/FETCH ve LIMIT

Serinin 05. yazısındaki "en yeni kitaplar, 3. sayfa" sorgusunu sayfa başına 5 kitapla yazalım. SQL Server'da standart OFFSET ... FETCH:

SELECT book_id, title, listed_on
FROM dbo.books
ORDER BY listed_on DESC, book_id DESC
OFFSET 10 ROWS FETCH NEXT 5 ROWS ONLY;
book_id title                                             listed_on
------- ------------------------------------------------- ----------
     67 The Little Prince                                 2026-01-11
     66 The Algorithm Design Manual                       2025-12-27
     65 The Art of Computer Programming                   2025-12-22
     64 Structure and Interpretation of Computer Programs 2025-12-02
     63 Domain-Driven Design                              2025-11-26

PostgreSQL aynı standart syntax'ı birebir kabul etti ve aynı beş row'u döndürdü. Ama PostgreSQL dünyasında yaygın olan LIMIT:

SELECT book_id, title, listed_on
FROM books
ORDER BY listed_on DESC, book_id DESC
LIMIT 5 OFFSET 10;

MySQL ve SQLite ise standart OFFSET ... FETCH'i tanımıyor; ikisinde de yalnız LIMIT var:

SELECT book_id, title, listed_on
FROM books
ORDER BY listed_on DESC, book_id DESC
OFFSET 10 ROWS FETCH NEXT 5 ROWS ONLY;
ERROR 1064 (42000) at line 2: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'OFFSET 10 ROWS FETCH NEXT 5 ROWS ONLY' at line 4

LIMIT 5 OFFSET 10 dört engine'in üçünde çalışır, SQL Server'da çalışmaz. OFFSET ... FETCH ise SQL Server ve PostgreSQL'de çalışır. SQL Server'ın eski TOP (n)'i standart değildir ve yalnız ilk sayfa içindir. Dört engine'de de aynı sonuç için tek şart, ORDER BY'ın benzersiz bir tie-breaker ile bitmesi; 30 kitap aynı listed_on değerini paylaşıyor ve book_id DESC olmasa her engine eşitler arasında farklı bir sıra seçebilirdi.

String Birleştirme ve Listeler

İki string'i birleştirmenin standart operator'ü ||. SQL Server bunu ancak 2025 sürümüyle destekledi; öncesinde operator olarak yalnız + vardı (CONCAT function'ı 2012'den beri var). MySQL'de ise yukarıda gördüğün gibi || varsayılan olarak OR; PIPES_AS_CONCAT SQL mode'u açılmadıkça birleştirme yapmaz. MySQL 8.4 bunu yaptığında bir de uyarı veriyor: '|| as a synonym for OR' is deprecated. Dört engine'de aynı çalışan tek yol CONCAT() function'ı, ama NULL davranışı farklı: SQL Server ve PostgreSQL'de CONCAT('a', NULL) "a" döndürür (NULL'ı boş string sayar), MySQL'de NULL döner. || ise standarda uygun olarak NULL ile birleşince NULL üretir.

Gruptan liste üretmek de dört farklı syntax demek. Ülke başına müşteri şehirleri:

SELECT country_code,
       STRING_AGG(city, ', ') WITHIN GROUP (ORDER BY city) AS cities
FROM (SELECT DISTINCT country_code, city FROM dbo.customers WHERE city IS NOT NULL) AS c
GROUP BY country_code
ORDER BY country_code;
SELECT country_code,
       string_agg(DISTINCT city, ', ' ORDER BY city) AS cities
FROM customers
GROUP BY country_code
ORDER BY country_code;
SELECT country_code,
       GROUP_CONCAT(DISTINCT city ORDER BY city SEPARATOR ', ') AS cities
FROM customers
GROUP BY country_code
ORDER BY country_code;
SELECT country_code,
       string_agg(city, ', ' ORDER BY city) AS cities
FROM (SELECT DISTINCT country_code, city FROM customers WHERE city IS NOT NULL)
GROUP BY country_code
ORDER BY country_code;
country_code cities
------------ ---------------------------------------
DE           Berlin, Munich
GB           London, Manchester
NL           Amsterdam
TR           Ankara, Antalya, Bursa, Istanbul, Izmir

Dördü de aynı listeyi döndürdü. Farklar syntax'ta: SQL Server sıralamayı WITHIN GROUP ile alıyor ve STRING_AGG içinde DISTINCT desteklemiyor (bu yüzden derived table); PostgreSQL ve SQLite sıralamayı aggregate'in içinde alıyor; MySQL GROUP_CONCAT'te ayracı SEPARATOR kelimesiyle veriyor. SQLite string_agg'i 3.44 ile ekledi ve DISTINCT'i yalnız tek argümanlı aggregate'lerde kabul ediyor:

Parse error near line 2: DISTINCT aggregates must have exactly one argument

Identity ve Yeni Id'yi Almak

Otomatik artan key için dört engine'in dört farklı tanımı var: SQL Server'da INT IDENTITY(1,1), PostgreSQL'de standart INT GENERATED BY DEFAULT AS IDENTITY (eski SERIAL hâlâ çalışıyor ama yeni kodda önerilmiyor), MySQL'de INT AUTO_INCREMENT, SQLite'ta ise INTEGER PRIMARY KEY (row'un dahili rowid'sinin takma adı olur). Bu farkları lab script'lerindeki CREATE TABLE'larda yazdım; birebir taşınabilir bir CREATE TABLE yazmak pratikte mümkün değil.

Yeni eklenen row'un id'sini geri almak:

INSERT INTO dbo.orders (customer_id, ordered_at, status)
OUTPUT inserted.order_id
VALUES (1, '2026-06-30 12:00:00', 'Pending');
INSERT INTO orders (customer_id, ordered_at, status)
VALUES (1, '2026-06-30 12:00:00', 'Pending')
RETURNING order_id;
INSERT INTO orders (customer_id, ordered_at, status)
VALUES (1, '2026-06-30 12:00:00', 'Pending');
SELECT LAST_INSERT_ID() AS order_id;
order_id
--------
     380

SQL Server OUTPUT, PostgreSQL ve SQLite RETURNING kullanıyor; dördü de 380 döndürdü. MySQL'de RETURNING yok, ayrı bir SELECT LAST_INSERT_ID() gerekiyor; bu değer connection'a özeldir, yani aynı connection'da sorulduğu sürece eşzamanlı insert'lerden etkilenmez. Serinin 15. yazısında OUTPUT'u ayrıntılı gördük.

Upsert: MERGE, ON CONFLICT ve ON DUPLICATE KEY

Depoya gelen sevkiyat: Pride and Prejudice'in (1) stok row'u var, 5 adet eklenecek; Charlotte's Web'in (70) stok row'u yok, 10 adetle oluşturulacak.

MERGE dbo.stock_items AS s
USING (VALUES (1, 5), (70, 10)) AS d (book_id, quantity)
   ON d.book_id = s.book_id
WHEN MATCHED THEN
    UPDATE SET s.quantity_on_hand = s.quantity_on_hand + d.quantity
WHEN NOT MATCHED THEN
    INSERT (book_id, quantity_on_hand) VALUES (d.book_id, d.quantity)
OUTPUT inserted.book_id, inserted.quantity_on_hand;
INSERT INTO stock_items (book_id, quantity_on_hand)
VALUES (1, 5), (70, 10)
ON CONFLICT (book_id) DO UPDATE
    SET quantity_on_hand = stock_items.quantity_on_hand + EXCLUDED.quantity_on_hand
RETURNING book_id, quantity_on_hand;
INSERT INTO stock_items (book_id, quantity_on_hand)
VALUES (1, 5), (70, 10) AS d
ON DUPLICATE KEY UPDATE
    quantity_on_hand = stock_items.quantity_on_hand + d.quantity_on_hand;
SELECT book_id, quantity_on_hand FROM stock_items WHERE book_id IN (1, 70);
INSERT INTO stock_items (book_id, quantity_on_hand)
VALUES (1, 5), (70, 10)
ON CONFLICT (book_id) DO UPDATE
    SET quantity_on_hand = quantity_on_hand + excluded.quantity_on_hand
RETURNING book_id, quantity_on_hand;
 book_id | quantity_on_hand 
---------+------------------
       1 |               52
      70 |               10
(2 rows)

Dördünde de sonuç aynı: 47 olan stok 52 oldu, 70 için yeni row açıldı. Ama mekanizmalar farklı. PostgreSQL ve SQLite'taki ON CONFLICT, bir unique constraint ya da primary key çakışmasına bağlanır ve tek statement içinde atomiktir; EXCLUDED eklenmeye çalışılan row'u temsil eder. MySQL'in ON DUPLICATE KEY UPDATE'i tablodaki herhangi bir unique key çakışmasında tetiklenir, bu yüzden birden çok unique index olan tablolarda beklemediğin row'u güncelleyebilir. 8.0.19'dan beri yeni row'a AS d gibi bir alias ile erişilir; eski VALUES(column) biçimi deprecated. SQL Server'da ise standart MERGE var ve 15. yazıda gördüğümüz concurrency notlarıyla birlikte gelir. PostgreSQL 15'ten beri MERGE'ü de destekliyor.

Tarih Function'ları

Ay başına göre gruplanmış sipariş sayıları:

SELECT DATETRUNC(month, ordered_at) AS month_start, COUNT(*) AS orders
FROM dbo.orders
WHERE ordered_at >= '2025-10-01' AND ordered_at < '2026-01-01'
GROUP BY DATETRUNC(month, ordered_at)
ORDER BY month_start;
SELECT date_trunc('month', ordered_at) AS month_start, COUNT(*) AS orders
FROM orders
WHERE ordered_at >= '2025-10-01' AND ordered_at < '2026-01-01'
GROUP BY date_trunc('month', ordered_at)
ORDER BY month_start;
SELECT DATE_FORMAT(ordered_at, '%Y-%m-01') AS month_start, COUNT(*) AS orders
FROM orders
WHERE ordered_at >= '2025-10-01' AND ordered_at < '2026-01-01'
GROUP BY DATE_FORMAT(ordered_at, '%Y-%m-01')
ORDER BY month_start;
SELECT strftime('%Y-%m-01', ordered_at) AS month_start, COUNT(*) AS orders
FROM orders
WHERE ordered_at >= '2025-10-01' AND ordered_at < '2026-01-01'
GROUP BY strftime('%Y-%m-01', ordered_at)
ORDER BY month_start;
month_start         orders
------------------- ------
2025-10-01 00:00:00     24
2025-11-01 00:00:00     23
2025-12-01 00:00:00     42

Sayılar dördünde de aynı (24, 23, 42), ama tipler değil. SQL Server'ın DATETRUNC'ı (2022+) ve PostgreSQL'in date_trunc'ı tarih/zaman döndürür; MySQL'deki DATE_FORMAT ve SQLite'taki strftime ise string. SQLite'ta ayrı bir tarih tipi zaten yok: tarihler TEXT, REAL ya da INTEGER olarak saklanır ve function'lar bunları yorumlar. Tarih aralığını >= '2025-10-01' AND < '2026-01-01' gibi yarı açık yazmak (serinin 06. yazısındaki kural) dördünde de aynı çalışan nadir şeylerden biri.

Identifier Quoting, Büyük-Küçük Harf, Boolean ve NULL Sıralaması

Boşluk içeren bir alias'ı tırnaklamanın standart yolu çift tırnak. SQL Server hem [ ] hem " " kabul ediyor, PostgreSQL yalnız " ", MySQL varsayılan olarak backtick:

SELECT TOP (1) title AS [Book Title], list_price AS "List Price" FROM dbo.books ORDER BY book_id;
SELECT title AS "Book Title", "Dune" AS dq FROM books ORDER BY book_id LIMIT 1;
+---------------------+------+
| Book Title          | dq   |
+---------------------+------+
| Pride and Prejudice | Dune |
+---------------------+------+

MySQL'de "Dune" bir column adı değil, string literal olarak okundu: ANSI_QUOTES SQL mode'u kapalıyken çift tırnak string demek. PostgreSQL'in tersi bir sürprizi var: tırnaksız identifier'ları küçük harfe çevirir, tırnaklıları olduğu gibi bırakır. Tabloyu title olarak oluşturup "Title" diye sorarsan:

ERROR:  column "Title" does not exist
LINE 1: SELECT "Title" FROM books LIMIT 1;
               ^
HINT:  Perhaps you meant to reference the column "books.title".

Bu yüzden PostgreSQL'de identifier'ları hep küçük harf ve snake_case yazmak gelenektir (Bookstore şeması da böyle). Açılıştaki büyük-küçük harf farkı ise identifier'la değil veriyle ilgili: SQL Server'da karşılaştırma veritabanının collation'ına bağlı (SQL_Latin1_General_CP1_CI_AS; buradaki CI (case-insensitive), büyük-küçük harf duyarsız demek), MySQL 8'in varsayılan collation'ı utf8mb4_0900_ai_ci de öyle. PostgreSQL ve SQLite ise varsayılan olarak büyük-küçük harf duyarlı; PostgreSQL'de ILIKE ya da lower(), SQLite'ta ASCII (American Standard Code for Information Interchange) karakterler için LIKE kullanırsın. Türkçe İ'nin collation ile ilişkisini serinin 03. yazısında görmüştük.

Boolean tipi de dört engine'de dört ayrı hikâye. PostgreSQL'in gerçek bir boolean'ı var ve WHERE is_active yazabilirsin. MySQL'deki BOOLEAN aslında TINYINT(1), TRUE da 1'in takma adı. SQLite TRUE'yu 1 olarak kabul ediyor (3.23'ten beri). SQL Server'da ise bit var ama SQL Server 2025'te bile TRUE literal'i yok:

SELECT COUNT(*) AS active_books FROM dbo.books WHERE is_active = TRUE;
Msg 207, Level 16, State 1, Server b8be943222fa, Line 1
Invalid column name 'TRUE'.

NULL'ların sıralamadaki yeri de farklı. ORDER BY city artan sırada SQL Server, MySQL ve SQLite NULL'ı en başa koyar, PostgreSQL en sona:

   city   
----------
 Berlin
 Berlin
 Istanbul
(3 rows)

Standart bunun için NULLS FIRST / NULLS LAST tanımlar. PostgreSQL ve SQLite (3.30+) destekliyor; SQL Server ve MySQL desteklemiyor:

Msg 102, Level 15, State 1, Server b8be943222fa, Line 1
Incorrect syntax near 'NULLS'.

Taşınabilir alternatif, sıralamaya bir CASE eklemek: ORDER BY CASE WHEN city IS NULL THEN 1 ELSE 0 END, city.

APPLY, LATERAL ve FULL OUTER JOIN

Serinin 09. yazısında her müşterinin son siparişini SQL Server'ın CROSS APPLY'ı ile almıştık. Standardın karşılığı LATERAL:

SELECT c.customer_id, last_order.order_id, last_order.ordered_at
FROM dbo.customers AS c
CROSS APPLY (
    SELECT TOP (1) o.order_id, o.ordered_at
    FROM dbo.orders AS o
    WHERE o.customer_id = c.customer_id
    ORDER BY o.ordered_at DESC
) AS last_order
WHERE c.customer_id <= 3
ORDER BY c.customer_id;
SELECT c.customer_id, last_order.order_id, last_order.ordered_at
FROM customers AS c
CROSS JOIN LATERAL (
    SELECT o.order_id, o.ordered_at
    FROM orders AS o
    WHERE o.customer_id = c.customer_id
    ORDER BY o.ordered_at DESC
    LIMIT 1
) AS last_order
WHERE c.customer_id <= 3
ORDER BY c.customer_id;
customer_id order_id ordered_at
----------- -------- -------------------
          1      380 2026-06-30 12:00:00
          2      366 2026-06-06 09:39:30
          3      377 2026-06-20 16:58:05

PostgreSQL'deki CROSS JOIN LATERAL sorgusu MySQL 8.4'te de değiştirmeden çalıştı (MySQL LATERAL'ı 8.0.14 ile ekledi) ve aynı üç row'u döndürdü. SQLite ise LATERAL'ı tanımıyor:

Parse error near line 2: near "SELECT": syntax error
  r_id FROM customers AS c CROSS JOIN LATERAL (     SELECT o.order_id FROM order
                                      error here ---^

SQLite'ta aynı sonucu correlated subquery ya da window function ile yazarsın. Ters yönde bir eksik de MySQL'de: FULL OUTER JOIN yok.

ERROR 1064 (42000) at line 2: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'FULL OUTER JOIN orders AS o ON o.customer_id = c.customer_id' at line 3

MySQL'de FULL OUTER JOIN'in yerine LEFT JOIN ile RIGHT JOIN'in UNION'ı yazılır. SQLite ise FULL OUTER JOIN'i 3.39 ile ekledi.

Üretimde Dikkat

Özet tablo, bu yazıdaki denemelere dayanıyor:

KonuSQL Server 2025PostgreSQL 18MySQL 8.4SQLite 3.54
SayfalamaOFFSET ... FETCH, TOPOFFSET ... FETCH, LIMITLIMITLIMIT
Birleştirme+, CONCAT, || (2025)||, concatCONCAT (|| = OR)||, concat
Liste aggregateSTRING_AGG ... WITHIN GROUPstring_agg(... ORDER BY)GROUP_CONCATstring_agg, group_concat
Otomatik keyIDENTITYGENERATED AS IDENTITYAUTO_INCREMENTINTEGER PRIMARY KEY
Yeni idOUTPUTRETURNINGLAST_INSERT_ID()RETURNING
UpsertMERGEON CONFLICT, MERGEON DUPLICATE KEY UPDATEON CONFLICT
Identifier quoting[ ], " "" "backtick" ", [ ], backtick
NULLS LASTYokVarYokVar
LATERALAPPLYVarVarYok
FULL OUTER JOINVarVarYokVar

SQLite'ın bir özelliği bu tabloya sığmıyor ve taşıma sırasında en çok sürpriz yaratanı o: tip esnekliği (type affinity). Column tipi bir öneridir, kural değil:

INSERT INTO books (isbn, title, list_price, listed_on, is_active)
VALUES ('9798900000999', 'Test', 'cheap', 'tomorrow', 'yes');
SELECT book_id, list_price, typeof(list_price) AS price_type, listed_on FROM books WHERE isbn = '9798900000999';
+---------+------------+------------+-----------+
| book_id | list_price | price_type | listed_on |
+---------+------------+------------+-----------+
|      78 | cheap      | text       | tomorrow  |
+---------+------------+------------+-----------+

NUMERIC olarak tanımlanmış fiyat column'una "cheap", tarih column'una "tomorrow" yazıldı ve hata yok. SQLite 3.37 ile gelen STRICT tablolar bunu engeller:

CREATE TABLE price_check (price REAL NOT NULL) STRICT;
INSERT INTO price_check (price) VALUES ('cheap');
Error near line 3: cannot store TEXT value in REAL column price_check.price

Entity Framework Core (EF Core) serisinde testlerde SQLite kullanmanın tuzaklarından bahsetmiştik; bu tip esnekliği, production'da SQL Server'ın reddedeceği verinin testte sessizce geçmesinin bir sebebi.

Taşınabilir SQL için benim kontrol listem:

  • Sayfalamada ORDER BY'ı her zaman benzersiz bir anahtarla bitir; LIMIT/FETCH syntax'ını veri erişim katmanında tek yerde tut.
  • String birleştirmede || ya da + yerine, NULL davranışını bilerek CONCAT ya da COALESCE kullan.
  • Tam sayı bölmesinde tipi açıkça genişlet (* 1.0 ya da CAST); MySQL'in farklı davrandığını unutma.
  • Büyük-küçük harf duyarlılığını collation'a bırakma; arama gerekiyorsa açıkça LOWER() ya da uygun collation kullan.
  • Upsert ve yeni id alma kodunu lehçeye özel yaz ve test et; ortak bir syntax yok.
  • Hedef engine'i ve sürümünü CI (Continuous Integration) ortamında gerçekten çalıştır; "ANSI (American National Standards Institute) uyumlu" etiketi yeterli değil.

ORM (Object-Relational Mapping) kullanıyorsan bu farkların çoğunu provider soyutlar; EF Core'un SQL Server, PostgreSQL (Npgsql), MySQL ve SQLite provider'ları aynı LINQ (Language Integrated Query) sorgusundan farklı SQL üretir. Ama raw SQL yazdığın an (EF Core serisinin Raw SQL yazısı, Dapper serisinin tamamı) lehçe senin sorumluluğuna geçer. PostgreSQL'e özgü konular ilerideki PostgreSQL serisinin konusu olacak.

Sık Düşülen Tuzaklar

"ANSI uyumlu" etiketine güvenip sorguyu hedef engine'de çalıştırmadan taşımak. Bu yazıdaki farkların hiçbiri derleme anında yakalanmaz, bazıları hiç hata vermez.

MySQL'de || ile string birleştirmek. Hata vermez, 0 döndürür.

PostgreSQL'de büyük harfli, tırnaklı identifier'lar oluşturmak ("BookId"). Sonra her sorguda tırnak yazmak zorunda kalırsın.

SQL Server'dan PostgreSQL'e geçerken büyük-küçük harf duyarsız aramaların sessizce boş dönmesi.

NULL sıralamasının her engine'de aynı olduğunu varsaymak.

SQLite'ı production engine'inin yerine test engine'i olarak kullanıp tip ve collation farklarını gözden kaçırmak.

Küçük Bir Deneme

★☆☆ Bu yazıdaki "her müşterinin son siparişi" sorgusunu SQLite için window function (ROW_NUMBER) ile yaz ve dört engine'de de aynı sonucu aldığını doğrula.

★★☆ MySQL'de FULL OUTER JOIN olmadan "sipariş vermemiş müşteriler ve müşterisi silinmiş siparişler" listesini LEFT JOIN + RIGHT JOIN + UNION ile yaz. UNION mı UNION ALL mı gerekiyor, neden?

★★★ Serinin 14. yazısındaki gaps and islands sorgusunu dört engine'de çalıştır. Hangi tarih function'larını değiştirmen gerekti? PostgreSQL'de date - integer aritmetiğinin SQL Server'daki DATEADD'den nasıl farklı olduğunu not et.

Lehçe farklarının bir kısmı zamanla kapanıyor: SQL Server 2022 ve 2025 standarda yaklaşan birçok syntax ekledi. Bir sonraki yazıda SQL Server 2022'nin T-SQL'e getirdiklerini, IS DISTINCT FROM'dan GENERATE_SERIES'e, önce/sonra kodlarıyla ve compatibility level gereksinimleriyle inceliyoruz.