PostgreSQL'de Tam Metin Araması Temelleri

Tam metin araması, arama yapmak için kullanılan kelime filtrelerinin teker teker veritabanımızdaki
kelimelerle karşılaştırmanın kapsamlı yoludur ve birçok yöntemi bulunmaktadır.
PostgreSQL'de tam metin aramasında kullanılan bazı fonksiyonlar vardır:
to_tsvector(): İlgili kolondaki veriyi tsvector veri tipine dönüştürür.
to_tsquery(): Tsquery operatörleriyle ayrıştırılmış token'lardan oluşan bir tsquery değeri oluşturur.
Kelimelerin sorgulanmasını kolaylaştırmak için kullanılır.

to_tsquery ve to_tsvector fonksiyonlarının kullanım örnekleri: 
 postgres=# SELECT to_tsvector('Tam metin arama örneği') @@ to_tsquery('arama');
 ?column?
----------
 t
(1 row)

postgres=# SELECT to_tsvector('Tam metin arama örneği') @@ to_tsquery('Arama');
 ?column?
----------
 t
(1 row)

postgres=# SELECT to_tsvector('Tam metin arama örneği') @@ to_tsquery('Tammetin');
 ?column?
----------
 f
(1 row)

postgres=# SELECT to_tsvector('İşte size bir tam metin araması örneği') @@ 'işte';
 ?column?
----------
 f
(1 row)

 
işte’ kelimesinin cümle içinde bulunamamasının nedeni, to_tsquery fonksiyonu ile bazı kelimeleri 
token’a dönüştürürken kökünü veya kısasını almasıdır.

Aşağıdaki örnekte kelimenin to_tsquery ile tutulduğu halini görebilirsiniz:
postgres=# SELECT 'işte'::tsquery, to_tsquery('işte');
 tsquery | to_tsquery
---------+------------
 'işte'  | 'işt'

Bu nedenle aşağıdaki gibi arama yaparsak sonuç true dönecektir:
postgres=# SELECT to_tsvector('İşte size bir tam metin araması örneği')
           @@ to_tsquery('işte');
 ?column?
----------
 t
(1 row)


 Tam metin aramasında operatörlerin kullanımları

Öncelikle bir tablo oluşturalım ve 2 satır veri ekleyelim:
postgres=# CREATE TABLE kitaplar (isim varchar(20), ozet text, yazar varchar(20));
CREATE TABLE

postgres=#
postgres=# INSERT INTO kitaplar postgres=# VALUES ('Master Algoritma', 'Belki  farkında  değilsiniz  ama  yapay  öğrenme,  hayatın  her  alanında yayılmış  durumda.  Kitap  satın  almak  için  Amazon.com  sitesine  veya  video izlemek için Netflix’egirdiğinizde yapay öğrenme sistemi size yardımcı olmak için hoşlanabileceğiniz  bazı  tavsiyelerde  bulunur.  Facebook size  gösterilecek güncellemeleri  belirlemek  için  yapay  öğrenmeyi  kullanır.  Bilgisayarı  her kullandığınızda yapay öğrenme muhtemelen bir yerlerde devreye girer.Geleneksel  olarak  bilgisayarların  iki  sayıyı  toplamaktan  bir  uçağı uçurmaya kadar herhangi bir şeyi yapmasının tek yolu, bunun nasıl yapılacağını açıklayan bir algoritmayı bütün ayrıntılarıyla yazmaktı.Cep  telefonunuz  da  algoritmalarladoludur.  Bunlar  yazım  hatalarınızı düzeltmek,  sesli  komutlarınızı  anlamak,  iletim  hatalarını  azaltmak,  barkodları okumak ve daha birçok şeyi yapmak için sürekli görev başındadır.' , 'Pedro DOMINGOS');
INSERT 0 1

