İçeriğe geç

Aynı Sorgu, Üç Lehçe — PostgreSQL, SQL Server, Oracle

Orta 8 dk Sık karşılaşılır

Önce şunu oku: SQL mi, NoSQL mi? — Önce Erişim Desenini Yaz

30 saniyede özet

SQL ortak bir dil, ama her veritabanı onu kendi şivesiyle konuşur. Yazım farkları hemen hata verir ve kolay bulunur. Asıl tehlike sessiz farklardır: Oracle'da boş metin NULL'dır, SQL Server çoğu zaman büyük-küçük harfi ayırmaz.

Oracle’dan PostgreSQL’e geçiş bitti, bütün testler yeşil. İki hafta sonra müşteri hizmetleri, ikinci adı olmayan müşterilerin faturalarında adın boş göründüğünü bildirdi. Hiçbir sorgu hata vermemişti.

  1. Bayt: Oracle'dan PostgreSQL'e geçtik, bütün testler yeşil!

  2. Sen: Harika. Sonra?

  3. Bayt: İki hafta sonra bazı faturalarda müşterinin adı boş çıkmaya başladı. Tek bir hata yok!

  4. Bayt: Aynı kelime iki ülkede farklı şey anlatabilir. SQL için de öyle mi?

Ortak dil, farklı şive

SQL bir ANSI/ISO standardıdır. Her motor bu standardın bir kısmını uygular, üstüne kendi SQL lehçesiBir veritabanının standart SQL'e eklediği ya da ondan farklı uyguladığı sözdizimi ve davranışlar: T-SQL, PL/SQL, PL/pgSQL.Sözlükte gör → ekler: PostgreSQL’de PL/pgSQL, SQL Server’da T-SQL, Oracle’da PL/SQL.

Farkları iki gruba ayırmak işe yarar. Gürültülü farklar sözdizimi hatası verir; ilk çalıştırmada görünür. Sessiz farklar çalışır ve başka bir sonuç döndürür.

Kafam karıştı, daha basit anlat

SQL ortak bir dildir, ama her veritabanının kendi şivesi vardır. Bazı farklar hemen hata verir, bazıları ise sessizce başka bir sonuç döndürür.

Hızlı kontrolOrta

Bir veritabanından diğerine taşırken hangi tür fark daha pahalıdır?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Sessiz farklar

Oracle'da name kolonuna '' (boş string) yazdın. WHERE name IS NULL bu satırı bulur mu? Cevabı göster

Bulur. Oracle VARCHAR2’de boş string ile NULL’ı aynı sayar. WHERE name = '' ise hiçbir satır bulmaz, çünkü NULL hiçbir şeye eşit değildir.

Aynı cümle, iki lehçe, iki anlam.
Adım adım oku
  1. İki veritabanında da aynı sorgu çalışıyor: ad, ikinci ad ve soyad birleştiriliyor; ikinci ad boş.
  2. Oracle boş metni NULL sayar ve birleştirirken NULL'u atlar: ad yine de oluşur.
  3. PostgreSQL boş metni ayrı tutar; NULL ile yapılan her birleştirme NULL döner.
  4. Sorgu hata vermez, ama ikinci adı olmayan müşterinin adı faturada boş görünür.

En sık görülen üç sessiz fark:

  • Boş string: Oracle’da '' NULL’dır; PostgreSQL ve SQL Server’da bir değerdir.
  • NULL ile birleştirme: Oracle’da 'Ali' || NULL sonucu 'Ali'dir; PostgreSQL’de NULL’dır.
  • Harf duyarlılığı: SQL Server’da karşılaştırma collationMetinlerin nasıl sıralanıp karşılaştırılacağını belirleyen kurallar: büyük/küçük harf ve aksan duyarlılığı. SQL Server'da çoğu kurulumun varsayılanı harfe duyarsızdır.Sözlükte gör →’a bağlıdır ve çoğu kurulumda 'Ali' = 'ali' doğrudur.
Kafam karıştı, daha basit anlat

Sessiz farklar en tehlikelisidir: sorgu çalışır, ama sonuç başka çıkar. Oracle’da boş yazı ile NULL aynı şeydir, diğerlerinde değildir.

