800100715151 Astronomide Veritabanları

Ders - 05 Yapılandırılmış Sorgu Dili: SQL

Doç. Dr. Özgür Baştürk
Ankara Üniversitesi, Astronomi ve Uzay Bilimleri Bölümü
obasturk at ankara.edu.tr
http://ozgur.astrotux.org

Giriş ve Gerekli Paketlerin Kurulumu

Yapılandırılmış Sorgu Dili - SQL ilişkisel (ing. relational) bir veritabanı yönetim sisteminde (RDBMS tutulan verileri yönetmek için tasarlanmış, veritabanı yönetimi ve sorgulanmasına özgü olarak geliştirilmiş bir dildir. Öncüllerinden farklı olarak tek bir komutla birçok kayda erişme imkanı sağlaması ile öne çıkmış ve o zamandan beri de veritabanı alanında bir standart olarak kullanılagelmiştir. Kendisi bir programlama dili olmayıp, veritabanlarını sınırlı bir programlama kapasitesi sağlayan komutlar üzerinden yönetme ve sorgulama olanağı sağladığı için genellikle ve yanlışlıkla bir programlama dili sanılır.

Bu derste temel SQL komutlarını python dilinin olanaklarıyla birleştirmek üzere yapılandırılmış sorgu diline dayalı, açık kaynak kodlu bir veritabanı yönetim sistemi olan PostgreSQL kullanılacaktır. Bu nedenle ilgili kodların çalışması için makinenizde bir PostgreSQL sunucusunun kurulu olması ya da bağlanabileceğiniz bir PostgreSQL sunucusunun varlığına ihtiyaç duyar. Kurulum için bkz.. İstendiği takdirde MySQL gibi yine SQL'e dayalı bir başka veritabanı yönetim sistemi de tercih edilebilir.

Ayrıca PostgreSQL-Python etkileşimi için pyscopg2 modülü kullanılacaktır. Bu amaçla yazılmış başka modüller de bulunmaktadır. Ayrıca sqlalchemy araçlarından da faydalanmanız gerekebilecektir. Python-SQL etkileşimi için sqlalchemy modülünün kurulu olmasında da fayda bulunmaktadır. Her iki modül de pip ya da başka bir "installer" ile kolaylıkla kurulabilir. Öneri psycopg2-binary kurulması yönündedir; zira 2.8 sürümü sonrası psycopg modülünün isminin değişmesi planlanmıştır.

Bu derste yazılan kodların çalışması için

  • Python > 3
  • PostgreSQL > 9
  • psycopg2-binary
  • sqlalchemy

sürümlerine ihtiyaç duyulur. Ayrıca bir şifresini bildiğiniz bir de PostgreSQL kullanıcısı gerekir. Bu derste bu kullanıcının adının postgres olduğu varsayılmıştır. Bu kullanıcıyla bağlanıp mydatabases isimli bir kullanıcı aşağıdaki şekilde terminalde gerçekleştirilebilir.

pysql -U postgres -W
...
# \c mydatabases;
\d;

Modüllerin Yüklenmesi ve Veritabanı Sunucusuna Bağlanma

Öncelikle ders boyunca kullanılacak modüllerin yüklenmesi gerekmektedir.

In [1]:
import os
import sys
# import the connect library for psycopg2
import psycopg2
import pandas as pd
import numpy as np
from sqlalchemy import create_engine
import matplotlib.pyplot as plt

Ardından veritabanı sunucusuna bağlanmak için gerekli fonksiyon connect aşağıda verilmiştir. Bu fonksiyonun argümanı conn_params_dic sözlük yapısında bağlantı ile ilgili kullanıcı adı (user), şifre (password), sunucu (host) ve veritabanı adı (database) anahtarlarının karışılığı olan değerlerini içermelidir. psycopg2.connect fonksiyonu bu parametreleri kullanarak veritabanı sunucusuna bağlanır. OperationalError bu bağlantının gerçekleşmemesi durumunda bir hata mesajı üretir ve bağlantı None türünde döndürülür. Aksi takdirde bağlantının gerçekleştiğine dair bir mesaj kullanıcıya bildirilir ve bağlantı nesnesi döndürülülr.

In [2]:
# Define a connect function for PostgreSQL database server
def connect(conn_params_dic):
    conn = None
    try:
        print('PostgreSQL sunucusuna baglaniliyor ...........')
        conn = psycopg2.connect(**conn_params_dic)
        print("Baglanti basarili..................")
        
    except OperationalError as err:
        # passing exception to function
        show_psycopg2_exception(err)        
        # set the connection to 'None' in case of error
        conn = None
    return conn

Bağlantı hatası durumunda hatanın türü ve hatayla ilgili bilgiler kullanıcıya verilmelidir ki kullanıcı buna göre bir çözüm arasın. Bu amaçla aşağıda sys.exc_info() fonksiyonuna dayalı olarak yazılan hata yönetimi fonksiyonu show_psycopg2_exception fonksiyonu verilmiştir. Bu fonksiyonun argümanı psycopg2 tarafından başarısız bağlantı denemelerinde üretilen error nesnesidir.

In [3]:
# Define function to catch exception
# Define a function that handles and parses psycopg2 exceptions
def show_psycopg2_exception(err):
    # get details about the exception
    err_type, err_obj, traceback = sys.exc_info()    
    # get the line number when exception occured
    line_n = traceback.tb_lineno    
    # print the connect() error
    print ("\npsycopg2 ERROR:", err, "on line number:", line_n)
    print ("psycopg2 traceback:", traceback, "-- type:", err_type) 
    # psycopg2 extensions.Diagnostics object attribute
    print ("\nextensions.Diagnostics:", err.diag)    
    # print the pgcode and pgerror exceptions
    print ("pgerror:", err.pgerror)
    print ("pgcode:", err.pgcode, "\n")

SQL ile Tablo Oluşturma

SQL'de tablo oluşturmak için CREATE TABLE table_name komutu kullanılır. Bu isimle aynı veritabanında kayıtlı bir tablo varsa öncelikle onu silmekte fayda olacaktır. Bu amaç için DROP TABLE IF EXISTS table_name komutu kullanılır. Her iki SQL komutunu çalıştırmak için psycopg2.connection.cursor nesnesinin execute fonksiyonu kullanılmalıdır. Bu fonksiyona çalıştırılmak istenen SQL komutu SQL'de çalışacağı şekilde metin yapısında geçirilmelidir. Bu amaçla ilgili komuta python nesnelerinden girdi alınıyorsa bu girdiler yer tutucularla metin içine yerleştirilerek execute fonksiyonuna gönderilmelidir.

NASA Kepler Ötegezegen Veritabanı Uygulaması

Bir veritabanı, organize edilmiş (genellikle yapılandırılmış) bir veri topluluğudur.

Bu derste örnek olarak NASA ötegezegen arşivinden alınan Kepler keşfi bir grup yıldız ve bu yıldızların etrafında keşfedilen gezegenler hakkında bilgileri içeren sırasıyla Star ve Planet adlarında iki tablodan oluşan bir veritabanı oluşturulmak istenmektedir.

Aşağıdaki şekilde, bu tabloların organizasyonu görülmektedir.

Aşağıda bu organizasyona uygun olarak Star tablosunu:

  • kepler_id boş bırakılamayacak (NOT NULL), tamsayıdan oluşan (INTEGER) nesnelerle tanımlanan Kepler katalog ID'leri,
  • koi_name 20 karakter uzunluğunda metin veri saklayabilecek (VARCHAR(20)) ve yine boş bırakılamayacak (NOT NULL) Kepler kataloğu nesne isimlerini,
  • t_eff tamsayı olarak tanımlanacak yıldız etkin sıcaklığını,
  • radius en fazla 5 ondalık basamak içeren kayan noktalı sayı formatında yıldız yarıçapını

içerecek şekilde mydatabases veritabanına ekleyen fonksiyon create_table_Star yer almaktadır. koi_name bu tablonun ana anahtar sütunudur (PRIMARY KEY). Bu nedenle her bir yıldızın koi_name adı tek (ing. unique) olmalı ve boş bırakılmamalıdır. Bu yapısıyla PRIMARY KEY her satırı tanımlayan sütun görevini görmektedir.

In [4]:
# Define function to create table
def create_table_Star(cursor):
    try:
        # Dropping table iris if exists
        cursor.execute("DROP TABLE IF EXISTS Star;")
        sql = '''CREATE TABLE Star (
        kepler_id INTEGER NOT NULL,
        koi_name VARCHAR(20) NOT NULL,
        t_eff INTEGER,
        radius FLOAT(5),
        PRIMARY KEY (koi_name)
        );'''
        # Creating a table
        cursor.execute(sql);
        print("Star tablosu basariyla olusturulmustur...............")  
    except psycopg2.OperationalError as err:
        # pass exception to function
        show_psycopg2_exception(err)
        # set the connection to 'None' in case of error
        conn = None

Gezegen tablosu Planet da aynı yapıda bir fonksiyonla

  • kepler_id boş bırakılamayacak (NOT NULL), tamsayıdan oluşan (INTEGER) nesnelerle tanımlanan Kepler katalog ID'lerini,
  • koi_name 20 karakter uzunluğunda metin veri saklayabilecek (VARCHAR(20)) ve yine boş bırakılamayacak (NOT NULL) Kepler kataloğu nesene isimlerini,
  • kepler_name Kepler keşfi her bir gezegenin 20 karakter uzunluğunda metin veri saklayabilecek (VARCHAR(20)) ve yine boş bırakılamayacak (NOT NULL) Kepler kataloğundaki isimlerini,
  • status gezegenin onaylanmış (CONFIRMED) olup olmadığı bilgisini,
  • period gezegenin kayan noktalı sayı formatında (FLOAT) yörünge dönemini,
  • radius gezegenin kayan noktalı sayı formatında yarıçapını,
  • t_eq gezegenin tamsayı formatında eşdeğer sıcaklığını,

içerecek şekilde oluşturulmaktadır. Bu fonksiyonun da PRIMARY KEY sütunu kepler_id olarak belirlenmiş ve istenen tablonun oluşturulamaması durumunda oluşacak hata mesajının nasıl yönetileceği belilrlenmiştir.

In [5]:
def create_table_Planet(cursor):
    try:
        # Dropping table iris if exists
        cursor.execute("DROP TABLE IF EXISTS Planet;")
        sql = '''CREATE TABLE Planet (
        kepler_id INTEGER NOT NULL,
        koi_name VARCHAR(20) NOT NULL,
        kepler_name VARCHAR(20),
        status VARCHAR(20) NOT NULL,
        period FLOAT,
        radius FLOAT,
        t_eq INTEGER,
        PRIMARY KEY (koi_name)
        )'''
        # Creating a table
        cursor.execute(sql);
        print("Planet tablosu basariyla olusturulmustur...............")  
    except psycopg2.OperationalError as err:
        # pass exception to function
        show_psycopg2_exception(err)
        # set the connection to 'None' in case of error
        conn = None

Veritabanıyla Bağlantının Kurulması

Öncelikle bağlantı için gerekli bilgilerin bir sözlük yapısında tanımlanarak connect fonksiyonuna gönderilmesiyle veritabanıyla bağlantının kurulması sonrası; sırasıyla Star ve Planet tabloları aşağıdaki şekilde oluşturulmaktadır.

In [6]:
conn_params_dic={ 'user':'postgres', 'password':'PostGRE:sql','host':'localhost','dbname':'mydatabase'}
conn = connect(conn_params_dic)
# We set autocommit=True so every command we execute will produce results immediately.
conn.autocommit = True
cursor = conn.cursor()
create_table_Star(cursor)
create_table_Planet(cursor)
PostgreSQL sunucusuna baglaniliyor ...........
Baglanti basarili..................
Star tablosu basariyla olusturulmustur...............
Planet tablosu basariyla olusturulmustur...............

Veritabanı Tablolarına Verinin Alınması

Veritabanındaki tablolara dışarıdan veri almak için öncelikle veriyi içeren dosya data/stars.csv açılmalıdır. Daha sonra bu dosya satır satır okunur ve yapısına bağlı olarak uygun bir şekilde bileşenlerine (sütunlarına) ayrılarak INSERT INTO column_name komutuyla tablonun ilgili sütunlarına alınır.

In [7]:
fn = open("veri/stars.csv")

for line in fn:
    f1 = line.split(",")
    cursor.execute(
        "INSERT INTO Star (kepler_id, koi_name, t_eff, radius) VALUES (%s, %s, %s, %s);", 
        (f1[0], f1[1], f1[2], f1[3])
    ) 

Planet tablosu da aynı şekilde ilgili dosyadan gelen verilerle aşağıdaki şekilde doldurulabilir.

In [8]:
fn = open("veri/planets.csv")

for line in fn:
    f1 = line.split(",")
    cursor.execute('''
        INSERT INTO Planet (kepler_id, koi_name, kepler_name, status, period, radius, t_eq) 
        VALUES (%s, %s, %s, %s, %s, %s, %s)
        ''',(f1[0], f1[1], f1[2], f1[3], f1[4], f1[5], f1[6])
    ) 

SQL Sorgularıyla Satır Seçme

Oluşturalan veritabanını sorgulamak üzere bir veya daha fazla tabloyu alan ve istenen verilerle yeni bir tablo döndüren bazı örnekler psycopg2 fonksiyonu cursor.execute ile çalıştırarak aşağıda verilmektedir.

In [9]:
query = "SELECT * FROM Star;"
cursor.execute(query)
sonuc = cursor.fetchall()
for kayit in sonuc:
    print(kayit)
(10341777, 'K07610.01', 6302, 0.815)
(11296798, 'K07615.01', 6335, 3.523)
(3836450, 'K05010.01', 5160, 0.784)
(4483235, 'K07550.01', 8782, 1.965)
(6590362, 'K07570.01', 5926, 0.887)
(7031208, 'K07574.01', 6319, 1.032)
(7115384, 'K07578.01', 3789, 27.384)
(8106973, 'K07588.01', 5810, 0.811)
(9391817, 'K05663.01', 6200, 0.958)

Görüldüğü gibi bu sorgu Star tablosundaki tüm satırları döndürmektedir. SELECT komutu sorguyu başlatmakta * (wildcard) karakteri tüm satırların ("attributes") döndürülmesini sağlamakta; FROM ifadesi ise sorgunun hangi tablo üzerinde uygulanabileceğini belirtmektedir. psycopg2 için zorunlu olmamakla birlikte SQL sorguları ; karakteri ile sonlandırılır.

psycopg2 fonksiyonlarından conn.cursor.execute() sorguyu uygularken cursor.fetchall() sorgunun ürettiği tüm satırları (ing. attributes) alır. Alternatif olarak sadece bir satır alınmak isteniyor ya da sorgunun sadece bir satır ürettiğinden emin olunmuş ise cursor.fetchone() fonksiyonu da kullanılabilir. İstenen sayıda satır almak için cursor.fetchmany() fonksiyonuna satır sayısı argüman olarak geçirilebilir. Tüm bu fonksiyonlar birer liste üretir. Bu liste bir for döngüsü ile taranarak yukarıdaki gibi her satıra (attribute) karşılık gelen demet değişken (tuple) yukarıdaki gibi ayrı ayrı ekrana yazdırılabileceği gibi herhangi bir amaçla ayrılarak da kullanılabilir.

Uyarı: SQL sorguları büyük / küçük harf duyarlı değldir.

SQL Sorgularıyla Bazı Satır ve Sütunları Seçme

Aşağıdaki sorguyla Star tablosundan bu kez tüm sütunlar yerine sadece koi_name ve radius sütunları seçilmektedir.

In [10]:
query = "SELECT koi_name, radius FROM Star;"
cursor.execute(query)
sonuc = cursor.fetchall()
print("koi_name\t R_*")
for kayit in sonuc:
    print(kayit)
koi_name	 R_*
('K07610.01', 0.815)
('K07615.01', 3.523)
('K05010.01', 0.784)
('K07550.01', 1.965)
('K07570.01', 0.887)
('K07574.01', 1.032)
('K07578.01', 27.384)
('K07588.01', 0.811)
('K05663.01', 0.958)

Bir Koşulu Sağlayan Satırları Elde Etme

Bazı durumlarda bir tablodan sadece bir koşula uyan bazı sütunlar istenebilir. İsteen sütun adları SELECT ifadesinden sonra verilir ve WHERE ifadesi ile hangi koşula uyan satırların istendiği bir "boolean" ifadeyle veritabanı sunucusuna gönderilir.

In [11]:
query = "SELECT koi_name, radius FROM Star WHERE radius < 2;"
cursor.execute(query)
sonuc = cursor.fetchall()
print("koi_name\t R_*")
for kayit in sonuc:
    print(kayit)
koi_name	 R_*
('K07610.01', 0.815)
('K05010.01', 0.784)
('K07550.01', 1.965)
('K07570.01', 0.887)
('K07574.01', 1.032)
('K07588.01', 0.811)
('K05663.01', 0.958)

Görüldüğü gibi bu sorgu tüm satırlar arasından sadece koşulu sağlayan (Güneş yarıçapı biriminde 2 Güneş yarıçapından küçük) yıldızları getirmiştir.

Örnek 1: Büyük Yıldızlar

Star tablosunda Güneş'ten büyük yıldızların yarıçapını ve sıcaklığını bulmak için bir SQL sorgusu yazınız. Tabloda yıldızların yarıçapı Güneş yarıçapı cinsinden verilmiştir.

In [12]:
cursor.execute("SELECT {:s} FROM {:s} WHERE {:s}".
              format("radius,t_eff","Star","radius > 1"))
print(cursor.fetchall())
[(3.523, 6335), (1.965, 8782), (1.032, 6319), (27.384, 3789)]

Şartlı Sorgular

SQL'de karşılaştırmalar için $><$, $>$, $\le$, $\ge$ gibi Boolean operatörlerin yanı sıra AND, OR, NOT gibi koşulları birleştirme işlevi taşıyan operatörler de kullanılır.

SELECT ifadesiyle basit bir örnek aşağıdaki şekilde verilebilir.

In [14]:
cursor.execute("SELECT 2 > 3;")
print(cursor.fetchone())
cursor.execute("SELECT NOT 2 > 3;")
print(cursor.fetchone())
cursor.execute("SELECT 2 = 3;")
print(cursor.fetchone())
(False,)
(True,)
(False,)

Sayısal karşılaştırma operatörleri, tırnak işaretleri kullanılarak verilen metin değişkenlerle de çalışır:

In [15]:
cursor.execute("SELECT 'abc' < 'abcd';")
print(cursor.fetchone())
(True,)
In [16]:
cursor.execute("SELECT 'abc' = 'ABC';")
print(cursor.fetchone())
(False,)

SQL büyük/küçük harfe duyarlı olmasa da metin değişkenler öyle değildir. Bir öznitelikte büyük mü yoksa küçük harflerin mi kullanıldığından emin olunamayan durumlarda, özniteliği dönüştürmek için UPPER veya LOWER işlevi kullanılabilir ve ardından karşılaştırma gerçekleştirilebilir:

In [17]:
cursor.execute("SELECT UPPER('aBc') = 'ABC';")
print(cursor.fetchone())
(True,)
In [20]:
cursor.execute("SELECT LOWER('aBc') = 'abc';")
print(cursor.fetchone())
(True,)

Koşulların Bağlanması

Bir WHERE ifadesi içindeki koşullar da birbirlerine AND ve OR ile bağlanabilir.

In [21]:
cursor.execute("SELECT 0 < 1 AND 1 < 2;")
print(cursor.fetchone())
(True,)

Bu özellik veri üzerinde daha komplike sorgular kurgulamak için kullanılır.

In [25]:
cursor.execute('''
SELECT koi_name, radius FROM Star
WHERE radius >= 1 AND radius <= 2;
''')
sonuc = cursor.fetchall()
print("koi_name\t R_*")
for kayit in sonuc:
    print(kayit)
koi_name	 R_*
('K07550.01', 1.965)
('K07574.01', 1.032)

Bu tür aralık sorguları SQL'de yaygın olarak kullanıldığı için SQL bu amaçla BETWEEN ifadesini sağlamıştır. Bu ifade kullanılarak okunması daha kolay sorgular yazılabilir:

In [28]:
cursor.execute('''
SELECT koi_name, radius, t_eff FROM Star
WHERE radius BETWEEN 1 AND 2;
''')
sonuc = cursor.fetchall()
print("koi_name, R_*, T_eff")
for kayit in sonuc:
    print(kayit)
koi_name, R_*, T_eff
('K07550.01', 1.965, 8782)
('K07574.01', 1.032, 6319)

BETWEEN sorguları aranan aralığın ($1 \le R_{\star} \le 2$) her iki ucunu da dahil ederek sonuç üretirler.

Örnek 2: Sıcak Yıldızlar

Star tablosunda etkin sıcaklıkları 5000 ile 6000 K arasında olan tüm yıldızların kepler_id ve t_eff sütunundaki bilgileri getiren bir sorgu yazınız.

In [30]:
# Solution
cursor.execute("SELECT {:s} FROM {:s} WHERE {:s} BETWEEN {:d} AND {:d};".
              format("kepler_id, t_eff","Star","t_eff", 5000, 6000))
print("Kepler ID, T_eff")
for kayit in sonuc:
    print(kayit)
Kepler ID, T_eff
('K07550.01', 1.965, 8782)
('K07574.01', 1.032, 6319)

Bir Veritabanının Yapısının İncelenmesi

Bir tabloyu sorgulamak için tablonun yapısının, sütunların adlarının ve ne tür bilgi içerdiklerinin bilinmesi gerekir. Bu amaçla SQL \d (description) komutundan faydalanır. Örneğin PostgreSQL prompt'una (psql) aşağıdaki ifade yazılarak Planet tablosunun yapısı öğrenilebilir.

\d Planet;

psql \d komutu gibi kısayollar sağlasa da bunlar SQL'in parçası değildirler.. psycopg2 ile sorgularda SQL'de bu amaçla kullanılan information_schema özniteliğinden aşağıdaki şekilde faydalanılır.

In [31]:
cursor.execute('''
SELECT column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_name = %s;
''',("planet",))
for column in cursor.fetchall():
    print(column)
('kepler_id', 'integer', 'NO')
('koi_name', 'character varying', 'NO')
('kepler_name', 'character varying', 'YES')
('status', 'character varying', 'NO')
('period', 'double precision', 'YES')
('radius', 'double precision', 'YES')
('t_eq', 'integer', 'YES')

information_schema (ya da \d) her sütunun adını, ne tür veri sakladığını ve NULL veri içerip içeremeyeceği gibi bilgiler döndürür. Elde edilebilecek diğer bilgiler için bkz.

Bazı sütunların NULL türünde veri içeremeyeceği görülmektedir. Bu konu önemli olduğundan kısaca bakmakta fayda vardır.

SQL'de NULL Değeri

Daha önceki derslerden bilindiği gibi NULLözel bir veri türüdür. Örnek olarak verilen Planet tablosunda tüm ötegezegnlerin kepler_name sütunları dolu değildir. Zira tüm gezegenler diğer yöntemlerle onaylanarak CONFIRMED statüsü almamış olabilir. Bu sütunlar NULL değeri alabilir. Ancak tüm çalışılan cisimlerin tekil birer (ing. unique) kepler_id numaraları bulunduğundan değerleri NULL olamz.

NULL ile boşluk ' ' aynı anlama gelmez:

In [34]:
cursor.execute("SELECT  '' = NULL;")
print(cursor.fetchone())
(None,)

Bu karşılaştırma SQL tarafından yapılamadığından False ya da True yerine None değeri döndürmüştür. Çünkü SQL tarafından NULL değeri bilinmemektedir. Ancak IS karşılaştırması yapılarak istenen amaca ulaşılabilir.

In [33]:
cursor.execute("SELECT NULL IS NULL;")
print(cursor.fetchone())
(True,)

With this, you can also check that the first query with the $=$ operator actually returns a $NULL$ value:

In [35]:
cursor.execute("SELECT ('' = NULL) IS NULL; ")
print(cursor.fetchone())
(True,)

Örnek 3: Onaylanmış Küçük Gezegenler

Planet tablosundaki onaylanmaış gezegenleri (kepler_name sütunu NULL olmayan ya da status sütunu CONFIRMED olan gezegenler) listeleyen bir SQL sorgusu yazınız. Sorgunuzun sonucunu sadece 1 ile 3 Yer yarıçapı gezegenler arasındakileri alacak şekilde sınırlandırınız. Tabloda gezegen yarıçapları Yer yarıçapı cinsinden verilmiştir.

In [36]:
query = '''
SELECT {} FROM {} WHERE {} = 'CONFIRMED' 
AND {} IS NOT NULL 
AND {} BETWEEN {} AND {}
'''.format("kepler_name,radius","Planet","status","kepler_name","radius",1,3)
print("Kepler Name, R_p")
cursor.execute(query)
for column in cursor.fetchall():
    print(column)
Kepler Name, R_p
('Kepler-10 b', 1.45)
('Kepler-466 c', 1.24)
('Kepler-105 c', 1.88)
('Kepler-106 c', 2.35)
('Kepler-106 e', 2.58)
('Kepler-107 c', 1.84)
('Kepler-660 b', 2.52)
('Kepler-226 c', 2.7)
('Kepler-226 b', 1.59)
('Kepler-226 d', 1.19)
('Kepler-662 b', 1.54)
('Kepler-663 b', 2.7)
('Kepler-664 b', 2.71)
('Kepler-228 b', 1.56)
('Kepler-229 b', 2.41)
('Kepler-665 b', 2.86)
('Kepler-230 c', 2.13)
('Kepler-666 b', 2.21)
('Kepler-668 b', 2.54)
('Kepler-671 b', 2.33)
('Kepler-672 b', 2.7)
('Kepler-52 c', 1.81)
('Kepler-52 b', 2.33)
('Kepler-52 d', 1.8)
('Kepler-674 b', 1.32)
('Kepler-675 b', 2.38)
('Kepler-231 c', 1.73)
('Kepler-231 b', 1.61)
('Kepler-679 b', 2.69)
('Kepler-680 b', 1.96)
('Kepler-233 b', 2.71)
('Kepler-233 c', 2.72)
('Kepler-683 b', 1.97)
('Kepler-684 b', 2.66)
('Kepler-30 b', 1.91)
('Kepler-235 b', 2.18)
('Kepler-235 d', 1.99)
('Kepler-235 e', 1.94)
('Kepler-235 c', 1.22)
('Kepler-689 b', 2.45)
('Kepler-53 b', 2.9)
('Kepler-53 d', 2.44)
('Kepler-239 b', 2.36)
('Kepler-239 c', 2.19)

SQL'de Veri İstatistikleri ve Birleştirme Fonksiyonları

COUNT Fonksiyonu

Veritabanlarının boyutu büyüdükçe, istenen tüm verilerin tam bir tablosunu yazdırmak artık öğretici ve hatta mümkün olmayabilir. Bu durumlarda sonuçları sınırlandırmak için sorguyu daha spesifik hale getirme yoluna gidilebilir. Ancak hangi sonuçların beklendiği önceden bilinmiyorsa, verilerin boyutunu ve istatistiklerini karakterize etmek ve listelenen verileri sınırlandırmak için daha iyi bir yola ihtiyaç duyulur.

İstenen verileri tablo formatında döndürmek yerine, yalnızca sonuç tablosundaki satır sayısını döndürmek için COUNT fonksiyonu kullanılabilir. Örneğin Planet tablosundaki tüm gezegenleri saymak için bir örnek aşağıda verilmiştir.

In [37]:
cursor.execute("SELECT COUNT(*) FROM Planet;")
print(cursor.fetchone()) 
(96,)

COUNT daha önce görülen diğer tüm SQL ifadeleriyle birleştirilebilir. Örneğin yalnızca onaylanmış gezegenlerin sayısını almak için aşağıdaki gibi bir sorgu yazılabilir.

In [38]:
query = '''
SELECT COUNT(*) FROM {}
WHERE {} IS NOT NULL;
'''.format('Planet','kepler_name')
cursor.execute(query)
print(cursor.fetchall())
[(96,)]

Diğer Birleştirme ve İstatistik Fonksiyonları

COUNT SQL'in veri birleştirme (ing. aggregate) fonksiyonlarından yalnızca biridir. Bu tür fonksiyonlar yalnızca bir sonuç döndürdüğünden psycopg2 'de cursor.fetchone() fonksiyonuyla içerikleri bir demet değişkene (tuple) alınabilir. SQL

SQL'in sunduğu diğer birleştirme ve istatistik fonksiyonlarında en çok kullanılan bazıları aşağıda listelenmiştir. Bu fonksiyonların hangi veriler veya hangi veri aralığına uygulanacağını bilmek, bir sorguyu daha spesifik hale getirmeye yardımcı olabilir.

FonksiyonÇıktı
COUNTSorgu sonucu döndürülecek satır sayısı
MINBir sütundaki minimum değer
MAXBir sütundaki maksimum değer
SUMBir sütundaki değerlerin toplamı
AVGBir sütundaki değerlerin ortalaması
STDDEVBir sütundaki değerlerin standart sapması

Bu fonksiyonların kullanımına ilişkin bazı örnekler aşağıda sunulmuştur.

In [39]:
query = '''
SELECT MIN({}), MAX({}), AVG({})
FROM {};
'''.format('radius','radius','radius','Planet')
cursor.execute(query)
print(cursor.fetchone())
(0.49, 3791.05, 85.7476041666667)

SELECT ifadesinde SUM ve bölme (/) operatörlerini birlikte kullanarak AVG fonksiyonunun sonucuyla karşılaştırmak için aşağıdaki gibi bir sorgu yazılabilir.

In [40]:
query = '''
SELECT SUM({})/COUNT(*), AVG({})
FROM {};
'''.format('t_eff','t_eff','Star')
cursor.execute(query)
print(cursor.fetchone())
(6069, Decimal('6069.2222222222222222'))

Bu iki sonucun birbirinden farklı olmasının nedeni t_eff özniteliğinin bir tamsayı olması ve SQL 'in "/" operatörüyle bu özniteliği içeren bölme işlemlerini tamsayı bölmesi şeklinde yapmasıdır. SQL'de tam sayı bölmesi aşağıdaki şekilde uygulanır. SQL'de "/" operatörüyle uygulanan bölme işlemi bu yapısyla Python 2.x versiyonlarındakine benzer şekilde çalışır.

In [41]:
query = "SELECT 1/2, 1/2.0"
cursor.execute(query)
print(cursor.fetchone())
(0, Decimal('0.50000000000000000000'))

Sorgu Sonuçlarını Sıralama ve Limitleme

Bir sorgu sonucunda elde edilen tablodaki sonuçlar yararlı bir şekilde sıralanmış olmayabilir; zira veritabanındaki girildikleri sırayla getirilirler.

ORDER BY fonksiyonu istenen bir sütuna dayalı olarak istenen sırada sonuçların sıralanmasını sağlar. Örneğin Planet tablosundaki bir sorgunun gezegen yarıçapına (radius) azalan sırada (descending -- DESC) sıralanması için aşağıdaki sorgu yazılabilir.

In [42]:
query = '''
SELECT {} FROM {} 
ORDER BY {} DESC;
'''.format("koi_name, radius", "Planet", "radius")
print("KOI Numarasi, Yaricap (Yer Yaricapi)")
cursor.execute(query)
for record in cursor.fetchall():
    print(record)
KOI Numarasi, Yaricap (Yer Yaricapi)
('K00759.01', 3791.05)
('K00753.01', 3462.25)
('K00799.01', 447.32)
('K00772.01', 64.23)
('K00744.01', 51.4)
('K00754.01', 34.04)
('K00002.01', 16.39)
('K00010.01', 14.83)
('K00771.01', 14.41)
('K00806.02', 12.88)
('K00001.01', 12.85)
('K00767.01', 12.82)
('K00802.01', 12.0)
('K00760.01', 11.88)
('K00830.01', 11.87)
('K00809.01', 11.77)
('K00801.01', 9.74)
('K00806.01', 9.36)
('K00797.01', 8.18)
('K00777.01', 8.02)
('K00813.01', 7.92)
('K00791.01', 7.66)
('K00865.01', 7.58)
('K00776.01', 6.27)
('K00764.01', 5.73)
('K00782.01', 5.38)
('K00780.02', 5.32)
('K00757.01', 5.27)
('K00783.01', 4.91)
('K00766.01', 4.46)
('K00756.01', 4.02)
('K00787.02', 3.74)
('K00800.01', 3.62)
('K00757.02', 3.62)
('K00811.01', 3.62)
('K00829.03', 3.57)
('K00800.02', 3.51)
('K00115.01', 3.28)
('K00788.01', 3.16)
('K00747.01', 3.14)
('K00752.02', 3.1)
('K00752.01', 3.1)
('K00781.01', 3.07)
('K00787.01', 3.07)
('K00756.02', 3.02)
('K00829.01', 2.9)
('K00758.01', 2.86)
('K00810.01', 2.76)
('K00804.01', 2.72)
('K00790.02', 2.72)
('K00790.01', 2.71)
('K00755.01', 2.71)
('K00773.01', 2.7)
('K00749.01', 2.7)
('K00751.01', 2.7)
('K00785.01', 2.69)
('K00795.01', 2.66)
('K00116.02', 2.58)
('K00765.01', 2.54)
('K00746.01', 2.52)
('K00814.01', 2.45)
('K00829.02', 2.44)
('K00757.03', 2.41)
('K00780.01', 2.38)
('K00835.01', 2.36)
('K00116.01', 2.35)
('K00775.02', 2.33)
('K00769.01', 2.33)
('K00762.01', 2.21)
('K00835.02', 2.19)
('K00812.01', 2.18)
('K00759.02', 2.13)
('K00812.02', 1.99)
('K00794.01', 1.97)
('K00786.01', 1.96)
('K00812.03', 1.94)
('K00806.03', 1.91)
('K00115.02', 1.88)
('K00117.02', 1.84)
('K00775.01', 1.81)
('K00775.03', 1.8)
('K00784.01', 1.73)
('K00784.02', 1.61)
('K00749.02', 1.59)
('K00756.03', 1.56)
('K00750.01', 1.54)
('K00072.01', 1.45)
('K00778.01', 1.32)
('K00112.02', 1.24)
('K00812.04', 1.22)
('K00749.03', 1.19)
('K00116.04', 0.99)
('K00116.03', 0.85)
('K00115.03', 0.65)
('K00082.04', 0.58)
('K00082.05', 0.49)

Görüldüğü gibi tablo çok uzundur. Bu tablonun sadece bir kısmını görmek için LIMIT fonksiyonu kullanılabilir. Örneğin en küçük 5 gezegeni listelemek için azalan sıra (DESC) yerine artan sırada (ASC) sıralama istenerek sorgu sonuçları aşağıdaki şekilde limitlenebilir.

In [44]:
query = '''
SELECT {} FROM {} 
ORDER BY {} ASC
LIMIT 5;
'''.format("koi_name, radius", "Planet", "radius")
print("KOI Numarasi, Yaricap (Yer Yaricapi)")
cursor.execute(query)
for record in cursor.fetchall():
    print(record)
KOI Numarasi, Yaricap (Yer Yaricapi)
('K00082.05', 0.49)
('K00082.04', 0.58)
('K00115.03', 0.65)
('K00116.03', 0.85)
('K00116.04', 0.99)

Bu tür bir sınırlandırma büyük tablolarla karşılaşıldığında performansı önemli ölçüde etkileyecektir.

Örnek 4: Gezegen İstatistikleri

Onaylanmış gezegenler için

  • minimum yarıçap;
  • maksimum yarıçap;
  • ortalama yarıçap ve
  • yarıçapın standart sapma değerlerini

veren bir SQL sorgusu yazınız.

In [55]:
query = '''
SELECT MIN(radius),MAX(radius),AVG(radius),STDDEV(radius) 
FROM Planet WHERE status IS NOT NULL
'''
print("Minimum, Maksimum, Ortalama, St.Sapma")
cursor.execute(query)
for record in cursor.fetchall():
    print(record)
Minimum, Maksimum, Ortalama, St.Sapma
(0.49, 3791.05, 85.7476041666667, 521.780900581934)

Veriyi Gruplama

Planet tablosunda aynı boyuta sahip (eşit yarıçaplı) birden fazla gezegen olup olmadığı öğrenilmek isteniyor olsun. Bu amaçla sonuçları aynı yarıçapın birden çok kez geçtiği gezegenleri görmek üzere sıralanmış basit bir sorgu yazılabilir.

In [59]:
query = '''
SELECT  {} FROM {}
ORDER BY {} ASC;
'''.format('kepler_id, radius','Planet','radius')
cursor.execute(query)
print("Kepler ID, Yaricap")
for record in cursor.fetchall():
    print(record)
Kepler ID, Yaricap
(10187017, 0.49)
(10187017, 0.58)
(9579641, 0.65)
(8395660, 0.85)
(8395660, 0.99)
(10601284, 1.19)
(4139816, 1.22)
(10984090, 1.24)
(11853255, 1.32)
(11904151, 1.45)
(10662202, 1.54)
(10872983, 1.56)
(10601284, 1.59)
(12066335, 1.61)
(12066335, 1.73)
(11754553, 1.8)
(11754553, 1.81)
(10875245, 1.84)
(9579641, 1.88)
(3832474, 1.91)
(4139816, 1.94)
(12110942, 1.96)
(2713049, 1.97)
(4139816, 1.99)
(11018648, 2.13)
(4139816, 2.18)
(5456651, 2.19)
(11153539, 2.21)
(11460018, 2.33)
(11754553, 2.33)
(8395660, 2.35)
(5456651, 2.36)
(11918099, 2.38)
(10910878, 2.41)
(5358241, 2.44)
(4476123, 2.45)
(10526549, 2.52)
(11391957, 2.54)
(8395660, 2.58)
(3114167, 2.66)
(12070811, 2.69)
(10682541, 2.7)
(10601284, 2.7)
(11507101, 2.7)
(10854555, 2.71)
(12470844, 2.71)
(12470844, 2.72)
(3641726, 2.72)
(3940418, 2.76)
(10987985, 2.86)
(5358241, 2.9)
(10872983, 3.02)
(12366084, 3.07)
(11923270, 3.07)
(10797460, 3.1)
(10797460, 3.1)
(10583066, 3.14)
(12404086, 3.16)
(9579641, 3.28)
(3342970, 3.51)
(5358241, 3.57)
(3342970, 3.62)
(10910878, 3.62)
(4049131, 3.62)
(12366084, 3.74)
(10872983, 4.02)
(11403044, 4.46)
(12020329, 4.91)
(10910878, 5.27)
(11918099, 5.32)
(11960862, 5.38)
(11304958, 5.73)
(11812062, 6.27)
(6862328, 7.58)
(12644822, 7.66)
(4275191, 7.92)
(11818800, 8.02)
(3115833, 8.18)
(3832474, 9.36)
(3351888, 9.74)
(3935914, 11.77)
(5358624, 11.87)
(11138155, 11.88)
(3453214, 12.0)
(11414511, 12.82)
(11446443, 12.85)
(3832474, 12.88)
(11465813, 14.41)
(6922244, 14.83)
(10666592, 16.39)
(10848459, 34.04)
(10480982, 51.4)
(11493732, 64.23)
(3246984, 447.32)
(10811496, 3462.25)
(11018648, 3791.05)

Ancak bu tür bir tabloda birden fazla kez geçen yarıçapu bulmak zor olduğu gibi tablonun daha büyük olması durumunda olanaksız hale de gelebilir. Böyle durumlarda GROUP BY fonksiyonundan yararlanarak birbiriyle aynı bilgiler üzerindne gruplandırmalar yapılarak sayma (COUNT) gibi birleştirme fonksiyonlarından faydalanılabilir. Bir önceki sorguda karşılaşılan problem aşağıdaki gibi bir sorguyla çözülebilir.

In [60]:
query = '''
SELECT {}, COUNT({}) 
FROM {} 
GROUP BY {};
'''.format('radius','koi_name','Planet','radius')
print("Yaricap, Sayi")
cursor.execute(query)
for record in cursor.fetchall():
    print(record)
Yaricap, Sayi
(1.84, 1)
(3.07, 2)
(1.19, 1)
(0.49, 1)
(1.8, 1)
(2.52, 1)
(2.76, 1)
(2.35, 1)
(5.38, 1)
(2.41, 1)
(3462.25, 1)
(2.36, 1)
(12.85, 1)
(447.32, 1)
(1.96, 1)
(51.4, 1)
(1.32, 1)
(12.82, 1)
(3.14, 1)
(1.99, 1)
(0.65, 1)
(6.27, 1)
(2.71, 2)
(3.57, 1)
(3.1, 2)
(1.24, 1)
(2.38, 1)
(0.85, 1)
(7.92, 1)
(1.91, 1)
(1.54, 1)
(2.7, 3)
(2.44, 1)
(2.33, 2)
(11.87, 1)
(8.02, 1)
(2.9, 1)
(14.83, 1)
(7.66, 1)
(2.13, 1)
(12.88, 1)
(7.58, 1)
(2.18, 1)
(2.72, 2)
(8.18, 1)
(5.27, 1)
(2.58, 1)
(1.81, 1)
(3.62, 3)
(2.45, 1)
(3.74, 1)
(2.21, 1)
(12.0, 1)
(1.73, 1)
(1.22, 1)
(2.66, 1)
(1.61, 1)
(4.02, 1)
(1.59, 1)
(3.51, 1)
(1.88, 1)
(64.23, 1)
(9.74, 1)
(9.36, 1)
(5.73, 1)
(34.04, 1)
(14.41, 1)
(11.77, 1)
(16.39, 1)
(0.99, 1)
(3.16, 1)
(4.91, 1)
(11.88, 1)
(4.46, 1)
(2.86, 1)
(1.45, 1)
(0.58, 1)
(3.02, 1)
(2.19, 1)
(3.28, 1)
(2.69, 1)
(1.94, 1)
(5.32, 1)
(1.56, 1)
(1.97, 1)
(2.54, 1)
(3791.05, 1)

Bu sorgu bir öncekine göre daha faydalı, ancak hala çok uzundur. Bu sorgunun sonuçlarını daha da sınırlandırmakta fayda vardır.

Gruplandırılmış Veride Şartlı Sorgulama

Bu sorgunun sonucundaki tabloyu gezegen sayısının birden büyük olduğu sonuçlarla sınırlamak istendiğinde, WHERE ifadesi kullanılarak bir koşul eklenebilir. Aşağıdaki sorgu bu amaç için ilk bakışta ideal gözükebilir:

SELECT radius, COUNT(koi_name) 
FROM Planet 
WHERE COUNT(koi_name) > 1
GROUP BY radius;

Ancak WHERE ifadesi GROUP BY fonksiyonundan önce geçtiği için gruplandırılmış tablounun COUNT fonsiyonuna erişimi kalmamıştır.

COUNT gibi birleştirme fonksiyonlarına GROUP BY ifadeleriyle gruplanan tabloların erişimi içinWHERE yerine HAVING ifadesi kullanılır.

In [61]:
query = '''
SELECT {}, COUNT({}) 
FROM {} 
GROUP BY {}
HAVING COUNT({}) > {};
'''.format('radius','koi_name','Planet','radius','koi_name',1)
cursor.execute(query)
for record in cursor.fetchall():
    print(record)
(3.07, 2)
(2.71, 2)
(3.1, 2)
(2.7, 3)
(2.33, 2)
(2.72, 2)
(3.62, 3)

Örnek 5: Çoklu Gezegen Sistemleri

Planet tablosunda yer alan çok gezegenli sistemlerdeki sayısını verecek bir sorgu yazınız. Aynı barınak yıldızın etrafında dolanan gezegenler aynı kepler_id değerine, ancak farklı koi_name değerlerine sahip olacaktır.

Sorgunuz, her satırda yıldızın kepler_id değerini ve o yıldızın etrafında dönen gezegenlerin sayısını (yani bu kepler_id değerini paylaşan gezegenlerin sayısını) içeren bir tablo döndürmelidir.

Sonuçlarınız sadece çoklu gezegen sistemleri (birden fazla gezegen içeren) sıralanmış olmalıı ve artan gezegen sayısına göre sıralanmalıdır.

In [63]:
query = '''
SELECT {}, COUNT({}) FROM {}
GROUP BY {}
HAVING COUNT({}) > {}
ORDER BY COUNT({}) DESC;
'''.format('kepler_id','koi_name','Planet','kepler_id','koi_name',1,'koi_name')
print("Kepler ID, Gezegen Sayisi")
cursor.execute(query)
for record in cursor.fetchall():
    print(record)
Kepler ID, Gezegen Sayisi
(8395660, 4)
(4139816, 4)
(9579641, 3)
(10601284, 3)
(10872983, 3)
(11754553, 3)
(10910878, 3)
(5358241, 3)
(3832474, 3)
(3342970, 2)
(11018648, 2)
(11918099, 2)
(10187017, 2)
(10797460, 2)
(5456651, 2)
(12066335, 2)
(12366084, 2)
(12470844, 2)