postgres=# INSERT INTO kitaplar VALUES ('Gen Bencildir', 'Şempanze ve insanın evrimsel geçmişlerinin yaklaşık yüzde 99,5’i ortaktır; yine de birçok mantıklı  insan  şempanzeye  eğribüğrü,  insanla  ilgisiz,  tuhaf  bir  yaratık  olarak  bakar  ve  kendisini Mutlak Yaradan’a erişme yolunda bir basamak taşı olarak görür. Evrimci için böyle bir şey  olamaz.  Bir  türü,  diğer  bir  türden  üstün  kılacak  hiçbir  nesnel  dayanak  yoktur.  Şempanze ve insan, kertenkele ve mantar, hepimiz, üç milyar sene kadar önce doğal seçilim olarak   tanıdığımız   bir   süreç   içerisinde   evrimleştik.   Her   tür   içerisinde,   kimi   bireyler   diğerlerinden daha çok sayıda, yaşamını sürdürebilen döl vermişlerdir. Buna bağlı olarak da, üreme bakımından başarılı olan bireyin kalıtsal özellikleri (genler), bir sonraki nesilde sayıca artmıştır. İşte  bu  doğal  seçilimdir  (Genlerin  farklı,  gelişigüzel  olmayan  üremesi).  Bizi  doğal seçilim  inşa  etmiştir  ve  eğer  kendi  kimliklerimizi  kavrayabilmek  istiyorsak  anlamamız gereken de bu doğal seçilimdir. ', 'Richard DAWKINS');
INSERT 0 1

Tam metin arama için geliştirilmiş fonksiyonları kullanmasaydık, aşağıdaki gibi
bir arama yapacaktık:

postgres=# SELECT isim FROM kitaplar WHERE ozet LIKE '%insan%' OR ozet LIKE '%bir%';
       isim      
------------------
 Master Algoritma
 Gen Bencildir
(2 rows)

Bu aramayı yapmak için PostgreSQL tüm tabloyu taradı. Büyük veri setlerinde bu 
sorgu çok uzun sürer ve çok performans harcar. Bunun yerine tam metin araması
yapmak için geliştirilen fonksiyonları kullanmak daha karlıdır. Örneğin yukarıdaki sorgunun aynısını to_tsvector() ve to_tsquery() fonksiyonları ile 
yazalım.
ozet’ kolonunda ‘insan’ veya ‘tür’ geçen kelimelerin olduğu kayıtların
‘isim’ kolonundaki bilgisini sorgulayalım:
 
postgres=# SELECT isim FROM kitaplar WHERE to_tsvector(ozet) @@ to_tsquery('insan | bir');
       isim      
------------------
 Master Algoritma
 Gen Bencildir
(2 rows)

ozet’ kolonunda ‘insan’ ve ‘tür’ geçen kelimelerin olduğu kayıtların ‘isim’ kolonundaki
bilgisini sorgulayalım:
postgres=# SELECT isim FROM kitaplar WHERE to_tsvector(ozet) @@ to_tsquery('insan & bir');
     isim     
---------------
 Gen Bencildir
(1 row)



ozet’ kolonunda ‘insan’ kelimesi geçmeyen kayıtların ‘isim’ kolonundaki bilgisini sorgulayalım:

postgres=# SELECT isim FROM kitaplar WHERE to_tsvector(ozet) @@ to_tsquery('!insan');
       isim      
------------------
 Master Algoritma
(1 row)

isim’ kolonunda ‘Ben’ ile  başlayan verilerin bilgisini sorgulayalım:
postgres=# SELECT isim FROM kitaplar WHERE to_tsvector(isim) @@ to_tsquery('Ben:*');
     isim     
---------------
 Gen Bencildir
(1 row)

bir’ ve ‘süreç’ kelimelerinin yan yana olduğu kayıtların bilgisini sorgulayalım:
postgres=# SELECT * FROM kitaplar WHERE ozet @@ to_tsquery('bir <-> süreç');

-[ RECORD 1 ]---------------------------------------------------------------------------------------------------------------------------

isim  | Gen Bencildir

ozet  | Şempanze ve insanın evrimsel geçmişlerinin yaklaşık yüzde 99,5’i ortaktır; yine de birçok mantıklı  insan  şempanzeye  eğribüğrü,  insanla  ilgisiz,  tuhaf  bir  yaratık  olarak  bakar  ve  kendisini Mutlak Yaradan’a erişme yolunda bir basamak taşı olarak görür. Evrimci için böyle bir şey  olamaz.  Bir  türü,  diğer  bir  türden  üstün  kılacak  hiçbir  nesnel  dayanak  yoktur.  Şempanze ve insan, kertenkele ve mantar, hepimiz, üç milyar sene kadar önce doğal seçilim olarak   tanıdığımız   bir   süreç   içerisinde   evrimleştik.   Her   tür   içerisinde,   kimi   bireyler   diğerlerinden daha çok sayıda, yaşamını sürdürebilen döl vermişlerdir. Buna bağlı olarak da, üreme bakımından başarılı olan bireyin kalıtsal özellikleri (genler), bir sonraki nesilde sayıca artmıştır. İşte  bu  doğal  seçilimdir  (Genlerin  farklı,  gelişigüzel  olmayan  üremesi).  Bizi  doğal seçilim  inşa  etmiştir  ve  eğer  kendi  kimliklerimizi  kavrayabilmek  istiyorsak  anlamamız gereken de bu doğal seçilimdir.