Hızlı kontrolOrta

Oracle'da son iki sorgu ne döndürür?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.
users.sql
1CREATE TABLE users (id NUMBER, name VARCHAR2(50));
2INSERT INTO users VALUES (1, '');
3INSERT INTO users VALUES (2, 'Ayşe');
4SELECT count(*) FROM users WHERE name = '';
5SELECT count(*) FROM users WHERE name IS NULL;
SQLUTF-8LF

SQL Server'dan PostgreSQL'e geçtikten sonra bazı kullanıcılar giriş yapamıyor. Giriş sorgusu `WHERE email = ?`. En olası sebep?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Kendin gör

Aynı SQL, başka bir veritabanında

Tohum 173032

Veri: users tablosuna name = '' olan bir satır eklendi

Oynat ya da adımla.

Hız
Adım 0

Şu an ne oldu?

Boş string: '' NULL mı?: Oracle → PostgreSQL

Aynı SQL cümlesi hedef veritabanında çalıştırılacak. Hata mı verecek, aynı sonucu mu, yoksa sessizce başka bir sonucu mu?

Görevler0/3

  • Hata vermeden farklı sonuç veren bir taşıma bulaçık

    İpucu

    NULL, boş string ya da harf duyarlılığı.

  • Sözdizimi hatasıyla duran bir taşıma bulaçık

    İpucu

    Bir motora özgü bir anahtar kelime.

  • Sayfalamayı iki farklı veritabanı arasında değiştirmeden taşıaçık

    İpucu

    Standart sözdizimiyle yazılmış kaynaktan başla.

Olay günlüğü (0)

Henüz olay yok. Oynat veya adımla.

  1. Varsayılanla oynat. Oracle’daki boş string sorgusu PostgreSQL’de başka bir sayı döndürdü.
  2. Ad birleştirme, Oracle → PostgreSQL. İkinci adı olmayanın tüm adı NULL oldu.
  3. E-posta karşılaştırma, SQL Server → PostgreSQL. Giriş sessizce bozuldu.
  4. Sayfalama, PostgreSQL → SQL Server. Gürültülü: hemen hata.
  5. Sayfalama, SQL Server → Oracle. Standart sözdizimi olduğu gibi taşındı.
Hızlı kontrolOrta

Simülatörde Oracle'daki `first || ' ' || middle || ' ' || last` PostgreSQL'e taşınınca ikinci adı olmayanlar için NULL döndü. Neden, ve PostgreSQL'de doğru yazım ne?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Aynı iş, üç yazım

Günlük işlerin çoğu, satır varsa güncelleyip yoksa eklemek (upsertKayıt varsa güncelleyip yoksa ekleyen tek işlem. PostgreSQL'de ON CONFLICT, SQL Server ve Oracle'da MERGE ile yazılır; eşzamanlılıkta davranışları farklıdır.Sözlükte gör →) dahil, üç motorda farklı yazılır.

İşPostgreSQLSQL ServerOracle
Otomatik idGENERATED AS IDENTITYIDENTITY(1,1)GENERATED AS IDENTITY (12c+)
Eklenen id’yi alRETURNING idOUTPUT inserted.idRETURNING id INTO :id
SayfalamaLIMIT ya da OFFSET … FETCHOFFSET … FETCHOFFSET … FETCH (12c+)
UpsertON CONFLICT … DO UPDATEMERGE + HOLDLOCKMERGE
Boolean kolonbooleanbitBOOLEAN (23ai), öncesinde NUMBER(1)

Standart sözdizimi olan yerde onu seç: OFFSET … FETCH ve GENERATED AS IDENTITY üç motorda da çalışır.

Küçük bir banka örneğine bakalım. Her sabah günün döviz kurları tabloya yazılır: kur varsa güncellenir, yoksa eklenir. Aynı işi üç motorda yan yana görünce farklar kendiliğinden göze çarpıyor.

Derinleş · Günün döviz kuru: aynı upsert, üç lehçe 4 dosya · ~71 satır · ilk okumada atlayabilirsin
Proje dosyaları

