Skip to main content

PostgreSQL’de Range Veri Tipleri ve Örnekleri

Merhaba,

Range veri tipleri, verilerin belirli aralıkta olmasıyla ilgilidir. Örneğin otel rezervasyon uygulamasında bir odanın hangi saatler arasında boş veya dolu olduğunu tstzrange(timestamp with time zone) veri tipini kullanarak saklayabilirsiniz veritabanınızda. Postgres’de bahsedilen bu örnekteki gibi farklı veri tiplerine de uygun range veri tipleri bulunmaktadır. Timestamp’in yanında bir de numeric, date, int veri tiplerine de uygun farklı range veri tipleri bulunmaktadır.

Postgres’de mevcut range veri tipleri aşağıdaki gibidir.

  • int4range — Range of integer
  • int8range — Range of bigint
  • numrange — Range of numeric
  • tsrange — Range of timestamp without time zone
  • tstzrange — Range of timestamp with time zone
  • daterange — Range of date

Aralıkların Belirlenmesi

İçi boş olmayan aralığın düşük ve yüksek olmak üzere iki adet sınırı bulunur. Bu iki sınır arasındaki tüm değerler bizim veri tipimize uygun bu aralık içinde yer alır. Düşük sınırın aralığa dahil olması için ‘[‘ işareti kullanılır, eğer başlangıç değerinin bu aralığa dahil olması istenmezse ‘(‘ işareti kullanılır. Yüksek sınır değerinin verilen aralık içine dahil edilmesi istenirse ‘]’ işareti kullanılır. Eğer bu değerin dahil olması istenmezse de ‘)’ yazılır.

Eğer herhangi bir bilgi verilmezse aralıkların başlangıç ve bitişine ait ön tanımlı olarak Postgres ‘[)’ şeklinde alır. Yani düşük değer dahil, yüksek değer dahil değildir bu aralığa.

İstenen aralığı belirlemek için bazı örnekler şöyle;
[gunce] # SELECT '[1,6]'::int4range;
-[ RECORD 1 ]-----
int4range | [1,7)

Aşağıdaki örneğin sorguda verilen bitiş tarihi ile sorgu sonucunda gelen değere bakın. Son değeri dahil ettiğimiz için bir sonraki günü dahil etmeden bize gösteriyor, postgres’in ön tanımlı olan yukarıda bahsettiğim ‘[)’ durumundan dolayı.

[gunce] # SELECT '[2012-03-28, 2012-04-02]'::tsrange;
-[ RECORD 1 ]------------------------------------------
tsrange | ["2012-03-28 00:00:00","2012-04-02 00:00:00"]

Yukarıdaki tsrange veri tipini aşağıdaki şekilde de kullanabiliriz.

[gunce] # SELECT tsrange('[2012-03-28, 2012-04-02]');
-[ RECORD 1 ]------------------------------------------
tsrange | ["2012-03-28 00:00:00","2012-04-02 00:00:00"]

Bu örnekte başlangıç ve bitiş değerlerinin dahil olup olmama durumunu bildirmediğimiz için Postgres’in ön tanımlı özelliği devreye giriyor ve düşük değeri dahil edip, son değeri dahil etmiyor bu aralığa
.
[gunce] # SELECT numrange(9.0, 9.5);
-[ RECORD 1 ]-------
numrange | [9.0,9.5)

Alt ve üst sınıra ait daterange örnekleri;

-- Alt ve üst sınır arasındaki tüm değerler(üst sınır değeri hariç)
[gunce] # SELECT daterange'[today, 2019-03-02)';
-[ RECORD 1 ]----------------------
daterange | [2019-02-27,2019-03-02)


-- Alt ve üst sınır dahil olmak üzere, bu sınırlar arasında kalan tüm tarihler
[gunce] # select daterange'[today, 2019-03-02]';
-[ RECORD 1 ]----------------------
daterange | [2019-02-27,2019-03-03)