yazar | Richard DAWKINS


kitap’ kelimesinden sonraki 7. karakterin ‘video’ olduğu kayıtları sorgulayalım:
postgres=# SELECT * FROM kitaplar WHERE ozet @@ to_tsquery('Kitap <7> video');
-[ RECORD 1 ]-------------------------------------------------------------------------------------------------------------------------
isim  | Master Algoritma
ozet  | Belki  farkında  değilsiniz  ama  yapay  öğrenme,  hayatın  her  alanında yayılmış  durumda.  Kitap  satın  almak  için  Amazon.com  sitesine  veya  video izlemek için Netflix’egirdiğinizde yapay öğrenme sistemi size yardımcı olmak için hoşlanabileceğiniz  bazı  tavsiyelerde  bulunur.  Facebook size  gösterilecek güncellemeleri  belirlemek  için  yapay  öğrenmeyi  kullanır.  Bilgisayarı  her kullandığınızda yapay öğrenme muhtemelen bir yerlerde devreye girer.Geleneksel  olarak  bilgisayarların  iki  sayıyı  toplamaktan  bir  uçağı uçurmaya kadar herhangi bir şeyi yapmasının tek yolu, bunun nasıl yapılacağını açıklayan bir algoritmayı bütün ayrıntılarıyla yazmaktı.Cep  telefonunuz  da  algoritmalarladoludur.  Bunlar  yazım  hatalarınızı düzeltmek,  sesli  komutlarınızı  anlamak,  iletim  hatalarını  azaltmak,  barkodları okumak ve daha birçok şeyi yapmak için sürekli görev başındadır.
yazar | Pedro DOMINGOS


to_tsvector() fonksiyonu ile kolondaki verilerin kaçıncı sırada/sıralarda tutulduğunu görebiliriz. 
Örneğin ‘5’ kelimesi 9. sıradaki kelimeymiş ve ‘bir’ kelimesi 22. , 33. , 41. , 44. , 47. , 71. , 100.
sıradaki kelimeymiş. Bu fonksiyonda kelimeler tam haliyle tutulmaz, kısaltılır veya köklerine
ayrılarak tutulur. Örneğin 105. karakter olan ‘işte’ kelimesi, bu fonksiyon sayesinde ‘işt’ olarak
tutulmuş.
postgres=# SELECT to_tsvector(ozet)
                    FROM kitaplar
                    WHERE isim=('Gen Bencildir');                                                                                        
        
  to_tsvector                                                                                   
-----------------------------------------------------------------------------------------

 '5':9 '99':8 'anlamamız':125 'artmıştır':104 'bakar':25 'bakımından':93 'basamak':34 'bağlı':89
