You are on page 1of 171

I.

Adatbazisok

4
1 Bevezetés

Manapság az adatbázisok már a mindennapi életünk részévé váltak. Tudtunkkal vagy anélkül, lépten-
nyomon adatbázisokat használunk, személyes adatainkat adatbázisokban tárolják. Elég, ha lefuttatunk
egy keresést a Google-ben, megnézünk egy videót a YouTube videó megosztón, keresünk és vásárolunk
egy online áruházban, banki átutalást végzünk az interneten, és a lista még hosszan folytatható lenne.
Ami közös a fenti esetekben, hogy mindegyik webes alkalmazás mögött egy adatbázis áll: a weboldalak
rendszerezett gyűjteménye, videók rendszerezett gyűjteménye, termékek és megrendelések
nyilvántartása, bankszámlaadatok nyilvántartása stb. A legtöbb összetett webes alkalmazásról
elmondható, hogy egy adatbázis áll mögötte, amiben a felhasználói és tartalmi adatok tárolódnak.
Természetesen nem csak webes alkalmazások használnak adatbázisokat, hanem éppúgy asztali és
gyakran beágyazott szoftverek is: adatbázisokban tárolják a hallgatói adatokat, amit az
adminisztrátorok egy asztali alkalmazás segítségével érhetnek el, mobil szoftveren keresztül
hozzáférhetünk a banki adatainkhoz, és tranzakciókat indíthatunk, adatbázisban tárolódnak el a
mobiltelefonunkon rögzített naptár események, de ugyanígy valamilyen adatbázisban tárolódnak el a
gépjárművek GPS és diagnosztikai adatai is.

Adatbázis alatt információk olyan rendezett gyűjteményét értjük, amely hosszú időn át – akár évekig
is – megőrzi a tartalmát. Azt a szoftvert, amellyel ezekhez az adatokhoz hozzáférhetünk, adatbázis-
kezelő rendszernek (Database Management System – DBMS) nevezzük. Az adatbázis-kezelő
rendszerek számos funkcióval rendelkeznek, és olyan, gyakran használt szolgáltatásokat valósítanak
meg, amelyek saját implementációja tipikusan egyenként is sok emberhónap vagy év kutatási és
fejlesztési munkát igényelne. A leggyakrabban támogatott szolgáltatások a következők:

 Lehetővé tenni a felhasználók számára új adatbázisok létrehozását, az adatbázisban tárolt


adatok szerkezetének (sémájának) meghatározását. Az adatbázis-kezelő rendszer biztosítja a
tárolt adatok integritását: a tárolt adatoknak meg kell felelniük az adatbázis sémájának, a
sémában meghatározott típusoknak, és értelmezési tartományoknak (tartományi integritás).
Kapcsolatot lehet létesíteni a tárolt adatok között, és a rendszer ezeknek a kapcsolatoknak az
érvényességét az adatbázis életciklusa során biztosítja (referenciális integritás).
 Egy speciális, erre a célra kifejlesztett nyelv segítségével lehetőségünk van az adatbázisban
adatok elhelyezésére, visszakeresésére, módosítására. Az egyszerű kereséseken kívül általában
lehetőségünk van több, különböző típusú, de összefüggő adatokon átívelő lekérdezések
megfogalmazására, az eredmények transzformálására, aggregálására. Relációs adatbázisok (2.
fejezet) esetén ez a nyelv általában az SQL (5. fejezet) szokott lenni.
 Lehetőségünk van nagy mennyiségű – akár több terabájtnyi vagy annál is több – adat hatékony
tárolására, illetve az adatokhoz történő gyors hozzáférésre. Az adatbázis-kezelő rendszer az
adatokat megfelelő rendszerezettséggel tárolja, az információkeresést és módosítást pedig
megfelelő adatstruktúrákkal és algoritmusokkal támogatja. Ennek egyik eszköze az ún. indexek
(7. fejezet) használata, amelyek a tárolt adat valamilyen tulajdonság szerinti rendezett
tartalomjegyzékét jelentik, és a keresés hatékonyságát lényegesen javítani tudják.
 Az adatbázis-kezelő rendszer gondoskodik az adatok biztonságos tárolásáról, csökkenti az
adatvesztés lehetőségét. Ezt az adatok redundáns tárolásával, illetve biztonsági mentések
rendszeres készítésével támogatja.

5
 A tranzakciókezelés (8. fejezet) segítségével biztosított az összetartozó műveletek atomi
végrehajtása, nem fordulhat elő például, hogy egy banki átutalás során az egyik számla még
terhelésre kerül, de a másik számlán egy rendszerhiba miatt a jóváírás már nem következik be.
A tranzakciós naplózás (8.3. fejezet) során az adatbázison elvégzett műveletekről
bejegyzéseket készítünk egy, az erre a célra fenntartott naplóban. Rendszerhiba esetén a nem
teljesen végrehajtott műveletek hatása a napló alapján visszagörgethető, így helyreállítva az
adatbázis konzisztens állapotát.
 Az adatbázishoz általában egyszerre több felhasználó is hozzáférhet, azok párhuzamosan
írhatják és olvashatják az abban tárolt adatokat. A konkurens elérés nem megfelelő
implementáció esetén nemkívánatos következményekkel járhat, például a különböző
felhasználói folyamatok felülírhatják egymás módosításainak hatásait, vagy az egyik folyamat
egy köztes állapotából kiindulva egy másik folyamat hibás adatokat állíthat elő. Az adatbázis-
kezelő rendszer a különböző felhasználói folyamatok – ún. tranzakciók (8. fejezet) –
izolációjával biztosítja a nemkívánatos mellékhatások elkerülését. Ehhez különböző zárolási
stratégiákat (8.2.2.3. fejezet) és ugyanazon adat eltérő verzióinak nyilvántartását (8.2.3.
fejezet) alkalmazza.
 Lehetőségünk van az adatokhoz történő hozzáférés szabályozására: a DBMS-ek általában
biztosítják az alapszintű felhasználó-kezelést, amelyen keresztül pontosan meghatározhatjuk,
hogy mely felhasználók az adatok mely köréhez férhetnek hozzá, és azon milyen műveleteket
hajthatnak végre.

A fentiekben csak a legalapvetőbb szolgáltatásokra térünk ki. A kereskedelmi forgalomban elérhető,


illetve elterjedt nyílt forrású adatbázis-kezelő rendszerek számos további funkcionalitással is
rendelkeznek, beleértve procedurális programozhatóságot, horizontális skálázhatóságot,
adatbányászati eszközöket, és a lista még hosszan sorolható lenne.

1.1 Az adatbázis-kezelő rendszerek felépítése


Az 1.1 ábrán egy tipikus adatbázis-kezelő rendszer felépítését mutatjuk be. A rendszer számára
felhasználók és felhasználói programok generálják a végrehajtandó utasításokat: az adatbázis
adminisztrátorok felelősek az adatbázis felépítésének (sémájának) kialakításáért, az egyszerű
felhasználók és felhasználói alkalmazások pedig az adatok bevitelét, lekérdezését, módosítását végzik.

6
Egyszerű felhasználók,
Adatbázis adminisztrátor
felhasználói programok

Lekérdezés fordító Séma leírás fordító Tranzakciós utasítás fordító

Végrehajtó motor Tranzakció-kezelő

Naplókezelő
Ütemező
Erőforrás kezelő (adat, index) és helyreállító

Puffer kezelő / Puffer

Tárkezelő

Tárterület

1.1. ábra: Adatbázis-kezelő rendszerek felépítése

A lekérdezéseket és adat módosító utasításokat a lekérdezés fordító értelmezi, és a kapott


utasítások alapján előállít egy végrehajtási tervet, amely a lekérdezés megválaszolásához szükséges
primitív lépések sorozatát tartalmazza. A végrehajtási tervet a fordító optimalizálja a minél
hatékonyabb végrehajtás érdekében. A végrehajtási tervet a végrehajtó motor kapja meg, amely a
terv alapján primitív adatműveletek sorozatát hajtja végre. Az egyes műveleteket az erőforrás
kezelőnek továbbítja, amely ismeri az adatállományok felépítését, valamint a lekérdezések gyorsítását
elősegítő index állományokat. A szükséges adatokat az adatbázis-kezelő először a
rendszermemóriában lévő pufferben keresi. A puffert a puffer kezelő felügyeli, ami szükség esetén
a hiányzó adatokat a háttértárról a pufferbe mozgatja, illetve a módosított adatokat a pufferből a
háttértárra írja. A perzisztens tárterületet az adatbázis-kezelő rendszer a tárkezelő modul
segítségével éri el, ami gyakran az operációs rendszer része, de hatékonysági okokból akár adatbázis-
kezelő rendszer közvetlenül is küldhet utasításokat a lemez vezérlőnek.

A lekérdező / módosító műveleteket tranzakciókba foglalhatjuk, a tranzakció vezérlő utasításokat


értelmezés után a tranzakciókezelő fogadja, amely két fő modulból épül fel: (i) a naplókezelő és
helyreállító modulból, amely a tranzakciós naplót tartja karban, és ezáltal a tranzakciók tartósságáért
és hiba esetén a konzisztens állapot visszaállításért felelős, valamint (ii) a tranzakciós ütemezőből,
ami a tranzakciók izolációjáért felel.

Az adatbázis sémáját az ún. adat-definíciós nyelv (Data Definition Language – DDL, ld. 5. fejezet)
segítségével írhatja le és módosíthatja az adatbázis adminisztrátor: meghatározhatja a tárolt adatok
típusát, tulajdonságait, a különböző adatok közti kapcsolatokat és kényszereket. A DDL utasításokat a
séma leírás fordító dolgozza fel, majd az értelmezett utasításokat átadja a végrehajtó motornak,
ami az erőforrás-kezelőn keresztül elvégzi a módosításokat az adatbázis adataiban és
metaadataiban.

7
1.2 Áttekintés
Jegyzetünk célja egy alapszintű betekintést nyújtani az adatbázisok világába: megismerni a relációs
adatbázisok elméleti hátterét, az adatbázis-kezelő rendszerek által nyújtott leggyakoribb
szolgáltatásokat, és azok gyakorlati alkalmazási lehetőségeit. A jegyzet megírása során a Database
Systems: The Complete Book [1] című szakirodalom felépítésére és terminológiájára támaszkodtunk.

A jegyzet további adatbázisokkal foglalkozó része a következő egységekből épül fel:

 A 2. fejezetben bemutatjuk a leggyakrabban alkalmazott ún. relációs adatmodellt – amely az


adatokat kétdimenziós táblázatokként reprezentálja – valamint az alapvető műveleteket,
amelyek segítségével a benne tárolt adatokat elérhetjük.
 A 3. fejezetben ismertetjük a relációs adatbázisok tervezésének alapelveit, valamint olyan
normál formákat, amelyek segítségével a tárolt adatokban jelenlévő redundancia
csökkenthető.
 A 4. fejezetben grafikus formalizmusokat mutatunk be, amelyek megkönnyítik az adatbázis
szerkezetének leírását és dokumentálását, valamint megfeleltetéseket a grafikus formalizmus
és a relációs adatmodell között.
 A relációs adatbázisok programozására leggyakrabban használt nyelv az SQL (Structured Query
Language), amelybe az 5. fejezet biztosít alapszintű betekintést.
 A 6. fejezet témája adatbázis-kezelő rendszerek memória-hierarchiája, illetve azok a
megoldások, amelyek az adatok háttértáron történő elhelyezését biztosítják.
 A 7. fejezetben megismertetjük az olvasót az adatelérést gyorsító adatbázis indexek elméleti
hátterével, és azok gyakorlati alkalmazásával.
 A 8. fejezetben a tranzakciókezeléssel ismerkedünk meg, amely segítségével biztosítható az
adatbázis konzisztens állapotának tartós fenntartása.

8
2 Az adatok relációs modellje

Az adatmodell az adatbázisok elméletének egyik legalapvetőbb fogalma, meghatározza az adat


leírásának módját. Az adatmodell három dolgot rögzít:

1. Az adat struktúráját: meghatározza, hogy az adatokat milyen formában reprezentáljuk, ahhoz


hogyan férünk hozzá. Adatbázis-kezelő rendszereknél nem alacsony szintű struktúrákra és
tömbökre, listákra kell gondolni, hanem egy magas szintű, implementáció-független
interfészre, amelyet speciális lekérdező nyelvek segítségével tudunk elérni.
2. Az adatokon érvényes kényszereket: az adatok tárolási szerkezetét gyakran kényszerekkel
pontosíthatjuk, megszabhatjuk, hogy milyen típusú, méretű, értelmezési tartományú stb.
adatok jelenhetnek meg az adatstruktúra egyes pontjain. Ez magában foglal olyan egyszerű
kényszereket, mint hogy valahol kötelező értéknek szerepelnie, valamelyik értéknek egész
számnak kell lennie bizonyos intervallumon belül, de például kényszerekkel fejezhetünk ki
kapcsolatokat az adatmodell egyes részei között is (ld. 2.4.2 fejezet).
3. Az adaton elvégezhető műveleteket: az adatbázis adatmodelleken általában műveletek egy
szűk körét tudjuk csak elvégezni, nincs akkora szabadságunk, mint egy általános célú
programozási nyelvnél. Egyben ez a legnagyobb előnye is: az adatbázis programozó tömör, jól
olvasható utasításokat adhat, és a műveletek végrehajtására az adatbázis-kezelő rendszer
hatékony implementációval rendelkezik. A programozónak ezen műveletek implementációját
egyáltalán nem kell ismernie.

Az adatbázis-kezelő rendszerekben napjainkban két leggyakrabban használt adatmodell a (i) relációs


(2.1 fejezet) – amely jelen fejezet központi témája –, és a (ii) félig strukturált (2.5.1 fejezet) adatmodell.

2.1 A relációs adatmodell


A relációs adatmodellt Edgar F. Codd, az IBM kutatója dolgozta ki és publikálta. Alapeleme a reláció,
ami nem más, mint egy két dimenziós táblázat. Erre egy egyszerű példát a 2.1. táblázatban ábrázolt
𝑇𝑒𝑟𝑚é𝑘 reláció szolgáltat.

Név Ár Raktárkészlet Kategória GyártóNév


Kanapé 150.000 3 Nappali Nagy Bútor Kft
Dohányzóasztal 12.000 3 Nappali Nagy Bútor Kft
Szék 22.000 15 Iroda Fabric Rt
Hintaágy 32.000 1 Kert Bútorgyár

2.1. táblázat: A Termék reláció

A táblázat egy fejlécből, sorokból és oszlopokból áll. A táblázat egyes sorai reprezentálják a relációban
tárolt egyedeket, az egyes oszlopok pedig az egyedek egyes tulajdonságait. Jelen reláció egy
képzeletbeli bútorboltban forgalmazott termékeket és azok adatait tárolja, tehát a reláció egyes sorai
egy-egy terméknek felelnek meg. Az egyed tulajdonságait a relációs adatmodellben attribútumnak
nevezzük. Az attribútumok neveit a fejléc elnevezései rögzítik: 𝑁é𝑣, Á𝑟, 𝑅𝑎𝑘𝑡á𝑟𝑘é𝑠𝑧𝑙𝑒𝑡, 𝐾𝑎𝑡𝑒𝑔ó𝑟𝑖𝑎,
𝐺𝑦á𝑟𝑡ó𝑁é𝑣. Mivel ez a reláció öt attribútummal rendelkezik, ezért azt mondjuk, hogy a reláció foka
(aritása) öt. A relációban tárolt sorok számát pedig a reláció számosságának nevezzük, tehát a
𝑇𝑒𝑟𝑚é𝑘 reláció számossága négy. A reláció sorait n-esnek (tupple) szokták nevezni, egy ilyen n-es a
reláció minden egyes attribútumához egy értéket rendel. A példa reláció első n-ese a 𝑁é𝑣

9
attribútumhoz a „Kanapé”, az Ár attribútumhoz a 150.000, a 𝑅𝑎𝑘𝑡á𝑟𝑘é𝑠𝑧𝑙𝑒𝑡 attribútumhoz a 3 stb.
értékeket rendeli. Az attribútum típusa mindig valamilyen egyszerű típus (szám, szöveg, dátum stb.),
egy attribútum nem tárolhat összetett típusokat, mint egy C-beli struktúra vagy tömb.

A relációt a sémára illeszkedő n-esek halmaza alkotja. Annak, hogy a reláció egy halmaz, lényeges
következményei, hogy

1. a relációban nem lehet két egyforma sor (n-es),


2. a relációban tárolt sorok nem sorrendezettek,
3. a reláció tetszőleges számú sort tartalmazhat.

A reláció sémáját úgy kapjuk, meg, ha a nevét követően zárójelek között felsoroljuk a reláció
attribútumait. A séma határozza meg az adatok tárolási struktúráját az adott relációban:

𝑇𝑒𝑟𝑚é𝑘(𝑁é𝑣, Á𝑟, 𝑅𝑎𝑘𝑡á𝑟𝑘é𝑠𝑧𝑙𝑒𝑡, 𝐾𝑎𝑡𝑒𝑔ó𝑟𝑖𝑎, 𝐿𝑒í𝑟á𝑠, 𝐺𝑦á𝑟𝑡ó𝑁é𝑣)

Egy relációban egy típusú egyed (pl. termékek) példányait lehet eltárolni, de természetesen tetszőleges
számú relációt készíthetünk különböző típusú egyedekhez, és a relációk között kapcsolatot is
létesíthetünk. Például nyilvántarthatjuk a termékeket, a gyártóikat, a termékekre leadott
megrendeléseket, a megrendelésekhez kapcsolódó vásárolói és számlainformációt stb. Az egy témakör
köré csoportosuló, összefüggő relációk összessége alkotja az adatbázist, a relációk sémájának
összessége pedig az adatbázis sémáját. Ha az adatbázisban a termékek adatain túl a gyártók adatait
is el szeretnénk tárolni, akkor ez például a

𝐺𝑦á𝑟𝑡ó(𝐺𝑦á𝑟𝑡ó𝑁é𝑣, 𝐶í𝑚, 𝐸𝑚𝑎𝑖𝑙)

sémájú relációval lehetséges: a gyártókról nyilvántartjuk a nevüket, címüket, e-mail címüket. Erre a
sémára illeszkedő konkrét relációt a 2.2. táblázatban láthatunk.

GyártóNév Cím Email


Nagy Bútor Kft 5000 Szolnok Fa u 2 nb@example.org
Fabric Rt 9012 Győr Ág u 15 fabric@example.org
Bútorgyár 1111 Bp. Hős krt 3. xyb@example.org

2.2. táblázat: Gyártó reláció

2.2 Algebrai lekérdezések


Ahhoz, hogy a relációkból adatokat tudjunk kinyerni, szükségünk van valamilyen módszerre, amelynek
a segítségével egyszerűen le tudjuk írni, hogy pontosan milyen adatokra van szükségünk. Egy
lehetséges megoldás erre a relációs algebra, ami mint minden algebra, operátorokból és
operandusokból áll. Jelen esetben az operandusok a relációk és a relációkat jelölő változók, az
operátorok pedig a relációkon leggyakrabban használt műveleteket jelölik. Relációs algebrai
kifejezésekkel ún. lekérdezéseket tudunk megfogalmazni a relációkon: új relációkat tudunk
előállítani, amelyekben a számunkra érdekes, szűrt, akár aggregált adatok szerepelnek.

Felmerülhet a kérdés, hogy miért van szükségünk egy új algebrára, egy új nyelvre, miért nem
használunk helyette egy általános célú programozási nyelvet, mint például a C vagy a JAVA. A válasz
az, hogy azért, mert egy speciális, erre a célra kifejlesztett nyelvvel (i) sokkal hatékonyabban tudunk

10
(ii) implementáció-független műveletsorozatokat leírni relációkon, amelyeket (iii) a végrehajtó
optimalizáltan tud elvégezni. Ahhoz, hogy egy C nyelvű programot tudjunk írni, amely egy bizonyos
műveletet végrehajt egy reláción, ismernünk kell a kezelt adatbázis implementációját, adatábrázolását,
adattárolási módszereit stb., hogy a kódunk illeszkedjen hozzá. Azáltal, hogy egy magas szintű,
kizárólag relációk kezelésére alkalmas formalizmust alkotunk, szűkítjük ugyan a leírható műveletek
körét, de amit így meg tudunk fogalmazni, az jóval tömörebb lesz (nem kell az implementációs
részletekkel foglalkozni), adatbázis-kezelő rendszerek között hordozható (független az
implementációtól), és mivel erre a szűkített eszközkészletre az adatbázis kezelő rendszer magasan
optimalizált végrehajtó motorral rendelkezik, ezért rendkívül hatékonyan hajtható végre.

Meg kell jegyeznünk, hogy magát a reláció algebrát közvetlenül már nem használják lekérdezések
megfogalmazására, erre manapság leginkább a Structured Query Language-et (SQL) [2] (5. fejezet)
alkalmazzák. Tudnunk kell viszont, hogy az SQL nyelv jelentős része nem más, mint egy „szintaktikus
édesítőszer” a relációalgebrai kifejezésekhez, hogy javítsa annak olvashatóságát. Másrészt az SQL
utasítások feldolgozásakor azok relációalgebrai – vagy ahhoz nagyon hasonló – reprezentációira
fordulnak le. A továbbiakban részletesen ismertetjük a relációs algebrai műveleteket.

2.2.1 Vetítés (projekció)

A vetítés során egy 𝑅 relációból egy olyan másik 𝑅’ relációt hozunk létre, amely az eredeti 𝑅 relációnak
csak bizonyos oszlopait tartalmazza. A műveletet a π operátorral jelöljük: a 𝜋𝐴1 ,𝐴2 ,…,𝐴𝑛 (𝑅) kifejezés
érteke tehát egy 𝑅’ reláció, amely az 𝑅 reláció 𝐴1 , 𝐴2 , … , 𝐴𝑛 oszlopait (attribútumait) tartalmazza. A
létrejött reláció sémája ennek megfelelően 𝑅 ′ (𝐴1 , 𝐴2, … , 𝐴𝑛 ). Az attribútumok pontos megnevezése
helyett alkalmazhatjuk azok 𝑅-relációbeli sorszámát is: 𝜋𝑖1 ,𝑖2 ,…,𝑖𝑛 . A keletkező reláció természetesen
nem lesz üres: oszlopai 𝑅 megfelelő oszlopainak értékeit fogják tartalmazni.

Tekintsük a 2.1. táblázatban ismertetett 𝑇𝑒𝑟𝑚é𝑘 relációt. Tegyük fel, hogy a termék tábla oszlopai
közül csak az egyszerű adatokat (𝑁é𝑣, Á𝑟, 𝑅𝑎𝑘𝑡á𝑟𝑘é𝑠𝑧𝑙𝑒𝑡, 𝐾𝑎𝑡𝑒𝑔ó𝑟𝑖𝑎) szeretnénk megjeleníteni, a
𝐺𝑦á𝑟𝑡ó𝑁é𝑣 mezőre pedig nincs szükségünk ehhez. Az irreleváns oszlopok kiszűréséhez használhatjuk
az alábbi projekciót:

𝜋𝑁é𝑣,Á𝑟,𝑅𝑎𝑘𝑡á𝑟𝑘é𝑠𝑧𝑙𝑒𝑡,𝐾𝑎𝑡𝑒𝑔ó𝑟𝑖𝑎 (𝑇𝑒𝑟𝑚é𝑘)

Az eredményül kapott relációt a 2.3. táblázat illusztrálja.

Név Ár Raktárkészlet Kategória


Kanapé 150.000 3 Nappali
Dohányzóasztal 12.000 3 Nappali
Szék 22.000 15 Iroda
Hintaágy 32.000 1 Kert

2.3. táblázat: Vetített Termék reláció

Azonos eredményre jutunk, ha az attribútumok nevei helyett azok sorszámát alkalmazzuk:

𝜋1,2,3,4 (𝑇𝑒𝑟𝑚é𝑘)

11
A fenti példában a forrás és az eredmény reláció számossága megegyezik, de ez nem feltétlenül lesz
mindig így. Tegyük fel, hogy a projekció során nem az első négy oszlopot akarjuk megtartani, csak a
𝐾𝑎𝑡𝑒𝑔ó𝑟𝑖á𝑡. Vagyis a 𝜋𝐾𝑎𝑡𝑒𝑔ó𝑟𝑖𝑎 (𝑇𝑒𝑟𝑚é𝑘) projekciót alkalmazzuk. Azt várhatnánk, hogy az eredmény
relációnak 4 sora lesz, amelyek a 4 terméknek megfelelő 1-1 kategóriát tartalmazzák. Ehelyett a
kategória attribútumra levetített relációnak csak három sora lesz (2.4. táblázat).

Kategória
Nappali
Iroda
Kert

2.4. táblázat: Számosság csökkenés vetítés során

Ennek oka, hogy a művelet eredménye szintén reláció, vagyis teljesítenie kell a relációk halmazoktól
örökölt azon tulajdonságát, hogy ugyanazon elem nem szerepelhet kétszer vagy többször ugyanabban
a halmazban. Következésképpen a „Nappali” kategória érték kettő helyett csak egyszer fog szerepelni
az eredményben.

2.2.1.1 Kiterjesztett vetítés


A vetítés operátor kiterjesztett változata nem csak attribútumokat képes kiválasztani, hanem akár
számított értékeket is: ilyenkor a 𝜋𝐿 (𝑅) kifejezés 𝐿 attribútum listájában az egyszerű attribútumokon
kívül 𝐸 → 𝑎 alakú kifejezéseket is felsorolhatunk, ahol i) 𝐸 egy létező attribútumot vagy egy olyan
kifejezést jelent, amiben létező attribútumokat aritmetikai / logikai / sztring stb. műveletekkel
kapcsolunk össze, ii) a pedig egy új attribútum nevet takar. Az ilyen kifejezések eredményeképp az
eredmény relációban a kapcsolódó oszlopban az E kifejezés aktuális sorra kiértékelt eredménye fog
megjelenni, az oszlop neve pedig 𝑎 lesz. Ha ki szeretnénk számolni a raktáron lévő termékek
összértékét (termékenként), akkor a 𝑅𝑎𝑘𝑡á𝑟𝑘é𝑠𝑧𝑙𝑒𝑡 ∗ Á𝑟 értéket szeretnénk látni minden egyes
sorban. Ez a

𝜋𝑁é𝑣,𝑅𝑎𝑘𝑡á𝑟𝑘é𝑠𝑧𝑙𝑒𝑡∗Á𝑟→Ö𝑠𝑠𝑧𝑒𝑔 (𝑇𝑒𝑟𝑚é𝑘)

lekérdezéssel írható le, eredménye pedig a 2.5. táblázatban látható.

Név Összeg
Kanapé 450.000
Dohányzóasztal 36.000
Szék 330.000
Hintaágy 32.000

2.5. táblázat: Termékenkénti összérték

Az eredmény relációnak két attribútuma lesz: 𝑁é𝑣 és Ö𝑠𝑠𝑧𝑒𝑔. A 𝑁é𝑣 attribútum értéke minden egyes
n-esre megegyezik a forrás n-esek 𝑁é𝑣 attribútum értékével, az Ö𝑠𝑠𝑧𝑒𝑔 attribútum értéke pedig a
𝑅𝑎𝑘𝑡á𝑟𝑘é𝑠𝑧𝑙𝑒𝑡 ∗ Á𝑟 szorzat eredményeképp fog előállni minden egyes sorra.

2.2.2 Kiválasztás (szelekció)

A kiválasztás operátor 𝑅 reláción történő alkalmazásával egy olyan új 𝑅’ reláció jön létre, amelynek
sémája megegyezik az 𝑅 reláció sémájával, viszont sorainak csak egy részhalmazát fogja tartalmazni.

12
Azt, hogy pontosan melyik sorok fognak megmaradni, azt a kiválasztáshoz rendelt C feltétel fogja
meghatározni. Az operátort a 𝜎𝐶 (𝑅) – kifejezéssel jelöljük.

A C feltétel egy logikai igaz/hamisra kiértékelhető kifejezést kell, hogy tartalmazzon, amely a reláció
egyes sorain értékelődik ki. A feltétel hivatkozhat a reláció egyes sorainak attribútum értékeire,
konstansokra, és ezeket tetszőleges aritmetikai (pl. +, −,∗,/ stb), relációs (>,<…) és logikai (¬,∧,∨)
operátorokkal kapcsolhatja össze. A 𝜎𝐶 (𝑅) művelet alkalmazása során a C feltétel az R reláció minden
egyes sorára kiértékelődik, és az eredmény relációba csak azok a sorok fognak belekerülni, amelyek a
feltételt kielégítik.

Tekintsük a 2.1. táblázatban ismertetett 𝑇𝑒𝑟𝑚é𝑘 relációt. A 𝜎Á𝑟>30000 (𝑇𝑒𝑟𝑚é𝑘) kifejezés a 30.000 Ft-
nál drágább termékekből álló relációt fogja eredményezni: az Ár > 30000 Feltétel a 𝑇𝑒𝑟𝑚é𝑘 reláció
valamennyi sorára kiértékelődik, de csak az első és negyedik n-es fogja azt kielégíteni. A művelet
eredménye a 2.6. táblázatban látható.

Név Ár Raktárkészlet Kategória GyártóNév


Kanapé 150.000 3 Nappali Nagy Bútor
Kft
Hintaágy 32.000 1 Kert Bútorgyár

2.6. táblázat: A 30.000 Ft-nál drágább termékek kiválasztása

Természetesen jóval bonyolultabb feltételeket is megfogalmazhatunk, amelyek az adott n-es több


attribútumát is figyelembe veszik valamilyen módon. Például ha a 30.000 Ft-nál drágább ÉS raktáron
lévő termékeket szeretnénk kiválasztani, azt a következő kifejezéssel tehetjük meg:
𝜎Á𝑟>30000∧ 𝑅𝑎𝑘𝑡á𝑟𝑘é𝑠𝑧𝑙𝑒𝑡>0 (2.7. táblázat).

Név Ár Raktárkészlet Kategória GyártóNév


Hintaágy 32.000 1 Kert Bútorgyár

2.7. táblázat: A 30.000 Ft-nál drágább raktáron lévő termékek

A különböző műveleteket egymásba is ágyazhatjuk, hiszen egy relációból egy másik relációt állítanak
elő. Például egy szelektált reláción végrehajthatunk vetítést, vagy egy vetített reláción szelekciót.
Például a 30.000 Ft-nál olcsóbb termékek nevét a közvetkező kifejezés adja meg:
𝜋𝑁é𝑣 (𝜎Á𝑟<30000 (𝑇𝑒𝑟𝑚é𝑘)). Hasonlóképpen alkalmazhatunk akár egymás után több kiválasztás és
szelekció műveletet is.

Fontos azonban megjegyezni, hogy a kiválasztás és vetítés műveletek általában nem felcserélhetők
(nem kommutatívak). A 𝜋𝑁é𝑣 (𝜎Á𝑟<30000 (𝑇𝑒𝑟𝑚é𝑘)) művelet eredménye a 30.000 Ft-nál olcsóbb
termékek neve. A 𝜎Á𝑟<30000 (𝜋𝑁é𝑣 (𝑇𝑒𝑟𝑚é𝑘)) kifejezés viszont hibás, hiszen a 𝜋𝑁é𝑣 (𝑇𝑒𝑟𝑚é𝑘) művelet
eredményének sémája egyetlen Név attribútummal rendelkezik, amelyen az Ár < 30000 feltétel nem
értelmezhető. Két vetítés művelet viszont mindig felcserélhető: a végeredmény szempontjából
indifferens, hogy melyik oszlopokat hagyjuk el előbb, illetve később, végül csak azok az attribútumok
maradnak, amelyeket mindkét vetítés művelet meghagyja. Általánosan:

𝜋𝐴1 ..𝐴𝑛 (𝜋𝐵1 ..𝐵𝑛 (𝑅)) = 𝜋𝐵1 ..𝐵𝑛 (𝜋𝐴1 ..𝐴𝑛 (𝑅)) = 𝜋{𝐴1 ..𝐴𝑛 }∩{𝐵1 ..𝐵𝑛 } (𝑅)

13
Hasonlóképpen két kiválasztás művelet is mindig felcserélhető: az eredmény relációban csak azok a
sorok fognak megmaradni, amelyek mindkét feltételt kielégítik. Általánosan:

𝜎𝐶 (𝜎𝐶 ′ (𝑅)) = 𝜎𝐶 ′ (𝜎𝐶 (𝑅)) = 𝜎𝐶∧𝐶 ′ (𝑅)

2.2.3 Halmazműveletek

Mivel a relációk tulajdonképpen halmazok, ezért értelmezettek rajtuk az alapvető halmazműveletek:


az unió, a metszet és a különbség. Ahhoz, hogy ezeket a műveleteket két reláción alkalmazni tudjuk,
néhány feltételnek teljesülnie kell: (i) R és S relációknak azonos attribútumokkal kell rendelkezniük, (ii)
amelyeknek megfelelő oszlopok azonos sorrendben kell, hogy szerepeljenek a két relációban.
Amennyiben (i) nem teljesül, viszont a két reláció azonos számú attribútummal rendelkezik, amelyek
típusa páronként megegyezik, nevük viszont különböző, akkor a 2.2.7 fejezetben ismertetett átnevezés
operátort használhatjuk a nevek egyeztetésére. (ii) nem teljesülése esetén pedig a megfelelő
oszlopokat egyszerűen átsorrendezhetjük. A relációkon értelmezett halmazműveletek:

 R és S relációk uniója (𝑅 ∪ 𝑆) egy olyan reláció, amelynek sémája megegyezik R és S sémájával,


és R és S valamennyi sorát tartalmazza, de az egyező sorokat csak egyszer.
 R és S relációk metszete (𝑅 ∩ 𝑆) egy olyan reláció, amelynek sémája megegyezik R és S
sémájával, és R és S sorai közül csak azokat tartalmazza, amelyek mindkettőben szerepelnek.
 𝑅 és 𝑆 relációk különbségének (𝑅 − 𝑆) sémája megegyezik 𝑅 és 𝑆 sémájával, és 𝑅 sorai közül
csak azokat tartalmazza, amelyek nem szerepelnek 𝑆 reláció sorai között.
o Két reláció metszete kifejezhető különbségek segítségével is: 𝑅 ∩ 𝑆 = 𝑅 − (𝑅 − 𝑆),
vagyis az egyik relációból eltávolítjuk azokat az elemeket, amelyek a másikban nem
szerepelnek, így csak azok maradnak, amelyek mindkettőben jelen vannak.

Értelemszerűen, míg az unió és metszet műveletek kommutatívak (𝑅 ∪ 𝑆 = 𝑆 ∪ 𝑅 és 𝑅 ∩ 𝑆 = 𝑆 ∩ 𝑅),


addig a különbség operátor nem az (𝑅 − 𝑆 ≠ 𝑆 − 𝑅).

Tételezzük fel a 2.8. táblázatban ismertetett Termék1 és Termék2 relációkat.

14
Név Ár Raktárkészlet Kategória GyártóNév
Kanapé 150.000 3 Nappali Nagy Bútor
Kft
Dohányzóasztal 12.000 3 Nappali Nagy Bútor
Kft
Szék 22.000 15 Iroda Fabric Rt

Termék1 reláció

Név Ár Raktárkészlet Kategória GyártóNév


Szék 22.000 15 Iroda Fabric Rt
Hintaágy 32.000 1 Kert Bútorgyár

Termék2 reláció
2.8. táblázat: Termék1 és Termék2 relációk

A Termék1 és Termék2 relációk uniója (𝑇𝑒𝑟𝑚é𝑘1 ∪ 𝑇𝑒𝑟𝑚é𝑘2) a 2.9. táblázatban látható. Az unió
definíciójának megfelelően a 𝑆𝑧é𝑘 termék csak egyszer fog szerepelni az eredményben, bár mind
Termék1-ben, mind Termék2-ben szerepelt.

Név Ár Raktárkészlet Kategória GyártóNév


Kanapé 150.000 3 Nappali Nagy Bútor Kft
Dohányzóasztal 12.000 3 Nappali Nagy Bútor Kft
Szék 22.000 15 Iroda Fabric Rt
Hintaágy 32.000 1 Kert Bútorgyár

2.9. táblázat: Termék1 ∪ Termék2

A Termék1 és Termék2 relációk metszete (𝑇𝑒𝑟𝑚é𝑘1 ∩ 𝑇𝑒𝑟𝑚é𝑘2) azokat a termékeket tartalmazza,


amelyek mindkét relációban szerepelnek (2.10. táblázat). Ennek a feltételnek csak a 𝑆𝑧é𝑘 fog
megfelelni.

Név Ár Raktárkészlet Kategória GyártóNév


Szék 22.000 15 Iroda Fabric Rt

2.10. táblázat: Termék1 ∩ Termék2

A két reláció különbségében (Termék1 − Termék2) pedig azok az elemek fognak megjelenni, amelyek
megtalálhatók Termék1-ben, de hiányoznak Termék2-ből (2.11. táblázat).

Név Ár Raktárkészlet Kategória GyártóNév


Kanapé 150.000 3 Nappali Nagy Bútor Kft
Dohányzóasztal 12.000 3 Nappali Nagy Bútor Kft

2.11. táblázat: Termék1 - Termék2

Tegyük fel, hogy azon termékek listáját szeretnénk megkapni, amelyeket a nappaliba ajánlanak, és
100.000 Ft-nál olcsóbbak. A kívánt eredményt többféleképpen is kiszámolhatjuk:

1) Kiválaszthatjuk a nappaliba szánt termékeket egy szelekció művelettel, egy másik szelekcióval
kiválaszthatjuk a 100.000 Ft-nál olcsóbb terméket, majd képezhetjük a két reláció metszetét:
𝜎𝐾𝑎𝑡𝑒𝑔ó𝑟𝑖𝑎=′ 𝑁𝑎𝑝𝑝𝑎𝑙𝑖′ (𝑇𝑒𝑟𝑚é𝑘) ∩ 𝜎Á𝑟<100.000 (𝑇𝑒𝑟𝑚é𝑘)

15
2) Azonos eredményre jutunk akkor is, ha a Termék reláción egy szelekció operátort alkalmazunk,
amelynek a feltétel kifejezése a két kritérium ÉS kapcsolatát (konjugáltját) tartalmazza:
𝜎𝐾𝑎𝑡𝑒𝑔ó𝑟𝑖𝑎=′ 𝑁𝑎𝑝𝑝𝑎𝑙𝑖′ ∧Á𝑟<100.000 (𝑇𝑒𝑟𝑚é𝑘)

Számos adatbázis-kezelő rendszer rendelkezik egy, a relációs algebrai kifejezéseihez hasonló lekérdező
nyelvvel. Amint ebből az egyszerű példából is látható, ugyanazt a lekérdezést gyakran nagyon
sokféleképpen tudjuk megfogalmazni. Bár a végeredményük ugyanaz, akár nagyságrendi különbség is
lehet a különböző lekérdezések végrehajtásához szükséges számítási idő között. Az ilyen
rendszerekben található lekérdezés optimalizáló modul feladata a lekérdezést olyan formára hozni,
hogy az hatékonyan végrehajtható legyen.

2.2.4 Keresztszorzat

A és B halmazok keresztszorzata (vagy egyszerűen csak szorzata) alatt egy olyan új halmazt értünk,
amelynek elemei rendezett párosok: a pár első eleme az A, míg második eleme a B halmaz elemei
közül kerül ki. A keresztszorzat az összes ilyen lehetséges párost tartalmazza. A keresztszorzatot az 𝐴 ×
𝐵 kifejezéssel jelöljük.

Amikor általános halmazok helyett relációkat szorzunk, ugyanígy kell eljárnunk: R és S relációk
keresztszorzata szintén rendezett párosokból fog állni, ahol a párok első fele az R reláció n-esei közül
kerül ki, míg a másik fele az S reláció n-esei közül. Mivel az n-esek tipikusan nem egy attribútum értéket
tartalmaznak, hanem egyszerre többet, ezért a párok is tulajdonképpen nem érték-párosok lesznek,
hanem tetszőleges számú attribútum értékből álló n-esek. Az így előálló új reláció sémája R és S
sémájának egymás után illesztésével áll elő: 𝑅(𝐴1 , … , 𝐴𝑛 ), illetve 𝑆(𝐵1 , … , 𝐵𝑛 ) relációs sémák esetén
𝑅 × 𝑆(𝐴1 , … , 𝐴𝑛 , 𝐵1 , … , 𝐵𝑛 ) lesz az eredmény reláció sémája. Amennyiben az eredeti két reláció
attribútumai között található azonos nevű, akkor vagy a 2.2.7-ben ismertetett attribútum átnevezést
használjuk az elnevezések egyértelműsítésére, vagy a forrás relációk neveivel egyértelműsítjük az
attribútumokat, pl 𝑅. 𝐴𝑥 , 𝑆. 𝐴𝑥 .

Tekintsük a 2.1. táblázatban bemutatott termék listát, valamint a 2.2. táblázatban felsorolt gyártói
listát. A 𝑇𝑒𝑟𝑚é𝑘 × 𝐺𝑦á𝑟𝑡ó reláció sémája tehát a két kiindulási reláció attribútumainak összességéből
áll elő:

𝑇𝑒𝑟𝑚é𝑘 × 𝐺𝑦á𝑟𝑡ó(𝑁é𝑣, Á𝑟, 𝑅𝑎𝑘𝑡á𝑟𝑘é𝑠𝑧𝑙𝑒𝑡, 𝐾𝑎𝑡𝑒𝑔ó𝑟𝑖𝑎, 𝑇𝑒𝑟𝑚é𝑘. 𝐺𝑦á𝑟𝑡ó𝑁é𝑣, 𝐺𝑦á𝑟𝑡ó. 𝐺𝑦á𝑟𝑡ó𝑁é𝑣, 𝐶í𝑚, 𝐸𝑚𝑎𝑖𝑙)

A reláció sorait pedig a két kiindulási reláció n-eseiből álló párosok alkotják (2.12. táblázat).

16
Raktár- Termék. Gyártó.
Név Ár Kategória Cím Email
készlet GyártóNév GyártóNév
Kanapé 150.000 3 Nappali Nagy Bútor Kft Nagy Bútor Kft 5000 Szolnok Fa u 2 nb@example.org
Dohányzóasztal 12.000 3 Nappali Nagy Bútor Kft Nagy Bútor Kft 5000 Szolnok Fa u 2 nb@example.org
Szék 22.000 15 Iroda Fabric Rt Nagy Bútor Kft 5000 Szolnok Fa u 2 nb@example.org
Hintaágy 32.000 1 Kert Bútorgyár Nagy Bútor Kft 5000 Szolnok Fa u 2 nb@example.org
Kanapé 150.000 3 Nappali Nagy Bútor Kft Fabric Rt 9012 Győr Ág u 15 fabric@example.org
Dohányzóasztal 12.000 3 Nappali Nagy Bútor Kft Fabric Rt 9012 Győr Ág u 15 fabric@example.org
Szék 22.000 15 Iroda Fabric Rt Fabric Rt 9012 Győr Ág u 15 fabric@example.org
Hintaágy 32.000 1 Kert Bútorgyár Fabric Rt 9012 Győr Ág u 15 fabric@example.org
Kanapé 150.000 3 Nappali Nagy Bútor Kft Bútorgyár 1111 Bp. Hős krt 3. xyb@example.org
Dohányzóasztal 12.000 3 Nappali Nagy Bútor Kft Bútorgyár 1111 Bp. Hős krt 3. xyb@example.org
Szék 22.000 15 Iroda Fabric Rt Bútorgyár 1111 Bp. Hős krt 3. xyb@example.org
Hintaágy 32.000 1 Kert Bútorgyár Bútorgyár 1111 Bp. Hős krt 3. xyb@example.org

2.12. táblázat: Termék×Gyártó

Bár első ránézésre a keresztszorzat művelet gyakorlati szempontból nem tűnik túl hasznosnak, számos
bonyolultabb illesztési művelet (2.2.5, 2.2.6, 2.2.8) kiindulási alapjául szolgál.

2.2.5 Természetes illesztés

Ahogyan 2.2.4-ben is láttuk, két reláció n-eseit a keresztszorzat művelet segítségével egyszerűen
kapcsolatba tudjuk egymással hozni, bár az eredmény nem sok hasznos információval szolgál, mivel az
egyik reláció minden egyes n-esét a másik reláció minden egyes n-esével összepárosítja. Ezek közül
kellene nekünk kiszűrni azokat a párosításokat, amelyek valamilyen módon összetartoznak. A 2.12.
táblázatban látott relációban például azok az érdekes párosítások, ahol a termék és annak gyártója
található egymás mellett, vagyis a Termék. GyártóNév attribútum értéke megegyezik a
Gyártó. GyártóNév attribútum értékével (2.13. táblázat).
Raktár- Termék. Gyártó.
Név Ár Kategória Cím Email
készlet GyártóNév GyártóNév
Dohányzóasztal 12.000 3 Nappali Nagy Bútor Kft Nagy Bútor Kft 5000 Szolnok Fa u 2 nb@example.org
Kanapé 150.000 3 Nappali Nagy Bútor Kft Nagy Bútor Kft 5000 Szolnok Fa u 2 nb@example.org
Szék 22.000 15 Iroda Fabric Rt Fabric Rt 9012 Győr Ág u 15 fabric@example.org
Hintaágy 32.000 1 Kert Bútorgyár Bútorgyár 1111 Bp. Hős krt 3. xyb@example.org

2.13. táblázat: Termék×Gyártó „összeillő” n-esei

Amikor két reláció n-eseit csak akkor párosítjuk össze, ha azok azonos (nevű és típusú) attribútumai
azonos értékkel rendelkeznek, a párosítást természetes illesztésnek nevezzük, és a ⋈ operátorral
jelöljük: 𝑇𝑒𝑟𝑚é𝑘 ⋈ 𝐺𝑦á𝑟𝑡ó.

Precízebben megfogalmazva: legyen R és S két reláció, és 𝐴1 , … , 𝐴𝑛 (n > 0) az összes olyan


attribútum, amelyek mind R, mind S sémájában megtalálhatók. Az 𝑅 ⋈ 𝑆relációban azok és csak azok
az 𝑟 ∈ 𝑅, 𝑠 ∈ 𝑆 párosítások találhatók meg, amelyekre igaz, hogy mind r mind s minden egyes
𝐴1 , … , 𝐴𝑛 attribútumra azonos értékkel rendelkezik. Az eredmény reláció sémája az illesztett relációk
sémájának uniójaként fog előállni: tehát azok az attribútumok, amelyek mindkét relációban
szerepelnek, az eredményben csak egyszer fognak. Az ennek megfelelő eredmény reláció látható a
2.14. táblázatban.

17
Raktár-
Név Ár Kategória GyártóNév Cím Email
készlet
Dohányzóasztal 12.000 3 Nappali Nagy Bútor Kft 5000 Szolnok Fa u 2 nb@example.org
Kanapé 150.000 3 Nappali Nagy Bútor Kft 5000 Szolnok Fa u 2 nb@example.org
Szék 22.000 15 Iroda Fabric Rt 9012 Győr Ág u 15 fabric@example.org
Hintaágy 32.000 1 Kert Bútorgyár 1111 Bp. Hős krt 3. xyb@example.org

2.14. táblázat: Termék⋈Gyártó

2.2.6 Theta-illesztés

A természetes illesztés esetén két relációt az egyező nevű attribútumok mentén illesztettünk össze. De
sokszor ez túl szigorú megkötés. Gondoljunk csak bele, hogy ha a Gyártó relációban a gyártó nevét
nem GyártóNév-nek neveztük volna el, hanem egyszerűen Névnek (mint ahogy a termék nevét is
egyszerűen egy Név attribútum jelöli), akkor a két reláció természetes illesztése során a Termék. Név
és a Gyártó. Név attribútum értékek mentén párosítottuk volna össze a két reláció n-eseit. Ez jelen
példában egyetlen sort sem eredményezett volna, de általános esetben is hibás eredményt adna.
További probléma, hogy az egyszerű egyenlőség vizsgálat két attribútum között nem feltétlen
elégséges minden esetben. A bemutatott problémákra nyújt megoldást a theta-illesztés, amely
tetszőleges feltétel mentén történő párosítást tesz lehetővé.

Az R és S relációk theta-illesztését az 𝑅 ⋈𝐶 𝑆 kifejezéssel írhatjuk le, ahol C az illesztési feltétel. A


theta-illesztés során a két reláció keresztszorzatából kiindulva azokat a párosításokat hagyjuk meg,
ahol a 𝐶 feltétel teljesül, vagyis 𝑅 ⋈𝐶 𝑆 ≡ 𝜎𝐶 (𝑅 × 𝑆). A keresztszorzathoz hasonlóan, az eredmény
reláció sémája az 𝑅 és 𝑆 relációk sémájának uniójaként áll elő. Attribútum név ütközés esetén a
relációnévvel prefixálhatjuk az egyező nevű attribútumokat.

A 2.1. táblázatban és 2.2. táblázatban látható Termék és Gyártó relációkat a gyártó neve szerint akár
theta-illesztéssel is összekapcsolhatjuk:

𝑇𝑒𝑟𝑚é𝑘 ⋈𝑇𝑒𝑟𝑚é𝑘.𝐺𝑦á𝑟𝑡ó𝑁é𝑣=𝐺𝑦á𝑟𝑡ó.𝐺𝑦á𝑟𝑡ó𝑁é𝑣 𝐺𝑦á𝑟𝑡ó

Az eredmény kissé különböző lesz, hiszen a theta-illesztés nem távolítja el az egyező attribútumokat.

Míg természetes illesztésnél a mindkét relációban megtalálható attribútumok a párosításoknál


mindkét illesztett n-esre ugyanazzal az értékkel rendelkeznek, theta-illesztésnél ez nem feltétlen van
így. Ahogy említettük, az illesztési feltételben nem csak attribútum egyezőséget lehet vizsgálni, hanem
tetszőleges logikai feltételt ki lehet értékelni a párosított n-eseken. Például ha egy másik relációban
eltároljuk az értéktől függő (tól-ig) szállítási költségeket, akkor egy theta-illesztéssel egyszerűen
meghatározhatjuk minden egyes termékre annak kiszállítási költségét. A szállítási költségeket az
SzKöltség relációban tároljuk (2.15. táblázat).

MinÖsszeg MaxÖsszeg Költség


0 10.000 2.500
10.001 20.000 2.000
20.001 50.000 1.500
50.001 9.999.999 1.000

2.15. táblázat: Szállítási költségek az SzKöltség relációban

Az egyes termékekhez a szállítási költséget a következő illesztéssel kaphatjuk meg:

18
𝑇𝑒𝑟𝑚é𝑘 ⋈Á𝑟≥𝑀𝑖𝑛Ö𝑠𝑠𝑧𝑒𝑔∧Á𝑟≤𝑀𝑎𝑥Ö𝑠𝑠𝑧𝑒𝑔 𝑆𝑧𝐾ö𝑙𝑡𝑠é𝑔

Vagyis az egyes termékekhez azokat a szállítási költség n-eseket illesztjük hozzá, amelyekre igaz, hogy
a termék ára a szállítási költség minimum és maximum értéke közé esik. Mivel a minimum-maximum
intervallumok diszjunktak, és az utolsó praktikusan nagy, ezért minden egyes termékhez garantáltan
egy illesztett n-es lesz az SzKöltség táblában (2.16. táblázat).

Név Ár Raktárkészlet Kategória GyártóNév MinÖsszeg MaxÖsszeg Költség


Kanapé 150.000 3 Nappali Nagy Bútor 50.001 9.999.999 1.000
Kft
Dohányzóasztal 12.000 3 Nappali Nagy Bútor 10.001 20.000 2.000
Kft
Szék 22.000 15 Iroda Fabric Rt 20.001 50.000 1.500
Hintaágy 32.000 1 Kert Bútorgyár 20.001 50.000 1.500

2.16. táblázat: Termékek és a kapcsolódó szállítási költségek

Ha az eredmény túl terjengős, és számunkra például csak a termék neve, ára és szállítási költsége
érdekes, akkor egy projekcióval tovább szűkíthetjük az eredményt:

𝜋𝑁é𝑣,Á𝑟,𝐾ö𝑙𝑡𝑠é𝑔 (𝑇𝑒𝑟𝑚é𝑘 ⋈Á𝑟≥𝑀𝑖𝑛Ö𝑠𝑠𝑧𝑒𝑔∧Á𝑟≤𝑀𝑎𝑥Ö𝑠𝑠𝑧𝑒𝑔 𝑆𝑧𝐾ö𝑙𝑡𝑠é𝑔)

Ha ezek közül is csak a 100.000 Ft-nál olcsóbb termékeket szeretnénk látni, akkor egy szelekciót kell
alkalmaznunk (jelen esetben mindegy, hogy a projekció előtt vagy után, hiszen a termék ára a projekció
eredményében is szerepel):

𝜋𝑁é𝑣,Á𝑟,𝐾ö𝑙𝑡𝑠é𝑔 (𝜎Á𝑟<100.000 (𝑇𝑒𝑟𝑚é𝑘 ⋈Á𝑟≥𝑀𝑖𝑛Ö𝑠𝑠𝑧𝑒𝑔∧Á𝑟≤𝑀𝑎𝑥Ö𝑠𝑠𝑧𝑒𝑔 𝑆𝑧𝐾ö𝑙𝑡𝑠é𝑔))

A fenti lekérdezés eredménye a 2.17. táblázatban látható.

Név Ár Költség
Dohányzóasztal 12.000 2.000
Szék 22.000 1.500
Hintaágy 32.000 1.500

2.17. táblázat: 100.000 Ft-nál olcsóbb termékek szállítási költséggel

2.2.7 Átnevezés

A keresztszorzat (2.2.4) műveletnél már láttuk, hogy azonos nevű attribútumokat tartalmazó relációk
szorzásánál az eredményben nem egyértelmű, hogy melyik attribútum melyik relációból származik (és
ezáltal, hogy pontosan mi a jelentése), ezért név ütközés esetén az attribútumokat a forrás reláció
nevével prefixáltuk (Termék.GyártóNév, Gyártó. GyártóNév). Hasznos lett volna, ha a szorzás előtt
vagy után át tudtuk volna nevezni az ütköző attribútumokat a könnyebb megkülönböztethetőség
érdekében.

Ugyanígy szükségünk lehet átnevezésekre, ha például egy relációt saját magával szeretnénk theta-
illeszteni. Ilyenkor az illesztés operátor két oldalán ugyanaz a reláció szerepel, az illesztési feltételben
nem tudunk köztük különbséget tenni, hiszen azonos a nevük. Adott a 2.18. táblázatban látható

19
Személy reláció. A reláció minden egyes sora egy természetes személy adatait tartalmazza: a személyi
igazolvány számát (SzigSzám), a nevét, illetve az apja és az anyja személyi igazolvány számát
(amennyiben ismert). Tegyük fel, hogy ki akarjuk deríteni, hogy mi a neve a Kovács Máté nevű személy
apjának. Ehhez keresni kell egy olyan személyt, akinek a személyi igazolvány száma egyezik a Kovács
Mátéhoz tartozó n-esben az Apa attribútumhoz tárolt értékkel.

SzigSzám Név Apa Anya


999999PQ Kovács Máté 777777AB 666666CD
777777AB Kovács Péter 444444AA 333333JH
888888GT Kovács Éva 777777AB 666666CD
666666CD Nagy Éva 555555HG 222222MU
555555HG Nagy Béla 111111MN 000000HB

2.18. táblázat: Személy reláció

Vagyis a Személy relációt saját magával kell illeszteni úgy, hogy az egyik példány n-eseinek SzigSzám
attribútum értékeit a másik példány n-eseinek Apa attribútum értékeivel egyeztetjük. Természetesen
a 𝑆𝑧𝑖𝑔𝑆𝑧á𝑚 = 𝐴𝑝𝑎 feltétel egy theta-illesztésnél nem egyértelmű, hasonlóképpen a
𝑆𝑧𝑒𝑚é𝑙𝑦. 𝑆𝑧𝑖𝑔𝑆𝑧á𝑚 = 𝑆𝑧𝑒𝑚é𝑙𝑦. 𝐴𝑝𝑎 feltétel sem, mivel az egyenlőség két oldalának a két különböző
relációra kellene hivatkoznia, de mindkettő neve Személy ebben az esetben.

Többek között a fent vázolt problémákra nyújt megoldást az átnevezés operátor, amellyel egy reláció
attribútumait és magát a relációt is átnevezhetjük. Jelölése: 𝜌𝑆(𝐴1 ,…,𝐴𝑛 ) (𝑅). A művelet eredménye egy
olyan új reláció, amelynek a neve S, attribútumai neve 𝐴1 , … , 𝐴𝑛 . Az attribútumok száma megegyezik
az R reláció attribútumainak számával, az új reláció n-esei is ugyanazok, mint az R relációé. Ha a reláció
attribútumait nem, csak magát a relációt szeretnénk átnevezni, akkor használhatjuk az egyszerűsített
𝜌𝑆 (𝑅) jelölést. Ez utóbbi jelölési módot felhasználva a példában említett SzigSzám = Apa illesztést a
következőképpen fogalmazhatjuk meg:

𝑆𝑧𝑒𝑚é𝑙𝑦 ⋈𝑆𝑧𝑒𝑚é𝑙𝑦.𝐴𝑝𝑎=𝑆𝑧ü𝑙ő.𝑆𝑧𝑖𝑔𝑆𝑧á𝑚 𝜌𝑆𝑧ü𝑙ő (𝑆𝑧𝑒𝑚é𝑙𝑦)

Tehát a Személy relációról készítünk Szülő néven egy másolatot, így a theta-illesztés feltételében már
egyértelműen azonosíthatjuk az operátor két oldalán lévő relációkat. Az illesztés eredménye a 2.19.
táblázatban látható.

Személy. Személy. Személy. Személy. Szülő. Szülő.


Szülő. Név Szülő. Apa
SzigSzám Név Apa Anya SzigSzám Anya
999999PQ Kovács Máté 777777AB 666666CD 777777AB Kovács Péter 444444AA 333333JH
888888GT Kovács Éva 777777AB 666666CD 777777AB Kovács Péter 444444AA 333333JH
666666CD Nagy Éva 555555HG 222222MU 555555HG Nagy Béla 111111MN 000000HB

2.19. táblázat: Személy és Szülő relációk illesztése az apa személyi igazolvány száma szerint

A teljes feladat megoldása ezek után (Kovács Máté apjának neve):

𝜋𝑆𝑧ü𝑙ő.𝑁é𝑣 (𝜎𝑆𝑧𝑒𝑚é𝑙𝑦.𝑁é𝑣=′ 𝐾𝑜𝑣á𝑐𝑠 𝑀á𝑡é′ (𝑆𝑧𝑒𝑚é𝑙𝑦 ⋈𝑆𝑧𝑒𝑚é𝑙𝑦.𝐴𝑝𝑎=𝑆𝑧ü𝑙ő.𝑆𝑧𝑖𝑔𝑆𝑧á𝑚 𝜌𝑆𝑧ü𝑙ő (𝑆𝑧𝑒𝑚é𝑙𝑦)))

Vagyis az illesztés után egy szelekcióval kiválasztjuk azt (azokat) a sorokat, ahol a Személy (gyerek)
neve „Kovács Máté” (az első sor), majd egy vetítéssel a talált sorokból kiválasztjuk azt a Név
attribútumot, ami a Szülő relációból származik (2.20. táblázat).

20
Szülő. Név
Kovács Péter

2.20. táblázat: Kovács Máté apjának neve

Megjegyezzük, hogy a 2.2.1.1 fejezetben ismertetett kiterjesztett vetítés is alkalmazható egyes


attribútumok átnevezésére, amennyiben az 𝐸 → 𝑎 kifejezés bal oldalán egy egyszerű attribútum
szerepel.

2.2.8 Külső illesztés

Két reláció illesztése során (akár természetes, akár theta) azok az n-es párok maradnak csak meg,
amelyek „összeillenek” valamilyen feltétel szerint. Azok az n-esek, amelyekhez a másik relációban nem
található egyetlen megfelelő párosítás sem, az eredményben semmilyen módon nem reprezentálják
magukat. Ez sokszor megfelelő számunkra, de vannak esetek, amikor ezekre a n-esekre is szükségünk
van, vagy épp pont csak a párosítatlan n-esekre lenne szükségünk. Például a 2.18. táblázatban
ismertetett reláción egy olyan kérdést, hogy „hogy hívják a rendszerben tárolt személyeket, és
amennyiben ismert, az anyjukat” az eddig tanult illesztésekkel nem tudjuk megválaszolni, hiszen az
eredménybe csak azok a személyek kerülnek bele, akiknek az anyja is szerepel a relációban. A
különböző külső illesztések a fenti problémát célozzák megoldani úgy, hogy az eredmény relációba a
párosításokon túl beleveszik a párosítatlan n-eseket is az egyik/másik/mindkét relációból.

2.2.8.1 Teljes külső theta-illesztés



Az R és S relációk külső (vagy teljes külső) theta-illesztését 𝑅⋈ 𝐶 𝑆 –sel jelöljük, eredménye pedig az
𝑅 ⋈𝐶 𝑆 theta-illesztés kiegészítve R és S párosítatlan n-eseivel úgy, hogy a párosítatlan n-eseket egy
speciális üres (⊥ , null) szimbólummal töltjük fel teljes párosítássá.

Tekintsük a 2.18. táblázatban ismertetett relációt. Illesszük teljes külső illesztéssel saját magához a
𝑆𝑧𝑒𝑚é𝑙𝑦. 𝐴𝑝𝑎 = 𝑆𝑧ü𝑙ő. 𝑆𝑧𝑖𝑔𝑆𝑧á𝑚 feltétel segítségével 𝜌𝑆𝑧𝑒𝑚é𝑙𝑦 (𝑆𝑧ü𝑙ő) átnevezés után:

𝑆𝑧𝑒𝑚é𝑙𝑦⋈∘ 𝑆𝑧𝑒𝑚é𝑙𝑦.𝐴𝑝𝑎=𝑆𝑧ü𝑙ő.𝑆𝑧𝑖𝑔𝑆𝑧á𝑚 𝜌𝑆𝑧ü𝑙ő (𝑆𝑧𝑒𝑚é𝑙𝑦)

A művelet eredménye definíció szerint a theta-illesztés (2.19. táblázat) kiegészítve mindkét reláció
párosítatlan n-eseivel (2.21. táblázat).

Személy. Személy. Személy. Szülő. Szülő.


Személy. Név Szülő. Név Szülő. Apa
SzigSzám Apa Anya SzigSzám Anya
777777AB Kovács Péter 444444AA 333333JH ⊥ ⊥ ⊥ ⊥
555555HG Nagy Béla 111111MN 000000HB ⊥ ⊥ ⊥ ⊥
999999PQ Kovács Máté 777777AB 666666CD 777777AB Kovács Péter 444444AA 333333JH
888888GT Kovács Éva 777777AB 666666CD 777777AB Kovács Péter 444444AA 333333JH
666666CD Nagy Éva 555555HG 222222MU 555555HG Nagy Béla 111111MN 000000HB
⊥ ⊥ ⊥ ⊥ 999999PQ Kovács Máté 777777AB 666666CD
⊥ ⊥ ⊥ ⊥ 888888GT Kovács Éva 777777AB 666666CD
⊥ ⊥ ⊥ ⊥ 666666CD Nagy Éva 555555HG 222222MU

2.21. táblázat: Személy és Szülő relációk külső illesztése az apa személyi igazolvány száma szerint

Mivel Kovács Péter és Nagy Béla apja nem szerepel a Személy relációban, ezért az ő n-eseik jobb oldalát
üres szimbólumokkal egészítjük ki teljes párosításokká (a táblázat első két sora). Hasonlóképpen, mivel

21
Kovács Máté, Kovács Éva és Nagy Éva senkinek nem az apja (nincs a Személy relációban olyan n-es,
aminek az Apa attribútum értéke egyezne az ő SzigSzám attribútum értékükkel), azért a kapcsolódó
n-esek bal oldalát töltjük fel üres szimbólumokkal, hogy teljes párosításokat kapjunk (utolsó három
sor).

2.2.8.2 Bal és jobb oldali külső theta-illesztés



A bal oldali külső theta-illesztés (az illesztés jel után egy L betűvel jelöljük: 𝑅⋈ 𝐿 𝐶 𝑆) abban különbözik
a teljes külső illesztéstől, hogy a theta-illesztéshez csak a bal oldali reláció (𝑅) párosítatlan n-eseit
vesszük hozzá (jobb oldalon üres jelekkel kiegészítve), a jobb oldali reláció párosítatlan n-eseit
figyelmen kívül hagyjuk.

Egy olyan feladat megválaszolása, hogy „soroljuk fel a Személy relációban tárolt emberek és mellettük
az apjuk adatait (amennyiben ismert)”, a következő bal oldali külső theta-illesztéssel lehetséges:

𝑆𝑧𝑒𝑚é𝑙𝑦⋈∘ 𝐿 𝑆𝑧𝑒𝑚é𝑙𝑦.𝐴𝑝𝑎=𝑆𝑧ü𝑙ő.𝑆𝑧𝑖𝑔𝑆𝑧á𝑚 𝜌𝑆𝑧ü𝑙ő (𝑆𝑧𝑒𝑚é𝑙𝑦)

Így valamennyi személy megjelenik az eredményben akkor is, ha az apa adatai nem elérhetőek (2.22.
táblázat).

Személy. Személy. Személy. Szülő. Szülő.


Személy. Név Szülő. Név Szülő. Apa
SzigSzám Apa Anya SzigSzám Anya
777777AB Kovács Péter 444444AA 333333JH ⊥ ⊥ ⊥ ⊥
555555HG Nagy Béla 111111MN 000000HB ⊥ ⊥ ⊥ ⊥
999999PQ Kovács Máté 777777AB 666666CD 777777AB Kovács Péter 444444AA 333333JH
888888GT Kovács Éva 777777AB 666666CD 777777AB Kovács Péter 444444AA 333333JH
666666CD Nagy Éva 555555HG 222222MU 555555HG Nagy Béla 111111MN 000000HB

2.22. táblázat: Személy és Szülő relációk bal oldali külső illesztése az apa személyi igazolvány száma szerint


A jobb oldali külső theta-illesztés (az illesztés jel után egy R betűvel jelöljük: 𝑅⋈ 𝑅 𝐶 𝑆) a theta-illesztést
a jobb oldali reláció (S) párosítatlan n-eseivel egészíti ki, azok bal oldalát üres jelekkel feltöltve.

2.2.8.3 Külső természetes illesztés


A külső theta-illesztésekhez hasonlóan léteznek külső természetes illesztés operátorok is, mind a teljes
∘ ∘ ∘
illesztéshez (𝑅⋈ 𝑆), illetve a bal (𝑅⋈ 𝐿 𝑆) és jobb (𝑅⋈ 𝑅 𝑆) oldali külső természetes illesztésekhez is. Ezen
operátorok az egyszerű természetes illesztéshez (2.2.5) hasonlóan az azonos nevű attribútumok
értékeinek egyezését várják el a párosított n-esektől, és az így előálló párosításokat egészítik ki mindkét
/ a bal / a jobb oldali reláció párosítatlan n-eseivel.

2.3 Relációs műveletek zsákokon


A kereskedelmi forgalomban kapható relációs adatbázis-kezelő rendszerek (Relational Database
Management System – RDBMS) a relációkat és a relációs műveleteket tipikusan nem halmazokon (set),
hanem zsákokon (bag, multiset) implementálják. A zsák abban különbözik a halmaztól, hogy ugyanazt
az értéket többször is tartalmazhatja. Használatuknak egyik legfőbb oka, hogy a relációs műveletek egy
része jóval hatékonyabban végezhető el zsákokon, mint halmazokon, hiszen az eredményből nem kell
kiszűrni az ismétlődéseket. Például unió képzéskor elég az egyik reláció tartalmát egyszerűen a másik
elemei után másolni, nincs szükség a két reláció elemeinek összehasonlítására. Hasonlóképpen, ahogy

22
a 2.4. táblázatban is láttuk, vetítés során előfordulhat, hogy az attribútum szám csökkenés miatt azonos
n-esek keletkeznek, amit halmazok használata esetén meg kell tisztítani az ismétlődésektől. Zsákok
esetében ezzel szemben a vetítést soronként tudjuk elvégezni anélkül, hogy meg kellene vizsgálni, hogy
az így előálló új n-es egyedi-e vagy sem.

A hatékonyságon túl, zsákok használatának másik fontos oka az, hogy bizonyos értékeket csak
zsákokon tudunk kiszámolni. Például ha szeretnénk megállapítani a 2.23. táblázatban látható termékek
raktárkészletének összegét, akkor először kiválasztjuk az Raktárkészlet oszlop értékeit, majd
összegezzük őket (2.3.6).

Név Ár Raktárkészlet Kategória GyártóNév


Kanapé 150.000 3 Nappali Nagy Bútor Kft
Dohányzóasztal 12.000 3 Nappali Nagy Bútor Kft
Szék 22.000 15 Iroda Fabric Rt
Hintaágy 32.000 1 Kert Bútorgyár

2.23. táblázat: Termékek nem egyedi raktárkészlettel

Ha a relációkat halmazokként értelmezzük, akkor a Raktárkészlet értékek meghagyása után – a


duplikáció eliminálás miatt - három érték marad: 3,15,1, amelynek az összege 19, ami helytelen. Míg
zsákokkal dolgozva vetítés után mind a 4 érték megmarad: 3,3,15,1, ezeket összegezve a helyes értéket
kapjuk.

2.3.1 Vetítés

Ahogyan már utaltunk rá, a vetítés műveletet zsákok esetén az egyes n-eseken egymástól teljesen
függetlenül végezhetjük el. A végeredményből nem kell eltávolítani az esetlegesen keletkező
ismétlődéseket sem akkor, ha az eredeti relációban (mint zsákban) már eleve voltak ismétlődések, sem
akkor, ha a vetítés során, az egyes attribútumok elhagyása miatt keletkeztek.

A 𝜋𝑅𝑎𝑘𝑡á𝑟𝑘é𝑠𝑧𝑙𝑒𝑡,𝐾𝑎𝑡𝑒𝑔ó𝑟𝑖𝑎 (𝑇𝑒𝑟𝑚é𝑘) vetítést a 2.23. táblázatban mint zsákon elvégezve az eredmény


tehát összesen négy n-est fog tartalmazni, a {𝑅𝑎𝑘𝑡á𝑟𝑘é𝑠𝑧𝑙𝑒𝑡 = 3, 𝐾𝑎𝑡𝑒𝑔ó𝑟𝑖𝑎 = ”𝑁𝑎𝑝𝑝𝑎𝑙𝑖”} sort
kétszer, mivel a keletkező ismétlődő bejegyzéseket nem távolítjuk el.

Raktárkészlet Kategória
3 Nappali
3 Nappali
15 Iroda
1 Kert

2.24. táblázat: Ismétlődések meghagyása vetítés után zsákokon

2.3.2 Kiválasztás

Kiválasztást zsák-alapú relációk n-esein egymástól függetlenül végezzük el, az egyes sorok vizsgálata
közben nem kell ellenőriznünk, hogy az eredménybe belekerült-e már korábban egy, az aktuálissal
megegyező bejegyzés, hiszen az eredmény tartalmazhat ismétlődéseket.

23
A kiválasztás önmagában nem képes új ismétlődő n-esek létrehozására, mivel az egy már létező n-esről
dönti csak el, hogy az belekerül-e az eredménybe vagy sem, tehát a kiválasztás eredményébe csak úgy
kerülhet duplikáció, ha már a forrás relációban is volt. Tekintsük a 2.3.1-ben ismertetett
𝜋𝑅𝑎𝑘𝑡á𝑟𝑘é𝑠𝑧𝑙𝑒𝑡,𝐾𝑎𝑡𝑒𝑔ó𝑟𝑖𝑎 (𝑇𝑒𝑟𝑚é𝑘) relációt. Végezzük el ezen a 𝑅𝑎𝑘𝑡á𝑟𝑘é𝑠𝑧𝑙𝑒𝑡 < 5 szűrést:

𝜎𝑅𝑎𝑘𝑡á𝑟𝑘é𝑠𝑧𝑙𝑒𝑡<5 (𝜋𝑅𝑎𝑘𝑡á𝑟𝑘é𝑠𝑧𝑙𝑒𝑡,𝐾𝑎𝑡𝑒𝑔ó𝑟𝑖𝑎 (𝑇𝑒𝑟𝑚é𝑘))

A szelekció tehát nem távolítja el az ismétlődő {𝑅𝑎𝑘𝑡á𝑟𝑘é𝑠𝑧𝑙𝑒𝑡 = 3, 𝐾𝑎𝑡𝑒𝑔ó𝑟𝑖𝑎 = ”𝑁𝑎𝑝𝑝𝑎𝑙𝑖”} n-est


(2.25. táblázat).

Raktárkészlet Kategória
3 Nappali
3 Nappali
1 Kert

2.25. táblázat: Ismétlődések meghagyása kiválasztás után zsákokon

2.3.3 Halmazműveletek

Az unió, metszet és különbség operátorok is újradefiniálásra kerülnek zsákok használata esetén.


Tegyük fel, hogy az r n-es 𝑛-szer szerepel az R relációban, és m-szer az S relációban (𝑛 és 𝑚 is lehet 0).
Ekkor 𝑅-t és 𝑆-t zsákokként értelmezve:

1. 𝑅 ∪ 𝑆 n + m-szer fogja tartalmazni az 𝑟 n-est.


Tételezzük fel a 2.26. táblázatban látható 𝐾1 és 𝐾2 relációkat.

Kategória Kategória
Iroda Nappali
Iroda Nappali
Nappali Iroda
Kert Kert

2.26. táblázat: K1 és K2 relációk

A két reláció uniója mindkét reláció n-eseit tartalmazni fogja, összesen 8-at (2.27. táblázat).

Kategória
Iroda
Iroda
Nappali
Kert
Nappali
Nappali
Iroda
Kert

2.27. táblázat: K1 ∪ K2

2. 𝑅 ∩ 𝑆 MIN(n, m)-szer fogja tartalmazni az 𝑟 n-est.


A 𝐾1 ∩ 𝐾2 műveletet elvégezve tehát az eredményben 𝐾1 és 𝐾2 közös n-esei fognak szerepelni,
mégpedig a kisebb előfordulási számúszor. Mivel az „Iroda” érték K1 -ben kétszer, K 2 -ben csak

24
egyszer szerepel, ezért az eredményben is csak egyszer fog szerepelni, hasonlóképpen a
„Nappali” is csak egyszer kerül bele az eredménybe. A „Kert” mindkét relációban egyszer
szerepel, így értelemszerűen az eredményben is csak egyszer fog (2.28. táblázat).

Kategória
Iroda
Nappali
Kert

2.28. táblázat: K1 ∩ K2

3. 𝑅 − 𝑆 𝑀𝐴𝑋(0, 𝑛 − 𝑚)-szer fogja tartalmazni 𝑟-t. Vagyis ha r többször fordul elő R-ben mint
S-ben, akkor az előfordulások különbsége számúszor fog szerepelni az eredményben, ha
viszont S-ben legalább annyiszor szerepel, mint R-ben, akkor az eredménybe nem fog
belekerülni (természetesen negatív számúszor nem szerepelhet egy n-es).
A 𝐾1 − 𝐾2 műveletet elvégezve tehát 𝐾1 n-esei közül csak azok fognak megmaradni és
annyiszor, amelyek és amennyivel többször szerepelnek 𝐾1 -ben, mint 𝐾2 -ben. Mivel 𝐾1 2
„Irodát” tartalmaz, míg 𝐾1 csak egyet, ezért az eredményben egyszer fog szerepelni. „Nappali”
𝐾2 -ben többször fordul elő, mint 𝐾1 -ben, ezért az eredménybe nem fog belekerülni egyszer
sem. „Kert” mindkét relációban egyszer fordul elő, ezért az eredménybe szintén nem kerül
bele (2.29. táblázat).

Kategória
Iroda

2.29. táblázat: K1 - K2

2.3.4 Keresztszorzat, illesztések

Két zsák-alapú reláció keresztszorzatának előállítása a halmaz-alapúhoz hasonlóan történik: az egyik


reláció minden egyes elemét összepárosítjuk a másik reláció minden egyes elemével. Amennyiben a
forrás relációk egyike sem tartalmaz ismétlődést, akkor az eredmény reláció minden egyes n-ese
szintén egyedi lesz. Ha viszont valamelyik relációban már eleve volt duplikátum, akkor az eredményben
is szerepelni fog. Általánosan, ha egy r n-es az R relációban m-szer, egy s n-es az S relációban n-szer
szerepel, akkor az (r, s) n-es az 𝑅 × 𝑆 relációban n ∗ m-szer fog szerepelni.

A zsák-alapú relációkon dolgozó illesztés műveletek szintén a keresztszorzatból indulnak ki, és a


keresztszorzatból választják ki az összeillő párosításokat, illetve külső illesztés esetén azokat egészítik
ki a párosítatlan n-esekkel. A művelet elvégzése során csak akkor jöhetnek létre ismétlődések, ha az
illesztett relációk legalább egyike tartalmazott ismétlődő bejegyzéseket. A zsákokon értelmezett
illesztés műveletek szintén nem távolítják el ezeket az ismétlődő bejegyzéseket.

Tekintsük a 2.30. táblázatban ábrázolt gyártói statisztikát (𝐺𝑦á𝑟𝑡ó𝑆𝑡𝑎𝑡𝑖𝑠𝑧𝑡𝑖𝑘𝑎 =


𝜋𝑅𝑎𝑘𝑡á𝑟𝑘é𝑠𝑧𝑙𝑒𝑡,𝐾𝑎𝑡𝑒𝑔ó𝑟𝑖𝑎,𝐺𝑦á𝑟𝑡ó𝑁é𝑣 (𝑇𝑒𝑟𝑚é𝑘), valamint a Gyártó relációkat. A két reláció természetes
illesztése (𝐺𝑦á𝑟𝑡ó ⋈ 𝐺𝑦á𝑟𝑡ó𝑆𝑡𝑎𝑡𝑖𝑠𝑧𝑡𝑖𝑘𝑎) 3 helyett 4 sorból fog állni, hiszen a
{3, „Nappali”, „NagyBútor Kft”} n-es kétszer szerepel a GyártóStatisztika relációban, mivel
mindkettőhöz illesztettük a Gyártó reláció.

25
{„Nagy Bútor Kft”, „5000 Szolnok Fa u 2”, „nb@example. org”} n-esét, mindkét párosítást benne is
hagyjuk az eredményben (2.31. táblázat).

Raktár- Kategória GyártóNév GyártóNév Cím Email


készlet
3 Nappali Nagy Bútor Kft Nagy Bútor Kft 5000 Szolnok nb@example.org
Fa u 2
3 Nappali Nagy Bútor Kft Fabric Rt 9012 Győr Ág u fabric@example.org
15
15 Iroda Fabric Rt Bútorgyár 1111 Bp. Hős xyb@example.org
krt 3.
1 Kert Bútorgyár

2.30. táblázat: GyártóStatisztika és Gyártó relációk

Raktár- Kategória GyártóNév Cím Email


készlet
3 Nappali Nagy Bútor Kft 5000 Szolnok nb@example.org
Fa u 2
3 Nappali Nagy Bútor Kft 5000 Szolnok nb@example.org
Fa u 2
15 Iroda Fabric Rt 9012 Győr Ág u fabric@example.org
15
1 Kert Bútorgyár 1111 Bp. Hős xyb@example.org
krt 3.

2.31. táblázat: GyártóStatisztika⋈Gyártó

2.3.5 Duplikáció eliminálás

Bár a kereskedelmi forgalomban elterjedt legtöbb relációs adatbázis-kezelő rendszer zsák-alapú


relációkkal dolgozik pont azért, hogy az ismétlődések ne vesszenek el, előfordul, hogy éppen azt
szeretnénk, hogy az eredményben minden n-es csak egyszer szerepeljen.

Például, ha a 2.23. táblázatban ábrázolt termék reláció alapján meg szeretnénk válaszolni azt a kérdést,
hogy „Melyik gyártóktól van jelenleg termék raktáron?”, akkor a 𝜎𝐺𝑦á𝑟𝑡ó𝑁é𝑣 (𝑇𝑒𝑟𝑚é𝑘) lekérdezés
eredményében a „Nagy Bútor Kft” kétszer is szerepel. A probléma még szembetűnőbb, ha például 5
gyártó 100 fajta termékét árusítjuk, hiszen akkor az 5 gyártó különböző ismétlődésekkel összesen
százszor fog megjelenni az eredményben, ezáltal emberi felhasználásra alkalmatlanná téve azt.

A fenti problémára megoldás a 𝛿(𝑅) művelet, amely az 𝑅 relációból eltávolítja az ismétlődő n-eseket,
mintegy halmazzá alakítva azt. A 𝛿(𝜋𝐺𝑦á𝑟𝑡ó𝑁é𝑣 (𝑇𝑒𝑟𝑚é𝑘)) kifejezést a fenti példában említett Termék
reláción alkalmazva az eredmény tehát 4 helyett 3 sort fog tartalmazni (2.32. táblázat).

26
GyártóNév
Nagy Bútor Kft
Fabric Rt
Bútorgyár

2.32. táblázat: Gyártók nevei ismétlődések nélkül

2.3.6 Aggregálás

Az eddig megismert műveletek segítségével egy reláció egyes n-eseit egymástól függetlenül dolgoztuk
fel (eltekintve a halmazok esetén implicit / zsákok esetén explicit alkalmazott duplikáció eltávolítástól).
Gyakori feladat viszont, hogy több n-esről együttesen tudjunk megállapítani dolgokat. Például meg
tudjuk számolni a bizonyos feltételeket kielégítő n-eseket, vagy adott oszlop értékeit összegezni,
átlagolni stb. tudjuk. Ha meg szeretnénk válaszolni azt a kérdést, hogy mennyibe kerül a
nyilvántartásunk szerinti legdrágább termék, akkor az eredmény az Á𝑟 oszlop értékeinek
aggregálásával állítható elő úgy, hogy a maximum értéket keressük meg az összes érték közül. A
leggyakrabban használt aggregációs műveletek a következők:

1. 𝐶𝑂𝑈𝑁𝑇: a 𝐶𝑂𝑈𝑁𝑇(𝐴) művelet megszámolja, hogy hány ürestől különböző, de nem feltétlen
egyedi érték található az A oszlopban.
2. 𝑆𝑈𝑀: a 𝑆𝑈𝑀(𝐴) művelet összegzi az A oszlopban tárolt numerikus értékeket.
3. 𝑀𝐼𝑁, 𝑀𝐴𝑋: a 𝑀𝐼𝑁(𝐴) , illetve 𝑀𝐴𝑋(𝐴) operátorok az A oszlop legkisebb, illetve legnagyobb
értékeit adják vissza. Numerikus értékeket tároló oszlopoknál a működésük egyértelmű,
stringeket tároló oszlopok esetén az alfabetikusan legkisebb, illetve legnagyobb értéket
eredményezik.
4. 𝐴𝑉𝐺: az 𝐴𝑉𝐺(𝐴) művelet az A oszlopban tárolt numerikus értékek átlagát állítja elő.

A fenti műveleteket tipikusan egy projekció vagy egy csoportosítás (2.3.7) művelet részeként
alkalmazzuk. A 2.23. táblázatban elvégzett 𝜋𝑀𝐼𝑁(Á𝑟)→𝑚𝑖𝑛𝑖𝑚𝑢𝑚,𝑀𝐴𝑋(Á𝑟)→𝑚𝑎𝑥𝑖𝑚𝑢𝑚 (𝑇𝑒𝑟𝑚é𝑘)
lekérdezés eredményének sémája két attribútummal fog rendelkezni: 𝑚𝑖𝑛𝑖𝑚𝑢𝑚 és 𝑚𝑎𝑥𝑖𝑚𝑢𝑚, a
reláció pedig egy n-est fog tartalmazni az Á𝑟 oszlopban talált legkisebb és legnagyobb értékkel (2.33.
táblázat).

minimum maximum
12.000 150.000

2.33. táblázat: MIN(Ár) és MAX(Ár) aggregálások eredménye

Fontos, hogy ha aggregálás műveleteket végzünk el projekció közben, akkor az eredménynek mindig
egy sora lesz, amely az aggregált értékeket tartalmazza. Ebben az egy sorban csak aggregált értékek
szerepelhetnek, egyszerű attribútumok nem (mivel nem lenne egyértelmű, hogy az aggregált
értékekhez melyik egyedi n-es attribútumainak értéke társul).

2.3.7 Csoportosítás

A 2.3.6 fejezetben bemutattuk a leggyakrabban használt aggregáló operátorokat, amelyek egy reláció
valamely oszlopának összes értékén végeznek valamilyen típusú összegzést. Sokszor előfordul

27
azonban, hogy az aggregálást nem az összes n-esen egyszerre, hanem az n-eseket csoportosítva, a
csoportokon külön-külön szeretnénk elvégezni. Erre például olyan kérdések megválaszolásához lehet
szükség, mint hogy „a termékek átlag ára kategóriánként”, „a forgalmazott termékek száma
gyártónként” vagy például „az összes raktározott termékszám kategóriánként és gyártónként”. Ehhez
a forrás reláció n-eseit kategóriánként / gyártónként / kategória-gyártó párosonként csoportokra kell
bontani, és az átlagolást / számlálást / összegzést csoportonként kell elvégezni.

Egy reláció n-eseinek csoportokra bontására, és a csoportonkénti aggregálásra szolgál a 𝛾𝐿 (𝑅)


operátor, ahol az 𝐿 paraméter tartalmazhat (i) egyszerű attribútumokat (ún. csoportosító
attribútumokat), amelyek alapján az n-eseket csoportokra bontjuk, (ii) valamint 𝐸 → 𝑎 kifejezéseket,
ahol 𝐸 egy aggregáló kifejezés (2.3.6 fejezet), 𝑎 pedig egy attribútum név.

A 𝛾𝐿 (𝑅) kifejezés eredménye a következőképp áll elő:

1. Az 𝑅 relációt 𝐿 csoportosító attribútumai mentén csoportokra bontjuk. Az egyes csoportokba


𝑅 azon n-esei kerülnek, amelyek 𝐿 egyes csoportosító attribútumaihoz n-esenként
ugyanazokat az értékeket rendelik (ha egy ilyen attribútum sincs, akkor az összes n-es egy
csoportba kerül).
2. Az eredmény reláció sémája a csoportosító attribútumokból és az aggregáló kifejezésekhez
rendelt attribútumokból fog állni.
3. Minden egyes csoporthoz egy új n-est rendelünk: az n-esbe belekerülnek a csoportosító
attribútumok adott csoporthoz rendelt értékei (amelyek a csoporton belüli n-esekre
azonosak), valamint az aggregáló operátorok eredményei.

Tekintsük a 2.23. táblázatban ismertetett Termék relációt. Például ha termék kategóriánként


szeretnénk összegezni a raktárkészletet, az a következő kifejezéssel lehetséges:

𝛾𝐾𝑎𝑡𝑒𝑔ó𝑟𝑖𝑎,𝑆𝑈𝑀(𝑅𝑎𝑘𝑡á𝑟𝑘é𝑠𝑧𝑙𝑒𝑡)→𝐾é𝑠𝑧𝑙𝑒𝑡 (𝑇𝑒𝑟𝑚é𝑘)

Az operátor a Termék relációt a 2.34. táblázatban látható három képzeletbeli csoportra bontja.

Név Ár Raktárkészlet Kategória GyártóNév


Kanapé 150.000 3 Nappali Nagy Bútor Kft
Dohányzóasztal 12.000 3 Nappali Nagy Bútor Kft
Szék 22.000 15 Iroda Fabric Rt
Hintaágy 32.000 1 Kert Bútorgyár

2.34. táblázat: Termékek kategóriánként csoportosítva

Majd csoportonként elvégzi a Raktárkészlet értékek összegzését. A végeredménybe a csoportosító


attribútum (Raktárkészlet) és az aggregálás eredménye kerül (2.35. táblázat).

28
Kategória RaktárkészletKészlet
Nappali 6
Iroda 15
Kert 1

2.35. táblázat: Raktárkészletek kategóriánként összegezve

Megjegyezzük, hogy a duplikáció eliminálás operátor kifejezhető csoportosítás segítségével úgy, hogy
a reláció valamennyi attribútumát felsoroljuk csoportosító attribútumként, de nem alkalmazunk
aggregálást. Ezáltal az ekvivalens n-esek és csak azok azonos csoportokba fognak kerülni, és
csoportonként csak egy n-es (az összes attribútummal) kerül az eredmény relációba. Vagyis 𝛿(𝑅) ≡
𝛾𝐴1 …𝐴𝑛 (R), ahol 𝐴1 … 𝐴𝑛 𝑅 összes attribútumát jelöli.

2.3.8 Rendezés

Lekérdezések megfogalmazásakor gyakori igény, hogy az eredményt rendezetten kapjuk meg. Például
gyártók a neveik szerint ABC sorrendbe rendezve, vagy a termékek az áraik szerint növekvő sorrendbe
rendezve. Elméleti szempontból a rendezésnek nincs különösebb jelentősége, sokkal inkább az adatok
emberek általi értelmezését könnyíti meg. A rendezést reláció algebrában a 𝜏𝐿 (𝑅) operátorral jelöljük,
ahol 𝐿 a rendező attribútumok listája: 𝐴1 , 𝐴2 , … , 𝐴𝑛 . A rendezés során az n-esek először 𝐴1 szerint
növekvő, azonos 𝐴1 értékekkel rendelkező n-esek 𝐴2 szerint növekvő stb. módon kerülnek rendezésre.
Ha két n-es valamennyi rendező attribútumra ugyanazokkal az értékekkel rendelkezik, akkor ezen n-
esek sorrendje nem definiált.

Ha a 2.23. táblázatban látható termék relációt például raktárkészlet és ár szerint szeretnénk sorba
rendezni, az a 𝜏𝑅𝑎𝑘𝑡á𝑟𝑘é𝑠𝑧𝑙𝑒𝑡,Á𝑟 (𝑇𝑒𝑟𝑚é𝑘) kifejezéssel lehetséges. Mivel a 𝐾𝑎𝑛𝑎𝑝é és a
𝐷𝑜ℎá𝑛𝑦𝑧ó𝑎𝑠𝑧𝑡𝑎𝑙 nevű termékekből egyaránt 3 darab van raktáron, ezért kettejük sorrendjét az áruk
fogja meghatározni (2.36. táblázat).

Név Ár Raktárkészlet Kategória GyártóNév


Hintaágy 32.000 1 Kert Bútorgyár
Dohányzóasztal 12.000 3 Nappali Nagy Bútor Kft
Kanapé 150.000 3 Nappali Nagy Bútor Kft
Szék 22.000 15 Iroda Fabric Rt

2.36. táblázat: Termékek raktárkészlet és ár szerint sorrendezve.

2.4 Kényszerek relációkon


A relációkon számos kényszert fogalmazhatunk meg (részletesen lsd. 5. fejezet), amelyek egy részét
reláció algebrai konstrukciókkal is le tudunk írni. A legfontosabb kényszerek a reláció kulcsa, illetve a
relációkat összekapcsoló külső kulcs, amelyekkel most részletesebben is megismerkedünk.

2.4.1 Reláció kulcsa

A kulcs azon attribútumok összessége, amelyekhez az n-esek által rendelt értékek együttesen
garantáltan egyediek az összes lehetséges n-esre: nem csak egy konkrét relációban, hanem a reláció

29
összes lehetséges példányában. A kulcs attribútumokhoz rendelt értékek ismeretével tehát
egyértelműen azonosítani tudjuk a reláció egy konkrét n-esét.

Név Ár Raktárkészlet Kategória GyártóNév


Kanapé 150.000 3 Nappali Nagy Bútor Kft
Dohányzóasztal 12.000 3 Nappali Nagy Bútor Kft
Szék 22.000 15 Iroda Fabric Rt
Hintaágy 32.000 1 Kert Bútorgyár

2.37. táblázat: A Termék reláció

A 2.37. táblázatban látható 𝑇𝑒𝑟𝑚é𝑘 relációban egy lehetséges kulcs például a termékek neve, de csak
akkor, ha feltesszük, hogy nem árusítunk két azonos nevű terméket két különböző gyártótól. De
például egy gyártó valószínűleg nem fog két különböző, de teljesen azonos nevű terméket gyártani, a
termék neveket legalább egy kóddal vagy utótaggal meg fogja különböztetni. Tehát a Név és a
GyártóNév együtt már jó kulcsnak ígérkezik. A kulcsot aláhúzással jelöljük a relációs sémában:

𝑇𝑒𝑟𝑚é𝑘(𝑁é𝑣, Á𝑟, 𝑅𝑎𝑘𝑡á𝑟𝑘é𝑠𝑧𝑙𝑒𝑡, 𝐾𝑎𝑡𝑒𝑔ó𝑟𝑖𝑎, 𝐺𝑦á𝑟𝑡ó𝑁é𝑣)

A fenti néhány n-es alapján azt is gondolhatnánk, hogy az Á𝑟 attribútum is kulcs, hiszen egyedi mind a
négy nyilvántartott termékre. Ne felejtsük el azonban, hogy a kulcs a reláció nem csak egy konkrét
példányára egyedi, hanem az összes lehetséges változatára, és természetesen nem kizárt két
különböző termék azonos árral.

Bár jelen esetben a termék és a gyártó neve együtt egyedinek tűnik, a gyakorlatban legtöbbször nem
szokták az egyedek ún. természetes attribútumait kulcsként használni, mivel azok idővel változhatnak,
túl hosszúak, összetettek (sok attribútum együttesen kulcs), hanem inkább egy mesterséges azonosítót
rendelnek az egyedekhez, amely rövid és állandó. Ilyen azonosító lehet például egy személy személyi
száma, taj száma , egy könyv ISBN száma, egy termék kód, alvázszám, motorszám stb. Sokszor még
ezeknél is tömörebb és egyszerűbb kulcsot választanak akár az említett azonosítók mellé: például egy
futó sorszámot, ami minden egyes új n-esnél, amit a relációba beszúrunk, eggyel nő.

A kulcsot relációalgebrai kifejezésekkel is ki tudjuk fejezni. Mivel egy kulcs érték egyértelműen
meghatározza egy R reláció pontosan egy n-esét, ebből az következik, hogy a reláció minden egyes n-
esét csak saját magával tudjuk összepárosítani, ha a relációt saját magával theta-illesztjük a kulcs
attribútum mentén. Vagyis a párost előállító két n-es attribútum értékei páronként megegyeznek,
másképp fogalmazva: nincs olyan párosítás, amelyben a megfelelő attribútum értékek a két n-esben
különböznek. A 𝑇𝑒𝑟𝑚é𝑘 reláció esetében ez a következőképp fogalmazható meg:

Legyen 𝑇1 = 𝜌𝑇1 (𝑇𝑒𝑟𝑚é𝑘) és 𝑇2 = 𝜌𝑇2 (𝑇𝑒𝑟𝑚é𝑘), ekkor

𝜎 𝑇1.Á𝑟≠𝑇2.Á𝑟∨𝑇1.𝑅𝑎𝑘𝑡á𝑟𝑘é𝑠𝑧𝑙𝑒𝑡≠𝑇2 .𝑅𝑎𝑘𝑡á𝑟𝑘é𝑠𝑧𝑙𝑒𝑡∨𝑇1.𝐾𝑎𝑡𝑒𝑔ó𝑟𝑖𝑎≠𝑇2 .𝐾𝑎𝑡𝑒𝑔ó𝑟𝑖𝑎 (𝑇1 ⋈ 𝑇1 .𝑁é𝑣=𝑇2.𝑁é𝑣∧𝑇1.𝐺𝑦á𝑟𝑡ó𝑁é𝑣=𝑇2.𝐺𝑦á𝑟𝑡ó𝑁é𝑣 𝑇2 ) = ∅,

ahol ∅ az üres relációt jelöli.

Általánosan fogalmazva, egy 𝑅(𝐾1 , … , 𝐾𝑛 , 𝐴1 , … , 𝐴𝑚 ) relációban a {𝐾1 , … , 𝐾𝑛 } attribútumok együtt


egy kulcsot alkotnak, ha 𝑅1 = 𝜌𝑅1 (𝑅), 𝑅2 = 𝜌𝑅2 (𝑅) esetén

𝜎𝑅1 .𝐴1 ≠𝑅2 .𝐴1 ∨…∨𝑅1 .𝐴𝑚≠𝑅2 .𝐴𝑚(𝑅1 ⋈𝑅


1 .𝐾1 =𝑅2 .𝐾2 ∧…∧𝑅1 .𝐾𝑛 =𝑅2 .𝐾𝑛 𝑅2 )=∅

30
2.4.2 Külső kulcs

GyártóNév Cím Email


Nagy Bútor Kft 5000 Szolnok Fa u 2 nb@example.org
Fabric Rt 9012 Győr Ág u 15 fabric@example.org
Bútorgyár 1111 Bp. Hős krt 3. xyb@example.org

2.38. táblázat: Gyártó reláció

A gyártói adatokat tároló 𝐺𝑦á𝑟𝑡ó relációban (2.38. táblázat) a gyártó neve (mivel egy cég név) egyedi
kell, hogy legyen, ezért használhatjuk azt kulcsként. Figyeljük meg, hogy a 𝑇𝑒𝑟𝑚é𝑘 relációban szintén
van egy 𝐺𝑦á𝑟𝑡ó𝑁é𝑣 attribútum, amely azt rögzíti, hogy az egyes termékeket ki gyártja. A 𝐺𝑦á𝑟𝑡ó𝑁é𝑣
attribútum a 𝑇𝑒𝑟𝑚é𝑘 relációban egy külső kulcs, amelynek lehetséges értékei a 𝐺𝑦á𝑟𝑡ó reláció
𝐺𝑦á𝑟𝑡ó𝑁é𝑣 attribútumához (kulcsához) rendelt értékek közül kerülhetnek ki. Vagyis a 𝑇𝑒𝑟𝑚é𝑘
𝐺𝑦á𝑟𝑡ó𝑁é𝑣 attribútumával egy konkrét 𝐺𝑦á𝑟𝑡ó𝑟𝑎 mutatunk rá. Általánosan is igaz, hogy a külső kulcs
célja, hogy különböző relációk egyedei között kapcsolatot létesítsen úgy, hogy az egyik reláció egy n-
eséből rámutat egy másik (vagy akár ugyanazon) reláció egy n-esére úgy, hogy eltárolja a mutatott n-
es kulcs értékét.

Mivel a külső kulcs értékek mind a kulcs értékek közül kell, hogy kikerüljenek, következésképpen a
külső kulcs értékek között nincs olyan, amely nem szerepel a kulcs értékek között. Ez az átfogalmazott
feltétel kifejezhető a különbség művelettel:

𝜋𝐺𝑦á𝑟𝑡ó𝑁é𝑣 (𝑇𝑒𝑟𝑚é𝑘) − 𝜋𝐺𝑦á𝑟𝑡ó𝑁é𝑣 (𝐺𝑦á𝑟𝑡ó) = ∅

Amennyiben a relációkat zsákként és nem halmazként értelmezzük, akkor előfordulhat, hogy a


𝜋𝐺𝑦á𝑟𝑡ó𝑁é𝑣 (𝑇𝑒𝑟𝑚é𝑘) relációban található elemek nem egyediek, hiszen több terméket is előállíthat
ugyanaz a gyártó. Ilyenkor a különbség művelet az ismétlődő elemeket meghagyja, hiszen a
𝜋𝐺𝑦á𝑟𝑡ó𝑁é𝑣 (𝐺𝑦á𝑟𝑡ó) relációban (mivel a 𝐺𝑦á𝑟𝑡ó𝑁é𝑣 kulcs) minden elem egyedi, ezért az eredmény
nem lesz üres, bár a kényszer teljesül. Ennek kiküszöbölésére a különbség elvégzése előtt dobjuk el az
ismétlődő elemeket:

𝛿(𝜋𝐺𝑦á𝑟𝑡ó𝑁é𝑣 (𝑇𝑒𝑟𝑚é𝑘)) − 𝜋𝐺𝑦á𝑟𝑡ó𝑁é𝑣 (𝐺𝑦á𝑟𝑡ó) = ∅

Ha a kulcs (és így a külső kulcs is) több attribútumból áll, akkor a vetítés műveleteknél az összes kulcs
attribútumot ki kell választani.

A külső kulcs kényszert általános formában is kifejezhetjük:

Legyen 𝑅(𝐾1 , … , 𝐾𝑖 , 𝐴1 , … , 𝐴𝑗 ) és 𝑆(𝐿1 , … , 𝐿𝑖 , 𝐵1 , … , 𝐵𝑘 ) két reláció, 𝑅 kulcsa {𝐾1 , … , 𝐾𝑖 }, az 𝑆 reláció


{𝐾1 , … , 𝐾𝑖 }-re mutató külső kulcsa pedig {𝐿1 , … , 𝐿𝑖 }. Ekkor

𝛿 (𝜋𝐿1 ...𝐿𝑖 (𝑆)) − 𝜋𝐾1 ...𝐾𝑖 (𝑅) = ∅

2.4.3 Egyéb kényszerek

Relációalgebrai kifejezések segítségével egyéb kényszereket is kifejezhetünk. Egy reláció


attribútumaira gyakori, hogy tartományi megkötéseket alkalmazunk, például hogy a termék ára egy

31
nem negatív egész szám, a termék nevét kötelező kitölteni, egy személy neme csak ’f’ (férfi) vagy ’n’
(nő) lehet stb:

 𝜋Á𝑟 (𝑇𝑒𝑟𝑚é𝑘) ⊆ 𝑁 + (a részhalmaz művelet a halmazoknál szokásos jelentéssel bír)


 𝜎𝑁é𝑣=⊥ (𝑇𝑒𝑟𝑚é𝑘) = ∅
 𝜎𝑁𝑒𝑚≠′ 𝑓′ ∧𝑁𝑒𝑚≠′ 𝑛′ (𝑆𝑧𝑒𝑚é𝑙𝑦) = ∅

Előírhatunk összetettebb, több reláción átívelő kényszert is. Például megkövetelhetjük, hogy csak
olyan gyártók termékeit forgalmazzuk, amelyeknek a postacíme ismert:

𝜎𝐶í𝑚=⊥ (𝐺𝑦á𝑟𝑡ó ⋈𝐺𝑦á𝑟𝑡ó.𝐺𝑦á𝑟𝑡ó𝑁é𝑣=𝑇𝑒𝑟𝑚é𝑘.𝐺𝑦á𝑟𝑡ó𝑁é𝑣 𝑇𝑒𝑟𝑚é𝑘) = ∅

A Gyártó és a Termék relációt a GyártóNév attribútumon keresztül theta-illesztjük egymással, így


megkapjuk az összetartozó termékeket és gyártókat (a forgalmazott termékkel nem rendelkező
gyártókat ezzel kiszűrtük). Majd ezek közül a párok közül kiválasztjuk azokat, ahol Cím attribútum
értéke üres. Elvárásunk szerint ennek a lekérdezésnek az eredménye üres halmaz, tehát egyetlen olyan
gyártó sincs, akinek a címe nincs kitöltve, és rendelkezik forgalmazott termékkel.

2.5 Más adatmodellek


Adatok reprezentálására természetesen nem kizárólag a relációs adatmodellt használják. Gyakran
előfordul, hogy a relációs adatmodellt objektum-orientált funkciókkal egészítik ki, ezáltal i) lehetővé
válik primitív értékeken túl összetett adatokat is eltárolni egy attribútumhoz, továbbá ii) a relációkhoz
műveleteket is rendelhetünk. A fenti kiegészítéseket nevezik objektum-relációs modellnek. Egy másik,
egyre népszerűbb megközelítés a félig strukturált adatmodell, amelyben a tárolt adat és a séma nem
válik olyan élesen szét, mint a relációs modell esetén. A relációs adatbázisok és az SQL nyelv
elterjedését jól jellemzi, hogy az egyéb adatbázistípusokat egységesen nem relációs (non-relational),
vagy egyszerűen csak NoSQL adatbázisoknak nevezzük.

2.5.1 Félig strukturált adatmodellek

A félig strukturált adatmodellek esetén nem válik élesen ketté az adat és a séma, mint a klasszikus
kétdimenziós relációk esetén, hanem a sémainformáció valamilyen címkék formájában megjelenik az
adatreprezentációban is. Az ilyen adatmodellek tipikusan fa vagy gráf-alapú megközelítést
alkalmaznak, az adatmodellek leírására leggyakrabban XML1 vagy JSON2 formátumot használnak. Az
adatstruktúrák szülő és (fák esetén) gyermek csomópontokból épülnek fel, amelyekhez attribútumok
segítségével rendelhetünk értékeket.

A 2.1. táblázatban ismertetett 𝑇𝑒𝑟𝑚é𝑘 reláció egy lehetséges XML reprezentációja a következő:

<Termékek>
<Termék Név=“Kanapé” Raktárkészlet=“3”>
<Ár>150.000</Ár>
<Kategória>Nappali</Kategória>

1
Extensible Markup Language (XML) - http://www.w3.org/XML
2
The JSON Data Interchange Format - http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-
404.pdf

32
<GyártóNév>Nagy Bútor Kft</GyártóNév>
</Termék>
<Termék Név=“Dohányzóasztal” Raktárkészlet=“3”>
<Ár>12.000</Ár>
<Kategória>Nappali</Kategória>
<GyártóNév>Nagy Bútor Kft</GyártóNév>
</Termék>
<Termék Név=“Szék” Raktárkészlet=“15”>
<Ár>22.000</Ár>
<Kategória>Iroda</Kategória>
<GyártóNév>Fabric Rt</GyártóNév>
</Termék>
<Termék Név=“Hintaágy” Raktárkészlet=“1”>
<Ár>32.000</Ár>
<Kategória>Kert</Kategória>
<GyártóNév>Bútorgyár</GyártóNév>
</Termék>
</Termékek>

Ugyanazt az adatot számtalan módon le lehet írni XML-ben, a tervező döntésén múlik, hogy mit
reprezentál attribútumként és mit címkeként. A fenti példában a nevet és raktárkészletet
attribútumként, a többi tulajdonságot címke segítségével írtuk le.

A példában jól látszik, hogy XML dokumentumok esetén a séma információ (címke és attribútum
nevek, csomópontok hierarchiája) megjelenik az adatábrázolásban is.

Ugyanezt az adatot JSON formátumban is leírhatjuk, ebben az esetben tulajdonságot csak


attribútumok segítségével tudunk csomópontokhoz kapcsolni:

{
"Termékek": {
"Termék": [
{
"Név": "Kanapé",
"Raktárkészlet": "3",
"Ár": "150.000",
"Kategória": "Nappali",
"GyártóNév": "Nagy Bútor Kft"
},
{
"Név": "Dohányzóasztal",
"Raktárkészlet": "3",
"Ár": "12.000",
"Kategória": "Nappali",
"GyártóNév": "Nagy Bútor Kft"
},
{
"Név": "Szék",
"Raktárkészlet": "15",
"Ár": "22.000",
"Kategória": "Iroda",

33
"GyártóNév": "Fabric Rt"
},
{
"Név": "Hintaágy",
"Raktárkészlet": "1",
"Ár": "32.000",
"Kategória": "Kert",
"GyártóNév": "Bútorgyár"
}
]
}
}

A fa struktúrával leírt adatmodellekben általában olyan lekérdező nyelvet használnak, ami megkönnyíti
a csomópontok közötti navigációt és keresést. XML esetén ez leggyakrabban az XPath3 lekérdező nyelv
szokott lenni. Például azon termék kiválasztása, amelynek a neve „Hintaágy”, a következő XPath
lekérdezéssel lehetséges:

//Termékek/Termék[@Név=’Hintaágy’]

Az adatmodellhez kényszereket is rendelhetünk: megköthetjük, hogy az egyes attribútumok, illetve


csomópontok milyen típusú adatokat tartalmazhatnak, egy csomópontnak milyen és hány gyermek
csomópontja lehet stb. XML esetén erre az XML Schema Definition Language (XSD)4 szolgál.

A félig strukturált adatmodellek legfőbb előnye, hogy a sémájuk rugalmasan változtatható, és


hatékonyan használhatók rendszerek közti adatcserére, hiszen az adat a séma információt is magában
hordozza. Hátrányuk épp legfőbb előnyükből származik: mivel a séma kevésbé kötött, ezért nem lehet
olyan hatékonyan kiértékelni a lekérdezéseket, mint például a kétdimenziós relációs modellnél.

Napjaink népszerű félig strukturált adatbázis-kezelő rendszerei közé tartozik például a CouchDB5, a
MongoDB6 vagy az Elasticsearch7.

3
XML Path Language (XPath) Version 1.0 - http://www.w3.org/TR/xpath/
4
XML Schema Definition Language (XSD) 1.1 - http://www.w3.org/TR/xmlschema11-1
5
Apache CouchDB - http://couchdb.apache.org/
6
MongoDB - https://www.mongodb.org/
7
Elasticsearch - https://www.elastic.co/

34
3 Relációs adatbázisok tervezésének elmélete

Adatbázisok sémáját számos módon felírhatjuk, alkalmazhatjuk például 2. fejezetben megismert


𝑅(𝐴1 , … , 𝐴𝑛 ) alakot vagy 4. fejezetben bemutatott magas szintű adatbázis modelleket, de írhatunk
rögtön a relációkat létrehozó SQL kódot is. Bármilyen módszert is választunk, sokszor elsőre nem
sikerül meghatározni egy valamilyen szempontból optimális megoldást. Tipikus hiba például, hogy túl
sok tulajdonságot próbálunk egy relációba zsúfolni, és így redundanciát viszünk be a rendszerbe, ami
később több problémát is okoz.

A redundanciából eredő különféle problémák kivédésére a relációs adatbázisok elmélete szerencsére


rendelkezik már jól bejáratott megoldásokkal, a normál formákkal, amelyek biztosítják az adatok
optimális modellezését. A relációk normalizációjához alapvető fontosságú az ún. függőségek
megismerése, amelyek egy reláció különböző attribútumai közti összefüggéseket fejezik ki
(hasonlóképp, mint ahogy a kulcs attribútum értékektől is függ egy n-es többi attribútumának értéke).
A függőségek ismeretében, a normalizáció során a redundanciát tartalmazó relációkat kisebb részekre
bontjuk úgy, hogy az általuk tárolt információ a kisebb részekből továbbra is helyreállítható legyen,
miközben a redundanciát megszűntetjük.

3.1 Funkcionális függőségek


A funkcionális függőségeket a kulcs általánosításaként a legegyszerűbb elképzelni. Egy reláció kulcs
attribútumaihoz rendelt érték n-esek egyértelműen meghatározzák a reláció egy konkrét n-esét, annak
valamennyi attribútum értékét. Egy funkcionális függőség a kulcstól abban különbözik, hogy nem az
összes attribútum értéket határozza meg, hanem csak bizonyosakat azok közül. Azt mondjuk, hogy az
𝑅 reláció 𝐵1 , … , 𝐵𝑛 attribútumai funkcionálisan függnek az 𝐴1 , … , 𝐴𝑚 attribútumoktól, ha a reláció
tetszőleges két n-esében az 𝐴1 , … , 𝐴𝑚 attribútumokhoz rendelt értékek egyezése esetén a 𝐵1 , … , 𝐵𝑛
attribútumokhoz rendelt értékek is páronként ugyanazok a két n-esben. Jelölése:

𝐴1 … 𝐴𝑚 → 𝐵1 … 𝐵𝑛

Egy funkcionális függőség nem csak egy konkrét reláció példányra kell, hogy teljesüljön, hanem minden
olyan n-esre, ami előfordulhat az adott relációban, tehát az adott relációra általánosan igaz.

A funkcionális jelző onnan ered, hogy az ilyen függőségek gyakorlatilag függvények, amelyek
paraméterként egy konkrét 𝐴1 , … , 𝐴𝑚 attribútum érték n-est kapnak meg, és ehhez az n-eshez
rendelnek hozzá egy 𝐵1 , … , 𝐵𝑛 érték n-est.

Tekintsük a 𝐺𝑦á𝑟𝑡ó(𝐺𝑦á𝑟𝑡ó𝑁é𝑣, 𝐼𝑟á𝑛𝑦í𝑡ó𝑠𝑧á𝑚, 𝑉á𝑟𝑜𝑠, 𝐶í𝑚, 𝐸𝑚𝑎𝑖𝑙) relációt (3.1. táblázat). A reláció
az eddig alkalmazottal (2.38. táblázat) ellentétben néhány új attribútumot is tartalmaz: az eredeti 𝐶í𝑚
attribútumot három részre bontottuk: 𝐼𝑟á𝑛𝑦í𝑡ó𝑠𝑧á𝑚, 𝑉á𝑟𝑜𝑠 és 𝐶í𝑚 – ez utóbbi már csak az
utca/házszám értéket tartalmazza.

35
GyártóNév Irányítószám Város Cím Email
Nagy Bútor Kft 5000 Szolnok Fa u 2 nb@example.org
Fabric Rt 9012 Győr Ág u 15 fabric@example.org
Bútorgyár 1111 Budapest Hős krt 3 xyb@example.org
Kiss Bútor Kft 1111 Budapest Virágos tér 4 kb@example.org

3.1. táblázat: Gyártó reláció

A 𝐺𝑦á𝑟𝑡ó relációban kijelenthetjük, hogy ha két cégnek ugyanaz az irányítószáma, akkor ugyanaz lesz
a 𝑉á𝑟𝑜𝑠 mező értéke is. Ez általánosan is igaz, hiszen egy irányítószám pontosan egy települést határoz
meg. Formálisan:

𝐼𝑟á𝑛𝑦í𝑡ó𝑠𝑧á𝑚 → 𝑉á𝑟𝑜𝑠

Ha erre a függőségre egy 𝑓 függvényként tekintünk, akkor

𝑓(1111) = "𝐵𝑢𝑑𝑎𝑝𝑒𝑠𝑡"

𝑓(5000) = "𝑆𝑧𝑜𝑙𝑛𝑜𝑘"

𝑓(9012) = "𝐺𝑦ő𝑟"

A 2.38. táblázatban ismertetett konkrét reláció példány alapján feltételezhetnénk a

𝑉á𝑟𝑜𝑠 → 𝐼𝑟á𝑛𝑦í𝑡ó𝑠𝑧á𝑚

függőséget is, hiszen a példában Budapesthez mindig a 1111-es irányítószám tartozik, de ne felejtsük,
hogy a függőségek valamennyi reláció példányra vonatkoznak, és nem kizárt (mint például Budapest
esetében), hogy egy város több irányítószámmal is rendelkezik. Tehát a városnévből nem következik
egyértelműen egy konkrét irányítószám.

A fenti példában még számos egyéb függést is felfedezhetünk: egy gyártó neve (mivel a cégnevek
egyediek kell, hogy legyenek), egyértelműen meghatározza a gyártó székhelyének címét, illetve e-mail
címét, ahol kapcsolatba léphetünk velük, vagyis

𝐺𝑦á𝑟𝑡ó𝑁é𝑣 → 𝐼𝑟á𝑛𝑦í𝑡ó𝑆𝑧á𝑚 𝑉á𝑟𝑜𝑠 𝐶í𝑚 𝐸𝑚𝑎𝑖𝑙

Általában az is igaz, hogy a cég e-mail címe egyedi, tehát legtöbbször az

𝐸𝑚𝑎𝑖𝑙 → 𝐺𝑦á𝑟𝑡ó𝑁é𝑣 𝐼𝑟á𝑛𝑦í𝑡ó𝑆𝑧á𝑚 𝑉á𝑟𝑜𝑠 𝐶í𝑚

függőség is teljesül. Sokszor meg is követeljük, hogy az egyes felhasználók különböző e-mail címekkel
regisztráljanak egy rendszerbe. Az

𝐼𝑟á𝑛𝑦í𝑡ó𝑠𝑧á𝑚 𝑉á𝑟𝑜𝑠 𝐶í𝑚 → 𝐺𝑦á𝑟𝑡ó𝑁é𝑣

függőséget viszont nem feltételezhetjük, hiszen gyakori, hogy ugyanarra a címre több cég is be van
jegyezve, például egy irodaház esetén. A fenti példák alapján jól látható, hogy a függőségek
megállapításánál nagyon körültekintően kell eljárnunk, mivel ezáltal felesleges, esetenként
teljesíthetetlen kötöttségeket vihetünk be az adatbázisunkba.

36
Az olyan függőségeket, amelyek jobb oldalán egynél több attribútum áll (mint például 𝐺𝑦á𝑟𝑡ó𝑁é𝑣 →
𝐼𝑟á𝑛𝑦í𝑡ó𝑆𝑧á𝑚 𝑉á𝑟𝑜𝑠 𝐶í𝑚 𝐸𝑚𝑎𝑖𝑙), több egyszerű függőségre is bonthatjuk, ahol a jobb oldalon
csupán egy attribútum áll:

𝐺𝑦á𝑟𝑡ó𝑁é𝑣 → 𝐼𝑟á𝑛𝑦í𝑡ó𝑆𝑧á𝑚

𝐺𝑦á𝑟𝑡ó𝑁é𝑣 → 𝑉á𝑟𝑜𝑠

𝐺𝑦á𝑟𝑡ó𝑁é𝑣 → 𝐶í𝑚

𝐺𝑦á𝑟𝑡ó𝑁é𝑣 → 𝐸𝑚𝑎𝑖𝑙

Általánosan is igaz (lsd 3.3.fejezet), hogy egy

𝐴1 … 𝐴𝑛 → 𝐵1 … 𝐵𝑚

függőség felbontható vele ekvivalens

𝐴1 … 𝐴𝑛 → 𝐵1 , . .. , 𝐴1 … 𝐴𝑛 → 𝐵𝑚

függőségek halmazára. A felbontás művelet visszafelé is alkalmazható: azonos bal oldallal rendelkező
függőségek összekombinálhatók egy függőségbe, amely jobb oldalán az egyedi jobb oldalak uniója áll.

3.1.1 Triviális és nem triviális funkcionális függőségek

Triviálisnak nevezzük azokat a funkcionális függőségeket, amelyek egy reláció valamennyi példányára
teljesülnek, függetlenül az esetleges egyéb függőségektől. Ezen függőségek általános alakja:

𝐴1 … 𝐴𝑛 → 𝐵1 … 𝐵𝑘 , 𝑎ℎ𝑜𝑙 {𝐵1 , … , 𝐵𝑘 } ⊆ {𝐴1 , … , 𝐴𝑛 }

Vagyis a függőség jobb oldala a bal oldal részhalmaza. Az ilyen jellegű függőségek annyit állítanak, hogy
ha az 𝐴1 … 𝐴𝑛 attribútumokhoz rendelt értékek két n-esben megegyeznek, akkor ezen attribútumok
részhalmazára is meg fognak egyezni. Például

𝐼𝑟á𝑛𝑦í𝑡ó𝑠𝑧á𝑚 𝑉á𝑟𝑜𝑠 → 𝑉á𝑟𝑜𝑠

𝐸𝑚𝑎𝑖𝑙 → 𝐸𝑚𝑎𝑖𝑙

A nem triviális funkcionális függőségek ezzel szemben olyan függőségek, amelyek jobb oldalán
szerepel olyan attribútum, ami a bal oldalon nem:

𝐴1 … 𝐴𝑛 → 𝐵1 … 𝐵𝑘 , 𝑎ℎ𝑜𝑙 {𝐵1 , … , 𝐵𝑘 } − {𝐴1 , … , 𝐴𝑛 } ≠ ∅

Például:

𝐼𝑟á𝑛𝑦í𝑡ó𝑠𝑧á𝑚 → 𝑉á𝑟𝑜𝑠

𝐼𝑟á𝑛𝑦í𝑡ó𝑠𝑧á𝑚 𝐸𝑚𝑎𝑖𝑙 → 𝑉á𝑟𝑜𝑠 𝐸𝑚𝑎𝑖𝑙

Ez utóbbi példánál, bár az 𝐸𝑚𝑎𝑖𝑙 attribútum a bal és jobb oldalon is szerepel, a 𝑉á𝑟𝑜𝑠 attribútum csak
a jobb oldalon fordul elő, így a függőség nem triviális.

37
A teljesen nem triviális függőségek esetén a jobb oldalon csak a bal oldalon nem szereplő
attribútumok fordulhatnak elő:

𝐴1 … 𝐴𝑛 → 𝐵1 … 𝐵𝑘 , 𝑎ℎ𝑜𝑙 {𝐴1 , … , 𝐴𝑛 } ∩ {𝐵1 , … , 𝐵𝑘 } = ∅

Például:

𝐼𝑟á𝑛𝑦í𝑡ó𝑠𝑧á𝑚 → 𝑉á𝑟𝑜𝑠

𝐺𝑦á𝑟𝑡ó𝑁é𝑣 → 𝐶í𝑚 𝐸𝑚𝑎𝑖𝑙

A nem triviális függőségek mindig átalakíthatók velük ekvivalens jelentéssel bíró, teljesen nem triviális
függőségekké, ha a jobb oldalról eltávolítjuk a bal oldalon is szereplő attribútumokat: ha

𝐴1 … 𝐴𝑛 → 𝐵1 … 𝐵𝑘 , 𝑎ℎ𝑜𝑙 {𝐴1 , … , 𝐴𝑛 } ∩ {𝐵1 , … , 𝐵𝑘 } ≠ ∅

akkor

𝐴1 … 𝐴𝑛 → 𝐶1 … 𝐶𝑙 , 𝑎ℎ𝑜𝑙 {𝐶1 , … , 𝐶𝑙 } = {𝐵1 , … , 𝐵𝑘 } − {𝐴1 , … , 𝐴𝑛 }

3.2 Kulcsok
Egy 𝑅(𝐴1 , … , 𝐴𝑛 ) reláció kulcsának azt a {𝐾1 , … , 𝐾𝑘 } attribútum halmazt nevezzük, i) amelytől
funkcionálisan függ a reláció összes többi attribútuma, és ii) ez semelyik valódi részhalmazára nem igaz.

A fenti példában a 𝐺𝑦á𝑟𝑡ó𝑁é𝑣 attribútum kulcs, hiszen a cégnevek egyediek kell, hogy legyenek,
következésképp egy cégnév egyértelműen meghatározza annak postacímét és elsődleges e-mail címét:

𝐺𝑦á𝑟𝑡ó𝑁é𝑣 → 𝐸𝑚𝑎𝑖𝑙 𝐼𝑟á𝑛𝑦í𝑡ó𝑠𝑧á𝑚 𝑉á𝑟𝑜𝑠 𝐶í𝑚

Mivel a bal oldalon egyetlen attribútum szerepel, ezért az biztosan minimális. Ha a bal oldal tartalmazza
a kulcs attribútumait, de esetleg még egyéb attribútumokat is, akkor beszélünk szuperkulcsról. A
szuperkulcs teljesíti a kulcs definíciójának egyik pontját (funkcionálisan függ tőle a reláció összes
attribútuma), de a másikat nem feltétlenül: nem feltétlenül minimális. Minden kulcs egyben
szuperkulcs is. A szuperkulcs ha minimális, akkor egyben kulcs is.

A kulcs nem szükségszerűen egyedi, ugyanazon relációnak több kulcsa is lehet. Példánkban mind a
𝐺𝑦á𝑟𝑡ó𝑁é𝑣, mind az 𝐸𝑚𝑎𝑖𝑙 attribútum egyértelműen meghatároz egy konkrét gyártót, tehát
mindkettő kulcs. Ha több kulcs is létezik, akkor ezek közül az egyiket ki szoktuk nevezni elsődleges
kulcsnak. Az elsődleges kulcs megválasztása adatbázis-kezelő rendszerekben általában teljesítmény
megfontolások alapján történik: ez sokszor egy egyszerű attribútum szokott lenni, amely egy
sorszámot vagy egy mesterséges azonosítót tartalmaz, nem pedig a modellezett entitás természetes
attribútumai közül áll elő (pl. cím, név stb). Mivel az elsődleges kulcs attribútumaihoz rendelt értékeket
használjuk fel más relációkban külső kulcs attribútum értékekként, illetve az ún. index állományokban
is (lsd. 7. fejezet), célszerű rövid, de garantáltan egyedi értékeket választani. Azokat a kulcsokat,
amelyeket nem választottunk ki elsődleges kulcsnak, kulcsjelöltnek is szoktuk nevezni. Ha tehát a
𝐺𝑦á𝑟𝑡ó𝑁é𝑣 attribútumot elsődleges kulcsnak nevezzük ki, akkor az 𝐸𝑚𝑎𝑖𝑙 attribútum egy kulcsjelölt.

A kulcs lehet összetett is, vagyis állhat több attribútumból. A már jól ismert

38
𝑇𝑒𝑟𝑚é𝑘(𝑁é𝑣, 𝐺𝑦á𝑟𝑡ó𝑁é𝑣, Á𝑟, 𝑅𝑎𝑘𝑡á𝑟𝑘é𝑠𝑧𝑙𝑒𝑡, 𝐾𝑎𝑡𝑒𝑔ó𝑟𝑖𝑎)

relációban például nem feltételezzük, hogy két különböző gyártónak nem lehet azonos nevű terméke,
csak azt, hogy egy gyártó nem gyárt két különböző, de azonos nevű terméket. Vagyis egy terméknév
nem azonosít egyértelműen egy konkrét terméket, csak a gyártó nevével együtt. Tehát a reláció kulcsa:
{𝑁é𝑣, 𝐺𝑦á𝑟𝑡ó𝑁é𝑣}, ami minimális, mivel egy termék név nem azonosít egyértelműen egy gyártót,
illetve egy gyártó gyárthat több terméket is, tehát egy gyártó név nem azonosít egy termék nevet.

3.3 Funkcionális függőségek tulajdonságai és következményei


Amennyiben ismerünk néhány függőséget, amelyet egy reláció minden példánya kielégít, úgy ezen
függőségekből kiindulva más összefüggéseket is levezethetünk. Az egyes attribútumok közti pontos
összefüggések ismerete elengedhetetlen a jó adatbázis séma megalkotásához. Az új függőségek
levezetéséhez az ún. Armstrong axiómákat használhatjuk. Az axiómákat William Ward Armstrong
publikálta 1974-ben megjelent cikkében [3], segítségükkel bizonyítottan előállítható az összes, az
eredeti függőségekből levezethető összefüggés, és csak azok. Az axiómák a következők:

1. Reflexivitás: Ha {𝐵1 , … , 𝐵𝑚 } ⊆ {𝐴1 , … , 𝐴𝑛 }, akkor 𝐴1 … 𝐴𝑛 → 𝐵1 … 𝐵𝑚 .


Az ilyen alakú függőségekkel már találkoztunk korábban: ezek a triviális funkcionális
függőségek, például: 𝑉á𝑟𝑜𝑠 𝐶í𝑚 → 𝑉á𝑟𝑜𝑠

2. Bővíthetőség: Ha 𝐴1 … 𝐴𝑛 → 𝐵1 … 𝐵𝑚 , akkor 𝐴1 … 𝐴𝑛 𝐶1 … 𝐶𝑙 → 𝐵1 … 𝐵𝑚 𝐶1 … 𝐶𝑙 , bármely


{𝐶1 , … , 𝐶𝑙 } attribútum halmazra. Vagyis egy funkcionális függőség két oldala tetszőlegesen
bővíthető ugyanazzal az attribútum halmazzal, a kapott függőség szintén teljesül a relációra.
Például: 𝐼𝑟á𝑛𝑦í𝑡ó𝑠𝑧á𝑚 → 𝑉á𝑟𝑜𝑠 ⇒ 𝐼𝑟á𝑛𝑦í𝑡ó𝑠𝑧á𝑚 𝐶í𝑚 → 𝑉á𝑟𝑜𝑠 𝐶í𝑚

3. Tranzitivitás: Ha 𝐴1 … 𝐴𝑛 → 𝐵1 … 𝐵𝑚 és 𝐵1 … 𝐵𝑚 → 𝐶1 … 𝐶𝑘 , akkor 𝐴1 … 𝐴𝑛 → 𝐶1 … 𝐶𝑘 .
Például:
𝐺𝑦á𝑟𝑡ó𝑁é𝑣 → 𝐼𝑟á𝑛𝑦í𝑡ó𝑠𝑧á𝑚 ∧ 𝐼𝑟á𝑛𝑦í𝑡ó𝑠𝑧á𝑚 → 𝑉á𝑟𝑜𝑠 ⇒ 𝐺𝑦á𝑟𝑡ó𝑁é𝑣 → 𝑉á𝑟𝑜𝑠

Az ismert függőségekből az axiómák egymás után történő alkalmazásával előállíthatunk új


függőségeket. Példaként tekintsük az 𝑅(𝐴, 𝐵, 𝐶, 𝐷) relációt, az 𝐹 = {𝐵𝐶 → 𝐷, 𝐴 → 𝐶} függőségekkel.
Bizonyítsuk, hogy 𝐴𝐵 → 𝐷:

 Az 𝐴 → 𝐶 szabályból a bővíthetőség miatt levezethető az 𝐴𝐵 → 𝐵𝐶 függőség


 Az 𝐴𝐵 → 𝐵𝐶 és 𝐵𝐶 → 𝐷 függőségből a tranzitivitás miatt levezethető az 𝐴𝐵 → 𝐷 függőség.

Az Armstrong axiómákból levezethetők egyéb következtetési szabályok is, amelyek valamennyi


relációra teljesülnek:

4. Egyesíthetőség: Ha 𝐴 → 𝐵 és 𝐴 → 𝐶, akkor 𝐴 → 𝐵𝐶.


A szabály az Armstrong axiómák segítségével a következőképp bizonyítható:
 𝐴 → 𝐵 ⇒ 𝑨𝑨 → 𝑨𝑩: a bővíthetőség alapján mindkét oldalt kiegészítettük az A
attribútummal
 𝐴𝐴 → 𝐴𝐵 ⇒ 𝑨 → 𝑨𝑩, hiszen 𝐴𝐴 = 𝐴
 𝐴 → 𝐶 ⇒ 𝑨𝑩 → 𝑩𝑪 (mindkét oldalt bővítjük 𝐵-vel)

39
 𝐴 → 𝐴𝐵 ∧ 𝐴𝐵 → 𝐵𝐶 ⇒ 𝑨 → 𝑩𝑪, a tranzitivitás alapján.
Például: 𝐺𝑦á𝑟𝑡ó𝑁é𝑣 → 𝑉á𝑟𝑜𝑠 ∧ 𝐺𝑦á𝑟𝑡ó𝑁é𝑣 → 𝐶í𝑚 ⇒ 𝐺𝑦á𝑟𝑡ó𝑁é𝑣 → 𝑉á𝑟𝑜𝑠 𝐶í𝑚

5. Felbonthatóság (dekompozíció): Ha 𝐴 → 𝐵𝐶, akkor 𝐴 → 𝐵, és 𝐴 → 𝐶


 mivel 𝐵 ⊆ 𝐵𝐶, ezért 𝑩𝑪 → 𝑩 a reflexivitás alapján,
 𝐴 → 𝐵𝐶 ∧ 𝐵𝐶 → 𝐵 ⇒ 𝑨 → 𝑩 a tranzitivitás alapján
 hasonlóképp 𝐶 ⊆ 𝐵𝐶 ⇒ 𝑩𝑪 → 𝑪
 𝐴 → 𝐵𝐶 ∧ 𝐵𝐶 → 𝐶 ⇒ 𝑨 → 𝑪 a tranzitivitás alapján
Például: 𝐺𝑦á𝑟𝑡ó𝑁é𝑣 → 𝑉á𝑟𝑜𝑠 𝐶í𝑚 ⇒ 𝐺𝑦á𝑟𝑡ó𝑁é𝑣 → 𝑉á𝑟𝑜𝑠 ∧ 𝐺𝑦á𝑟𝑡ó𝑁é𝑣 → 𝐶í𝑚

6. Pszeudotranzitivitás: Ha 𝐴 → 𝐵 é𝑠 𝐵𝐶 → 𝐷, akkor 𝐴𝐶 → 𝐷
 𝐴 → 𝐵 mindkét oldalát 𝐶-vel bővítve kapjuk, hogy 𝑨𝑪 → 𝑩𝑪
 A tranzitivitás miatt 𝐴𝐶 → 𝐵𝐶 ∧ 𝐵𝐶 → 𝐷 ⇒ 𝑨𝑪 → 𝑫
Példaként tekintsük a következő relációt:
𝑇𝑒𝑟𝑚é𝑘3(𝑁é𝑣, 𝐺𝑦á𝑟𝑡ó𝑁é𝑣, 𝐺𝑦á𝑟𝑡ó𝐴𝑑ó𝑠𝑧á𝑚, 𝐾𝑎𝑡𝑒𝑔ó𝑟𝑖𝑎, Á𝑟, 𝑅𝑎𝑘𝑡á𝑟𝑘é𝑠𝑧𝑙𝑒𝑡)
Tehát a szokásos termék relációt (2.1. fejezet) kiegészítettük a gyártó adószámával. A reláció
kulcsa továbbra is {𝑁é𝑣, 𝐺𝑦á𝑟𝑡ó𝑁é𝑣}, következésképpen igaz, hogy 𝑁é𝑣 𝐺𝑦á𝑟𝑡ó𝑁é𝑣 → Á𝑟.
Egy cég adószáma viszont egyértelműen azonosítja a céget, vagyis teljesül a
𝐺𝑦á𝑟𝑡ó𝐴𝑑ó𝑠𝑧á𝑚 → 𝐺𝑦á𝑟𝑡ó𝑁é𝑣 függőség, vagyis egy gyártó adószáma és egy terméknév
egyértelműen meghatározza az árat:
𝐺𝑦á𝑟𝑡ó𝐴𝑑ó𝑠𝑧á𝑚 𝑁é𝑣 → Á𝑟
Illetve nem csak az árat, hanem valamennyi attribútum értékét, hiszen a
{𝐺𝑦á𝑟𝑡ó𝐴𝑑ó𝑠𝑧á𝑚, 𝑁é𝑣} páros egy kulcsjelölt a 𝑇𝑒𝑟𝑚é𝑘3 relációban.

3.4 Attribútum halmaz lezártja


Egy {𝐴1 , … 𝐴𝑛 } attribútum halmaz 𝐹 függőség halmaz szerinti lezártján azt a legbővebb 𝐵1 … 𝐵𝑚
attribútum halmazt értjük, amelyre igaz, hogy 𝐴1 … 𝐴𝑛 → 𝐵1 … 𝐵𝑚 teljesül valamennyi olyan relációra,
amely az összes 𝐹-beli funkcionális függőséget kielégíti. A halmaz lezártját az {𝐴1 , … , 𝐴𝑛 }+ kifejezéssel
jelöljük. Az attribútum halmaz lezártja tehát az összes olyan attribútumot tartalmazza, amely 𝐹 alapján
funkcionálisan függ a kiindulási halmaztól. A lezárt tartalmazza az eredeti attribútumokat is, hiszen
𝐴1 … 𝐴𝑛 → 𝐴𝑖 , 1 ≤ 𝑖 ≤ 𝑛 esetén.

A lezárt előállítása egy iteratív algoritmussal történik: a kezdeti attribútum halmazhoz hozzávesszük az
összes olyan függőség jobb oldalát, amelyeknek a bal oldalán található összes attribútum már szerepel
a kezdeti halmazban. Ezután a kibővített halmazt tekintjük a kezdetinek, és megismételjük a bővítést
egészen addig, amíg már egyetlen funkcionális függőséget figyelembe véve sem tudunk új elemeket a
halmazhoz venni. Az így előálló halmaz lesz a kiindulási attribútumok lezártja. Formálisan:

Algoritmus: attribútum halmaz lezártja

Bemenet: 𝑿 = {𝑨𝟏 , … , 𝑨𝒏 } attribútum halmaz és 𝑭 funkcionális függőségek halmaza

Kimenet: Az 𝑿 attribútum halmaz 𝑿+ lezártja

Módszer:

40
1. Legyen 𝒀𝟎 = 𝑿, 𝒊 = 𝟎

2. Legyen 𝒀𝒊+𝟏 = 𝒀𝒊 ∪ {𝑾|𝑽 → 𝑾 ∈ 𝑭 ∧ 𝑽 ⊆ 𝒀𝒊 }

Vagyis a halmazhoz hozzávesszük azon függőségek jobb oldalát, amelyek bal oldala már a
halmazban van.

3. Amíg 𝒀𝒊+𝟏 ≠ 𝒀𝒊 , addig 𝒊 ≔ 𝒊 + 𝟏, és ismételjük 2-től

4. Egyébként a keresett halmaz 𝑿+ = 𝒀𝒊

Példaként tekintsük az 𝑅 = {𝐴, 𝐵, 𝐶, 𝐷, 𝐸, 𝐹} relációt, amelyre teljesülnek az 𝐹 = {𝐴 → 𝐵, 𝐵 →


𝐶, 𝐶𝐷 → 𝐸, 𝐷𝐸 → 𝐹} függőségek. Számítsuk ki az {𝐴}+ lezártat, vagyis valamennyi attribútumot, amely
𝐴-tól funkcionálisan függ. A kiindulási attribútum halmazunk tehát 𝑋 = {𝐴}. Keressünk olyan 𝑓 ∈ 𝐹
függőségeket, amelyeknek a bal oldala 𝐴! Egyetlen ilyen függőség van: 𝐴 → 𝐵. Vegyük hozzá az 𝑋
halmazhoz a függőség jobb oldalát, így a kibővített halmaz: {𝐴, 𝐵}. Most keressünk olyan függőséget,
amely bal oldala {𝐴, 𝐵} valamely részhalmaza (A-t már vizsgáltuk, tehát AB vagy B). Az egyetlen ilyen
függőség 𝐵 → 𝐶, ennek jobb oldalával kiegészítve a halmazt kapjuk, hogy {𝐴, 𝐵, 𝐶}. További, eddig nem
feldolgozott függőség, amely bal oldalának valamennyi attribútuma szerepel az {𝐴, 𝐵, 𝐶} halmazban
nem található, vagyis 𝐴+ = {𝐴, 𝐵, 𝐶}. Az attribútum halmaz definíciója alapján tehát 𝐴 → 𝐵 (amit eddig
is tudtunk) és 𝐴 → 𝐶 (levezethető a tranzitivitás segítségével).

Ha azt a kérdést szeretnénk megválaszolni, hogy 𝐶𝐷 → 𝐹 teljesül-e a fenti reláción, akkor tehát {𝐶, 𝐷}
lezártját kell kiszámolnunk. A 𝐷 → 𝐸 függőség miatt a halmazhoz hozzávehetjük az E attribútumot:
{𝐶, 𝐷, 𝐸}, majd ezután a 𝐷𝐸 → 𝐹 függőség miatt az 𝐹 attribútumot is: {𝐶, 𝐷, 𝐸, 𝐹}. Vagyis a keresett
függőség következik az 𝐹 függőséghalmazból.

3.5 Függőséghalmaz lezártja, bázis


Amint láttuk, funkcionális függőségek halmazából az Armstrong axiómák segítségével vagy a
függőségek bal oldalaiból képzett attribútum halmaz lezártjával további függőségeket tudunk
levezetni. Egy 𝐹 függőséghalmaz lezártján azt az 𝐹 + függőséghalmazt értjük, amely úgy áll elő, hogy
𝐹-hez hozzávesszük az összes, 𝐹-ből levezethető függőséget.

Például az {𝐴 → 𝐵} függőséghalmaz lezártja: {𝐴 → 𝐵, 𝐴 → 𝐴𝐵}, ha a triviális függőségektől


eltekintünk. Az {𝐴 → 𝐵, 𝐵 → 𝐶} függőség halmaz lezártja pedig {𝐴 → 𝐵, 𝐴 → 𝐴𝐵, 𝐴 → 𝐶, 𝐴 → 𝐴𝐶, 𝐴 →
𝐵𝐶, 𝐴 → 𝐴𝐵𝐶, 𝐴𝐵 → 𝐴𝐶, 𝐴𝐵 → 𝐴𝐵𝐶, 𝐴𝐶 → 𝐴𝐵, 𝐴𝐶 → 𝐴𝐵𝐶, 𝐵 → 𝐶}. Jól látható, hogy már nagyon
kevés szabály esetén is számos függőséget fog tartalmazni a lezárt, még akkor is, ha a triviális
függőségektől eltekintünk. Az 𝐹 és 𝐺 függőség halmazokat ekvivalensnek mondjuk (F levezethető G-
ből, és G levezethető F-ből), ha 𝐹 + = 𝐺 + . Ekkor G-t F bázisának hívjuk (illetve F-et G bázisának). Egy
függőséghalmaznak nagyon sok bázisa lehet, például a fenti {𝐴 → 𝐵, 𝐵 → 𝐶} függőséghalmaznak
bázisa többek között {𝐴 → 𝐵, 𝐵 → 𝐶, 𝐴 → 𝐶} vagy {𝐴 → 𝐵𝐶, 𝐵 → 𝐶} de akár a lezártja is.

A bázis tehát nem egyértelmű, és számos redundáns (a többi függőség segítségével kifejezhető)
függőséget is tartalmazhat. A gyakorlatban legtöbbször egy olyan bázisra van szükségünk, amelyből a
redundanciákat eltávolítottuk, és a függőségek a lehető legegyszerűbb formában szerepelnek. Ezt
hívjuk minimális bázisnak. A minimális bázisnak három feltételnek kell megfelelnie:

41
1. minden egyes szabály jobb oldalán pontosan egy attribútum szerepel,
2. bármely függőséget eltávolítva az eredmény már nem bázis (nem tartalmaz olyan
függőséget, amely következik a többi függőségből),
3. ha bármely szabály bal oldaláról eltávolítunk egy attribútumot, akkor a kapott
függőséghalmaz már nem bázis.

A minimális bázis előállítása a három kritérium egyenkénti biztosításával történik:

1. Ha egy szabály jobb oldalán több attribútum szerepel, akkor a felbonthatóság miatt
szétszedjük azt olyan szabályokra, amelyek jobb oldalán már csak egy-egy attribútum szerepel.
Vagyis az 𝑋 → 𝐴1 … 𝐴𝑛 alakú szabályokat felbontjuk 𝑋 → 𝐴1 , … , 𝑋 → 𝐴𝑛 szabályokra, ahol
𝐴𝑖 ∉ 𝑋 (a triviális függőségeket elhagyjuk).
2. Minden függőséget megvizsgálunk, hogy levezethető-e a többi segítségével. Ha igen, akkor
elhagyjuk. A vizsgálatot legegyszerűbben úgy tudjuk elvégezni, hogy minden egyes függőség
bal oldalának attribútum halmazára kiszámítjuk annak a lezártját az adott szabály nélkül, és ha
a lezártak között szerepel a szabály jobb oldalán álló összes attribútum, akkor a függőség
levezethető a többi függőségből is, tehát eltávolítható.
3. Azon függőségek esetén, ahol a bal oldal több attribútumból áll, az egyes bal oldali
attribútumokra egyenként megvizsgáljuk, hogy szerepelnek-e a függőség rajtuk kívüli bal oldali
attribútumainak lezártjában: vagyis 𝐴 → 𝐵 esetén valamely 𝑋 ∈ 𝐴-ra teljesül-e 𝑋 ∈
(𝐴 − {𝑋})+ vagy sem. Ha egy 𝑋 attribútumra ez teljesül, akkor eltávolíthatjuk a bal oldalról,
vagyis az 𝐴 → 𝐵 szabályt kicserélhetjük egy (𝐴 − {𝑋}) → 𝐵 szabályra.

Példaként tekintsük az 𝑅(𝐴, 𝐵, 𝐶, 𝐷, 𝐸) relációs sémát, 𝐹 = {𝐴 → 𝐵𝐶, 𝐶 → 𝐷, 𝐴 → 𝐷, 𝐴𝐷 → 𝐸}


függőségekkel. A minimális bázis előállításához egyenként biztosítjuk a kritériumok teljesülését:

1. Egyetlen szabály jobb oldalán szerepel két attribútum (𝐴 → 𝐵𝐶), ezért azt felbontjuk két
függőségre: 𝐹1 = {𝐴 → 𝐵, 𝐴 → 𝐶, 𝐶 → 𝐷, 𝐴 → 𝐷, 𝐴𝐷 → 𝐸}
2. A redundáns függőségek kiszűréséhez kiszámítjuk minden függőség bal oldalának lezártját a
függőség nélkül:
 {𝐴}+ 𝐹−{𝐴→𝐵} = {A, C, D, E}, mivel 𝐵 nincs a lezártban, ezért az 𝐴 → 𝐵 függőség nem
távolítható el.
 {𝐴}+
𝐹−{𝐴→𝐶} = {𝐴, 𝐵}, mivel 𝐶 nincs a lezártban, 𝐴 → 𝐶 sem távolítható el
 {𝐴}+
𝐹−{𝐴→𝐷} = {𝐴, 𝐵, 𝐶, 𝐷, 𝐸}, mivel D szerepel a lezártban, ezért az 𝑨 → 𝑫 függőség
elhagyható, hiszen a többi szabály is kifejezi ezt a függőséget, mégpedig az 𝐴 → 𝐶, 𝐶 →
𝐷 szabályokból következik a tranzitív tulajdonság miatt.
 {𝐶}+
𝐹−{𝐶→𝐷} = {𝐶}, a 𝐶 → 𝐷 függőség nem hagyható el.
 {𝐴, 𝐷}+
𝐹−{𝐴𝐷→𝐸} = {𝐴, 𝐵, 𝐶, 𝐷}, az 𝐴𝐷 → 𝐸 függőség sem hagyható el.

𝐹2 = {𝐴 → 𝐵, 𝐴 → 𝐶, 𝐶 → 𝐷, 𝐴𝐷 → 𝐸}

3. Egyetlen függőség bal oldala áll egynél több attribútumból (𝐴𝐷 → 𝐸), ezért elég ezt az egyet
megvizsgálnunk. Mivel 𝐷 ∈ {𝐴}+, ezért 𝑫 elhagyható a függőség bal oldaláról.

𝐹 minimális bázisa tehát:

𝐹3 = {𝐴 → 𝐵, 𝐴 → 𝐶, 𝐴 → 𝐸, 𝐶 → 𝐷}

42
A minimális bázis nem feltétlen egyértelmű. Tekintsük a 𝐺 = {𝐴 → 𝐵, 𝐵 → 𝐶, 𝐶 → 𝐴}
függőséghalmazt. Az egyes függőségek bal és jobb oldalán csupán 1-1 attribútum szerepel, viszont
bármelyik függőség kifejezhető a másik kettő segítségével a tranzitív tulajdonság alkalmazásával.
Ennek megfelelően 3 minimális bázisa is létezik a 𝐺 halmaznak:

 {𝐴 → 𝐵, 𝐵 → 𝐶}
 {𝐵 → 𝐶, 𝐶 → 𝐴}
 {𝐶 → 𝐴, 𝐴 → 𝐵}

3.6 Funkcionális függőségek vetítése


Amikor egy 𝑅 reláción vetítést hajtunk végre (𝜋𝐿 (𝑅)), és csak bizonyos attribútumait (𝐿) hagyjuk meg,
jogosan vetődik fel a kérdés, hogy mi történik a funkcionális függőségek azon 𝐹 halmazával, amelyek
az eredeti reláción teljesültek. Mit állíthatunk, milyen függőségek állnak fenn az eredmény reláció
attribútumai között? A választ a funkcionális függőségek vetítése által kaphatjuk meg. A vetítés
eredményeként egy olyan 𝐹′ függőséghalmazt kapunk, amely az összes olyan függőséget tartalmazza,
amely i) 𝐹-ből következik, és ii) csak 𝐿 attribútumaiból áll. Természetesen az 𝐹′ halmazt a könnyebb
kezelhetőség érdekében leegyszerűsíthetjük, képezhetjük annak akár egy minimális bázisát is. A
függőségek vetítését végző algoritmus ezek után a következőképp fogalmazható meg:

Algoritmus: funkcionális függőségek vetítése

Bemenet: 𝑹 reláció, 𝑹′ = 𝝅𝑳 (𝑹), 𝑭 függőséghalmaz, amely R-re teljesül.

Kimenet: Az 𝑹′-ben érvényes 𝑭′ függőséghalmaz.

Módszer:

1. 𝑭’ = {}

2. 𝑹’ minden X attribútum részhalmazára számítsuk ki 𝑿+ -t 𝑭 alapján

 Eközben felhasználhatunk nem R’-beli attribútumokat is.

3. Adjuk hozzá 𝑭’-höz az összes 𝑿 → 𝑨 függőséget, ahol A mind 𝑿+ , mind 𝑹′ attribútuma.

 Tehát 𝑭’-höz azokat a függőségeket adjuk, amelyek levezethetők 𝑭-ből, de csak 𝑹’-beli
attribútumokat használnak.

4. Ha 𝑭’ nem minimális, minimalizáljuk a tanult módszerrel.

Példaként tekintsük az 𝑅(𝐴, 𝐵, 𝐶, 𝐷, 𝐸) relációt az 𝐹 = {𝐴 → 𝐵, 𝐴 → 𝐶, 𝐵 → 𝐷, 𝐷 → 𝐸} függőségekkel.


Végezzük el az 𝑅 ′ = 𝜋𝐴,𝐶,𝐷,𝐸 (𝑅) vetítést. Milyen függőségek teljesülnek az 𝑅′ relációra? Az algoritmus
szerint az {𝐴, 𝐶, 𝐷, 𝐸} attribútum halmaz minden részhalmazára ki kell számítani annak lezártját.
Nyilvánvaló, hogy az összes attribútumot tartalmazó halmazra nincs értelme ezt elvégezni, hiszen nem
találnánk nem triviális függőségeket. Ha pedig egy egy-attribútumos halmaz lezártja már eleve az
összes attribútumot tartalmazza, akkor nincs értelme ezen attribútumot más attribútumokkal együtt
is megvizsgálni. Kezdjük először az egy attribútumos részhalmazokkal:

43
1. {𝐴}+ = {𝐴, 𝐵, 𝐶, 𝐷}, vagyis 𝐴 → 𝐵, 𝐴 → 𝐶, 𝐴 → 𝐷, 𝐴 → 𝐸. Mivel 𝐵 nem szerepel a vetített
attribútumok között, ezért 𝐹1′ = {𝐴 → 𝐶, 𝐴 → 𝐷, 𝐴 → 𝐸}.
2. {𝐶}+ = {𝐶}, vagyis nem találtunk nemtriviális függőséget.
3. {𝐷}+ = {𝐷, 𝐸}, tehát 𝐷 → 𝐸. Így 𝐹2′ = {𝐴 → 𝐶, 𝐴 → 𝐷, 𝐴 → 𝐸, 𝐷 → 𝐸}
4. {𝐸}+ = {𝐸}, nem eredményez új függőséget.

Mivel {𝐴} lezártja az összes attribútumot tartalmazza, ezért csak az 𝐴-t nem tartalmazó részhalmazokat
érdemes tovább vizsgálni:

5. {𝐶, 𝐷}+ = {𝐶, 𝐷, 𝐸}, nem eredményez új függőséget, hiszen 𝐷 → 𝐸 már ismert
6. Hasonlóképpen {𝐶, 𝐸}, {𝐷, 𝐸} és {𝐶, 𝐷, 𝐸} sem eredményez új függőséget.

A vetített relációban tehát az 𝐹2′ = {𝐴 → 𝐶, 𝐴 → 𝐷, 𝐴 → 𝐸, 𝐷 → 𝐸} függőségek teljesülnek. A


függőséghalmaz nem minimális, minimalizálás során rájöhetünk, hogy az 𝐴 → 𝐸 függőség redundáns,
hiszen következik az 𝐴 → 𝐷, 𝐷 → 𝐸 szabályokból a tranzitív tulajdonságok alapján. Így 𝐹2′ minimális
bázisa: {𝐴 → 𝐶, 𝐴 → 𝐷, 𝐷 → 𝐸}.

3.7 Anomáliák, relációk dekompozíciója


Tipikus adatbázis tervezési hiba, ha túl sok tulajdonságot próbálunk egy relációba zsúfolni. Ez a
tervezési hiba különböző problémákhoz vezet, amelyeket anomáliáknak nevezünk. Példaként
tekintsük a 𝑇𝑒𝑟𝑚é𝑘𝐺𝑦á𝑟𝑡ó relációt (3.2. táblázat), amely mind a forgalmazott termékeket, mind az
őket gyártó cégek adatait tárolja.

Név Ár Raktárkészlet Kategória GyátóNév Cím Email


Dohányzóasztal 12.000 3 Nappali Nagy Bútor Kft 5000 Szolnok Fa u 2 nb@example.org
Kanapé 150.000 3 Nappali Nagy Bútor Kft 5000 Szolnok Fa u 2 nb@example.org
Szék 22.000 15 Iroda Fabric Rt 9012 Győr Ág u 15 fabric@example.org
Hintaágy 32.000 1 Kert Bútorgyár 1111 Bp. Hős krt 3. xyb@example.org

3.2. táblázat: TermékGyártó reláció

A reláción jól megfigyelhető anomáliák:

1. Redundancia: ugyanazt az adatot többször is eltároljuk. A példában a Nagy Bútor Kft adatai
kétszer is szerepelnek, mivel két termékét is forgalmazzuk a cégnek. Természetesen, ha több
terméket is forgalmazunk egy cégtől, akkor annyiszor tároljuk el (feleslegesen) a cég adatait,
ahány terméket forgalmazunk tőlük. Ez nyilvánvalóan pazarolja a tárolási kapacitást.
2. Módosítási anomália: a redundáns tárolásból kifolyólag bizonyos adatok megváltoztatását
több helyen is el kell végeznünk. Ha a Nagy Bútor Kft e-mail címe megváltozik, és ezt
szeretnénk az adatbázisunkban is eltárolni, akkor nem elég egy helyen átírnunk az e-mail
címet, hanem annyi helyen kell ezt elvégeznünk, ahány termékét forgalmazzuk.
3. Törlési anomália: mivel egy n-es egyszerre több különálló dologról is hordoz információt,
ezért egy n-es eltávolításával egyszerre több dologról törlünk információt. Tegyük fel, hogy
befejezzük a Hintaágy nevű termék forgalmazását, és töröljük a kapcsolódó bejegyzést. Ezzel
egyúttal a Bútorgyár nevű gyártó összes adatát is töröljük, még ha ez nem is állt
szándékunkban.

44
Az anomáliák megszűntetésére a megoldás a reláció dekompozíciója, szétszedése kisebb darabokra.
Az 𝑅(𝐴1 , … , 𝐴𝑛 ) reláció dekompozíciójakor úgy bontjuk azt szét 𝑆(𝐵1 , … , 𝐵𝑚 ) és 𝑇(𝐶1 , … , 𝐶𝑘 )
relációkra, hogy

1. a kapott két reláció sémája az eredeti reláció összes attribútumát tartalmazza:


{𝐵1 , … , 𝐵𝑚 } ∪ {𝐶1 , … , 𝐶𝑘 } = {𝐴1 , … , 𝐴𝑛 }
2. a kapott két reláció attribútumai között legyen átfedés:
{𝐵1 , … , 𝐵𝑚 } ∩ {𝐶1 , … , 𝐶𝑘 } ≠ ∅
3. az eredmény relációk az eredeti reláció vetítésével jönnek létre:
𝑆 = 𝜋𝐵1 ,…,𝐵𝑚 (𝑅)
𝑇 = 𝜋𝐶1 ,…,𝐶𝑘 (𝑅)

Dekomponáljuk a 𝑇𝑒𝑟𝑚é𝑘𝐺𝑦á𝑟𝑡ó(𝑁é𝑣, Á𝑟, 𝑅𝑎𝑘𝑡á𝑟𝑘é𝑠𝑧𝑙𝑒𝑡, 𝐾𝑎𝑡𝑒𝑔ó𝑟𝑖𝑎, 𝐺𝑦á𝑡ó𝑁é𝑣, 𝐶í𝑚, 𝐸𝑚𝑎𝑖𝑙)


relációt két részre úgy, hogy a 𝐺𝑦á𝑟𝑡ó𝑁é𝑣 attribútum mindkettőben szerepeljen:

𝑇𝑒𝑟𝑚é𝑘(𝑁é𝑣, Á𝑟, 𝑅𝑎𝑘𝑡á𝑟𝑘é𝑠𝑧𝑙𝑒𝑡, 𝐾𝑎𝑡𝑒𝑔ó𝑟𝑖𝑎, 𝐺𝑦á𝑟𝑡ó𝑁é𝑣) és 𝐺𝑦á𝑟𝑡ó(𝐺𝑦á𝑟𝑡ó𝑁é𝑣, 𝐶í𝑚, 𝐸𝑚𝑎𝑖𝑙)

A vetítés elvégzése után a már ismert Termék és Gyártó relációkat kapjuk (3.3. táblázat, 3.4. táblázat).

Név Ár Raktárkészlet Kategória GyártóNév


Kanapé 150.000 3 Nappali Nagy Bútor Kft
Dohányzóasztal 12.000 3 Nappali Nagy Bútor Kft
Szék 22.000 15 Iroda Fabric Rt
Hintaágy 32.000 1 Kert Bútorgyár

3.3. táblázat: A Termék reláció

GyártóNév Cím Email


Nagy Bútor Kft 5000 Szolnok Fa u 2 nb@example.org
Fabric Rt 9012 Győr Ág u 15 fabric@example.org
Bútorgyár 1111 Bp. Hős krt 3. xyb@example.org

3.4. táblázat: Gyártó reláció

A dekompozícióval megszűntettük a gyártók adatainak redundáns tárolását: most már minden egyes
gyártóról csupán egy n-esben tárolunk adatokat. Megoldottuk a módosítási anomáliát, hiszen, ha egy
gyártónak változik valamelyik tulajdonsága, akkor elég csupán egy n-esben módosítani. (Kivétel ez alól
a gyártó neve, mivel az kulcsként és külső kulcsként is funkcionál, így annak megváltozása esetén a
Termék relációban is frissíteni kell a hivatkozásokat. Erre szolgál megoldásként egy mesterséges kulcs
– pl. egy futó sorszám – használata, amely soha nem változik meg). A dekompozíció szintén
megszűntette a törlési anomáliát, hiszen egy gyártó utolsó termékének törlése után a gyártó alap
adatai még mindig rendelkezésre fognak állni a Gyártó relációban. A dekompozíció szükség esetén
rekurzívan tovább alkalmazható, tehát a már eredményül kapott kisebb relációkat is tovább
bonthatjuk. De természetesen nem mindegy, hogy a dekompozíciót hogy hajtjuk végre, mely
attribútumokat tesszük az egyik, melyeket a másik relációba, és melyek jelennek meg mindkét
relációban.

A dekompozíció módja döntően befolyásolja, hogy az eredeti információ tartalmat helyre tudjuk-e
állítani a dekomponált táblázatokból: nem veszítünk adatot, és nem jelennek meg érvénytelen adatok

45
a helyreállítás során. A redundáns relációk felbontására szisztematikus módszereket az ún. normál
formák, és a normál formára alakító algoritmusok biztosítanak, amelyeket a továbbiakban részletesen
is ismertetünk.

3.8 Boyce-Codd normál forma (BCNF)


A 3.7 fejezetben ismertetett anomáliák oka tulajdonképpen az, hogy a TermékGyártó (3.2. táblázat)
relációban nem csak a kulcs attribútumoktól függ funkcionálisan az összes többi attribútum. Erre példa
a 𝐺𝑦á𝑟𝑡ó𝑁é𝑣 → 𝐶í𝑚, illetve 𝐺𝑦á𝑟𝑡ó𝑁é𝑣 → 𝐸𝑚𝑎𝑖𝑙 függőségek. A funkcionális függőségből
következően egy konkrét gyártó névhez mindig pontosan egy cím, illetve pontosan egy e-mail tartozik.
Ezért mivel a bejegyzéseket termékenként tároljuk, amelyekhez rendelhetjük ugyanazt a gyártót is,
ezért egyazon gyártóhoz redundánsan eltároljuk a hozzá tartozó adatokat is.

Ezt a lehetőséget tiltja meg a Boyce-Codd normál forma (BCNF) [4]: Egy 𝑅 reláció akkor és csak akkor
van Boyce-Codd normál formában, ha a relációban minden nem triviális 𝐴1 … 𝐴𝑛 → 𝐵1 … 𝐵𝑚
funkcionális függőségre {𝐴1 , … , 𝐴𝑛 } szuperkulcs 𝑅-ben. Másképp fogalmazva: minden nem triviális
funkcionális függőség bal oldalában szerepel egy kulcs.

Vegyük észre, hogy a definíció nem szabja meg, hogy ugyanannak a kulcsnak kell szerepelnie minden
bal oldalban, a kulcs pedig nem feltétlen egyedi egy relációban.

A 𝑇𝑒𝑟𝑚é𝑘𝐺𝑦á𝑟𝑡ó reláció egyértelműen sérti a BCNF kritériumát: a reláció kulcsa a {𝑁é𝑣, 𝐺𝑦á𝑟𝑡ó𝑁é𝑣}
attribútum páros, hiszen ahogyan már korábban is megállapítottuk, a termék neve nem feltétlen
egyedi, de a gyártó nevével együtt már bizonyosan az. Egy konkrét termék pedig egyértelműen
meghatározza a saját és gyártója tulajdonságait. Más attribútum kombinációt, amely egyértelműen
meghatározna egy terméket, nem találunk. Ebből következően a nem triviális funkcionális függőségek
bal oldala csak ez az attribútum páros, vagy ennek egy kibővített változata lehet. A 𝐺𝑦á𝑟𝑡ó𝑁é𝑣 → 𝐶í𝑚
ill. 𝐺𝑦á𝑟𝑡ó𝑁é𝑣 → 𝐸𝑚𝑎𝑖𝑙 függőségek pedig ennek nem felelnek meg.

A 𝐺𝑦á𝑟𝑡ó reláció (3.4. táblázat) ezzel szemben BCNF-ban van, hiszen benne csak az előbbi két függőség
áll fenn, mivel ugyanarra a címre több gyártó is be lehet jegyezve, illetve egy e-mail cím (ha az például
egy kisvállalkozás ügyvezetőjének a címe) több gyártót is azonosíthat (pl különböző profilú
vállalkozások közös tulajdonossal). Tehát az 𝐸𝑚𝑎𝑖𝑙 és 𝐶í𝑚 attribútumoktól nem függ funkcionálisan
semelyik másik attribútum. Mivel a 𝐺𝑦á𝑟𝑡ó𝑁é𝑣 attribútumtól viszont funkcionálisan függ a többi, ezért
az lesz a reláció kulcsa, így teljesül a BCNF kritériuma, amely szerint minden függőség bal oldalán
szuperkulcs kell, hogy álljon.

Azt, hogy az adatbázis sémánk BCNF-ben legyen, úgy tudjuk elérni, hogy a nem BCNF-ben lévő
relációkat kisebb részekre dekomponáljuk, amelyekben már biztosított a BCNF. Természetesen nem
mindegy, hogy hogy darabolunk fel egy relációt, mert lehet, hogy az eredményre teljesül a BCNF, de
közben információt vesztünk. A dekomponálás során keresünk egy olyan funkcionális függőséget,
amely sérti a normál formát, tehát a bal oldala nem szuperkulcs. Ezután képezzük a bal oldali
attribútumok lezártját, az így kapott attribútumokat tesszük az egyik relációba (amelynek a kulcsa –
vagy szuperkulcsa – a függőség bal oldala lesz, hiszen tőle a reláció összes attribútuma funkcionálisan
függ), a másik relációba pedig a lezárton kívüli attribútumokat és a vizsgált függőség bal oldalának
attribútumait (amelyek így külső kulcsként fognak funkcionálni). A műveletsorozatot addig ismételjük
a keletkezett relációkra, amíg találunk bennük BCNF-t sértő függőséget. Amikor egy relációt két részre

46
bontunk, akkor a relációhoz tartozó függőségeket is vetítenünk kell a két rész attribútumaira, mivel a
kisebb relációkon az eredeti függőséghalmaznak már csak egy része fog teljesülni.

Algoritmus: Reláció dekomponálása BCNF-ben lévő kisebb relációkra

Bemenet: 𝑹 reláció, és 𝑭 függőség halmaz.

Kimenet: 𝑹 dekompozíciója kisebb relációkra, amelyek mind BCNF-ben vannak

Módszer:

1. 𝑅0 = 𝑅, 𝐹0 = 𝐹 kezdeti értékek
2. Ha 𝑅𝑖 BCNF-ben van, akkor a válasz {𝑅𝑖 }
3. Legyen 𝑋 → 𝑌 ∈ 𝐹𝑖 egy BCNF-et sértő függőség
4. Számítsuk ki 𝑋 + -t
5. Legyen 𝑅𝑖+1 = 𝑋 + és 𝑅𝑖+2 pedig 𝑅 𝑋 + nélküli attribútumai és 𝑋 uniója; 𝐹𝑖+1 és 𝐹𝑖+2 legyenek
𝐹𝑖 projekciói 𝑅𝑖+1 -re és 𝑅𝑖+2 -re.
6. Végezzük el a felbontást 2.-től rekurzívan 𝑅𝑖+1 -re és 𝑅𝑖+2 -re. A válasz a két felbontás uniója.

Példaként tekintsük az

Ö𝑠𝑠𝑧𝑉á𝑠á𝑟𝑙á𝑠(𝑇𝑒𝑟𝑚é𝑘𝐾ó𝑑, 𝑇𝑒𝑟𝑚é𝑘𝑁é𝑣, 𝐾𝑎𝑡𝑒𝑔ó𝑟𝑖𝑎, 𝑆𝑧í𝑛, 𝑉𝑒𝑣ő𝐾ó𝑑, 𝑉𝑒𝑣ő𝑁é𝑣, 𝐼𝑟𝑠𝑧á𝑚, 𝑉á𝑟𝑜𝑠, 𝐶í𝑚, 𝑇𝑒𝑙𝑒𝑓𝑜𝑛, 𝐷𝑎𝑟𝑎𝑏)

relációt, amely a termékekre leadott összesített megrendeléseket hivatott rögzíteni vásárlónként.


Eltároljuk a megvásárolt termék jellemzőit (termék kód, név, kategória, szín), a terméket vásárló
személy adatait (név, cím, telefonszám), és hogy a vásárló az adott termékből összesen hány darabot
rendelt eddig. A reláció egy példányát a 3.5. táblázat illusztrálja.
TermékKód TermékNév Kategória Szín VevőKód VevőNév Irszám Város Cím Telefon Darab

301232 Kerti szék Kert Fehér 87438 Nagy János 1111 Bp Ág u 1 123-456 6
412340 Műanyag Kert Fehér 87438 Nagy János 1111 Bp Ág u 1 123-456 1
asztal
301232 Kerti szék Kert Fehér 98142 Kovács Péter 7622 Pécs Tél u 3 234-567 2
674573 Fotel Nappali Barna 01845 Kiss Norbert 9028 Győr Ősz u 6 345-678 1

3.5. táblázat: A ÖsszVásárlás reláció

A relációban a következő függőségek érvényesek (𝐹 halmaz):

𝑇𝑒𝑟𝑚é𝑘𝐾ó𝑑 → 𝑇𝑒𝑟𝑚é𝑘𝑁é𝑣 𝐾𝑎𝑡𝑒𝑔ó𝑟𝑖𝑎 𝑆𝑧í𝑛

𝑉𝑒𝑣ő𝐾ó𝑑 → 𝑉𝑒𝑣ő𝑁é𝑣 𝐼𝑟𝑠𝑧á𝑚 𝐶í𝑚 𝑇𝑒𝑙𝑒𝑓𝑜𝑛

𝐼𝑟𝑠𝑧á𝑚 → 𝑉á𝑟𝑜𝑠

𝑇𝑒𝑟𝑚é𝑘𝐾ó𝑑 𝑉𝑒𝑣ő𝐾ó𝑑 → 𝐷𝑎𝑟𝑎𝑏

Ahhoz, hogy meg tudjuk állapítani, hogy mely függőségek sértik a BCNF-t, először meg kell keresnünk
a reláció kulcsát. Érdemes azokkal az attribútumokkal kezdeni a vizsgálatot, amelyek semelyik másiktól
sem függnek funkcionálisan (ha vannak ilyenek), hiszen azok az attribútumok az összes kulcs részének
kell, hogy legyenek. Ezek az attribútumok: 𝑇𝑒𝑟𝑚é𝑘𝐾ó𝑑, 𝑉𝑒𝑣ő𝐾ó𝑑. Ezeknek az attribútumoknak a
lezártja a reláció valamennyi attribútumát tartalmazza, ezért ez a páros biztosan szuperkulcs, és mivel

47
minimális is (hiszen egyik sem függ funkcionálisan a másiktól), ezért kulcs is. Mellesleg, mivel ezek az
attribútumok valamennyi kulcs részének kell, hogy legyenek, de már önmagukban is kulcsot alkotnak,
ezért biztosan az egyetlen kulcs is egyben.

Ezután válasszunk ki egy olyan függőséget, amelynek a bal oldala nem szuperkulcs. Mivel több ilyen is
létezik, mi válasszuk mondjuk a 𝑇𝑒𝑟𝑚é𝑘𝐾ó𝑑 → 𝑇𝑒𝑟𝑚é𝑘𝑁é𝑣 𝐾𝑎𝑡𝑒𝑔ó𝑟𝑖𝑎 𝑆𝑧í𝑛 függőséget.
(Választhatnánk másik függőséget is, ekkor nem feltétlen azonos, de szintén helyes eredményre
jutnánk végül). Képezzük a bal oldal lezártját:

{𝑇𝑒𝑟𝑚é𝑘𝐾ó𝑑}+ = {𝑇𝑒𝑟𝑚é𝑘𝐾ó𝑑, 𝑇𝑒𝑟𝑚é𝑘𝑁é𝑣, 𝐾𝑎𝑡𝑒𝑔ó𝑟𝑖𝑎, 𝑆𝑧í𝑛}.

Ennek megfelelően a relációt két részre bontjuk:

𝑹𝟏 {𝑻𝒆𝒓𝒎é𝒌𝑲ó𝒅, 𝑻𝒆𝒓𝒎é𝒌𝑵é𝒗, 𝑲𝒂𝒕𝒆𝒈ó𝒓𝒊𝒂, 𝑺𝒛í𝒏}

Illetve

𝑅2 {𝑇𝑒𝑟𝑚é𝑘𝐾ó𝑑, 𝑉𝑒𝑣ő𝐾ó𝑑, 𝑉𝑒𝑣ő𝑁é𝑣, 𝐼𝑟𝑠𝑧á𝑚, 𝑉á𝑟𝑜𝑠, 𝐶í𝑚, 𝑇𝑒𝑙𝑒𝑓𝑜𝑛, 𝐷𝑎𝑟𝑎𝑏}

Az egyes relációkhoz tartozó függőségeket az 𝐹 függőséghalmaz 𝑅1 -re, illetve 𝑅2 -re történő vetítésével
kapjuk meg:

𝐹1 = {𝑇𝑒𝑟𝑚é𝑘𝐾ó𝑑 → 𝑇𝑒𝑟𝑚é𝑘𝑁é𝑣 𝐾𝑎𝑡𝑒𝑔ó𝑟𝑖𝑎 𝑆𝑧í𝑛}

𝐹2 = {𝑉𝑒𝑣ő𝐾ó𝑑 → 𝑉𝑒𝑣ő𝑁é𝑣 𝐼𝑟𝑠𝑧á𝑚 𝐶í𝑚 𝑇𝑒𝑙𝑒𝑓𝑜𝑛, 𝐼𝑟𝑠𝑧á𝑚 → 𝑉á𝑟𝑜𝑠,


𝑇𝑒𝑟𝑚é𝑘𝐾ó𝑑 𝑉𝑒𝑣ő𝐾ó𝑑 → 𝐷𝑎𝑟𝑎𝑏}

Az algoritmust az 𝑅1 és 𝑅2 relációkon megismételjük. Az 𝑅1 relációban a kulcs a 𝑇𝑒𝑟𝑚é𝑘𝐾ó𝑑


attribútum, hiszen a relációt ezen attribútum lezártjaként állítottuk elő. A relációhoz tartozó 𝐹1
függőséghalmaz valamennyi függőségére teljesül, hogy azok bal oldalán található attribútumok
(szuper)kulcsot alkotnak, tehát az 𝑅1 reláció BCNF-ban van.

Az 𝑅2 reláció kulcsa R-hez hasonlóan szintén 𝑇𝑒𝑟𝑚é𝑘𝐾ó𝑑, 𝑉𝑒𝑣ő𝐾ó𝑑. A BCNF definícióját sérti a
𝑉𝑒𝑣ő𝐾ó𝑑 → 𝑉𝑒𝑣ő𝑁é𝑣 𝐼𝑟𝑠𝑧á𝑚 𝐶í𝑚 𝑇𝑒𝑙𝑒𝑓𝑜𝑛 függőség, tehát a 𝑉𝑒𝑣ő𝐾ó𝑑 lezártjában szereplő
attribútumokat egy új relációba helyezzük (𝑅3 ), a maradék attribútumot és a 𝑉𝑒𝑣ő𝐾ó𝑑 attribútumot
szintén (𝑅4 ):

𝑅3 (𝑉𝑒𝑣ő𝐾ó𝑑, 𝑉𝑒𝑣ő𝑁é𝑣, 𝐼𝑟𝑠𝑧á𝑚, 𝑉á𝑟𝑜𝑠, 𝐶í𝑚, 𝑇𝑒𝑙𝑒𝑓𝑜𝑛)

𝑹𝟒 (𝑻𝒆𝒓𝒎é𝒌𝑲ó𝒅, 𝑽𝒆𝒗ő𝑲ó𝒅, 𝑫𝒂𝒓𝒂𝒃)

A két relációhoz tartozó vetített függőségek:

𝐹3 (𝑉𝑒𝑣ő𝐾ó𝑑 → 𝑉𝑒𝑣ő𝑁é𝑣 𝐼𝑟𝑠𝑧á𝑚 𝐶í𝑚 𝑇𝑒𝑙𝑒𝑓𝑜𝑛, 𝐼𝑟𝑠𝑧á𝑚 → 𝑉á𝑟𝑜𝑠)

𝐹4 (𝑇𝑒𝑟𝑚é𝑘𝐾ó𝑑 𝑉𝑒𝑣ő𝐾ó𝑑 → 𝐷𝑎𝑟𝑎𝑏)

Az 𝑅4 relációban a kulcs a 𝑇𝑒𝑟𝑚é𝑘𝐾ó𝑑, 𝑉𝑒𝑣ő𝐾ó𝑑 páros, hiszen a relációhoz tartozó egyetlen nem
triviális függőség bal oldalán ők szerepelnek, és lezártjuk a reláció összes attribútumát tartalmazza.
Következésképpen az 𝑅4 reláció BCNF-ban van.

48
Az 𝑅3 reláció kulcsa a 𝑉𝑒𝑣ő𝐾ó𝑑 attribútum (hiszen lezártja a reláció összes attribútumát tartalmazza).
Következésképpen az 𝐼𝑟𝑠𝑧á𝑚 → 𝑉á𝑟𝑜𝑠 függőség sérti a normál formát. Az 𝐼𝑟𝑠𝑧á𝑚 attribútum
lezártjában található attribútumokat egy új relációba helyezzük (𝑅5 ), a többi attribútumot valamint az
𝐼𝑟𝑠𝑧á𝑚 attribútumot szintén (𝑅6 ):

𝑹𝟓 (𝑰𝒓𝒔𝒛á𝒎, 𝑽á𝒓𝒐𝒔)

𝑹𝟔 (𝑽𝒆𝒗ő𝑲ó𝒅, 𝑽𝒆𝒗ő𝑵é𝒗, 𝑰𝒓𝒔𝒛á𝒎, 𝑪í𝒎, 𝑻𝒆𝒍𝒆𝒇𝒐𝒏)

A kapcsolódó vetített függőségek:

𝐹5 (𝐼𝑟𝑠𝑧á𝑚 → 𝑉á𝑟𝑜𝑠)

𝐹6 (𝑉𝑒𝑣ő𝐾ó𝑑 → 𝑉𝑒𝑣ő𝑁é𝑣 𝐼𝑟𝑠𝑧á𝑚 𝐶í𝑚 𝑇𝑒𝑙𝑒𝑓𝑜𝑛)

Az 𝑅5 és 𝑅6 relációk kulcsai rendre 𝐼𝑟𝑠𝑧á𝑚, illetve 𝑉𝑒𝑣ő𝐾ó𝑑, a két reláció nem triviális függőségeinek
bal oldalán (szuper)kulcs áll, tehát a relációk BCNF-ben vannak, az algoritmus itt leáll. Az eredmény
tehát az 𝑅1 reláció (amely a termék adatokat tartalmazza), az 𝑅6 (amely a vevők adatait tartalmazza),
az 𝑅5 (amely az irányítószám – város összerendelést tartalmazza), valamint az 𝑅4 (amely egy vevő –
termék pároshoz megmondja, hogy az adott vevő mennyit rendelt az adott termékből eddig összesen).
A mintaadatoknak megfelelő reláció példányokat a 3.6. táblázat - 3.9. táblázat ismerteti.

TermékKód TermékNév Kategória Szín


VevőKód VevőNév Irszám Cím Telefon
301232 Kerti szék Kert Fehér
87438 Nagy János 1111 Ág u 1 123-456
412340 Műanyag Kert Fehér
asztal 98142 Kovács Péter 7622 Tél u 3 234-567
674573 Fotel Nappali Barna 01845 Kiss Norbert 9028 Ősz u 6 345-678

3.6. táblázat: R1: termék adatok 3.7. táblázat: R6: vevő adatok

TermékKód VevőKód Darab


Irszám Város 301232 87438 6
1111 Bp 412340 87438 1
7622 Pécs 321240 98142 2
9028 Győr 674573 01845 1

3.8. táblázat: R5: város adatok 3.9. táblázat: R4: vásárlás adatok

Jól látható, hogy megszűnt mind a termékek, mind a vevők és a város adatok redundáns tárolása is.

3.8.1 Két-attribútumos relációk

Bizonyítható, hogy minden két-attribútumos reláció BCNF-ben van. Egy 𝑅(𝐴, 𝐵) reláció esetén nem
triviális függőségeknek 4-féle variációja fordulhat elő:

1. A relációra egyetlen nem triviális funkcionális függőség sem teljesül. Ekkor a reláció kulcsa az
𝐴, 𝐵 páros. Mivel nem létezik nem triviális függőség, ezért teljesül a BCNF feltétele, amely
szerint valamennyi ilyen függőség bal oldalán szuperkulcs áll.
2. 𝐴 → 𝐵 teljesül, de 𝐵 → 𝐴 nem. Ekkor 𝐴 a kulcs, és teljesül, hogy valamennyi nem triviális
függőség (𝐴 → 𝐵) bal oldala szuperkulcs.
3. 𝐵 → 𝐴 teljesül, de 𝐴 → 𝐵 nem. Ez az előző esettel ekvivalens (𝐴 és 𝐵 szerepet cserélt).

49
4. 𝐴 → 𝐵 és 𝐵 → 𝐴 is teljesül. Ekkor 𝐴 és 𝐵 is kulcs, így szintén igaz, hogy minden nem triviális
függőség bal oldala tartalmazza valamelyiket.

3.8.2 Helyreállítás

A 3.8.1 fejezetben ismertetett példa alapján minden két-attribútumos reláció BCNF-ben van.
Felmerülhet a kérdés, hogy akkor miért van szükség a BCNF-ra alakító dekompozíciós algoritmusra,
miért nem daraboljuk a relációkat egyszerűen két-attribútumos részekre? A válasz pedig az, hogy a
dekompozíciós algoritmust alkalmazva az eredeti reláció (az eredeti információ) helyreállítható a már
BCNF-ben lévő relációk természetes illesztésével. A relációk tetszőleges darabolása esetén pedig ez
nem feltétlen lenne így, vagyis információt veszíthetnénk. Egy R relációs séma 𝑅1 , 𝑅2 , … , 𝑅𝑛
felbontását veszteségmentesnek nevezünk, ha 𝑅 = 𝑅1 ⋈ 𝑅2 ⋈ ⋯ ⋈ 𝑅𝑛 .

Tekintsük a 3.10. táblázatban ismertetett 𝑅 relációt, amelyre egyetlen nem triviális függőség sem
teljesül. A relációt vetítsük le az 𝐴, 𝐵, illetve 𝐵, 𝐶 attribútumokra: 𝑅1 = 𝜋𝐴,𝐵 (𝑅) (3.11. táblázat), illetve
𝑅2 = 𝜋𝐵,𝐶 (𝑅) (3.12. táblázat).

A B C
a 1 2 A C B C
b 1 2 a 2 1 2
b 2 2 b 2 2 2

3.10. táblázat: R 3.11. táblázat: R1=πA,B(R) 3.12. táblázat: R2=πB,C(R)

Ha az eredeti relációt megpróbáljuk természetes illesztéssel visszaállítani (𝑅 ′ = 𝑅1 ⋈ 𝑅2 ), akkor egy,


az eredetitől különböző reláció példányt kapunk vissza (3.13. táblázat), mivel a 𝐶 = 2 attribútum érték
mentén két olyan n-est is össze tudunk illeszteni, amelyek az 𝑅 relációban nem illettek össze, tehát
információt vesztettünk, ez a felbontás veszteséges.

A B C
a 1 2
b 2 2
a 2 2
b 1 2

3.13. táblázat: R’

Ellenben ha egy relációt a dekompozíciós algoritmus szerint mindig a BCNF-t sértő függőségek mentén
darabolunk fel, akkor az eredeti reláció helyreállítható természetes illesztéssel. Ennek igazolására
tekintsük az 𝑅(𝐴, 𝐵, 𝐶) relációt az 𝐹 = {𝐵 → 𝐶} függőséghalmazzal. A reláció kulcsa ekkor A,B lesz,
tehát a 𝐵 → 𝐶 függőség sérti a BCNF-t. Bontsuk fel a függőség mentén a relációt:

𝑅1 (𝐴, 𝐵) = 𝜋𝐴,𝐵 (𝑅) 𝑖𝑙𝑙𝑒𝑡𝑣𝑒 𝑅2 (𝐴, 𝐵) = 𝜋𝐵,𝐶 (𝑅)

Legyen 𝑡 = (𝑎, 𝑏, 𝑐) egy 𝑅-beli n-es, ennek 𝑅1 -beli képe (𝑎, 𝑏), 𝑅2 -beli képe (𝑏, 𝑐). 𝑅1 és 𝑅2
természetes illesztésével egyértelmű, hogy az (𝑎, 𝑏, 𝑐) n-est visszakapjuk.

A kérdés csupán az, hogy kaphatunk-e vissza más n-est is, amely eredetileg nem volt 𝑅 része? Legyen
𝑣 = (𝑑, 𝑏, 𝑒) szintén az 𝑅 reláció egy n-ese. Ekkor 𝑡 𝑅1 -beli képe 𝑢 = (𝑎, 𝑏), 𝑣 𝑅2 -beli képe 𝑤 = (𝑏, 𝑒).
Ez a két n-es szintén illeszthető egymással, mégpedig egy 𝑥 = (𝑎, 𝑏, 𝑒) n-est eredményeznek.

50
Lehetséges, hogy 𝑥 egy hibás n-es, amely eredetileg nem volt 𝑅 része? A válasz pedig nem, mivel a
𝐵 → 𝐶 függőség miatt 𝑐 = 𝑒, hiszen mindkét értékhez 𝐵 = 𝑏 attribútum érték tartozik, eredményképp
𝑡 = 𝑥.

Általános esetben az 𝐴, 𝐵, 𝐶 attribútumokat attribútum halmazokkal helyettesítjük: legyenek 𝑅


tetszőleges attribútum halmazai 𝑋, 𝑌, 𝑍 úgy, hogy 𝑋 ∪ 𝑌 ∪ 𝑍 lefedi R-t, és 𝑌 → 𝑍. Ekkor 𝑅 =
𝜋𝑋∪𝑌 (𝑅) ⋈ 𝜋𝑌∪𝑍 (𝑅). A dekompozíciós algoritmust alkalmazva egy reláció felbontására az eredeti
reláció természetes illesztésekkel mindig helyreállítható. Ez azért igaz, mert a dekompozíciós
algoritmust rekurzívan alkalmaztuk, és bármely elemi felbontási lépés megfordítható: a két kisebb rész
természetes illesztésével visszakapjuk az adott lépésben ketté bontott relációt. A természetes illesztés
bizonyíthatóan asszociatív és kommutatív, tehát a dekompozíciós algoritmus végén előálló relációkat
tetszőleges sorrendben illeszthetjük egymáshoz, a végeredmény mindig az eredeti reláció lesz.

3.9 Függőség őrzés


A BCNF dekompozíciós algoritmussal előállított relációkból az eredeti reláció természetes illesztésekkel
mindig helyreállítható, de nem minden esetben biztosítható a dekomponált relációkban az eredeti
reláció valamennyi függősége. A BCNF dekompozíció nem függőségőrző. Példaként tekintsük az
𝑅(𝑈𝑡𝑐𝑎𝐻á𝑧𝑠𝑧á𝑚, 𝑉á𝑟𝑜𝑠, 𝐼𝑟á𝑛𝑦í𝑡ó𝑠𝑧á𝑚) relációt, amelyben lakcímeket tudunk nyilvántartani. Ha egy
városon belül egy utca nevet csak egyszer használnak, akkor a következő függőségek teljesülnek:

 𝑈𝑡𝑐𝑎𝐻á𝑧𝑠𝑧á𝑚 𝑉á𝑟𝑜𝑠 → 𝐼𝑟á𝑛𝑦í𝑡ó𝑠𝑧á𝑚: bár egy utca több kerületet is érinthet, egy konkrét
városban egy konkrét címhez pontosan egy irányítószám tartozik.
 𝐼𝑟á𝑛𝑦í𝑡ó𝑠𝑧á𝑚 → 𝑉á𝑟𝑜𝑠: Az irányítószám egy országon belül mindig pontosan azonosít egy
konkrét települést.

Az első lépés, hogy megkeressük a reláció kulcsait. Egyetlen attribútum lezártja sem tartalmazza az
összes attribútumot, ezért a kételemű attribútum halmazokkal próbálkozunk. Ezek közül kettő is
kulcsnak bizonyul: az {𝑈𝑡𝑐𝑎𝐻á𝑧𝑠𝑧á𝑚, 𝑉á𝑟𝑜𝑠}, illetve az {𝑈𝑡𝑐𝑎𝐻á𝑧𝑠𝑧á𝑚, 𝐼𝑟á𝑛𝑦í𝑡ó𝑠𝑧á𝑚} attribútum
párosok.

Az 𝐼𝑟á𝑛𝑦í𝑡ó𝑠𝑧á𝑚 → 𝑉á𝑟𝑜𝑠 függőség sérti a BCNF-t, ezért dekomponáljuk a relációt:

 𝑅1 (𝑈𝑡𝑐𝑎𝐻á𝑧𝑠𝑧á𝑚, 𝐼𝑟á𝑛𝑦í𝑡ó𝑠𝑧á𝑚), 𝐹1 = {}
 𝑅2 (𝑉á𝑟𝑜𝑠, 𝐼𝑟á𝑛𝑦í𝑡ó𝑠𝑧á𝑚), 𝐹2 = {𝐼𝑟á𝑛𝑦í𝑡ó𝑠𝑧á𝑚 → 𝑉á𝑟𝑜𝑠}

A dekomponálás során az 𝑈𝑡𝑐𝑎𝐻á𝑧𝑠𝑧á𝑚 𝑉á𝑟𝑜𝑠 → 𝐼𝑟á𝑛𝑦í𝑡ó𝑠𝑧á𝑚 függőség „elveszik”, egyetlen


relációban sem biztosítható ennek betartása. Ha mégis el szeretnénk érni ennek a függőségnek a
teljesülését, akkor minden adat módosítás során illesztenünk kellene az 𝑅1 és 𝑅2 relációkat, hogy
elvégezhessük az ellenőrzést, ami hatékonyan nem kivitelezhető. Következésképpen felvehetünk az 𝑅1
és 𝑅2 relációkba olyan adatokat, amelyek bár az egyes relációk függőségeit kielégítik, természetes
illesztés után az illesztett relációban már nem teljesül az 𝑈𝑡𝑐𝑎𝐻á𝑧𝑠𝑧á𝑚 𝑉á𝑟𝑜𝑠 → 𝐼𝑟á𝑛𝑦í𝑡ó𝑠𝑧á𝑚
függőség. Erre látunk példát a 3.14. táblázatban: az 𝑅1 relációba az azonos utca-házszám adathoz
felvehetjük ugyanannak a városnak különböző irányítószámait is (hiszen egyik attribútum sem függ
funkcionálisan a másiktól, az irányítószámok városhoz tartozása pedig nem fejezhető ki ebben a
relációban). Ezután, ha a relációhoz hozzá illesztjük a város adatokat, akkor olyan n-est is kaphatunk,
amelyben két város/utca/házszám adathoz két különböző irányítószám is tartozik.

51
UtcaHázszám Irányítószám Város Irányítószám UtcaHázszám Irányítószám Város
Ág u 3 1111 Budapest 1111 Ág u 3 1111 Budapest
Ág u 3 1085 Budapest 1085 Ág u 3 1085 Budapest
Ág u 3 5000 Szolnok 5000 Ág u 3 5000 Szolnok

𝑅1 𝑅2 𝑅1 ⋈ 𝑅2

3.14. táblázat: A BCNF dekompozíció nem függőségőrző

3.10 Harmadik normál forma (3NF)


Láttuk, hogy a BCNF dekompozíció nem képes biztosítani valamennyi függőség betartását, a megoldás
egy a BCNF-nél „megengedőbb” normál forma bevezetése, amelyet harmadik normál formának
(3NF) nevezünk. A „megengedőbb” alatt azt értjük, hogy

 a dekompozíció legyen függőségőrző, és


 a dekomponált relációk lehessenek kicsit redundánsak.

Egy 𝑅 reláció 3NF-ben van, ha bármely 𝐴1 … 𝐴𝑛 → 𝑋 nem triviális függőségre

 𝐴1 … 𝐴𝑛 szuperkulcsa 𝑅-nek (ez a BCNF követelménye is egyben), vagy


 𝑋 egy kulcs része (prím attribútum).

Tehát minden BCNF-ben lévő reláció egyben 3NF-ben is van. A BCNF definíciójához képest a különbség
az, hogy ha a függőség jobb oldala prím attribútum, akkor nem szükséges, hogy a bal oldal szuperkulcs
legyen. A 3.9 fejezetben ismertetett példában ennek megfelelően sem az 𝑈𝑡𝑐𝑎𝐻á𝑧𝑠𝑧á𝑚 𝑉á𝑟𝑜𝑠 →
𝐼𝑟á𝑛𝑦í𝑡ó𝑠𝑧á𝑚, sem az 𝐼𝑟á𝑛𝑦í𝑡ó𝑠𝑧á𝑚 → 𝑉á𝑟𝑜𝑠 függőség nem sérti a 3NF-t, hiszen mind az
𝐼𝑟á𝑛𝑦í𝑡ó𝑠𝑧á𝑚, mind a 𝑉á𝑟𝑜𝑠 attribútum része egy kulcsnak. A következőkben ismertetjük a 3NF
dekompozíciós algoritmusát.

Algoritmus: 3NF dekompozíciós algoritmus

Bemenet: 𝑹 reláció, és 𝑭 függőség halmaz.

Kimenet: 𝑹 dekompozíciója kisebb relációkra, amelyek mind 3NF-ben vannak, a dekompozíció


biztosítja a veszteségmentes természetes illesztést és a függőségek megőrzését.

Módszer:

1. Keressük meg 𝐹 egy 𝐺 minimális bázisát.


2. Minden 𝑋 → 𝐴 ∈ 𝐺 függőségre készítsünk egy 𝑋𝐴 relációt.
3. Ha a 2)-ben létrehozott relációk között egyik attribútumai sem alkotják 𝑅 szuperkulcsát,
akkor készítsünk még egy relációt 𝑅 egy kulcsának attribútumaival.
4. A végeredmény a 2) és 3) lépésekben létrehozott relációk összessége

Ahhoz, hogy lássuk, hogy az algoritmus miért helyes, három dolgot kell megvizsgálnunk:

1. Az eredeti reláció veszteségmentesen helyreállítható: Tekintsük azt a relációt, amely a 𝐾 kulcs


attribútumokat tartalmazza. Tudjuk, hogy ezeknek az attribútumoknak a lezártja a reláció
valamennyi attribútumát tartalmazza. Vegyük a függőségeket abban a sorrendben, ahogy a

52
𝐾 + lezártat előállítanánk. Illesszük az egyes 𝑋 → 𝑌 függőségeknek megfelelő relációkat a már
illesztett relációhoz egymás után. Ekkor az 𝑅𝑖 (𝐴, 𝑋) alakú relációhoz illesztjük az 𝑅𝑋→𝑌 (𝑋, 𝑌)
relációt. Az illesztés során az 𝑋 → 𝑌 függőség miatt a 3.8.2 fejezetben ismertetett indoklás
alapján csak olyan n-es jöhet létre, amely az eredeti relációban is szerepelt (az eddig
felhasznált attribútumokra vetítve), és azok mind létre is jönnek.
2. A dekompozíció megőrzi a függőségeket: Mivel a minimális bázis összes függősége alapján
készítettünk egy relációt, ezért a függőségeket egyenként biztosítani tudjuk valamelyik
relációban.
3. Az eredményül kapott relációk mind 3NF-ben vannak: Ha a prím attribútumokat egy külön
relációként fel kellett venni, akkor az bizonyosan 3NF-ben van, hiszen minden a reláción
érvényes függőségre teljesül, hogy a jobb oldala prím attribútum. A minimális bázis függőségei
alapján felvett relációkra szintén bizonyítható, hogy azok 3NF-ben vannak, de annak indoklása
túlmutat ezen könyv keretein.

Példaként tekintsük az 𝑅(𝐴, 𝐵, 𝐶, 𝐷, 𝐸, 𝐹, 𝐺, 𝐻) relációt az 𝐹 = {𝐴 → 𝐵, 𝐴𝐵𝐶𝐷 → 𝐸, 𝐸𝐹 →


𝐺𝐻, 𝐴𝐶𝐷𝐹 → 𝐸𝐺} függőségekkel. Első lépésként elő kell állítanunk a reláció függőséghalmazának
minimális bázisát. Ehhez három kritériumot kell teljesítenünk:

1. A függőségek jobb oldalán csak egy attribútum állhat, ehhez az 𝐸𝐹 → 𝐺𝐻, illetve 𝐴𝐶𝐷𝐹 →
𝐸𝐺 függőségeket ketté bontjuk:
𝐹1 = {𝐴 → 𝐵, 𝐴𝐵𝐶𝐷 → 𝐸, 𝐸𝐹 → 𝐺, 𝐸𝐹 → 𝐻, 𝐴𝐶𝐷𝐹 → 𝐸, 𝐴𝐶𝐷𝐹 → 𝐺}
2. A függőségek bal oldaláról eltávolítjuk azokat az attribútumokat, amelyek a bal oldal többi
attribútumától funkcionálisan függnek. A 𝐵 attribútum funkcionálisan függ az 𝐴-tól, ezért az
𝐴𝐵𝐶𝐷 → 𝐸 szabály bal oldaláról eltávolítható. A többi, jobb oldalon szereplő attribútum közül
egyedül az 𝐸 szerepel a bal oldalon is, ekkor az 𝐹 attribútum mellett, viszont 𝐸 nem szerepel
𝐹 lezártjában, ezért más egyszerűsítést nem végezhetünk el.
𝐹2 = {𝐴 → 𝐵, 𝐴𝐶𝐷 → 𝐸, 𝐸𝐹 → 𝐺, 𝐸𝐹 → 𝐻, 𝐴𝐶𝐷𝐹 → 𝐸, 𝐴𝐶𝐷𝐹 → 𝐺}
3. Utolsó lépésként eltávolítjuk a redundáns szabályokat. Az 𝐴𝐶𝐷 → 𝐸 függőség miatt az
𝐴𝐶𝐷𝐹 → 𝐸 függőség nem hordoz többlet információt, hiszen ha 𝐴𝐶𝐷-től már funkcionálisan
függ 𝐸, akkor 𝐴𝐶𝐷𝐹-től is biztosan. Az 𝐴𝐶𝐷 → 𝐸 és 𝐸𝐹 → 𝐺 szabályokból a
pszeudotranzitivitás miatt következik az 𝐴𝐶𝐷𝐹 → 𝐺 szabály, tehát az is eltávolítható.
𝐹3 = {𝐴 → 𝐵, 𝐴𝐶𝐷 → 𝐸, 𝐸𝐹 → 𝐺, 𝐸𝐹 → 𝐻 }

Következő lépésként megkeressük a reláció kulcsát. Célszerű azokkal az attribútumokkal kezdeni a


vizsgálatot, amelyek egyik függőség jobb oldalán sem szerepelnek (természetesen, csak ha van ilyen).
Ezek az 𝐴, 𝐶, 𝐷, 𝐹 attribútumok. Ha kiszámoljuk ezen négy attribútum lezártját, akkor az összes
attribútumot megkapjuk: 𝐴 → 𝐵 miatt hozzájuk vehetjük 𝐵-t, 𝐴 𝐶 𝐷 → miatt pedig 𝐸-t. Ekkor a lezárt
már tartalmazza 𝐸-t és 𝐹-t, tehát az 𝐸𝐹 → 𝐺, illetve 𝐸𝐹 → 𝐻 függőségek miatt a lezártba belekerül 𝐺
és 𝐻 is.

Végül az 𝐹3 minimális bázis alapján minden függőséghez felveszünk egy relációt:

 𝑅1 (𝐴, 𝐵)
 𝑅2 (𝐴, 𝐶, 𝐷, 𝐸)
 𝑅3 (𝐸, 𝐹, 𝐺)
 𝑅4 (𝐸, 𝐹, 𝐻)

53
Mivel egyik reláció sem tartalmazza az összes kulcs attribútumot, ezért fel kell vennünk még egy
további relációt:

 𝑅5 (𝐴, 𝐶, 𝐷, 𝐹)

3.11 Egyéb normál formák


A BCNF és a 3NF-en kívül további normál formák is ismertek, illetve használatosak. A következőkben
áttekintjük az ismertebb normál formákat, valamint azok egymáshoz, illetve a már tanult normál
formákhoz fűződő viszonyát.

Természetesen, ha létezik harmadik normál forma, akkor logikusan következik, hogy léteznie kell
elsőnek és másodiknak is. Az első normál forma (1NF) csupán annyit követel meg, hogy minden
entitás minden attribútuma atomi (egy sor és oszlop kereszteződésében egy egyszerű érték áll).

A második normál forma (2NF) az 1NF-hez képes még azt is megköveteli, hogy a relációnak ne
legyen olyan nem prím attribútuma, amely a reláció egy kulcsának valódi részhalmazától függ. Vagyis
a nem prím attribútumok nem függhetnek egy kulcs valódi részhalmazától (nem kulcs attribútumoktól
függhetnek).

A harmadik normál forma (3NF) a 2NF-t azzal egészíti ki, hogy a nem prím attribútumok csakis a
(szuper)kulcstól függhetnek (mástól nem, a prím attribútumok pedig bármitől).

A BCNF (egyesek 3.5NF-nek is nevezik) a 3NF feltételeit is tovább szűkíti: minden attribútum csak a
szuperkulcstól függhet (nem triviális függőségektől eltekintve természetesen).

3.11.1 Negyedik normál forma

Bár a BCNF dekompozíció megszűnteti az összes funkcionális függőségből eredő redundanciát, egy
másik típusú, ún. többértékű függőségből következően még előfordulhatnak redundanciák a
relációban. A negyedik normál forma (4NF) ezen redundanciák megszűntetését célozza. Tekintsük
a 3.15. táblázatban ábrázolt relációt.

VevőId Név Cím Telefon


0001 Kiss Éva 1111 Bp. Ág u 1 555-5555
0001 Kiss Éva 1111 Bp. Ág u 1 555-6666
0001 Kiss Éva 1121 Bp. Nyár u 3 555-5555
0001 Kiss Éva 1121 Bp. Nyár u 3 555-6666
0002 Nagy József 1221 Bp. Tél u 2 555-7777
3.15. táblázat: Vásárló információ több címmel és telefonszámmal

Ha egy vásárlóhoz több telefonszám és több cím is tartozhat, akkor (az 1NF-ből következően) azt csak
külön n-esekben tárolhatjuk el. De mivel a telefonszám és a cím egymástól függetlenek (hiszen egy
címen több vásárló is lakhat különböző telefonszámokkal, és egy telefonszámon akár több vásárló is
lehet regisztrálva különböző címekkel), ezért az összes cím-telefonszám párosnak meg kell jelennie a
relációban. A 𝑉𝑒𝑣ő(𝑉𝑒𝑣ő𝐼𝑑, 𝑁é𝑣, 𝐶í𝑚, 𝑇𝑒𝑙𝑒𝑓𝑜𝑛) reláció kulcsa a 𝑉𝑒𝑣𝑜𝐼𝑑 attribútum, mivel tőle
funkcionálisan függ az összes többi attribútum, más funkcionális függőséget viszont nem
fogalmazhatunk meg, a reláció BCNF-ben van, mégis nyilvánvalóan hemzseg a redundanciától. A

54
logikus reprezentáció a 𝑉𝑒𝑣ő(𝑉𝑒𝑣ő𝐼𝑑, 𝑁é𝑣), 𝐶í𝑚(𝑉𝑒𝑣ő𝐼𝑑, 𝐶í𝑚), 𝑇𝑒𝑙𝑒𝑓𝑜𝑛(𝑉𝑒𝑣ő𝐼𝑑, 𝑇𝑒𝑙𝑒𝑓𝑜𝑛)
relációkkal történne, de csupán a funkcionális függőségek alapján, a BCNF dekompozíciót alkalmazva
ide nem juthatunk el.

Az 𝑅 relációban érvényes 𝑋 ↠ 𝑌 többértékű függőség azt fejezi ki, hogy ha 𝑅 két n-ese 𝑋 összes
attribútumán páronként azonos értékkel rendelkezik, akkor a két n-es 𝑌-hoz tartozó részei
felcserélhetők, és az így kapott n-esek szintén a relációban lesznek. Másképp fogalmazva: 𝑋 minden
lehetséges értékére az 𝑌-hoz rendelt értékek függetlenek az 𝑅 − 𝑋 − 𝑌 értékektől. A példában két
többértékű függőség is található: 𝑉𝑒𝑣ő𝐼𝑑 ↠ 𝐶í𝑚, illetve 𝑉𝑒𝑣ő𝐼𝑑 ↠ 𝑇𝑒𝑙𝑒𝑓𝑜𝑛. Tehát definíció szerint,
ha a relációban megtalálható a következő két n-es (3.16. táblázat):

VevőId Név Cím Telefon


0001 Kiss Éva 1111 Bp. Ág u 1 555-5555
0001 Kiss Éva 1121 Bp. Nyár u 3 555-6666
3.16. táblázat: Vásárló információ

Akkor a felcserélt cím és telefonszámmal rendelkező n-esnek és szerepelnie kell benne (3.17. táblázat):

VevőId Név Cím Telefon


0001 Kiss Éva 1111 Bp. Ág u 1 555-5555
0001 Kiss Éva 1111 Bp. Ág u 1 555-6666
0001 Kiss Éva 1121 Bp. Nyár u 3 555-5555
0001 Kiss Éva 1121 Bp. Nyár u 3 555-6666
3.17. táblázat: Vásárló információ minden lehetséges kombinációban

A funkcionális függőségek egyben többértékű függőségek is, hiszen egy 𝑋 → 𝑌 funkcionális függőség
esetén ha két n-esben, amely az X-hez rendelt értékben megegyezik, akkor az Y-hoz rendelt értékben
is meg fog egyezni. Tehát az Y értékeket felcserélve ugyanazt a két n-est kapjuk vissza.

Szintén bizonyítható, hogy ha egy 𝑅 relációban 𝑋 ↠ 𝑌, és 𝑍 az 𝑅 reláció 𝑋 és 𝑌-on kívüli összes


attribútuma, akkor 𝑋 ↠ 𝑍 szintén teljesül

A negyedik normál forma a többértékű függőségeket is a funkcionális függőségekkel azonos módon


kezeli a dekompozíciós algoritmus alkalmazása során, de csak a funkcionális függőségeket veszi
figyelembe a reláció kulcsának meghatározásakor:

Egy reláció akkor van 4NF-ben, ha minden nem triviális 𝑋 ↠ 𝑌 többértékű függőségre 𝑋 szuperkulcs.
Egy többértékű függőség nem triviális, ha

 𝑌 nem részhalmaza 𝑋-nek, és


 𝑋 és 𝑌 együtt nem az összes attribútumot eredményezi.

Mivel minden funkcionális függőség egyben többértékű függőség is, ezért minden 4NF-ben lévő reláció
egyben BCNF-ben is van.

Léteznek magasabb szintű normál formák is (5NF, 6NF, 7NF), de a gyakorlatban kisebb jelentőséggel
bírnak, mint az eddig ismertetettek. Az egyes normál formák egymásra épülnek, az alacsonyabb
szintűeket a magasabbak újabb és újabb kritériumokkal egészítik ki (3.1. ábra).

55
1NF
2NF
3NF
BCNF
4NF
Magasabb normál formák

3.1. ábra: Normál formák hierarchiája

56
4 Magas szintű adatbázis modellek

Egy adatbázis létrehozása mindig a tervezéssel kezdődik. A tervezés során azonosítjuk azokat a
dolgokat, amelyekről információt szeretnénk tárolni, azok számunkra fontos tulajdonságait.
Feltérképezzük a különböző kapcsolatokat, amelyek az egyes dolgokat egymáshoz kötik, megkeressük
azokat a kényszereket, amelyeknek a tárolt adatra teljesülniük kell. Az első kérdés, amellyel a tervezés
során találkozunk, hogy pontosan hogy néz ki egy adatbázis terv? Hogy érdemes egy ilyen tervet
strukturált formában megfogalmazni úgy, hogy az később számunkra és mások számára is érthető és
egyértelmű legyen?

Természetesen alkalmazhatnánk rögtön a relációs sémákat, és egyszerűen felírhatnánk az elkészítendő


relációkat egymás mellé. Bár a relációs séma hatékony, ha implementáció-közeli leírásra van
szükségünk – mivel közvetlenül lefordítható SQL utasításokra (5. fejezet) –, ez a fajta megközelítés távol
áll a valós világ fogalmaitól, a köztük fennálló viszonyoktól. Előnyösebb lenne egy olyan magasabb
szintű formalizmust alkalmazni, amely nem csak egy fogalmat ismer (reláció), hanem egy összetettebb
eszközkészlettel képes összetett objektumok és köztük fennálló kapcsolatok hatékony leírására. Ezeket
a magas szintű leírásokat, modelleket szokták az adatbázis koncepcionális modelljének nevezni. A
koncepcionális modell alkalmas arra, hogy kapcsolatot teremtsen a modellezett szakterület
(kereskedelem, egészségügy, bank, oktatás stb.) programozásban járatlan szakértője, és az adatbázis
programozó között, mivel magas szintű, a modellezett világhoz közeli fogalmakkal dolgozik. A magas
szintű modellek alapján ezután – legtöbbször automatizáltan – elkészíthetjük az adatbázis relációs
sémáját, illetve az alkalmazott adatbázis kezelő rendszerre specifikus adatbázis létrehozó scripteket
(4.1. ábra).

Magas szintű
Ötlet Relációs séma Implementáció
terv

4.1. ábra: Egy adatbázis kialakításának folyamata

Számos megközelítés létezik a koncepcionális modellek készítésére. Az egyik legrégebben használt az


Entitás-relációs (ER) (vagy Egyed-kapcsolat (EK)) [5] modell, de napjainkban gyakran használják még az
Unified Modeling Language (UML)8 nyelvcsalád osztálydiagram jelölésrendszerét – amelyet eredetileg
objektum-orientált szoftver komponensek leírására dolgoztak ki –, illetve az Object Definition
Language-et (ODL) [6], amely egy az UML-hez hasonló, de szöveges jelölésrendszert alkalmaz. Fontos,
hogy koncepcionális modelleket nem csak a tervezés során, hanem gyakran a dokumentációban is
alkalmaznak, mivel egy vizuálisan megjelenített adatbázis terv alapján sokkal könnyebb áttekinteni a
teljes sémát, illetve kiemelni, ráfókuszálni annak egyes részeire. Ebben a fejezetben az Entitás-relációs
modellezéssel fogunk közelebbről megismerkedni, és azokkal a módszerekkel, amelyekkel az ER
modelleket szisztematikusan relációs sémára tudjuk fordítani.

8
Unified Modeling Language - http://www.omg.org/spec/UML/

57
4.1 Alapfogalmak
Az Entitás-relációs modellezésben használt három legalapvetőbb fogalom az entitás, az attribútum
és a kapcsolat (reláció – az angol relationship kifejezésből, nem keverendő az eddig használt relációval,
ami egy kétdimenziós táblázatot jelentett). Entitásoknak nevezzük azokat a megkülönböztethető
objektumokat, amelyekről adatot szeretnénk tárolni. Valamelyest hasonlítanak az objektum-orientált
programozásban használt objektumokhoz: az entitások is rendelkezhetnek tulajdonságokkal, illetve
kapcsolódhatnak más entitásokhoz, viszont – az objektumokkal ellentétben – nem definiálhatunk
rajtuk műveleteket. Az, hogy miből lesz entitás, és miből nem, azt mindig a modellezett probléma dönti
el. Például egy hallgatói nyilvántartásban egy a hallgatóhoz rendelt telefonszám csak a hallgató egy
jellemzője, míg egy online telefonkönyvben egy különálló entitás, ami egyéb jellemzőkkel is
rendelkezhet (pl., hogy titkos-e, emelt díjas-e stb). De a hallgató sem feltétlen lesz mindig entitás: bár
a hallgatói nyilvántartásban az egyes hallgatókat természetesen megkülönböztetjük egymástól, egy
egyetemi rangsorban a hallgatói összlétszám csupán egy numerikus jellemzője az egyetemnek.

Az azonos jellemzőkkel rendelkező entitásokat együttesen entitás halmaznak nevezzük. Például az


eddig is használt termék adatbázisunkban entitások lesznek az egyes konkrét termékek, gyártók, és
vásárlók, míg entitás halmazok a termékek együttesen, a gyártók együttesen stb. Az entitás halmazokat
tipikusan relációkkal szoktuk megvalósítani, de – majd ahogyan később látni fogjuk – nem feltétlen
csak entitás halmazokból jöhetnek létre relációk.

Ahogy már utaltunk rá, az entitások jellemzőkkel, ún. attribútumokkal (tulajdonságokkal)


rendelkeznek. Az attribútumokkal szabhatjuk testre, tehetjük valóban megkülönböztethetővé az egyes
entitásokat. Attribútum például egy termék neve, azonosítója, raktárkészlete, vagy például egy gyártó
címe is. Az attribútumok sok esetben valamilyen egyszerű, elemi tulajdonságot reprezentálnak
(szöveges, numerikus, dátum stb.) mint például a név, ár, telefonszám stb. Ezeket az attribútumokat
egyszerű attribútumoknak nevezzük. De a modellezés során nem feltétlenül kell mindent rögtön
primitív tulajdonságokkal leírni, egyszerű attribútumokból összeállíthatunk összetett
attribútumokat is: például egy vevő entitáshoz hozzárendelünk egy 𝑐í𝑚 attribútumot, de a 𝑐í𝑚
attribútum nem egy egyszerű szöveges tulajdonság lesz, hanem az 𝑖𝑟á𝑛𝑦í𝑡ó𝑠𝑧á𝑚, 𝑣á𝑟𝑜𝑠, 𝑢𝑡𝑐𝑎,
ℎá𝑧𝑠𝑧á𝑚, 𝑒𝑚𝑒𝑙𝑒𝑡, 𝑎𝑗𝑡ó attribútumok összessége. Ezáltal áttekinthetőbbé tesszük a modellünket
magas szinten, de a dolgokat kibontva tovább pontosíthatjuk az egyes tulajdonságokat. A legtöbb
attribútum egyszerre csak egy értéket vehet fel (egyértékű attribútum): egy terméknek egyszerre
csak egy neve lehet és egy darabszáma, egy vásárolónak csak egy azonosítója. Létezhetnek olyan
attribútumok is (többértékű attribútum), amelyek egyszerre több értéket is felvehetnek. Például
egy vásárlóhoz eltárolhatnánk akár egyszerre több telefonszámot is, vagy egy termékhez akár több
színt is, ha az nem egyértelműen meghatározható. Többértékű attribútum lehet például egy személy
nyilvántartásban egy szülő gyermekeinek a neve is (amennyiben a gyermekekről nem akarunk egyéb
információt eltárolni, tehát nem entitásként modellezzük).

Az egyes entitáshalmazok között kapcsolatokat hozhatunk létre, a kapcsolatok az entitás


halmazokban található entitások közötti viszonyokat fejezik ki. Például a 𝑇𝑒𝑟𝑚é𝑘 és a 𝐺𝑦á𝑟𝑡ó reláció
között felvehetünk egy 𝐺𝑦á𝑟𝑡 kapcsolatot, amely azt fejezi ki, hogy egy adott terméket egy bizonyos
gyártó állít elő. Hasonlóképp a 𝑉𝑒𝑣ő és a 𝑇𝑒𝑟𝑚é𝑘 reláció között is modellezhetünk például egy
𝑉á𝑠á𝑟𝑜𝑙 kapcsolatot, amely azt fejezi ki, hogy az egyes termékeket mely vásárlók vásárolták meg. A
gyakorlatban legtöbbször bináris kapcsolatokat szoktak használni (a kapcsolat fokszáma kettő),
amely azt jelenti, hogy a kapcsolat két entitáshalmazt köt össze. De tetszőlegesen nagy fokszámú

58
kapcsolatokat és létrehozhatunk. Ha egy tanuló nyilvántartásban önálló entitásként tároljuk el a
szemesztereket (pl. mert egyéb tulajdonságokat is szeretnénk egy félévhez rendelni), akkor azt, hogy
egy hallgató egy adott félévben felvett egy adott kurzust, egy hármas (ternális) kapcsolattal tudjuk
kifejezni, hiszen három különböző entitáshalmaz elemei között kell kapcsolatot teremteni.

Az entitás-relációs diagram az entitás-relációs modell grafikus ábrázolása. Grafikus megjelenítésre két


fajta szintaktikát szoktak használni: a Chen jelölésmódot (Peter Chen után), illetve a Crow’s foot
jelölést. Mi először a Chen-féle ábrázolással ismerkedünk meg, majd a 4.5.5.2. fejezetben összevetjük
a két jelölésrendszert. A Chen jelölésmódban a következő alakzatokat alkalmazzuk:

 az entitás halmazokat téglalapokkal,


 az attribútumokat körökkel vagy ellipszisekkel,
 az entitás halmazok közti kapcsolatokat rombuszokkal,
 az összetartozást (entitás-attribútum, valamint entitás-kapcsolat) pedig irányított élekkel
ábrázoljuk.

GyártóKód Név TermékKód Név


Ár

Gyártó Gyárt Termék Kategória

Szín Raktárkészlet
Email Cím

4.2. ábra: Termékek és gyártók entitás-relációs diagramja

A 4.2. ábrán egy olyan adatbázis entitás-relációs diagramját láthatjuk, amelyben termékekről, és az
őket előállító gyártókról tárolhatunk el információt. A 𝐺𝑦á𝑟𝑡ó entitás halmaz négy attribútummal
rendelkezik: 𝐺𝑦á𝑟𝑡ó𝐾ó𝑑, 𝑁é𝑣, 𝐸𝑚𝑎𝑖𝑙, 𝐶í𝑚. Az aláhúzás a 𝐺𝑦á𝑟𝑡ó𝐾ó𝑑 attribútum alatt jelzi azt, hogy
ez a tulajdonság az entitáshalmaz kulcsa. A 𝑇𝑒𝑟𝑚é𝑘 relációt hat attribútum jellemzi: a 𝑇𝑒𝑟𝑚é𝑘𝐾ó𝑑, a
𝑁é𝑣, 𝐾𝑎𝑡𝑒𝑔ó𝑟𝑖𝑎, 𝑆𝑧í𝑛, Á𝑟 és a 𝑅𝑎𝑘𝑡á𝑟𝑘é𝑠𝑧𝑙𝑒𝑡. A két entitáshalmazt a 𝐺𝑦á𝑟𝑡 kapcsolat köti össze,
amely egy termékhez legfeljebb egy gyártót rendel (amennyiben az ismert), egy gyártóhoz pedig
tetszőleges számú gyártott terméket. (A számosság korlátozást az él végpontján elhelyezett nyíl jelöli,
ld. részletesen a 4.2.1. fejezetben).

4.2 Attribútumok
Attribútumot nem csak entitás halmazokhoz, hanem akár kapcsolatokhoz is rendelhetünk. Például ha
nyilván szeretnénk tartani, hogy egy gyártó mióta gyárt egy bizonyos terméket, akkor ezt egy a 𝐺𝑦á𝑟𝑡
kapcsolathoz rendelt 𝑀𝑖ó𝑡𝑎 nevű attribútummal írhatjuk le (4.3. ábra). A kapcsolathoz rendelt
attribútum nem az egyes entitásokat jellemzi, hanem a köztük fennálló viszonyhoz rendel
tulajdonságot.

59
GyártóKód Név TermékKód Név
Mióta
Ár

Gyártó Gyárt Termék Kategória

Szín Raktárkészlet
Email Cím

4.3. ábra: – Attribútummal rendelkező kapcsolat.

Entitás halmazon és kapcsolaton túl rendelhetünk attribútumokat akár más attribútumokhoz is, ezáltal
modellezhetünk összetett attribútumokat. Például a cím tulajdonságot gyakran nem egy egyszerű
szöveges mezőben szokták leírni, hanem külön tárolják el az irányítószámot, a város nevet, az utca
nevet, valamint a házszámot (4.4. ábra), ezáltal könnyebben rendszerezhetővé és feldolgozhatóvá téve
azokat.

GyártóKód Név
Irányítószám

Gyártó Város

Utca

Email Cím
Házszám

4.4. ábra: Összetett attribútum

Adott esetben egy gyártó rendelkezhet több címmel is (pl. székhely és telephelyek címei). A
modellünkben ezt az ún. többértékű attribútumokkal jelölhetjük, amelyet dupla ellipszis vonallal
ábrázolunk (4.5. ábra).

60
GyártóKód Név
Irányítószám

Gyártó Város

Utca

Email Cím
Házszám

4.5. ábra: Többértékű attribútum

Többértékű attribútumokkal tipikusan halmaz vagy lista jellegű tulajdonságokat szoktunk modellezni,
a modellezett tulajdonság lehet egyszerű, vagy akár összetett is, mint ahogy a példában a 𝐶í𝑚
attribútumnál is láttuk.

Vegyük észre, hogy az egyed-kapcsolat diagramok eszközkészlete mennyivel gazdagabb, mint a relációs
adatmodellé. Míg a relációs adatmodellben az attribútumok egy-egy primitív érték tárolására voltak
alkalmasak, addig az egyed-kapcsolat diagramon attribútumok segítségével összetett, többszörös
tulajdonságokat is leírhatunk. A relációs adatmodellben két entitás (n-es) kapcsolatát egy külső kulccsal
fejezhettük ki, míg az entitás-relációs modellben a kapcsolathoz is rendelhetünk tulajdonságokat.

4.2.1 Kapcsolatok

A bináris kapcsolatok tovább osztályozhatók aszerint, hogy hány entitás között létesítenek viszonyt.
Ezt nevezzük a kapcsolat multiplicitásának:

 Az egy-egy kapcsolat olyan viszonyt fejez ki, amelyben az egyik entitás halmaz egy elemét
egy másik entitás halmaz pontosan egy elemével hozzuk összefüggésbe és fordítva. Például
egy egyetemnek egy rektora van, és egy rektor egyszerre pontosan egy egyetemnek lehet az
első számú embere. Tehát egy egyetemi nyilvántartásban, ahol eltároljuk az egyes
intézményeket (𝐸𝑔𝑦𝑒𝑡𝑒𝑚), illetve az alkalmazottakat (𝐴𝑙𝑘𝑎𝑙𝑚𝑎𝑧𝑜𝑡𝑡), akkor a
𝑉𝑒𝑧𝑒𝑡(𝐸𝑔𝑦𝑒𝑡𝑒𝑚, 𝐴𝑙𝑘𝑎𝑙𝑚𝑎𝑧𝑜𝑡𝑡) kapcsolat egy-egy típusú lesz, mivel egy egyetem entitáshoz
legfeljebb egy alkalmazottat köthetünk, mint vezetőt (az átmeneti időszakban esetleg nincs
rektor), és egy alkalmazott egyszerre csak egy intézményt vezethet.
 Több-egy kapcsolatról akkor beszélünk, ha az egyik entitás halmaz egy konkrét eleméhez a
másik entitás halmaz több elemét is hozzárendelhetjük. Például az 𝐺𝑦á𝑟𝑡(𝐺𝑦á𝑟𝑡ó, 𝑇𝑒𝑟𝑚é𝑘)
kapcsolat egy gyártóhoz több terméket is rendelhet (hiszen egy vállalkozás egyszerre több
terméket is előállíthat, de akár nullát is), egy konkrét terméket viszont mindig pontosan egy
gyártó állít elő (eltekintve a részegységek előállításától)
 Több-több kapcsolat esetén az egyik entitás halmaz több elemét is kapcsolatba hozhatjuk a
másik entitás halmaz egy konkrét elemével és fordítva. Tehát egy 𝑉á𝑠á𝑟𝑜𝑙(𝑉𝑒𝑣ő, 𝑇𝑒𝑟𝑚é𝑘)
kapcsolat – amely azt fejezi ki, hogy egy vevő megvásárol egy terméket – több-több típusú,
hiszen egy vevő több terméket is vásárolhat, illetve egy termékből több vevő is vásárolhat.
Hasonlóképpen az 𝐴𝑙𝑘𝑎𝑙𝑚𝑎𝑧(𝐸𝑔𝑦𝑒𝑡𝑒𝑚, 𝐴𝑙𝑘𝑎𝑙𝑚𝑎𝑧𝑜𝑡𝑡) kapcsolat szintén több-több típusú,

61
hiszen egy egyetemen több alkalmazott is dolgozik, és egy alkalmazottat egyszerre akár több
egyetem is foglalkoztathat.

A kapcsolat multiplicitását grafikus jelölésben az élek végpontjain elhelyezett nyilakkal jelöljük. Ha az


él végpontján semmilyen egyéb jelölés nem áll, az azt jelenti, hogy a kapcsolat a végponthoz tartozó
entitás halmaz tetszőleges számú egyedét hozzá kötheti a másik entitás halmaz egy egyedéhez. A 4.6.
ábrán illusztrált példában a vevők és a termékek kapcsolatát látjuk: egy vevő tetszőleges számú
terméket megvásárolhat, illetve egy termékből tetszőleges számú vevő vásárolhat.

Vevő Vásárol Termék

4.6. ábra: Több-többes kapcsolat

Ha a kapcsolat egy oldalán legfeljebb egy entitás állhat, akkor azt egy tömör nyíllal jelöljük (4.7. ábra).
Például egy terméket mindig egy gyártó állít elő, egy gyártó viszont tetszőleges számú terméket
gyárthat. Ez a típusú kapcsolat megengedi a nulla darab entitást is, tehát jelen esetben egy terméktől
nem várjuk el, hogy feltétlen kapcsolódjon egy gyártóhoz (ismert legyen annak gyártója).

Gyártó Gyárt Termék

4.7. ábra: Egy-többes kapcsolat

A 4.8. ábrán látható a 𝑉𝑒𝑧𝑒𝑡(𝐸𝑔𝑦𝑒𝑡𝑒𝑚, 𝐴𝑙𝑘𝑎𝑙𝑚𝑎𝑧𝑜𝑡𝑡) kapcsolat, amelyben a kapcsolat legfeljebb egy
egyetemet kapcsol legfeljebb egy alkalmazotthoz.

Egyetem Vezet Alkalmazott

4.8. ábra: Egy-egyes kapcsolat

Ha viszont egy kapcsolat egyik vagy másik oldalán pontosan egy entitás kell, hogy szerepeljen (vagyis
az egyik entitás halmaz egy eleméhez a másik entitás halmaznak mindig pontosan egy elemét rendeli),
akkor azt félkörrel jelöljük. A 4.9. ábrán látható adatbázis modell például egy nemzetközi település
nyilvántartó része lehet, amelyben minden egyes városhoz kötelezően meg kell adni a tartalmazó
országot is. Természetesen egy országon belül tetszőléges számú város helyet foglalhat.

Ország Része Város

4.9. ábra: Pontosan egy elvárt entitás

Elképzelhető olyan eset is, amikor az eddig ismertetetteknél pontosabb kényszerekre van szükségünk,
tehát a számosságokat pontos számok közé akarjuk szorítani (pl. 0 és 10 között, legalább 1, kevesebb,
mint 30 stb). Ilyenkor a számosság kényszereket a vonal megfelelő végénél egyenlőtlenségekkel
jelezzük. Például egy hallgatói nyilvántartásban el szeretnénk tárolni, hogy melyik hallgató melyik
tankörbe tartozik. Minden hallgató pontosan egy tankörhöz tartozik, egy tankör viszont legalább 1,
maximum 30 hallgatóból állhat (4.10. ábra).

62
>0, <31
Tankör Tagja Hallgató

4.10. ábra: Egyéni multiplicitás

4.2.2 Szerepek

Egy kapcsolat nem feltétlenül különböző entitás halmazokat köt össze, gyakran előfordul, hogy például
egy bináris kapcsolat mindkét oldalán ugyanaz az entitás halmaz szerepel. Egy családnyilvántartásban,
ahol eltároljuk a személyi adatokat, és a két személy közt fennálló házastársi viszonyt, egy
𝐻á𝑧𝑎𝑠𝑡á𝑟𝑠𝑎(𝑆𝑧𝑒𝑚é𝑙𝑦, 𝑆𝑧𝑒𝑚é𝑙𝑦) kapcsolat két 𝑆𝑧𝑒𝑚é𝑙𝑦 entitást kapcsol össze ugyanabból a
𝑆𝑧𝑒𝑚é𝑙𝑦 entitás halmazból. Mivel ebben az esetben nem lenne egyértelmű, hogy a kapcsolat melyik
oldalán milyen szerepben áll az entitás halmaz (férj vagy feleség), ezért a kapcsolat végpontjain
elhelyezett szerepnevekkel határozhatjuk meg azokat (4.11. ábra).

férj
Személy Házastársa
feleség

4.11. ábra: Szerepnevek a szemantika pontosításáért

Hasonlóképp, ha a család nyilvántartásban a szülő-gyermek viszonyt is modellezni szeretnénk, akkor a


Szülője bináris kapcsolat mindkét oldalán a Személy entitás halmaz fog állni: egyszer szülő, egyszer
gyermek szerepben (4.12. ábra). Ilyen módon rekurzív hierarchiát is ki tudunk fejezni, hiszen egy
személy gyermeke szintén személy, akinek lehetnek további gyermekei és így tovább.

szülő
Személy Szülője
gyermek

4.12. ábra: Rekurzív hierarchia modellezése bináris kapcsolattal

Szerepneveket nem csak akkor használhatunk, ha a kapcsolat több végén is ugyanaz az entitás halmaz
szerepel, általános esetben is alkalmazhatjuk jelentés pontosítása érdekében, így téve egyértelművé
az entitás halmazok szerepét az egyes kapcsolatokban.

4.2.3 Többes kapcsolatok

Ahogyan már utaltunk rá, a gyakorlatban legtöbbször bináris kapcsolatokkal találkozunk, de bizonyos
esetekben egy kapcsolat kettőnél több entitás halmaz között is teremthet kapcsolatot. Ha egy
üzlethálózat több telephellyel is rendelkezik, és azon túl, hogy egy vásárló milyen termékeket vásárolt
eddig, azt is nyilván szeretnénk tartani, hogy pontosan hol, melyik telephelyeken tette ezt meg, akkor
ezt egy hármas kapcsolattal modellezhetjük, amely a vevő, a termék és a telephely között teremt
összefüggést. Ezt a sémát illusztrálja a 4.13. ábra.

63
Telephely

Vevő Vásárol Termék

4.13. ábra: Többes kapcsolat

Mivel i) egy vevő egy telephelyen tetszőleges számú terméket vásárolhat, ii) egy vevő egy terméket
több telephelyen és megvásárolhat, illetve iii) egy telephelyen egy bizonyos termékből több vevő is
vásárolhat, ezért az élek végpontjain nem jelöltük a multiplicitást. Úgy is fogalmazhatunk, hogy
semelyik két különböző entitás halmazból származó entitás sem határozza meg egyértelműen a
harmadik entitást.

Mivel a gyakorlati megvalósítások legtöbbször csak a bináris kapcsolatokat támogatják (4.5. fejezet),
ezért a többes kapcsolatokat előbb vagy utóbb le kell képeznünk binárisakra. Ez úgy lehetséges, hogy
a többes kapcsolatot egy entitás halmazzal helyettesítjük, amelyet bináris kapcsolatokkal kötjük össze
az eredeti entitás halmazokkal. Ezt az új entitás halmazt szokták kapcsoló entitás halmaznak
nevezni.

Telephely

Helyszíne

Vevő Alanya Vásárlás Tárgya Termék

4.14. ábra: Többes kapcsolat átalakítása bináris kapcsolatokká

A 4.14. ábrán azt láthatjuk, hogy a 𝑉á𝑠á𝑟𝑜𝑙 hármas kapcsolatot kicseréltük egy 𝑉á𝑠á𝑟𝑙á𝑠 entitás
halmazra. Minden egyes entitás ebben a halmazban egy vevőt, egy terméket és egy telephelyet kapcsol
össze az 𝐴𝑙𝑎𝑛𝑦𝑎, 𝑇á𝑟𝑔𝑦𝑎 és 𝐻𝑒𝑙𝑦𝑠𝑧í𝑛𝑒 kapcsolatokon keresztül, amelyek már mind bináris
kapcsolatok. Mivel minden egyes 𝑉á𝑠á𝑟𝑙á𝑠 entitás egyértelműen meghatároz pontosan egy vevőt, egy
terméket és egy telephelyet, ezért a kapcsolatok 𝑉𝑒𝑣ő, 𝑇𝑒𝑙𝑒𝑝ℎ𝑒𝑙𝑦 és 𝑇𝑒𝑟𝑚é𝑘 entitáshalmazok felé
mutató élein félkörökkel jelöljük a pontosan 1-es multiplicitást. Viszont egy vevőhöz tetszőleges számú
(beleértve a nullát is) vásárlás köthető (hasonlóképpen egy telephelyhez és egy termékhez is), ezért a
kapcsolatok másik oldalára nem tehetünk multiplicitásbeli megkötést.

4.3 Gyenge entitás halmazok


Azokat az entitás halmazokat, amelyek egyedeit nem azonosítják a saját attribútumai egyértelműen,
gyenge entitás halmazoknak nevezzük, amelyiket pedig igen, azokat erős entitás halmazoknak.

64
Tegyük fel, hogy nem csak a vevők összesített vásárlásait szeretnénk eltárolni, hanem az egyes
megrendeléseit külön-külön. Egy megrendelésen belül egyszerre tipikusan több típusú termékből
rendelünk valamennyi darabot, a megrendelésen belül ezeket az elemeket megrendeléstételeknek
szoktuk nevezni. Minden megrendelés rendelkezik egy egyedi sorszámmal, amely egyértelműen
azonosítja azt. Minden termék és vevő szintén rendelkezik egy-egy egyedi sorszámmal. Az egyes
megrendelések több tételből állnak össze.

Név
Vevő
VevőKód
Darabszám Név TermékKód
Elküld MegrendelésKód

Megrendelés Része MegrendelésTétel Tárgya Termék

Dátum

4.15. ábra: Gyenge entitás halmaz és azonosító kapcsolatok

A 4.15. ábrán ábrázoltuk a fent vázolt adatbázis sémát. A példában a 𝑀𝑒𝑔𝑟𝑒𝑛𝑑𝑒𝑙é𝑠𝑇é𝑡𝑒𝑙 entitásokat
nem azonosítják egyértelműen a saját attribútumai (𝐷𝑎𝑟𝑎𝑏𝑠𝑧á𝑚), ezért az egy gyenge entitás halmaz,
amelyet dupla körvonallal jelölünk. Ahhoz, hogy az egyes tételeket mégis azonosítani tudjuk, hozzá kell
vennünk a 𝑀𝑒𝑔𝑟𝑒𝑛𝑑𝑒𝑙é𝑠 és a 𝑇𝑒𝑟𝑚é𝑘 halmazok elsődleges kulcsait (𝑇𝑒𝑟𝑚é𝑘𝐾ó𝑑,
𝑀𝑒𝑔𝑟𝑒𝑛𝑑𝑒𝑙é𝑠𝐾ó𝑑). Tehát egy megrendeléstételt a tartalmazó megrendelés kulcsa, és a megrendelt
termék kulcsa fogja azonosítani. Ha megmondjuk, hogy melyik megrendelésben melyik termékre
vagyunk kíváncsiak, akkor ahhoz egyértelműen rendelhető egy darabszám, hiszen egy megrendelésen
belül egy termék csak egyszer szerepelhet (ha többet rendelünk, akkor a darabszámot növeljük). A
𝑀𝑒𝑔𝑟𝑒𝑛𝑑𝑒𝑙é𝑠 és 𝑇𝑒𝑟𝑚é𝑘 halmazokat meghatározó entitás halmazoknak nevezzük, a 𝑅é𝑠𝑧𝑒 és
𝑇á𝑟𝑔𝑦𝑎 kapcsolatokat pedig meghatározó kapcsolatoknak, ez utóbbiakat dupla körvonallal jelöljük.

Annak, hogy egy 𝑅 kapcsolat meghatározó legyen 𝐺 gyenge és 𝑀 meghatározó entitás halmaz között,
a következő feltételei vannak:

1. G bináris kapcsolat kell, hogy legyen 𝑀 és 𝐺 között.


2. A kapcsolat 𝑀 oldalán 1-es multiplicitásnak kell lennie, tehát minden egyes 𝐺-beli gyenge
entitáshoz pontosan egy 𝑀-beli meghatározó entitásnak kell kapcsolódnia.
3. Az 𝑅 kapcsolat által 𝐺-nek szolgáltatott attribútumok 𝑀 kulcsát alkotják.

Előfordul, hogy a meghatározó entitás halmaz is gyenge entitás halmaz, tehát az ő attribútumai sem
azonosítják egyértelműen az egyes egyedeit. Ilyenkor rekurzívan tovább kell haladnunk az ő
meghatározó entitás halmazai felé. Ha 𝐺 gyenge entitás halmaz meghatározó entitás halmaza 𝑀,
amelynek meghatározó entitás halmaza 𝑁, akkor 𝑀 kulcsának része lesz 𝑁 kulcsa, 𝐺 kulcsának pedig
𝑀 kulcsa (amely magában foglalja 𝑁 kulcsát is). A 4.16. ábrán egy egyetemi adatbázis modellt
illusztrálunk, amelyben nyilvántartjuk az egyetemeket, az oktatott szakokat és a tanköröket. Egy
tankört az évfolyam és a sorszám azonosít egyértelműen, de csak egy adott szakon belül, mivel két

65
különböző szakon lehet azonos sorszámú tankör egy évben. Ezért a 𝑇𝑎𝑛𝑘ö𝑟 gyenge entitás halmaz
lesz, a 𝑆𝑧𝑎𝑘 pedig meghatározó entitás halmaz, köztük a 𝑅é𝑠𝑧𝑒 meghatározó kapcsolattal. Egy
egyetemen belül a szak neve egyedi, de több egyetemen is nevezhetnek ugyanúgy szakokat, tehát a
𝑆𝑧𝑎𝑘𝑁é𝑣 önmagában nem lehet kulcs, a 𝑆𝑧𝑎𝑘 is gyenge entitás halmaz lesz, amelyet az
𝐸𝑔𝑦𝑒𝑡𝑒𝑚 entitás halmaz fog meghatározni az 𝐸𝑔𝑦𝑒𝑡𝑒𝑚𝑁é𝑣 kulccsal az 𝑂𝑘𝑡𝑎𝑡 kapcsolaton keresztül.
A 𝑆𝑧𝑎𝑘 kulcsa tehát {𝐸𝑔𝑦𝑒𝑡𝑒𝑚𝑁é𝑣, 𝑆𝑧𝑎𝑘𝑁é𝑣}, a 𝑇𝑎𝑛𝑘ö𝑟 kulcsa pedig
{𝐸𝑔𝑦𝑒𝑡𝑒𝑚𝑁é𝑣, 𝑆𝑧𝑎𝑘𝑁é𝑣, É𝑣𝑓𝑜𝑙𝑦𝑎𝑚, 𝑆𝑜𝑟𝑠𝑧á𝑚}.

SzakNév Évfolyam Sorszám Létszám


EgyetemNév

Egyetem Oktat Szak Része Tankör

4.16. ábra: Gyenge meghatározó entitás

4.4 Öröklés
Az objektum-orientált modellezésből már jól ismert öröklésnek is van alternatívája és létjogosultsága
a magas szintű adatbázis modellezésben. Öröklést használhatunk például, ha egy entitás halmaz
bizonyos attribútumai az entitás halmaz összes eleménél, míg bizonyos attribútumai csak egyes
elemeinél használatosak. Ilyenkor a közös attribútumokat kiemelhetjük egy szülő entitás halmazba, és
a speciális attribútumokat egy vagy több leszármazott entitás halmazba mozgathatjuk.

Példaképp tegyük fel, hogy az áruházunkban különböző fajta termékeket forgalmazunk: játékokat,
ruhaneműket, lábbeliket és könyveket. Valamennyi termék rendelkezik egy árral, egy raktárkészlettel,
egy névvel, valamint egy termék kóddal. A játékoknak ezen kívül van egy minimum korhatára (szám),
amikortól ajánlott a használatuk, a ruhaneműknek van egy szín, egy méret (XS, S, M, L ,XL…), valamint
egy anyag tulajdonsága. A lábbeliknek van típusa (papucs, csizma, félcipő…), valamint egy mérete
(szám), a könyveknek ISBN száma, szerzője, hossza. Objektum-orientált programozásban kézenfekvő
lenne, hogy a 𝑇𝑒𝑟𝑚é𝑘 ősosztályból származzanak le a 𝐽á𝑡é𝑘, 𝑅𝑢ℎ𝑎, 𝐶𝑖𝑝ő és 𝐾ö𝑛𝑦𝑣 osztályok a
megfelelő attribútumokkal. Entitás-relációs diagramon ezt szintén kifejezhetjük az öröklés típusú él
segítségével, amelyet egy üres háromszög fejez ki az entitás halmazokat összekötő élen. Az ismertetett
termék típusok modelljét a 4.17. ábrán láthatjuk.

66
Név

Darabszám Termék Kód

Korhatár
Játék Ruha Cipő Könyv
Szerző

Szín
Hossz
RuhaMéret Anyag CipőMéret Típus

ISBN

4.17. ábra: Leszármazott entitás halmazok

Bár az entitás-relációs diagramon használt öröklés sokban hasonlít az objektum-orientált


programozásban megismerthez, van köztük egy fontos különbség: az objektum-orientált öröklés
esetén minden egyes objektumnak pontosan egy típusa van (vagy egyszerű termék, vagy ruha, vagy
játék stb.), míg az entitás-relációs modellben egy entitás egyszerre több entitás halmazban is
reprezentálhatja magát. Például egy rövid ujjú póló egyszerre 𝑇𝑒𝑟𝑚é𝑘 és 𝑅𝑢ℎ𝑎 is, tehát egyes
komponensei a 𝑇𝑒𝑟𝑚é𝑘, míg mások a 𝑅𝑢ℎ𝑎 entitás halmazokban találhatók. Még szembetűnőbb
azonban a különbség, ha egy olyan termékkel találkozunk, amely egyszerre 𝐽á𝑡é𝑘 és 𝐾ö𝑛𝑦𝑣 is, léteznek
például vízhatlan, fürdés közben is használható gyerekkönyvek, vagy akár egy kifestő is egyszerre 𝐽á𝑡é𝑘
és 𝐾ö𝑛𝑦𝑣 is: ezek rendelkeznek mind a könyvek, mind a játékok tulajdonságaival. Objektum-orientált
programozásban egy ilyen termék reprezentálásához szükségünk lenne egy 𝐽á𝑡é𝑘𝐾ö𝑛𝑦𝑣 osztályra is,
amely a 𝐽á𝑡é𝑘 és a 𝐾ö𝑛𝑦𝑣 osztályokból származna le. Az entitás-relációs modellben viszont az ilyen
termékek egyszerre lesznek jelen a 𝑇𝑒𝑟𝑚é𝑘, a 𝐽á𝑡é𝑘 és a 𝐾ö𝑛𝑦𝑣 entitás halmazokban is.

Fontos, hogy az öröklés él nem rendelkezik multiplicitással. Mindig 1-1 típusú, vagyis a leszármazott
entitás halmazban található entitáshoz mindig egy, a szülő relációban található entitás kapcsolódik, és
a szülő halmazban található entitáshoz is legfeljebb egy entitás kapcsolódik a leszármazott halmazban.
Szülő entitás tehát mindig létezik, de leszármazott egy szülőhöz nem feltétlenül. Az öröklés kapcsolat
segítségével összetett öröklési fákat építhetünk fel, de lényeges, hogy csak fákat. Irányított körök nem
lehetnek az öröklési láncban, egy entitás halmaz nem lehet a saját szülője / saját maga leszármazottja.

67
Név

Darabszám Termék Ruha Ing

Kód Szín

RuhaMéret Anyag Fazon

4.18. ábra: Többszintű öröklés

A 4.18. ábrán azt láthatjuk, hogy az általános ruha terméket tovább specializálhatjuk: például
bevezethetünk egy 𝐼𝑛𝑔 entitás halmazt, amely a 𝑅𝑢ℎá𝑡ó𝑙 és a 𝑇𝑒𝑟𝑚é𝑘𝑡ő𝑙 örökölt attribútumokat
kiegészíti egy 𝐹𝑎𝑧𝑜𝑛 attribútummal is (Slim fit, Regular fit, …).

4.5 Leképezés relációs sémára


A tervezés célja természetesen az, hogy egy adatbázist létrehozzunk. Mivel mi a relációs adatbázisok
témakörére szorítkozunk, ezért kézenfekvő, hogy az entitás-relációs modellt relációs sémára képezzük
le. A leképezés alapötlete rendkívül egyszerű:

 minden entitás halmazból képezzünk egy relációt azonos attribútumokkal,


 minden kapcsolatból készítsünk egy relációt, amelybe a kapcsolat saját attribútumain kívül
felvesszük az összekapcsolt entitáshalmazok kulcsait is (a kulcsok segítségével tudjuk
azonosítani, hogy egy kapcsolat példány pontosan mely entitások között teremt
összeköttetést).

Bár a fenti megközelítés az adatbázis modell nagy részét képes relációs sémára képezni, számos
hiányossággal és hátránnyal is rendelkezik:

 sokszor felesleges egy kapcsolathoz külön relációt létrehozni, különösen akkor, ha a kapcsolat
1-1 vagy 1-több típusú, és nem rendelkezik attribútumokkal,
 a gyenge entitáshalmazokat a bemutatott módszer nem kezeli,
 illetve nem megoldott az öröklések leképezése sem.

A továbbiakban azt fogjuk megvizsgálni, hogyan készíthetjük el egy egyed-kapcsolat modell teljes és
lehetőség szerint hatékony leképezését relációs sémára.

4.5.1 Erős entitás halmazok

Az erős entitás halmazok definíció szerint önállóan értelmezhetők, egyedei saját attribútumai alapján
megkülönböztethetők. Ezért ezekhez a halmazokhoz egyszerűen létrehozhatunk egy-egy relációt az
entitás halmazok neveivel és azok attribútumaival. A kapcsolatokat és kapcsolódó entitáshalmazokat
ezen a ponton nem kell figyelembe vennünk. Tekintsük a 4.19. ábrán bemutatott adatbázis modellt.

68
VevőKód Név Mennyit TermékKód Név
Ár

Vevő Vásárol Termék Kategória

Szín Raktárkészlet
Email Cím

4.19. ábra: Termékek és gyártók entitás-relációs diagramja

A bemutatott 𝑉𝑒𝑣ő és 𝑇𝑒𝑟𝑚é𝑘 entitás halmazok egyaránt erős entitás halmazok, így hozzájuk
elkészítjük a következő két relációt:

 𝑉𝑒𝑣ő(𝑉𝑒𝑣ő𝐾ó𝑑, 𝑁é𝑣, 𝐸𝑚𝑎𝑖𝑙, 𝐶í𝑚)


 𝑇𝑒𝑟𝑚é𝑘(𝑇𝑒𝑟𝑚é𝑘𝐾ó𝑑, 𝑁é𝑣, 𝐾𝑎𝑡𝑒𝑔ó𝑟𝑖𝑎, 𝑆𝑧í𝑛, Á𝑟, 𝑅𝑎𝑘𝑡á𝑟𝑘é𝑠𝑧𝑙𝑒𝑡)

4.5.2 Kapcsolatok

A kapcsolatok leképezésére az alap ötletünk az volt, hogy minden kapcsolathoz készítsünk egy relációt,
és a kapcsolat saját attribútumain kívül adjuk hozzá a kapcsolódó entitáshalmazok kulcs attribútumait
is. Amennyiben egy kapcsolat két vagy több végén is ugyanaz az entitás halmaz áll (pl. a 𝑆𝑧ü𝑙ő𝑗𝑒
kapcsolat a 4.12. ábrán), akkor az érintett entitás halmaz kulcs attribútumait többször is hozzá kell
adnunk a létrehozott reláció attribútumaihoz (természetesen különböző nevekkel). Az ebből a célból
létrehozott relációkat szoktuk kapcsoló relációknak vagy kapcsoló tábláknak nevezni. A 4.19. ábrán
bemutatott példának megfelelően tehát fel kell vennünk egy

 𝑉á𝑠á𝑟𝑜𝑙(𝑉𝑒𝑣ő𝐾ó𝑑, 𝑇𝑒𝑟𝑚é𝑘𝐾ó𝑑, 𝑀𝑒𝑛𝑛𝑦𝑖𝑠é𝑔)

relációt is az adatbázis sémába.

Ez egy több-többes kapcsolat (mint amilyen a 𝑉á𝑠á𝑟𝑜𝑙 is) esetén megfelelő megoldásnak tűnik. De
vizsgáljuk meg, mi történik az egy-többes típusú kapcsolatok esetén.

69
GyártóKód Név TermékKód Név
Ár

Gyártó Gyárt Termék Kategória

Szín Raktárkészlet
Email Cím

4.20. ábra: Termékek és gyártók entitás-relációs diagramja

Ha a 4.20. ábrának megfelelően létrehozunk egy 𝐺𝑦á𝑟𝑡(𝐺𝑦á𝑟𝑡ó𝐾ó𝑑, 𝑇𝑒𝑟𝑚é𝑘𝐾ó𝑑) relációt, akkor a


𝑇𝑒𝑟𝑚é𝑘 reláció minden egyes sorához 0 vagy egy sor fog tartozni a 𝐺𝑦á𝑟𝑡 relációban (4.1. táblázat) (0
akkor, ha egy termék gyártója nem ismert, ami viszonylag ritka).
TermékKód Név … TermékKód GyártóKód GyártóKód Név …
1 Dohányzóasztal 1 1 1 Nagy Bútor Kft
2 Kanapé 2 1 2 Fabric Rt
3 Szék 3 2 3 Bútorgyár
4 Hintaágy 4 3

𝑇𝑒𝑟𝑚é𝑘 𝐺𝑦á𝑟𝑡 𝐺𝑦á𝑟𝑡ó


4.1. táblázat: Termék és Gyártó relációk kapcsolata a Gyárt reláción keresztül

A 𝐺𝑦á𝑟𝑡 táblázat minden egyes sora két külső kulcs értéket tárol el, az egyik a megfelelő termékre, a
másik a termékhez tartozó gyártóra mutat. Mivel a termékek túlnyomó többségéhez tartozik gyártó,
ezért pazarlás egy külön relációt fenntartani csak arra a célra, hogy a termékhez tartozó gyártó
azonosítót eltároljuk. Az, hogy ezt az információt egy külön táblában tartjuk nyilván, bonyolítja és
lassítja a lekérdezéseket, mivel a 𝑇𝑒𝑟𝑚é𝑘-𝐺𝑦á𝑟𝑡ó kapcsolatot csak két illesztéssel tudjuk feloldani.
Jobb megoldásnak tűnik, hogy ha külön reláció létrehozása helyett a 𝐺𝑦á𝑟𝑡ó𝐾ó𝑑 attribútumot mint
külső kulcsot egyszerűen elhelyezzük a 𝑇𝑒𝑟𝑚é𝑘 táblában (4.2. táblázat).
TermékKód Név GyártóKód … GyártóKód Név …
1 Dohányzóasztal 1 1 Nagy Bútor Kft
2 Kanapé 1 2 Fabric Rt
3 Szék 2 3 Bútorgyár
4 Hintaágy 3

𝑇𝑒𝑟𝑚é𝑘 𝐺𝑦á𝑟𝑡ó
4.2. táblázat: Termék és Gyártó relációk kapcsolata külső kulcson keresztül

Ezáltal egyszerűsödnek a lekérdezések, és helyet is takarítunk meg: mivel a 𝐺𝑦á𝑟𝑡ó𝐾ó𝑑 attribútum az


esetek többségében ki van töltve, ezért a kapcsoló táblában minden egyes termékhez újból el kellene
tárolnunk annak kulcsát is.

Ha a kapcsolat rendelkezik attribútumokkal is (pl. egy 𝑀𝑖ó𝑡𝑎 mező a 𝐺𝑦á𝑟𝑡 kapcsolaton), akkor az 1-
többes kapcsolat attribútumait is a kapcsolat többes oldalán található entitás halmaznak megfelelő
relációba (𝑇𝑒𝑟𝑚é𝑘) helyezzük. Ezeket az attribútumokat (mint a külső kulcsot is) természetesen csak
akkor töltjük ki az egyes entitásoknál, amennyiben létezik az entitáshoz az adott kapcsolat.

Teljesítmény okokból bizonyos esetekben megfontolás tárgya lehet, hogy a kapcsolatot mégis külön
relációra képezzük le, például ha a kapcsolat sok és/vagy nagy helyigényű attribútummal rendelkezik.
Azáltal, hogy a kapcsolat attribútumaihoz tartozó értékeket az összekapcsolt relációktól elkülönítve

70
tároljuk, csökkenthetjük az összekapcsolt relációk helyigényét, ezáltal gyorsítva rajtuk a lekérdezések
végrehajtását, amennyiben a kapcsolat nem szerepel a lekérdezésben.

4.5.3 Attribútumok

Amint láttuk, az egyed-kapcsolat diagramban modellezett egyszerű attribútumokat a relációs séma


attribútumainak feleltetjük meg. Mivel a relációk csak primitív és egyértékű attribútumokat engednek
meg, ezért az összetett és többértékű attribútumok leképezésére más módszer szükséges. Összetett
attribútumok esetén, mint amilyen a 𝐶í𝑚(𝐼𝑟á𝑛𝑦í𝑡ó𝑆𝑧á𝑚, 𝑉á𝑟𝑜𝑠, 𝑈𝑡𝑐𝑎𝐻𝑠𝑧) attribútum is (4.21. ábra),
az attribútumot egyszerűen kilapítjuk, vagyis a 𝐶í𝑚 attribútum helyett annak al-attribútumait
helyezzük el a relációban (4.3. táblázat). Vegyük észre, hogy 𝐶í𝑚 nevű attribútum nem jelenik meg a
relációban, csupán annak egyszerű al-attribútumai. Amennyiben az al-attribútumok között is található
összetett, úgy rekurzívan azokat is kilapítjuk.

71
GyártóKód Név
Irányítószám

Gyártó Város

UtcaHsz

Email Cím

4.21. ábra: Gyártó entitás összetett Cím attribútummal

GyártóKód GyártóNév Email Irányítószám Város UtcaHsz


1 Nagy Bútor Kft nb@example.org 5000 Szolnok Fa u 2
2 Fabric Rt fabric@example.org 9012 Győr Ág u 15
3 Bútorgyár xyb@example.org 1111 Budapest Hős krt 3
4 Kiss Bútor Kft kb@example.org 1111 Budapest Virágos tér 4
4.3. táblázat: Összetett Cím attribútum leképezése
Tegyük fel, hogy a 𝐶í𝑚 attribútum nem csak összetett, hanem többszörös is, vagyis egy gyártó több
címmel is rendelkezhet. A 𝐺𝑦á𝑟𝑡ó reláción belül egy konkrét gyártóhoz nem tudunk több összetartozó
(𝐼𝑟á𝑛𝑦í𝑡ó𝑠𝑧á𝑚, 𝑉á𝑟𝑜𝑠, 𝑈𝑡𝑐𝑎𝐻𝑠𝑧) attribútum hármast felvenni, ezért a cím információt egy külön
relációban helyezzük el, amely külső kulccsal hivatkozik a kapcsolódó gyártóra:
𝐶í𝑚(𝐺𝑦á𝑟𝑡ó𝐾ó𝑑, 𝐼𝑟á𝑛𝑦í𝑡ó𝑠𝑧á𝑚, 𝑉á𝑟𝑜𝑠, 𝑈𝑡𝑐𝑎𝐻𝑠𝑧). Minden egyes adott gyártóhoz rendelt címnek
egy új n-es fog megfelelni a 𝐶í𝑚 relációban (4.4. táblázat).

GyártóKód GyártóNév … GyártóKód Irányítószám Város UtcaHsz


1 Nagy Bútor Kft 1 5000 Szolnok Fa u 2
2 Fabric Rt 2 9012 Győr Ág u 15
3 Bútorgyár 2 1222 Budapest Ló u 3
4 Kiss Bútor Kft 3 1111 Budapest Hős krt
3
3 7691 Pécs Tél u 1
4 1111 Budapest Virágos
tér 4
Gyártó Cím
4.4. táblázat: Összetett és többszörös Cím attribútum leképezése

Ugyanígy járunk el, amennyiben a többszörös attribútum nem összetett: ilyenkor a létrehozott
relációban csupán a kapcsolódó reláció külső kulcsa és a többszörös, de egyszerű attribútum szerepel.
Időnként szükség lehet rá, hogy a többszörös attribútum értékeit sorrendezzük (pl. a címeket,
telefonszámot sorrendezzünk fontosság szerint). Ilyenkor a létrehozott relációban egy sorszám
attribútummal jelezhetjük, hogy az adott n-es a külső kulccsal hivatkozott entitáson belül a többszörös
attribútum hányadik pozícióján szerepel (4.5. táblázat). A sorszám attribútum értéke a kapcsolódó
entitást hivatkozó külső kulcs értékenként (𝐺𝑦á𝑟𝑡ó𝐾ó𝑑) egyedi.

72
GyártóKód Sorszám Irányítószám Város UtcaHsz
1 1 5000 Szolnok Fa u 2
2 1 9012 Győr Ág u 15
2 2 1222 Budapest Ló u 3
3 1 1111 Budapest Hős krt 3
3 2 7691 Pécs Tél u 1
4 1 1111 Budapest Virágos tér 4
4.5. táblázat: Sorrendezett többszörös attribútum leképezése

4.5.4 Gyenge entitás halmazok

A gyenge entitásokat definíciószerűen az entitás halmaz saját és a meghatározó entitáshalmazainak


kulcs attribútumai azonosítják. Ennek megfelelően, amikor egy gyenge entitás halmaz alapján relációt
készítünk, akkor a meghatározó entitáshalmazok kulcs attribútumait is fel kell venni az új relációba.

Darabszám Név TermékKód


MegrendelésKód

Megrendelés Része MegrendelésTétel Tárgya Termék

Dátum

4.22. ábra: MegrendelésTétel gyenge entitáshalmaz

A 4.22. ábrán látható 𝑀𝑒𝑔𝑟𝑒𝑛𝑑𝑒𝑙é𝑠𝑇é𝑡𝑒𝑙 gyenge entitás halmaz leképezésekor tehát a


𝑀𝑒𝑔𝑟𝑒𝑛𝑑𝑒𝑙é𝑠𝑇é𝑡𝑒𝑙 nevű relációba felvesszük a 𝐷𝑎𝑟𝑎𝑏𝑠𝑧á𝑚 attribútumot és a 𝑀𝑒𝑔𝑟𝑒𝑛𝑑𝑒𝑙é𝑠, illetve
𝑇𝑒𝑟𝑚é𝑘 táblák kulcsait: 𝑀𝑒𝑔𝑟𝑒𝑛𝑑𝑒𝑙é𝑠𝑇é𝑡𝑒𝑙(𝑀𝑒𝑔𝑟𝑒𝑛𝑑𝑒𝑙é𝑠𝐾ó𝑑, 𝑇𝑒𝑟𝑚é𝑘𝐾ó𝑑, 𝐷𝑎𝑟𝑎𝑏𝑠𝑧á𝑚). A
reláció egy lehetséges példányát a 4.6. táblázatban láthatjuk.

MegrendelésKód TermékKód Darabszám


1 1 1
1 2 2
1 3 4
2 3 2
2 4 3
3 2 5
4.6. táblázat: A MegrendelésTétel reláció egy lehetséges példánya

Mivel a meghatározó kapcsolatok mindig 1-1 vagy 1-több típusúak, ezért ezeket a kapcsolatokat nem
kell külön relációként leképeznünk.

4.5.5 Öröklés

A leszármazott entitások relációs sémára történő leképezésére több lehetőség is kínálkozik, amelyek
eltérő hatékonyságúak és bonyolultságúak. A megfelelő módszer kiválasztása mindig az aktuális

73
feladattól függ. A következőkben a korábban már ismertetett 4.23. ábrán illusztrált egyed-kapcsolat
diagramon keresztül fogjuk bemutatni a különböző módszerek előnyeit és hátrányait.

Név

Darabszám Termék Kód

Ár

Korhatár
Játék Ruha Cipő Könyv
Szerző

Szín
Hossz
RuhaMéret Anyag CipőMéret Típus

ISBN

4.23. ábra: Leszármazott entitás halmazok

4.5.5.1 Külön reláció minden egyes entitáshalmazhoz


Az első megoldásunkban a diagramot szinte egy-az-egyben képezzük le relációs sémára: egy-egy
relációt készítünk mind az ős, mind a leszármazott entitáshalmazokhoz, amelyeket külső kulcsokon
keresztül kapcsolunk össze. A példának megfelelően tehát készítünk egy relációt a 𝑇𝑒𝑟𝑚é𝑘 entitás
halmazhoz, amely a többi entitás halmaz őse:

 𝑇𝑒𝑟𝑚é𝑘(𝐾ó𝑑, 𝑁é𝑣, Á𝑟, 𝐷𝑎𝑟𝑎𝑏𝑠𝑧á𝑚)

Ebben a relációban tároljuk el valamennyi termék alapvető tulajdonságait: a nevüket, árukat és a


darabszámukat, az egyes entitásokat azok kódja azonosítja.

Az egyes leszármazott entitásokhoz szintén készítünk egy-egy relációt. A leszármazott relációkhoz


készíthetünk egy-egy mesterséges kulcsot is, illetve egy-egy külső kulcsot, amely a 𝑇𝑒𝑟𝑚é𝑘 reláció
𝐾ó𝑑 attribútumára hivatkozik. Mivel az öröklés mindig egy-egy vagy egy több kapcsolatot fejez ki
(tehát egy ős entitásnak nem lehet több leszármazottja ugyanabban az entitás halmazban, csak
különbözőkben), ezért a leszármazott entitás halmazhoz létrehozott reláció kulcsa lehet egyben az ős
relációra mutató külső kulcs is:

 𝐽á𝑡é𝑘(𝐾ó𝑑, 𝐾𝑜𝑟ℎ𝑎𝑡á𝑟)
 𝑅𝑢ℎ𝑎(𝐾ó𝑑, 𝑆𝑧í𝑛, 𝑅𝑢ℎ𝑎𝑚é𝑟𝑒𝑡, 𝐴𝑛𝑦𝑎𝑔)
 𝐶𝑖𝑝ő(𝐾ó𝑑, 𝐶𝑖𝑝ő𝑚é𝑟𝑒𝑡, 𝑇í𝑝𝑢𝑠)
 𝐾ö𝑛𝑦𝑣(𝐾ó𝑑, 𝐼𝑆𝐵𝑁, 𝐻𝑜𝑠𝑠𝑧, 𝑆𝑧𝑒𝑟𝑧ő)

A fenti relációs sémában a sárga pamut póló terméket a 4.7. táblázatban látható két n-es fog kifejezni.

74
Kód Név Ár Darabszám Kód Szín Ruhaméret Anyag
1 Póló 4500 15 1 sárga M pamut
… … … … … … …
Termék Ruha
4.7. táblázat: Leszármazott entitás reprezentációja két relációban

Figyeljük meg, hogy a két relációban a két n-es ugyanazzal a kóddal rendelkezik: mindkét reláció
elsődleges kulcsa a 𝐾ó𝑑 attribútum, és a 𝑅𝑢ℎ𝑎 reláció 𝐾ó𝑑 attribútuma egy a 𝑇𝑒𝑟𝑚é𝑘 reláció 𝐾ó𝑑
attribútumát hivatkozó külső kulcs is. Ha egy ruhanemű valamennyi adatára szükségünk van, akkor a
𝐾ó𝑑 attribútum mentén össze kell illesztenünk a 𝑇𝑒𝑟𝑚é𝑘 és 𝑅𝑢ℎ𝑎 relációkat. Ez ennek a
megvalósításnak a hátránya is, mivel minél mélyebb az öröklési lánc, annál több illesztésre van szükség
egy entitás adatainak kinyeréséhez. Ezzel a struktúrával viszont könnyen tudunk reagálni az
entitásmodell változásaira: egy új leszármazott entitás felvételéhez csupán egy új relációt kell
létrehoznunk, illetve egy új attribútum felvétele / törlése esetén is csupán mindig az attribútumot
tartalmazó relációt kell módosítanunk.

4.5.5.2 Összes attribútum a leszármazott relációkban


Egy másik lehetőség leszármazási hierarchiák leképezésére az, ha a leszármazott entitás halmazokhoz
készített relációkba az ős entitás halmazok valamennyi attribútumát is felvesszük. Ilyenkor a
gyerekeknek nem kell az ős relációkra hivatkozniuk külső kulcs segítségével, minden entitás csak abban
a relációban fog megjelenni, ami a tényleges típusa.

A termék nyilvántartó példánkban ennek a leképezésnek az eredményeként előáll egy

 𝑇𝑒𝑟𝑚é𝑘(𝐾ó𝑑, 𝑁é𝑣, Á𝑟, 𝐷𝑎𝑟𝑎𝑏𝑠𝑧á𝑚)

reláció, valamint valamennyi leszármazott entitáshoz egy-egy újabb reláció, amelyek megöröklik a
termék reláció attribútumait is:

 𝐽á𝑡é𝑘(𝐾ó𝑑, 𝑁é𝑣, Á𝑟, 𝐷𝑎𝑟𝑎𝑏𝑠𝑧á𝑚, 𝐾𝑜𝑟ℎ𝑎𝑡á𝑟)


 𝑅𝑢ℎ𝑎(𝐾ó𝑑, 𝑁é𝑣, Á𝑟, 𝐷𝑎𝑟𝑎𝑏𝑠𝑧á𝑚, 𝑆𝑧í𝑛, 𝑅𝑢ℎ𝑎𝑚é𝑟𝑒𝑡, 𝐴𝑛𝑦𝑎𝑔)
 𝐶𝑖𝑝ő(𝐾ó𝑑, 𝑁é𝑣, Á𝑟, 𝐷𝑎𝑟𝑎𝑏𝑠𝑧á𝑚, 𝐶𝑖𝑝ő𝑚é𝑟𝑒𝑡, 𝑇í𝑝𝑢𝑠)
 𝐾ö𝑛𝑦𝑣(𝐾ó𝑑, 𝑁é𝑣, Á𝑟, 𝐷𝑎𝑟𝑎𝑏𝑠𝑧á𝑚, 𝐼𝑆𝐵𝑁, 𝐻𝑜𝑠𝑠𝑧, 𝑆𝑧𝑒𝑟𝑧ő)

A fenti sémában egy sárga pamut pólót a 4.8. táblázatban látható egyetlen n-esként tudjuk eltárolni.

Kód Név Ár Darabszám Szín Ruhaméret Anyag


1 Póló 4500 15 sárga M pamut
… … … … … …
4.8. táblázat: Leszármazott entitás reprezentációja egy relációban

Amint látható, a megoldás nagy előnye, hogy az egy entitáshoz tartozó adatokat egy n-esben tárolja,
és csak az entitásra jellemző attribútumok jelennek meg benne. Eredményképpen hatékonyan tudjuk
a jellemző adatokat kinyerni, nincs szükség több tábla illesztésére. További előny, hogy ebben a
leképezésben elegendő csupán a nem absztrakt entitás halmazokat relációkra leképeznünk. Az
absztrakt ősöknek megfelelő relációkban úgysem tárolnánk egyetlen n-est sem.

75
A megoldás hátránya, hogy mivel a leszármazott entitáshalmazoknak megfelelő relációk az ős
halmazok valamennyi attribútumát is tartalmazzák, ezért egy ős entitás halmazban elvégzett
változtatás (pl. egy attribútum felvétele vagy törlése) a teljes öröklési fán végiggyűrűzik, így valamennyi
leszármazott halmazhoz tartozó relációban el kell végezni a változtatást. További problémát jelenthet
az olyan entitások kezelése, amelyek egyszerre több típussal is rendelkeznek: például egyszerre 𝐽á𝑡é𝑘
és 𝐾ö𝑛𝑦𝑣 egy termék. Ilyenkor mind a 𝐽á𝑡é𝑘, mind a 𝐾ö𝑛𝑦𝑣 relációban meg fog jelenni egy-egy n-es,
de mivel az ősöktől örökölt attribútumok (Név, Ár, Darabszám) mindkét relációban megjelennek, ezért
az ezekhez az attribútumokhoz rendelt értékek redundánsan, mindkét n-esben eltárolódnak.

4.5.5.3 Valamennyi entitás halmaz egy relációban


További lehetőség öröklési hierarchiák leképezésére, hogy az ős entitás halmaz valamennyi
leszármazottját egyetlen relációra képezünk le: ebbe a relációba elhelyezzük a leszármazott entitás
halmazok valamennyi (saját és örökölt) attribútumát, amelyek közül egy adott n-es esetén mindig csak
azt töltjük ki, amely a kapcsolódó entitás típusára jellemző. A megoldás így még nem teljes, hiszen egy
n-esről nem tudjuk eldönteni, hogy az milyen típusú entitásról tárol adatot, pontosan melyik
attribútumokat szabad egyáltalán figyelembe vennünk az esetében. Ezért egy további attribútumot
adunk a relációhoz, amely arra szolgál, hogy az egyes entitások típusát el tudjuk tárolni. Ezt az
attribútumot diszkriminátornak is szokták nevezni.

A fenti példának megfelelően egy

 𝑇𝑒𝑟𝑚é𝑘(𝐾ó𝑑, 𝑁é𝑣, Á𝑟, 𝐷𝑎𝑟𝑎𝑏𝑠𝑧á𝑚, 𝐾𝑜𝑟ℎ𝑎𝑡á𝑟, 𝑆𝑧í𝑛, 𝑅𝑢ℎ𝑎𝑚é𝑟𝑒𝑡, 𝐴𝑛𝑦𝑎𝑔, 𝐶𝑖𝑝ő𝑚é𝑟𝑒𝑡,


𝑇í𝑝𝑢𝑠, 𝐻𝑜𝑠𝑠𝑧, 𝐼𝑆𝐵𝑁, 𝑆𝑧𝑒𝑟𝑧ő, 𝑇𝑒𝑟𝑚é𝑘𝑇í𝑝𝑢𝑠)

relációt hozunk létre, amelyben a TermékTípus mező szolgál diszkriminátorként, vagyis hogy az egyes
n-eseknek megfelelő entitások típusát jelölje. A diszkriminátor típusa általában numerikus, amely az
egyes típusokat kódolja. Példánkban legyen az egyszerű termék a 0, a játék az 1-es, a ruha a 2-es, a
cipő a 3-as és a könyv a 4-es típussal kódolt. Ennek megfelelően egy építőkocka játék és egy sárga
pamut póló adatait a 4.9. táblázatban látható módon tárolhatjuk el.

Kód Név Ár Darabszám Korhatár Szín Ruhaméret Anyag … TermékTípus


1 Póló 4500 15 ⊥ sárga M pamut … 2
2 Építőkocka 6200 5 3 ⊥ ⊥ ⊥ … 1
4.9. táblázat: Valamennyi leszármazott entitás egy táblára történő leképezése

Ennek a megoldásnak szintén az a legnagyobb előnye (azon túl, hogy könnyen megvalósítható), hogy
az összes adat egy helyen van, ezért egy adott entitáshoz tartozó adatok könnyen, egyszerű
lekérdezések segítségével illesztés nélkül kinyerhetők. Hátránya pedig természetesen az, hogy minden
egyes entitáshoz (típustól függetlenül) valamennyi attribútum számára biztosítanunk kell a tárolási
lehetőséget.

Mivel bizonyos esetekben előfordulhat, hogy egy entitás egyszerre több típussal is rendelkezik, ezért
az egyszerű típus sorszámozás nem mindig elegendő, hiszen egy mezőben egyszerre nem tudjuk az 1-
es és a 4-es értéket is eltárolni. Ilyenkor több lehetőségünk is van:

 Ha a típusokhoz kettő hatványokat rendelünk (1,2,4,8,16,…, vagyis 00001, 00010,00100,01000


…), akkor őket a bináris VAGY művelettel aggregálva egy olyan értéket kapunk, amelyből
egyértelműen helyreállíthatók az eredeti értékek. Például ha a játék a 2-es (00010), a könyv

76
pedig a 16-os (10000), és egy termék egyszerre könyv és játék is, akkor hozzá a 2 OR 16=18-at
rendeljük típusként (10010). (Vegyük észre, hogy különböző kettő hatványoknál a bináris vagy
művelet az összeadással azonos eredményt produkál). Egy konkrét n-es bizonyos típusba
tartozását az n-es típus azonosítójának és a konkrét típus azonosítójának összeéselésével
dönthetjük el (10010 AND 10000 = 10000).
 További lehetőség minden egyes típushoz felvenni 1-1 saját diszkriminátor oszlopot, amely
logikai igazat (1) tartalmaz, ha egy n-es az adott típusba tartozik, és logikai hamist (0), ha nem
az adott típus példánya.

4.6 Crow’s foot jelölésmód


A Crow’s foot jelölésmódot Gordon Everest dolgozta ki, a jelölésmód sokkal közelebb áll a relációs
sémához, mint a Chen-féle: gyakorlatilag a relációs séma grafikus ábrázolásának felel meg. Ennek
megfelelően ezt a szintaxist alkalmazzák leggyakrabban a grafikus adatbázis-tervező eszközök is, hiszen
az ilyen diagramok alapján automatizáltan előállítható a relációs séma. Nem támogat tehát olyan
magas szintű konstrukciókat, mint az összetett, illetve többszörös attribútumok, öröklés, többes,
illetve attributált kapcsolatok, erős és gyenge entitás halmazok. Minden olyan fogalmat, amely relációs
sémával közvetlenül nem fejezhető ki, csupán a relációs sémára történő leképezés után tud
megjeleníteni. A két legfontosabb jelölésbeli különbség, hogy a Crow’s foot jelölésmód

 az entitás halmazok attribútumait táblázatos formában, az entitás halmaz téglalapjának


belsejében ábrázolja, valamint
 csupán bináris kapcsolatokat engedélyez, amelyet egyszerű élekkel jelöl.

Tekintsük a 4.24. ábrán bemutatott 𝑇𝑒𝑟𝑚é𝑘𝑒𝑘 és 𝐺𝑦á𝑟𝑡ó𝑘 kapcsolatát bemutató Chen-féle


diagramot. A 𝑇𝑒𝑟𝑚é𝑘ek és 𝐺𝑦á𝑟𝑡ók között egy-többes kapcsolat áll fenn, a 𝐺𝑦á𝑟𝑡 kapcsolat pedig
rendelkezik egy attribútummal (𝑀𝑖ó𝑡𝑎).

GyártóKód Név TermékKód Név


Mióta
Ár

Gyártó Gyárt Termék Kategória

Szín Raktárkészlet
Email Cím

4.24. ábra: Termékek és gyártók entitás-relációs diagramja Chen-féle jelölésmóddal

Ugyanezen adatbázis Crow’s feet jelölésmóddal történő ábrázolása a 4.25. ábrán látható.

77
Termék

Gyártó TermékKód
Név
GyártóKód Ár
Név Kategória
Cím Szín
Email Raktárkészlet
GyártóKód
GyártMióta

4.25. ábra: Termékek és gyártók entitás-relációs diagramja Crow’s foot jelölésmóddal

Vegyük észre, hogy az egy-többes kapcsolatot egy egyszerű éllel jelöljük (az él multiplicitását az él
végein elhelyezett jelölésekkel fejezzük ki, lásd később), a kapcsolathoz rendelt 𝑀𝑖ó𝑡𝑎 attribútumot
pedig a 𝑇𝑒𝑟𝑚é𝑘 entitás halmazba helyeztük 𝐺𝑦á𝑟𝑡𝑀𝑖ó𝑡𝑎 néven (ez egy önkényes döntés volt,
készíthettünk volna kapcsoló entitást is). A Chen-féle jelölésmóddal ellentétben ezen a diagramon már
megjelenik az egy-többes kapcsolatot kifejező 𝐺𝑦á𝑟𝑡ó𝐾ó𝑑 külső kulcs is a 𝑇𝑒𝑟𝑚é𝑘 relációban. Az
entitás halmazok elsődleges kulcsát jelen esetben az attribútumok alatti aláhúzás fejezi ki, de ez
tervező eszközönként eltérő lehet, gyakori még a kulcs piktogram vagy a PK (Primary Key) felirat is az
attribútum mellett.

Ahogyan említettük, a kapcsolatok számosságát az él végein elhelyezett jelölésekkel fejezzük ki. A kör
a 0-t, a függőleges vonal az 1-t, a szétágazó vonal (köznyelvben „tyúkláb”), pedig a tetszőleges számot
jelöli. Az él mindkét végén 2-2 jelölés található: az él középpontjához közelebbi jelzi a minimum, az
entitás halmazhoz közelebbi pedig a maximum számosságot. A 4.25. ábrán látható él 𝐺𝑦á𝑟𝑡ó felöli
oldalán látható jel tehát azt jelenti, hogy egy termékhez minimum 0, maximum 1 𝐺𝑦á𝑟𝑡ó entitás
kapcsolódhat. Az él jobb oldalán látható jel pedig azt fejezi ki, hogy egy 𝐺𝑦á𝑟𝑡óhoz tetszőleges
számú (beleértve 0) 𝑇𝑒𝑟𝑚é𝑘 tartozhat. 4.10. táblázatban összefoglaljuk a Chen és a Crow’s foot
jelölésmód tipikus multiplicitás jelöléseit.

Chen Crow’s foot


0..1

1..1
0..*
1..*

4.10. táblázat: Chen és Crow’s foot multiplicitás jelölésmódok

Vegyük észre, hogy mivel a relációs sémában nem tudunk tetszőleges számosságot kifejezni (pl. 3..5
vagy 0..30), ezért a Crow’s foot jelölésmód sem biztosít erre lehetőséget.

Az ismertetett multiplicitás jelölésekkel a tipikusan használt kapcsolatok már könnyedén kifejezhetők.


4.26. ábrán az 𝐸𝑔𝑦𝑒𝑡𝑒𝑚 és 𝑅𝑒𝑘𝑡𝑜𝑟 entitáshalmazok közti egy-egy típusú 𝑉𝑒𝑧𝑒𝑡 kapcsolatot
illusztráljuk: minden egyetemnek legfeljebb egy (0..1) rektora van, ha valaki rektor, akkor pedig
pontosan egy (1..1) egyetemet vezet.

78
Egyetem Rektor
Egyetem Vezet Rektor Vezet

4.26. ábra: Egy-egy kapcsolat kifejezése Chen és Crow’s foot jelöléssel

A 4.27. ábra egy egy-többes kapcsolatot mutat be: egy egyetem tetszőleges számú (de legalább egy)
karból áll (ezért az 1..* multiplicitás), egy kar viszont pontosan egy egyetemhez tartozik (1..1).

>0 Egyetem Kar


Egyetem Része Kar Része

4.27. ábra: Egy-több kapcsolat kifejezése Chen és Crow’s foot jelöléssel

A 4.28. ábrán látható több-többes kapcsolat azt fejezi ki, hogy egy egyetemen tetszőleges számú (0..*)
oktató oktathat (akár 0 is, közvetlenül az alapítás után), illetve egy oktató tetszőleges számú (0..*)
egyetemen dolgozhat.

Egyetem Oktató
Egyetem Alkalmaz Oktató Alkalmaz

4.28. ábra: Több-több kapcsolat kifejezése Chen és Crow’s foot jelöléssel

Az ábrán látható több-többes kapcsolat nem rendelkezik attribútumokkal, ezért ebben az esetben
egyetlen éllel ki tudjuk fejezni. Gyakorlatban a több-többes kapcsolatot relációs sémára csak kapcsoló
táblák segítségével tudjuk leképezni, mint ahogy a 4.29. ábrán is látható.

Egyetem Alkalmazás Oktató

4.29. ábra: Több-több kapcsolat kifejezése kapcsoló relációval

Az 𝐴𝑙𝑘𝑎𝑙𝑚𝑎𝑧 kapcsolatot egy relációvá alakítottuk, a több-többes multiplicitást pedig két 1-többessé:
egy 𝐴𝑙𝑘𝑎𝑙𝑚𝑎𝑧 kapcsolat példány (tehát egy 𝐴𝑙𝑘𝑎𝑙𝑚𝑎𝑧á𝑠 entitás) pontosan egy 𝐸𝑔𝑦𝑒𝑡𝑒𝑚 és egy
𝑂𝑘𝑡𝑎𝑡ó között teremt kapcsolatot, ezért használjuk az 1..1 multiplicitásokat az 𝐸𝑔𝑦𝑒𝑡𝑒𝑚 és 𝑂𝑘𝑡𝑎𝑡ó
entitás halmazok felé. Egy 𝑂𝑘𝑡𝑎𝑡óhoz, illetve egy 𝐸𝑔𝑦𝑒𝑡𝑒𝑚hez viszont tetszőleges számú (akár 0)
𝐴𝑙𝑘𝑎𝑙𝑚𝑎𝑧á𝑠 entitás is kapcsolódhat (az 𝐸𝑔𝑦𝑒𝑡𝑒𝑚 felől nézve egy 𝐴𝑙𝑘𝑎𝑙𝑚𝑎𝑧á𝑠 entitás egy 𝑂𝑘𝑡𝑎𝑡ó𝑡
fejez ki, az 𝑂𝑘𝑡𝑎𝑡ó felől nézve egy 𝐴𝑙𝑘𝑎𝑙𝑚𝑎𝑧á𝑠 entitás pedig pontosan egy 𝐸𝑔𝑦𝑒𝑡𝑒𝑚𝑒𝑡), ezért az élek
másik végén a 0..* multiplicitást alkalmazzuk.

79
5 Structured Query Language (SQL)

Miután Edgar F. Codd 1970-es cikke lefektette a relációs adatbázis-kezelés elméleti alapjait, kollégái –
Donald D. Champerlin és Raymond F. Boyce – kifejlesztettek egy SQUARE (Specifying Queries As
Relational Expressions) [7] nevű lekérdező nyelvet, amely halmazelméleti és predikátum kifejezések
segítségével tette lehetővé relációs adatok lekérdezését. Ez a nyelv még bonyolult matematikai
szintaxist használt, amelyet felismerve elkészítették a nyelv továbbfejlesztését, amelyet 1974-ben
SEQUEL (Structured English QUEry Language) néven publikáltak. Ez a változat megtartotta elődje
kifejezőerejét, de – ahogyan a neve is mutatja – egyszerű angol nyelvű kifejezésekkel operált, ezáltal
sokkal olvashatóbb és kezelhetőbb kódot eredményezett, valamint a nem szakértők is könnyebben el
tudták sajátítani.

Például az 𝐸𝑀𝑃(𝑁𝐴𝑀𝐸, 𝐷𝐸𝑃𝑇, 𝑆𝐴𝐿𝐴𝑅𝑌, 𝑀𝐺𝑅) alkalmazotti relációban azon alkalmazottak nevének
és fizetésének listáját, akik a ’TOY’ részlegen (DEPT) dolgoznak és ’ANDERSON’-nak hívják a
menedzserét (MGR), az alábbi SQUARE kifejezéssel lehet előállítani:

EMP ('TOY', 'ANDERSON')


NAME, SALARY DEPT, MGR

Ugyanez SEQUEL-ben sokkal olvashatóbb formában írható le:

SELECT NAME, SALARY FROM EMP WHERE DEPT=’TOY’ AND MGR=’ANDERSON’

A SEQUEL megtartotta a SQUARE deklaratív jellegét, vagyis azt fogalmazzuk meg, hogy milyen adatokra
vagyunk kíváncsiak, nem pedig azt, hogy hogyan kell ezeket az adatokat előállítani. A SEQUEL
elnevezést később SQL-re egyszerűsítették (mivel a SEQUEL már bejegyzett védjegy volt egy teljesen
más iparágban), bár kiejtésben még mindig gyakori (és elfogadott) a régi névre utaló „szíkvel” alak is.

1979-ben jelent meg az első kereskedelmi relációs adatbázis-kezelő rendszer, amely már az SQL-t
használta. A termék neve Oracle V2 volt, a szoftvert pedig a Relational Software nevű vállalat
készítette, amelyet 1983 óta Oracle-nek hívunk. Hetekkel az Oracle V2 megjelenése után az IBM is
bemutatta saját SQL-alapú termékét, a System R-t, amelyet 1981-ben az SQL/DS, majd 1983-ban végül
a DB2 követett. A DB2 a mai napig az az IBM első számú adatbázis-kezelő rendszere.

Az SQL nyelvet először az ANSI szabványosította 1986-ban, majd az ISO 1987-ben. A bejegyzett
szabványokat 1989-ben átdolgozták, és SQL89 néven publikálták. Azóta számos újabb SQL szabvány
jelent meg: SQL92, SQL99, SQL:2003, SQL:2006, SQL:2008, SQL:2011, valamennyi a megelőző
változatot egészíti ki új nyelvi és szemantikai elemekkel. Az implementáció megkönnyítése érdekében
minden szabvány három szintet definiál: belépő, közepes és teljes implementáció. Általában egy újabb
szabvány belépő szintje mindig a megelőző szabvány teljes szintjéből indul ki. Mivel a szabványok
általában csak lassan reagálnak az iparági igényekre, ezért a legtöbb adatbázis-kezelő rendszer a
szabványos elemeken túl saját, szabványon kívüli nyelvi elemeket és funkcionalitást is tartalmaz.

Egy tipikus SQL implementáció legalább 4 fajta utasítás csoportot támogat:

1. Data Definition Language (DDL): ebbe az utasítás csoportba tartoznak azok a műveletek,
amelyekkel új adatbázisokat, relációkat tudunk létrehozni, illetve meglévők sémáját
módosítani (pl. CREATE, ALTER, DROP…).

80
2. Data Manipulation Language (DML): a DML-be tartoznak a leggyakrabban használt
utasítások, amelyek segítségével a relációkban tárolt adatokon tudunk műveleteket végezni
(beszúrás, módosítás, lekérdezés stb) (pl. INSERT, SELECT, UPDATE, DELETE…)
3. Data Control Language (DCL): a DCL utasítások segítségével az adatbázis bizonyos részeihez
történő felhasználói hozzáférést tudjuk szabályozni (pl. GRANT, REVOKE)
4. Transaction Control Language (TCL): a tranzakciós műveletek segítségével tudjuk az
adatbázis integritását biztosítani úgy, hogy összetartozó SQL utasítások egy csoportját
oszthatatlan egységbe fogjuk velük össze (pl. BEGIN TRAN, COMMIT, ROLLBACK)

Ebben a fejezetben csak a legalapvetőbb SQL utasításokat fogjuk áttekinteni, amelyeket már az
SQL:2003 szabvány is támogatott (természetesen ebből sem az összeset), és a legtöbb ma használatos
rendszeren ugyanúgy működnek. Továbbá néhány szabványon kívüli utasítást és típust is
megismerünk, amelyek a mindennapi munkához nélkülözhetetlenek (pl. dátum és sztring kezelő
függvényeket). Ismertetőnk elsősorban a MySQL9 5 nyílt forrású adatbázis-kezelő rendszerre épül, de
ahol lehet, igyekszünk bemutatni az Oracle10 12c és Microsoft SQL Server11 2014-beli (TSQL)
implementációt is.

5.1 Relációs adatmodell leírása SQL-lel


Ahogyan a 2.3. fejezetben már említettük, a ma kereskedelmi forgalomban kapható relációs adatbázis-
kezelők nem a klasszikus halmaz, hanem a zsák-alapú megközelítést alkalmazzák. Ennek több oka is
van: egyrészt hatékonysági (az egyes műveletek elvégzése után nem kell a keletkező ismétlődő
rekordokat eltávolítani), másrész a zsákok nagyobb kifejező erővel rendelkeznek, hiszen
duplikátumokat is le tudnak írni. Ennek megfelelően a továbbiakban, amikor relációkról beszélünk,
akkor a zsák-alapú relációkat értjük alattuk.

A relációs adatmodellben megismert relációk három módon jelenhetnek meg SQL-ben:

1. Tárolt relációként, amelyet táblának nevezünk. A táblák az adatbázisban perzisztens módon


eltárolódnak, n-eseit – amelyeket rekordnak szoktunk nevezni – módosíthatjuk,
lekérdezhetjük.
2. Nézetek (View): amelyek futási időben számítódnak ki eltárolt táblák alapján (lsd. 5.2.13
fejezet).
3. Ideiglenes táblák, amelyeket az SQL végrehajtó motor vagy az adatbázisban futó tárolt
eljárások (lsd. 5.4. fejezet) hoznak létre (majd törölnek) köztes adatok tárolása céljából (a
memóriában vagy a lemezen).

Az első lépés új táblák létrehozása előtt, hogy készítünk egy adatbázist, amelyben a táblákat tárolni
szeretnénk. Új adatbázis létrehozása tipikusan a

CREATE DATABASE adatbazisnev

utasítással történik, amelyben megadhatjuk a létrehozandó adatbázis nevét. A legtöbb adatbázis-


kezelő rendszer még számos további paramétert biztosít a létrehozott adatbázis testreszabásához (fájl

9
MySQL website: http://www.mysql.com/
10
Oracle database website: https://www.oracle.com/database/
11
Microsoft SQL Server Website: http://www.microsoft.com/en-us/server-cloud/products/sql-server/

81
név a lemezen, fájl méret, alapértelmezett kódolás stb.). Számos implementációban (MySQL, MSSQL)
lehetőségünk van az aktuálisan használt adatbázis kiválasztására, illetve cserélésére egy felhasználói
folyamaton belül, ezt általában a

USE adatbázisnév

utasítással tehetjük meg. Más rendszerekben (pl. Oracle) minden felhasználóhoz egy saját adatbázis
rendelődik, amelynek a neve a benne létrehozott táblák nevének is a része lesz, így nem szükséges
aktuális adatbázist választani, valamennyi táblanév így is egyedi lesz.

Itt jegyeznénk meg, hogy az SQL nyelv (sztringeket leszámítva) nem különbözteti meg a kis és
nagybetűket. Mégis, az utasításokat és beépített kulcsszavakat általában nagybetűvel szokták írni, a
felhasználói objektumokat (tábla, oszlopnevek stb.) pedig kicsivel, esetleg nagy kezdőbetűvel, hogy így
javítsák a kód olvashatóságát.

5.1.1 Típusok

A táblák attribútumai (mezői) minden esetben típusosak, tehát csak bizonyos, előre meghatározott
típusú adatot lehet bennük tárolni. A következőkben bemutatjuk az SQL-alapú rendszerek által
leggyakrabban támogatott típusokat.

5.1.1.1 Numerikus típusok


 BIT: 1 bit tárolására alkalmas (0 vagy 1). Fizikai szinten általában mégsem egy bitet, hanem
egy bájtot foglal, viszont ha egy táblában több BIT-típusú mező is található, akkor azok
ugyanazon bájt különböző bitjeire képződnek le. Gyakran használják logikai igaz / hamis
(TRUE/FALSE) típusú adatok tárolására is. (MySQL, MSSQL)
 TINYINT: 1 bájtnak megfelelő egész tartományba tartozó számokat tárolhatunk benne,
MSSQL esetén ez 0…255, MySQL esetén -127…128.
 SMALLINT: 2 bájtos tartomány tárolására alkalmas: -215…+215-1. (MySQL, MSSQL)
 INT / INTEGER: a leggyakrabban használt egész típus, a C-beli implementációhoz hasonlóan 4
bájtos tartománnyal rendelkezik: -231…+231-1. (MySQL, MSSQL)
 BIGINT: 8 bájtos értelmezési tartománnyal rendelkező egész típus: -263…+263-1. (MySQL,
MSSQL)
 DECIMAL[(p,s)] (NUMERIC[(p,s)]): szabadon konfigurálható valós típus, a p (<= 38,
precision) paraméterrel definiálható a helyi értékek száma, az s-sel pedig (<=p, scale) a helyi
értékeken belül a tizedes helyek száma. Például a DECIMAL(5,2) a -999.99…+999.99
tartományt jelenti. Alapértelmezetten (ha nem definiáljuk a p és s paramétereket), akkor a
DECIMAL(18,0)-t értjük alatta. (MySQL, MSSQL).
 NUMBER(p,s): csak ORACLE rendszeren használatos, a DECIMAL-hoz hasonló típus, amely
megenged negatív s paramétert, illetve p-nél nagyobbat is (-84<=s<=127). Például s=-2 esetén
százas pontossággal tárolja el az értékeket (9999.99 => 9900), p-nél nagyobb érték esetén
pedig p a tizedes ponttól jobbra található szignifikáns számjegyek számát fogja meghatározni.
Tehát a NUMBER(4,5) típus a 0.000127 értéket 0.00013-ként fogja eltárolni (5 tizedes hely,
abból csak az utolsó 4 lehet használt).

82
 FLOAT[(n)] (REAL[(n)]): az n paraméterrel azt adhatjuk meg, hogy a lebegőpontos szám
mantisszáját hány biten tároljuk el. n maximális és egyben alapértelmezett értéke 53. (MySQL,
MSSQL).

MySQL esetén valamennyi típusnál használható azok UNSIGNED változata is (UNSIGNED INT,
UNSIGNED BIGINT …), ilyenkor a típusok nem rendelkeznek negatív tartománnyal, viszont a pozitív
tartomány a korábbi duplájára nő.

5.1.1.2 Sztringek
SQL-ben a sztring literálokat egyszeres aposztrófok között ábrázoljuk: ’szöveg’. Az egyes
implementációkban támogatottak mind fix, mind változó hosszúságú sztringek, amelyek eltérő
ábrázolási pontossággal (ASCII, Unicode) és megengedett hosszúsággal rendelkeznek:

 CHAR(n): n hosszúságú, ASCII karakterekből álló állandó hosszúságú sztring. Amennyiben az


n hossznál rövidebb szöveget tárolunk benne, akkor a tartalom automatikusan feltöltődik
szóközökkel a megengedett hosszúságra. Például CHAR(6) típuson tárolva a ’tej’ szót
’tej...’-t kapunk. Egy darab karakter tárolására használhatjuk egyszerűen a CHAR típust
is. n maximális értéke MSSQL esetén 8000, Oracle-nél 4000, MySQL esetén pedig 255.
 VARCHAR(n): változó hosszúságú, de maximum n darab karakter tárolására használható. A
CHAR(n) típustól eltérően nem tölti fel a maximális hosszra a benne tárolt szövegeket.
 NCHAR(n), NVARCHAR(n): a CHAR(n) és VARCHAR(n) Unicode kódolású megfelelői. A
dupla pontosságú, két bájtos tárolás következtében MSSQL és Oracle esetén a maximális hossz
az ASCII kódolású fele (4000, illetve 2000), MySQL esetén változatlanul 255.
 VARCHAR2(n), NVARCHAR2(n): csak az Oracle-ben használt adattípusok, amelyek nem
tesznek különbséget a 0 hosszú sztring és a NULL érték (lsd. 5.1.4 fejezet) között (a 0 hosszú
sztringet NULL-ként tárolja)
 VARCHAR(MAX), NVARCHAR(MAX): csak az MSSQL által támogatott speciális típusok. A MAX
nem egy konstanst jelöl, ezen típusok 231-1, illetve 230-1 karaktert tudnak eltárolni: 8000 (ill.
4000) karakterig általános szövegként, az azon túlnyúló szövegrészt pedig átnyúló rekordokon
(lsd. 6.3.3. fejezet).
 TINYTEXT, TEXT, MEDIUMTEXT, LONGTEXT: csak MySQL-ben használt típusok, legfeljebb 255,
216-2, 224-1, illetve 230-1 bájton tárolják az adatokat.
 CBLOB, NCBLOB: csak Oracle-ben használt hosszú ASCII és UNICODE szövegek tárolására
alkalmas típusok. A felhasznált maximális tárterület 128TB.

5.1.1.3 Dátum és idő


A különböző SQL implementációk különböző dátum és idő típusokat támogatnak, amelyek sokszor még
akkor is különböző tartományokkal rendelkeznek, ha azonos két típus neve. Ezért a megfelelő típus
kiválasztása előtt mindenképp ajánlott a kapcsolódó dokumentáció tanulmányozása.

 DATE: a szabvány által is rögzített egyszerű dátum típus, amely év, hónap, nap értékek
reprezentálására alkalmas. Értékadás során ’2015-01-01’ formátumú sztringről automatikusan
konvertálódik. A legtöbb SQL implementáció támogatja, de nagyon eltérő értelmezési
tartományokkal: MySQL esetén 1000-01-01…9999-12-31, MSSQL-nél 0001-01-01…9999-12-
31, Oracle esetén -4712-01-01…9999-12-31, viszont Oracle esetén a típus óra, perc,
másodperc értékeket is tárol a dátumon kívül.

83
 TIME: a szabvány szerint óra, perc, másodperc adatok tárolására alkalmas, a legtöbb
implementáció ezen kívül még törtmásodpercek leírását is támogatja. MySQL esetén az idő
formátuma óra:perc:másodperc és a következő intervallumból veheti fel az értékét:
–838:59:59...838:59:59. A TIME típust nem csak egy napon belüli időpont leírására, hanem
időtartam leírására is használjuk, ezért lehet az óra értéke nagyobb, mint 23. Az MSSQL-nél
00:00:00.0000000... 23:59:59.9999999 tartományon belüli értéket vehet fel egy TIME típusú
oszlop. Bár az SQL 92 szabvány rögzíti, az Oracle mégsem támogatja ezt a típust.
 TIMESTAMP: a szabvány által deklarált típus dátum és idő együttes tárolására. MySQL esetén
az értelmezési tartomány 1970-01-01 00:00:01…2038-01-19 03:14:07 (másodperces
pontossággal), Oracle esetén TIMESTAMP(n) típust használva n-nel (<=9) a törtmásodpercek
tárolási pontosságát (tizedes jegy) adhatjuk meg (ha nem adjuk meg, alapértelmezetten n=6).
MSSQL-ben nem támogatott.
 DATETIME: az MSSQL és MySQL által támogatott típus, amely a DATE és TIME
kombinációjának felel meg. Oracle esetén a DATE típus már önmagában is képes napon belüli
idő értékek eltárolására. MySQL esetén az értelmezési tartomány 1000-01-01 00:00:00…9999-
12-31 23:59:59, MSSQL esetén pedig 1753-01-01...9999-12-31, 0.00333 másodperces
pontossággal.
 DATETIME2: az MSSQL által támogatott típus, amely a DATETIME egy precízebb változata, az
idő reprezentáció pontossága 100ns.

5.1.1.4 Bináris adatok


Az egyszerű típusokon kívül általában lehetőségünk van tetszőleges bináris adat eltárolására is,
akárcsak C-ben egy közönséges byte-tömbben. Erre szolgálnak SQL-ben a különböző BLOB (Binary
Large Object) típusok:

 BINARY(n), VARBINARY(n): a CHAR, illetve VARCHAR-hoz hasonlító típusok, de nem


karaktereket, hanem nyers bájtokat tudunk bennük tárolni. BINARY esetén a lefoglalt
adatméret fixen n bájt, VARBINARY esetén pedig legfeljebb n. MySQL esetén n maximális
értéke 255, MSSQL esetén 8000, Oracle rendszereken pedig 8300, illetve 4M.
 VARBINARY(MAX): az NVARCHAR(MAX)-hoz hasonló típus MSSQL rendszereken, amely
maximálisan 231-1 bájt tárolására alkalmas.
 TINYBLOB, BLOB, MEDIUMBLOB, LONGBLOB: MySQL által támogatott blob típusok: 255, 216-1,
224-1, illetve 232-1 maximális méretekkel. BLOB(n) használata esetén a legkisebb, legalább n
maximális mérettel rendelkező típus választódik ki.
 BLOB: Az Oracle rendszereken használatos típus, maximális mérete adatbázis blokk mérettől
függően 8-128TB.

5.1.2 Táblák létrehozása

Új SQL tábla létrehozása a CREATE TABLE utasítással lehetséges, amelyet a tábla neve, majd
zárójelben vesszővel elválasztva az egyes mezői követnek. Például a 4. fejezetben is bemutatott
𝑇𝑒𝑟𝑚é𝑘(𝑇𝑒𝑟𝑚é𝑘𝐾ó𝑑, 𝑁é𝑣, Á𝑟, 𝐾𝑎𝑡𝑒𝑔ó𝑟𝑖𝑎, 𝑆𝑧í𝑛, 𝑅𝑎𝑘𝑡á𝑟𝑘é𝑠𝑧𝑙𝑒𝑡) relációnak megfelelő táblát az
alábbi SQL utasítással lehet létrehozni:

CREATE TABLE Termek(

84
TermekKod INT,
Nev VARCHAR(100),
Ar INT,
Kategoria VARCHAR(50),
Szin VARCHAR(20),
Raktarkeszlet INT
)

Vegyük észre, hogy az SQL utasításban – az elméleti szinten létező relációs sémával szemben – nem
használtunk ékezetes betűket a tábla vagy az oszlopok elnevezésére. Ennek az az oka, hogy sok SQL
implementáció csak az angol betűket, számokat és néhány speciális karaktert (például _) támogatja a
tábla és oszlopnevekben, ezért célszerű a speciális és ékezetes karaktereket az implementációban
elkerülni.

Termékkódként egy egész számot (INT) használunk, a termék nevét egy sztringben (VARCHAR(n))
írjuk le, amelynek maximális hosszát önkényesen 100 karakterben szabtuk meg. A termék kategóriáját
50, színét pedig 20 karakteren írhatjuk le. Az ár és a raktárkészlet reprezentálására természetesen
szintén INT típust használunk (váltópénz használata esetén az árnál megfontolandó valamilyen
lebegőpontos típus alkalmazása). Egy sztring hosszát érdemes úgy megválasztani, hogy ne legyen túl
rövid, mert akkor bevitel során a szöveg csonkolódna, vagy hibaüzenetet kapnánk. De nem érdemes
feleslegesen nagy méretet sem választani, mivel implementációtól függően ennek teljesítménybeli ára
lehet.

Az egyes mezőkre a típuson kívül további kényszereket fogalmazhatunk meg (például kötelező
kitölteni, a tárolt értéknek egyedinek kell lennie stb.), illetve az egész táblára is adhatunk meg
kényszereket, tárolási paramétereket. A CREATE TABLE utasítás általános alakja a következőképp fest:

CREATE TABLE <TáblaNév>(


<oszlop1> <típus1> [<megkötések1>],
<oszlop2> <típus2> [<megkötések2>],
...
[<tábla szintű megkötések>]
)

5.1.2.1 Virtuális oszlop


Számos adatbázis-kezelő rendszerben lehetőségünk van olyan oszlopok létrehozására is, amelyek nem
tárolnak adatot, hanem a reprezentált értéket futási időben, az aktuális rekord többi attribútumának
értéke alapján állítják elő. Ezeket a mezőket nevezzük virtuális oszlopoknak. Például ha a termékek
árát nettó árként tároljuk, akkor a bruttó árat (27%-os ÁFÁ-t feltételezve) a nettó ár 1.27-szereseként
kapjuk meg. Az aktuális MySQL (5) implementációk még nem támogatják a virtuális oszlopokat, MSSQL
és Oracle szerveren egyszerűen az oszlop nevet követően az AS kulcsszó után írjuk a számított értéket
előállító képletet:

CREATE TABLE Termek(


TermekKod INT,
Nev VARCHAR(100),
Ar INT,
BruttoAr AS Ar*1.27
Kategoria VARCHAR(50),

85
Szin VARCHAR(20),
Raktarkeszlet INT
)

A virtuális oszlop típusa a számított értéket előállító kifejezésből automatikusan következik. MSSQL
esetén a PERSISTED kulcsszó használatával (BruttoAr AS Ar*1,27 PERSISTED) a számított érték
el is tárolható a lemezen. Lekérdezések kiértékelésekor nem kell a számítást futási időben elvégezni
minden egyes alkalommal, hanem az érték azonnal rendelkezésre áll, ami hatékonyságnövekedést
eredményez. (Természetesen az Ar érték módosításakor a BruttoAr-at is újra ki kell értékelni, és el
kell tárolni, ami viszont többletműveletet jelent. Ez nem okoz problémát, ha – mint általában – a
lekérdezés művelet jóval gyakoribb, mint a módosítás).

5.1.3 Adatbevitel

Egy már létező táblába új rekordot az INSERT utasítással vehetünk fel. Általános alakja:

INSERT INTO <tábla> [(<oszlop1>, <oszlop2>, …, <oszlopn>)]


VALUES(<érték1>, <érték2>, …, <értékn>)

A művelet egy új rekordot ad a tábla névvel azonosított relációba, és a rekord oszlop1…oszlopn


attribútumaihoz az érték1…értékn értékeket rendeli. Például egy konkrét terméket a következőképp
vehetünk fel:

INSERT INTO Termek (TermekKod, Nev, Kategoria, Szin, Raktarkeszlet)


VALUES(1, ’Póló’, ’Ruha’, ’Sárga’, 15)

Az új rekord létrehozása során nem kötelező az összes attribútumot felsorolni és annak értéket adni.
A fel nem sorolt oszlopokhoz rendelt érték vagy üres (NULL) vagy az oszlophoz rendelt alapértelmezett
érték lesz (lsd. 5.1.4 fejezet). A táblanév után szereplő attribútum lista el is hagyható, ekkor az oszlopok
adatbázis sémában tárolt sorrendjében kell az új értékeket megadnunk a VALUES kulcsszó után (az
5.1.2 fejezetben létrehozott sémát feltételezve):

INSERT INTO Termek VALUES(1, ’Póló’, ’Ruha’, ’Sárga’, 15)

Mivel egy tábla különböző rendszereken tárolt példányaiban az oszlopok sorrendje nem feltétlenül
egyezik meg (például egyes módosító szkripteket különböző sorrendben futtattunk le a két
adatbázison, ezért az újonnan felvett oszlopok más sorrendben jöttek létre), ezért mindig célszerű az
oszlop neveket is pontosan megadni. Ezáltal a kódunk is olvashatóbb lesz, és csökkentjük a hibák
lehetőségét.

MySQL és MSSQL rendszereken a VALUES kulcsszó után akár több rekord adatait is felsorolhatjuk
vesszővel elválasztva, így egy utasítással több entitás adatait is el tudjuk tárolni:

INSERT INTO Termek VALUES


(1, ’Póló’, ’Ruha’, ’Sárga’, 15),
(1, ’Póló’, ’Ruha’, ’Piros’, 10)

86
5.1.4 NULL, alapértelmezett érték

Az SQL-ben is létezik egy a C-beli NULL-hoz hasonló speciális érték, amit szintén NULL-lal jelölünk. SQL-
ben bármely típusú attribútumhoz rendelhetünk NULL értéket, tehát akár egész számokhoz is. NULL-t
használunk abban az esetben, ha egy adott attribútum értéke az entitás rögzítésének pillanatában nem
ismert, vagy nem használatos.

Például ha egy online boltban nem csak fizikai, hanem virtuális termékeket is árulunk (például újság
előfizetés), amelynek nem értelmezett a színe vagy a darabszáma, akkor a nem értelmezett
attribútumokat vagy nem tűntetjük fel, vagy expliciten NULL értéket adunk nekik:

INSERT INTO Termek (TermekKod, Nev, Kategoria)


VALUES(2, ’Barkácsmagazin előfizetés’, ’Előfizetés’)

illetve

INSERT INTO Termek (TermekKod, Nev, Kategoria, Szin, Raktarkeszlet)


VALUES(2, ’Barkácsmagazin előfizetés’, ’Előfizetés’, NULL, NULL)

Bizonyos attribútumoknál nem értelmezett a NULL érték. Például egy termék azonosítója tipikusan
nem lehet ismeretlen, hasonlóképp egy termék neve sem, illetve minden termék kategorizálható. Az
ilyen attribútumoknál a NOT NULL kényszerrel már a tábla létrehozásakor megtilthatjuk, hogy
kitöltetlenek maradjanak:

CREATE TABLE Termek(


TermekKod INT NOT NULL,
Nev VARCHAR(100) NOT NULL,
Ar INT,
Kategoria VARCHAR(50) NOT NULL,
Szin VARCHAR(20),
Raktarkeszlet INT
)
Ha mégsem adnánk nekik értéket, akkor hibaüzenetet kapunk, és az új rekord nem jön létre.

Az adatbevitel egyszerűsítése céljából az egyes mezőkhöz rendelhetünk alapértelmezett értéket is, így
ha nem rendelünk hozzájuk konkrét értéket a beszúrás során, akkor azok nem a NULL, hanem az
alapértelmezett értéket fogják felvenni. Ezt az értéket a DEFAULT kulcsszót követően adhatjuk meg
minden egyes oszlop esetén. Például egy újonnan felvett termék típusnál a raktárkészlet
alapértelmezetten 0 darab, az ára szintén 0:

CREATE TABLE Termek(


TermekKod INT NOT NULL,
Nev VARCHAR(100) NOT NULL,
Ar INT DEFAULT 0,
Kategoria VARCHAR(50) NOT NULL,
Szin VARCHAR(20),
Raktarkeszlet INT DEFAULT 0
)

Így az

87
INSERT INTO Termek (TermekKod, Nev, Kategoria) VALUES(3, ’Labda’, ’Játék’)

utasítás eredménye az 5.1. táblázatban látható rekord lesz.

TermekKod Nev Ar Kategoria Szin Raktarkeszlet


3 Labda 0 Játék NULL 0
5.1. táblázat: Alapértelmezett érték felvétele

Mivel a Szin-hez nem rendeltünk konkrét értéket, ezért az a NULL értéket fogja felvenni, az Ar és a
Rakterkeszlet attribútumhoz szintén nem rendeltünk konkrét értéket a beszúrás során, de ők
rendelkeznek alapértelmezett értékkel (0), ezért hozzájuk az fog rendelődni.

Az alapértelmezett érték kényszert tábla szintű kényszerként is felvehetjük a CONSTRAINT kulcsszót


követően, névvel ellátva (MSSQL):

CREATE TABLE Termek(


TermekKod INT NOT NULL,
Nev VARCHAR(100) NOT NULL,
Ar INT NOT NULL,
Kategoria VARCHAR(50) NOT NULL,
Szin VARCHAR(20),
Raktarkeszlet INT NOT NULL,
CONSTRAINT DF_Ar DEFAULT 0 FOR Ar,
CONSTRAINT DF_Raktarkeszlet DEFAULT 0 FOR Raktarkeszlet
)

A megoldás előnye, hogy név alapján a kényszer könnyen hivatkozható, így egyszerűen törölhető vagy
módosítható.

5.1.5 Kulcsok, egyedi értékek

Bár a TermekKod attribútumra megköveteljük, hogy minden termék esetén ki legyen töltve,
valahogyan biztosítani kellene, hogy az minden egyes termékre egyedi is legyen. Erre SQL-ben két
lehetőség is kínálkozik: a UNIQUE és a PRIMARY KEY kényszerek. Mindkét kényszer megköveteli, hogy
az attribútum, amelyen alkalmaztuk, nem vehet fel azonos értéket a reláció két különböző
rekordjában. A fő különbségek a két kényszer között a következők:

 Amíg a UNIQUE kényszer megengedi, hogy NULL értéket is felvehessen az attribútum egy vagy
több rekordban (és csak a nem NULL értékek egyediségét követeli meg), addig a PRIMARY KEY
kényszer nem engedi meg NULL értékek tárolását az adott attribútumban.
 Egy táblában egyszerre csak egy elsődleges kulcs lehet, viszont akár több attribútumra is
helyezhetünk UNIQUE kényszert.
 A PRIMARY KEY kényszer alapértelmezetten a rekordok lemezen történő tárolási sorrendjét is
meghatározza (lsd. 7.1 fejezet).

A Termek táblát tehát a következőképpen deklarálhatjuk:

CREATE TABLE Termek( CREATE TABLE Termek(


TermekKod INT PRIMARY KEY, TermekKod INT UNIQUE NOT NULL,

88
Nev VARCHAR(100) NOT NULL, Nev VARCHAR(100) NOT NULL,
Ar INT DEFAULT 0, Ar INT DEFAULT 0,
Kategoria VARCHAR(50) NOT NULL, Kategoria VARCHAR(50) NOT NULL,
Szin VARCHAR(20), Szin VARCHAR(20),
Raktarkeszlet INT DEFAULT 0 Raktarkeszlet INT DEFAULT 0
) )

Gyakran találkozhatunk SQL kódban PRIMARY KEY NOT NULL kényszerrel is, amely – mivel a
specifikáció megköveteli, hogy a PRIMARY KEY attribútum nem vehet fel NULL értéket – redundánsnak
tűnik. Az, hogy gyakran mégis használják a NOT NULL kényszert is, azért van, mert így egyrészt
olvashatóbb az SQL kód, másrészt így nem hagyatkozunk arra, hogy az aktuális SQL szerver
implementáció valóban teljes mértékben megvalósítja a szabványt, és nem engedi meg a NULL
értékeket.

Előfordulhat, hogy nem egy attribútum egyediségét szeretnénk előírni, hanem több attribútumét
együtt. Például nem létezik két olyan termék, amely azonos kategóriában szerepel és azonos a nevük
is (tehát csak különböző termékkategóriában engedélyezünk névegyezést). Ilyen esetekben a UNIQUE
kényszert a tábla szintű kényszerek között kell felvennünk, az oszlopneveket pedig utána zárójelek
között kell felsorolnunk:

CREATE TABLE Termek(


TermekKod INT PRIMARY KEY,
Nev VARCHAR(100) NOT NULL,
Ar INT DEFAULT 0,
Kategoria VARCHAR(50) NOT NULL,
Szin VARCHAR(20),
Raktarkeszlet INT DEFAULT 0,
UNIQUE (Nev, Kategoria)
)

Hasonlóképp alkalmazhatjuk a PRIMARY KEY kényszert is attribútum együttesre (pl. PRIMARY


KEY(TermekKod, Nev)), és mindkét esetben akár egy attribútumot is felsorolhatunk a zárójelben
(pl. UNIQUE(Nev)). A tábla szintű megkötés ezen szabványos formáját nem minden adatbázis-kezelő
rendszer támogatja (a MySQL igen), legtöbb esetben a kényszert csak nevesítve, a CONSTRAINT
kulcsszó segítségével hozhatjuk létre:

CREATE TABLE Termek(


TermekKod INT NOT NULL,
Nev VARCHAR(100) NOT NULL,
Ar INT DEFAULT 0,
Kategoria VARCHAR(50) NOT NULL,
Szin VARCHAR(20),
Raktarkeszlet INT DEFAULT 0,
CONSTRAINT pk_Termek PRIMARY KEY (TermekKod),
CONSTRAINT uc_NevKat UNIQUE (Nev, Kategoria)
)

A példában mind az elsődleges kulcsot, mind az egyedi név-kategória párost leíró kényszert tábla szintű
kényszerként hoztuk létre pk_Termek, illetve uc_NevKat néven. A fenti megoldásnak az a legfőbb

89
előnye, hogy a kényszerekre ezután név szerint tudunk hivatkozni, a név alapján egyszerűen tudjuk
őket módosítani, törölni.

5.1.5.1 Kulcsok generálása


Kényszerek segítségével biztosítani tudjuk, hogy egy kulcs attribútumba csak egyedi értékeket tudjunk
beszúrni, de továbbra is kérdés, hogy hogyan tudunk garantáltan egyedi kulcsokat előállítani? Erre
feladattól és környezettől függően több megoldás is a rendelkezésünkre állhat:

 Természetes elsődleges kulcs esetén (például személyi szám, taj szám stb…) két különböző
személyre (rekordra) ezen attribútumok mindig különböző értékeket vesznek fel, így nem
szükséges további művelet.
 Előállíthatunk elsődleges kulcsot például egy időbélyeg alapján vagy az időbélyeget valamilyen
természetes tulajdonsággal kombinálva. Például a rekord beszúrásának időpontja
milliszekundumos pontossággal (természetesen ehhez biztosítani kellene, hogy egy
milliszekundumon belül ne lehessen két rekordot beilleszteni).
 Használjunk globálisan egyedi azonosítókat (GUID, UUID …), amelyek általában 128 bites
számok, és egyediségükről az operációs rendszer gondoskodik. Új azonosítót MySQL-ben a
UUID(), MSSQL-ben a NEWID(), Oracle alatt pedig a sys_guid() függvényekkel állíthatunk
elő. Bár használatuk kényelmes, a generált számok nem folytonos, véletlenszerű eloszlása
nehézségeket okozhat: ahogyan említettük, az elsődleges kulcs gyakran a rekordok tárolási
sorrendjét is meghatározza, ezért ha a kulcs kiosztása nem folytonos, akkor egy új rekord
felvételekor azt a megfelelő helyre kell beszúrni, és az utána következő rekordokat „arrébb
tolni”, ami többlet terhelést jelent az adatbázis kezelőnek. Továbbá egy véletlennek tűnő nagy
128 bites szám, például 48cd04e8-74b0-478d-b1dc-3ca5ba904f8c (a globálisan egyedi
azonosítók tipikus ábrázolási módja) humán használatra nem kifejezetten alkalmas.
 A leggyakrabban használt módszer, hogy az adatbázis-kezelőre bízzuk egy egyedi sorszám
előállítását. MySQL környezetben ez az AUTO_INCREMENT kényszer segítségével lehetséges,
MSSQL-ben pedig az IDENTITY(seed, increment) kényszerrel:

CREATE TABLE Termek( CREATE TABLE Termek(


TermekKod INT TermekKod INT
PRIMARY KEY AUTO_INCREMENT, PRIMARY KEY IDENTITY(1,1),
Nev VARCHAR(100) NOT NULL, Nev VARCHAR(100) NOT NULL,
Ar INT DEFAULT 0, Ar INT DEFAULT 0,
Kategoria VARCHAR(50) NOT NULL, Kategoria VARCHAR(50) NOT NULL,
Szin VARCHAR(20), Szin VARCHAR(20),
Raktarkeszlet INT DEFAULT 0 Raktarkeszlet INT DEFAULT 0
) )

MySQL esetén az AUTO_INCREMENT kényszer hatására a TermekKod attribútum minden


egyes új rekordnál egy 1-től induló, egyesével növekvő sorozatból veszi az újabb és újabb
értéket (1,2,3,…). Az MSSQL IDENTITIY(seed, increment) kényszere hasonló hatást vált
ki azzal a különbséggel, hogy mi határozhatjuk meg a kezdőértéket (seed), illetve hogy
mennyivel legyen nagyobb (increment) az attribútum értéke a következő rekordnál. Fontos,
hogy a rekordok esetleges törlése nem befolyásolja a generált értékeket, tehát ha az első 100
beszúrt rekordot mind kitöröljük, a 101. rekord akkor is a 101-es értéket kapja, és nem az 1-t.
Amennyiben az attribútum értéke automatikusan generált, akkor nem szerepelhet az INSERT

90
utasítás paraméterei között, és nem adhatunk neki explicit értéket, azt az adatbázis-kezelő
rendszertől fogja megkapni (5.2. táblázat):

INSERT INTO Termek(Nev, Ar, Kategoria, Szin, Raktarkeszlet)


VALUES (’Póló’, 4500, ’Ruha’, ’Sárga’, 15)
INSERT INTO Termek(Nev, Ar, Kategoria, Szin, Raktarkeszlet)
VALUES (’Póló’, 4500, ’Ruha’, ’Piros’, 5)

TermekKod Nev Ar Kategoria Szin Raktarkeszlet


1 Póló 4500 Ruha Sárga 15
2 Póló 4500 Ruha Piros 5
5.2. táblázat: Automatikus kulcs érték generálás

Oracle rendszereken az egyedi értékeket nem attribútum szintű kényszerekkel adhatjuk meg,
hanem úgynevezett szekvenciát kell létrehoznunk, amely egy önálló adatbázis objektum, és
az a feladata, hogy egyedi értékeket állítson elő:

CREATE SEQUENCE seq_termek START WITH 1 INCREMENT BY 1 CACHE 10

A fenti utasítás seq_termek néven hoz létre egy új szekvenciát, amely 1-től indul, egyesével
nő, és a gyorsabb hozzáférés érdekében egyszerre 10 új értéket generál, amelyek közül a még
fel nem használtakat a memóriában tárolja. Új rekord beszúrásakor a szekvencia által generált
következő értéket a NEXTVAL tulajdonsággal érhetjük el:

INSERT INTO Termek(TermekKod, Nev, Ar, Kategoria, Szin, Raktarkeszlet)


VALUES (seq_termek.NEXTVAL, ’Póló’, 4500, ’Ruha’, ’Sárga’, 15)

Ha esetleg a további utasításokban szükség lenne a szekvencia aktuális értékére (a NEXTVAL


mindig a következő értékre ugrik, majd az új értéket adja vissza), akkor azt a CURVAL
tulajdonsággal kaphatjuk meg (seq_termek.CURVAL).
(Megjegyezzük, hogy a 2012-es verziótól kezdve az MSSQL szerverek is támogatják szekvenciák
létrehozását és kezelését hasonló szintaktikával).

5.1.6 Külső kulcs

Külső kulcsokkal SQL-ben csak PRIMARY KEY vagy UNIQUE oszlopokra hivatkozhatunk. Külső kulcs
kényszert legcélszerűbb tábla szinten deklarálni, névvel ellátott kényszerként:

CONSTRAINT <kényszer név> FOREIGN KEY <külső kulcs oszlop1, külső kulcs oszlop2…>
REFERENCES <hivatkozott tábla>(<kulcs oszlop1, kulcs oszlop2…> )

Ahogy a szintaktikából látszik, a hivatkozott kulcs lehet egyszerű és összetett is, ennek megfelelően a
külső kulcs is állhat akár több attribútumból is. Illusztrációképp készítsük el a gyártók tárolására
szolgáló Gyarto relációt, amelynek rekordjaira a termékekből hivatkozni tudunk, mint az egyes
termékek gyártóira:

CREATE TABLE Gyarto(


GyartoKod INT PRIMARY KEY,
Nev VARCHAR(100) NOT NULL,

91
Cím VARCHAR(200) NOT NULL,
Email VARCHAR(100)
)

A tábla elsődleges kulcsa a GyartoKod egész típusú attribútum, minden egyes gyártóhoz eltároljuk
annak nevét és (amennyiben ismert), címét és az e-mail címét. A Termek relációból ezek után a
következőképp hivatkozhatjuk a megfelelő gyártót:

CREATE TABLE Termek(


TermekKod INT PRIMARY KEY,
Nev VARCHAR(100) NOT NULL,
Ar INT DEFAULT 0,
Kategoria VARCHAR(50) NOT NULL,
Szin VARCHAR(20),
Raktarkeszlet INT DEFAULT 0,
GyartoKod INT NOT NULL,
CONSTRAINT FK_Termek_Gyarto FOREIGN KEY (GyartoKod)
REFERENCES Gyarto(GyartoKod)
)

A Termek tábla GyartoKod oszlopát NOT NULL-ként deklaráltuk, tehát kötelező kitölteni, és az
FK_Termek_Gyarto kényszerrel írjuk elő, hogy annak értékei csak a Gyarto reláció GyartoKod
attribútumai közül kerülhetnek ki. Természetesen a külső kulcs típusának egyeznie kell a hivatkozott
kulcs oszlop típusával.

A külső kulcs oszlopoknak nem feltétlen kell kizárniuk a NULL értékeket. Ha megengedik azokat, akkor
az integritás ellenőrzés csak akkor történik meg (vagyis hogy a hivatkozott táblában létezik kulcs azonos
értékkel), ha a külső kulcs attribútum egy konkrét rekordnál ki van töltve. Az integritást az adatbázis-
kezelő rendszer minden körülmények között fenntartja:

 ellenőrzi az érték helyességét, amikor egy külső kulcsnak új értéket adunk (vagyis hogy a
hivatkozott táblában szerepel-e rekord azonos kulcs értékkel), illetve
 ellenőrzi, hogy egy kulcs megváltozásakor (a teljes rekord törlésekor, vagy amennyiben a kulcs
attribútum új értéket kap) vannak-e más olyan rekordok, amelyek külső kulcsokon keresztül
ezt a rekordot hivatkozzák.

Ha az integritás sérülne, akkor alapértelmezetten az utasítás nem hajtódik végre, az adatbázis-kezelő


rendszer hibát jelez a felhasználónak. Az alapértelmezett működésen kívül általában lehetőségünk van
kaszkád viselkedés választására is: egy rekord törlésekor valamennyi hivatkozó rekord is törlődik,
illetve egy hivatkozott kulcs érték megváltoztatásakor valamennyi hivatkozó külső kulcs értéke is
megváltozik. Továbbá lehetőségünk van NULL-ra állítást is választani, vagyis egy rekord törlésekor vagy
egy kulcs érték megváltozásakor valamennyi hivatkozó külső kulcs érték NULL-ra állítódik.

CREATE TABLE Termek(


TermekKod INT PRIMARY KEY,
Nev VARCHAR(100) NOT NULL,
...
GyartoKod INT NOT NULL,
CONSTRAINT FK_Termek_Gyarto FOREIGN KEY (GyartoKod)
REFERENCES Gyarto(GyartoKod)

92
ON DELETE SET NULL
ON UPDATE CASCADE
)

A fenti példában a hivatkozott gyártó rekord törlésekor a kapcsolódó Termek rekordok GyartoKod
külső kulcs egyszerűen NULL-ra állítódik, míg ha a hivatkozott gyártó elsődleges kulcsa megváltozik,
akkor a hivatkozó termékek GyartoKod külső kulcsa automatikusa az új elsődleges kulcs értéket
venné fel.

A különböző implementációkban használatosak a típus után fűzött külső kulcs deklarációk is, mint
például (MSSQL, Oracle):

GyartoKod INT NOT NULL FOREIGN KEY REFERENCES Gyarto(GyartoKod)

vagy tábla szintű, de nem nevesített kényszerek is (MySQL):

FOREIGN KEY (GyartoKod) REFERENCES Gyarto(GyartoKod)

Mi mégis a tábla szinten, névvel ellátott konstrukciót javasoljuk, hiszen – a többi kényszerhez
hasonlóan – ha névvel tudunk hivatkozni a kényszerre, az megkönnyíti annak módosítását, esetleges
törlését.

5.1.7 Séma módosítás

Miután felépítettünk egy adatbázis sémát, a legtöbb esetben nem tekinthetjük azt véglegesnek, előbb-
utóbb szükségünk lesz rá, hogy módosítsunk rajta. Ez lehet bizonyos elemek törlése, attribútumok
típusának megváltoztatása vagy akár új kényszerek létrehozása stb. Ha az adatbázisban már tárolunk
felhasználói adatot, akkor az adatbázis törlése és újbóli létrehozása az új sémával nem járható út. Az
SQL nyelv és az adatbázis-kezelő rendszerek biztosítják a megfelelő műveleteket az adatbázis séma
módosítására.

5.1.7.1 Műveletek oszlopokon


Az egyik leggyakoribb séma módosító művelet az új oszlop felvétele egy táblába, illetve létező oszlopok
módosítása, törlése. A tábla módosító műveletek az ALTER TABLE kulcsszavakkal kezdődnek, amit a
táblanév, majd a specifikus utasítás követ. Egy új oszlopot a következő utasítással hozhatunk létre:

ALTER TABLE <tábla név> ADD <oszlop> <típus> <kényszerek>

Például, ha a Termek relációhoz szeretnénk felvenni egy új oszlopot, amelyben szöveges megjegyzést
tárolhatunk a termékről, akkor azt a következőképp tehetjük meg:

ALTER TABLE Termek ADD Megjegyzes VARCHAR(100)

A típus után felsorolhatjuk az oszlopra vonatkozó kényszereket is, például:

ALTER TABLE Termek ADD Megjegyzes VARCHAR(100) NOT NULL DEFAULT(’’)

Vagyis a Megjegyzes nem lehet NULL, és alapértelmezetten üres sztring. Az adatbázis-kezelő


rendszer természetesen az oszlop létrehozásakor is rögtön biztosítani fogja a kényszereket: tehát ha a

93
táblában már voltak rekordok, és mi egy NOT NULL oszlopot hozunk létre alapértelmezett érték nélkül,
akkor – mivel nem tudja, hogy mivel kellene feltölteni az új oszlopot a már létező rekordoknál –
hibaüzenetet kapunk.

Egy létező oszlop típusát szintén az ALTER TABLE utasítással változtathatjuk meg, a művelet-
specifikus kulcsszavak viszont implementációnként eltérőek. MySQL esetén a MODIFY COLUMN
kulcsszavakat használhatjuk az oszlopdefiníció megváltoztatására.:

ALTER TABLE <tábla név> MODIFY COLUMN <oszlop> <típus> <kényszerek>

Például ha használat közben kiderül, hogy a termékek nevének kicsi a 100 karakteres méret, inkább
150-re lenne szükség, és meg akarjuk engedni a NULL értéket is, akkor azt a következőképp tehetjük
meg:

ALTER TABLE Termek MODIFY COLUMN Nev VARCHAR(150) NULL

Amennyiben az oszlopot át is szeretnénk nevezni, akkor a CHANGE COLUMN kulcsszavakat kell


használnunk:

ALTER TABLE <tábla név>


CHANGE COLUMN <oszlop> <új oszlop név> <típus> <kényszerek>

Oracle környezetben a MODIFY kulcsszó szolgál az oszlop definíció megváltoztatására:

ALTER TABLE <tábla név> MODIFY <oszlop> <típus> <kényszerek>

Oszlop átnevezést pedig a RENAME COLUMN utasítással hajthatunk végre:

ALTER TABLE <tábla név> RENAME COLUMN <oszlop> TO <új oszlop név>

MSSQL rendszeren az ALTER COLUMN utasítás használatos egy létező oszlop tulajdonságainak
megváltoztatására:

ALTER TABLE <tábla név> ALTER COLUMN <oszlop> <típus> <kényszerek>

Oszlopot átnevezni pedig az eddigiektől eltérően az ún. sp_rename nevű tárolt eljárással (5.4. fejezet)
lehet:

EXEC sp_rename ’<tábla név>.<oszlop név>’, ’<új oszlop név>’, ’COLUMN’

Ha az oszlop definíciójának megváltoztatásakor az új típus nem kompatibilis a korábbival (például


rövidebb új sztring méret, mint a már tárolt adatok maximális hossza, vagy szöveges típus helyett
numerikus), akkor konfigurációtól függően hibaüzenetet kapunk, vagy adatvesztés történik.

Létező oszlopot a DROP COLUMN utasítással törölhetünk:

ALTER TABLE <tábla név> DROP COLUMN <oszlop>

Például

ALTER TABLE Termek DROP COLUMN Megjegyzes

94
Az adatbázis-kezelő rendszerek az integritás biztosítása miatt csak azokat az oszlopokat engedik
törölni, amelyekre nem hivatkozik külső kulcs kényszer, tehát az oszlop törlése előtt a kapcsolódó külső
kulcsokat a többi táblából törölni kell.

5.1.7.2 Kényszerek módosítása


A különböző implementációk akár több, eltérő szintaktikát is támogatnak egyszerre egyazon kényszer
törlésére (attól is függően, hogy a kényszer rendelkezik-e névvel vagy sem), mi most csak egyet-egyet
mutatunk be a három rendszer által támogatott utasítások közül. MSSQL és Oracle szerver esetén a
nevesített kényszereket egységesen az

ALTER TABLE <táblanév> DROP CONSTRAINT <kényszer név>

utasítással lehet törölni. Ez vonatkozik a külső kulcsokra (FOREIGN KEY), az elsődleges kulcs kényszerre
(PRIMARY KEY), illetve MSSQL esetén az alapértelmezett értékre (Oracle-ben az alapértelmezett érték
kényszer nem törölhető, csak NULL-ra állítható az értéke). A Gyarto és Termek tábla közti kapcsolatot
tehát a következő utasítással szűntethetjük meg:

ALTER TABLE Termek DROP CONSTRAINT FK_Termek_Gyarto

MySQL esetén enyhén eltérő szintaktikát kell használni. Külső kulcsok törlésére a következő utasítás
szolgál:

ALTER TABLE <táblanév> DROP FOREIGN KEY <kényszer név>

(Tehát CONSTRAINT helyett a FOREIGN KEY kulcsszavakat kell használni). Elsődleges kulcs törlésekor
nincs szükségünk a kulcs megnevezésére (mivel egy táblában úgyis csak egy kulcs lehet), a PRIMARY
KEY kulcsszavak használatosak:

ALTER TABLE <táblanév> DROP PRIMARY KEY

Alapértelmezett kényszer törlésekor pedig egy oszlop módosítást kell végrehajtanunk, és az érintett
oszlop nevével azonosíthatjuk a kényszert (ami egyértelmű, hiszen egy oszlopon úgyis csak egy
alapértelmezett kényszer lehet):

ALTER TABLE <táblanév> ALTER COLUMN <oszlop> DROP DEFAULT

Például a Termek relációban az Ar oszlopra felvett alapértelmezett értéket (DEFAULT(0)) a


következő utasítással törölhetjük:

ALTER TABLE Termek ALTER COLUMN Ar DROP DEFAULT

Természetesen nem csak törölhetjük a kényszereket, hanem akár újakat és létrehozhatunk. Itt
implementációtól függően szintén eltérő módszereket használhatunk. Külső kulcs felvételére
egységesen alkalmazható a következő alak:

ALTER TABLE <tábla név> ADD CONSTRAINT <kényszer név>


FOREIGN KEY <külső kulcs oszlop1, külső kulcs oszlop2…>
REFERENCES <hivatkozott tábla>(<kulcs oszlop1, kulcs oszlop2…> )

95
Ha a Gyarto és Termek táblák közt korábban nem létesítettünk külső kulccsal kapcsolatot, akkor azt
a következőképp tehetjük meg:

ALTER TABLE Termek ADD CONSTRAINT FK_Termek_Gyarto


FOREIGN KEY (GyartoKod) REFERENCES Gyarto(GyartoKod)

Hasonlóképp hozhatunk létre új elsődleges kulcsot is (amennyiben a tábla még nem rendelkezett
ilyennel, vagy töröltük azt):

ALTER TABLE <tábla név> ADD CONSTRAINT <kényszer név>


PRIMARY KEY (<kulcs oszlop1, kulcs oszlop2…> )

Alapértelmezett érték kényszert pedig a különböző rendszereken a következő utasításokkal hozhatunk


létre. MySQL:

ALTER TABLE <tábla név> ALTER COLUMN <oszlop> SET DEFAULT (érték)

Oracle:

ALTER TABLE <tábla név> MODIFY COLUMN <oszlop> DEFAULT (érték)

MSSQL:

ALTER TABLE <tábla név> ADD CONSTRAINT <kényszer név> DEFAULT (érték)

Szintén elmondható, hogy más szintaktikák is használatosak, de terjedelmi okokból mi csak egy-egy
gyakran használt módot mutattunk be a lehetségesek közül.

5.1.7.3 Adatbázis és táblák törlése


Amennyiben már nincs szükségünk egy adatbázisra, akkor azt a

DROP DATABASE <adatbázis név>

utasítással tudjuk törölni. Egyes rendszerek nem engedik az éppen használt adatbázis törlését, tehát
mielőtt az utasítást kiadjuk, célszerű az aktív kapcsolatokat a törlendő adatbázishoz lezárni.
Hasonlóképp, ha egy bizonyos táblára nincs már szükségünk, akkor azt a

DROP TABLE <tábla név>

utasítással tehetjük meg, például:

DROP TABLE Gyarto

Az adatbázis-kezelő rendszerek az integritás biztosítása miatt csak azokat a táblákat engedik meg
törölni, amelynek oszlopaira nem hivatkozik másik táblából külső kulcs kényszer, tehát a tábla törlése
előtt a kapcsolódó külső kulcsokat a többi táblából törölni kell.

5.2 Lekérdezések
A lekérdezések célja, hogy az adatbázis tábláiban tárolt, bizonyos feltételnek megfelelő adatokat
megkeresse, és a felhasználó számára valamilyen szűrt formátumban visszaadja. Az SQL nyelv

96
lekérdezéseket megfogalmazó elemei gyakorlatilag az algebrai lekérdezések (2.2-2.3 fejezetek)
implementációjának tekinthetők, amelyekhez az adatbázis-kezelő rendszer természetesen végrehajtó
motort is biztosít. A továbbiakban tekintsük a következő táblákat adottnak:

Gyarto(GyartoKod INT, GyartoNev VARCHAR(50), Irszam INT, Varos VARCHAR(50), Cim


VARCHAR(50), Email VARCHAR(100))
Termek(TermekKod INT, TermekNev VARCHAR(100), Ar INT, Kategoria VARCHAR(20), Szin
VARCHAR(20), Raktarkeszlet INT,GyartoKod INT)

Továbbá feltételezzük a vásárló és megrendelés adatok tárolására alkalmas táblákat szintén létezőnek:

Vevo(VevoKod INT, VevoNev VARCHAR(50), Irszam INT, Varos VARCHAR(50), Cim


VARCHAR(50))
Megrendeles(MegrendelesKod INT, VevoKod INT, Datum DateTime)
MegrendelesTetel(MegrendelesKod INT, TermekKod INT, Darab INT)

A Vevo, Megrendeles és MegrendelesTetel táblák a 4. fejezetben ismertetett modellnek


megfelelően a vásárlók személyes adatait, a vásárlók által elküldött megrendeléseket és az egyes
megrendelésekbe tartozó tételeket (amelyek rögzítik, hogy melyik termékből pontosan mennyit
rendeltünk egy adott megrendelésben) hivatottak tárolni.

5.2.1 Projekció és szelekció

Egy tipikus SQL lekérdezés tartalmaz projekciót és szelekciót is, tehát mind a rekordokat, mind a
megjelenített attribútumokat szűri. Egy projekciót és szelekciót tartalmazó egyszerű lekérdezés a
következőképp fest:

SELECT <oszlopok> FROM <táblák> WHERE <szelekciós feltétel>

A SELECT után adhatjuk meg az eredményben várt attribútumokat (oszlopokat), a FROM kulcsszó után
pedig a táblát vagy táblákat, amelyekben a keresést végezzük. A WHERE kulcsszó utáni szelekciós
feltétel határozza meg, hogy a vizsgált rekordok közül melyik fog belekerülni a végeredménybe. Reláció
algebrában ez a következő kifejezésnek felel meg:

𝜋𝑜𝑠𝑧𝑙𝑜𝑝𝑜𝑘 (𝜎𝑠𝑧𝑒𝑙𝑒𝑘𝑐𝑖ó𝑠 𝑓𝑒𝑙𝑡é𝑡𝑒𝑙 (𝑡á𝑏𝑙á𝑘))

Az összes olyan termék, amelyből 5-nél kevesebb darab van, a következő lekérdezéssel kereshető meg:

SELECT TermekKod, TermekNev, Ar, Kategoria, Szin, Raktarkeszlet, GyartoKod


FROM Termek WHERE Raktarkeszlet < 5

Ha valamennyi attribútumot szeretnénk megjeleníteni az eredményben, akkor egyszerűsítésképpen


használhatjuk a * rövidítést is:

SELECT * FROM Termek WHERE Raktarkeszlet < 5

A szelekciós feltétel akár el is hagyható, ennek megfelelően a

SELECT * FROM Termek

97
utasítás az összes termék összes attribútumát megjeleníti, ahogy az például az 5.3. táblázatban látható.

TermekKod TermekNev Ar Kategoria Szin Raktarkeszlet GyartoKod


1 Kanapé 150.000 Nappali Szürke 3 1
2 Dohányzóasztal 12.000 Nappali Fekete 3 1
3 Szék 22.000 Iroda Barna 15 2
4 Hintaágy 32.000 Kert Fehér 1 3
5.3. táblázat: Az összes termék összes attribútuma

A projekció közben nem csupán bizonyos oszlopok tartalmát jeleníthetjük meg, hanem műveleteket is
végezhetünk az egyes attribútumok értékein. Ha a 100.000 Ft-nál drágább termékek nevét és bruttó
árát szeretnénk megkapni (feltételezve, hogy az Ár oszlop nettó árakat tartalmaz, és az ÁFA kulcs 27%),
a következő lekérdezést alkalmazhatjuk:

SELECT TermekNev, Ar*1.27 FROM Termek WHERE Ar > 100.000

A számított értéket tartalmazó oszlopok neve implementációtól függően vagy üres, vagy például maga
az értéket előállító képlet (Ar*1.27). Ha pontosan meg akarjuk határozni az oszlop nevét, akkor
alkalmazhatjuk az AS átnevező kulcsszót:

SELECT TermekNev, Ar*1.27 AS BruttoAr FROM Termek WHERE Ar > 100.000

Megjegyezzük, hogy az AS kulcsszó tetszőleges, nem számított oszlop mellett is használható, valamint
akár el is hagyható:

SELECT TermekNev, Ar*1.27 BruttoAr FROM Termek WHERE Ar > 100.000

Az 5.3. táblázatban látható értékek alapján a végeredmény egyetlen rekordot, a kanapé termék adatait
fogja tartalmazni (5.4. táblázat).

TermekNev BruttoAr
Kanapé 190.500
5.4. táblázat: Oszlop átnevezés projekcióban

A következő lekérdezésben minden egyes rekordban megjelenítjük a „Forint” pénznemet is:

SELECT TermekNev, Ar*1.27 BruttoAr, ’Forint’ Penznem FROM Termek


WHERE Ar > 100.000

A lekérdezés eredménye az 5.5. táblázatban látható:

TermekNev BruttoAr Penznem


Kanapé 190.500 Forint
5.5. táblázat: Konstans érték használata projekcióban

Az eddigi lekérdezésekben már használtunk egyszerű relációs operátorokat (> és <) ezeken kívül
használható az =, <=, >= és az egyenlőtlenséget jelölő <> is, valamint a segítségükkel előállított primitív
műveletek logikai kombinációi: a logikai „és”, „vagy”, illetve „nem” műveleteket az SQL AND, OR, illetve
NOT logikai operátoraival fejezhetjük ki. Például azon termékek listája, amelyekre nem igaz, hogy
100.000 Ft-nál olcsóbbak és 5-nél több darab van belőlük, a következő lekérdezéssel állítható elő:

98
SELECT * FROM Termek WHERE NOT(Ar < 100.000 AND Raktarkeszlet > 5)

amivel ekvivalens a

SELECT * FROM Termek WHERE Ar >= 100.000 OR Raktarkeszlet <= 5

lekérdezés (feltételezve, hogy az Ar és Raktarkeszlet oszlopok mindig ki vannak töltve, lsd. 5.2.4.
fejezet).

5.2.2 Sztringek kezelése

Sztringek összehasonlítására használhatók a szokásos relációs műveletek (=, >, <, >=, <=), amelyek két
szöveges érték alfabetikus összehasonlítását végzik el. Például a kanapé nevű termék adatai a

SELECT * FROM Termek WHERE TermekNev =’Kanapé’

utasítással listázhatók ki, de ha az összes olyan termék adataira vagyunk kíváncsiak, amelyek az ABC
szerinti összehasonlításban a ’Kanapé’ szó után következnek, azt is könnyedén megkaphatjuk:

SELECT * FROM Termek WHERE TermekNev >’Kanapé’

Az SQL nyelv az egyszerű összehasonlításnál jóval összetettebb sztring vizsgálatot is lehetővé tesz a
LIKE operátor segítségével. A LIKE kulcsszó után egy szöveg mintát adhatunk meg, amelyben az
ismeretlen részeket %-kal (tetszőleges hosszúságú rész), illetve _ karakterrel (pontosan egy tetszőleges
karakter) jelölhetjük. Például a ’K’ betűvel kezdődő termékeket a következő utasítással kérdezhetjük
le:

SELECT * FROM Termek WHERE TermekNev LIKE ’K%’

Ha minden olyan termékre vagyunk kíváncsiak, ami valahol tartalmaz egy ’k’ betűt, akkor azt a
következőképp tehetjük meg:

SELECT * FROM Termek WHERE TermekNev LIKE ’%k%’

Ahogy említettük, a _ karakter pontosan egy tetszőleges karaktert jelöl. Ennek megfelelően a LIKE
’K_ _ _ _’ kifejezés például az 5 karakter hosszú, ’K’-val kezdődő termékekre szűr.

Amennyiben a két speciális karaktert (% és _) a mintában nem helyettesítő karakterként szeretnénk


használni, hanem például a % karaktert tartalmazó nevű termékeket szeretnénk látni, akkor az ESCAPE
kulcsszó után deklarálhatunk egy olyan speciális jelet, amely után a mintában álló karaktert egyszerű
szövegként kell feldolgozni. A következő utasítás a % jelet tartalmazó nevű termékeket listázza ki:

SELECT * FROM Termek WHERE TermekNev LIKE ’%!%%’ ESCAPE ’!’

A ’%!%%’ mintában az első és utolsó % jel tetszőleges karaktersorozatot jelöl, míg a !% minta pedig
magát a % karaktert.

A különböző SQL implementációk a LIKE utáni sztringben definiált mintában beállítástól függően
megkülönböztethetik a kis és nagy betűket. Ha biztosak akarunk benne lenni, hogy az összehasonlítás
nem tesz különbséget köztük, akkor a vizsgált sztringet a kis vagy nagybetűs megfelelőjére
konvertálhatjuk az UPPER(), illetve LOWER() függvényekkel:

99
SELECT * FROM TERMEK WHERE LOWER(TermekNev) LIKE ’k%’

A fenti lekérdezés a ’k’ betűvel (kicsi vagy nagy) kezdődő nevű termékeket jeleníti meg.

Szintén gyakran használt sztring művelet egy szöveges érték hosszának lekérdezése. A művelet
elvégzésére MySQL-ben a CHAR_LENGTH(szöveg), MSSQL-ben a LEN(szöveg), Oracle-ben pedig a
LENGTH(szöveg) függvények szolgálnak. MySQL szintaktikát követve a

SELECT Nev, CHAR_LENGTH(TermekNev) Hossz FROM Termek

lekérdezés a Táblázat 5-6. táblázatban látható eredményt produkálja.

TermekNev Hossz
Kanapé 6
Dohányzóasztal 14
Szék 4
Hintaágy 8
Táblázat 5-6 – Termékek neve és nevük hossza

Megjegyezzük, hogy MySQL-ben is létezik LENGTH(szöveg) függvény, de az a szöveg paraméternek


nem a karakterben, hanem a bájtban számított méretét adja vissza.

5.2.3 Dátumok

A legtöbb SQL implementáció biztosítja az alapvető műveleteket a dátum és idő típusú értékek
manipulációjához. Az aktuális dátum és idő lekérdezésére MySQL környezetben a NOW() függvény
(illetve csak a dátum lekérdezésére a DATE()), MSSQL rendszereken a CURRENT_TIMESTAMP, Oracle-
ben pedig a SYSDATE kulcsszó szolgál, amelyek DATETIME, illetve Oracle alatt DATE (amely idő értéket
is képes tárolni) típusú értéket ad vissza.

Tegyük fel, hogy a gyártókra vonatkozó információk között eltároljuk az első kapcsolatfelvétel idejét is:

CREATE TABLE Gyarto(


GyartoKod INT PRIMARY KEY,
GyartoNev VARCHAR(100) NOT NULL,
Irszam INT,
Varos VARCHAR(50),
Cim VARCHAR(50),
Email VARCHAR(100),
KapcsolatFelvetel DATETIME)
Egy új gyártót az aktuális dátummal a következő utasítással szúrhatunk be (MySQL):

INSERT INTO Gyarto VALUES(5, ’Kiss Bútor Kft’, 1111, ’Budapest’, ’Virágos tér 4’,
’kb@example.org’, NOW())

Természetesen nem csak az aktuális dátumot használhatjuk a különböző utasításokban, hanem


konkrét dátumot is megadhatunk. MySQL és MSSQL környezetben a konverzió automatikusan
megtörténik a ’yyyy-MM-dd’, illetve ’yyyy-MM-dd HH:mm:ss’ formátumról, tehát például a ’2015-01-
01’ sztring automatikusan konvertálódik dátumra (vagy dátum és időre 00:00:00 időponttal), a ’2015-
01-01 12:10:20’ sztring pedig dátum-idő értékre, amennyiben dátum(idő) típusú érték helyén áll:

100
INSERT INTO Gyarto VALUES(5, ’…’, …, ’…’, ’…’,’…’, ’2015-01-01’)

A 2014.01.01. előtti gyártók adatait például a következő lekérdezéssel kaphatjuk meg:

SELECT * FROM Gyarto WHERE Kapcsolatfelvetel < ’2014-01-01’

Ahogyan a példából is látszik, a dátum, illetve dátum-idő értékek összehasonlíthatóak egymással.

Ha a dátumot más formátumban szeretnénk megadni, akkor konverziós függvényt kell alkalmaznunk.
MySQL esetén az STR_TO_DATE(string, format) függvény használható erre a célra, ahol a
string paraméter tárolja a dátum(és idő) szöveges reprezentációját, a format paraméter pedig a
szöveges reprezentáció felépítését adja meg. Például a ’31.01.2015’ formában megadott dátumot
a ’%d.%m.%Y’ formátum sztringgel lehet feldolgozni:

INSERT INTO Gyarto VALUES(5, ’…’, …, ’…’, ’…’, ’…’ STR_TO_DATE(’31.01.2015’,


’%d.%m.%Y’))

Hasonló célra szolgál az Oracle TO_DATE(string[, format]) konverziós függvénye, valamint az


MSSQL CONVERT(datetime, expression, style) kifejezése, ahol a style paraméteren keresztül
előre definiált formátumok közül lehet választani egy egész szám segítségével.

A dátum értékeket tetszőleges formátumú sztringgé is alakíthatjuk MySQL-ben a


DATE_FORMAT(value, format), Oracle-ben a TO_CHAR(value, format) függvénnyel, illetve
MSSQL-ben a CONVERT(varchar, value, type) kifejezéssel, ahol a type paraméter szintén egy
előre definiált formátumot jelöl ki.

A dátum-idő értékekkel az összehasonlításon kívül különböző számításokat is végezhetünk, például


kivonhatjuk őket egymásból: Oracle-ben erre a célra egyszerűen a kivonás ( - ) operátort használhatjuk.
MySQL-ben erre szolgál a TIMEDIFF(enddate, startdate) függvény, amely idő értékben fejezi ki
a különbséget, valamint a DATEDIFF(enddate, startdate), amely napokban méri a különbséget.
MSSQL-ben a DATEDIFF(datepart, startdate, enddate) a datepart paraméterben kijelölt
időegységben kifejezett különbséget adja vissza. Például a

SELECT DATEDIFF(millisecond, ’2015-01-01’, ’2015-01-02’)

lekérdezés 86400000-t fog eredményezni, ami egy nap hossza milliszekundumban kifejezve.

5.2.4 NULL és UNKNOWN értékek kezelése

Ahogyan már korábban is említettük, a NULL egy speciális érték, amely ismeretlen, vagy adott rekord
esetén nem értelmezhető attribútum értéket takar. A NULL-lal végzett különböző műveletek kiemelt
figyelmet igényelnek, mivel művelettől függően NULL vagy UNKNOWN (ismeretlen) értéket
eredményezhetnek:

 Ha egy aritmetikai operátor (+, -, *, /) valamelyik oldalán NULL-t eredményező kifejezés


áll, akkor a művelet eredménye is NULL lesz. Például ha x=NULL, akkor x+3 is NULL lesz.
Hasonlóképpen, számos beépített függvény eredménye NULL, ha bizonyos paramétere NULL
értéket kap (például CHAR_LENGTH(NULL)).

101
 Ha NULL értéket relációs operátorok (>, >=, =, <=, <) segítségével hasonlítunk össze
bármilyen másik értékkel (akár másik NULL értékkel is), akkor a művelet eredménye
UNKNOWN (ismeretlen), tehát se nem logikai igaz (TRUE), se nem logikai hamis (FALSE). Az
x=NULL esetén az x<3 kifejezés eredménye UNKNOWN. Ha az x<3 egy WHERE feltételben
szerepel, akkor azok a rekordok, amelyeknél a kifejezés UNKNOWN-ra értékelődik ki (ahol az
x attribútum értéke NULL), nem fognak az eredményhalmazba kerülni.

Mivel a NULL nem hasonlítható össze semmilyen értékkel, ezért annak eldöntésére, hogy egy kifejezés
vagy attribútum értéke NULL, nem használhatjuk a szokásos =, illetve <> operátorokat, hiszen az
x=NULL kifejezés eredménye mindig UNKNOWN lesz. A NULL érték vizsgálata az IS NULL, illetve IS
NOT NULL operátorokkal lehetséges: az x IS NULL kifejezés logikai igazat ad vissza, ha x értéke
NULL, különben logikai hamisat. Az x IS NOT NULL kifejezés pedig akkor ad vissza logikai igazat, ha
x egy NULL-tól különböző értéket reprezentál. Ha azokat a gyártókat szeretnénk kilistázni tehát,
amelyeknek az e-mail címe NULL, akkor azt a következő kifejezéssel tehetjük meg:

SELECT * FROM Gyarto WHERE Email IS NULL

Megjegyezzük, hogy ha azt a kérdést szeretnénk megválaszolni, hogy melyek azok a gyártók,
amelyeknél az e-mail cím nincs kitöltve (tehát az Email mező értéke NULL vagy üres sztring), akkor azt
például a következő, kibővített lekérdezéssel tehetjük meg (MySQL):

SELECT * FROM Gyarto WHERE Email IS NULL OR CHAR_LENGTH(Email)=0

A legtöbb SQL implementáció arra is biztosít lehetőséget, hogy lekérdezésekben a NULL értékeket egy
alapértelmezett értékkel helyettesítsük: például ha a NULL e-mail címek helyett üres sztringet vagy
akár azt szeretnénk látni, hogy „<ismeretlen>”. A művelet szintaktikája nem egységes a különböző
implementációkban, MySQL alatt az IFNULL(<kifejezés1>, <kifejezés2>), MSSQL-ben az
ISNULL, Oracle-ben pedig az NVL (azonos paraméterezéssel) utasítások valósítják meg ezt a funkciót.
A kifejezés értéke <kifejezés1>, ha annak az értéke nem NULL, egyébként <kifejezés2>. A
következő utasítás a gyártók nevét és e-mail címét kérdezi le, és a NULL értékű e-mail címeket üres
sztringgel helyettesíti:

SELECT Nev, ISNULL(Email, ’’) FROM Gyarto

<kifejezés1> és <kifejezés2> helyén természetesen nem csak attribútum hivatkozás és


konstans állhat, hanem tetszőleges, skalár értékre kiértékelhető összetett kifejezés is.

Ahogy láttuk, bizonyos logikai kifejezések a szokásos TRUE és FALSE értéken kívül az „ismeretlen”
UNKNOWN értéket is eredményezhetik. Mivel a logikai kifejezéseket az AND, OR, illetve NOT
operátorokkal összefűzhetjük, ezért ezek viselkedése UNKNOWN operandusok esetén további
vizsgálatot igényel:

 Két logikai érték „és” kapcsolata (AND) akkor és csak akkor logikai igaz (TRUE), ha mindkét
operandusa igaz, és pontosan akkor hamis (FALSE), ha legalább az egyik operandusa hamis.
Minden egyéb esetben (egyik operandusa sem FALSE, és legalább az egyik operandusa
UNKNOWN) az eredmény UNKNOWN. Ezt úgy is értelmezhetjük, hogy ha az ismeretlen operandus
értékét ismernénk, akkor attól függően a végeredmény még igaz, illetve hamis is lehetne, ezért

102
eldöntetlen a végeredmény. Míg ha az egyik operandus hamis, akkor az ismeretlen operandus
értékétől függetlenül (akár igaz, akár hamis), a végeredmény mindenképpen hamis.
 Két logikai érték „vagy” kapcsolata (OR) akkor és csak akkor igaz, ha legalább az egyik
operandusa igaz, és pontosan akkor hamis, ha mindkét operandusa hamis. Minden egyéb
esetben (tehát legalább az egyik operandus UNKNOWN, és egyik operandus sem igaz) az
eredmény UNKNOWN.
 Egy logikai érték negáltja (NOT) pontosan akkor igaz, ha az eredeti érték hamis volt, és
pontosan akkor hamis, ha az eredeti érték igaz volt. UNKNOWN érték negáltja szintén UNKNOWN.

A logikai operátorok viselkedését leíró igazságtáblákat az 5.7. táblázatban foglaltuk össze.

AND FALSE UNK TRUE OR FALSE UNK TRUE NOT


FALSE FALSE FALSE FALSE FALSE FALSE UNK TRUE FALSE TRUE
UNK FALSE UNK UNK UNK UNK UNK TRUE TRUE FALSE
TRUE FALSE UNK TRUE TRUE TRUE TRUE TRUE UNK UNK
5.7. táblázat: Logikai operátorok igazságtáblázatai

5.2.5 A CASE operátor

Az SQL nyelv támogat egy, a C-ből már ismert ? : és switch-case operátorokhoz hasonló feltételes
operátort, amely tetszőleges számú elágazással rendelkezhet (csak úgy, mint a switch-case), de
kiértékelhető kifejezés (mint a ? : ), nem pedig vezérlési szerkezet. Általános alakja:

CASE [<kifejezés0>]
WHEN <feltétel1> THEN <kifejezés1>
WHEN <feltétel2> THEN <kifejezés2>

WHEN <feltételn> THEN <kifejezésn>
ELSE <alapértelmezett kifejezés>
END

Tehát az SQL-beli CASE szerkezet egyes ágai nem procedurális szemantikát fogalmaznak meg, mint a
C-beli switch-case elágazás ágai. A CASE operátor rendelkezik eredménnyel, mégpedig azon
kifejezés értékével, amelyhez tartozó feltétel elsőként teljesül. Ha egyik feltétel sem teljesül, akkor az
ELSE ágban megadott kifejezés értéke lesz a CASE operátor értéke is.

A szerkezet két módon is használható:

 Ha a kifejezés0 kifejezést elhagyjuk, akkor a WHEN ágakban használt feltételeknek logikai


igaz-hamisra kell kiértékelhetőnek lenniük.
 Ha kifejezés0 szerepel, akkor a WHEN ágakban használt feltételeknek olyan kifejezéseknek
kell lenniük, amelyek értéke összehasonlítható kifejezés0-val, és az operátor a WHEN
ágakban szereplő feltételek kifejezés0-val való egyezését vizsgálja.

A CASE szerkezet állhat bárhol, ahol SQL kifejezés állhat (SELECT utáni oszlop listában, WHERE szűrő
feltételben, akár másik CASE szerkezetbe ágyazva is. Első példánkban a termékeket szeretnénk
osztályozni ár szerint: a legfeljebb 15000Ft-ba kerülő termékek ’olcsó’-k, a 15001-50000Ft-ba kerülő
termékek ’normál’ árúak, az 50000Ft-nál drágább termékek pedig ’drágá’-k:

103
SELECT TermekKod, TermekNev, Ar,
CASE WHEN Ar <=15000 THEN ’olcsó’ WHEN Ar <= 50000 THEN ’normál’
ELSE ’drága’ END Osztaly FROM Termek

A CASE kifejezéssel számított oszlopnak az ’Osztaly’ nevet adtuk. Ebben az esetben tehát a
szerkezet első változatát alkalmaztuk, amelynek megfelelően a WHEN kulcsszó után logikai kifejezések
szerepelnek. A lekérdezés egy lehetséges kimenete az 5.8. táblázatban látható.

TermekKod TermekNev Ar Osztaly


1 Kanapé 150.000 Drága
2 Dohányzóasztal 12.000 Olcsó
3 Szék 22.000 Normál
4 Hintaágy 32.000 Normál
5.8. táblázat: Termékek osztályozása ár szerint

A második példában a termék kategóriákat szeretnénk átkódolni számokra: a ’Nappali’ kategóriát 0-


val, az ’Iroda’-t 1-gyel, a ’Kert’ kategóriát pedig 2-vel kódoljuk:

SELECT TermekKod, TermekNev, Ar, Kategoria,


CASE Kategoria WHEN ’Nappali’ THEN 0 WHEN ’Iroda’ THEN 1 ELSE 2 END KatKod
FROM Termek

Ebben az esetben a CASE szerkezet második formáját alkalmaztuk: a WHEN ágakban a Kategoria
attribútum bizonyos értékekkel való egyezését vizsgáljuk. (A megoldásban feltételeztük, hogy csak ez
a három kategória létezik, ezért szerepel a Kert kódja (2) az ELSE ágban.) A lekérdezés egy lehetséges
kimenete 5.9. táblázatban látható.

TermekKod TermekNev Ar Kategoria KatKod


1 Kanapé 150.000 Nappali 0
2 Dohányzóasztal 12.000 Nappali 0
3 Szék 22.000 Iroda 1
4 Hintaágy 32.000 Kert 2
5.9. táblázat: A kategória értékek átkódolása számra

5.2.6 Duplikáció eliminálás

Ahogyan már az 5.1. fejezet elején is említettük, a kereskedelmi forgalomban elterjedt relációs
adatbázis-kezelők zsák-alapú relációkat alkalmaznak. Tehát amikor az adatbázis-kezelő rendszer az SQL
lekérdezést kiértékeli, akkor a végeredményben előforduló ismétlődéseket nem távolítja el. Ha az 5.3.
táblázatban ismertetett Termek táblából lekérdezzük a kategóriákat a következő lekérdezéssel, akkor
az eredményben a ’Nappali’ kategória kétszer fog szerepelni (5.10. táblázat).

SELECT Kategoria FROM Termek

104
Kategoria
Nappali
Nappali
Iroda
Kert
5.10. táblázat: A Termek relációban szereplő kategória értékek

A fenti lekérdezés megválaszolásához csupán végig kell iterálni a Termek táblában szereplő
rekordokon, és minden egyes rekord Kategoria attribútum értékét az eredmény relációhoz (zsák!)
kell adni. Bizonyos esetekben viszont nem kívánatos az ismétlődések megjelenítése. Tegyük fel, hogy
a relációban nem 4, hanem 400 termékről tárolunk adatokat, amelyeket 10 kategóriába sorolunk be.
Ha a fenti módszerrel kérdezzük le az alkalmazott kategóriákat, akkor minden egyes kategória
átlagosan 40-szer fog szerepelni a végeredményben, ami az adatok emberi vagy további automatikus
feldolgozását igencsak megnehezíti. Az SQL szerencsére biztosít eszközt a duplikációk kiszűrésére:
ehhez a DISTINCT kulcsszót kell a SELECT utasítás után illeszteni:

SELECT DISTINCT Kategoria FROM Termek

Következésképp minden egyes rekord (amelyek jelen esetben egyetlen értékből állnak, de ugyanúgy
alkalmazható több attribútumos rekordokra is) egyszer fog szerepelni az eredmény relációban. A
DISTINCT operátor a relációs algebrából már megismert 𝛿 operátornak (2.3.5 fejezet) felel meg.

A DISTINCT operátort célszerű körültekintően alkalmazni, mivel az ismétlődések kiszűrésének gyakran


nagyobb a számítás igénye, mint az alap lekérdezés kiértékelésének. A duplikációk eltávolításához az
eredmény reláció rekordjait sorba kell rendezni, hogy az azonos rekordok egymás mellé kerüljenek,
csak ez után lehet az ismétlődéseket hatékonyan felismerni.

5.2.7 Halmazműveletek

Az SQL nyelv támogatja a relációs algebrában már megismert alapvető halmazműveleteket (2.2.3): az
uniót (UNION operátor), a metszetet (INTERSECT) és a különbséget (EXCEPT). Kivételes módon ezen
műveletek implementációja nem zsák-, hanem halmaz-alapú megközelítést alkalmaz: az operandus
relációkat halmazokká alakítja, majd ezek után halmaz alapon végzi el a műveletet. A szabvány szerint
mindhárom operátornak létezik zsák-alapú megfelelője is: UNION ALL, INTERSECT ALL, EXCEPT
ALL, de a gyakorlatban a legtöbb adatbázis kezelő (ahogyan a MySQL, MSSQL és Oracle is) csupán az
UNION ALL operátort szokta implementálni. Az operátorok felhasználásának általános alakja:

R [UNION|UNION ALL|INTERSECT|EXCEPT] S

ahol R és S két azonos sémájú reláció. A két reláció attribútumainak nem kell azonos nevekkel
rendelkezniük, de az attribútumoknak azonos típussal és azonos sorrendben kell szerepelniük a két
relációban.

Ha elő szeretnénk állítani egy listát, amely az összes vásárló és gyártó címét tartalmazza, akkor azt a
következő lekérdezéssel tehetjük meg:

SELECT Irszam, Varos, Cim FROM Vevo


UNION

105
SELECT Irszam, Varos, Cim FROM Gyarto

Ha biztosan tudjuk, hogy nincs olyan gyártó, amely vevőként is megjelenik – vagy ha előfordul ilyen
eset, akkor nem probléma, hogy ezen gyártók címe kétszer is szerepel a végeredményben (mivel a vevő
relációban is szerepel) –, akkor az UNION operátor helyett használhatjuk az UNION ALL operátort is:

SELECT Irszam, Varos, Cim FROM Vevo


UNION ALL
SELECT Irszam, Varos, Cim FROM Gyarto

Ez utóbbi lekérdezés jóval hatékonyabban végrehajtható, mivel a két rész-lekérdezés eredményét


csupán egymás után kell fűzni, a végeredményből nem kell eltávolítani az ismétlődéseket.

Megjegyezzük, hogy az Oracle a MINUS kulcsszót használja a különbség képzésre, a MySQL pedig
egyáltalán nem valósítja meg ezt az operátort, de máshogy (például külső illesztéssel 5.2.8.3. fejezet)
lehetőségünk van két reláció különbségét képezni.

5.2.8 Lekérdezések több reláción

Az eddigiekben áttekintettük, hogy hogyan lehet egy táblára vonatkozó egyszerű lekérdezéseket
megfogalmazni, a kapott eredményeket halmazműveletekkel kombinálni. A relációs adatbázis-kezelő
rendszerek igazi ereje viszont abban rejlik, hogy lehetőséget nyújtanak olyan lekérdezések
megfogalmazására és végrehajtására, amelyek több relációból gyűjtik össze a kívánt adatot. A relációs
algebrában erre szolgált a szorzat operátor és a különböző illesztések. A továbbiakban áttekintjük ezen
operátorok SQL-beli megfelelőit, valamint a lekérdezések egymásba ágyazásának lehetőségét.

5.2.8.1 Keresztszorzat, természetes illesztés


Két vagy több reláció keresztszorzatának (2.2.4. fejezet) előállításához egyszerűen fel kell sorolnunk az
egyes relációkat a FROM kulcsszó után:

SELECT * FROM <tábla1>, <tábla2>, …, <táblan>

Például a

SELECT * FROM Termek, Gyarto

lekérdezés a Termek és Gyarto relációk keresztszorzatát állítja elő. A könnyebb olvashatóság


érdekében a szabvány rögzít egy dedikált operátort is erre a célra: CROSS JOIN. Használatát tekintve
egyszerűen csak le kell cserélnünk a vesszőket a tábla nevek között erre az operátorra:

SELECT * FROM Termek CROSS JOIN Gyarto

Az eredmény reláció sémája a két séma egymás után illesztéséből áll elő:

(TermekKod INT, TermekNev VARCHAR(100), Ar INT, Kategoria VARCHAR(20), Szin


VARCHAR(20), Raktarkeszlet INT, GyartoKod INT, GyartoKod INT, GyartoNev
VARCHAR(50), Irszam INT, Varos VARCHAR(50), Cim VARCHAR(50), Email
VARCHAR(100))

106
Vegyük észre, hogy mivel a GyartoKod attribútum mindkét reláció sémájában szerepel, ezért az
eredmény relációban is kétszer fog szerepelni. Amennyiben meg szeretnénk különböztetni az egyes
attribútumokat akár a projekciós listában, akár a szelekciós feltételek között, akkor a forrás reláció
nevével egyértelműen azonosíthatjuk a tábla.oszlop formátumú hivatkozással:

SELECT Termek.*, Gyarto.GyartoKod, GyartoNev FROM Termek, Gyarto

A fenti lekérdezés a Termek tábla összes oszlopából álló n-eseit párosítja össze a Gyarto tábla
GyartoKod és GyartoNev oszlopaiból álló n-eseivel. Ha csak az összeillő rekordokat szeretnénk az
eredményben tartani - tehát ahol a Termek és a Gyarto táblák rekordjainak GyartoKod attribútumai
megegyeznek – akkor a két reláció természetes illesztését (2.2.5.fejezet) kell végrehajtanunk. A
természetes illesztést SQL-ben a NATURAL JOIN operátorral lehet végrehajtani:

SELECT * FROM Termek NATURAL JOIN Gyarto

Az eredmény reláció sémájában az azonos nevű attribútumok csak egyszer fognak szerepelni:

(TermekKod INT, TermekNev VARCHAR(100), Ar INT, Kategoria VARCHAR(20), Szin


VARCHAR(20), Raktarkeszlet INT, GyartoKod INT, GyartoNev VARCHAR(50), Irszam
INT, Varos VARCHAR(50), Cim VARCHAR(50), Email VARCHAR(100))

Természetesen az illesztés után mind projekciót, mind szelekciót végrehajthatunk az eredmény


reláción. Például a 20.000 Ft-nál drágább termékek nevének és gyártójának listáját az alábbi
lekérdezéssel állíthatjuk elő:

SELECT TermekNev, GyartoNev FROM Termek NATURAL JOIN Gyarto WHERE Ar > 20.000

Megjegyezzük, hogy az Oracle és a MySQL rendszerekkel ellentétben az MSSQL rendszerek nem


támogatják a NATURAL JOIN operátort, mivel a fejlesztői túl nagy hibalehetőséget láttak az
alkalmazásában: az operátor az attribútum nevek egyezésére épít, ezért egy oszlop átnevezés
kellemetlen mellékhatásokkal járhat. Ha például a Gyarto relációban a GyartoKod oszlopot
átnevezzük egyszerűen Kod-ra, akkor a természetes illesztés közben az egyező oszlopnevek teljes
hiánya miatt a végeredmény a két reláció keresztszorzata lesz. Ellenben ha mind a TermekNev, mind
a GyartoNev oszlopok helyett egyszerűen csak Nev-et használunk, akkor – az oszlopnevek egyezése
miatt – a természetes illesztés a Nev attribútum értékek egyezését is megköveteli, ezért az eredmény
reláció üres lesz (feltehetően nincs azonos nevű termék és gyártó), de legalábbis helytelen. A
hibalehetőségből kifolyóan a természetes illesztés helyett mi is az általánosan alkalmazható Theta-
illesztés (5.2.8.2. fejezet) használatát javasoljuk, az illesztett oszlopok pontos megnevezésével.

5.2.8.2 Theta-illesztés
Relációk theta-illesztésekor (2.2.6. fejezet) az illesztési feltétel segítségével pontosan megadhatjuk,
hogy a különböző relációk mely rekordjait illesszük össze. SQL-ben az INNER JOIN vagy egyszerűen
csak JOIN kulcsszót használhatjuk a theta-illesztés elvégzésére, az illesztési feltételt pedig az ON
kulcsszó után adhatjuk meg. Egyszerre tetszőleges számú táblát kapcsolhatunk össze az alábbi
formában:

SELECT <attribútumok> FROM A


INNER JOIN B ON <feltétel1>

107
INNER JOIN C ON <feltétel2>

Ha a termékek és a hozzájuk tartozó gyártók adatait szeretnénk lekérdezni, akkor azt a következő
lekérdezéssel tehetjük meg:

SELECT * FROM Termek INNER JOIN Gyarto ON Termek.GyartoKod = Gyarto.GyartoKod

Mivel a GyartoKod oszlopnév nem egyértelmű, ezért a szűrési feltételben ezen attribútumokat a
táblanévvel különböztetjük meg. Az eredmény reláció sémája – a keresztszorzathoz hasonlóan – az
összekapcsolt relációk sémájának egymás után illesztésével áll elő.

A lekérdezést természetesen szelekcióval és projekcióval is tovább szűrhetjük. Például a 20.000 Ft-nál


drágább termékek nevének és gyártójának listáját theta-illesztéssel az alábbi lekérdezéssel állíthatjuk
elő:

SELECT TermekNev, GyartoNev


FROM Termek INNER JOIN Gyarto ON Termek.GyartoKod=Gyarto.GyartoKod
WHERE Ar > 20.000

Megjegyezzük, hogy az illesztési feltételben – csakúgy, mint a reláció algebrai illesztésben – nem csak
egyenlőséget, hanem tetszőleges logikai igaz-hamisra kiértékelhető kifejezést is vizsgálhatunk.

Abban az esetben, ha egy relációt önmagával akarunk illeszteni, akkor az attribútumok


megkülönböztethetősége érdekében az egyik reláció hivatkozást – átnevezés segítségével – egy
álnéven keresztül kell elvégeznünk. Képzeljük el a következő, személyi adatok tárolására alkalmas
táblát:

CREATE TABLE Szemely


(Id INT PRIMARY KEY NOT NULL, Nev VARCHAR(100), ApaId INT, AnyaId INT)

Ahol az ApaId és az AnyaId a Szemely.Id-re hivatkozó külső kulcs, amelyeket a következő


utasításokkal hozhatjuk létre:

ALTER TABLE Szemely ADD CONSTRAINT FK_Szemely_ApaId


FOREIGN KEY (ApaId) REFERENCES Szemely(Id)
ALTER TABLE Szemely ADD CONSTRAINT FK_Szemely_AnyaId
FOREIGN KEY (AnyaId) REFERENCES Szemely(Id)

Megjegyezzük, hogy mivel a külső kulcsok ugyanazon tábla attribútumait hivatkozzák, ezért a
kényszereket csak a tábla létrehozása után készíthetjük el séma módosító utasításokkal, hiszen a tábla
létrehozása pillanatában még nem létezne a hivatkozott Szemely tábla.

Ha a fenti relációból ki szeretnénk listázni az összes személyt és azok anyjának a nevét, akkor a
Szemely táblát önmagával kell illeszteni, mégpedig az AnyaId=Id feltétel mentén. Ahhoz, hogy a
projekció során mind a gyerek, mind az anya szerepben használt Szemely tábla Nev attribútumát
hivatkozni tudjuk, az egyik reláció hivatkozást (például az anya szerepben lévőt) átnevezésen keresztül
valósítjuk meg:

SELECT Szemely.Nev, Anya.Nev FROM Szemely

108
INNER JOIN Szemely AS Anya ON Szemely.AnyaId=Anya.Id

(Mivel a theta-illesztés csak a párosított rekordokat tartja meg, ezért azon személyek, akiknek az anyja
nem ismert, nem kerülnek bele az eredmény halmazba.)

Hasonlóképp járhatunk el, ha például a testvérpárokat szeretnénk felsorolni. Testvérpár minden olyan
személy páros, amelyeknek azonos vagy az anyja vagy az apja. Első megközelítésben használhatjuk a
következő lekérdezést:

SELECT Szemely.Id, Szemely.Nev, Masik.Id, Masik.Nev


FROM Szemely INNER JOIN Szemely AS Masik
ON Szemely.AnyaId=Masik.AnyaId OR Szemely.ApaId=Masik.ApaId

Mivel a NULL értékek összehasonlítása UNKNOWN logikai értéket eredményez, ezért egy páros nem fog
az eredménybe kerülni csak azért, mert például mindkettőjük ApaId-je NULL. Tételezzük fel az 5.11.
táblázatban látható Szemely táblát mint forrás adatot.

Id Nev ApaId AnyaId


1 Kovács József NULL NULL
2 Kiss Anna NULL NULL
3 Kovács Béla 1 2
4 Kovács Anna 1 2
5.11. táblázat: A Szemely tábla

Mivel Kovács József és Kiss Anna szülei ismeretlenek, ezért ők nem fognak az eredménybe kerülni,
viszont mivel az illesztés a keresztszorzatból indul ki, ezért az eredményben a valós testvérpárok
kétszer is szerepelnek, és minden személy saját magával is testvérpárt alkot (5.12. táblázat).

Id Nev Id Nev
3 Kovács Béla 3 Kovács Béla
3 Kovács Béla 4 Kovács Anna
4 Kovács Anna 3 Kovács Béla
4 Kovács Anna 4 Kovács Anna
5.12. táblázat: Testvérpárok

Triviális kiegészítése a szelekciós feltételnek a Szemely.Id <> Masik.Id feltétel, de ez csak az egy
személy alkotta párokat szűri ki. Ellenben ha Szemely.Id < Masik.Id-t használunk, vagyis a Masik
relációból illesztett személynek mindig a Szemely relációból illesztett személy utáni azonosítóval kell
rendelkeznie, akkor azzal kizárhatjuk a testvérpárok fordított felsorolását is:

SELECT Szemely.Id, Szemely.Nev, Masik.Id, Masik.Nev


FROM Szemely INNER JOIN Szemely AS Masik
ON Szemely.AnyaId=Masik.AnyaId OR Szemely.ApaId=Masik.ApaId
WHERE Szemely.Id < Masik.Id

5.2.8.3 Külső illesztések


Az algebrai külső illesztésekkel a 2.2.8. fejezetben ismerkedtünk meg. Ezek a műveletek a theta, illetve
természetes illesztés eredményét azon rekordokkal is kiegészítik, amelyekhez egyetlen n-est sem
sikerült illeszteni a másik relációból. A művelet elvégzésére az SQL a különböző OUTER JOIN
operátorokat biztosítja:

109
 FULL OUTER JOIN: teljes külső theta-illesztés
 LEFT OUTER JOIN: bal oldali külső theta-illesztés
 RIGHT OUTER JOIN: jobb oldali külső theta-illesztés
 NATURAL FULL OUTER JOIN: teljes külső természetes illesztés
 NATURAL LEFT OUTER JOIN: bal oldali külső természetes illesztés
 NATURAL RIGHT OUTER JOIN: jobb oldali külső természetes illesztés

Külső theta-illesztés esetén az illesztési feltételt szintén az ON kulcsszó után definiálhatjuk.

Példaként soroljuk fel az összes gyártót és gyártónként az általuk előállított termékeket (ha egy gyártó
több terméket is készít, akkor annyiszor fog szerepelni, ahány terméket készít). Ha egy gyártó egyetlen
terméket sem állít elő, akkor is kerüljön bele az eredménybe, a kapcsolódó termék tulajdonságokat
üresen hagyva:

SELECT GyartoNev, TermekNev FROM Gyarto


LEFT OUTER JOIN Termek ON Gyarto.GyartoKod=Termek.GyartoKod

Ha csak azokat a gyártókat szeretnénk visszakapni, akiktől egyetlen terméket sem forgalmazunk (vagyis
egyetlen előfordulásuknál a külső illesztésben a kapcsolódó termék adatai NULL-lal vannak feltöltve),
akkor a szelekciós feltételben azt kell vizsgálnunk, hogy a kapcsolódó termék kulcsa NULL-e (hiszen az
is üres lesz, minden egyéb esetben a kulcs ki lenne töltve):

SELECT GyartoNev, TermekNev FROM Gyarto


LEFT OUTER JOIN Termek ON Gyarto.GyartoKod=Termek.GyartoKod
WHERE Termek.TermekKod IS NULL

A szokásos Szemely(Id, Nev, ApaId, AnyaId) táblát feltételezve, ha ki szeretnénk listázni


minden személyt és az egyes személyekhez tartozó gyerekek nevét – azokat a személyeket sem akarjuk
kihagyni az eredményből, akiknek egyik szülője sem ismert vagy egy gyereke sincs – akkor azt egy teljes
külső lekérdezéssel tehetjük meg:

SELECT Szemely.Nev, Szulo.Nev FROM Szemely


FULL OUTER JOIN Szemely Szulo
ON Szemely.AnyaId=Szulo.Id OR Szemely.ApaId=Szulo.Id

Megjegyezzük, hogy mivel az MSSQL implementációk nem támogatják a természetes illesztést, ezért
ezeken a rendszereken a természetes külső illesztések sem elérhetők. MySQL rendszereken pedig a
FULL OUTER JOIN művelet nem támogatott, de két reláció teljes külső illesztése egyszerűen
előállítható a bal oldali és a jobb oldali külső illesztések uniójaként (hiszen az egyik a bal oldali, míg a
másik a jobb oldali párosítatlan rekordokkal egészíti ki a párosított rekordokat):

SELECT A1..An, B1..Bm FROM A LEFT OUTER JOIN B ON <feltétel1>


UNION
SELECT A1..An, B1..Bm FROM A RIGHT OUTER JOIN B ON <feltétel2>

Az 5.2.7 fejezetben említettük, hogy a MySQL implementációk nem támogatják a különbség


halmazműveletet. Az A – B relációba azok a rekordok kerülnek bele, amelyek A-ban szerepelnek, de
B-ben nem. Tehát ha az A rekordjait bal oldali külső illesztéssel összekapcsoljuk a B-beli rekordokkal,

110
akkor nekünk az A párosítatlan rekordjaira van szükségünk. Feltételezve, hogy A és B kulcsa az Id
attribútum, A – B a következő lekérdezéssel állítható elő:

SELECT A.* FROM A LEFT OUTER JOIN B ON A.Id=B.Id WHERE B.Id IS NULL

5.2.9 Aggregálás

A 2.3.6. fejezetben megismert algebrai aggregációs műveleteket SQL-ben is megvalósították. Ezek a


műveletek a következők:

1. COUNT: a COUNT(A) művelet megszámolja, hogy hány ürestől különböző, de nem feltétlen
egyedi érték található az A oszlopban. Egyszerűsítésként használható a COUNT(*) kifejezés is,
amely a reláció összes sorának számát adja vissza.
2. SUM: a SUM(A) művelet összegzi az A oszlopban tárolt numerikus értékeket.
3. MIN, MAX: a MIN(A), illetve MAX(A) operátorok az A oszlop legkisebb, illetve legnagyobb
értékeit adják vissza. Numerikus értékeket tároló oszlopoknál a működésük egyértelmű,
sztringeket tároló oszlopok esetén az alfabetikusan legkisebb, illetve legnagyobb értéket
eredményezik.
4. AVG: az AVG(A) művelet az A oszlopban tárolt numerikus értékek átlagát állítja elő.

Amennyiben aggregálást végzünk, akkor ugyanezen lekérdezésen belül nem kérdezhetünk le egyszerű
attribútumokat, csak más aggregált vagy csoportosított értékeket (lsd. 5.2.10. fejezet). A lekérdezés
eredménye egyetlen rekordból fog állni. A maximális raktárkészlet értékét (a legnagyobb termék
mennyiség) a következőképp kérdezhetjük le:

SELECT MAX(Raktarkeszlet) FROM Termek

Egy lekérdezésen belül egyszerre több aggregált értéket is számíthatunk, például a maximális, a
minimális és az átlagos raktárkészletet:

SELECT MAX(Raktarkeszlet), MIN(Raktarkeszlet), AVG(Raktarkeszlet) FROM Termek

Aggregációt nem csak oszlopokon végezhetünk, hanem akár számított értékeken is. A következő
lekérdezés a raktáron lévő termékek összegzett értékét állítja elő:

SELECT SUM(Raktarkeszlet*Ar) FROM Termek

Vagyis kiszámoljuk termékenként a raktárkészlet értékét, majd ezt összegezzük az összes termékre.

5.2.10 Csoportosítás

Az algebrai lekérdezéseknél már megismert csoportosítás műveletet (2.3.7. fejezet) SQL-ben a GROUP
BY utasítással valósíthatjuk meg, amelyet a csoportosító attribútumok követnek. A csoportosítás a
szelekció (WHERE feltétel) alkalmazása után történik meg, és szintaktikailag is a szelekciós feltétel után
következik. Ha a lekérdezés csoportosítást alkalmaz, akkor a SELECT kulcsszó után csak csoportosító
attribútumok vagy aggregált értékek állhatnak: ekkor az aggregálás csoportonként történik meg.
Tekintsünk egy egyszerű esetet, amikor csak csoportosító attribútumokat kérdezünk le:

111
SELECT Kategoria FROM Termek GROUP BY Kategoria

Vagyis a termékeket kategóriánként csoportosítjuk, és minden csoportból a kategóriát kérdezzük le.


Így a különböző kategóriákat csak egyszer kapjuk meg, vagyis szemantikailag ugyanaz az eredmény,
mintha a DISTINCT utasítást használnánk:

SELECT DISTINCT Kategoria FROM Termek

Ha azt is meg szeretnénk kapni, hogy hány fajta termék szerepel az egyes kategóriákban, akkor a
lekérdezést kiegészíthetjük a COUNT(*) kifejezéssel:

SELECT Kategoria, COUNT(*) FROM Termek GROUP BY Kategoria

Mivel a csoportosítás a WHERE feltétel alkalmazása után történik meg, ezért a következő lekérdezés a
10.000 Ft-nál többe kerülő termék típusokat számolja meg kategóriánként:

SELECT Kategoria, COUNT(*) FROM Termek WHERE Ar > 10000 GROUP BY Kategoria

Ha a csoportosítás után előálló rekordokra szeretnénk szelekciós feltételt megadni, akkor erre a célra
használhatjuk a GROUP BY utáni HAVING kulcsszót, amely mögött egy további szelekciós feltételt
adhatunk meg, ami már a csoportosítás után alkalmazódik. Ha azt szeretnénk megtudni, hogy melyek
azok a kategóriák, amelyekben legalább 5 különböző, 10.000 Ft-nál többe kerülő termék fajta található,
akkor az a következőképp válaszolhatjuk meg:

SELECT Kategoria, COUNT(*) FROM Termek WHERE Ar > 10000


GROUP BY Kategoria HAVING COUNT(*) >=5

A következő példában az egyes termékekből eddig megrendelt összes darabszámot jelenítjük meg, a
termék nevével és azonosítójával együtt:

SELECT Termek.TermekKod, Nev, SUM(Darab) Darab FROM Termek


INNER JOIN MegrendelesTetel ON Termek.TermekKod=MegrendelesTetel.TermekKod
GROUP BY Termek.Id, Nev

Mivel csoportosítást alkalmazó lekérdezésben csak aggregált értéket vagy csoportosító attribútumot
kérdezhetünk le (hiszen csak ezek az értékek azok, amelyek egyértelműek a csoporton belül), ezért ha
a termék nevét is meg szeretnénk jeleníteni, akkor azt is a csoportosító attribútumokhoz kell adnunk.

5.2.11 Rendezés

Gyakori igény, hogy a lekérdezés során előállított rekordokat valamely attribútum szerint sorba
rendezzük (𝜏 operátor - 2.3.8. fejezet). Az SQL nyelvben ennek elvégzésére az

ORDER BY <kifejezés1> [ASC|DESC], <kifejezés2> [ASC|DESC], …

kifejezés szolgál, amely a lekérdezésekben a GROUP BY / HAVING utasítások után foglal helyet, és
szemantikailag közvetlenül a SELECT által meghatározott projekció végrehajtása előtt fut le. Az ORDER
BY utasítás az eredmény rekordokat az utána megadott kifejezések szerint rendezi alapértelmezetten
növekvő sorrendbe, a DESC módosító szó alkalmazásával pedig csökkenőbe (a növekvő sorrendet

112
expliciten is kifejezhetjük az ASC kulcsszóval). A rendező kifejezésnek skalár értékre kell kiértékelődnie:
legegyszerűbb esetben egy attribútum hivatkozásból áll, de tetszőleges számítást is takarhat. Egyszerre
több kifejezés szerint is rendezhetünk: a második, harmadik stb. kifejezés szerinti rendezés akkor
történik meg, amennyiben az előző kifejezéshez rendelt értékek megegyeznek két rekord esetén. A
következő utasítás a ’Nappali’ kategóriájú termékeket rendezi ár szerint csökkenő sorrendbe,
amennyiben két terméknek egyezik az egységára, akkor őket pedig név szerint növekvő sorrendbe:

SELECT * FROM Termek WHERE Kategoria=’Nappali’ ORDER BY Ar DESC, Nev

Ha az adatbázisban tárolt termékek közül csak azokat akarjuk megjeleníteni, amelyekből eddig
összesen már legalább 100.000 Ft értékben rendeltek, és a termékeket az összes megrendelés érték
szerint csökkenő sorrendbe akarjuk rendezni, akkor az a következő lekérdezéssel tehetjük meg:

SELECT Termek.TermekKod, Nev, SUM(Ar*Darab) FROM Termek


INNER JOIN MegrendelesTetel ON MegrendelesTetel.TermekKod=Termek.TermekKod
GROUP BY Termek.TermekKod, Termek.Nev HAVING SUM(Ar*Darab) > 100.000
ORDER BY SUM(Ar*Darab)

5.2.11.1 Az eredmény rekordok számának korlátozása


A különböző implementációk általában biztosítanak arra lehetőséget, hogy a lekérdezés által előállított
rekordok közül csak bizonyos számút kapjunk vissza. Ezt a funkcionalitást általában rendezéssel együtt
alkalmazzák, és olyan kérdések megválaszolására ad lehetőséget, mint például „az 5 legdrágább
termék”, illetve segítségükkel tudunk hatékonyan „lapozást” megvalósítani: 1-10 rekord, 11-20, 21-30
stb. MySQL-ben erre a célra a LIMIT [<eltolás>,] <darabszám> utasítás használható, ahol a
<darabszám> paraméterrel adhatjuk meg, hogy az eredmény első hány rekordját szeretnénk
megkapni. Az opcionális <eltolás> paraméter segítségével azt határozhatjuk meg, hogy az első hány
rekordot ugorjuk át (alapértelmezetten 0). A LIMIT kulcsszó sorrendben az ORDER BY után
következik. Ha az abc-sorrendbe rendezett termékek közül az első ötöt szeretnénk megkapni, akkor a
következő lekérdezést használhatjuk:

SELECT * FROM TERMEK ORDER BY Nev LIMIT 5

Ha pedig 6..15. helyeken állókat szeretnénk visszakapni, akkor a következő lekérdezést használhatjuk:

SELECT * FROM TERMEK ORDER BY Nev LIMIT 5,10

Az Oracle adatbázis kezelő rendszerek a lekérdezés által előállított sémát kiegészítik egy ROWNUM nevű
pszeudo-oszloppal, amely minden kiválasztott rekordhoz egy számot rendel, amely azt adja meg, hogy
az adott rekord sorrendben hányadikként került kiválasztásra a tartalmazó táblából vagy illesztett
relációkból (a számozás 1-től indul). Amennyiben a ROWNUM hivatkozás sorrendben az ORDER BY után
következik, akkor a sorszámok is újrarendezésre kerülnek. Az abc sorrendben a 6..15 helyen álló
termékeket tehát a következőképp kaphatjuk meg Oracle-ben:

SELECT * FROM TERMEK ORDER BY Nev WHERE ROWNUM >5 AND ROWNUM < 16

MSSQL-ben a visszakapott rekordok számát közvetlenül a SELECT után szereplő TOP n utasítással
korlátozhatjuk legfeljebb n elemre:

113
SELECT TOP 5 * FROM Termek ORDER BY Nev

A lapozás MSSQL rendszerekben a ROW_NUMBER( ) OVER (…) [8] utasítással lehetséges, amely
részletes ismertetésére terjedelmi okokból nem térünk ki.

5.2.12 Beágyazott lekérdezések

Ahogyan az algebrai lekérdezésekben, úgy az SQL is biztosít lehetőséget arra, hogy egy lekérdezés
eredményét egy másik lekérdezés részeként használjuk fel. Egy SQL lekérdezés eredményét
háromféleképp ágyazhatjuk be egy másikba:

1. Felhasználhatjuk egy másik lekérdezés FROM listájában, akárcsak egy fizikailag létező táblát, vagy
illeszthetünk is hozzá. Ebben az esetben az al-lekérdezést mindig nevesíteni kell. Például:

SELECT TermekId FROM (SELECT * FROM Termek WHERE Raktarkeszlet > 3) AS temp

Vagyis a 3-nál nagyobb raktárkészlettel rendelkező termékeket előállító lekérdezés


eredményrelációjára temp néven hivatkozunk, majd ebből a relációból kérdezzük le a termékek
azonosítóit.
Beágyazott lekérdezéssel kifejezhető a csoportosítás utáni szűrés, a HAVING használata is.

SELECT Kategoria, COUNT(*) FROM Termek WHERE Ar > 10000


GROUP BY Kategoria HAVING COUNT(*) >=5

A fenti lekérdezésben a kategóriánkénti csoportosítás után a HAVING segítségével egy további


szelekciót alkalmazunk, mégpedig csak azokat a kategóriákat kérjük, amelyekben legalább 5, a
feltételnek megfelelő termék szerepel. Ugyanezt az eredményt kapjuk, ha a csoportosítás által
előállított relációból egy második lekérdezéssel kérjük le az 5-nél több eleműeket:

SELECT * FROM
(SELECT Kategoria, COUNT(*) CNT FROM Termek WHERE Ar > 10000
GROUP BY Kategoria) Csoportok
WHERE CNT >=5

A Csoportok-nak elnevezett beágyazott lekérdezésben a COUNT(*)-nak megfelelő oszlopot CNT-


nek nevezzük el, hogy a külső lekérdezésből hivatkozni lehessen rá.

2. Amennyiben egy lekérdezés skalár értéket eredményez (egy sor, egy oszlop), akkor azt
felhasználhatjuk olyan helyeken, ahol egy egyszerű skalár érték vagy attribútum az elvárt. Például
a termékek és a rájuk leadott összes megrendelés (kapcsolódó megrendeléstételek
darabszámának az összege) listája a következőképp is előállítható:

SELECT TermekNev, (SELECT SUM(Darabszam) FROM MegrendelesTetel


WHERE MegrendelesTetel.TermekKod=Termek.TermekKod) Osszes FROM Termek

A külső lekérdezés minden egyes Termek rekordra lefuttatja a beágyazott lekérdezést, és előállítja
az egyes termékekhez tartozó összegzett megrendelés számot, amelyet „Osszes”-nek nevezünk
el. Ahogy a példában is látjuk, a beágyazott lekérdezésben hivatkozhatjuk a beágyazó
lekérdezésben használt táblák oszlopait is. Megjegyezzük, hogy az SQL végrehajtó motor a fenti

114
lekérdezést általában illesztésre fordítja át, és nem végzi el ténylegesen minden egyes termékre a
beágyazott lekérdezést újra és újra.

3. Egy lekérdezés szerepelhet egy másik WHERE feltételében. A visszaadott rekordokra logikai
feltételeket fogalmazhatunk meg:
 Az EXISTS kulcsszóval azt vizsgálhatjuk meg, hogy egy reláció üres-e. Ha azokat a
termékeket szeretnénk kilistázni, amelyekhez tartozik legalább egy megrendeléstétel,
akkor azt egy beágyazott lekérdezéssel is előállíthatjuk:

SELECT * FROM Termek WHERE EXISTS (SELECT * FROM MegrendelesTetel


WHERE MegrendelesTetel.TermekKod=Termek.TermekKod)

A lekérdezés minden termékre megvizsgálja, hogy van-e akár egy, a kódjához tartozó
megrendeléstétel.
 Az IN kulcsszóval azt ellenőrizhetjük, hogy egy bizonyos érték szerepel-e egy egyoszlopos
relációban. A megrendeléssel rendelkező termékek listáját úgy is előállíthatjuk, hogy a
MegrendelesTetel táblából először leválogatjuk a termék kódokat (minden termék kód,
amely szerepel a MegrendelesTetel táblában, értelemszerűen rendelkezik
megrendeléssel), majd a Termek táblából azokat a rekordokat választjuk ki, amelyek kódja
szerepel a leválogatott kódok között:

SELECT * FROM Termek WHERE TermekKod IN


(SELECT TermekKod FROM MegrendelesTetel)

 Az ALL, illetve ANY kulcsszavakkal azt írhatjuk elő, hogy az eredmény reláció összes (ALL)
vagy bármely (ANY) elemére teljesül egy logikai feltétel. Ha azokat a Termékeket
szeretnénk megkeresni, amelyekből legalább egy megrendeléstételt ki tudunk elégíteni
(tehát több van raktárkészleten, mint a megrendelésben szereplő darabszám az adott
termékből), akkor azt az alábbi beágyazott lekérdezéssel valósíthatjuk meg (többek
között):

SELECT * FROM Termek WHERE Raktarkeszlet >= ANY (SELECT Darabszam FROM
MegrendelesTetel WHERE MegrendelesTetel.TermekKod = Termek.TermekKod)

Vagyis minden egyes termékre kiválasztjuk a rájuk a különböző megrendelésekben leadott


rendelés darabszámokat, és a végeredménybe azokat a termékeket választjuk, amelyekből
legalább annyi van raktáron, mint akár csak egy darabszám is a kiválasztottak között.
 A NOT kulcsszóval az IN és az EXISTS operátorok negálhatók (NOT IN, NOT EXISTS)

5.2.13 Nézetek

A táblák az általuk reprezentált adatot fizikailag is eltárolják az adathordozón, és megőrzik azt, amíg
utasítást nem adunk annak megváltoztatására. Az SQL-beli relációk egy másik fajtája a (virtuális) nézet
(view), amely nem tárol adatot, tartalmát egy SQL lekérdezéssel határozhatjuk meg, amely más
nézeteket és táblákat használ adatforrásként. A nézeteket éppúgy fel tudjuk használni
lekérdezésekben, mint a fizikailag is eltárolt táblákat, viszont a lekérdezés kiértékelésekor a felhasznált

115
nézetek mögött álló lekérdezést is ki kell értékelni (akárcsak egy beágyazott lekérdezést). Egy nézet
definiálására a következő SQL utasítást használhatjuk:

CREATE VIEW <nézet neve> AS <lekérdezés>

Például ha egy olyan nézetre van szükségünk, amely a legnépszerűbb öt terméket listázza ki (a
legnagyobb megrendelés-állománnyal rendelkező termékek), akkor azt a következő utasítással
hozhatjuk létre (MySQL):

CREATE VIEW TopTermekek AS


SELECT Termek.TermekKod, Nev, SUM(Darab) Darab FROM Termek
INNER JOIN MegrendelesTetel ON Termek.TermekKod=MegrendelesTetel.TermekKod
GROUP BY Termek.TermekKod, Termek.Nev
ORDER BY SUM(Darab) DESC
LIMIT 5

A TopTermekek nézetnek, mint relációnak a sémája 3 oszlopból fog állni: TermekKod, Nev, Darab
(mivel a SUM(Darab) összeget egyszerűen Darab-nak neveztük el). Ezek után, ha szükségünk van a
nézet által reprezentált adatokra, akkor egyszerűen megkaphatjuk azt a következő lekérdezéssel:

SELECT * FROM TopTermekek

A nézeteket alkalmazó lekérdezésekben ugyanúgy használhatunk szelekciós feltételt is, mint a csak
táblákat használó társaikban, de akár illeszthetünk is nézetekhez. A következő lekérdezés a
legnépszerűbb termékekről megjelenített adatokat még kiegészíti a termékek aktuális
raktárkészletével is:

SELECT tt.TermekKod, tt.Nev, Raktarkeszlet


FROM TopTermekek tt INNER JOIN Termek t ON tt.TermekKod=t.TermekKod

Amennyiben egy nézetre már nincs szükség, akkor azt a következő utasítással törölhetjük:

DROP VIEW <nézet neve>

Ha pedig meg szeretnénk változtatni egy nézet definícióját, akkor azt az ALTER VIEW utasítással
tehetjük meg:

ALTER VIEW <nézet neve> AS <új lekérdezés>

5.2.13.1 Materializált nézetek


Az eddigiekben a virtuális nézetekkel ismerkedtünk meg: amikor az általuk reprezentált adathoz hozzá
akarunk férni, akkor a nézetbe ágyazott lekérdezés minden egyes alkalommal kiértékelődik. A nézetek
egy másik fajtája az ún. materializált nézet, amely a nézetbe ágyazott lekérdezés által előállított
rekordokat fizikailag is eltárolja. Ezáltal amikor az általuk megjelenített adatot fel akarjuk használni,
akkor az rögtön rendelkezésre áll a lemezen, így a rájuk építő lekérdezések jelentősen gyorsulnak. Az
ilyen nézeteket rendszeresen frissíteni is kell, hiszen az általuk hivatkozott táblák tartalma is
folyamatosan változhat. A frissítés – implementációtól függően – kétféleképpen történhet:

 kérésre, a felhasználó általi kezdeményezésre,

116
 automatikusan: a hivatkozott táblák tartalmának megváltozásakor a hivatkozó
materializált nézetek újraszámítódnak (optimalizáltan, a változásokat inkrementálisan
alkalmazva).

Természetesen a nézetek karbantartása erőforrást igényel, de gyakran használt nézeteknél, amelyek


adatforrásai relatíve ritkán változnak, ez a többlet erőforrás igény bőven megtérülhet.

A materializált nézeteket csak a nagyobb adatbázis-kezelő rendszerek támogatják (pl. MSSQL, Oracle,
DB2 – a MySQL nem), részletes ismertetésükre terjedelmi okokból nem kerül sor.

5.2.14 Lekérdezés eredményének beszúrása

Az 5.1.3 fejezetben már találkoztunk az INSERT INTO utasítással, amely segítségével új rekordokat
tudunk egy adatbázis táblába beszúrni. A példákban feltételeztük, hogy az új adatok az utasítás
megírásának pillanatában ismertek, hiszen csak konstans vagy egyszerű számított (aktuális dátum)
értékeket használtunk. Az INSERT INTO utasítást használhatjuk azonban úgy is, hogy a beszúrandó
rekordokat futási időben, egy lekérdezés segítségével állítjuk elő:

INSERT INTO <cél tábla> (<cél oszlop1>,<cél oszlop2> …)


SELECT <forrás oszlop1>, <forrás oszlop2> … FROM …

Természetesen a beágyazott lekérdezésnek ugyanannyi, és sorrendben ugyanolyan típusú oszlopot kell


előállítani, mint ahány és amilyen attribútumnak értéket szeretnénk adni a beszúrás közben. Az
utasítás lefutásakor a beágyazott lekérdezés kiértékelődik, és az általa előállított minden egyes
rekordot a céltáblához adja. A következő példában az 100-as azonosítóval rendelkező megrendeléshez
minden egyes 10.000 Ft-nál drágább termékből hozzá adunk egy 1 darabos megrendeléstételt:

INSERT INTO MegrendelesTetel (MegrendelesKod, TermekKod, Darab)


SELECT 100, TermekKod, 1 FROM Termek WHERE Ar > 10.000

5.3 Adat módosítás


Az adatbázis-kezelő rendszerek természetesen nem csak új adatok beszúrását, illetve lekérdezést
teszik lehetővé, hanem a már az adatbázisban tárolt adatok módosítására, törlésére is biztosítanak
módszereket. Létező rekordok tulajdonságainak módosítására az UPDATE, rekordok törlésére pedig a
DELETE utasítás szolgál.

5.3.1 Létező adatok frissítése

Amennyiben korábban eltárolt adatokon módosítást szeretnénk végrehajtani, akkor azt az UPDATE
utasítás segítségével végezhetjük el, amelynek általános alakja:

UPDATE <tábla> SET <oszlop1>=<érték1>, <oszlop2>=<érték2>, …


[WHERE <szűrő feltétel>]

Az UPDATE utasítás utáni első paraméter a tábla neve, amelyben tárolt adatot módosítani szeretnénk,
a WHERE utáni szűrőfeltétellel pedig kiválaszthatjuk a módosítandó rekordokat: itt a lekérdezésekhez
hasonló feltételeket alkalmazhatunk, de a feltételt (a WHERE-rel együtt) akár el is hagyhatjuk, ekkor a

117
módosítás a tábla minden egyes rekordjára lefut. A SET kulcsszó után vesszővel elválasztva
felsorolhatjuk az attribútum érték módosításokat, amelyek minden egyes a feltételnek megfelelő
rekordra alkalmazódnak. Az értékadások jobb oldalán nem csak konstans, hanem számított értékek is
szerepelhetnek, amelyek – akárcsak egy lekérdezésnél – állhatnak a módosított rekord
attribútumaiból, beépített függvényekből, különböző aritmetikai kifejezésekből, de akár beágyazott
lekérdezésekből is, amelyek skalár értéket szolgáltatnak. A következő utasítás a 100.000 Ft-nál többe
kerülő termékek árát csökkenti 10%-kal:

UPDATE Termek SET Ar = Ar*0.9 WHERE Ar > 100000

Előfordulhat, hogy a módosítandó rekordok kiválasztásához más táblák adataira is szükség van, de akár
az új értékek előállításához is szükség lehet más relációkban tárolt rekordokra. Tegyük fel, hogy
azoknak a termékeknek az árát akarjuk csak 10%-kal csökkenteni, amelyekből még soha nem
rendeltek. A szelekciós feltételben – csakúgy, mint egy lekérdezésnél – alkalmazhatunk egy beágyazott
lekérdezést a megfelelő termékek kiválasztásához:

UPDATE Termek SET Ar = Ar*0.9 WHERE TermekKod NOT IN


(SELECT TermekKod FROM MegrendelesTetel)

Vagyis a beágyazott lekérdezésben összesítjük azokat a termék kódokat, amelyekre történt már
megrendelés, és a módosítást ezen a halmazon kívüli termékekre végezzük el. Egyes SQL
implementációk – bár nem egységes módon – támogatják illesztések alkalmazását is az UPDATE
utasítások használata közben. MySQL-ben a táblákat és az illesztési feltételeket egyszerűen az UPDATE
utasítás után kell felsorolni:

UPDATE Termek LEFT OUTER JOIN MegrendelesTetel


ON Termek.TermekKod=MegrendelesTetel.TermekKod
SET Ar = Ar*0.9
WHERE MegrendelesTetel.TermekKod IS NULL

(Külső illesztéssel megkeressük a párosítatlan termékeket.) MySQL-ben megengedett egyszerre több


tábla illesztett rekordjainak a módosítása is. Az MSSQL-beli szintaktika némileg különböző: az UPDATE
utasítás után megnevezzük a változtatott táblát (csak egy tábla rekordjainak módosítása
megengedett), és a FROM kulcsszót követően végezzük el a relációk illesztését:

UPDATE Termek SET Ar = Ar*0.9


FROM Termek LEFT OUTER JOIN MegrendelesTetel
ON Termek.TermekKod=MegrendelesTetel.TermekKod
WHERE MegrendelesTetel.TermekKod IS NULL

Oracle-ben az illesztett módosítás nem támogatott, beágyazott lekérdezésekkel tudjuk a több táblán
átívelő módosításokat megoldani.

5.3.2 Törlés

Az utolsó, még nem részletezett adatkezelési primitív a törlés, amelyre a DELETE utasítás szolgál.
Szintaktikája:

DELETE FROM <tábla> [WHERE <szűrő feltétel>]

118
A szűrő feltételben a szelekciós feltételeknél már megismert konstrukciók használhatók, a feltétel
elhagyása a tábla valamennyi rekordjának törlését eredményezi. Például az összes, ’Nappali’
kategóriájú terméket a következő utasítással törölhetjük:

DELETE FROM Termek WHERE Kategoria=’Nappali’

A termékek törlése csak akkor lehetséges, ha már nem hivatkozik rájuk másik rekord külső kulcson
keresztül, ennek megfelelően a termékek törlése előtt el kell távolítanunk a kapcsolódó
megrendeléstételeket is:

DELETE FROM MegrendelesTetel WHERE TermekKod IN


(SELECT TermekKod FROM Termek WHERE Kategoria=’Nappali’)

A legtöbb implementáció – a módosításhoz hasonlóan – támogatja az illesztések használatát a törölt


rekordok azonosításánál: a MySQL és Oracle rendszereken a DELETE utasítás után fel kell sorolnunk a
relációkat, amelyekből törölni szeretnénk, a FROM kulcsszót követően pedig elvégezzük az illesztést.
MSSQL adatbázis-kezelők esetén egy utasítással csak egy táblából törölhetünk, tehát a DELETE utasítás
után csak egy relációt nevezhetünk meg:

DELETE MegrendelesTetel FROM MegrendelesTetel


INNER JOIN Termek ON Termek.TermekKod=MegrendelesTetel.TermekKod
WHERE Kategoria=’Nappali’

5.4 Az SQL procedurális kiterjesztései


Az eddigi fejezetekben megismerkedtünk azokkal az alapvető SQL utasításokkal, amelyek segítségével
adatbázis sémákat készíthetünk és szerkeszthetünk, illetve a négy alapművelettel (SELECT, INSERT,
UPDATE, DELETE) az adatbázisokban tárolt adatok elérésére és módosítására. Az elterjedt adatbázis-
kezelő rendszerek az alapműveleteken túl általában támogatnak valamilyen procedurális-jellegű
kiegészítést is, amely segítségével SQL utasítások sorozatát szervezhetjük egységbe, és hajthatjuk
végre egyszerre. A procedurális kiegészítések az SQL:1999 szabvány óta kezdtek beépülni a nyelvbe
Persistent Stored Modules (PSM) [9] néven. Mivel az elterjedt adatbázis-kezelő rendszerek ekkor már
rendelkeztek saját nyelvi elemekkel procedurális konstrukciók leírására (Oracle – PL/SQL [10], MSSQL
– TSQL [8], IBM DB2 – SQL PL [11]), ezért a szabványt azok tipikusan csak részben követik. Az általában
minden rendszer által támogatott modul típusok a következők:

 Tárolt eljárás: A tárolt eljárások leginkább a strukturált programozási nyelvekben megismert


függvényekre hasonlítanak: rendelkezhetnek be- és kimenő paraméterekkel, változókkal, a
törzsükben SQL utasításokat hajthatnak végre, amelyeket vezérlési szerkezetekkel fűzhetünk
egymás után. A tárolt eljárásban esetlegesen lefuttatott lekérdezés eredménye egyben a tárolt
eljárás eredménye is lesz. Tárolt eljárásokat tipikusan DML jellegű műveletek egységbe
zárására szokták használni, nem hívhatók meg más lekérdezés részeként.
A következő MySQL-nek megfelelő szintaktikájú tárolt eljárás egy adott kóddal (kod bemenő
paraméter) rendelkező megrendelést töröl az összes kapcsolódó tétellel együtt:

CREATE PROCEDURE DeleteMegrendeles(IN kod INT)


BEGIN
DELETE FROM MegrendelesTetel WHERE MegrendelesKod=kod

119
DELETE FROM Megrendeles WHERE MegrendelesKod=kod
END

Az eljárás ezután a CALL utasítással indítható el:

CALL DeleteMegrendeles(5)

 Felhasználói függvény: A függvények olyan speciális tárolt eljárások, amelyek kötelezően


rendelkeznek visszatérési értékkel, ami egy egyszerű skalár érték kell, hogy legyen. A
felhasználói függvényeket – csakúgy, mint beépített társaikat – tipikusan attribútum
transzformációkhoz használhatjuk fel, felhasználhatók más lekérdezések szelekciós és illesztési
feltételeiben, illetve a szelektált attribútumok transzformációjához. A következő függvény
(MySQL) egy árat reprezentáló egész számhoz egy sztringet rendel: ’drága’, ha 50.000-nél
nagyobb egész paramétert kap, egyébként pedig ’olcsó’.

CREATE FUNCTION Arszint (ar INT) RETURNS VARCHAR(20) DETERMINISTIC


BEGIN
IF ar > 50000
THEN RETURN ‘drága’
ELSE RETURN ‘olcsó’
END IF;
END

A függvény a létrehozása után tetszőleges lekérdezésekben felhasználható:

SELECT Nev, Ar, Arszint(Ar) FROM Termek

(Az eredmény egy három oszlopos reláció, a harmadik oszlopban ’drága’, illetve ’olcsó’
szerepel az aktuális termék árától függően.)
 Trigger: A triggerek esemény-vezérelt tárolt eljárások, amelyek nem felhasználói utasításra
futnak le, hanem automatikusan, az adatbázisban tárolt adatokon történő módosítások
hatására. Implementációtól függően triggereket rendelhetünk a beszúrás, törlés és módosítás
eseményekhez, azok bekövetkeztekor a triggerek által reprezentált műveletek is
automatikusan végrehajtódnak. A következő MySQL trigger új megrendeléstételek beszúrása
után fut le, összesíti a kapcsolódó termékre eddig leadott összes megrendelést, és eltárolja azt
a Termek rekord Osszrendeles mezőjében:

CREATE TRIGGER ins_mt AFTER INSERT ON MegrendelesTetel FOR EACH ROW


UPDATE Termek SET Osszrendeles=(
SELECT SUM(Darab) FROM MegrendelesTetel
WHERE TermekKod=NEW.TermekKod)

(A NEW kulcsszó segítségével érhetjük el az újonnan beszúrt rekordot)

Jelen fejezetben csupán egy rövid bepillantást nyújtottunk az adatbázis-kezelő rendszerek procedurális
képességeibe. Terjedelmi okokból sajnos nem áll módunkban valamennyi rendszer összes képességét
ismertetni, a téma iránt érdeklődőknek javasoljuk az alkalmazott környezet dokumentációjának
áttanulmányozását.

120
6 Adattárolási módszerek

Az adatbázis-kezelő rendszerek a tárolt adatot tipikusan még napjainkban is legtöbbször egy


merevlemezes háttértáron perzisztálják, mivel egységnyi tárterületre eső költsége ennek a típusú
tárolónak viszonylag alacsony, miközben gyors és hosszú élettartamú. A fejezet első felében áttekintjük
egy tipikus adatbázis-kezelő rendszer memória hierarchiáját, a lassabb háttértárolóktól a processzorba
épített gyorsítótárakig, valamint különböző módszereket, amelyek segítségével a merevlemezen
történő adatelérést gyorsítani tudjuk. Ezt követően ismertetjük a rekordok lemezen történő
elhelyezésének módját, és azokat a megoldásokat, amelyek a rekordok módosítását, törlését célozzák.

6.1 Memória hierarchia


Egy számítógépben különböző fajta memóriák találhatók, amelyek között sok nagyságrendnyi méret-
és gyorsaságbeli különbség is lehet. Általánosan elmondható, hogy minél nagyobb maximális
kapacitással rendelkezik egy adott memória típus, annál olcsóbb, és egyúttal annál lassabb is. 6.1.
ábrán egy számítógép különböző memória típusainak hierarchiáját ábrázoltuk.

Cache

Rendszermemória

Virtuális Fájl
memória Lemez rends zer
Másodlagos tár
Harmadlagos
tár

6.1. ábra: Számítógépek memória hierarchiája

A leggyorsabb memória típus a cache, amelyet manapság már általában a CPU-ba ágyazva valósítanak
meg. Mérete tipikusan csupán néhány megabájt (3-12), de a tartalmát nagyon kicsi, néhány
nanoszekundumos (1-10) késleltetetéssel éri el a feldolgozó egység. Amikor a CPU-nak szüksége van
egy adatra a rendszermemóriából (RAM), akkor először a cache memóriát ellenőrzi, hogy oda korábban
betöltötték-e már esetleg az adatot. Amennyiben igen, akkor meg lehet spórolni a sokkal lassabb RAM-
hoz történő hozzáférési időt. Amennyiben az adat nem található meg a cache-ben, akkor azt először
oda kell másolni a rendszermemóriából. Általában külön cache-t tartanak fent az adatoknak és az
utasításoknak. Az adat cachet legtöbbször szintén több szinten valósítják meg: Level 1, Level 2, Level 3
(L1, L2, L3), amelyek közül az L1 cache található a legközelebb a feldolgozó egységhez, ez a leggyorsabb
és a legkisebb, az L3 pedig a legtávolabb helyezkedik el a feldolgozó egységtől, a leglassabb a három
közül, de egyben a legnagyobb is.

A rendszermemóriába töltjük be a háttértárolóról az alkalmazásokat futtatáshoz, illetve az adatot


is először a rendszermemóriába másoljuk, mielőtt dolgozni tudnánk vele. A rendszermemória mérete
napjainkban tipikusan néhány GB (tipikusan 4-16, de szerver konfigurációk esetén akár egy
nagyságrenddel nagyobb RAM méretek is előfordulnak), tehát körülbelül három nagyságrenddel
nagyobb, mint a cache mérete. Az elérési ideje viszont átlagosan egy nagyságrenddel lassabb, mint a

121
CPU-ba ágyazott cache-é (10-100ns). A rendszermemória a számítógép kikapcsolásakor elveszti
tartalmát, tehát nem alkalmas az adatok tartós megőrzésére.

Az adatok perzisztens tárolására alkalmas másodlagos tárt napjainkban általában merevlemezes


meghajtóval (Hard Disk Drive – HDD), illetve szilárdtest meghajtóval (Solid State Drive – SSD) valósítják
meg. Azonos kapacitás melletti közel egy nagyságrenddel magasabb ár miatt és a magas terhelés miatti
gyorsabb elhasználódás miatt, ahol nagy adatmennyiséget kell tárolni, még mindig a HDD tekinthető
elterjedtnek az SSD-vel szemben, de néhány éven belül ez az állapot megfordulhat. A másodlagos
tárolók tipikus mérete néhány száz GB, esetleg néhány TB, egy ilyen meghajtóból természetesen több
is helyet foglalhat ugyanazon számítógépben vagy tároló egységben. A HDD-k tipikus késleltetési ideje
néhány tíz milliszekundumos, míg az SSD-ké már a mikroszekundumos (10-100µs) tartományban
mozog, de mindenképp több nagyságrenddel lassabb, mint a RAM elérési ideje.

A másodlagos tárolón valósítható meg az ún. virtuális memória is: az operációs rendszer a futtatott
alkalmazásoknak virtuális címeken keresztül teszi elérhetővé a (fizikai) rendszermemóriát, és az egyes
memória eléréseknél leképezést végez a két (virtuális és fizikai) címzés között. Azáltal, hogy az
alkalmazások a RAM-ot nem közvetlenül címzik, lehetővé válik a virtuális címzésen keresztül a fizikailag
létezőnél nagyobb memória terület elérése. Az operációs rendszer rendszermemóriát tud szabaddá
tenni az aktuális művelethez azáltal, hogy az éppen nem használt adatokat a másodlagos tárra másolja,
és csak akkor tölti onnan vissza a RAM-ba, amikor azokra újra szükség lesz.

Bár a másodlagos tárolók akár több TB adat tárolására is alkalmasak, előfordulnak esetek, amikor még
ennél is sokkal több adat tárolására van szükség, például tudományos mérési eredmények tárolására
vagy sok milliós felhasználói bázisú alkalmazások biztonsági mentésére. Ilyen esetekben alkalmaznak
ún. harmadlagos tárakat, amelyeket legtöbbször mágnesszalagos meghajtókkal valósítanak meg:
napjainkban egy tipikus mágnesszalag több tíz (de akár 185!) TB adatot is képes tárolni a legkisebb
fajlagos költség, hosszú élettartam és minimális helyigény mellett. Mivel a mágnesszalagot csak
szekvenciálisan lehet olvasni (úgy viszont akár több 100MB/s sebességgel), ezért nem megfelelő az
adatok véletlenszerű, gyors elérésére, viszont kiválóan alkalmas biztonsági mentések készítésére, és
azok igény szerinti másodlagos tárba történő visszatöltésére. Komplex szalagos rendszerek lehetővé
teszik a mágnesszalagok automatizált kezelését is, amely során egy robotkar mozgatja a keresett
mágnesszalagot az író/olvasó egységhez. A harmadlagos tárak adatelérési ideje a másodperc-perc
tartományban mozog.

Az operációs rendszer az adatot mindig két szomszédos hierarchia szint között mozgatja. A másodlagos
és harmadlagos tárban az adat elérése már sok nagyságrenddel lassabb, mint a fentebbi hierarchia
szinteken, ezért ezeken a szinteken az adatkezelés már nagyobb, másodlagos tárak esetén 4-64kB-os
blokkokban történik. Mivel egy lépésben úgyis egy teljes blokk írásra/olvasásra kerül, ezért alapvető
fontosságú az összetartozó adatokat egymás közelében tartani, hiszen a blokk olvasás során így a
kívánttal együtt a kapcsolódó adatok is (amelyekre nagy valószínűséggel szintén szükség lesz)
betöltődnek a rendszer memóriába. Hasonló megállapítás tehető a harmadlagos tárakra is: az
összetartozó adatokat célszerű egy tároló egységen elhelyezni, ezáltal minimalizálva az azok
eléréséhez szükséges időt.

122
6.1.1 Merevlemezes háttértárak

Az alacsony költségek mellett elérhető viszonylag nagy kapacitás, elfogadható sebesség és


megbízhatóság miatt a merevlemezes háttértárak – bár folyamatosan teret veszítenek a szilárdtest
meghajtókkal szemben – évtizedek óta a legnépszerűbb eszközök az adatok perzisztens tárolására.
Mivel számos adatbázis-kezeléssel kapcsolatos algoritmust merevlemezes háttértáron történő
használatra optimalizáltan fejlesztettek ki, ezért elengedhetetlennek tartjuk, hogy a mágneslemezes
háttértárakkal kapcsolatos alapvető fogalmakat és megoldásokat megismerjük.

Sáv

Tányér

Szektor

6.2. ábra: Merevlemezes meghajtó felépítése

6.2. ábrán láthatjuk egy merevlemezes meghajtó egység sematikus és valós belső felépítését. Az
adatok tárolása kör alakú, közös tengely mentén forgó, mindkét oldalán mágneses réteggel bevont
tányérokból épül fel, amelyeken az adatok elérését szintén együtt mozgó író-olvasó fejekkel érhetjük
el. Az egyes tányérok felszíne koncentrikus sávokból (track) épül fel, az egymás alatt (azonos tányér
különböző oldalain, illetve különböző tányérokon) található sávok együttesen alkotnak egy cilindert
(henger, cylinder). Az egyes tányérokon található sávok sugár irányú képzeletbeli egyenesekkel
határoltan több szektorból állnak össze, amelyeket egy keskeny, nem mágneses terület választ el
egymástól. Napjainkban egy szektor tipikus mérete 4kB (régebben 512 bájt), egy vagy több (1-16)
szektor együttesen alkot egy blokkot, amely a lemez és a rendszermemória közti adatmozgatás logikai
egysége. A lemezeken tárolt adatok elérése az egyes felületekhez nagyon közel mozgó író-olvasó fejek
segítségével történik, amelyek egy közös tengelyhez kapcsolódnak, és mindig együttesen mozdulnak a
kívánt pozícióba. A fejek pozícionálását, az adatok átvitelét és pufferelését a lemez vezérlő
elektronika végzi. Ahhoz, hogy egy blokkhoz hozzáférjünk, a következő lépésekre van szükség:

1. A lemez vezérlő a fejet (együtt az összes fejet) a kívánt sáv fölé mozgatja. Ehhez természetesen
időre van szükség, az így kapott késleltetést nevezzük keresési időnek (seek time).
2. Ezután meg kell várni, hogy a lemez folyamatos forgása következtében az író-olvasó fej a keresett
blokk első szektora fölé forduljon. Az ehhez szükséges időt nevezzük forgási időnek (rotation
time).
3. Végül meg kell várni, hogy a keresett blokk valamennyi szektora áthaladjon a fej alatt, és az el tudja
végezni az írást / olvasást. A kapcsolódó késleltetést átviteli időnek (transfer time) nevezzük.

A fenti három késleltetés együttesen átlagosan 10ms-ot tesz ki.

6.1.2 Szilárdtest meghajtók

A szilárdtest meghajtók (Solid State Drive – SSD) meghajtók legfőbb jellemzője – ahogyan a nevük is
mutatja –, hogy nem tartalmaznak mozgó alkatrészt. Bár az SSD eszközök több évtizedes múlttal

123
rendelkeznek, megfizethetetlen áruk miatt nem tudtak olyan népszerűek lenni, mint lemezes társaik.
A gyártási költségek csökkenésével és a technológiai problémák leküzdésével azonban az utóbbi
években piacuk robbanásszerű növekedésnek indult. A napjainkban kapható legtöbb SSD meghajtó
NAND kapu alapú flash memóriára épül, amely elektromos úton írható és törölhető, valamint tartalmát
tápellátás nélkül is megőrzi.

Az SSD eszközök két fő részből épülnek fel: a vezérlő egységből és a flash memória chipekből. A vezérlő
egység feladata a számítógép többi komponensével történő kapcsolattartás – tipikusan Serial ATA
(SATA) interfészen keresztül –, ez végzi el az adatok pufferelését, hibakezelést, esetleg tömörítést,
titkosítást, és természetesen vezérli a flash memória írását, olvasását. A flash média általában NAND
kapukból épül fel, amelyek napjainkban már több síkban, egymás felett helyezkednek el, így lehetővé
téve a párhuzamos hozzáférést.

A szilárdtest meghajtóknál jóval kisebb késleltetési időkkel kell számolnunk, mint lemezes társaiknál.
Természetesen nem értelmezhető a keresési és a forgási idő, csupán egyetlen átviteli késleltetés
létezik, ami nagyságrendekkel kisebb (5-10 µs), mint a HDD-k átlagosan 10ms-os késleltetése.

6.2 Az adatelérés gyorsítása


Mivel a másodlagos tárak késleltetése és átviteli sebessége még mindig nagyságrendekkel marad le az
operatív memória azonos tulajdonságaitól, további algoritmikus és hardware-es trükköket
alkalmaznak a különbség csökkentésére. Általánosan elmondható, hogy az adat eléréséhez szükséges
idő sokkal nagyobb, mint a rendszermemóriában a rajta elvégzett műveletek időigénye, ezért a legtöbb
megoldás az I/O műveletek számának minimalizálását célozza.

Merevlemezek esetén az adatelérési idő például jelentősen csökkenthető azáltal, ha az összetartozó


adatokat (például egy reláció rekordjait) fizikailag is egymáshoz közel tároljuk el: egy sávban, ha ott
nem fér, akkor egy cilinderen vagy szomszédos cilindereken. Például ha a tábla összes rekordját fel kell
olvasnunk egy sávból vagy egy cilinderről, akkor csupán a kezdeti keresési és forgási késleltetést kell
figyelembe vennünk, ami ahhoz szükséges, hogy az első rekord helyét megkeressük, ezután már csak
az összes adathoz szükséges átviteli késleltetéssel kell számolnunk. A merevlemez vezérlők továbbá
gyakran élnek azzal a módszerrel is, hogy egy olvasás kérés kiszolgálásakor nem csak a kért blokkot,
hanem az utána következő néhány blokkot is betöltik a pufferükbe arra számítva, hogy nemsokára
rájuk is szükség lesz. Ez sok esetben – nagyméretű vagy összetartozó adatok olvasásakor – valóban így
is történik, ezáltal a keresési és forgási késleltetést a további blokkok esetében meg tudjuk spórolni,
hiszen azok már a memóriában lesznek. Ezt nevezik előre olvasásnak (prefetch).

Szintén az olvasó fejek mozgatásához szükséges idő csökkenthető azáltal, ha az egységhez érkező I/O
kéréseket a lemezvezérlő megfelelően sorrendezi: ahelyett, hogy a kéréseket a beérkezés
sorrendjében szolgálná ki, a fej egységgel oda-vissza pásztázásokat végez a lemez felületén a közepétől
a széle felé és vissza, és csak akkor vált irányt, ha az adott irányban már nincs több kérés a várakozási
sorban. Egy adott irányba mozogva pedig valamennyi, a sorban álló I/O kérést kiszolgálja, mivel ez a
fajta viselkedés sokban hasonlít a liftek mozgására, ezt az algoritmust lift vagy elevátor (az angol
elevator szó után) algoritmusnak szokták nevezni.

HDD és SSD meghajtók esetén egyaránt alkalmazható az a megoldás, hogy ugyanazt az adatot
egyszerre több meghajtón, tükrözve tároljuk el. Így azon túl, hogy hardware hiba esetén nem történik

124
adatvesztés, hiszen az adatot többszörösen is eltároltuk, jelentősen gyorsítható az adatok olvasási
sebessége, hiszen az olvasáskérést – mivel minden meghajtón ugyanaz a tartalom szerepel – bármelyik,
éppen szabad átviteli kapacitással rendelkező meghajtóhoz rendelhetjük. Lemezes meghajtók esetén
a tükrözés a keresési időt is jelentősen csökkenti, hiszen egy együttmozgó fejegység helyett több,
egymástól függetlenül mozgó fejegységhez jutunk. A megoldás hátránya természetesen a
megnövekedett tárterület igény, továbbá vegyük észre, hogy az írási sebesség ilyen módon nem
növelhető, hiszen ugyanazt az adatot párhuzamosan minden tárolóra ki kell írni.

Szintén késleltetés csökkenést, illetve átviteli sebesség növekedést érhetünk el azáltal, ha az adatokat
nem tükrözve, hanem csíkokra bontva (striping) tároljuk el több meghajtó egységen. Ez azt jelenti,
hogy az eltárolandó adatot egyforma részekre osztjuk, és az egyes darabokat különböző meghajtókon
helyezzük el. Például ha egy reláció rekordjait két meghajtón osztjuk el, akkor a két tárolóról
párhuzamosan tudjuk felolvasni az adatokat (közel megkétszerezve az olvasási sebességet).
Hasonlóképpen, az írási sebesség is közel kétszereződik, hiszen az egyes meghajtókra csak az eredeti
adat felét kell kiírni. A megoldás hátránya, hogy – mivel nem alkalmaz redundanciát – bármelyik
meghajtó meghibásodása esetén jelentős adatvesztéssel számolhatunk, hiszen minden egyes
meghajtó részt vesz a teljes adathalmaz egyes részeinek eltárolásában, a hiba nem korlátozódik
bizonyos relációkra, bizonyos állományokra.

6.3 Rekordok elhelyezkedése a lemezen


A továbbiakban azzal fogunk foglalkozni, hogy egy adatbázis rekordjai hogyan helyezhetők el úgy a
meghajtón, hogy hatékonyan írhatók és olvashatók legyenek. Feltételezzük, hogy egy blokkban csupán
egyetlen reláció rekordjait tároljuk – bár léteznek ettől eltérő megoldások is. Mivel számos rendszer a
rendszermemória hatékonyabb elérését teszi lehetővé, ha az adat a 4 vagy 8 többszörösének
megfelelő címen kezdődik, ezért gyakori, hogy már az adatbázis tárolását is így szervezik, és minden
egyes, a rekordban eltárolt mezőt szintén 4-es vagy 8-as bájthatárra igazítanak. Ez azt jelenti, hogy a 4
vagy 8 bájtnál esetleg kisebb mezők utáni terület kihasználatlanul marad.

A rekordok tipikusan rendelkeznek egy fejléccel, ami a rekordról szolgáltat különböző információkat:

 Hivatkozást tartalmaz a rekordot tartalmazó reláció sémájára, így egyértelmű, hogy az adott
rekordban tárolt adatok pontosan melyik relációhoz tartoznak, a rekord milyen mezőkkel
rendelkezik, és – állandó hosszúságú rekordok esetén – azok pontosan hányadik bájtnál
kezdődnek.
 A fejléc általában tartalmazza a rekord teljes hosszát is, aminek ismeretében egyszerűen át
tudunk ugrani egy rekordot a kapcsolódó séma elérése nélkül is.
 A fejlécben el szokták tárolni a rekord legfontosabb időbélyegeit is: az utolsó módosítás és
esetleg az olvasás időpontjait. Ezek ismerete különösen a tranzakciókezelés megvalósításánál
lesz fontos (8. fejezet).
 Az olyan rekordok esetén, amelyek hossza változhat annak függvényében, hogy pontosan
milyen értékeket tárolunk az egyes mezőikben, a fejléc mutatókat is tartalmaz az egyes mezők
kezdőpontjára.

125
6.3.1 Állandó hosszúságú rekordok

A legegyszerűbb dolgunk akkor van, ha a reláció rekordjainak hossza állandó, vagyis valamennyi mezője
állandó hosszúsággal rendelkezik. Tekintsük a szokásos Termek relációt azzal a módosítással, hogy a
szöveges mezőit állandó hosszúságú sztringekben tároljuk el:

CREATE TABLE Termek(


TermekKod INT,
Nev CHAR(100),
Ar INT,
Kategoria CHAR(50),
Szin CHAR(20),
Raktarkeszlet INT
)

A tábla egy rekordjának egy lehetséges (egyszerűsített) memóriatérképe a 6.3. ábrán látható,
feltételezve, hogy a rekord egyes mezői 4-es bájthatárra vannak igazítva.

séma
hossz
időbélyeg

TK Nev Ar Kategoria Szin RK

0 12 16 116 120 172 192 196


fejléc

6.3. ábra: Egy állandó méretű rekord memóriaképe a másodlagos tárban

A rekord fejlécében három tulajdonság található: egy mutató a sémára, a rekord hossz, valamint egy
időbélyeg (utolsó módosítás). Mindhárom mezőt 32biten ábrázoljuk, vagyis a teljes fejléc összesen 12
bájtot foglal el. Ezt követi a 4 bájtos INT típusú TermekKod (TK) mező, a 100 bájtos Nev, a szintén 4
bájtos Ar, majd az 50 bájtos Kategoria, amelyet 4-es bájthatárra igazítva kapjuk az 52 bájtos
helyfoglalást. A rekord végén a Szin (20 bájt) valamit a Raktarkeszlet (RK) mezők foglalnak helyet.

Az egyes rekordok a másodlagos tároló blokkjaiban kerülnek eltárolásra. A legegyszerűbb esetben, egy
blokkban csupán egy tábla n-eseit helyezzük el, amelyek mind azonos méretűek (6.4. ábra).

fejléc rekord 1 rekord 2 ... rekord n

6.4. ábra: Rekordok elhelyezkedése blokkon belül

A blokk elején szintén egy fejléc foglal helyet, amely tipikusan öt dolgot tárol:

1. hivatkozás a relációra, amelynek a rekordjai a blokkban szerepelnek,


2. a blokkban eltárolt rekordok pozícióját a blokkon belül,
3. időbélyegeket, amelyek a blokk eseményeinek időpontjait tárolják (pl. utolsó módosítás,
utolsó hozzáférés),

126
4. mutatókat más blokkokra (a blokkokból hálózatok építhetők fel, mint például index fák,
amelyek az adatbázisban történő hatékony keresést segítik, lsd. 7. fejezet),
5. a blokk szerepe a hálózatban, amelynek építőeleme.

6.3.2 Változó hosszúságú rekordok

Általános esetben az adatbázis rekordok sajnos nem állandó méretűek, hanem tartalmazhatnak olyan
mezőket is, amelyek helyfoglalása az aktuális tartalmuktól függ. Például a Termek táblában pazarlás
minden esetben 100 karaktert fenntartani a név számára, tekintve, hogy a legtöbb termék név
tipikusan 10-20 karakteres csupán. Szerencsésebb lenne a tárolt szöveg hosszával arányos
helyfoglalást alkalmazni, következésképpen a teljes rekord mérete is a tárolt szöveg hosszától függne.
Tételezzük fel a következő módosított Termek relációt, amelyben a szöveges mezők már változó
hosszúságúak (VARCHAR a CHAR helyett):

CREATE TABLE Termek(


TermekKod INT,
Nev VARCHAR(100),
Ar INT,
Kategoria VARCHAR(50),
Szin VARCHAR(20),
Raktarkeszlet INT
)

Ha az egyes mezők pontos hossza nem következik a sémából, akkor a mezők eltolása sem ugyanaz az
egyes rekordok memória képében, tehát ezt az információt rekordonként el kell tárolni. Hogy az ehhez
szükséges területet és többlet adminisztrációs terhet minimalizálják, a tipikus megoldás az, hogy

1. az állandó méretű mezőket a rekord elején helyezik el, hogy pozíciójuk megállapításához ne
legyen szükség mutatókra,
2. a változó hosszúságú mezők az állandó méretűek után következnek, és a második ilyen
mezőtől kezdve – hiszen az első pozíciója még ismert – minden egyes mezőre egy mutatót
helyeznek el a rekord fejlécében. Mivel a rekord hosszát már egyébként is eltároljuk a
fejlécben, ezért az utolsó mező hossza is könnyedén megállapítható.

A fenti elvet illusztráltuk a 6.5. ábrán: A Nev mező pozíciója még ismert (hiszen rögtön az állandó
méretű mezők után következik), a Kategoria és Szin mezőkre viszont már egy mutató mutat a
rekord fejlécéből.

127
séma Kategoria eltolás
hossz
időbélyeg Szin eltolás

TK Ar RK Nev Kategoria Szin

fejléc

6.5. ábra: Változó hosszúságú rekord memóriaképe a másodlagos tárban

A megoldás segítségével könnyedén megvalósítható a NULL értékek kezelése is: ebben az esetben a
NULL-t tároló mezőre mutató eltolás értéke 0.

6.3.3 Blokkméretnél nagyobb rekordok

Valós alkalmazásokban gyakran előfordul, hogy egy rekord vagy annak már akár csak egy mezője is
több helyet igényel, mint a másodlagos tár egy blokkja. Tipikus példa erre, ha egy adatbázis mezőben
képfájlt, videót, hanganyagot vagy akár csak hosszabb szöveges tartalmat tárolunk. Ilyenkor egy olyan
megoldásra van szükség, ami lehetővé teszi egy rekord több blokkon átnyúló tárolását. Hasonló
problémával szembesülünk, ha az átlagos rekord méret kevéssel több, mint a blokk méretének a fele:
ha a blokk másik felét feltöltetlenül hagynánk – mivel egy újabb rekord számára már nincs elég hely–,
a tábla által igényelt hely közel felén nem tárolnánk semmit. Ebben az esetben szintén arra van szükség,
hogy a rekordot több blokkon átnyúló módon tudjuk eltárolni.

Az adatbázis-kezelő rendszerek megoldása a problémára az, hogy a rekordokat szükség esetén


töredékekre bontják, és az egyes töredékeket a különböző blokkokba helyezik. Természetesen a
tördelést adminisztrálni kell, hogy az eredeti tartalom egyértelműen helyreállítható legyen. Ennek
megfelelően:

1. minden egyes rekord fejlécben egy biten jelezni kell, hogy az adott rekord töredék-e vagy sem,
2. további biteken el kell tárolni, hogy az aktuális darab első vagy az utolsó töredék-e,
3. a fejlécben mutatókat kell elhelyezni a megelőző és a következő töredékre (amennyiben
létezik).

A fenti elvet illusztráltuk a 6.6. ábrán: a 2-es rekord két töredékének fejlécében jelezni kell, hogy azok
töredékek, továbbá hogy a 2/1-es darab ezek közül az első, a 2/2-es pedig az utolsó. Ezen kívül mindkét
töredék a fejlécében mutatóval rendelkezik a másikra.

128
Blokk Blokk
rekord 1 rekord 2/1 rekord 2/2 rekord 3
fejléc fejléc

blokk1 blokk2

6.6. ábra: Blokkokon átnyúló rekordok tárolása

6.3.4 Blobok

A 6.3.3. fejezetben ismertetetthez hasonló megoldásra van szükség, amikor egy adatbázis mezőben
nagyon nagy méretű adatot, ún. BLOB-ot (Binary, Large OBject) szeretnénk tárolni. BLOB-nak
minősülnek például a nagyméretű képfájlok vagy a sok mega-, akár gigabájtos hang- és video
állományok. Mivel például egy video állomány folyamatos lejátszásához az egyes rekord töredékeket
megfelelő sebességgel kell visszaadni, ezért a BLOB-okhoz tartozó blokkokat célszerű folytonosan, egy
cilinderen vagy szomszédos cilindereken lefoglalni a gyorsabb elérés érdekében. További lehetőség a
BLOB tartalom csíkokra bontása (lsd. 6.2. fejezet), és több háttértárolón történő elhelyezése.

Például hang és video tartalmak olvasásakor további probléma, hogy az eltárolt adatra általában nem
egyszerre, teljes egészében van szükség, hanem a rekordnak mindig csak egy bizonyos töredékére, ami
éppen lejátszás alatt áll. Hasonlóképpen, természetes elvárás, hogy például egy video lejátszása
közben lehetőségünk legyen a tartalomban tetszőleges pontról folytatni a megtekintést. Ehhez arra
van szükség, hogy a kliensnek lehetősége legyen a rekord egyes blokkjait az adatbázis-kezelő
rendszertől igény szerint elkérni. A megoldás során gyakran használnak indexeket (lsd. 7.fejezet),
amelyek megkönnyítik a tartalomban történő navigálást (például egy másodperc felbontású indexet
készítenek a video tartalom blokkjaira).

Az SQL-ben használatos BLOB típusokat az 5.1.1.4. fejezetben ismertettük.

6.4 Adatok módosítása


Egy reláció másodlagos tárbeli reprezentációja általában nem tekinthető véglegesnek: új rekordokat
adhatunk a relációhoz, létezőket módosíthatunk vagy akár törölhetünk is. Ezek a műveletek újabb
problémákat vetnek fel, amelyekre az adatbázis-kezelő rendszereknek megoldást kell nyújtaniuk.

Vizsgálatunkat az új rekordok felvételével kezdjük. A legegyszerűbb esetben a rekordokat


rendezetlenül tároljuk, ekkor elég csupán találnunk egy tetszőleges blokkot, amelyben még elfér az új
n-es, és ott elhelyezni azt. A legtöbb esetben azonban nem ilyen egyszerű a helyzet, mivel egy reláció
rekordjait általában valamely attribútum szerint (tipikusan az elsődleges kulcs szerint, lsd. 7.1. fejezet)
rendezetten szokták eltárolni. Ekkor az első lépés, hogy megkeressük azt a blokkot, ahova ideális
esetben az új bejegyzésnek kerülnie kellene. Ha blokkon belül még van elegendő hely, akkor – mivel a
rekordokat továbbra is rendezetten kell tárolni – a többi rekordot szükség esetén odébb kell tolni a
blokkon belül. Végül frissítjük a rekord pozíció mutatókat a blokk fejlécében. Amennyiben viszont nincs
elegendő hely, két lehetőségünk van:

129
1. Megnézzük, hogy a szomszédos blokkokban esetleg van-e elegendő hely, és amennyiben igen,
az egyik rekordot (a megelőző blokkba az elsőt, a követő blokkba az utolsót) átmozgatjuk a
másik blokkba, így teremtve helyet az új rekordnak.
2. Ha egyik szomszédos blokkban sincs elegendő hely, akkor ún. túlcsordulás blokkokat adunk a
cél blokkhoz: a blokkok fejlécében fenntartunk helyet egy mutatónak, amely segítségével egy
túlcsordulás blokkra mutathatunk, amelyben a beszúrandó rekordot elhelyezhetjük. A
mutatók segítségével szükség esetén akár több blokk is egymás után fűzhető. A túlcsordulás
blokkokat az ütemezett adatbázis karbantartó folyamatok szüntetik meg és alakítják egyszerű
blokkokká.

Rekordok törlésekor valamivel egyszerűbb dolgunk van, mivel ilyenkor új blokk lefoglalására soha
nincs szükség. Törléskor a blokk fejlécben lévő rekord mutatót fel kell szabadítani. A rekord által
lefoglalt helyre blokkon belül más blokkokat is mozgathatunk, hogy az egybefüggő szabad helyet
maximalizáljuk. Problémába akkor ütközünk, ha a rekordra az adatbázis más részeiből esetleg
közvetlenül hivatkozhatnak objektumok mutatók segítségével. Ekkor a mutatók érvénytelen területre
mutatnának vagy esetleg egy másik rekordra, ha időközben a törölt helyére létező rekordot
mozgattunk. Ennek kiküszöbölésére a tényleges törlés helyett a rekord helyére egy ún. sírkövet
(tombstone) helyezünk, ami meggátolja, hogy az adatbázis újraszervezéséig az adott helyre más
rekordot helyezzünk, és a hivatkozó objektum számára egyértelművé teszi, hogy a mutató már
érvénytelen területre mutat. Ha a hivatkozások a blokk kezdőcímén és az abban szereplő rekord
eltoláson keresztül történnek, akkor a sírkövet e fejlécbe helyezzük (6.7. ábra: Sírkő a blokk fejlécben).

rekord 1 rekord 2 ... rekord n


T

6.7. ábra: Sírkő a blokk fejlécben

Amennyiben a hivatkozások közvetlenül a rekord kezdőcímére mutatnak, úgy a sírkövet a rekord


helyére tesszük (6.8. ábra: Sírkő a rekord helyén).

rekord 1 rekord 2 ... rekord n


T

6.8. ábra: Sírkő a rekord helyén

Állandó hosszúságú rekord módosításakor további adminisztrációra nincs szükség, hiszen pontosan
ugyanannyi helyet foglal el, mint a korábbi változat. Ha viszont az új változat mérete eltér a korábbitól,
akkor a beszúrás és a törlés során alkalmazott technikák alkalmazhatók:

 Ha az új rekord mérete a korábbinál kisebb, akkor a törlés során ismertetett módon a


rekordokat átmozgathatjuk, hogy így tegyük a blokkon belüli szabad helyet egybefüggővé.

130
 Ha viszont a rekord mérete megnőtt, akkor a beszúrás műveletnél látott módon a többi,
azonos blokkon belüli rekordot odébb mozgathatjuk, szükség esetén akár más blokkba is, vagy
túlcsordulás blokkokat hozhatunk létre.

131
7 Indexek

Bár a 6. fejezetben megismerkedtünk az adatbázis rekordok eltárolásának alapvető problémáival, ha


csupán az ott látott egyszerű adatstruktúrákra hagyatkoznánk, akkor a legtöbb lekérdezés túlságosan
lassan futna le. Tételezzük fel a következő lekérdezést:

SELECT * FROM Termek WHERE GyartoKod=312

Ha csak azt tudjuk, hogy a Termek tábla rekordjai mely blokkokban szerepelnek, akkor a kérdés
megválaszolásához végig kell olvasnunk a táblához tartozó összes blokkot, és minden Termek rekord
esetén meg kell vizsgálnunk a GyartoKod attribútum értékét. Természetesen a 6. fejezetben
megismert trükköket alkalmazhatjuk (például, hogy egy tábla blokkjait egy vagy egymáshoz közeli
cilinderekre helyezünk), de nagyságrendbeli javulást így nem érhetünk el. A probléma olyan speciális
attribútumok szerinti keresésnél is jelentkezik, mint például az elsődleges kulcs:

SELECT * FROM Termek WHERE TermekKod=523

Mivel tudjuk, hogy az elsődleges kulcs értéke egyedi, ezért a keresés az első találat után leállítható, de
átlagosan még így is a tábla blokkjainak felét be kell olvasni a rendszermemóriába.

A problémára a megoldást az index jelenti, amely nem más, mint egy hatékonyan kereshető,
kisméretű tartalomjegyzék a tábla egyes rekordjaira. A tartalomjegyzék a tábla tetszőleges oszlopa
szerint készülhet, de akár több indexet is készíthetünk különböző oszlopok szerint. Az indexben tárolt
értékek az index kulcs értékei (nem összetévesztendő az eddigi kulcs fogalmakkal). 7.1. ábrán egy
nagyon egyszerű index megvalósítást láthatunk: az index a Termek tábla GyartoKod oszlopa szerinti
keresést gyorsítja meg. Az index felépítésénél a termék tábla aktuális tartalma alapján megvizsgáljuk,
hogy milyen gyártókódok szerepelnek. Ezek után ezeket az értékeket rendezve (annak érdekében, hogy
hatékonyan tudjunk keresni benne) eltároljuk. Ezután az egyes értékekhez mutatókat tartunk karban,
minden érték referálja a Termek tábla összes olyan sorát, amelyben az adott érték található.
Természetesen, amikor a Termek tábla minden módosításakor gondoskodni kell az index frissítéséről.

GyartoKod index 1 2 3 4 5 6 7 8 9

GyartoKod 3 5 1 3 8 7 5 4 2 2 6 9 6 5 1 9
Termek

7.1. ábra: GyartoKod szerinti index a termékeken

Az index mérete sokkal, tipikusan több nagyságrenddel kisebb, mint a teljes tábla mérete, hiszen
bejegyzésenként csupán egy attribútum értéket és néhány mutatót kell eltárolni. Következésképpen,
ha az indexált attribútum szerint keresünk, akkor először az indexben végezzük el a keresést – amely
sokkal kevesebb blokkművelettel bejárható – majd egyszerűen követjük a releváns bejegyzések
mutatóit a tényleges rekordokhoz. A gyakorlatban az indexeket általában fa adatszerkezetekkel
reprezentálják, így lineáris keresés helyett a rekordok számának logaritmusával közelíthetjük a

132
szükséges I/O műveletek számát. Ideális esetben az index néhány megabájt helyfoglalás mellett elfér
a rendszermemóriában is, így I/O műveletek nélkül tudjuk megkeresni a releváns rekordokat, és a
másodlagos tárból már csak a valóban szükséges blokkokat kell beolvasni.

A fejezetben megismerkedünk az indexekhez köthető alapvető fogalmakkal, azok leggyakoribb


megvalósításával – a B-fákkal – és az indexek kezelésére szolgáló alapvető SQL utasításokkal.

7.1 Index típusok


Egy tipikus adatbázis általában két féle állományból épül fel: adat és index állományokból. Az adat
állomány tárolja a táblák adatait, míg az index állomány(ok) index kulcs érték - mutató
összerendeléseket tárol(nak), ahol egy kulcs értékhez akár több, az adat állományban tárolt rekordokat
hivatkozó pointert rendelünk. Az indexeket két szempont szerint szoktuk csoportosítani:

1. sűrű, illetve ritka index,


2. elsődleges, illetve másodlagos index.

Sűrű indexről akkor beszélünk, ha az adat fájl minden egyes rekordjára mutat bejegyzés az index
állományból. A 7.2. ábra bal oldalán láthatóak az index blokkok, amelyekben található bejegyzések a
jobb oldalon látható adatblokkokban tárolt rekordokra mutatnak. Egy blokkban tipikusan sokkal (akár
nagyságrendekkel) több index bejegyzés elfér, mint adatblokk, ezért jóval hatékonyabban kereshető,
mint maga az adat állomány. Az index állomány bejegyzései mindig rendezetten helyezkednek el, de
ez a sorrend nem feltétlen egyezik meg az adat rekordok sorrendjével.

1 3

2 7

3 1
4 4

5 5
6 6
7
2
8
8

7.2. ábra: Sűrű index

Amennyiben egy konkrét értékhez tartozó rekordot keresünk, akkor megkeressük azt az index blokkot,
amelyben ehhez az értékhez tartozó mutató(k) vannak, majd a mutató(k) segítségével kiválaszthatjuk
a betöltendő adat blokkokat. Az index bejegyzésekben történő keresést több tényező is gyorsítja:

 Mivel az index bejegyzések lényegesen kisebbek, mint az adat rekordok, sokszor a teljes index
struktúra betölthető a rendszermemóriába, és a keresés ott sokkal hatékonyabban
elvégezhető.
 Az index bejegyzéseket rendezetten tároljuk, ezért a lineáris keresésnél hatékonyabb
módszerek is alkalmazhatók.

Mivel a sűrű indexek minden egyes rekordra tartalmaznak bejegyzést, ezért megengedik az ismétlődést
is az index kulcs értékek között (7.4. ábra).

133
A ritka indexek bejegyzései általában adatblokkonként egy rekordra mutatnak (7.3. ábra), ezáltal
kevesebb helyet foglalnak, mint a sűrű indexek, viszont csak akkor alkalmazhatók, ha az adat rekordok
az indexált oszlop értékei szerinti sorrendben tárolódnak el. Ezzel szemben a sűrű indexek tetszőleges
oszlop esetén alkalmazhatók.

1
2

1 3
3 4
5
5
7
6

7
8

7.3. ábra: Ritka index

Egy adott K értékhez tartozó rekordot a következő lépésekkel találhatunk meg:

1. megkeressük a legnagyobb, K-nál kisebb kulcs értékhez tartozó index bejegyzést,


2. betöltjük a bejegyzéshez tartozó adat blokkot (ezen található a keresett rekord),
3. majd az adat blokkban megkeressük a K értékkel rendelkező rekordot.

Egy indexet akkor nevezünk elsődlegesnek, ha az index kulcs értékeinek sorrendje megegyezik a
kapcsolódó adat rekordok tárolási sorrendjével – mint ahogy ez a 7.3. ábrán látható esetben is fennáll.
A ritka indexek mindig csak elsődleges indexek lehetnek, ellenkező esetben az index állományban nem
szereplő kulcs értékhez nem találhatnánk meg a kapcsolódó rekordot tartalmazó blokkot.

A másodlagos indexek kulcs értékeinek sorrendje eltérhet a hivatkozott rekordok sorrendjétől.


Következésképpen a másodlagos indexek mindig sűrűk. 7.4. ábrán jól látható, hogy bár az index
bejegyzések rendezettek, ez a sorrend nem feltétlen egyezik meg az adat rekordok sorrendjével.

1 2

2 5

2 4
3 3

4 1
4 2
5
6
6
4

7.4. ábra: Másodlagos index

Mivel a másodlagos indexek nem követelik meg, hogy az index kulcsokkal azonos sorrenden
tárolódjanak el az adat rekordok is, ezért tetszőleges attribútum esetén alkalmazhatók. Amennyiben
az adat rekordokat egy ún. heap-állományban tároljuk el (mindenfajta rendezés nélkül), akkor az
elsődleges kulcsra is csak másodlagos indexet készíthetünk.

134
Az adatbázis rekordokat klaszterezetten is tárolhatjuk, ami azt jelenti, hogy az egyik tábla rekordjait
egy másik tábla hozzá tartozó rekordjaival egy csoportban tároljuk el. Például ha gyakran van együtt
szükség a Vevo és a hozzájuk tartozó Megrendeles rekordokra, akkor az egyes vevőkhöz tartozó
megrendelés rekordokat közvetlenül a kapcsolódó rekord mellett is eltárolhatjuk a lemezen (7.5. ábra).
Egy vevő után azok a megrendelések helyezkednek el, amelyeknek a VevoKod attribútum értéke
megegyezik a megelőző Vevo rekord VevoKod attribútumával.

Vevo1 Vevo2 Vevo3

Vevo1 Vevo2 Vevo3


megrendelései megrendelései megrendelései

7.5. ábra: Klaszterezett állomány

Ezzel az adattárolási módszerrel hatékonyan tudjuk megválaszolni például a következő típusú illesztett
lekérdezéseket:

SELECT … FROM Vevo INNER JOIN Megrendeles WHERE Vevo.VevoKod = Megrendeles.VevoKod


WHERE …

Mivel a Megrendeles rekordok nem folytonosan, és nem egy egyedi értékekkel rendelkező
attribútum szerint rendezetten helyezkednek el, ezért értelemszerűen minden indexe csak másodlagos
lehet.

7.2 B-fák
Bár az index állomány sokkal kisebb helyen elfér, mint az adat fájl, sokszor még ez sem tölthető be
teljes egészében a rendszer memóriába, ezért hatékony módszerekre van szükség az index
állományokban történő keresésre is.

Mivel az index kulcsok rendezettek, ezért ha a teljes index struktúra elfér a memóriában,
alkalmazhatunk bináris keresést, így a kereséshez szükséges összehasonlítások száma legrosszabb
esetben log 2 𝑛, ahol 𝑛 az index struktúra elemeinek a száma. Mivel a teljes index sok esetben nem fér
be a rendszer memóriába, ezért a kulcs értékeket felfűzhetjük például egy bináris keresőfába (7.6.
ábra), ahol a csomópontokat a harmadlagos tárban tároljuk el. A bináris keresőfa csomópontjai egy-
egy index kulcs értéket tárolnak, illetve mutatót a kulcs értékhez tartozó rekord(ok)ra. Továbbá minden
egyes csomópontra teljesül, hogy legfeljebb két gyermeke lehet (bal és jobb): a bal oldali gyermekével
induló részfában csak a csomópontban tárolt kulcs értéknél kisebb, a jobb oldali részfában pedig csak
nagyobb értékek szerepelhetnek.

135
4

2 6

1 3 5 7

1 2 3 4 5 6 7

7.6. ábra: Bináris keresőfa

Egy K értékhez tartozó rekord keresése a fa gyökerében indul: ha a gyökérben tárolt érték egyezik a K
értékkel, akkor követjük a csomópont mutatóját a rekordhoz, ha K kisebb, mint a gyökérben tárolt
érték, akkor a keresést a bal oldali részfában folytatjuk rekurzívan, egyébként pedig a jobban.

Nyilvánvaló, hogy minden egyes összehasonlítással felezzük a keresési teret, ezért ha az index
n bejegyzést tárol, akkor legrosszabb esetben log 2 𝑛 összehasonlítással megtaláljuk a keresett
csomópontot. Ez egyben log 2 𝑛 csomópont bejárását is jelenti, ami – mivel a csomópontok lemezen
való elhelyezkedéséről nem feltételeztünk semmit – akár ugyanennyi blokk betöltését is jelentheti.
Mivel az I/O műveletek sok nagyságrenddel lassabbak, mint a rendszermemóriában végzett
összehasonlítások, joggal vetődik fel a kérdés, hogy az adatstruktúra átalakításával csökkenthetjük-e
valahogy a betöltött blokkok számát?

7.2.1 B-fa

A B-fák úgy csökkentik az I/O műveletek számát, hogy a fa csomópontjaiban növelik a kulcsok és a
mutatók számát, ezáltal csökkentik a fa mélységét. Mivel a beolvasás alapegysége a blokk, ezért ideális
esetben, egy csomópontban annyi kulcs és mutató foglal helyet, hogy a blokkban rendelkezésre álló
helyet maximálisan kihasználják. Egy B-fa általában három rétegből épül fel: i) egy gyökércsomópont,
ii) köztes csomópontok, iii) levél csomópontok (7.7. ábra). Minden csomópontot egy önálló blokkban
tárolunk.

15

18 25 30
5 9

1 2 4 5 7 8 9 11 13

7.7. ábra: B-fa

Adatrekordokra csak a legalsó szinten elhelyezkedő levél csomópontok mutatnak: a 𝑝𝑖 mutató a 𝐾𝑖


index kulcs értékhez tartozó rekordra mutat. Az összes levél csomópont azonos mélységben található.

136
A gyökér és a köztes csomópontok azonos felépítésűek (7.8. ábra): 𝑛 kulcs érték, és 𝑛 + 1 mutató
számára tartanak fent helyet. Minden 𝑝𝑖 mutatóra teljesül, hogy az általa mutatott részfában a
legkisebb kulcs érték 𝐾𝑖−1, és az összes érték kisebb, mint 𝐾𝑖 .

K1 K2 K3
p1 p2 p3 p4

<K1 >=K2, <K3


>=K1, < K2 >=K3

7.8. ábra: B-fa gyökér és köztes csomópontja

A B-fákra továbbá teljesülnek a következő feltételek:

1. A gyökér csomópontban mindig legalább 2 mutató szerepel (feltéve, hogy létezik legalább két
adatrekord a táblában).
2. Minden index blokk legalább félig tele van (legalább ⌈(𝑘 + 1)/2⌉ mutató található benne), így
akadályozva meg, hogy a fa mélysége feleslegesen nőjön.
3. A levél csomópontok kulcs értékei rendezetten helyezkednek el.

Egy konkrét 𝑘 érték utáni keresés egy B-fában a következőképp történik:

1) A gyökér és köztes csomópontokban megkeressük azt a maximális 𝐾𝑖 -t, amely még kisebb,
mint a keresett 𝑘 kulcs.
a. Ha létezik ilyen 𝐾𝑖 , akkor a keresést a 𝑝𝑖+1 által mutatott részfában folytatjuk.
b. Ha nem létezik ilyen 𝐾𝑖 , akkor a keresést a 𝑝0 által mutatott részfában folytatjuk.
2) Ha eljutottunk egy levél csomópontig, akkor megkeressük a 𝑘-val egyező 𝐾𝑖 kulcsot.
a. Ha megtaláltuk, akkor követjük a 𝑝𝑖 mutatót az adat rekordig.
b. Egyébként nem létezik a keresett értékkel adat.

A fa mélysége log 𝑛+1 𝑚-mel arányos, ahol 𝑛 a csomóponton belüli kulcsok maximális száma (tehát 𝑛 +
1 a fa elágazás száma), 𝑚 pedig az összes kulcs érték száma.

7.2.2 B+ fa

A B-fákban hatékonyan tudunk egy konkrét értékhez tartozó rekordot keresni, viszont tartomány-
alapú keresésnél (pl. WHERE X > a AND X < b) a különböző levél csomópontok bejárásához vissza
kell lépni a köztes vagy akár a gyökér csomópontig is. A B+ fa ezt a hiányosságot úgy küszöböli ki, hogy
a levél csomópontokban egy további mutató segítségével a következő levél csomópontra mutat (7.9.
ábra). Következésképpen a B+ fában egységesen, minden csomópontban 𝑛 érték és 𝑛 + 1 mutató
számára van hely.

137
15

18 25 30
5 9

1 2 4 5 7 8 9 11 13 15 ... ...

7.9. ábra: B+ fa

Egy konkrét érték utáni keresés ugyanúgy történik, mint a B-fa esetén, viszont például a

WHERE X > a AND X < b

jellegű szelekciós feltételek esetén (ha az X oszlopra korábban készítettünk egy indexet) a
következőképp hajtható végre a keresés:

1) Megkeressük a fában az 𝑎-t, vagy – amennyiben 𝑎 nem szerepel az indexben – a legkisebb 𝑎-


nál nagyobb értéket tároló levél csomópontot.
2) Ettől a levél csomóponttól kezdve az utolsó mutató segítségével bejárjuk a szomszédos levél
csomópontokat (és a hivatkozott adat rekordokat) egészen addig, amíg 𝑏-t vagy egy annál
nagyobb értéket nem találunk.

Amennyiben az intervallum valamelyik oldalon nyitott (X>a vagy X<b hiányzik), akkor a bejárást az első
levél csomóponttól vagy az utolsó levélcsomópontig folytatjuk.

7.2.3 Ismétlődő értékek

Az eddigiekben feltételeztük, hogy az indexált attribútum értékek egyediek, vagyis hogy egy értékhez
csak egy rekord tartozik. A gyakorlatban ez természetesen nem feltétlenül lesz így, ezért a gyökér és
köztes csomópontok szerkezetét nem, de szemantikáját némileg módosítani kell: az eredeti definíció
szerint a 𝐾𝑖 kulcs a 𝑝𝑖+1 mutató által hivatkozott részfa legkisebb kulcsa volt. Ezt a feltételt annyiban
módosítjuk, hogy mostantól a 𝐾𝑖 kulcs a 𝑝𝑖+1 által mutatott ág legkisebb új kulcsa lesz:

 𝐾𝑖 nem szerepel a 𝑝𝑖+1 által mutatott részfától balra a teljes fában,


 ha a 𝑝𝑖+1 által mutatott részfában csupa olyan kulcs érték szerepel, amely már 𝑝𝑖 -ben is
szerepelt, akkor pedig 𝐾𝑖 legyen üres (NULL).

138
15
1)

5 - 7 3) 15 25 30
2)

1 2 4 4 5 6 6 6 6 7 7 15 ...

4) 5) 6) 7) 8)

7.10. ábra: B+ fa ismétlődő kulcs értékekkel

A 7.10. ábrán egy olyan B+ fát láthatunk, amelyben a leveleken ismétlődő kulcs értékek találhatók.
Például a 2) csomópont 𝐾1 értéke azért 5, mert a 𝑝2 mutatója által mutatott 5) csomópont legkisebb
új értéke 5, hiszen bár a legkisebb kulcsa a 4-es, de a 4-es érték már a 4) csomóponton is szerepelt.
Hasonló okokból a 2) csomópont 𝐾3 kulcs értéke 7, mivel a 𝑝4 által mutatott 7) csomópont legkisebb
új kulcsa 7 (hiszen a 6-os érték már megelőző leveleken is szerepelt). A 2) csomópont 𝐾2 kulcsa pedig
azért üres, mert a 𝑝3 mutatója által hivatkozott 6) csomóponton egyetlen új érték sem szerepel: az
összes kulcs értéke 6, de az már az 5) levélen is szerepelt.

Az ábrán szereplő fában, ha a 7-es értéket keressük, akkor a gyökérben (mivel 7 < 15) 2) felé indulunk,
amelynek a kulcsai között szerepel 7, tehát 2) 𝑝4 -es mutatóján a 7) levélhez jutunk. Itt a 𝑝2 mutatótól
kezdve bejárjuk az összes adatmutatót egészen a 8) csomópont első mutatójáig.

Ellenben, ha a 6) értékhez tartozó rekordokat keressük, akkor 1)-nél szintén 2) felé indulunk, majd –
mivel 5 < 6 < 7 – a keresést a 2) 𝑝2 mutatója felé követjük: 𝑝3 -at soha nem választjuk, mivel azon az
ágon nincs új kulcs érték, 𝑝4 -ben pedig a legkisebb új érték a 7-es. A 𝑝2 -t követve eljutunk a 5) levélhez,
ahonnan elindulva már betölthetők a keresett rekordok.

7.2.4 B-fák alkalmazása

A B-fákat valamennyi tanult index típus esetén alkalmazhatjuk:

1. Ha a B-fa index kulcsa az adat fájl elsődleges kulcsa, és az index sűrű: ekkor az adatfájl minden
egyes rekordjára hivatkozik az index fa levél szintjén egy-egy mutató. Mivel az indexált
attribútum az elsődleges kulcs, ezért az index kulcs értékek mind különbözőek.
2. Ha a B-fa index kulcsa az adat fájl elsődleges kulcsa, és az adat fájl az elsődleges kulcs szerint
rendezett, akkor az index lehet ritka: ekkor a levél szinten elhelyezkedő mutatók nem adat
rekordokra, hanem adat blokkokra hivatkoznak, a levél szinten elhelyezkedő kulcs értékek
adatblokkonként a legkisebb értéket tartalmazzák.
3. B-fát felépíthetjük a reláció tetszőleges attribútuma alapján is: amennyiben az adatfájl ezen
attribútum szerint rendezett, akkor az index lehet ritka, egyébként csak sűrű.

Bár az eddig ismertetett példákban az index kulcs egész szám volt, indexet természetesen nem csak
egész típusú oszlopokra készíthetünk, hanem bármely összehasonlítható típusú oszlopra (valós

139
számok, dátum értékek, sztringek stb), hiszen az index fa felépítéséhez az szükséges, hogy tetszőleges
két értéket sorba tudjunk rendezni.

Sztringek esetén az összehasonlítás a két sztring elejétől kezdődik, és karakterenként történik. Ennek
következtében indexek segítségével hatékonyan tudunk prefixum keresést is végrehajtani:

SELECT * FROM Termek WHERE Nev LIKE ’Asztal%’

A fenti lekérdezés végrehajtása során (amennyiben a Nev oszlopra készítettünk indexet) az index fában
az ’Asztal’ kulcs értéket tartalmazó levél blokkig kell elnavigálni – vagy ha ilyen nincs, akkor az első,
’Asztal’-nál nagyobb, de még ’Asztal’-lal kezdődő értékig, majd innentől kezdve kell bejárni a
szomszédos levél csomópontokat, amíg mással kezdődő kulcs értéket nem találunk.

Érthető okokból a szuffixum keresést (’%asztal’) nem gyorsítják az ilyen indexek, hiszen míg az
azonos prefixummal rendelkező értékek közvetlenül egymás mellett helyezkednek el a levél szinten,
az azonos végződésű értékek eleje tetszőleges lehet, ezért tetszőleges helyen helyezkedhetnek el a
levél szinten. Hasonlóképp nem alkalmazhatók az ilyen típusú indexek akkor sem, ha a keresett
kifejezés a kulcs érték közepén szerepel (’%asztal%’).

Nem alkalmazhatók indexek a lekérdezés kiértékelése során akkor sem, ha az indexált attribútumot
valamilyen módon transzformáljuk a szelekciós feltételben, például:

SELECT * FROM Termek WHERE Ar*2=1000

Ekkor a végrehajtás során az összes adat rekord betöltődik a memóriába, és a végrehajtó egyenként
kiértékeli rajtuk a szelekciós feltételt. Ilyen egyszerű feltételeknél még akár el is várhatnánk, hogy a
feltétel átrendezésével az Ar=500 feltételt értékelje ki a végrehajtó (amelynél már hatékonyan tudná
az indexet is használni), de a legtöbb esetben ez nem kivitelezhető, ezért ezzel meg sem próbálkozik.
Például a következő lekérdezésben a LEN(Nev)<10 feltételt lehetetlen úgy átalakítani, hogy a Nev
attribútum értékét önmagában hasonlítsuk össze egy vagy több konstans értékkel:

SELECT * FROM Termek WHERE LEN(Nev)<10

7.2.5 B-fák hatékonysága

Az eddigi illusztrációkban az egyszerűség kedvéért általában 4 volt az index csomópontok


elágazásszáma, a gyakorlatban ez tipikusan sokkal több szokott lenni, hiszen ideális esetben a
csomópont a teljes blokkot kitölti. Feltételezzünk egy átlagos 4kB-os (4096 bájt) blokkméretet,
indexáljunk egy INT típusú oszlopot, amely 4 bájtos értékeket tárol (tehát az index kulcs mérete 4
bájt), valamint 64bites mutatókat (8 bájt). Ha az index csomópont nem tartalmaz egyéb fejléc
információt, akkor (mivel minden csomópontban 𝑛 kulcs és 𝑛 + 1 mutató található) teljesül a
következő egyenlőtlenség:

[𝑘𝑢𝑙𝑐𝑠 𝑚é𝑟𝑒𝑡] ∗ 𝑛 + [𝑚𝑢𝑡𝑎𝑡ó 𝑚é𝑟𝑒𝑡] ∗ (𝑛 + 1) ≤ 𝑏𝑙𝑜𝑘𝑘𝑚é𝑟𝑒𝑡

Ez a fenti értékek esetén a következőt jelenti:

4 ∗ 𝑛 + 8 ∗ (𝑛 + 1) ≤ 4096

140
𝑛 ≤ 340, 6̇

Vagyis csomópontonként 340 kulcs értéket és 341 mutatót tudunk eltárolni: az index fa minden egyes
csomópontja legfeljebb 341 felé ágazik el. Egy ilyen konfiguráció esetén – mivel a mutatók legalább
fele mindig ki kell, hogy töltve legyen az egyes csomópontokban – átlagosan (341 + 341/2)/2 ≈ 255
az egyes csomópontok elágazásszáma. Ez azt jelenti, hogy a fa második (gyökér alatti) szintjén
átlagosan 255, a harmadik szinten 255*255=65536 stb. csomópont található. Ha a levél szint a
harmadik, akkor azok átlagosan 65536*255=16M adatrekordra tudnak mutatni, tehát a legtöbb index
fa valószínű nem lesz mélyebb, mint 3 szint.

Mivel a 4kB-os gyökér csomópontot, és a 255*4kB méretű második szintet (~1MB) általában a
memóriában tartja az adatbázis-kezelő rendszer, ezért egy 3 szintű index fa esetén egy konkrét érték
keresésekor elég egyetlen I/O műveletet elvégezni az érintett levél csomópont betöltéséhez, majd
további egy blokkművelettel beolvasni a keresett adat rekordot. Ha az index fa alacsonyabb – például
csak 2 szint – és az egész betölthető a rendszermemóriába, akkor a keresés elvégezhető csak a
memóriában is, és a keresett adatblokk egyetlen I/O művelettel betölthető.

7.2.5.1 Illesztés végrehajtása


Indexek segítségével az illesztések végrehajtásához szükséges I/O műveletek száma is
nagyságrendekkel csökkenthető, hiszen az illesztési feltétel kiértékelésekor is keresést kell
végrehajtani. Két táblát feltételezünk: t1 és t2, t1 1000, t2 pedig 100.000 rekordot tartalmaz. Legyen
col1 t1 egy oszlopa, col2 pedig t2 egy oszlopa. Az egyszerűség kedvéért mindkét tábla rekordjai
legyenek pontosan 1 blokk méretűek, valamint a col1 és col2 oszlopokban legyenek egyedei értékek.
Készítsünk indexeket a col1 és col2 oszlopokra. Ha átlagosan 255-ös elágazásszámot feltételezünk
az egyes index csomópontokban, akkor a col1-hez tartozó index mélysége ⌈log 256 1.000⌉ = 2, a col2-
höz tartozó index mélysége pedig ⌈log 256 100.000⌉ = 3. Ha az index fák egyik szintjét sem tartjuk a
memóriában, akkor t1 egy rekordjának megkereséséhez 2 index blokkot és az adatblokkot kell
betölteni, tehát összesen 3 I/O művelet szükséges. Hasonlóképp, t2 egy rekordjának megkereséséhez
és betöltéséhez 3+1=4 olvasás szükséges. Ennek ismeretében hajtsuk végre a következő lekérdezést:

SELECT * FROM t1 INNER JOIN t2 ON t1.col1=t2.col2

A lekérdezés végrehajtása során három lehetőségünk van:

1. Egyik indexet sem használjuk: Ekkor az egyik tábla összes rekordját (blokkját) fel kell olvasni,
és minden egyes rekordhoz végig kell olvasni a másik tábla összes blokkját is. Ez összesen
1.000*100.000=100.000.000 blokkolvasást jelentene.
2. A t1 tábla col1 oszlopára készített indexet használjuk: a t2 reláció minden egyes blokkját
beolvassuk (100.000 blokkművelet), és index alapú keresést végzünk az illeszkedő t1-beli
rekord után: ez rekordonként 3 I/O műveletet jelent, tehát összesen
100.000+3*100.000=400.000 blokkműveletet eredményez összesen.
3. Ha a t2 tábla col2 oszlopára készített indexet használjuk, és a t1 reláció rekordjait olvassuk
be szekvenciálisan, akkor ez összesen 1.000+4*1.000 = 5.000 olvasást jelent, mivel t2 egy
konkrét rekordjának index alapú megkeresése 4 blokkműveletet igényelt.

Ha az index fák első két szintjét a memóriában tartjuk, akkor t1-ben blokkművelet nélkül tudunk
keresni, és egy olvasással elérhetjük a keresett rekordot, t2-ben pedig 1 index blokk és 1 adatblokk

141
olvasással tudunk megkeresni egy rekordot. Ebben az esetben a fenti illesztés elvégzéséhez
100.000+1*100.000 = 400.000, illetve 1.000+2*1.000 = 3.000 I/O művelet szükséges, ha col1 ,illetve
col2 indexét használjuk. A megfelelő végrehajtási terv kiválasztása szerencsére nem az adatbázis
programozó feladata, hanem az adatbázis aktuális állapota alapján az adatbázis kezelő rendszer állítja
azt elő.

7.2.6 Műveletek B-fákon

Az eddigiekben bemutattuk, hogy milyen hatékonyan tudják csökkenteni a B-fák a lekérdezések


végrehajtásához szükséges I/O műveletek számát, most áttekintjük azokat az alapvető műveleteket,
amelyek segítségével a B-fákat építeni, illetve módosítani tudjuk. Ne feledjük a B-fák két alapvető
tulajdonságát: az egyes csomópontok mindig legalább félig tele vannak, illetve, hogy a levelek mindig
egy szinten vannak.

Egy új érték beszúrása a következő lépésekből áll:

1. Megkeressük azt a levél csomópontot, amelyben az új értéket el kellene helyezni. Ha a


csomópontban még van szabad hely, akkor létrehozzuk benne az új kulcs értéket és mutatót
az adat rekordra.
2. Ha a csomópontban már nincs szabad hely, akkor létrehozunk egy új levelet, és az eredeti levél
kulcsait, illetve mutatóit az új kulcs-mutató párossal együtt elfelezzük a két csomópont között.
3. Természetesen az új csomópontra referenciát kell elhelyezni egy szinttel feljebb is: az új
csomópont legkisebb kulcsát és a csomópontot hivatkozó mutatót kell elhelyezni az eredeti
csomópont szülőjében.
a. Ha a szülő csomópontban van elég hely egy új kulcs és mutató számára, akkor
egyszerűen beszúrjuk őket.
b. Ha a szülő csomópont már tele van, akkor a levél szinthez hasonlóan itt is létrehozunk
egy új csomópontot: az eredeti szülő csomópont tartalmát kiegészítjük az új kulcs-
mutató párossal, és a mutatók felét az új csomópontba mozgatjuk a megfelelő
kulcsokkal együtt. Az eredeti csomópontban maradó utolsó mutatóhoz tartozó kulcs
értéket pedig felmozgatjuk a szülő csomópontba a 3) lépés szerint rekurzívan.
Ha a gyökér csomópontot is ketté kellett vágni, akkor egy új gyökér csomópontot kell
felvenni az eddigi fölé, és el kell benne helyezni két mutatót a régi kettévágott gyökér
két csomópontjára, valamint a kapcsolódó kulcs értéket a megfelelő új blokk legkisebb
kulcsára.

142
17 30 50
3)

2) 5 9 14

1) 1 2 4 5 7 8 9 11 13 14 15 16

7.11. ábra: B+ fa új érték beszúrása előtt

Példaképp a 7.11. ábrán látható B+ fába próbáljuk meg beszúrni a 3-as értéket. A fa egyes
csomópontjaiban 3 kulcs és 4 mutató számára van hely. Egy, a gyökérből induló kereséssel kiderítjük,
hogy a 3-at az 1-es csomópontba kellene beszúrni, de mivel abban már nincs elég hely, ezért
kettévágjuk: a régi csomópontban marad az 1 és 2 érték, az új csomópontba pedig bekerül a 3 és a 4.
Az új csomópont a régi levél jobb oldalán foglal helyet, ezért a 2-es szülő csomópontba be kell szúrni a
3-as értéket és egy mutatót az új levélre. Mivel 2-ben nincs már szabad hely, ezért azt is kettévágjuk:
az eredetiben maradnak a 3-as és 5-ös értékek a kapcsolódó 3 mutatóval, az új csomópontba kerül a
14-es érték a kapcsolódó 2 mutatóval, és a 9-es kulcsot felmozgatjuk a gyökérbe (3-as csomópont).
Mivel a gyökérben így már a megengedettnél több kulcs és mutató lenne, ezért azt is kettévágjuk: az
eredetiben marad a 9-es és 17-es kulcs a kapcsolódó 3 mutatóval, az újba kerül az 50-es érték két
mutatóval, valamint egy új gyökér csomópontot hozunk létre a 30-as értékkel és két mutatóval a
kettévágott eredeti gyökér két darabjára. Az eredmény a 7.12. ábrán látható.

30

9 17 50

3 5 14

1 2 3 4 5 7 8 9 11 13 14 15 16

7.12. ábra: B+ fa új érték beszúrása után

Egy adott érték eltávolítása a fából szintén az érintett levél megkeresésével kezdődik. Ha az érték és a
mutató eltávolítása során a telítettség a minimum alá csökken, akkor összevonásokkal próbáljuk
fenntartani a telítettséget, miközben a szülő csomópontokban adminisztráljuk a gyerekekben
bekövetkező változásokat. A pontos lépések a következők:

143
1. Megkeressük azt a levél csomópontot, amelyben az eltávolítandó érték szerepel. Ha az érték
nem található a fában, akkor nincs egyéb teendő.
2. Eltávolítjuk a kulcsot és a mutatót a levél csomópontból.
a. Ha a legkisebb kulcsot távolítottuk el a levélből, akkor a szülő csomópont kapcsolódó
kulcsát frissíthetjük, de nem kötelező, a fa enélkül is helyes marad (hiszen az adott
ágon továbbra is a szülőben lévő kulcsnál csak nagyobb értékek szerepelnek).
b. Ha a levél csomópont még mindig legalább félig tele van, akkor nincs egyéb teendő.
3. Ha a levél csomópontban a lehetséges mutatóknak már kevesebb, mint fele van kitöltve, akkor
megpróbáljuk valamelyik szomszédos testvér (azonos szülő) csomópontból pótolni a szükséges
számú mutatót és kulcsot.
a. Ha az egyik szomszédos testvérben a minimumnál nagyobb számú mutató volt, akkor
átmozgatjuk a jobb testvér legkisebb (vagy a bal testvér legnagyobb) kulcs-mutató
párosát, és frissítjük a szülőben lévő kulcsot (pl. a jobb szomszéd módosítása esetén a
jobb szomszéd legkisebb kulcsát tükröző szülőbeli értéket frissítjük az új legkisebb
értékre).
b. Ha egyik szomszédos testvérben sincs a minimálisnál több mutató, akkor a módosított
csomópontot összevonhatjuk valamelyik testvérrel, majd a szülőben kitöröljük a
hivatkozást a megszűntetett csomópontra.
Ha a szülőben a minimum alá csökken a mutatók száma, akkor 3-tól rekurzívan
folytatjuk az algoritmust a szülő csomópontra.

Példaképp töröljük ki a 7.13. ábrán látható fából a 11-es értéket.

30

5) 9 17 50

3 5 14
4) 3)

2) 1)
1 2 3 4 5 7 8 9 11 14 15 16

7.13. ábra: B+ fa a 11-es érték törlése előtt

A keresett érték a 2-es levél csomópontban található, amelyben az érték és a kapcsolódó mutató
eltávolítása után a telítettség a minimum 2 alá csökken. A csomópont testvérét (1-es csomópont)
megvizsgálva látjuk, hogy abban a minimumnál több mutató van, így a legkisebb kulcsot (14) és a
kapcsolódó mutatót átmozgatjuk. Egyúttal a 3-as szülő csomópontban a 14-et 15-re cseréljük ki, hiszen
az 1-es csomópont legkisebb kulcsa most már a 15. További módosítást nem kell elvégezni, az
eredmény 7.14. ábrán látható.

144
30

5) 9 17 50

3 5 15
4) 3)

2) 1)
1 2 3 4 5 7 8 9 14 15 16

7.14. ábra: B+ fa a 11-es érték törlése után

Ezek után próbáljuk meg eltávolítani a 14-es értéket, amely szintén a 2-es csomópontban található.
Mivel a levélben eleve csak a minimális mutató szerepel, és egyetlen szomszédos testvérében (1-es
csomópont) szintén a minimális számú mutató van, ezért 1-et és 2-t összevonhatjuk. A csomópontok
összevonása után a 3-as szülő csomópontból az egyik mutató eltávolítható, ezáltal az is a minimálisnál
kisebb telítettséggel fog rendelkezni. Mivel a 4-es számú szomszédos testvérben még van hely, ezért
összevonhatjuk őket: az összevont 2-es csomópont legkisebb kulcsa 9, ezért a 4-es csomópontba is a
9-es értéket helyezzük és egy mutatót az összevont 2-es levélre. Miután az 5-ös csomópont egyik
gyermekét (3-as) megszűntettük, ezért a kapcsolódó kulcs (9-es) ott is eltávolítható. Az eredmény a
7.15. ábrán látható.

30

17 50

3 5 9

1 2 3 4 5 7 8 9 15 16

7.15. ábra: B+ fa a 14-es érték törlése után

Amennyiben egy, már a fában lévő értéket egy újra akarunk módosítani, akkor azt a korábbi érték
törlésével, majd az új érték beszúrásával tudjuk elvégezni.

Amint látható, mind a beszúrás, mind a törlés műveletre igaz, hogy az érintett levél csomóponton kívül
csak 1 szomszéd, illetve a szülő csomópontok, és azok szomszédjai lehetnek érintettek, vagyis
legrosszabb esetben is a fa mélységével arányos számú I/O műveletre van szükség.

145
7.2.7 B* fa

A B* fa a B+ fa továbbfejlesztésének tekinthető, amely a B, illetve B+ fáknál alkalmazott minimum 50%-


os csomópont kihasználtságot 2/3-ra emeli fel (a gyökér csomóponttól eltekintve), így tartva a fa
mélységét a minimálishoz közel. A nagyobb kihasználtság igény miatt, ha egy csomópont megtelik,
akkor nem vághatjuk egyszerűen két részre – mint a B+ fa csomópontjait –, hiszen az új
csomópontokban 2/3 alá csökkenne a kihasznált mutatók aránya. A B* fák esetén, ha egy csomópontba
már nem helyezhető több mutató, akkor

1) megpróbálunk mutatókat áthelyezni a szomszédos csomópontokba, így szabadítva fel helyet


az új kulcs és mutató számára,
2) amennyiben már a szomszédos csomópontokban sincs hely, akkor két szomszédos csomópont
együttesét vágjuk három felé, így a keletkező új csomópontokban egyaránt 2/3 lesz a
kihasználtság, illetve lesz hely az új mutató és kulcs számára is.

7.3 Indexek használata SQL-ben.


Az indexkezelő műveletek nem részei az SQL szabványnak, de a legtöbb rendszeren (MySql, Oracle,
MSSQL) a CREATE INDEX utasítással hozhatunk létre új indexet:

CREATE INDEX <index név> ON <Tábla név>(<oszlop1>[, <oszlop2>, …])

Minden index rendelkezik egy névvel, és pontosan egy tábla egy vagy több attribútumát foglalja
magában. Például a következő utasítás a GyartoKod attribútumra épülő indexet építi fel, amely így az
ezen attribútum szerinti keresést gyorsítja:

CREATE INDEX IDX_Termek_GyartoKod ON Termek(GyartoKod)

Az utasítás kiadása után az adatbázis-kezelő rendszer bejárja a Termek relációt, és a felhasznált


GyartoKod értékek alapján felépíti az index fát, és perzisztálja azt a lemezen is. Amennyiben egy index
egynél több attribútumot foglal magában, úgy az index fa az egyes mezők egymás után fűzésével
előálló érték szerint épül fel:

CREATE INDEX IDX_Termek_Kategoria_Szin ON Termek(Kategoria, Szin)

A fenti IDX_Termek_Kategoria_Szin indexben a kulcs értékeket a Kategoria és Szin értékek


konkatenáltja szolgáltatja, így hatékonnyá teszi olyan lekérdezések megválaszolását, amelyekben a
kategóriára és a színre egyszerre szűrünk. Mivel az index fában a rendezés az összefűzött értékek
szerint történik, amelyeknek az első fele mindig a kategória érték, ezért a fában elfoglalt pozíciót
elsősorban az első attribútum (Kategoria) fogja meghatározni. Így ha csak a kategória szerint
keresünk, akkor is tudjuk használni az indexet, hiszen az egy kategóriához tartozó összes rekord az
index fa egy részfájába fog kerülni: abba, amelyben azok a párosok vannak, amelynek az első fele a
keresett kategória.

Amennyiben egy indexre már nincs többé szükségünk, akkor a DROP INDEX utasítással tudjuk azt
törölni. MySQL és MSSQL rendszeren a tartalmazó táblát is definiálni kell az ON kulcsszó után:

DROP INDEX <index név> ON <tábla név>

146
Oracle esetén elég csupán az index nevét megadni:

DROP INDEX <index név>

7.4 Indexek megválasztása


Bár az indexek használatával számottevő gyorsulást érhetünk el egyes lekérdezések kiértékelésekor, a
megfelelő indexek megválasztása körültekintést igényel, hiszen

 természetesen az indexeknek is van hely igényük,


 az index fa bejárása időbe kerül,
 továbbá az adatok módosításával, törlésével, új adatok felvételével az összes kapcsolódó
indexet is aktualizálni kell, ami többlet feladatot jelent az adatbázis-kezelő rendszernek.

Tehát nem célszerű automatikusan minden egyes oszlopra indexet helyezni, hanem csak azokra,
amelyek szerint valóban keresni is akarunk. Amire viszont minden esetben érdemes, az az elsődleges
kulcs. A legtöbb adatbázis-kezelő rendszer ezt alapértelmezetten meg is teszi, hiszen sok lekérdezés
hivatkozik az elsődleges kulcsra a szelekciós feltételében vagy több táblát magában foglaló lekérdezés
esetén az illesztési feltételben. Mivel az elsődleges kulcs szerinti lekérdezések tipikusan egyenlőséget
vizsgálnak, ezért azok mindig legfeljebb egy eredmény rekordot eredményeznek, hiszen a kulcs egyedi.
Az ilyen lekérdezéseknél elég csupán az index fa 1 vagy 2, a rendszermemóriában nem szereplő
blokkját betölteni a lemezről, majd további egy blokkolvasással már elérhető a keresett rekord.

Az adatbázis-kezelő rendszerek szintén indexet használnak az egyediség biztosítására (UNIQUE


kényszer), mivel így egy új érték felvételekor nem kell a teljes relációt beolvasni, hogy ellenőrizni tudjuk
az érték egyediségét, hanem elég csupán az index fában elvégezni a keresést, és találat esetén hibát
jelezni.

7.4.1 Indexek szelektivitása

Egy index annál hatékonyabban alkalmazható, minél egyedibbek a kapcsolódó oszlop(ok) értékei,
minél kevesebb ismétlődés található köztük. Ezt a fajta egyediséget nevezzük az index
szelektivitásának. A szelektivitás az indexált oszlop(ok)ban található különböző értékek és az összes
rekord arányát jelenti. Ideális esetben ez 1, vagyis az adott oszlopban minden egyes rekordhoz
különböző érték található. Tételezzünk fel egy vásárló nyilvántartást, amelyben 10.000 vevő adatait
tároljuk, a 10.000 vevő nevei közül összesen 9.000 a különböző. Ekkor, ha az indexet a neveket tároló
oszlopra helyezzük, a szelektivitás 9.000/10.000=0.9. Ha név alapján keresünk egy vásárlót, akkor
átlagosan 10.000/9.000=1.1 találatunk lesz egy konkrét névhez, tehát átlagosan 1-2 adat blokkot kell
beolvasnunk, illetve a memóriában nem bent lévő 1-2 index blokkot. Ezzel szemben, ha például a
vásárlók nemére építenénk fel egy indexet, akkor abban értelemszerűen csak két lehetséges érték
lenne: férfi és nő (pl. 0 és 1), amelyek közül az egyik átlagosan a rekordok egyik felére, a másik pedig a
másik felére mutatna (7.16. ábrán).

147
Nem index 0 1

Vevo

7.16. ábra: Kis szelektivitású index

Az index szelektivitása ekkor 2/10.000=0.0002. Egy konkrét nem szerinti kereséshez átlagosan
10.000/2=5.000 találatunk lenne. Mivel a rekordokat valószínűleg nem a nemek szerint rendezetten
tároljuk, ezért jó eséllyel a tábla összes blokkját fel kellene olvasni például a női vásárlók
megjelenítéséhez, hiszen nagy valószínűséggel minden blokkra jutna egy női vásárló rekord. Ekkor az
index blokkok betöltése és a köztük történő navigáció, illetve az index fa leveleiből történő véletlen
adatblokk elérés miatt a lekérdezés jóval lassabb lenne, mintha egyszerűen szekvenciálisan
felolvasnánk a tábla összes blokkját a lemezről. Ezeket az eseteket a lekérdezés végrehajtó logika
szerencsére általában ki is szűri nekünk, tehát egy indexet csak akkor fog a lekérdezés kiértékelése
során alkalmazni, ha úgy ítéli meg, hogy azzal csökkenteni tudja az I/O műveletek számát.

Sajnos nem létezik egy általános szelektivitás határérték, amely megmondaná nekünk, hogy egy adott
oszlopra érdemes-e indexet helyezni vagy sem, a végrehajtó alkalmazná-e azt, vagy pedig nem. A
konkrét határérték sok tényezőtől függ (például a blokkmérettől, a rekordok számától, a rekordok
méretétől, illetve a futtatott lekérdezéstől is), de az esetek többségében valahol 0.8 és 1 között szokott
lenni. Továbbá hiába nagy egy index szelektivitása, ha a táblán futtatott lekérdezések az index előnyeit
nem tudják kihasználni, tehát a futtatott lekérdezések jellegét is figyelembe kell venni (lsd. 7.4.2.
fejezet).

7.4.2 Lekérdezések szelektivitása

Ahogyan az indexeknél, úgy a lekérdezések szűrőfeltételénél is beszélhetünk szelektivitásról: ezt a


feltételt teljesítő rekordok és az összes rekord hányadosaként számíthatjuk ki. Minél kevesebb rekord
teljesíti a feltételt, annál nagyobb lesz a szelektivitás, annál kisebb lesz a hányados értéke. Tehát ebben
az esetben a nagy szelektivitás kis értéket takar. Ha a szűrő feltétel az összes rekordra teljesül, akkor a
szelektivitás 1.0 (ez a maximuma). Például egy termék nyilvántartásban, ahol minden termék neve ki
van töltve, a Nev IS NOT NULL feltétel szelektivitása 1.0, hiszen az összes rekordra teljesül.

A feltételek szelektivitása szintén befolyásolja, hogy a végrehajtó motor alkalmaz-e egy indexet, vagy
sem. Tegyük fel, hogy a termék nyilvántartásban indexet készítettünk a Nev oszlopra, majd értékeljük
ki a következő lekérdezést:

SELECT * FROM Termek WHERE Nev<>’’

Nyilvánvaló, hogy a Nev oszlopban bármilyen különbözőek is az értékek (bármilyen nagy is a rajta lévő
index szelektivitása), mindenképp a teljes relációt fel kell olvasni a lemezről. Míg a

SELECT * FROM Termek WHERE Nev=’’

148
lekérdezés csupán az index használatával megválaszolható, hiszen már az indexben történő keresés
során kiderül, hogy egyetlen rekord sem felel meg a feltételnek.

Természetesen a fenti lekérdezések szélsőséges esetek, de jól érzékeltetik, hogy a szelekciós feltétel
döntően befolyásolhatja, hogy a kiértékelés során érdemes-e használni egy adott indexet vagy sem.

Említettük továbbá, hogy az indexek karbantartása erőforrást igényel, amely mindhárom módosítás
műveletnél jelentkezik. De azt is vegyük figyelembe, hogy az indexek használata általában ezen
műveletek végrehajtását is gyorsítani tudja:

 Az UPDATE és DELETE utasításokat tipikusan egy szelekciós feltétellel együtt szoktuk


használni, és indexek alkalmazásával akár nagyságrendekkel is gyorsabban tudjuk az érintett
rekordokat megkeresni, mint nélkülük. Például ha a Nev oszlopon nem lenne index, akkor a
következő műveletnél rossz esetben a teljes táblát fel kellene olvasni, míg index használata
esetén pontosan egy adatblokkot kell olvasni (majd írni), illetve az index fa néhány érintett
blokkját kell beolvasni majd újra kiírni, összességében nagyságrendekkel kevesebb I/O
műveletet igényelve:

UPDATE Termek SET Nev=’Asztal’ WHERE Nev=’Asztal’

 A beszúró és módosító műveletek végrehajtása során az adatbázis-kezelő rendszer


folyamatosan biztosítja a referenciális integritást, tehát például egy új rekord beszúrása során
vagy egy létező módosítása során ellenőrizni kell, hogy az új külső kulcs értékekhez tartozik-e
a hivatkozott táblában rekord. A következő beszúrás művelet végrehajtása során például
ellenőrizni kell, hogy a Gyarto relációban valóban létezik-e 9-es kulccsal rekord:

INSERT INTO Termek(Nev, Kategoria, GyartoKod) VALUES (’Asztal’, ’Konyha’, 9)

Index használata nélkül ehhez a Gyarto táblát szekvenciálisan fel kellene olvasni a lemezről,
míg index használatával elég csupán az index fában navigálni. Törlés műveletnél pedig azt kell
ellenőrizni, hogy van-e akár egyetlen rekord is, amely külső kulccsal hivatkozik a törlendő
rekordra:

DELETE FROM Gyarto WHERE GyartoKod=9

Amennyiben a Termek reláció GyartoKod oszlopán nincs index, úgy a teljes Termek táblát
végig kell olvasni, hogy az adatbázis-kezelő ellenőrizni tudja, hogy egyetlen termék rekord sem
hivatkozza a törlendő gyártót. Index használata mellett a Termek tábla egyetlen
adatblokkjának beolvasása nélkül, csak az index alapján ellenőrizhető ez a feltétel.

Tehát az indexek megválasztásakor érdemes figyelembe venni az adott táblán futtatott lekérdezések
és egyéb műveletek jellegét is. Az adatbázison a kliens alkalmazásból futtatott lekérdezéseket mindig
célszerű az adatbázis fejlesztő környezetével verifikálni: megtekinteni a lekérdezések végrehajtási
tervét, és ellenőrizni, hogy valóban azokat az indexeket használja-e fel, amelyekre számítottunk. Az
egyes adatbázis-kezelő rendszerek gyakran támogatást is nyújtanak az indexek helyes
megválasztásához statisztikai adatok alapján, illetve az adatbázis napló elemzésével javaslatot tudnak
adni, hogy milyen indexekkel tudnánk gyorsítani a gyakran használt és / vagy erőforrás-igényes
lekérdezések végrehajtását.

149
8 Tranzakciókezelés

Az eddigi példákban azt feltételeztük, hogy az adatbázis-kezelő rendszert egy felhasználó használja, az
egymás után kiadott utasítások az adatbázist olyan állapotban találják, amilyenben az előző utasítás
hagyja. Továbbá feltételeztük, hogy a kiadott módosító utasítások minden esetben végrehajtódnak.

A való életben sajnos nem ennyire idealizált környezetben működnek a rendszerek, az általános
működéshez hozzátartozik, hogy egyszerre több felhasználó vagy több felhasználói folyamat
párhuzamosan írja és olvassa akár ugyanazokat az adatokat is. Például banki vagy tőzsdei
rendszerekben másodpercenként akár több száz vagy ezer művelet is végrehajtódhat, nem zárható ki,
hogy egyes műveletek időben átlapolódnak, és ez az átlapolódás érdekes dolgokat eredményezhet.
Képzeljük el, hogy egy bankszámlára egy időben két helyről érkezik terhelés: egy bankkártyás fizetés
50.000 Ft értékben és egy csoportos beszedés 10.000 Ft értékben. A kiindulási számlaegyenleg 55.000
Ft. Ha a két műveletet közel egy időben hajtjuk végre, akkor előfordulhat olyan ütemezés, amely során
párhuzamosan ellenőrizzük a két művelet végrehajthatóságát (az egyenleg nagyobb, mint a terhelés),
majd mivel mindkettő előfeltétele teljesül, ezért mindkettőt végrehajtjuk, így a záró egyenleg -5.000
Ft lesz még akkor is, ha az adott bankszámla típuson nem engedélyezett a hitelezés. Még nagyobb
problémát jelent, ha a két terhelést az azonos kiindulási értéken hajtjuk végre, így a végeredmény vagy
55.000-50.000=5.000 vagy 55.000-10.000=45.000 Ft lesz (attól függően, hogy végül melyik érték íródik
ki később a lemezre). Bármelyik eredmény jut is érvényre, mindenképp hibás lesz.

Nem kizárható továbbá annak az esélye sem, hogy egy adatmódosítás végrehajtása valamilyen
hardveres vagy szoftveres hiba folytán megszakad. Például két számla közti banki átutalás közben
áramkimaradás történik, vagy hardver hiba miatt leáll az adatbázis-kezelő szerver. Az átutalás közben
az egyik számla egyenlegét csökkenteni, a másikét pedig ugyanennyivel növelni kell. Ha a rendszerhiba
szerencsétlen módon épp a két művelet végrehajtása közben következik be, akkor előfordulhat, hogy
az egyik számláról levonjuk az összeget, de a másikon már nem írjuk jóvá, vagy fordítva.

Szerencsére a mai modern adatbázis-kezelő rendszerek kész megoldással szolgálnak a fenti problémák
elkerülésére: ez pedig a tranzakciókezelés. A fejezetben megismerkedünk az adatbázis tranzakció
fogalmával, tulajdonságaival, valamint azzal, hogy az adatbázis-kezelő rendszerek milyen eszközök
segítségével képesek ezeket a tulajdonságokat biztosítani.

8.1 Tranzakciók alaptulajdonságai


Egy adatbázis tranzakció alatt összetartozó műveletek sorozatát értjük, amelyeket együtt, atomi
módon kell végrehajtani. Az tranzakcióktól a következő négy tulajdonság teljesítését várjuk el,
amelyeket (az angol kifejezések kezdőbetűi alapján) ACID tulajdonságoknak is szoktak nevezni:

 Atomitás (Atomicity)
 Konzisztencia (Consistency)
 Izoláció (Isolation)
 Tartósság (Durability)

Az atomitás azt fejezi ki, hogy a tranzakciók atomi műveletek, amelyek vagy teljes egészében
végrehajtódnak, vagy egyáltalán nem. Tehát nem fordulhat elő, hogy az egy tranzakcióba tartozó
műveletek egy része még lefut, de valamilyen hiba folytán a másik része már nem. Erre szolgált

150
példaként az átutalás végrehajtása két számla között: nem történhet meg, hogy az egyik egyenleget
csökkentettük, de a másikat már nem növeltük.

A tranzakciók mindig konzisztens állapotból konzisztens állapotba visznek át: ha hiba történik a
tranzakció végrehajtása közben, akkor az adatbázis-kezelő tranzakciókezelő alrendszerének
gondoskodnia kell a már végrehajtott utasítások hatásainak visszavonásáról, és az utolsó konzisztens
állapot visszaállításáról. Az adatbázis konzisztens állapotához hozzá tartozik az adatbázisban
lemodellezett különböző kényszerek, referenciális integritás kényszerek, valamint az
alkalmazásfejlesztő által elvárt, de az adatbázisban expliciten nem modellezett kényszerek teljesülése:
például a termékek darabszáma soha nem negatív (még ha az oszlop típusa ezt meg is engedi), vagy
egy megrendeléshez mindig kapcsolódik legalább egy megrendeléstétel.

A tranzakciók izolációja azt fejezi ki, hogy a párhuzamosan futó tranzakcióknak nem lehetnek
egymásra nem kívánatos mellékhatásai, a végeredménynek azonosnak kell lennie azzal, mintha a
párhuzamosan futó tranzakciókat egymás után hajtanánk végre. Például az egyik tranzakció a vele
párhuzamosan futó másik tranzakció közben előállított ideiglenes állapotból kiindulva nem viheti az
adatbázist inkonzisztens állapotba, nem történhet adatvesztés stb. Ezt az adatbázis-kezelők általában
zárakkal vagy időbélyegekkel valósítják meg. Az egyes rendszerek azonban általában biztosítanak
számunkra kevésbé szigorú – és kevésbé erőforrás igényes – megoldásokat is, így az adatbázis
programozó maga határozhatja meg, hogy biztonságos-e, ha más tranzakciók láthatják a tranzakció
futása közben előálló ideiglenes állapotokat, vagy sem (8.2. fejezet).

A tranzakciók hatása a végrehajtás után tartósan meg is marad: miután az adatbázis-kezelő rendszer
egy tranzakcióra azt mondta, hogy végrehajtódott, akkor az általa végrehajtott változtatások már a
lemezre is kiírásra kerültek. Ha ezután történik valamilyen rendszerhiba, akkor az adat már helyes
állapotban elérhető marad. Itt elsősorban olyan soft-crash jellegű hibákra gondolunk, mint például egy
váratlan áramszünet, rendszer újraindulás stb., de semmiképp sem a háttértárolók fizikai
károsodására. Hard-crash esetén – amikor a tároló egységek is károsodnak, vagy a rajtuk tárolt adatok
visszaállíthatatlanul sérülnek – általában manuálisan kezdeményezett rendszer visszaállításra van
szükség biztonsági mentések segítségével.

8.1.1 Tranzakciós határok

Amikor SQL utasításokat adunk ki, akkor tipikusan minden egyes utasítás egy-egy külön tranzakcióban
fut le. Ha több utasítást szeretnénk egy tranzakcióban végrehajtani, akkor ezt expliciten jeleznünk kell:
meg kell jelölnünk a tranzakció elejét és végét, vagyis a tranzakciós határokat. A tranzakciós határok
definiálására a következő három SQL utasítás szolgál:

 BEGIN TRANSACTION: a tranzakció kezdetét jelöli meg. Ettől a ponttól kezdve a kiadott
utasítások egy tranzakció részét képzik.
 COMMIT: a tranzakció sikeres lezárását jelezzük vele: a COMMIT jelzés után az utolsó BEGIN
TRANSACTION óta elvégzett műveletek hatásai tartósan az adatbázis részévé válnak, az újabb
tranzakciók már ebből az állapotból kiindulva végzik műveleteiket.
 ROLLBACK: az utasítással megszakíthatjuk egy tranzakció futását, a tranzakcióban eddig
elvégzett módosítások visszavonásra kerülnek, és nem válnak az adatbázis részévé. Rollbackre
általában akkor szoktuk utasítani az adatbázis-kezelőt, ha a tranzakció végrehajtása során

151
valamilyen, a művelet folytatásához szükséges feltétel nem teljesül. Rollbacket nem csak mi
kérhetünk, hanem az atomitás biztosítására automatikusan is megtörténhet, ha például a
tranzakció egy rendszerhiba miatt megszakad. Ekkor a következő rendszerinduláskor a
tranzakciós napló (8.3. fejezet) alapján a megszakadt tranzakció már elvégzett műveleteinek
hatása visszavonásra kerül.

A következő példában egy új megrendelést és egy kapcsolódó megrendeléstételt hozunk létre egy
tranzakcióban. Ha a megrendeléstétel létrehozása alatt például egy rendszerhiba miatt megszakadna
a tranzakció, akkor a megrendelés létrehozása is visszavonódik:

BEGIN TRANSACTION
INSERT INTO Megrendeles (MegdelesKod, VevoKod, Datum)
VALUES (1001, 100, ’2015-01-01’)
INSERT INTO MegrendelesTetel (MegrendelesKod, TermekKod, Darab)
VALUES (1001, 210, 5)
COMMIT

Bár az alapelvek azonosak, az egyes implementációk eltérhetnek a konkrét megvalósításban:

 Az egyik lényeges különbség, hogy a tranzakciók elejét kell-e jelölni, vagy sem. MySQL és
MSSQL rendszereken alapértelmezetten ún. auto commit mód van érvényben, vagyis minden
egyes utasítás önálló tranzakciót alkot. Ha több utasítást akarunk egy tranzakcióba összefogni,
akkor a tranzakció elejét és végét jelölnünk kell. Oracle-ben az implicit tranzakciók a
szokásosak, vagyis az egyik tranzakció vége után (COMMIT ill. ROLLBACK) automatikusan egy
új tranzakció indul, ami a következő COMMIT vagy ROLLBACK utasítással ér véget. Tehát nincs
szükségünk / lehetőségünk a tranzakció elejének jelzésére.
o Megjegyezzük, hogy Oracle esetén ki és be is kapcsolható az implicit tranzakciók
használata a SET IMPLICIT_TRANSACTIONS [ON|OFF] utasítás segítségével, de még
ha át is térünk az auto commitált üzemmódra, a tranzakciók elejét expliciten akkor
sem jelölhetjük meg. MySQL esetén szintén van lehetőség az implicit tranzakciókra
történő áttérésre, a SET autocommit=0 utasítás kiadásával, ekkor az Oracle-höz
hasonló működést érhetünk el. MSSQL esetén ezt a SET IMPLICIT TRANSACTIONS
OFF utasítással tehetjük meg.
 Szintaxis tekintetében is lehetnek eltérések: bár a COMMIT és a ROLLBACK utasítások általában
minden rendszerben szerepelnek, új tranzakciót azonban MySQL esetén a START
TRANSACTION utasítással, MSSQL-ben pedig a BEGIN TRAN, illetve BEGIN TRANSACTION
utasításokkal indíthatunk.
 Explicit tranzakciók használata esetén eltérések lehetnek abban, hogy mi történik, ha egy már
futó tranzakció alatt egy új tranzakciót indítunk. Az MSSQL rendszerek támogatják az
egymásba ágyazott tranzakciókat, így ha egy új tranzakciót indítunk egy másik lezárása előtt,
akkor az egy beágyazott tranzakcióként fog működni, amelyet akár önállóan is vissza tudunk
görgetni szükség esetén. A beágyazott tranzakciók csak akkor fognak az adatbázison is
visszavonhatatlanul alkalmazódni, ha valamennyi szülő tranzakció is kommitálódott. A MySQL
csak egyszintű tranzakciókat támogat, így egy új tranzakció indításakor a még esetleg futó
tranzakció automatikusan kommitálódik. Mivel Oracle-ben nem tudunk expliciten új
tranzakciót indítani, ott értelemszerűen nem merül fel ez a kérdés.

152
 Az egyes implementációk eltérhetnek a tekintetben is, hogy milyen jellegű utasításokat lehet
egy tranzakcióba foglalni. Míg az MSSQL rendszerek DML és DDL utasításokat is megengednek
(néhány globális hatású utasítástól eltekintve, mint például a CREATE DATABASE, BACKUP,
RESTORE…), addig a MySQL és ORACLE rendszerek csak DML utasításokat engednek
tranzakcióba foglalni, a DDL utasítások automatikusan commitot eredményeznek.

Számos adatbázis-kezelő rendszer (MySQL, MSSQL, Oracle) támogatja tranzakciókon belüli


visszaállítási pontok, ún. SAVEPOINT-ok deklarálását is, ezáltal lehetőséget biztosítva arra, hogy ne csak
a tranzakció elejéig tudjuk visszagörgetni az összes változtatást egyszerre, hanem azokhoz a
SAVEPOINT-okhoz is, amelyeket a tranzakció futása közben létrehoztunk. A következő MySQL-beli
példában egy megrendeles_created nevű SAVEPOINT-ot készítünk a megrendelés létrehozása
után, de még a kapcsolódó megrendeléstétel létrehozása előtt:

BEGIN TRANSACTION
INSERT INTO Megrendeles (MegdelesKod, VevoKod, Datum)
VALUES (1001, 100, ’2015-01-01’);
SAVEPOINT megrendeles_created;
...
INSERT INTO MegrendelesTetel (MegrendelesKod, TermekKod, Darab)
VALUES (1001, 210, 5);
...
ROLLBACK TO SAVEPOINT megrendeles_created;
...
COMMIT

Később, valamilyen okból a ROLLBACK TO SAVEPOINT utasítás segítségével visszagörgetjük a


tranzakció módosításait a mentési ponthoz, így az időközben létrehozott megrendeléstételt elvetjük.
A tranzakció a ROLLBACK TO SAVEPOINT utasítás utáni művelettel folytatódik (tehát nem egy, a
procedurális programozásból ismert ugrási – GOTO – művelet történik).

8.2 Tranzakciók izolációja


Bár egy tranzakciótól önmagában elvárjuk, hogy az adatbázist konzisztens állapotból konzisztens
állapotba vigye, az egyes tranzakciók általában nem kapnak egy dedikált időkeretet, amikor csak ők
futnak, hanem sok tranzakció futhat párhuzamosan, amelyeknek lehetnek nemkívánatos
mellékhatásai a többi tranzakció működésére. Ezért elengedhetetlen az egyes tranzakciók által
elvégzett műveletek megfelelő ütemezése. A különböző műveletek sorrendjét a tranzakciós ütemező
határozza meg. A tranzakciók írási és olvasási műveleteket hajtanak végre az adatbázison, amely
műveleteket az ütemező sokszor közvetlenül a beérkezés sorrendjében végre is hajtja az
adatelemeken. De bizonyos esetekben egyes műveleteket várakoztatni kell, sőt, ahogyan majd látni is
fogjuk, egyes tranzakciókat az ütemező akár meg is szakíthat.

8.2.1 Izolációs alapproblémák

Adatbázis tranzakciók párhuzamos végrehajtása során négy fajta nemkívánatos mellékhatás fordulhat
elő:

 Elveszett módosítás

153
 Piszkos olvasás
 Nem megismételhető olvasás
 Fantom rekordok

Az elveszett módosítás alatt azt a problémát értjük, amikor két (vagy több) párhuzamosan futó
tranzakció ugyanazon adat azonos kommitált képéből kiindulva módosításokat hajt végre, majd a
módosításokat kommitálják, és a különböző tranzakciók által elvégzett műveletek közül csak az egyik
hatása fog érvényre jutni az adatbázisban, tipikusan az, amelyik időben utoljára kommitálódott. A
következő példában feltételezzük, hogy a Termek(TermekKod, Nev, Ar, Raktarkeszlet)
táblában szerepel az „Asztal” nevű termék 1-es kulccsal és 10 db-os raktárkészlettel. Közel azonos
időben történik egy eladás és érkezik egy új példány ebből a termékből, a két műveletet két
munkaállomásról közel egy időben hajtják végre két különböző tranzakcióban. Mindkét művelet során
először lekérdezzük az aktuális raktárkészletet, majd az egyik tranzakcióban eggyel nagyobb, a
másikban pedig eggyel kisebb értékkel frissítjük azt. Az egyes utasítások végrehajtási sorrendjét a
következő ábra szemlélteti.

Tranzakció 1 Tranzakció 2
SELECT Raktarkeszlet FROM Termek
WHERE TermekKod=1
SELECT Raktarkeszlet FROM Termek
WHERE TermekKod=1
UPDATE Termek SET Raktarkeszlet = 11
WHERE Id=1
UPDATE Termek SET Raktarkeszlet = 9
WHERE Id=1
COMMIT
COMMIT
8.1. ábra: Elveszett módosítás lehetősége

Ha a 10 darabos raktárkészletből 1-et kiadunk, 1-et pedig hozzá veszünk, akkor azt várnánk el, hogy
végül ismét 10 darab legyen raktáron az adott termékből, de a fenti végrehajtás után – mivel mindkét
tranzakció a 10 darabos kezdeti készletből indult ki – végül 9-et kapunk eredményül. Tehát az 1-es
számú tranzakció módosításai elvesztek.

Piszkos olvasás akkor történik, amikor két, egymással párhuzamosan futó tranzakció közül az egyik
módosít egy adatot, és még mielőtt kommitálná azt, egy másik tranzakció ezt az ideiglenes értéket
kiolvassa és felhasználja. A 8.2. ábrán bemutatott példában azt láthatjuk, hogy két tranzakció 1-1
darabbal csökkenti az 1-es számú termék raktárkészletét, de az egyik tranzakció időközben egy
ROLLBACK művelettel visszavonja a módosításait. Ha a 2-es tranzakció az 1-es tranzakció által
módosított értékből indul ki, akkor 10 darabos kezdő raktárkészlet esetén a végeredmény 9 helyett 8
lesz. A 2-es tranzakciónak nem lett volna szabad felhasználnia az 1-es tranzakció által ideiglenesen
előállított értékeket, mivel azok végül nem váltak az adatbázis részévé.

154
Tranzakció 1 Tranzakció 2
UPDATE Termek SET Raktarkeszlet =
Raktarkeszlet – 1 WHERE TermekKod=1
UPDATE Termek SET Raktarkeszlet =
Raktarkeszlet – 1 WHERE TermekKod=1
ROLLBACK
COMMIT
8.2. ábra: Piszkos olvasás lehetősége

Egy olvasást akkor nevezünk nem megismételhetőnek, ha ugyanazon tranzakción belül ugyanazt az
adatmezőt kétszer lekérdezve különböző eredményt kapunk. Ez akkor fordulhat elő, ha az egyik
tranzakció által lekérdezett adatot egy másik tranzakció időközben módosítja (és kommitálja is, tehát
nem piszkos adatot olvas), majd az első tranzakcióban ugyanezt az adatot újból lekérdezve már a
módosított értéket kapjuk. Ezt illusztrálja a 8.3. ábrán látható példa: az 1-es tranzakcióban először
lekérdezzük az 1-es sorszámú termék raktárkészletét, majd amikor konstatáljuk, hogy elegendő áll
rendelkezésre, tízzel csökkentjük azt. Időközben a 2-es tranzakció nullára csökkenti a termék
raktárkészletét, és kommitálja is ezt a módosítást. Ezek után, amikor az 1-es tranzakció csökkenti a
raktárkészletet, akkor már a 0-ás értékből fog kiindulni, így a végeredmény -10 lesz.

Tranzakció 1 Tranzakció 2
SELECT Raktarkeszlet FROM Termek WHERE
Id=1
UPDATE Termek SET Raktarkeszlet=0 WHERE
Id=1
COMMIT
UPDATE Termek SET Raktarkeszlet =
Raktarkeszlet – 10 WHERE Id=1
COMMIT
8.3. ábra: Nem megismételhető olvasás lehetősége

Az elvárt helyes működés ebben az esetben vagy az lenne, hogy a kettes tranzakció módosító utasítása
addig ne tudjon lefutni, amíg az egyes tranzakció be nem fejeződik, vagy az 1-es tranzakció lekérdezése
addig várakozzon, amíg a 2-es tranzakció kommitálódik.

A fantom rekordok problémája a nem megismételhető olvasás egy változata azzal a különbséggel,
hogy nem csak egyszerű attribútumokra vonatkozik, hanem teljes rekordokra: ugyanazt a lekérdezést
ismételten lefuttatva egy tranzakcióban új rekordok jelennek meg az eredményben, vagy korábbiak
tűnnek el. A nem megismételhető olvasáshoz hasonlóan ez akkor fordulhat elő, ha időközben egy
másik tranzakció új rekordokat szúr be vagy töröl a táblából, és kommitálja is a módosítást (tehát nem
piszkos adat olvasása történik). A 8.4. ábrán látható példában az 1-es tranzakcióban a Termek tábla
teljes tartalmát kérdezzük le kétszer, de a második esetben már egy új rekord is megjelenik az
eredményben, mivel a párhuzamosan futó 2-es tranzakcióban beszúrtunk egy új rekordot (és
kommitáltuk is az eredményt).

155
Tranzakció 1 Tranzakció 2
SELECT * FROM Termek
INSERT INTO Termek VALUES(101, ’Szék’,
10)
COMMIT
SELECT * FROM Termek
COMMIT
8.4. ábra: Fantom rekordok előfordulásának lehetősége

A fenti problémák természetesen csak akkor fordulhatnak elő, ha az egyes tranzakciókat egyáltalán
nem izoláljuk egymástól, de a tranzakciókezelő alrendszer az adatbázis műveletek megfelelő
ütemezésével és zárak alkalmazásával biztosítani tudja az elvárt működést. A továbbiakban részletesen
megvizsgáljuk, hogy ez pontosan hogyan történik.

8.2.2 Tranzakciók ütemezése

Ahhoz, hogy az izolációs alapproblémák ne fordulhassanak elő, a különböző adatbázis tranzakciók


során elvégzett írás és olvasás műveleteket megfelelően sorrendezni kell. Természetesen az egy
tranzakción belül elvégzett műveletek sorrendjét nem változtatjuk meg, de ha egy tranzakció egy
művelete a párhuzamosan futó más tranzakció(k) működését megváltoztathatná, és ezáltal az
adatbázist inkonzisztens állapotba vihetné, akkor ennek a műveletnek a végrehajtását várakoztatni
kell.

Felmerül a kérdés, hogy honnan tudjuk, hogy a párhuzamosan futó tranzakciók működését nem
megengedett módon befolyásolná egy művelet végrehajtása? Amit biztosan tudunk, hogy ha a
párhuzamosan elindított tranzakciókat úgy hajtjuk végre, hogy egyszerre csak egy tranzakció
műveleteit végezzük el, és azokat közvetlenül egymás után, megszakítás nélkül, akkor biztosan helyes
végeredményt kapunk. Ezt nevezzük soros ütemezésnek: a különböző tranzakciókat átfedés nélkül,
egymás után sorban futtatjuk, párhuzamosítás nélkül.

A 8.5. ábrán két tranzakció soros ütemezését szemléltetjük. A tranzakciók futása során a
rendszermemóriából adatot olvasunk a tranzakció változóiba (READ(A,x): az A adat beolvasása az x
memória rekeszbe), illetve írunk vissza oda (WRITE(A,x): az A adat x helyen álló értékkel történő
frissítése).

156
Tranzakció 1 Tranzakció 2 A B
100 100
READ(A,x)
x = x+10
WRITE(A,x) 110
READ(B,x)
x = x+10
WRITE(B,x) 110
READ(A,y)
y = y*2
WRITE(A,y) 220
READ(B,y)
y = y*2
WRITE(B,y) 220
8.5. ábra: Tranzakciók soros ütemezése

A példában az adatbázist akkor tekintjük konzisztensnek, ha A és B értéke megegyezik. Az 1-es


tranzakció 10-zel növeli az A és B értékét, a 2-es tranzakció pedig 2-vel szorozza őket. Ha A és B kezdeti
értéke egyaránt 100, akkor a két tranzakció lefutása után 220 lesz a tartalmuk. Az ábrán látható
ütemezésben a két tranzakció műveleteit egymás után hajtjuk végre, így a nem megfelelő izolációból
adódó problémák egyike sem fordulhat elő. Hasonlóképp nem fordulnak elő izolációs problémák, ha a
2-es tranzakció műveleteit az egyes előtt hajtjuk végre. Bár a végeredmény más lesz
(A=B=100*2+10=210), ez is egy helyes lefutás, a végeredmény egy konzisztens adatbázis.

8.2.2.1 Sorosítható ütemezés


Programozói szemmel kényelmes megoldás lenne a tranzakciók soros végrehajtása, de ez a tranzakciók
túlzott várakoztatásával járna. Felmerülhet a kérdés, hogy léteznek-e más olyan ütemezések, amelyek
szintén megőrzik a konzisztenciát? A válasz pedig igen: egy ütemezést akkor nevezünk
sorosíthatónak, ha létezik olyan soros ütemezés, amellyel tetszőleges kezdőállapot esetén mindig
azonos végeredményt produkál.

Tranzakció 1 Tranzakció 2 A B
100 100
READ(A,x)
x = x+10
WRITE(A,x) 110
READ(A,y)
y = y*2
WRITE(A,y) 220
READ(B,x)
x = x+10
WRITE(B,x) 110
READ(B,y)
y = y*2
WRITE(B,y) 220
8.6. ábra: Egy sorosítható ütemezés

A 8.6. ábra egy sorosítható ütemezést láthatunk: a 2-es tranzakció A-t az után módosítja, hogy az 1-es
tranzakció már megtette ezt, de még az előtt, hogy az 1-es tranzakció B-t megváltoztatta volna.
Könnyen belátható, hogy ez az ütemezés is mindig konzisztens állapotba visz: Tetszőleges A=B=c
kezdőállapot esetén a tranzakciók lefutása után A és B értéke is (c+10)*2 lesz. Másrészt viszont a 8.7.

157
ábrán látható ütemezés nem sorosítható, mivel c közös kezdeti érték esetén a tranzakciók lefutása
után A értéke (c+10)*2, míg B értéke c*2+10 lesz.

Tranzakció 1 Tranzakció 2 A B
100 100
READ(A,x)
x = x+10
WRITE(A,x) 110
READ(A,y)
y = y*2
WRITE(A,y) 220
READ(B,y)
y = y*2
WRITE(B,y) 200
READ(B,x)
x = x+10
WRITE(B,x) 210
8.7. ábra: Egy nem sorosítható ütemezés

8.2.2.2 Konfliktus – sorosítható ütemezés


Tegyük fel, hogy a 8.7. ábrán látható ütemezésben a 2-es tranzakció nem szorzást, hanem szintén
összeadást végez el a forrás adatokon (8.8. ábrán).

Tranzakció 1 Tranzakció 2 A B
100 100
READ(A,x)
x = x+10
WRITE(A,x) 110
READ(A,y)
y = y+2
WRITE(A,y) 112
READ(B,y)
y = y+2
WRITE(B,y) 102
READ(B,x)
x = x+10
WRITE(B,x) 112
8.8. ábra: Művelettől függően sorosítható ütemezés

Ekkor tetszőleges c kezdeti érték esetén a fenti ütemezést alkalmazva A végső értéke c+10+2=c+12,
B értéke pedig szintén c+2+10=c+12 lesz. Tehát az elvégzett művelettől függően, azonos ütemezés
lehet sorosítható és nem sorosítható is. Sajnos az ütemezőtől nem várhatjuk el, hogy értelmezze a
tranzakciók által elvégzett műveleteket, mivel azokat sokszor általános célú programozási nyelveken
írják, és csak bináris formában érhetők el, illetve általános esetben a szemantika elemzése valós időben
nem kivitelezhető. Ezért az ütemező csak az írás és olvasás műveleteket veszi figyelembe, és ha írás
történik, akkor feltételezi, hogy a kiírt értéket a tranzakció úgy változtatta meg, hogy a párhuzamosan
futó tranzakciókban ugyanezen adatokkal végzett műveletekkel együtt inkonzisztens állapot jön létre.
Ennek megfelelően a sorosíthatóság vizsgálatakor azzal az egyszerűsítéssel élünk, hogy az ütemező
csak az alábbi két műveletet ismeri:

 𝑟𝑖 (𝐴): az i. tranzakció az A adatot olvassa


 𝑤𝑖 (𝐴): az i. tranzakció az A adatot írja

158
A fenti két művelettel a 8.6. ábrán látható két tranzakció az alábbi módon írható le:

 𝑇1 : 𝑟1 (𝐴); 𝑤1 (𝐴); 𝑟1 (𝐵); 𝑤1 (𝐵)


 𝑇2 : 𝑟2 (𝐴); 𝑤2 (𝐴); 𝑟2 (𝐵); 𝑤2 (𝐵)

Továbbá az ugyanezen az ábrán látható sorosítható ütemezés:

𝑟1 (𝐴); 𝑤1 (𝐴); 𝑟2 (𝐴); 𝑤2 (𝐴); 𝑟1 (𝐵); 𝑤1 (𝐵); 𝑟2 (𝐵); 𝑤2 (𝐵)

Egy ilyen művelet szekvenciában két műveletre azt mondjuk, hogy konfliktusban állnak, ha
sorrendjüket felcserélve legalább az egyik tranzakció viselkedése megváltozhat. Vizsgálatunkat kezdjük
azokkal az esetekkel, amelyek biztosan nem állnak konfliktusban (𝑖 ≠ 𝑗):

 𝑟𝑖 (𝑋); 𝑟𝑗 (𝑌): két olvasás művelet mindig felcserélhető, függetlenül az olvasott adattól. Ezt
azért tehetjük meg, mert az olvasás művelet nem változtatja meg az adatot.
 𝑟𝑖 (𝑋); 𝑤𝑗 (𝑌), ha 𝑋 ≠ 𝑌: különböző adatok írása és olvasása két különböző tranzakcióban
mindig felcserélhető. Ez azért lehetséges, mert 𝑋 értékét nem befolyásolja, ha előtte Y-t egy
másik tranzakcióban módosítottuk, illetve 𝑌 módosításához nincs szükségünk a másik
tranzakcióban kiolvasott 𝑋 értékre. Hasonló okból 𝑤𝑖 (𝑋); 𝑟𝑗 (𝑌) sem áll konfliktusban.
 𝑤𝑖 (𝑋); 𝑤𝑗 (𝑌), ha 𝑋 ≠ 𝑌: különböző adatok írása mindig felcserélhető, hiszen bármely
sorrendben is hajtjuk végre őket, az adatbázis állapota azonos lesz.

Biztosan konfliktusban állnak viszont a következő párok:

 Ugyanazon tranzakció két művelete (pl. 𝑟𝑖 (𝑋); 𝑤𝑖 (𝑌)). A tranzakción belüli műveletek
sorrendje kötött, nem rendezhetők át.
 𝑤𝑖 (𝑋); 𝑤𝑗 (𝑋): ha két különböző tranzakció ugyanazt az adatelemet írja, akkor az írás
műveletek nem felcserélhetők. Feltételezzük, hogy a két tranzakció más-más adatot írna
vissza, így 𝑋 új értéke attól függne, hogy melyik tranzakció írás művelete hajtódott később
végre.
 𝑟𝑖 (𝑋); 𝑤𝑗 (𝑋): ugyanazon adatelem írása és olvasása két különböző tranzakcióból szintén
konfliktusban áll, hiszen ha 𝑤𝑗 (𝑋)-et 𝑟𝑖 (𝑋) előtt hajtjuk végre, akkor 𝑟𝑖 (𝑋) már az új értéket
olvasná ki.

A fenti feltételeket úgy foglalhatjuk össze, hogy két különböző tranzakció két művelete akkor nem
cserélhető fel, ha mindkettő ugyanarra az adat elemre hivatkozik, és legalább az egyik művelet írás.

Két ütemezést akkor nevezünk konfliktus-ekvivalensnek, ha konfliktus mentes műveletek


cseréjével az egyikből el tudjuk érni a másikat. Ha egy ütemezés konfliktus-ekvivalens egy soros
ütemezéssel, akkor arra azt mondjuk, hogy konfliktus-sorosítható. Vegyük észre, hogy a konfliktus-
sorosítható ütemezések mindig sorosíthatóak is egyben, hiszen feltételezésünk szerint a konfliktus
mentes cserék nem változtatják meg az egyes tranzakciók hatását. Ellenben nem minden sorosítható
ütemezés konfliktus-sorosítható is (például a 8.8. ábrán látható sem). A kereskedelmi forgalomban
elérhető adatbázis-kezelő rendszerek ütemezői tipikusan a jóval könnyebben ellenőrizhető konfliktus-
sorosíthatóságot szokták vizsgálni, amikor a sorosíthatóságot akarják biztosítani.

A következő példában egy konkrét ütemezésről szeretnénk eldönteni, hogy az konfliktus-sorosítható-


e vagy sem:

159
𝑟1 (𝐴); 𝑤1 (𝐴); 𝑟2 (𝐴); 𝑤2 (𝐴); 𝑟1 (𝐵); 𝑤1 (𝐵); 𝑟2 (𝐵); 𝑤2 (𝐵)

Ehhez olyan szomszédos művelet párokat kell keresni, amelyek nem állnak konfliktusban, és a
cseréjükkel meg kell próbálni egy soros ütemezést előállítani. Az alábbiakban ismertetünk egy
lehetséges csere-sorozatot, amellyel a soros ütemezés elérhető, így bizonyítjuk, hogy az ütemezés
sorosítható. Az aktuálisan vizsgált műveletpárt aláhúzással jelöljük:

𝑟1 (𝐴); 𝑤1 (𝐴); 𝑟2 (𝐴); 𝑤2 (𝐴); 𝑟1 (𝐵); 𝑤1 (𝐵); 𝑟2 (𝐵); 𝑤2 (𝐵)

𝑟1 (𝐴); 𝑤1 (𝐴); 𝑟2 (𝐴); 𝑟1 (𝐵); 𝑤2 (𝐴); 𝑤1 (𝐵); 𝑟2 (𝐵); 𝑤2 (𝐵)

𝑟1 (𝐴); 𝑤1 (𝐴); 𝑟1 (𝐵); 𝑟2 (𝐴); 𝑤2 (𝐴); 𝑤1 (𝐵); 𝑟2 (𝐵); 𝑤2 (𝐵)

𝑟1 (𝐴); 𝑤1 (𝐴); 𝑟1 (𝐵); 𝑟2 (𝐴); 𝑤1 (𝐵); 𝑤2 (𝐴); 𝑟2 (𝐵); 𝑤2 (𝐵)

𝑟1 (𝐴); 𝑤1 (𝐴); 𝑟1 (𝐵); 𝑤1 (𝐵); 𝑟2 (𝐴); 𝑤2 (𝐴); 𝑟2 (𝐵); 𝑤2 (𝐵)

8.2.2.3 Zárak
Az ütemező feladata tehát, hogy a különböző tranzakciókból érkező írás és olvasás műveleteket
fogadja, és vagy közvetlenül végrehajtsa őket az adatbázison, vagy várakoztassa őket, amíg
biztonságosan végre nem hajthatók. Biztonságos végrehajtás alatt ebben az esetben azt értjük, hogy
az adatbázis konzisztens állapotba kerül, amikor valamennyi aktív tranzakció kommitálódik. A legtöbb
tranzakció ütemező a megismert konfliktus-sorosíthatóságot biztosítja, és ezt kétfázisú zárolás
segítségével teszi. Kétfázisú zárolás során az egyes tranzakcióknak zárat kell elhelyezniük az egyes
adatelemeken, mielőtt írni vagy olvasni tudnák őket, majd a zárat fel kell szabadítaniuk, amint már
nincs rá szükség. Az adott tranzakcióból az elérni kívánt adathoz csak akkor lehet hozzáférni, ha a zárat
a tranzakció sikeresen el tudta helyezni, zárat pedig csak akkor lehet elhelyezni, ha egy másik
tranzakció már nem zárolta a hivatkozott adatot. Ellenkező esetben a tranzakció műveleteit az
ütemező addig várakoztatja, amíg a kapcsolódó zár fel nem szabadul, és a várakozó tranzakció zár-
kérése nem teljesíthető. A zárak elhelyezése történhet automatikusan (az olvasó és író műveletek
előtt) vagy manuálisan (lsd. 8.2.3.1. fejezet), felszabadításuk pedig automatikusan, a tranzakció
befejeztével (commit vagy rollback) történik meg.

A gyakorlatban általában két fajta zárat szoktak használni: osztott (shared) és kizáró (exclusive)
zárat. Az osztott zárakat olvasási műveletek előtt szokták elhelyezni a hivatkozott adaton, a kizáró
zárat pedig írási műveletek előtt. Egy adatra egyszerre több osztott zár is hivatkozhat, ami azt fejezi ki,
hogy ugyanaz az adat párhuzamosan több tranzakcióból is olvasható, kizáró zár viszont csak akkor, ha
egy másik tranzakció nem helyezett még el semmilyen zárat ugyanerre az adatra. (Ugyanazon
tranzakcióból igényelhetünk kizáró zárat egy adatra, ha csak ebből a tranzakcióból helyeztünk rá
olvasási zárat korábban). A fenti zárolási módszer két dolgot biztosít:

 Ha egy 𝑇1 tranzakció már kiolvasott egy 𝐴 adatot, akkor egy 𝑇2 tranzakció ugyanezt az 𝐴 adatot
csak akkor módosíthatja, ha a 𝑇1 tranzakció kommitálódott (vagy rollbackelt). Vagyis mivel az
𝑟1 (𝐴); 𝑤2 (𝐴) műveletek konfliktusban állnak, az aktuális ütemezést csak olyan soros
ütemezéssé alakíthatjuk, amelyben a 𝑇2 tranzakció műveletei a 𝑇1 tranzakció után
következnek.

160
 Ha egy 𝑇1 tranzakció egy A adatot kiírt, akkor egy 𝑇2 tranzakció ugyanezt az A adatot csak akkor
olvashatja vagy írhatja, ha a 𝑇1 tranzakció már kommitálódott (vagy rollbackelt). Vagyis mivel
a 𝑤1 (𝐴); 𝑟2 (𝐴) ill. 𝑤1 (𝐴); 𝑤2 (𝐴) műveletek konfliktusban állnak; az aktuális ütemezést csak
olyan soros ütemezéssé alakíthatjuk, amelyben a 𝑇2 tranzakció műveletei a 𝑇1 tranzakció után
következnek.

A fenti két feltétel éppen a konfliktus-sorosíthatóságot biztosítja (két különböző tranzakció két
művelete akkor nem cserélhető fel, ha mindkettő ugyanarra az adat elemre hivatkozik, és legalább az
egyik írás művelet).

8.2.2.4 Holtpont
Zárak használata esetén felmerül a holtpont létrejöttének lehetősége. Például ha 𝑇1 és 𝑇2
párhuzamosan futó tranzakciók ugyanarra az 𝐴 adatra kérnek először osztott zárat, majd 𝑇1 megpróbál
az 𝐴 adatra kizáró zárat is igényelni, akkor várakozni kényszerül, mivel a másik tranzakció már osztott
zárral rendelkezik erre az adatra. Ha ezek után 𝑇2 is kizáró zárat igényel 𝐴-ra, akkor ő is kénytelen várni
𝑇1 -re, így a két tranzakció egymásra vár, holtpont alakul ki. Hasonló helyzetet eredményez, ha a 𝑇1
tranzakció először az 𝐴 adatra igényel kizáró zárat, a 𝑇2 tranzakció pedig 𝐵-re. Ha ezek után a 𝑇1
tranzakció a 𝐵 adatra igényel zárat, akkor várakozni kényszerül. Ha a 𝑇2 tranzakció pedig az 𝐴-ra kér
zárat, akkor ő is várakozni kényszerül a 𝑇1 tranzakcióra, tehát ismét holtpont alakul ki (8.9. ábra).

Tranzakció 1 Tranzakció 2
LOCK(A)
LOCK(B)
LOCK(B)
LOCK(A)
8.9. ábra: Holtpont

Sokszor a holtpont kialakulásának lehetőségét elkerülhetjük azáltal, hogy

 a szükséges adatokra minden tranzakcióban azonos sorrendben helyezzük fel a zárakat: ha


mindig először az A adatra, és csak utána a B-re helyezzük fel a zárt, akkor nem fordulhat elő a
8.9. ábrán illusztrált eset;
 továbbá ha a kizáró zárat – amennyiben a tranzakció során később szükség lesz rá – már a
tranzakció elején, az olvasási zár helyett felhelyezzük az adatra, úgy megakadályozhatjuk, hogy
más tranzakciók is osztott zárat tudjanak létrehozni.

Számos esetben azonban nem élhetünk a fenti lehetőségekkel, mivel a később elhelyezett zárak a
tranzakció elején még nem megjósolhatók, vagy a zár elhelyezési sorrend nem módosítható. Ekkor a
holtpontot detektálni és kezelni kell. A holtpont kezelése általában azt jelenti, hogy az egyik tranzakció
megszakításra kerül, és a változtatásait visszagörgeti a tranzakciós rendszer. Holtpontok észlelésére
több módszert is alkalmazhatnak:

 Időzítő: ha a tranzakciók tipikus futási idejénél az aktuális tranzakció lényegesen hosszabb


ideje fut (például milliszekundumok helyett perc), akkor a tranzakció valószínűleg holtpontra
jutott.
 Erőforrás foglalási gráf: ha a tranzakciókat és az általuk zárolt adatokat egy gráf
csomópontjaiként, a zárolást, illetve a zár elhelyezésre várakozást pedig a gráf éleiként
reprezentáljuk, akkor a holtpont detektálást visszavezethetjük az ebben a gráfban történő

161
irányított kör keresésre. Az ilyen modelleket szoktuk erőforrás foglalási gráfnak nevezni,
amelyben az erőforrások az adatok. 8.10. ábrán egy egyszerű erőforrás foglalási gráfot
láthatunk: 𝑇1 és 𝑇2 tranzakciók, 𝐴 és 𝐵 pedig adatok. A 𝑇1 tranzakció zárolja 𝐴-t, és arra vár,
hogy zárat helyezhessen el 𝐵-n is, a 𝑇2 tranzakció pedig 𝐵-re rendelkezik zárral, és 𝐴-ra
szeretne zárat felhelyezni. A tranzakciókezelő az irányított kör detektálásával a holtpontot
érzékeli, és az egyik tranzakciót abortálhatja.

A T2

T1 B

8.10. ábra: Erőforrás foglalási gráf

8.2.3 Izolációs szintek

Ahogyan láttuk, az ideális ütemezés a soros, illetve sorosítható ütemezések, de számos esetben ez
feleslegesen sok várakozásra kényszerítené a párhuzamos tranzakciókat. Gyakran az üzleti logika, ami
az adatbázis műveleteket vezérli, több párhuzamosságot is megenged, mint amit adatbázis szinten az
ütemező az írás és olvasás műveletekből ki tud következtetni. Az SQL szabvány az ütemezés
szigorúságát ún. izolációs szintekkel engedi szabályozni, és az adatbázis programozóra bízza a
megfelelő izolációs szint kiválasztását. A szabvány által meghatározott izolációs szintek a következők:

 UNCOMMITTED READ: semmilyen izolációt nem biztosít, egyik izolációs alapproblémát sem
zárja ki (MySQL, MSSQL).
 COMMITTED READ: a piszkos olvasás nem lehetséges, az egyes tranzakciók mindig csak
kommitált adatot olvashatnak (MySQL, MSSQL, Oracle).
 REPEATABLE READ: a piszkos olvasást és a nem megismételhető olvasást is kizárja (MySQL,
MSSQL).
 SERIALIZABLE (sorosítható): egyik alapprobléma sem fordulhat elő (MySQL, MSSQL, Oracle).

Az alkalmazott izolációs szintet a

SET TRANSACTION ISOLATION LEVEL <izolációs szint>


utasítással választhatjuk ki, ahol az izolációs szint egy a fentiek közül (UNCOMMITTED READ,
COMMITTED READ, REPEATABLE READ, SERIALIZABLE). Amint látható, az Oracle rendszerek a 4
közül csak két izolációs szintet támogatnak (COMMITTED READ és SERIALIZABLE), míg a MySQL és az
MSSQL mind a négyet. MySQL implementációknál az alapértelmezett szint a REPEATABLE READ, míg
MSSQL esetén a COMMITTED READ. Egyes adatbázis-kezelő rendszerek további izolációs szinteket is
támogatnak, például:

 SNAPSHOT: az MSSQL rendszerek által támogatott izolációs szint, amely azt biztosítja, hogy
a tranzakció futása során minden olvasott adat a tranzakció kezdetekor érvényes állapotot
tükrözi, mintha csak egy, a tranzakció kezdetekor készített pillanatfelvételen dolgozna a
tranzakció. Az izolációs szint megvalósításához sor verziózást alkalmaz az adatbázis-kezelő

162
rendszer: a tranzakciók által módosított rekordokat a tranzakció azonosítójával felcímkézve
egy ideiglenes táblában (tempdb) tárolja el, így minden tranzakció saját másolattal rendelkezik
a módosított adatokról. Ha egy tranzakció lezárult, és már nincs olyan, még futó tranzakció,
ami a lezárás előtt jött volna létre (tehát még szüksége lehetne az eredeti adatokra), akkor a
lezárt tranzakció módosításai alkalmazhatók az adatbázison.
 READONLY: az Oracle rendszerek által támogatott izolációs szint, amely konzisztensen, a
tranzakció kezdete elején érvényes állapotnak megfelelő értékeket adja vissza a lekérdezések
során, de nem ad lehetőséget a módosításra. Szintén sorverziózással valósítják meg, amely
némileg eltér az MSSQL-beli implementációtól: az Oracle a korábbi változatokat a tranzakciós
napló (8.3. fejezet) bejegyzései alapján tudja előállítani.

A gyakorlati megvalósítása természetesen a READ UNCOMMITTED izolációs szintnek a legegyszerűbb,


hiszen semmilyen szintű izolációt nem biztosít, így nincs szükség zárak és sor verziózás alkalmazására.

A SERIALIZABLE izolációs szint implementációja általában zárak segítségével (8.2.2.3. fejezet) történik,
és a sorosíthatóságot sértő művelet várakozni kényszerül, de például Oracle alatt (csak úgy, mint a
READ COMMITTED izolációs szinten) sorverziózás segítségével – a tranzakciós napló visszagörgetésével
– biztosítják a tranzakció futása alatt a konzisztens képet. Az adatok párhuzamos elérése ekkor nem
okoz blokkolást vagy azonnali hibát, viszont a tranzakció végén, amennyiben a sorosíthatóság nem
biztosítható, hibaüzenetet kapunk. Az Oracle e tekintetben optimista konkurencia kezelést valósít meg.

A COMMITTED READ és REPEATABLE READ izolációs szinteket MySQL-ben szintén sorverziózással


valósítják meg, amelynek implementációja az Oracle-höz hasonló: a tranzakciós napló
visszagörgetésével az utolsó kommitált állapot (COMMITTED READ), illetve az adott adat első
lekérdezésekor érvényes állapot (REPEATABLE READ) előállítható.

Az MSSQL ezen két izolációs szint biztosításához zárakat használ: rekord módosítás előtt írási zárat
helyez fel az érintett rekordokra, így megakadályozza, hogy más tranzakciók ezután olvasni tudják őket
a módosító tranzakció végéig (meggátolva a piszkos olvasást). Olvasás előtt pedig megosztott zárat
helyez el a rekordokon. A megosztott zárakat REPEATABLE READ izolációs szint esetén a tranzakció
végéig fent is hagyja, ezáltal megakadályozza, hogy más tranzakciók módosítsák ezeket a rekordokat,
és az olvasás ne legyen megismételhető. READ COMMITTED izolációs szint esetén az osztott zárat
rögtön fel is oldja, amivel csak azt teszteli, hogy van-e az érintett rekordokon kizáró zár, ami
párhuzamos, még nem kommitált módosítást jelentene. Természetesen, ha osztott zár elhelyezésekor
már van az érintett rekordokon kizáró zár, illetve kizáró zár felhelyezésekor osztott zár, akkor a
tranzakció várakozni kényszerül a másik zár feloldásáig. Felmerülhet a kérdés, hogy MSSQL-ben mi a
különbség a SERIALIZABLE és a REPEATABLE READ izolációs szint implementációja között, hiszen
mindkettő osztott zárat helyez el az érintett rekordokon olvasás előtt, illetve kizáró zárat írás előtt,
amelyeket a tranzakció végéig fent is tart. De ha végiggondoljuk, ez önmagában a fantom rekordok
megjelenését nem gátolja meg, hiszen zárak csak a korábban is létező rekordokon vannak.
SERIALIZABLE izolációs szint esetén az osztott zárakat nem egyes rekordokra, hanem intervallumokra
helyezi el a tranzakciókezelő, így várakoztatva azokat a műveleteket, amelyek olyan új rekordokat
eredményeznének, amelyek megjelenhetnének más, még futó tranzakciók lekérdezéseiben.

163
8.2.3.1 Zárak manuális elhelyezése
Ahogyan láttuk, bizonyos izolációs szinteknél az adatbázis-kezelő rendszer automatikusan osztott vagy
kizáró zárakat helyez az érintett rekordokra. Egyes esetekben azonban előnyösebb, ha az adatbázis
programozó saját maga dönthet arról, hogy a zárak pontosan mikor jöjjenek létre. Így például
biztosíthatjuk, hogy az érintett adatokat garantáltan azonos sorrendben zároljuk az egyes
tranzakciókban, és ne fordulhasson elő holtpont, de akár egy alacsonyabb szintű izolációs szinten is
megvalósíthatjuk a tranzakciók teljes izolációját. A zárak elhelyezése rendszerenként eltérő
szintaktikával, de hasonló formában történik: az érintett rekordokat egy SELECT utasítással
kiválasztjuk, és a SELECT utasításhoz fűzött megfelelő kulcsszavakkal elhelyezzük rajtuk a zárakat. (Az
utasítás természetesen várakozik, ha a zárakat nem lehet elhelyezni). A zárakat általában változatos
granularitással hozhatjuk létre: csak bizonyos rekordokon, a teljes táblán, de MSSQL-ben akár
blokkonként is helyezhetünk el zárakat. A 8.11. ábrán, 8.12. ábrán és 8.13. ábrán röviden összefoglaljuk
a MySQL, MSSQL és Oracle rendszerek azon alapvető utasításait, amelyek segítségével sor és tábla
szintű zárakat tudunk elhelyezni. Az egyes funkciók mélyebb megértése és további lehetőségek
megismerése végett ajánljuk az alkalmazott adatbázis-kezelő rendszer dokumentációjának
tanulmányozását.

MySQL
Megosztott zár létrehozása SELECT FROM … WHERE … LOCK IN SHARE MODE

Kizáró zár létrehozása SELECT FROM … WHERE … FOR UPDATE

Teljes tábla megosztott zárolása LOCK TABLE <tábla név> READ

Teljes tábla kizáró zárolása LOCK TABLE <tábla név> WRITE

8.11. ábra: Zárak explicit létrehozása MySQL-ben

MSSQL

Megosztott zár létrehozása SELECT FROM … WHERE … (HOLDLOCK)


Kizáró zár létrehozása SELECT FROM … WHERE … (XLOCK)
Teljes tábla megosztott zárolása SELECT * FROM <tábla név> (TABLOCK)
Teljes tábla kizáró zárolása SELECT * FROM <tábla név> (TABLOCKX)

8.12. ábra: Zárak explicit létrehozása MSSQL-ben

Oracle

Megosztott zár létrehozása SELECT FROM … WHERE … LOCK IN SHARE MODE


Kizáró zár létrehozása SELECT FROM … WHERE … FOR UPDATE
Teljes tábla megosztott zárolása LOCK TABLE <tábla név> IN SHARE MODE
Teljes tábla kizáró zárolása LOCK TABLE <tábla név> IN EXCLUSIVE MODE

8.13. ábra: Zárak explicit létrehozása Oracle-ben

A következő MySQL-t alkalmazó példával azt szemléltetjük, hogy hogyan kerülhető el az elveszett
módosítás zárak explicit használatával és a tranzakciók abortálása nélkül. A példában azt az esetet
valósítjuk meg, amikor a 3-as azonosítóval rendelkező termékből párhuzamosan 20-20 darabot

164
szeretnénk kiadni. A termék raktárkészletét (kezdetben 100) mindkét tranzakcióban leellenőrizzük egy-
egy lekérdezés segítségével (SELECT), majd egy 20-szal kisebb értékkel frissítjük (UPDATE). Ha a
párhuzamosan futó tranzakciókban SERIALIZABLE izolációs szintet használunk, akkor a 8.14. ábrán
látható ütemezés mellett holtpont alakulna ki, hiszen először mindkét tranzakció megosztott zárat
helyez fel, majd pedig mindkettő kizáró zárat szeretne kérni, de várni kényszerül. Ugyanez az ütemezés
REPEATABLE READ izolációs szint esetén elveszett módosítást eredményezne, hiszen mindkét
tranzakció ugyanabból a kommitált képből indul (100) ki, majd ezt az értéket 20-szal csökkentve
mindkét tranzakció 80-at írna vissza.

Tranzakció 1 Tranzakció 2
SELECT * FROM TERMEK WHERE ID=3;
SELECT * FROM TERMEK WHERE ID=3;
UPDATE Termek SET Raktarkeszlet = 80
WHERE ID=3;
COMMIT;
UPDATE Termek SET Raktarkeszlet = 80
WHERE ID=3;
COMMIT;
8.14. ábra: Adat párhuzamos írása és olvasása

(Megjegyzés: MySQL-ben az olvasás nem helyez el megosztott zárakat a READ COMMITTED és


REPEATABLE READ izolációs szinteken a SELECT utasítások végrehajtása során, viszont a módosított
rekordokra kizáró zárakat helyez fel.)

A probléma egyszerűen megoldható, akár READ COMMITTED izolációs szint használata mellett is, ha
az érintett rekordra már a lekérdezéssel egy időben kizáró zárat helyezünk. Ezt a megoldást szemlélteti
a 8.15. ábra: A SELECT utasítás után a FOR UPDATE kulcsszavakkal jelöljük, hogy a lekérdezéssel együtt
kizáró zárat kell igényelni a visszaadott rekordra. Így egy párhuzamos tranzakció lekérdezése – amely
szintén kizáró zárat igényel – várakozni fog, ameddig a kizáró zárral rendelkező tranzakció nem ér
véget. Ekkor a lekérdezés lefut, de már a módosított raktárkészlet értéket (80) adja vissza.

165
Tranzakció 1 Tranzakció 2
SET SESSION TRANSACTION ISOLATION LEVEL
READ COMMITTED;
START TRANSACTION;
SET SESSION TRANSACTION ISOLATION LEVEL
READ COMMITTED;
START TRANSACTION;
SELECT * FROM TERMEK WHERE ID=3 FOR
UPDATE;
SELECT * FROM TERMEK WHERE ID=3 FOR
UPDATE;
UPDATE Termek SET Raktarkeszlet = 80 VÁR...
WHERE ID=3;
COMMIT;
UPDATE Termek SET Raktarkeszlet = 60
WHERE ID=3;
COMMIT;
8.15. ábra: Zárak manuális használata

8.3 Tranzakciós naplózás


A tranzakciós naplózás elsődleges célja, hogy az adatbázis konzisztens állapotát helyreállíthassuk egy
rendszerhiba után. Ebben az esetben rendszerhiba alatt elsősorban szoftveres hibákra, esetleg
áramkimaradásra gondolunk, de semmiképp sem a másodlagos tár meghibásodására, fizikai
sérülésére, amikor a már perzisztált adatok és maga a tranzakciós napló is sérül. Ilyen esetekben a
helyreállítás az ideális esetben rendszeresen készített biztonsági mentésekből lehetséges. A
tranzakciós napló egy másik lehetséges felhasználása (ahogyan a 8.2.3. fejezetben is utaltunk rá) a
rekordok korábbi változatának előállítása az izoláció megvalósítása során.

A tranzakciók, a Naplókezelő modul valamit az adatbázis többi komponensének kapcsolatát a 8.16.


ábrán szemléltetjük.

166
Tranzakciókezelő

T1 Tn
Tranzakció Tranzakció

WRITE READ
READ WRITE

Memória puffer
Naplókezelő
(Puffer kezelő)
FLUSH-LOG
OUTPUT

INPUT
Adatbázis
Tranzakciós napló

8.16. ábra: Adat párhuzamos írása és olvasása

A tranzakciók futásuk során elsősorban a memória pufferben dolgoznak, ahonnan adatot olvasnak ki a
saját memória terükbe, illetve írnak oda vissza. A pufferbe – amennyiben még nem szerepel benne – a
felhasznált adatot természetesen be kell tölteni a háttértárról. A tranzakciók által elvégzett
műveletekről a naplókezelő bejegyzéseket készít, amelyet szintén a memória pufferbe ír. Mivel a
másodlagos tároló használata időigényes, ezért a pufferben elvégzett változtatásokat a perzisztens
tárra nem azonnal, hanem ütemezetten írják ki. A tranzakciós napló perzisztálása szintén nem minden
egyes bejegyzés után, hanem kötegelve történik meg.

Azért, hogy a különböző naplózási stratégiákat ismertetni tudjuk, szükségünk van egy
jelölésrendszerre, amellyel le tudjuk írni a tranzakciók működése során elvégzett adatmozgató
műveleteket:

 INPUT(X): az X adatot tartalmazó blokk beolvasása a másodlagos tárról a pufferbe


 OUTPUT(X): az X adatot tartalmazó blokk kiírása a pufferből a lemezre
 READ(X, t): az X adat átmásolása a memória pufferből a tranzakció saját t lokális
változójába. Ha X nem található a pufferben, akkor egy INPUT(X) művelet is végrehajtódik
előtte.
 WRITE(X, t): a pufferben található X adat felülírása a t lokális változó tartalmával. Ha X nem
található a pufferben, akkor egy INPUT(X) művelet is végrehajtódik előtte.
 FLUSH-LOG: a tranzakciós napló kiírása a pufferből a lemezre

A fenti műveletek feltételezik, hogy minden egyes adatelem elfér legfeljebb egy blokkon. Amennyiben
egy adat több blokknyi helyet foglal el, akkor minden egyes blokkot egy önálló adatelemnek kell
elképzelni, amelyeknek az atomi írásáról / olvasásáról a bemutatott naplózási stratégiák fognak
gondoskodni.

A továbbiakban három naplózási stratégiát ismertetünk részletesen (undo, redo, undo/redo), valamint
bemutatjuk, hogy a napló segítségével hogyan tudjuk az utolsó konzisztens állapotot helyreállítani.

167
8.3.1 Semmisségi (undo) naplózás

Az első ismertetett stratégia az semmisségi (undo) naplózás, amely a bejegyzések segítségével azt
teszi lehetővé, hogy a konzisztens állapotot az utolsó adatbázis műveletek visszagörgetésével állítsuk
helyre. A tranzakciós naplóba a következő bejegyzések kerülnek:

 <BEGIN T>: A T tranzakció kezdete


 <COMMIT T>: A T tranzakció sikeresen befejeződött. A tranzakció által elvégzett
módosításoknak végre kell hajtódnia a másodlagos táron is. A COMMIT jel megjelenése a
naplóban nem feltétlen jelenti azt, hogy a változtatott adatokat már ki is írta a pufferkezelő a
lemezre.
 <ABORT T>: A T tranzakció nem tudott sikeresen befejeződni, a pufferben elvégzett
változtatásokat nem lehet kiírni a lemezre, vagy ha már bizonyos adatok kiírásra kerültek,
akkor a korábbi naplóbejegyzések segítségével vissza kell azokat vonni.
 <T,X,v>: speciális, a semmisségi naplózásnál használt bejegyzés, amely azt rögzíti, hogy a T
tranzakció az X adat értékét v-ről megváltoztatta. Az ilyen típusú bejegyzések a WRITE
műveletekkel együtt jönnek létre, és a módosított adat korábbi értékét naplózzák.

A fent specifikált napló bejegyzések segítségével rendszerhiba esetén helyreállítható az utolsó


konzisztens állapot, ha a következő két feltétel teljesül:

1. Egy <T,X,v> naplóbejegyzéseknek még azelőtt a lemezre kell kerülnie, hogy X új értékét a
lemezre írnánk.
2. A COMMIT jelet csak akkor szabad kiírni a naplóba (akkor viszont minél előbb), ha a tranzakció
által elvégzett összes korábbi változtatást kiírtuk a lemezre.

Összefoglalva tehát, a napló bejegyzéseket mindig a módosítás előtt kell a lemezre írni, a COMMIT jelet
pedig minden más után.

A naplóbejegyzéseket a pufferkezelő ütemezetten írja ki a lemezre, de amint látjuk, bizonyos pontokon


biztosnak kell lennünk benne, hogy a bejegyzés még az elvégzett adatbázis művelet előtt a lemezre
kerül. Ezt a naplókezelő a FLUSH-LOG utasítás segítségével tudja kezdeményezni a puffer kezelőnél,
aminek hatására az összes, még nem perzisztált bejegyzés a lemezre kerül.

A következő példában a T1 tranzakció az A=10 és B=20 adatelemeken végez műveletet: A értékét 2-


vel csökkenti, B értékét pedig 2-vel növeli. A 8.17. ábrán a tranzakció által elvégzett műveletek és a
kapcsolódó napló bejegyzések időbeni egymásutániságát figyelhetjük meg.

168
Adatbázis Puffer
Művelet Napló
A B A B
1 BEGIN T1 10 20 - - <BEGIN T1>
2 INPUT(A) 10 20 10 -
3 READ(A,u) 10 20 10 -
4 WRITE(A,u) 10 20 8 - <T1,A,10>
5 INPUT(B) 10 20 8 20
6 READ(B,u) 10 20 8 20
7 WRITE(B,u) 10 20 8 22 <T1,B,20>
8 FLUSH-LOG 10 20 8 22
9 OUTPUT(A) 8 20 8 22
10 OUTPUT(B) 8 22 8 22
11 <COMMIT T1>
12 FLUSH-LOG
8.17. ábra: Semmisségi naplózás

A táblázat első sorában a tranzakció kezdetét jelöljük, aminek hatására a <BEGIN T1> bejegyzés kerül
a naplóba. A 2. lépésben az A adatot beolvassuk a másodlagos tárból a pufferbe, a 3. lépésben a
pufferből átmásoljuk azt a tranzakció saját u változójába. Ezután a tranzakció elvégzi az adaton a
változtatást, és visszaírja az eredményt a pufferbe (4. lépés). Ezzel egy időben a naplóban egy
<T1,A,10> bejegyzés keletkezik. Az 5-7 lépések a B adaton végzik el ugyanezeket a műveleteket. A 7.
lépéssel a tranzakció sikeresen befejeződött, a felhasználó kiadta a COMMIT utasítást, aminek hatására
– a semmisségi naplózási szabályoknak megfelelően:

1. egy FLUSH-LOG művelet biztosítja, hogy az eddigi naplóbejegyzések a lemezre kerüljenek (8.
lépés),
2. a változtatott adatok kiírásra kerülnek a lemezre (9-10. lépés),
3. végül a COMMIT jel bekerül a naplóba (11. lépés), amit szintén kiírunk a lemezre (12. lépés).

Ha a COMMIT jelet nem írnánk ki azonnal a lemezre, akkor – bár a felhasználó úgy látná, hogy a
tranzakció sikeresen lezárult – egy rendszerleállás esetén a tranzakció hatása nem feltétlen lenne
megőrizhető. Ha a 11. és 12. lépés között történik a hiba, akkor a tranzakciót kénytelenek lennénk
visszagörgetni, mivel a napló alapján nem lenne megállapítható, hogy a tranzakció sikeresen
befejeződött-e vagy sem.

A semmisségi naplózás során számos kötöttséget kell figyelembe venni: az adatbázist csak a
kapcsolódó napló bejegyzések perzisztálása után módosíthatjuk, a COMMIT jel csak az adatbázis
módosítása után kerülhet ki a naplóba, illetve miután a felhasználó kiadta a COMMIT utasítást, a
tranzakció nem fejeződik be addig, ameddig valamennyi változtatás és a napló is a lemezre nem került.

8.3.1.1 Helyreállítás hiba esetén


Rendszerhiba esetén a napló alapján egyszerűen visszatérhetünk az utolsó konzisztens állapothoz: a
naplót visszafelé bejárva, ha olyan <T,X,v> műveletet találunk, amelyhez tartozó T tranzakcióhoz
nem szerepelt COMMIT bejegyzés a naplóban, akkor az X adat értékét vissza kell állítanunk v-re, mivel
a tranzakcióról nem tudjuk, hogy sikeresen befejeződött-e. Mivel az adatok korábbi állapotát tükröző
bejegyzések mindig a változtatás előtt történnek, és a változtatás előtt a lemezre is kerülnek, ezért a
helyreállítás mindig megoldható. Ha egy tranzakcióhoz tartozik naplózott COMMIT bejegyzés, az azt
jelenti, hogy a tranzakció sikeresen befejeződött, és valamennyi változtatása a lemezre íródott, tehát
a műveleteit nem kell visszagörgetni.

169
Például, ha a 8.17. ábrán látható tranzakció közvetlenül a 7-es művelet után egy rendszerhiba miatt
megszakad, akkor a tranzakcióhoz tartozó bejegyzések nem kerülnek perzisztálásra, de ez a
helyreállítás során nem okoz problémát, hiszen az adat változtatásokat a lemezen még úgysem
végeztük el. Ha a tranzakció a 9-es művelet után szakad meg, akkor az A és B adatok eredeti értékét
tükröző bejegyzések már a lemezre kerültek, így helyreállítás során először B majd A értékét állítjuk
helyre a tranzakció előtti állapotra. Vegyük észre, hogy mivel B új értéke még nem perzisztálódott,
ezért a helyreállítása nem okoz tényleges változást.

8.3.1.2 Ellenőrzőpontok
Amint láttuk, a semmisségi napló visszafelé történő bejárásával a megszakadt tranzakciók változtatásai
visszagörgethetők. Problémát jelent ugyanakkor, hogy a napló olvasása közben nem tudhatjuk
biztosan, hogy van-e még olyan művelet a napló korábbi részeiben, amelyek félbeszakadt
tranzakciókhoz tartoznak. Ezért kénytelenek lennénk a teljes naplót egészen az elejéig ellenőrizni,
amely nagy naplók esetén rendkívül időigényes lenne.

A problémára megoldást az ellenőrző pontok (checkpoint) bevezetése jelent. Ennek egy egyszerű
megvalósítása a következő lépésekből áll:

1. Ütemezett időpontokban felfüggesztjük további tranzakciók indítását.


2. Megvárjuk, amíg valamennyi futó tranzakció befejeződik.
3. Kiírjuk a napló függő részeit a lemezre.
4. Kiírunk egy <CKPT> bejegyzést a naplóba, majd a naplót ismét a lemezre írjuk.
5. Engedélyezzük új tranzakciók indítását.

Mivel a <CKPT> bejegyzések előtt az összes futó tranzakció befejeződött, ezért helyreállítás során
elegendő csupán a (hátulról) legelső ellenőrzőpontig feldolgozni a naplóbejegyzéseket, mivel előtte
már garantáltan nem található félbeszakadt tranzakcióhoz tartozó bejegyzés.

A megoldás biztonságos, de sajnos lassú: az ellenőrzőpont elkészítésének idejére az adatbázis-kezelő


rendszer működését fel kell függeszteni, és meg kell várni, amíg a futó tranzakciók befejeződnek, ami
akár sokáig is eltarthat. A probléma az ún. működés közbeni ellenőrzőpontok (nonquiescent
checkpoint) használatával küszöbölhető ki, amely megengedi új tranzakciók indítását az ellenőrzőpont
készítésének idejére. Megvalósítása a következő lépésekből áll:

1. Ütemezett időpontokban készítünk egy <START CKPT(Tm,…,Tn)> naplóbejegyzést, és a


naplót kiírjuk a lemezre. A Tm,…,Tn azonosítók az éppen futó tranzakciókat azonosítják.
2. Megvárjuk, amíg a Tm,…,Tn tranzakciók befejeződnek, közben nem tiltjuk meg új tranzakciók
indítását.
3. Kiírjuk a naplóba az <END CKPT> bejegyzést, és a naplót perzisztáljuk.

Rendszerhiba esetén a helyreállításhoz a következő stratégiát kell követnünk:

 Ha a naplót visszafelé bejárva <END CKPT> bejegyzést találunk, akkor biztosan tudjuk, hogy a
hozzá tartozó <START CKPT(Tm,…,Tn)> bejegyzésben azonosított tranzakciók már mind
befejeződtek, ezért elég a Tn utáni tranzakciókat ellenőrizni.
 Ha a naplót visszafelé bejárva <START CKPT(Tm,…,Tn)> bejegyzést találunk, még az <END
CKPT> előtt, akkor a rendszerhiba valamikor az ellenőrzőpont készítése közben történt. A

170
<START CKPT(Tm,…,Tn)> bejegyzésből tudjuk, hogy ebben a pillanatban a Tm előtti
tranzakciók már mind lezárultak, ezért ekkor elég csupán a Tm és azt követő tranzakciókat
ellenőrizni.

A 8.18. ábrán látható napló szerint a rendszerhiba épp egy ellenőrzőpont készítése közben történt.
Helyreállítás során a naplóban visszafelé haladva először (9.sor) konstatáljuk, hogy a T6 tranzakció
kommitált, ezért az ő műveleteit nem kell visszagörgetni. A 8-as és 7-es sorban nem befejezett
tranzakcióhoz tartozó műveleteket találunk, ezért E-t visszaállítjuk 15-re, C-t pedig 10-re. Az 5. sorban
megtaláljuk a <START CKPT(T5,T6)> bejegyzést, tehát tudjuk, hogy már csak a T5 tranzakció
kezdetéig kell visszatekintenünk a naplóban. Mivel T6 sikeresen befejeződött, ezért a 4. sorban
található bejegyzéshez kapcsolódóan nincs teendőnk, viszont a 2. sornak megfelelően A értékét vissza
kell állítanunk 10-re. Az 1-es sorban megtaláljuk a T5 tranzakció kezdetét, amivel a helyreállítást
leállíthatjuk.

... ...
1 <BEGIN T5>
2 <T5,A,10>
3 <BEGIN T6>
4 <T6,B,20>
5 <START CKPT(T5,T6)>
6 <BEGIN T7>
7 <T7,C,10>
8 <T5,E,15>
9 <COMMIT T6>
8.18. ábra: Helyreállítás ellenőrzőpont készítése közben

Mivel helyreállítás során az utolsó <START CKPT(Tm,…,Tn)> bejegyzésben szereplő Tm


tranzakciónál korábbra soha nem kell tekinteni, ezért a <START Tm> bejegyzés előtti napló részek
törölhetők is.

8.3.2 Helyrehozó (redo) naplózás

A semmisségi naplózás egyik nagy hátránya, hogy a tranzakció addig nem kommitálható, amíg az összes
változtatást ki nem írtuk a lemezre. Előnyösebb lenne, ha a változtatásokat egy ideig még a
memóriában tarthatnánk, ameddig a lemezvezérlő fel nem szabadul, és az adatok perzisztálását
elvégezhetjük. Ezt csak akkor tehetjük meg, ha a naplóbejegyzések alapján a változtatásokat egy
rendszerhiba esetén is újra el tudjuk végezni. A helyrehozó naplózás (redo) a következő pontokon
különbözik a semmisségitől:

 A <T,X,v> naplóbejegyzésekben v X új értékét jelöli (nem a megelőzőt).


 A COMMIT jelet az adatbázis írás előtt kell a naplóba írni (nem pedig utána, mint a
semmisségi naplózásnál).

Az továbbra is igaz, hogy az adatok perzisztálása csak a tranzakciós napló lemezre mentése után
kezdődhet el.

8.19. ábrán egy lehetséges helyrehozó napló részletet láthatunk. A 4-es és 5-ös sorban tehát A és B új
értékét naplózzuk. A felhasználó a COMMIT utasítást a 7-es lépés után adta ki, aminek hatására a
naplóba kerül a <COMMIT T1> bejegyzés, és a naplót kiírjuk a lemezre (8-9 lépés). Mivel a T1

171
tranzakció által elvégzett műveletek hatását naplóztuk, ezért elkezdődhet a változtatások lemezre
mentése (10-11. lépések).

Adatbázis Puffer
Művelet Napló
A B A B
1 BEGIN T1 10 20 - - <BEGIN T1>
2 INPUT(A) 10 20 10 -
3 READ(A,u) 10 20 10 -
4 WRITE(A,u) 10 20 8 - <T1,A,8>
5 INPUT(B) 10 20 8 20
6 READ(B,u) 10 20 8 20
7 WRITE(B,u) 10 20 8 22 <T1,B,22>
8 <COMMIT T1>
9 FLUSH-LOG 10 20 8 22
10 OUTPUT(A) 8 20 8 22
11 OUTPUT(B) 8 22 8 22
8.19. ábra: Helyrehozó naplózás

Amint látható, a kommitálási folyamat a felhasználó szempontjából jóval rövidebb: csupán egy
<COMMIT> bejegyzést kell készíteni a naplóban, és a naplót ki kell írni a lemezre. Az adatok tényleges
megváltoztatása később, egy megfelelő időpontban történik meg.

8.3.2.1 Helyreállítás hiba esetén


Egy rendszerhiba esetén a naplót elölről hátrafelé kell feldolgozni. Azon tranzakciók, amelyekhez nem
található <COMMIT> bejegyzés a naplóban, biztosan nem végeztek el semmilyen változtatást a
lemezen, tehát nem igényelnek további figyelmet. A kommitált tranzakcióknál viszont nem tudhatjuk
biztosan, hogy a változtatásokat sikerült-e még a rendszerhiba előtt kiírni a lemezre vagy sem, ezért a
kapcsolódó naplóbejegyzések alapján a változtatásokat (újra) perzisztálni kell. A félbeszakadt
tranzakciókhoz tartozó bejegyzéseket eltávolíthatjuk a naplóból, vagy egy <ABORT …> bejegyzéssel
lezárhatjuk őket.

8.3.2.2 Ellenőrzőpontok
Mivel a helyreállító naplózásnál egy adott pillanatban már sikeresen befejeződött tranzakciók hatása
még nem feltétlenül jelent meg a lemezen tárolt adatbázisban is, ezért az ellenőrzőpontok
létrehozásánál nem csak a még aktív tranzakciókat kell figyelembe vennünk, hanem még a nem
perzisztált módosításokkal rendelkező tranzakciókat is. Azt, hogy a pufferben tárolt mely adatok nem
kerültek még a lemezre, és azokat melyik tranzakció módosította, a pufferkezelőnek kell
nyilvántartania. Helyreállító naplózás esetén az ellenőrzőpontokat a következő logika szerint kell
létrehozni:

1. Készítünk egy <START CKPT(Tm,…,Tn)> naplóbejegyzést, és a naplót kiírjuk a lemezre. A


Tm,…,Tn azonosítók az éppen futó tranzakciókat azonosítják.
2. Kiírjuk a lemezre az összes a pufferben lévő, még nem perzisztált, de kommitált tranzakcióhoz
tartozó adatot.
3. A naplóba írjuk az <END CKPT> bejegyzést, és a naplót perzisztáljuk.

Az <END CKPT> bejegyzésnél tehát azt tudjuk biztosan, hogy a Tm előtti tranzakciók valamennyi
változtatása már a lemezre kerül. Helyreállítás során – amikor a naplót az elejétől kezdve olvassuk – ha

172
az utolsó <END CKPT> bejegyzés után már nem szerepel újabb <START CKPT…> bejegyzés, akkor elég
a Tm és az azt követő tranzakciók változtatásait újra a lemezre írni. Ezzel szemben, ha az utolsó <START
CKPT(Tm,…,Tn)> bejegyzés után már nem szerepel <END CKPT>, akkor nem lehetünk benne
biztosak, hogy a Tm előtti kommitált tranzakciók módosításait már a lemezre írtuk. Ezért meg kell
keresni a megelőző <END CKPT> és a kapcsolódó <START CKPT(Ti,…,Tj)> bejegyzéseket, és a Ti
tranzakciótól kezdődően kell a változtatásokat újra perzisztálni.

8.3.3 Semmisségi / helyrehozó (undo/redo) naplózás

A helyreállító naplózásnál a kommitálási folyamat a semmisségihez képest jóval kevesebb


szinkronizációt igényel, és hamarabb végrehajtható, de a COMMIT jel helye még mindig kötött (az
adatbázis írás előtt kell a naplóba írni). Továbbá a helyreállítás során a semmisségi naplózáshoz képest
a napló nagyobb részét kellhet elemezni. A semmisségi / helyrehozó naplózás (undo/redo) a két
megközelítés előnyeit ötvözi. A módszer csupán az alábbi követelmény teljesülését várja el:

 Egy X módosított adat perzisztálása előtt a lemezen meg kell jelennie a <T,X,u,v>
naplóbejegyzésnek, amely azt jelöli, hogy a T tranzakció az X adatot u-ról v-re megváltoztatta.

Mivel az adatelemek megelőző és következő értéke is rendelkezésre áll az adat perzisztálása előtt, a
COMMIT jel az adatok lemezre írása előtt, közben és után is megjelenhet a naplóban. Az előző két
ismertetett naplózási stratégiához képest ez a módszer igényli a folyamatok közti legkevesebb
szinkronizációt: az adatelemet még a tranzakció vége előtt átírhatjuk az adatbázisban. Félbeszakadt
vagy visszagörgetett tranzakció esetén természetesen ez többletmunkát igényelhet, de általánosan
elmondható, hogy több a sikeres, mint a sikertelen tranzakció, ezért az előnyösebb pufferkezelés
(előbb felszabadítható az adatelemek számára lefoglalt memória) és kisebb szinkronizációs igény ezt
bőven kárpótolja.

8.20. ábrán egy olyan forgatókönyvet láthatunk, amely során a COMMIT jel a két adatbázis írás között
kerül a naplóba. Figyeljük meg, hogy a változtatások perzisztálása előtt a kapcsolódó napló
bejegyzéseket a lemezre írtuk (8-as lépés).

173
Adatbázis Puffer
Művelet Napló
A B A B
1 BEGIN T1 10 20 - - <BEGIN T1>
2 INPUT(A) 10 20 10 -
3 READ(A,u) 10 20 10 -
4 WRITE(A,u) 10 20 8 - <T1,A,10,8>
5 INPUT(B) 10 20 8 20
6 READ(B,u) 10 20 8 20
7 WRITE(B,u) 10 20 8 22 <T1,B,20,22>
8 FLUSH-LOG 10 20 8 22
9 OUTPUT(A) 8 20 8 22
10 <COMMIT T1>
11 FLUSH-LOG 8 20 8 22
12 OUTPUT(B) 8 22 8 22
8.20. ábra: Semmisségi / helyrehozó naplózás

8.3.3.1 Helyreállítás hiba esetén


Mivel a COMMIT jel helye nem kötött abban az értelemben, hogy a lemez módosítás előtt vagy után
kell a naplóba kerülnie, ezért a félbeszakadt és a kommitált tranzakcióknál is lehetséges, hogy semmi
/ csak bizonyos adatok / már az összes módosított adat a lemezre került. Ezért helyreállítás során a
semmisségi és helyrehozó naplózásnál megismert módszereket kell ötvözni: a kommitált
tranzakcióknál az adatelemek értékét a naplóban szereplő bejegyzéseknek megfelelő új értékekre kell
beállítani, a félbeszakadt tranzakciókat pedig a megelőző értékre.

8.3.3.2 Ellenőrző pontok


A semmisségi / helyrehozó naplózás során az ellenőrző pontok kezelése leegyszerűsödik:

1. Készítünk egy <START CKPT(Tm,…,Tn)> naplóbejegyzést, és a naplót kiírjuk a lemezre. A


Tm,…,Tn azonosítók az éppen futó tranzakciókat azonosítják.
2. Kiírjuk a lemezre a pufferben lévő összes a pufferben tárolt, még nem perzisztált adatot (tehát
nem csak a kommitált tranzakciókhoz tartozókat). Mivel az 1. lépésben az eddigi naplót már a
lemezre írtuk, ezért a pufferben található valamennyi módosított adathoz tartozó, a
módosítást rögzítő naplóbejegyzés is előzetesen a lemezre került, tehát a naplózási stratégia
szabályai teljesülnek.
3. A naplóba írjuk az <END CKPT> bejegyzést, és a naplót perzisztáljuk.

Az <END CKPT> bejegyzés a naplóban tehát azt fejezi ki, hogy a kapcsolódó <START
CKPT(Tm,…,Tn)> bejegyzés Tm tranzakciója előtti tranzakciók változtatásai a lemezre kerültek, a Tm
és azt követő tranzakciók változtatásai pedig részben. Tehát a Tm és az utáni kommitált tranzakciókat
újra végre kell hajtani a lemezen, a félbeszakadt tranzakciókat változtatásait pedig vissza kell állítani az
eredeti értékre.

174

You might also like