-- Alt sınırdan sonraki günden başlayarak üst sınır dahil tüm tarihler
[gunce] # select daterange'(today, 2019-03-02]';
-[ RECORD 1 ]----------------------
daterange | [2019-02-28,2019-03-03)


-- Alt ve üst sınırların olmadığı ve arasındaki tüm tarihler
[gunce] # select daterange'(today, 2019-03-02)';
-[ RECORD 1 ]----------------------
daterange | [2019-02-28,2019-03-02)

Aralık için kullanılan değerin dahil olup olmadığını lower_inc() ve upper_inc() fonksiyonlarını kullanarak görebilirsiniz.
SELECT UPPER_INC(numrange'[1,2)'); -- false

SELECT LOWER_INC(numrange'[1,2)'); -- true

SELECT UPPER_INC(numrange'[1,2]'); -- true

SELECT LOWER_INC(numrange'[1,1)'); -- false

SELECT UPPER_INC(numrange'[1,1)'); -- false

Sonsuz(Sınırsız) Aralıklar

Düşük veya yüksek sınır dışındaki tüm verileri saklamak istenebilir. Örneğin düşük sınır dışındaki tüm değerleri kabul etmek demek yüksek sınırın sınırsız olması anlamına gelir. Aynı şekilde yüksek sınırın dışındaki tüm değerlerin kabul edilmesi için düşük sınırının olmaması gerekir, yani yüksek sınırınızdan düşük sınırınıza doğru sonsuz değerlerin kabul edilmesi durumu vardır.

Sonsuzluk ifadesini “infinity” ifadesi ile belirleyebiliriz. Bunu timestamp değerler için kullanabiliriz. Örneğin, tsrange veri tipi için, bugünden itibaren sonsuz değerin kabul edildiği bir aralık olması için

[gunce] # select tsrange'[today,infinity)', numrange'[0,)';
-[ RECORD 1 ]------------------------------
tsrange  | ["2019-02-27 00:00:00",infinity)
numrange | [0,)

Aralığın herhangi bir tarafına doğru sonsuzluk olması istenirse NULL eklenebilir veya herhangi bir değer eklemezseniz de aynı durum söz konusudur. Bir değer ile sınırlandırma yapmayacağınız için sonsuzluk sağlanmış olur.
 

Aralığın herhangi bir tarafı için sonsuzluk durumunun olup olmadığını lower_inf() ve upper_inf() fonksiyonlarını kullanarak bulabiliriz fakat bu iki fonksiyon, aralık içi kullanılan özel ifadeleri algılayamaz, infinity gibi. Bu fonksiyonlar sınırın boş oluş olmadığını kontrol ederek sonsuzluk olup olmadığına karar verir.
select upper_inf(tsrange'[today,infinity)'); --false

select upper_inf(numrange'[0,)'); --true

select upper_inf(numrange(1, null)); --true

select upper_inf(numrange'[1, 4)'); --false

Yukarıdaki örnekte “infinity” ifadesi sonsuzluğu verse de bize, bu iki fonksiyon bu gibi özel ifadelerin tiplerini anlayamaz. Dolayısıyla bu alan kontrol edilemeyen alanlardan olduğu için sonuç false yani sonsuz değilmiş gibi döndü bize. Numrange için aynı kontrolü yaptığımızda sonsuzluk kontrolünü yapıp bize true dönüyor. Bu kısma dikkat edilmelidir. upper_inf(tsrange'[today,infinity)') ifadesinin neden false dönmesiyle ilgili açıklamayı Tom Lane yılar önce burada yapmıştır.

Aralıkların Belirlenmesinde Kullanılan Bloklar

“()”, “(]”, “[)”, or “[]” blokları yukarı kullanıldığından daha farklı şekilde kullanılabilir.
[gunce] # SELECT int8range(1, 8, '(]'),
numrange(NULL, 5),
numrange(1,6);
-[ RECORD 1 ]-----
int8range | [2,9)
numrange  | (,5)
numrange  | [1,6)