'başarılı':94 'bir':22,33,41,44,47,71,100 'bireyin':96 'bireyl':79 'birçok':14 'bizi':114 'bu':106,128
'buna':88 'böyle':40 'da':91 'daha':81 'dayanak':53 'de':13,127 'diğer':46 'diğerlerinden':80
'doğal':67,107,115,129 'döl':86 'erişm':31 'etmiştir':118 'evrimci':38 'evrimleştik':74 'evrimsel':4
'eğer':120 'eğribüğrü':18 'farklı':110 'gelişigüzel':111 'genler':99 'genlerin':109 'gereken':126
'geçmişlerinin':5 'görür':37 'hepimiz':61 'hiçbir':51 'ilgisiz':20 'insan':16,57 'insanla':19 'insanın':3
'inşa':117 'istiyorsak':124 'içerisind':73,77 'için':39 'işt':105 'kadar':65 'kalıtsal':97 'kavrayabilmek':123
'kendi':121 'kendisini':27 'kertenkel':58 'kimi':78 'kimliklerimizi':122 'kılacak':50 'mantar':60 'mantıklı':15
'milyar':63 'mutlak':28 'nesild':102 'nesnel':52 'olamaz':43 'olan':95 'olarak':24,36,69,90 'olmayan':112
'ortaktır':11 'sayıca':103 'sayıda':83 'sene':64 'seçilim':68,116 'seçilimdir':108,130 'sonraki':101
'sürdürebilen':85 'süreç':72 'tanıdığımız':70 'taşı':35 'tuhaf':21 'tür':76 'türden':48 'türü':45
've':2,26,56,59,119 'vermişlerdir':87 'yaklaşık':6 'yaradan':29 'yaratık':23 'yaşamını':84 'yine':12
'yoktur':54 'yolunda':32 'yüzde':7 'çok':82 'önce':66 'özellikleri':98 'üreme':92 'üremesi':113 'üstün':49
'üç':62 'şempanz':1,55 'şempanzey':17 'şey':42
(1 row)
Büyük veri setlerinde çalışırken kolondaki karakterleri sıralarına göre ayırıp liste
oluşturmak zor bir işlem olacağı için performans artışı sağlamak için sık sık arama
yapacağınız kolona index eklemeniz gerekir. Gin index tipi bunun için uygun
bir index tipidir. Kolonun veri tipini ‘tsvector’ olarak değiştirip gin index’i aşağıdaki
gibi ekleyebilirsiniz:
postgres=# ALTER TABLE kitaplar ALTER COLUMN ozet TYPE tsvector USING ozet::tsvector;
ALTER TABLE
postgres=# CREATE INDEX ON kitaplar USING GIN (ozet);
CREATE INDEX
postgres=#

Tsvector veri tipi örneği

Öncelikle bir tablo oluşturalım ve tsvector haricindeki kolonlara veri girelim:
postgres=# CREATE TABLE dersler (ders_id SERIAL, ders_adi TEXT, ders_tokenlari TSVECTOR);
CREATE TABLE
postgres=# INSERT INTO dersler (ders_adi) VALUES ('Bilgisayar Bilimlerine Giriş'), ('Bilgisayar Programlama I'), ('Bilgisayar Bilimleri İçin Ayrık Matematik'), ('Kombinatorik ve Çizge Kuramı'), ('Mantıksal Devre Tasarımı Laboratuvarı');INSERT 0 5


ders_adi kolonundaki verilerin tokenlara ayrılmış halini ders_tokenlari kolonunda 
tutmak istiyoruz, o yüzden UPDATE sorgusu ile ders_adi kolonundaki verilerin
tokenlara ayrılmış halini ders_tokenlari kolonuna aşağıdaki gibi ekleyelim:
postgres=# UPDATE dersler d1 SET ders_tokenlari = to_tsvector(d1.ders_adi);
UPDATE 5


ders_tokenlari kolonuna ders_adi kolonundaki veriler aşağıdaki gibi geldi:
postgres=# select * from dersler;
 ders_id |                 ders_adi                  |                        ders_tokenlari                        
---------+-------------------------------------------+---------------------------------------------------------------
       1 | Bilgisayar Bilimlerine Giriş              | 'bilgisayar':1 'bilimlerin':2 'giriş':3
       2 | Bilgisayar Programlama I                  | 'bilgisayar':1 'programlama':2
       3 | Bilgisayar Bilimleri İçin Ayrık Matematik | 'ayrık':4 'bilgisayar':1 'bilimleri':2 'için':3 'matematik':5
       4 | Kombinatorik ve Çizge Kuramı              | 'kombinatorik':1 'kuramı':4 've':2 'çizg':3
       5 | Mantıksal Devre Tasarımı Laboratuvarı     | 'devr':2 'laboratuvarı':4 'mantıksal':1 'tasarımı':3
(5 rows)



tsvector veri tipine sahip bir kolona veri aktarmak istersek to_tsvector fonksiyonunu
aşağıdaki gibi kullanabiliriz:
postgres=# INSERT INTO dersler (ders_adi, ders_tokenlari) VALUES ('Biçimsel Diller ve Otomata', to_tsvector('Biçimsel Diller ve Otomata'));
INSERT 0 1