db/postgresql/ V3__fx_rate.sql PostgreSQL: ON CONFLICT ile upsert; eşzamanlı iki istek de sorunsuz biter.

db/postgresql/V3__fx_rate.sql
CREATE TABLE fx_rate (
id bigint GENERATED ALWAYS AS IDENTITY,
rate_date date NOT NULL,
currency char(3) NOT NULL, -- ISO 4217: USD, EUR, GBP
buy_rate numeric(18,6) NOT NULL, -- TRY per 1 unit of currency
sell_rate numeric(18,6) NOT NULL,
CONSTRAINT pk_fx_rate PRIMARY KEY (rate_date, currency)
);
-- Daily rate feed: insert today's rate, or correct it if it is already there.
-- ON CONFLICT waits on the conflicting row instead of failing with a unique violation.
INSERT INTO fx_rate (rate_date, currency, buy_rate, sell_rate)
VALUES (:rateDate, :currency, :buyRate, :sellRate)
ON CONFLICT (rate_date, currency)
DO UPDATE SET buy_rate = EXCLUDED.buy_rate,
sell_rate = EXCLUDED.sell_rate
RETURNING id;

db/sqlserver/ V3__fx_rate.sql SQL Server: aynı iş MERGE ile; HOLDLOCK iki isteğin aynı satırı eklemesini önler.

db/sqlserver/V3__fx_rate.sql
CREATE TABLE fx_rate (
id bigint IDENTITY(1,1) NOT NULL,
rate_date date NOT NULL,
currency char(3) NOT NULL,
buy_rate decimal(18,6) NOT NULL,
sell_rate decimal(18,6) NOT NULL,
CONSTRAINT pk_fx_rate PRIMARY KEY (rate_date, currency)
);
-- HOLDLOCK keeps the "no such row" decision locked until the insert happens.
-- Without it two concurrent feeds can both take the NOT MATCHED branch.
MERGE fx_rate WITH (HOLDLOCK) AS t
USING (VALUES (@rateDate, @currency, @buyRate, @sellRate))
AS s (rate_date, currency, buy_rate, sell_rate)
ON t.rate_date = s.rate_date AND t.currency = s.currency
WHEN MATCHED THEN
UPDATE SET buy_rate = s.buy_rate, sell_rate = s.sell_rate
WHEN NOT MATCHED THEN
INSERT (rate_date, currency, buy_rate, sell_rate)
VALUES (s.rate_date, s.currency, s.buy_rate, s.sell_rate)
OUTPUT inserted.id; -- MERGE must end with a semicolon

db/oracle/ V3__fx_rate.sql Oracle: yine MERGE, ama ON parantez ister ve kaynak satır dual tablosundan gelir.

db/oracle/V3__fx_rate.sql
CREATE TABLE fx_rate (
id NUMBER(19) GENERATED ALWAYS AS IDENTITY, -- 12c+
rate_date DATE NOT NULL, -- DATE also holds a time of day
currency CHAR(3) NOT NULL,
buy_rate NUMBER(18,6) NOT NULL,
sell_rate NUMBER(18,6) NOT NULL,
CONSTRAINT pk_fx_rate PRIMARY KEY (rate_date, currency),
CONSTRAINT ck_fx_rate_day CHECK (rate_date = TRUNC(rate_date))
);
-- The ON condition needs parentheses and the source row comes from dual.
-- Two concurrent MERGEs can both try to insert: one gets ORA-00001 and is retried.
MERGE INTO fx_rate t
USING (SELECT :rateDate AS rate_date, :currency AS currency,
:buyRate AS buy_rate, :sellRate AS sell_rate
FROM dual) s
ON (t.rate_date = s.rate_date AND t.currency = s.currency)
WHEN MATCHED THEN
UPDATE SET t.buy_rate = s.buy_rate, t.sell_rate = s.sell_rate
WHEN NOT MATCHED THEN
INSERT (rate_date, currency, buy_rate, sell_rate)
VALUES (s.rate_date, s.currency, s.buy_rate, s.sell_rate);