Range Veri Tiplerinin Kullanımı

Bu aralıkların getirilmesinin yanında karşılaştırma yapmak için de kullanılabilir.
-- Bir aralık bulamayacağı için sonuç true döner.
SELECT isempty(numrange(1,1)); -- true

-- Bir aralık mevcut olduğu için sonuç false gelir.
SELECT isempty(numrange(1,5)); -- false


Range veri tipleri için aşağıdaki gibi karşılaştırma yapılabilir.
select daterange'[2019-01-01, 2019-02-01]' = daterange'[2019-01-01, 2019-02-01]'; -- true

select daterange'[2019-01-01, 2019-02-01]' = daterange'[2019-03-01, 2019-04-01]'; -- false

select daterange'[2019-01-01, 2019-02-01]' >= daterange'[2019-03-01, 2019-04-01]'; -- false

-- Alt ve üst sınıra ait verileri getirir.
[gunce] # SELECT upper(int8range(15, 25)), lower(int8range(15,25));
-[ RECORD 1 ]
upper | 25
lower | 15


-- Ortak aralığı alır
[gunce] # SELECT int4range(10, 20) * int4range(15, 25);
-[ RECORD 1 ]-----
?column? | [15,20)

-- 3'ün int4range aralığındaki elementlerden biri olup olmadığına bakar ve olmadığı için false döner.
[gunce] # SELECT int4range(10, 20) @> 3; -- false

-- 11'in 10,20 aralığında olup olmadığına bakar ve bu aralıkta yer aldığı için true döner.
[gunce] # SELECT int4range(10, 20) @>11; -- true

Otel Rezervasyon Örneği


Odaların hangi gün ve saatler arasında rezervasyonunun olduğu bilgisinin saklandığı bir tablomuz olsun. Oda numarası ve rezervasyon tarih aralığının girildiği sütunlara sahip olsun. Kalış süresini timestamp için bir range eklemeliyiz. Eğer timezone olacaksa tztsrange veri tipini, eğer timezone kullanılmayacaksa tsrange veri tipini kullanmamız yeterlidir.

CREATE TABLE ROOM_BOOKING (id serial, room_number int, stay tsrange);CREATE TABLE


INSERT INTO room_booking(room_number, stay) values(100, '[2019-02-25 14:00, 2019-02-26 11:00]');
INSERT INTO room_booking(room_number, stay) values(100, '[2019-02-26 14:00, 2019-02-27 11:00]');
INSERT INTO room_booking(room_number, stay) values(100, '[2019-02-27 14:00, 2019-02-28 11:00]');
INSERT INTO room_booking(room_number, stay) values(103, '[2019-02-27 14:00, 2019-02-28 11:00]');
INSERT INTO room_booking(room_number, stay) values(103, '[2019-02-27 14:00, 2019-02-28 11:00]');
INSERT INTO room_booking(room_number, stay) values(104, '[2019-02-28 14:00, 2019-03-05 11:00]');


[gunce] # SELECT * FROM room_booking;
id | room_number |                     stay                    
----+-------------+-----------------------------------------------
 1 |         100 | ["2019-02-25 14:00:00","2019-02-26 11:00:00"]
 2 |         100 | ["2019-02-26 14:00:00","2019-02-27 11:00:00"]
 3 |         100 | ["2019-02-27 14:00:00","2019-02-28 11:00:00"]
 4 |         103 | ["2019-02-27 14:00:00","2019-02-28 11:00:00"]
 5 |         103 | ["2019-02-27 14:00:00","2019-02-28 11:00:00"]
 6 |         104 | ["2019-02-28 14:00:00","2019-03-05 11:00:00"]
(6 rows)

Çıkış tarihlerine göre sıralamak için aşağıdaki script kullanılabilir.

