T-SQL'de Dönüşüm, Biçimlendirme ve NULL Function'ları: CAST/CONVERT Style'ları, TRY_CONVERT, FORMAT, ISNULL, COALESCE, NULLIF, IIF ve CHOOSE
ISNULL sonucu ilk argümanın tipine çevirir, COALESCE tip önceliğine göre seçer: ISNULL(NULL int, 1.5) 1 döndürür. CONVERT style kodları, boş string'in 0 ve 1900-01-01 olması, TRY_PARSE'ın tr-TR'de 1450.00'ı 145.000 yapması, FORMAT'ın CPU maliyeti, COALESCE'in subquery'yi iki kez çalıştırması ve GREATEST'in NULL davranışı gerçek çıktılarla, PostgreSQL karşılıklarıyla.
Sürüm notu: Bu yazıdaki SQL sorguları SQL Server 2025 (17.0, Cumulative Update 10) üzerinde serinin Bookstore veritabanıyla, compatibility level 170, 160 ve 150 ile; PostgreSQL karşılaştırmaları PostgreSQL 18.7 üzerinde çalıştırılmıştır.
Önce Tahmin Et: ISNULL ile COALESCE Aynı Sonucu Verir mi?
NULL bir INT değişkenin yerine 1,5 koymak istiyoruz. İki function da "ilk NULL olmayan değeri döndür" diye anlatılır:
DECLARE @i INT = NULL;
SELECT ISNULL(@i, 1.5) AS isnull_result, COALESCE(@i, 1.5) AS coalesce_result;
isnull_result coalesce_result
------------- ---------------
1 1.5
(1 rows affected)
ISNULL 1 döndürdü. Sonucun tipi ilk argümanın tipi, yani INT; 1,5 bu tipe çevrilirken ondalık kısım kesildi. Hata ya da uyarı yok. COALESCE ise argümanlar arasında tip önceliği (data type precedence) en yüksek olanı seçti, DECIMAL'i, ve 1,5 döndürdü. 06. yazıda aynı farkın string tarafını, ISNULL(@code, 'unknown')'un "unk"e kesilmesini görmüştük. Bu yazıda dönüşüm ve NULL function'larını ayrıntılı ele alıyoruz: tip önceliği ve implicit conversion, CONVERT'in style kodları, TRY_ ailesi, FORMAT'ın maliyeti, ISNULL ile COALESCE'in üç farkı, NULLIF, IIF, CHOOSE, GREATEST ve LEAST. Örnekler yine sitedeki serilerin ortak örnek uygulaması Bookstore'un (bir online kitapçı) veritabanında.
Tip Önceliği ve Implicit Conversion
İki farklı tipteki değer bir ifadede buluştuğunda SQL Server birini diğerinin tipine çevirir. Hangisinin çevrileceğine data type precedence listesi karar verir: düşük öncelikli olan yüksek öncelikliye çevrilir. Sayısal tipler string tiplerden önce gelir:
SELECT 1 + '1' AS int_plus_str, '1' + '1' AS str_plus_str, 1.5 + '1' AS dec_plus_str;
int_plus_str str_plus_str dec_plus_str
------------ ------------ ------------
2 11 2.5
1 + '1' toplama oldu, çünkü string INT'e çevrildi; '1' + '1' ise iki string olduğu için birleştirme. Çevrilemeyen bir string'de hata alırsın: SELECT 1 + 'a' "Conversion failed when converting the varchar value 'a' to data type int." (Msg 245) ile duruyor. Bu kural WHERE'de daha sinsi çalışır. customers.phone bir VARCHAR column'u; onu bir sayıyla karşılaştıralım:
SELECT customer_id FROM dbo.customers WHERE phone = 905550179553;
Msg 8114, Level 16, State 5, Line 1
Error converting data type varchar to numeric.
Sayı literal'ı NUMERIC(12,0) tipinde ve önceliği yüksek, bu yüzden SQL Server column'daki her değeri sayıya çevirmeye çalıştı ve "+90 555 ..." biçimindeki ilk telefonda durdu. Column'u çevirmek ayrıca index'i de kullanılamaz yapar; 07. yazıda CONVERT_IMPLICIT ile gördüğümüz durum. Kural: literal'ı ya da parametreyi column'un tipinde yaz.
Implicit conversion'ın bir de sessiz tarafı var. Boş string sayıya ve tarihe hatasız çevrilir:
SELECT CAST('' AS INT) AS empty_to_int, CAST('' AS DATE) AS empty_to_date, TRY_CAST('' AS INT) AS try_empty_int;
empty_to_int empty_to_date try_empty_int
------------ ------------- -------------
0 1900-01-01 0
Bir CSV'den (virgülle ayrılmış değerler dosyası) gelen boş bir miktar 0, boş bir tarih 1 Ocak 1900 olur ve TRY_CAST bile bunu "başarılı" sayar. DECIMAL'de ise CAST('' AS DECIMAL(10,2)) hata veriyor (Msg 8114); aynı boş değer tipe göre üç farklı sonuç üretiyor. PostgreSQL bu konuda daha katı: ''::int "invalid input syntax for type integer" hatası veriyor. Dışarıdan gelen veride boş string'i önce NULLIF(x, '') ile NULL'a çevir, sonra dönüştür.
CONVERT ve Style Kodları
CAST standart SQL, CONVERT T-SQL'e özgü ve üçüncü argümanı style kodu. Tarihten metne en sık kullanılan style'lar:
DECLARE @d DATETIME2(0) = '2026-11-03 14:05:09';
SELECT CONVERT(VARCHAR(30), @d, 112) AS s112, CONVERT(VARCHAR(30), @d, 23) AS s23, CONVERT(VARCHAR(30), @d, 120) AS s120,
CONVERT(VARCHAR(30), @d, 126) AS s126, CONVERT(VARCHAR(30), @d, 104) AS s104, CONVERT(VARCHAR(30), @d, 103) AS s103,
CONVERT(VARCHAR(30), @d, 101) AS s101, CONVERT(VARCHAR(30), @d, 108) AS s108;
s112 s23 s120 s126 s104 s103 s101 s108
-------- ---------- ------------------- ------------------- ---------- ---------- ---------- --------
20261103 2026-11-03 2026-11-03 14:05:09 2026-11-03T14:05:09 03.11.2026 03/11/2026 11/03/2026 14:05:09
112 dosya adı ve sıralanabilir anahtar için, 23 ve 120 log ve rapor için, 126 ISO (International Organization for Standardization; uluslararası standartları yayımlayan kuruluş) 8601 biçimi gereken JSON ve API çıktısı için, 104 Türkiye ve Almanya'daki gün.ay.yıl biçimi için. 103 ile 101 aynı ayraçları kullanıyor ama gün ve ayın yeri ters: 3 Kasım İngiliz biçiminde 03/11, Amerikan biçiminde 11/03. Ters yönde, metinden tarihe, style metnin biçimini tarif eder:
SELECT CONVERT(DATE, '03.11.2026', 104) AS p104, CONVERT(DATE, '03/11/2026', 103) AS p103, CONVERT(DATE, '03/11/2026', 101) AS p101,
CONVERT(DATE, '20261103', 112) AS p112;
p104 p103 p101 p112
---------- ---------- ---------- ----------
2026-11-03 2026-11-03 2026-03-11 2026-11-03
Aynı '03/11/2026' style'a göre 3 Kasım ya da 11 Mart oldu. Style vermezsen oturumun dil ayarı karar verir: us_english oturumunda CONVERT(DATE, '03.11.2026') 2026-03-11 döndürdü. Style ile metin uyuşmazsa da hata alırsın: CONVERT(DATE, '2026-11-03', 104) "Conversion failed when converting date and/or time from character string." (Msg 241) ile duruyor. Dışarıdan tarih metni alıyorsan biçimi bil ve style'ı her zaman açıkça yaz.
TRY_CAST, TRY_CONVERT, TRY_PARSE ve PARSE
Bir tedarikçi fiyat listesini Excel'den aktarıyor; fiyat ve geçerlilik tarihi metin olarak bir staging table'ına geliyor:
DROP TABLE IF EXISTS dbo.supplier_price_staging;
CREATE TABLE dbo.supplier_price_staging (
line_no INT NOT NULL PRIMARY KEY,
isbn VARCHAR(20) NOT NULL,
price_text VARCHAR(30) NULL,
valid_from VARCHAR(30) NULL
);
INSERT INTO dbo.supplier_price_staging (line_no, isbn, price_text, valid_from) VALUES
(1, '9798900000268', '420,00', '03.11.2026'),
(2, '9798900000398', '1.234,50', '03.11.2026'),
(3, '9798900000602', '1450.00', '2026-11-03'),
(4, '9798900000589', '890,00 TL', '31.11.2026'),
(5, '9798900000015', '', NULL),
(6, '9798900000022', 'N/A', '04.11.2026');
TRY_ ailesi dönüşemeyen değerde hata yerine NULL döndürür. TRY_CAST ve TRY_CONVERT SQL Server'ın kendi kurallarıyla çevirir; TRY_PARSE ise bir kültür (culture) adı alır ve .NET'in ayrıştırma kurallarını kullanır:
SELECT line_no, price_text,
TRY_CAST(price_text AS DECIMAL(10,2)) AS try_cast_price,
TRY_PARSE(price_text AS DECIMAL(10,2) USING 'tr-TR') AS try_parse_tr,
valid_from,
TRY_CONVERT(DATE, valid_from, 104) AS try_convert_104
FROM dbo.supplier_price_staging
ORDER BY line_no;
line_no price_text try_cast_price try_parse_tr valid_from try_convert_104
------- ---------- -------------- ------------ ---------- ---------------
1 420,00 NULL 420.00 03.11.2026 2026-11-03
2 1.234,50 NULL 1234.50 03.11.2026 2026-11-03
3 1450.00 1450.00 145000.00 2026-11-03 NULL
4 890,00 TL NULL NULL 31.11.2026 NULL
5 NULL NULL NULL NULL
6 N/A NULL NULL 04.11.2026 2026-11-04
Tabloyu dikkatli oku. TRY_CAST virgüllü Türkçe biçimi tanımadı. TRY_PARSE Türkçe biçimi doğru okudu, ama 3. satırdaki "1450.00"ı 145.000 yaptı: Türkçede nokta binlik ayracı. Bu row NULL olsaydı hemen fark ederdin; yüz katına çıkmış bir fiyat ise kontrolden geçebilir. "890,00 TL" ve "N/A" doğru şekilde NULL, boş string de NULL oldu. Tarih tarafında 31 Kasım diye bir gün olmadığı için 4. satır NULL, ISO biçimindeki 3. satır ise style 104'e uymadığı için NULL.
Ders şu: TRY_ function'ları "dönüşmeyen" row'ları bulur, "yanlış dönüşen" row'ları bulmaz. Bir kaynakta tek bir biçim olduğundan eminsen ona uygun tek bir yöntem seç; karışık biçimler varsa her row'un hangi biçime uyduğunu önce bir kuralla (ör. virgül var mı, nokta var mı) belirle, dönüşmeyen ve şüpheli row'ları ayrı bir hata table'ına yaz ve içeri almadan önce bir insanın onayına sun.
PARSE, TRY_PARSE'ın hata veren hâli: PARSE('N/A' AS DECIMAL(10,2) USING 'tr-TR') "Error converting string value 'N/A' into data type numeric using culture 'tr-TR'." (Msg 9819) ile duruyor. Kültüre göre ay adlarını da okur: PARSE(N'3 Kasım 2026' AS DATE USING 'tr-TR') 2026-11-03 döndürdü. Aynı ifadeyi N öneki olmadan yazdığımda hata mesajında metin "3 Kasim 2026" olarak göründü; 19. yazıdaki N öneki tuzağının bir örneği daha. PostgreSQL'de TRY_ karşılığı olarak 16. sürümden beri pg_input_is_valid('N/A', 'numeric(10,2)') var ve false döndürüyor.
FORMAT: Kültüre Göre Biçimlendirme ve Maliyeti
FORMAT değeri .NET biçim string'i ve kültürle metne çevirir:
DECLARE @p DECIMAL(10,2) = 1234.5;
SELECT CONVERT(VARCHAR(20), CAST(@p AS MONEY), 1) AS money_style1, CONVERT(VARCHAR(20), @p) AS plain,
FORMAT(@p, 'N2', 'tr-TR') AS fmt_tr, FORMAT(@p, 'C', 'tr-TR') AS fmt_currency, FORMAT(@p, '#,0.00', 'de-DE') AS fmt_de,
FORMAT(CAST('2026-11-03' AS DATE), 'dd MMMM yyyy dddd', 'tr-TR') AS fmt_date_tr;
money_style1 plain fmt_tr fmt_currency fmt_de fmt_date_tr
------------ ------- -------- ------------ -------- ------------------
1,234.50 1234.50 1.234,50 ₺1.234,50 1.234,50 03 Kasım 2026 Salı
Raporda "1.234,50" görünümü için en kısa yol bu. Bedeli CPU: Microsoft Learn'e göre FORMAT .NET CLR'ın (SQL Server içinde .NET kodunu çalıştıran runtime) varlığına dayanıyor ve biçimlendirme her row'un değeri için ayrı ayrı yapılıyor. 200.000 row'luk bir fiyat geçmişinde iki yazımı karşılaştırdım:
DROP TABLE IF EXISTS dbo.price_history;
CREATE TABLE dbo.price_history (
history_id INT NOT NULL PRIMARY KEY,
price DECIMAL(10,2) NOT NULL
);
INSERT INTO dbo.price_history (history_id, price)
SELECT value, 200 + (value % 1250) + 0.5
FROM GENERATE_SERIES(1, 200000);
SET STATISTICS TIME ON;
SELECT MAX(FORMAT(price, 'N2', 'tr-TR')) AS with_format FROM dbo.price_history;
SELECT MAX(CONVERT(VARCHAR(20), price)) AS with_convert FROM dbo.price_history;
SET STATISTICS TIME OFF;
SQL Server parse and compile time:
CPU time = 116 ms, elapsed time = 116 ms.
with_format
-----------
999,50
SQL Server Execution Times:
CPU time = 203 ms, elapsed time = 242 ms.
SQL Server parse and compile time:
CPU time = 2 ms, elapsed time = 2 ms.
with_convert
------------
999.50
SQL Server Execution Times:
CPU time = 24 ms, elapsed time = 24 ms.
Süreler makineden makineye ve çalıştırmadan çalıştırmaya değiştiği için mutlak değerler anlamlı değil; üç çalıştırmada FORMAT'lı sorgu CONVERT'li sorgunun 8 ile 15 katı arasında CPU harcadı. Burada sayılara değil orana bak. Birkaç yüz row'luk bir ekranda bu fark önemsiz, milyonlarca row'luk bir export'ta önemli. FORMAT gerekmiyorsa CONVERT'in money style'ı ve 19. yazıdaki TRANSLATE ile aynı görünümü elde edebilirsin:
SELECT TOP (3) title, list_price,
FORMAT(list_price, 'N2', 'tr-TR') AS with_format,
TRANSLATE(CONVERT(VARCHAR(20), CAST(list_price AS MONEY), 1), ',.', '.,') AS with_translate
FROM dbo.books
WHERE list_price >= 1000
ORDER BY book_id;
title list_price with_format with_translate
------------------------ ---------- ----------- --------------
The Pragmatic Programmer 1150.00 1.150,00 1.150,00
Refactoring 1150.00 1.150,00 1.150,00
Design Patterns 1450.00 1.450,00 1.450,00
Style 1 binlik ayracı virgül, ondalık ayracı nokta olan biçimi üretiyor; TRANSLATE ikisinin yerini değiştiriyor. Yine de en doğru yer çoğu zaman uygulama: sayıyı API'den sayı olarak döndür, Angular'daki DecimalPipe ya da .NET'teki ToString("N2", culture) kullanıcının kültürüne göre biçimlendirsin.
ISNULL ve COALESCE: Tip, Nullability ve Değerlendirme Sayısı
İlk farkı yazının başında gördük: ISNULL'un sonucu ilk argümanın tipini alır, COALESCE'inki tip önceliğine göre seçilir. İkinci fark, sonucun NULL olabilir mi (nullability) sayılması. SELECT INTO ile yeni bir table oluşturup column tanımlarına bakalım:
DROP TABLE IF EXISTS dbo.null_type_test;
SELECT customer_id,
ISNULL(city, N'-') AS city_isnull,
COALESCE(city, N'-') AS city_coalesce
INTO dbo.null_type_test
FROM dbo.customers;
SELECT c.name, TYPE_NAME(c.user_type_id) AS type_name, c.max_length, c.is_nullable
FROM sys.columns AS c
WHERE c.object_id = OBJECT_ID('dbo.null_type_test')
ORDER BY c.column_id;
DROP TABLE dbo.null_type_test;
name type_name max_length is_nullable
------------- --------- ---------- -----------
customer_id int 4 0
city_isnull nvarchar 200 0
city_coalesce nvarchar 200 1
İkisi de aynı değeri üretiyor, ama SQL Server ISNULL'lu column'u NOT NULL, COALESCE'li column'u NULL olabilir kabul etti. Computed column'u primary key'e katmak ya da SELECT INTO ile oluşturduğun table'da column'un NOT NULL olmasını istemek gibi durumlarda bu fark önemli.
Üçüncü fark değerlendirme sayısı. COALESCE(a, b) standartta CASE WHEN a IS NOT NULL THEN a ELSE b END'in kısaltması olarak tanımlı ve SQL Server da onu böyle açıyor; a bir subquery ise iki kez çalışabilir. Her kitabın satış adedini, hiç satmayanlar için 0 olacak şekilde iki yazımla toplayalım:
SET STATISTICS IO ON;
SELECT SUM(x) FROM (SELECT COALESCE((SELECT SUM(oi.quantity) FROM dbo.order_items AS oi WHERE oi.book_id = b.book_id), 0) AS x FROM dbo.books AS b) t;
SELECT SUM(x) FROM (SELECT ISNULL((SELECT SUM(oi.quantity) FROM dbo.order_items AS oi WHERE oi.book_id = b.book_id), 0) AS x FROM dbo.books AS b) t;
SET STATISTICS IO OFF;
Table 'Worktable'. Scan count 73, logical reads 1518, ...
Table 'order_items'. Scan count 2, logical reads 10, ...
Table 'books'. Scan count 1, logical reads 3, ...
Table 'books'. Scan count 1, logical reads 3, ...
Table 'Worktable'. Scan count 0, logical reads 0, ...
Table 'order_items'. Scan count 1, logical reads 5, ...
Sonuç ikisinde de 822. Ama COALESCE'li sorgu order_items'ı iki kez taradı ve geçici bir worktable'da 1.518 logical read yaptı; ISNULL'lu sorgu tek tarama ve 5 logical read ile bitti. Planda COALESCE'in CASE WHEN [Expr1002] IS NOT NULL THEN [Expr1005] ELSE (0) END'e açıldığı ve subquery'nin iki ayrı kolda hesaplandığı görülüyor; ISNULL ise tek bir isnull([Expr1002],(0)). Subquery'li ya da pahalı bir ifadede ISNULL'u tercih et, ya da subquery'yi bir join'e veya CROSS APPLY'a taşı. Taşınabilir kod yazıyorsan COALESCE standart olandır; PostgreSQL'de ISNULL yok.
NULLIF, IIF, CHOOSE, GREATEST ve LEAST
NULLIF(a, b) iki değer eşitse NULL, değilse a döndürür. En sık kullanımı sıfıra bölmeyi engellemek (06. yazı). Müşteri başına iptal oranı, hiç siparişi olmayan müşteride hata yerine NULL:
SELECT c.customer_id, c.first_name,
COUNT(o.order_id) AS orders,
SUM(IIF(o.status = 'Cancelled', 1, 0)) AS cancelled,
CAST(100.0 * SUM(IIF(o.status = 'Cancelled', 1, 0)) / NULLIF(COUNT(o.order_id), 0) AS DECIMAL(5,1)) AS cancel_pct
FROM dbo.customers AS c
LEFT JOIN dbo.orders AS o ON o.customer_id = c.customer_id
WHERE c.customer_id IN (1, 3, 38)
GROUP BY c.customer_id, c.first_name
ORDER BY c.customer_id;
customer_id first_name orders cancelled cancel_pct
----------- ---------- ------ --------- ----------
1 Ada 39 2 5.1
3 Grace 33 0 .0
38 Zeynep 0 0 NULL
IIF ve CHOOSE T-SQL'e 2012'de eklenen kısaltmalar ve optimizer onları CASE'e açar. Plan bunu açıkça gösteriyor:
SET SHOWPLAN_TEXT ON;
GO
SELECT IIF(list_price >= 1000, 'premium', 'standard') AS band,
CHOOSE(DATEPART(QUARTER, listed_on), 'Q1', 'Q2', 'Q3', 'Q4') AS listed_quarter
FROM dbo.books;
GO
SET SHOWPLAN_TEXT OFF;
|--Compute Scalar(DEFINE:([Expr1002]=CASE WHEN [sqlseries_ext_21].[dbo].[books].[list_price]>=(1000.00) THEN 'premium' ELSE 'standard' END, [Expr1003]=CASE WHEN datepart(quarter,[sqlseries_ext_21].[dbo].[books].[listed_on])=(1) THEN 'Q1' ELSE CASE WHEN datepart(quarter,[sqlseries_ext_21].[dbo].[books].[listed_on])=(2) THEN 'Q2' ELSE CASE WHEN datepart(quarter,[sqlseries_ext_21].[dbo].[books].[listed_on])=(3) THEN 'Q3' ELSE CASE WHEN datepart(quarter,[sqlseries_ext_21].[dbo].[books].[listed_on])=(4) THEN 'Q4' ELSE NULL END END END END))
CHOOSE iç içe dört CASE'e dönüştü ve datepart her dalda yeniden yazıldı; ilk argüman pahalı ya da nondeterministic bir ifadeyse (ör. NEWID()'den türetilmiş bir sayı) her dalda yeniden hesaplanabilir. Sınır değerlerinde de CASE gibi davranır:
SELECT CHOOSE(0, 'a', 'b') AS idx_0, CHOOSE(3, 'a', 'b') AS idx_3, CHOOSE(NULL, 'a', 'b') AS idx_null, CHOOSE(2.9, 'a', 'b', 'c') AS idx_2_9,
IIF(NULL = NULL, 'equal', 'not equal') AS iif_null;
idx_0 idx_3 idx_null idx_2_9 iif_null
----- ----- -------- ------- ---------
NULL NULL NULL b not equal
Aralık dışındaki ya da NULL index NULL döndürüyor; 2,9 ise INT'e çevrilirken 2'ye kesildi ve "b" geldi. IIF(NULL = NULL, ...) "not equal" döndürdü, çünkü koşul UNKNOWN ve IIF onu FALSE gibi ele alıyor (06. yazıdaki üç değerli mantık).
GREATEST ve LEAST 17. yazıda gördüğümüz gibi SQL Server 2022 ile geldi. NULL davranışlarını bu yazı için üç compatibility level'da yeniden denedim:
SELECT compatibility_level FROM sys.databases WHERE name = DB_NAME();
SELECT GREATEST(1, NULL, 3) AS g_mixed, GREATEST(NULL, NULL) AS g_all_null, LEAST(CAST(NULL AS INT), 2) AS l_mixed,
GREATEST('9', 10) AS g_types, GREATEST(N'istanbul', N'İzmir') AS g_collation;
ALTER DATABASE CURRENT SET COMPATIBILITY_LEVEL = 160;
SELECT compatibility_level FROM sys.databases WHERE name = DB_NAME();
SELECT GREATEST(1, NULL, 3) AS g_mixed, GREATEST(NULL, NULL) AS g_all_null, LEAST(CAST(NULL AS INT), 2) AS l_mixed;
ALTER DATABASE CURRENT SET COMPATIBILITY_LEVEL = 150;
SELECT GREATEST(1, NULL, 3) AS g_mixed_150;
ALTER DATABASE CURRENT SET COMPATIBILITY_LEVEL = 170;
compatibility_level
-------------------
170
g_mixed g_all_null l_mixed g_types g_collation
------- ---------- ------- ------- -----------
3 NULL 2 10 İzmir
compatibility_level
-------------------
160
g_mixed g_all_null l_mixed
------- ---------- -------
3 NULL 2
g_mixed_150
-----------
3
Üç seviyede de aynı: NULL argümanlar yok sayılıyor, yalnız hepsi NULL ise sonuç NULL. Microsoft Learn'deki GREATEST sayfası da SQL Server 2022 ve sonrası için aynı kuralı yazıyor. Benim denemem SQL Server 2025 motoru üzerinde 160 seviyesinde yapıldı; gerçek bir SQL Server 2022 kurulumunda çalıştırmadım. GREATEST('9', 10) 10 döndürdü, çünkü '9' tip önceliğine göre INT'e çevrildi; karşılaştırma metin değil sayı olarak yapıldı. String karşılaştırması ise collation'a uyuyor. MySQL'in bir NULL argümanda NULL döndürdüğünü 17. yazıda görmüştük.
Function Özeti ve PostgreSQL Karşılıkları
PostgreSQL sütunundaki ifadeleri kendi PostgreSQL 18 container'ımda çalıştırdım:
| T-SQL | NULL ve sınır davranışı | PostgreSQL |
|---|---|---|
CONVERT(type, x, style) | Style metinle uyuşmazsa hata | to_date(x, 'DD.MM.YYYY'), to_char |
TRY_CAST, TRY_CONVERT | Dönüşmezse NULL; boş string → 0 / 1900-01-01 | Yok; pg_input_is_valid(x, 'type') (16+) |
TRY_PARSE(x AS t USING culture) | Kültüre göre okur; yanlış biçimi yanlış okuyabilir | to_number(x, '9G999D99'), lc_numeric'e bağlı |
FORMAT(x, fmt, culture) | CLR üzerinden, her row için | to_char(x, 'FM9G999D00') |
ISNULL(a, b) | Tip a'nın; sonuç NOT NULL | Yok; coalesce |
COALESCE(a, b, ...) | Tip önceliği; CASE'e açılır | coalesce |
NULLIF(a, b) | Eşitse NULL | nullif |
IIF, CHOOSE | CASE'e açılır; aralık dışı → NULL | Yok; CASE |
GREATEST, LEAST | NULL'ı yok sayar; hepsi NULL → NULL | greatest, least, aynı davranış |
PostgreSQL'de bir ayrıntıya dikkat: to_number('1.234,50', '9G999D99') varsayılan C locale'inde 1.23 döndürdü, çünkü G ve D ayraçları lc_numeric ayarından okunuyor. TRY_PARSE'ın "1450.00"ı 145.000 yapmasının PostgreSQL'deki karşılığı bu.
Üretimde Dikkat
Dışarıdan gelen veriyi önce metin olarak bir staging table'ına al, dönüşümü orada set-based yap ve dönüşmeyen row'ları bir hata table'ına ayır. "Dönüşmedi" (NULL) ile "boştu" ('' ya da NULL) farklı durumlar; birincisi hata, ikincisi eksik veri. Bir de "yanlış dönüştü" var: kültür ya da style yanlış seçildiğinde 145.000 TL'lik bir kitap ya da 11 Mart'ta başlayan bir kampanya oluşur. Bunu yakalamak için dönüştürülmüş değerleri makul aralıklarla (fiyat 1–10.000 TL, tarih bugün ± 1 yıl) karşılaştıran bir kontrol sorgusu ekle.
FORMAT'ı rapor ekranındaki birkaç yüz row için kullanmak sorun değil; export ve toplu işte CONVERT kullan ya da biçimlendirmeyi uygulamaya bırak. ISNULL ile COALESCE arasında seçim yaparken tipi açıkça düşün: ISNULL(column, default)'ta default'un tipi column'unkine sığmalı. Subquery'li bir COALESCE varsa planda aynı table'ın iki kez okunup okunmadığına bak.
Sık Düşülen Tuzaklar
ISNULL(int_column, 1.5)'in 1 döndürmesi veISNULL(varchar(5), 'uzun metin')'in kesilmesi; sonuç tipi ilk argümanınki.VARCHARcolumn'u sayıyla karşılaştırıp her row'u sayıya çevirtmek (Msg 8114, Msg 245) ve index'i kaybetmek.- Boş string'in
INT'te 0,DATE'te 1900-01-01 olması; önceNULLIF(x, ''). TRY_PARSE(... USING 'tr-TR')'nin noktalı "1450.00"ı 145.000 yapması;TRY_"yanlış dönüşen" değeri yakalamaz.- Tarih metnini style vermeden çevirmek;
'03/11/2026'style ve dil ayarına göre 3 Kasım ya da 11 Mart olur. - Milyonlarca row'da
FORMATkullanmak; CPU maliyetiCONVERT'in katları. - Subquery'yi
COALESCEiçine koyup iki kez çalıştırmak.
Küçük Bir Deneme
★☆☆ supplier_price_staging'deki fiyatları, virgül içerenleri tr-TR, içermeyenleri en-US kültürüyle TRY_PARSE ederek dönüştür. 3. satır artık 1450,00 mi? Dönüşmeyen row'ları ayrı bir sorguyla listele.
★★☆ orders table'ından günlük sipariş özetini CONVERT style 104 ile "03.11.2026" biçiminde tarih, TRANSLATE hilesiyle "1.234,50" biçiminde ciro olarak üret. Aynı sorguyu FORMAT ile yazıp SET STATISTICS TIME ON ile CPU oranını karşılaştır.
★★★ Her kitabın son yorum tarihini, yoksa son sipariş tarihini, o da yoksa listed_on'u döndüren bir "son hareket" column'u yaz: önce iki subquery'li COALESCE ile, sonra OUTER APPLY ve GREATEST/COALESCE ile. SET STATISTICS IO ON ile iki yazımın logical read'lerini karşılaştır.
Bir sonraki yazıda matematik, sistem ve metadata function'larına geçiyoruz: ROUND ve tamsayı bölmesi, NEWID ile rastgele seçim, SCOPE_IDENTITY ile @@IDENTITY farkı, @@ROWCOUNT ve şemayı catalog view'larla sorgulamak.