NOT: Tam metin araması yaparken tsvector tipindeki verileri ayrı bir kolonda tutmadan,
sorgu sırasında to_tsvector fonksiyonu ile tam metin araması yapabiliriz veya yukarıdaki
örnekte olduğu gibi tsvector veri tipli halini ayrı bir kolonda tutabiliriz.
Kitaplar örneğinde olduğu gibi index eklersek sorgu esnasında to_tsvector fonksiyonu
ile tam metin aramasını hızlıca yapabiliriz, böylece ayrı bir kolona ihtiyacımız kalmaz.


 Sözlükler: PostgreSQL’de var olan birçok dil bulunmaktadır. \dF ile var olan dilleri görebilirsiniz:

postgres=# \dF
               List of text search configurations
   Schema   |    Name    |              Description              
------------+------------+---------------------------------------
 pg_catalog | arabic     | configuration for arabic language
 pg_catalog | danish     | configuration for danish language
 pg_catalog | dutch      | configuration for dutch language
 pg_catalog | english    | configuration for english language
 pg_catalog | finnish    | configuration for finnish language
 pg_catalog | french     | configuration for french language
 pg_catalog | german     | configuration for german language
 pg_catalog | hungarian  | configuration for hungarian language
 pg_catalog | indonesian | configuration for indonesian language
 pg_catalog | irish      | configuration for irish language
 pg_catalog | italian    | configuration for italian language
 pg_catalog | lithuanian | configuration for lithuanian language
 pg_catalog | nepali     | configuration for nepali language
 pg_catalog | norwegian  | configuration for norwegian language
 pg_catalog | portuguese | configuration for portuguese language
 pg_catalog | romanian   | configuration for romanian language
 pg_catalog | russian    | configuration for russian language
 pg_catalog | simple     | simple configuration
 pg_catalog | spanish    | configuration for spanish language
 pg_catalog | swedish    | configuration for swedish language
 pg_catalog | tamil      | configuration for tamil language
 pg_catalog | turkish    | configuration for turkish language
(22 rows)


Tam metin araması yaparken kullandığınız dili özellikle belirtmek isterseniz aşağıdaki 
gibi bir sorgu yazabilirsiniz:
postgres=# select to_tsvector('english', 'This text is very long and hard for processed');
              to_tsvector              
----------------------------------------
 'hard':7 'long':5 'process':9 'text':2
(1 row)


Aynı cümleyi turkçe sözlüğe göre tokenlara ayırmak istersek doğru şekilde ayırmayacaktır:
postgres=# select to_tsvector('turkish', 'This text is very long and hard for processed');
                                    to_tsvector                                    
------------------------------------------------------------------------------------
 'and':6 'for':8 'hard':7 'is':3 'long':5 'processedi':9 'text':2 'this':1 'very':4
(1 row)
Aşağıdaki örnekte de türkçe bir cümlenin türkçe sözlüğe göre tokenlara ayrıldığını ve 
tokenların oldukça kısaltıldığını görebilirsiniz:
postgres=# select to_tsvector('turkish', 'Bu metin oldukça uzundur ve işlenmesi zordur.');
                   to_tsvector                  
-------------------------------------------------
 'işlenmes':6 'met':2 'oldukça':3 'uz':4 'zor':7
(1 row)



Var olan sözlükleri ve dil kurallarını kullanmak istemezseniz yeni bir sözlük ve dil kuralları 
oluşturabilirsiniz, veya bunun için yazılmış eklentileri de kullanabilirsiniz.
Örneğin “dict-in” eklentisi tam sayıların indekslenmesini kontrol eden tam metin aramaları
için kullanılan bir sözlüktür. “unaccent” eklentisi ise kelimelerdeki dil aksanlarına dayalı
değişimleri filtreleyerek tam metin araması yapmak için kullanılan bir sözlüktür.
Örnek kullanımları:
postgres=# create extension dict_int ;
CREATE EXTENSION
postgres=# select ts_lexize('intdict', '252281774');
 ts_lexize
-----------
 {252281}
(1 row)


postgres=# create extension unaccent;
CREATE EXTENSION
postgres=# select ts_lexize('unaccent','Hôtel');
 ts_lexize
-----------
 {Hotel}
(1 row)



Yorumlar