[gunce] # SELECT * FROM room_booking ORDER BY upper(stay) desc;
id | room_number |                     stay                    
----+-------------+-----------------------------------------------
 6 |         104 | ["2019-02-28 14:00:00","2019-03-05 11:00:00"]
 3 |         100 | ["2019-02-27 14:00:00","2019-02-28 11:00:00"]
 4 |         103 | ["2019-02-27 14:00:00","2019-02-28 11:00:00"]
 5 |         103 | ["2019-02-27 14:00:00","2019-02-28 11:00:00"]
 2 |         100 | ["2019-02-26 14:00:00","2019-02-27 11:00:00"]
 1 |         100 | ["2019-02-25 14:00:00","2019-02-26 11:00:00"]
(6 rows)

Belirli tarih aralığını çekelim.
[gunce] # SELECT * FROM room_booking 
WHERE stay && tsrange'[2019-02-25, 2019-02-27)';
id | room_number |                     stay                    
----+-------------+-----------------------------------------------
 1 |         100 | ["2019-02-25 14:00:00","2019-02-26 11:00:00"]
 2 |         100 | ["2019-02-26 14:00:00","2019-02-27 11:00:00"]
(2 rows)

Bir tarih aralığından büyük olanları çekelim.

[gunce] # SELECT * FROM room_booking WHERE stay >> tsrange'[2019-02-25, 2019-02-27)';
id | room_number |                     stay                    
----+-------------+-----------------------------------------------
 3 |         100 | ["2019-02-27 14:00:00","2019-02-28 11:00:00"]
 4 |         103 | ["2019-02-27 14:00:00","2019-02-28 11:00:00"]
 5 |         103 | ["2019-02-27 14:00:00","2019-02-28 11:00:00"]
 6 |         104 | ["2019-02-28 14:00:00","2019-03-05 11:00:00"]
(4 rows)

Değerin belirlenen aralıkta olup olmadığını kontrol edelim.

[gunce] # SELECT stay * tsrange'[2019-02-25, 2019-02-27)',room_number FROM room_booking;
                  ?column?                    | room_number
-----------------------------------------------+-------------
["2019-02-25 14:00:00","2019-02-26 11:00:00"] |         100
["2019-02-26 14:00:00","2019-02-27 00:00:00") |         100
empty                                         |         100
empty                                         |         103
empty                                         |         103
empty                                         |         104
(6 rows)

Range veri tipleri için kullanılan operatörlerin bazıları aşağıdaki gibidir. Tamamına buradan ulaşabilirsiniz.

Zamanlama

Postgres'in harika bir özelliği bulunuyor: EXCLUDE constraint. Bu constraintin eklenmesiyle tablodaki iki satırın karşılaştırması yapılıyor belirli sütun için. EXCLUDE ifadesi index bazlı bir constraint olduğu için benzer bir değerin olup olmadığını ve daha önceden eklenmemiş verinin girilmesini kontrol eder. 

Aşağıdaki örnekte takvimde bir zaman aralığını doldurduysak o zaman aralığına başka veri girilmesini önlemek için bu constraint kullanılabilir. 

CREATE TABLE reservation_new 
(id SERIAL, 
room_number INT, 
stay DATERANGE, 
EXCLUDE USING GIST(stay WITH &&));

INSERT INTO reservation_new (room_number, stay)
VALUES(100, daterange'(2019-01-01, 2019-01-05)');


INSERT INTO reservation_new (room_number, stay)
VALUES(100, daterange('2019-01-01', '2019-01-05'));
ERROR: 23P01: conflicting key value violates exclusion 

constraint "reservation_new_stay_excl"
DETAIL: Key (stay)=([2019-01-01,2019-01-05)) conflicts with 

existing key (stay)=([2019-01-01,2019-01-05)).
LOCATION: check_exclusion_or_unique_constraint, execIndexing.c:836


 Sevgiler,

Comments

Popular posts from this blog

PGConf Europe 2018 Güncesi 1