counter-example/ silent-after-migration.sql Şöyle de yazılabilirdi, ama bak ne oluyor: iki sorgu hatasız çalışıyor, taşımadan sonra başka sonuç veriyor.

counter-example/silent-after-migration.sql
-- 1. Account holder name. Oracle stored the empty middle name as NULL and || skips NULL:
-- Oracle gives 'Ayse Yilmaz', PostgreSQL gives NULL for the whole name.
SELECT first_name || ' ' || middle_name || ' ' || last_name AS holder_name
FROM customer;
-- PostgreSQL: concat_ws skips NULLs, NULLIF turns '' into NULL first.
SELECT concat_ws(' ', first_name, NULLIF(middle_name, ''), last_name) AS holder_name
FROM customer;
-- 2. Transfers without a description. Migrated Oracle rows hold NULL, new rows hold ''.
SELECT count(*) FROM transfer WHERE description IS NULL; -- only the old ones
SELECT count(*) FROM transfer WHERE COALESCE(description, '') = ''; -- both
Kafam karıştı, daha basit anlat

Mümkün olan yerde herkesin anladığı standart yazımı seç. Böylece veritabanı değişse de sorgun aynı kalır.

Hızlı kontrolOrta

Üç motorda da (PostgreSQL, SQL Server, güncel Oracle) değiştirmeden çalışan sayfalama yazımı hangisi?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Her taşıma farkını gürültülü (hata verir) ya da sessiz (farklı sonuç verir) olarak ayır.

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Sınıflandırılmamış

Gürültülü

İlk çalıştırmada hata.

    Sessiz

    Çalışır, başka sonuç verir.

      Tuzaklar

      MERGE atomik sanılır. Eşzamanlı iki MERGE aynı yeni anahtarı ekleyebilir ve biri unique ihlaliyle düşer. PostgreSQL’in ON CONFLICT’i bu durum için tasarlanmıştır.

      @@IDENTITY. SQL Server’da bir trigger başka bir tabloya ekleme yaptıysa onun id’sini döndürür; SCOPE_IDENTITY() ya da OUTPUT kullan.

      ROWNUM ve ORDER BY. Oracle’da WHERE ROWNUM <= 10 ORDER BY … önce 10 satır seçer, sonra sıralar.

      Tanımlayıcı harfleri. PostgreSQL tırnaksız isimleri küçük harfe, Oracle büyük harfe çevirir. Tırnakla oluşturulan "OrderItem" her sorguda tırnak ister.

      Hızlı kontrolOrta

      Oracle'da 'en yüksek tutarlı 10 sipariş' raporu yanlış siparişleri gösteriyor. Hatalı satır hangisi?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

      Hatalı satıra dokun, sonra kontrol et.

      top_orders.sql
      SQLUTF-8LF

      Kendini sına

      Şimşek turu1/5

      SQL bir standarttır ama her veritabanı kendi lehçesini ekler.

      Soru 1/2İleri

      SQL Server'da `MERGE` ile yazılmış bir upsert, yük altında arada primary key ihlali veriyor. Neden ve tipik düzeltme ne?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

      Aklında kalacak üç şey

      1. 1 Yazım farkları (LIMIT, ON CONFLICT, dual) hata verir ve ilk testte yakalanır. Pahalı olanlar, hata vermeden farklı sonuç veren farklardır.
      2. 2 Oracle VARCHAR2'de boş metni NULL sayar. SQL Server'da metin karşılaştırması collation ayarına bağlıdır ve çoğu kurulumda büyük-küçük harfe duyarsızdır.
      3. 3 Taşınabilirlik için standart yazımı tercih et (OFFSET … FETCH, GENERATED AS IDENTITY) ve kenar durumları gerçek hedef veritabanında test et.
      Sonraki kapı Aynı sorgu dün bir saniyede, bugün bir dakikada bitti. Veritabanı neyi yanlış tahmin etti? Sorgu Planı ve Join Algoritmaları — Planner Neden Bu Yolu Seçti? · 9 dk

      5 kart sonraki derste seni bekliyor

      0/5 kart bu dersten toplandı