Merhaba, Diğer tüm veritabanları arasında en popüler açık kaynak kodlu veritabanı olan PostgreSQL, tüm yıl boyunca farklı ülkelerde bir çok konferansa sahiplik yapmaktadır. En bilinenleri PGDay ve PGConf'dur. Ayrıca her yıl 2 Şubatta Brüksel’de yapılan FOSDEM etkinliğinde de PostgreSQL konuşmalarının yapıldığı bir oturum mutlaka olmaktadır. PostgreSQL etkinlik detaylarına bu linkten ulaşabilirsiniz. Bu yıl ki PGConf Europe konferansı Lizbon'da yapıldı. Konferansla ilgili izlenimlerimi, yaptığım neredeyse 6 günlük yolculuğun en başından tüm detaylarıyla birlikte paylaşmak istiyorum. Yolculuğumuz sevgili Seda ile birlikte Atatürk Havalimanı’ndan sabah 07:30’da Lizbon’a direk uçuşla başladı.  Hava şahaneydi ve uçuşumuz konforlu geçti. Koltuğumuz cam kenarı ve uçağın kanadının hemen yanındaydı (benim favori koltuklarım 24, 25, 26, 27 ve 28. sıradakiler) ve biz 27. sıradaydık. Bol bol resim çektik bulutların üstündeyken ve uçak kanatlı olanlardan.  Lizbon’a vardığımı...

PostgreSQL High Availability - Patroni 2

Patroni kurulumuyla ilgili oldukça fazla kaynak bulunmakta fakat kurulum ve yönetimini beraber barındıran kaynağa denk gelmedim. Bu yazıda hem Patroni kurulumu hem de kurulum sonrası yönetimiyle alakalı detaylara ulaşabilirsiniz. PostgreSQL cluster'larının yönetimi için kullanılan Patroni ile ilgili temel bilgilerin yer aldığı Autofailover üzerine hazırladığım yazı serisine aşağıdaki linklerden erişebilirsiniz. PostgreSQL ve Autofailover PostgreSQL'de Autofailover ve Patroni 1 (Giriş) PostgreSQL'de Autofailover ve Patroni 2 (Kurulum, Konfigürasyon ve Yönetim) PostgreSQL'de Autofailover ve Patroni 3 (Mevcut PostgreSQL Cluster'inin Patroni'ye Geçirilmesi) Patroni, PostgreSQL veritabanlarının kurulumundan ve konfigürasyonundan sorumludur. Yani Patroni'yi kurduğumuz sunucular aynı zamanda Patroni ile kurulmuş PostgreSQL'leri barındıracak. Üç node'lu PostgreSQL ve üç node'lu ETCD cluster'larını oluşturacağım. Kuruluma önce üç no...

PostgreSQL High Availability - Patroni 1

Patroni, PostgreSQL HA (High Availability) Cluster yapısının kurulması ve yönetimi için kullanılan open source bir araçtır. Patroni, PostgreSQL Cluster’inin kurulumu (bootstrap), replikasyonunun kurulumu, PostgreSQL otomatik failover yapılması amacıyla kullanılır. Python ile yazılmıştır. Bir önceki yazıda PostgreSQL'de Autofailover araçlarının ne olduğu ve neden Autofailover'a gereksinim duyulduğu konusunda yazı paylaşmıştım. Yazıya buradan ulaşabilirsiniz. Patroni Kurulumu İçin Gereksinimler nedir? Patroni’nin tek başına kurulumu autofailover yapısını yapılandırmak için yeterli değildir. Patroni ile Autofailover yapısının kurulması için DCS (Distributed Configuration Store) araçlarından birine ihtiyaç vardır. Bunlardan bazıları; ETCD, Consul, ZooKeeper, Kubernetes. PostgreSQL üzerinde Autofailover araçlarından Patroni entegre edecekseniz DCS araçlarından birini kullanmanız gerekecek. DCS dediğimiz şey aslında Service Discovery araçlarından biri olduğundan bu iki tan...