Adatbázisok

Adatok keletkeznek, és ezeket tárolni szeretnénk. Ehhez számos rendszer és módszer alakult ki, melyekből most megnézünk párat. Először áttekintjük a legfontosabb alap adatbázis kezelő rendszereket, majd megismerkedünk az SQL lekérdező nyelvvel, utána annak gyártóspecifikus kiegészítőivel, végül a hagyományos relációs adatbázis rendszerek továbbgondolásaival.

Adatbázis rendszerek

Nehézsúlyú adatbázisok

Ezen adatbázisok néhány közös jellemzője:

  • Nagy, gigabájtokban mérhető teleptő és tíz gigabájtokban mérhető szükséges tárterülettel.
  • Sokféle beállítási lehetőség. Tehát ezek nem azok a rendszerek, hogy letöltöm a telepítőt, az alapértelmezett beállításokkal vakon feltelepítem, és máris működik.
  • Komoly erőforrás igények. Egy 5 éves házi laptopra feltenni a legfrissebb változatot alapból felejtős.
  • Komoly szakismeret igénnyel. Több napos, drága tanfolyamokat szerveznek, tehát nem olyan "fél óra alatt átfutom a dokumentációt, és tudom a tudnivaló 95%-át" rendszerek
  • A fizetős változatok igen borsos árúak, amelyek esetenként még az óriás cégek költségvetését is megterhelik.

Oracle

Amikor adatbázisra gondolunk, akkor gyakran ez jut eszünkbe; fogalmazhatunk úgy is, hogy ami a telefonoknál az iPhone, az az adatbázisoknál az Oracle. Ill. fordítva: noha az Oracle cégnek számos egyéb terméke van, mégis, magát a céget az adatbázis rendszerével azonosítjuk.

TODO: folytatni

Microsoft SQL

Ez egy másik nehézsúlyú szereplő az adatbázisok piacán. Az Oracle-lel szemben akár még nyerhet is, mivel egyrészt ennek vannak figyelemre méltó továbbgondolásai (pl. analysis services, data warehouse), valamint az, hogy igen jól integrálódik az Microsoft Azure felhő szolgáltatásába. Ugyanakkor sajnos vannak vele problémák, melyekhez hozzá kell szokni. Ilyen pl. az, hogy nemcsak normál SQL autentikációval lehet bejelentkezni hanem Windows autentikációval is; egészen pontosan kétféle lehetőség van: vagy csak WIndows autentikáció, vagy Windows autentikáció és SQL egyszerre. Az alapértelmezett viszont a csak Windows autentikáció; erre figyelni kell.

TODO: folytatni

Középsúlyú adatbázisok

Ezek azok az adatbázisok, amelyek ingyenesek (legalábbis házi használatra ill. egyszerűbb célokra mindenképp), a méretük közepes (mondjuk 100 MB nagyságrendű), telepítésük általában egyszerű (a telepítő letöltését és alapértelmezett feltelepítését követően nagyjából úgy fut, ahogy azt szeretnénk), jól átláthatóak (ide nem kell drága tanfolyam, hogy belerázódjunk), erőforrás igényük nem túl nagy (nyugodtan feltelepíthetjük az 5 éves laptopunkra is).

MySQL

Ha nincs jó okom rá, akkor én ezt használom.

Telepítés és beállítások

Töltsük le a telepítőt a https://www.mysql.com/ oldalról, közvetlenül innen: https://dev.mysql.com/downloads/installer/. Kétféle letöltési mód között választhatunk: web és normál; a webes azt jelenti, hogy egy kisebb méretű (kb. 20 MB) telepítőt kapunk, és a telepítés során tölti le a többit, a másik pedig egy komplett, de sokkal nagyobb (kb. 400 MB) telepítő. Én a 8.0.15-ös 64 bites Community verziót használom. Letöltés után telepítsük fel a programot a szokásos módon. Kiválaszthatjuk, hogy hova telepítse, kérdni fogja a rendszer adminisztrátori jelszót, és beállíthatjuk a portot is, amit célszerűen hagyjuk 3306-on. Windows alatt a telepítés során beregisztrálja magát Windows szolgáltatásként (Windows Service), ami alapból elindul, ld. Services alkalmazást.

Parancssor

A MySQL parancssori programja ez: MySQL 8.0 Command Line Client. Indításkor kéri a root jelszót, amit a telepítéskor állítottunk be. A parancsokat pontosvesszővel kell lezárni. A kis- és nagybetűt nem különbözteti meg; az SQL parancsokat szokás csupa nagybetűvel írni. Példák:

  • SHOW DATABASES; - adatbázisok kilistázása.
  • USE information_schema; - adatbázis kiválasztása.
  • SHOW TABLES; - kilistázza az imént kiválasztott adatbázis tábláit.
  • SELECT * FROM KEYWORDS; - egy adattábla összes elemének a lekérdezése.

Segítséget a \h paranccsal kérhetünk. Kilépés: quit.

Grafikus felhasználói felület

A MySQL grafikus felülete a MySQL Workbench, jelen esetben a 8.0 CE verzió. Ezzel rá tudunk kapcsolódni a helyi és a távoli adatbázisra. Adatbázisokat hozhatunk létre ill. menedzselhetünk. A már létrehozott adatbázisokat bal oldalon a Navigator alatt a Schemas fülre kattintva érhetjük el

Példa adatbázis létrehozása adattáblákkal

Grafikus felületen és parancssorból is tudunk adatbázist létrehozni ill. azt kezelni. A grafikus felület valójában legenerálja a létrehozó SQL utasításokat, és azokat hajtja végre, így a kettő lényegében ekvivalens.

Új adatbázist a negyedik ikonra kattintva tudunk létrehozni az adott kapcsolatnál: Create a new schema in the connected server. Ez egy CREATE SCHEMA parancsot generál, ami egyenértékű a következővel (szerintem nyilvánvalóbbal): CREATE DATABASE. Példa:

CREATE DATABASE testdb;
USE testdb;

A grafikus felületen a következőképpen tudunk adattáblákat létrehozni: Navigator → Schemas → adatbázisnév → jobb kattintás ezen: Tables → Create table… Ezt vagy ott vagy akár parancssorban is ki tudjuk adni. (Megjegyzés: a MySQL alapértelmezett beállítása az, hogy a táblanevek kisbetűsek.) A lenti példában létrehozunk két táblát: az egyik címeket, a másik személyeket tartalmaz. Mindegyik személynek egy lakcíme lehet, viszont ugyanaz a lakcíme több személynek is lehet.

CREATE TABLE address (
    id INT NOT NULL AUTO_INCREMENT,
    country VARCHAR(100),
    town VARCHAR(100),
    street VARCHAR(200),
    PRIMARY KEY (id)
);

CREATE TABLE person (
    id INT NOT NULL AUTO_INCREMENT,
    name VARCHAR(200),
    age INT,
    addressid INT,
    PRIMARY KEY (id),
    CONSTRAINT addressid FOREIGN KEY (addressid) REFERENCES address(id)
);

Adjunk hozzá néhány adatot, először címeket:

INSERT INTO address(country, town, street) VALUES("Hungary", "Budapest", "Pipacs utca 1");
INSERT INTO address(country, town, street) VALUES("Hungary", "Szeged", "Tavasz utca 2");

Kérdezzük le a beszúrt adatokat:

mysql> SELECT * FROM address;
+----+---------+----------+---------------+
| id | country | town     | street        |
+----+---------+----------+---------------+
|  1 | Hungary | Budapest | Pipacs utca 1 |
|  2 | Hungary | Szeged   | Tavasz utca 2 |
+----+---------+----------+---------------+
2 rows in set (0.00 sec)

Látható, hogy az azonosító automatikusan növekszik. Adjunk hozzá személyeket is:

INSERT INTO person(name, age, addressid) VALUES("Sanyi", 34, 1);
INSERT INTO person(name, age, addressid) VALUES("Kata", 32, 1);
INSERT INTO person(name, age, addressid) VALUES("Pista", 46, 2);

Lekérdezés:

mysql> SELECT * FROM person;
+----+-------+------+-----------+
| id | name  | age  | addressid |
+----+-------+------+-----------+
|  1 | Sanyi |   34 |         1 |
|  2 | Kata  |   32 |         1 |
|  3 | Pista |   46 |         2 |
+----+-------+------+-----------+
3 rows in set (0.00 sec)

Lekérdezések

Kérdezzük le csak a nevet és az életkort!

mysql> SELECT name, age FROM person;
+-------+------+
| name  | age  |
+-------+------+
| Sanyi |   34 |
| Kata  |   32 |
| Pista |   46 |
+-------+------+
3 rows in set (0.00 sec)

Listázzuk ki az összes budapesti lakost!

mysql> SELECT person.name FROM person JOIN address ON person.addressid = address.id WHERE address.town = 'Budapest';
+-------+
| name  |
+-------+
| Sanyi |
| Kata  |
+-------+
2 rows in set (0.00 sec)

Határozzuk meg, hogy melyik városban hányan laknak!

mysql> SELECT address.town, COUNT(*) as citizencount FROM person JOIN address ON person.addressid = address.id GROUP BY address.town;
+----------+--------------+
| town     | citizencount |
+----------+--------------+
| Budapest |            2 |
| Szeged   |            1 |
+----------+--------------+
2 rows in set (0.00 sec)

Felhasználó létrehozása

Felhasználókat többféleképpen tudunk azonosítani, a klasszikus megoldás az azonosító-jelszó páros. A MySQL 8-tól kezdve az alapértelmezett jelszó a caching_sha2_password, nekünk viszont mysql_native_password kell. Ha azt szeretnénk, hogy a frissen létrehozott felhasználó hozzáférjen a fenti adatbázishoz, azt engedélyezni kell. Parancssorból a következő parancsokkal ezt végrehajtani:

CREATE USER 'csaba'@'localhost' IDENTIFIED WITH mysql_native_password BY 'farago';
GRANT ALL PRIVILEGES ON testdb.* TO 'csaba'@'localhost';
FLUSH PRIVILEGES;

Itt tehát létrehoztunk egy SQL felhasználót csaba azonosítóval és farago jelszóval, aki megkapta a testdb minden jogosultságát. Grafikus felületen a Navigator → Adminisztration → Users and Privileges oldalon tudjuk kezelni a felhasználókat.

MariaDB

Amikor a MySQL-t felvásárolta az Oracle, sokan féltek, hogy az ingyenes konkurenciát egyszerűen megszünteti, vagy legalábbis olyan irányba változtatja, hogy elveszítse vonzerejét. Ők az eredeti kódbázist felhasználva fejlesztették tovább a MySQL-t, MariaDB néven. (Azt gondolom egyébként, hogy a MySQL ugyanúgy használható továbbra is mint korábban, de a MariaDB is megfelelő.)

TODO: telepítés

PostgreSQL

TODO: telepítés

Pehelysúlyú adatbázisok

Ezekre jellemző a minimális méret (pár megabájt), az egyszerű telepíthetőség, valamint lehetőség arra, hogy az adatok pusztán memóriában létezzenek. Ez utóbbi miatt nagyon jól használhatóak oktatási céllal, a különböző adatbázis műveletek kipróbálására, lényegében anélkül, hogy adatbázist kelljen telepíteni.

H2

TODO: telepítés

Derby

TODO: telepítés

HSQLDB

TODO: telepítés

SQL

Az SQL a Structured Query Language (strukturált lekérdezőnyelv) rövidítése, amelyet a relációs adatbáziskezelők lekérdezésére (és módosítására) használnak. Kiejtése magyarul es-kú-el, angolul esz-kjú-el, de gyakran előfordul a szíkvel is.

Adatbázis szintű SQL parancsok

Adatbázis létrehozása és törlése

CREATE DATABASE testDB;
DROP DATABASE testDB;

Tábla létrehozása, módosítása, törlése

CREATE TABLE Persons (
    PersonID int,
    LastName varchar(255),
    FirstName varchar(255),
    Address varchar(255),
    City varchar(255) 
);
DROP TABLE Shippers;
TRUNCATE TABLE Shippers;
ALTER TABLE table_name ADD COLUMN column_name datatype;
ALTER TABLE table_name MODIFY COLUMN column_name datatype;
ALTER TABLE table_name DROP COLUMN column_name;

Lekérdező SQL parancsok

SELECT

SELECT * FROM Customers;
SELECT CustomerName, City FROM Customers;
SELECT DISTINCT Country FROM Customers;
SELECT CustomerID as ID, CustomerName AS Customer FROM Customers;
SELECT CustomerName, Address + ', ' + PostalCode + ' ' + City + ', ' + Country AS Address FROM Customers;

WHERE

SELECT * FROM Customers WHERE Country='Mexico';

WHERE feltétel példák:

- columnName = value
- columnName LIKE 'pattern%'
- columnName IS NULL
- columnName IN ('listElement1', 'listElement2')
- columnName1 IN (SELECT columnName2 FROM table2)
- columnName BETWEEN x AND y
- EXISTS (SELECT columnName FROM table WHERE condition)
- columnName1 = ANY (SELECT columnName2 FROM table2 WHERE condition2)
- columnName1 = ALL (SELECT columnName2 FROM table2 WHERE condition2)
- condition1 AND condition2
- condition1 OR condition2
- NOT condition

TOP

SELECT TOP 3 * FROM Customers;
SELECT * FROM Customers LIMIT 3;
SELECT TOP 50 PERCENT * FROM Customers;

UNION

SELECT City FROM Customers UNION SELECT City FROM Suppliers;
SELECT City FROM Customers UNION ALL SELECT City FROM Suppliers;

Matematikai függvények

SELECT MIN(Price) AS SmallestPrice FROM Products;
SELECT MAX(Price) AS LargestPrice FROM Products;
SELECT COUNT(ProductID) FROM Products;
SELECT AVG(Price) FROM Products;
SELECT SUM(Quantity) FROM OrderDetails;

ORDER BY

SELECT * FROM Customers ORDER BY Country;
SELECT * FROM Customers ORDER BY Country ASC, CustomerName DESC;

GROUP BY, HAVING

SELECT COUNT(CustomerID), Country FROM Customers GROUP BY Country;
SELECT COUNT(CustomerID), Country FROM Customers GROUP BY Country HAVING COUNT(CustomerID) > 5;

Illesztés (join)

Az illesztéssel két adattáblából új, virtuális adattáblát tudunk létrehozni. Példaként vegyünk egy általános esetet: vannak vásárlók, áruk és vásárlások. Egy várásló tetszőleges számú árufajtát vásárolhat, és egy árufajtából tetszőleges számú vásárló vásárolhat. A példában legyen három vásárló: Sanyi, Juli és Peti, valamint három áru: alma, banán és csoki. Sanyi almát és csokit vesz, Juli csak csokit, Peti semmit. A következő módon lehet adattáblákat illeszteni:

  • JOIN, INNER JOIN: csak azokat az elemeket adja vissza, amelyek minkét táblában benne vannak A példában: Sanyi - alma, Sanyi - csoki, Juli - csoki.
  • LEFT JOIN, LEFT OUTER JOIN: a bal táblában levő összes elemet visszaadja, valamint a kapcsolódó elemeket a jobb táblából. A példában: a fenti három, valamint Peti - [üres].
  • RIGHT JOIN, RIGHT OUTER JOIN: a jobb táblában levő minden elemet visszaadja, valamint a kapcsolódó elemeket a bal táblából. A példában: az első három, valamint [üres] - banán.
  • FULL JOIN, FULL OUTER JOIN: azokat az elemeket adja vissza, amelyek legalább az egyikben szerepelnek. A példában: a fenti öt.
SELECT Orders.OrderID, Customers.CustomerName, Orders.OrderDate FROM Orders JOIN Customers ON Orders.CustomerID=Customers.CustomerID;

Módosító SQL parancsok

UPDATE

UPDATE Customers SET ContactName = 'Alfred Schmidt', City= 'Frankfurt' WHERE CustomerID = 1;

INSERT/SELECT INTO

INSERT INTO Customers (CustomerName, City, Country) VALUES ('Bob', 'New York', 'USA');
INSERT INTO Customers (CustomerName, City, Country) SELECT SupplierName, City, Country FROM Suppliers;
SELECT * INTO CustomersGermany FROM Customers WHERE Country = 'Germany';

DELETE

DELETE FROM Customers WHERE CustomerName='Bob';

Haladó SQL fogalmak

Nézet (view)

A nézet (view) egy virtuális tábla, ami egy SQL lekérdezés eredménye.

CREATE VIEW ViewName AS SELECT Column1, Column2 FROM TableName WHERE SomeColumn = SomeValue;
SELECT * FROM ViewName;
DROP VIEW ViewName;

Megszorítás (constraint)

CREATE TABLE table_name (
    column1 datatype constraint,
    column2 datatype constraint,
    column3 datatype constraint,
    ....
);

Az alábbi megszorítások a legelterjedtebben az SQL-ben:

  • NOT NULL - az adott oszlop nem tartalmazhat NULL értéket (pl. egy vásárlásnál nem hiányozhat a vásárlás összege)
  • UNIQUE - egy oszlop összes eleme különböző (pl. egy telefonszám táblában ne lehessen két egyforma telefonszám)
  • PRIMARY KEY - egyértelműen azonosítja a tábla sorait (a NOT NULL és UNIQUE egyszerre)
  • FOREIGN KEY - egyértelműen azonosítja egy másik tábla során
  • CHECK - biztosítja, hogy egy oszlop összes eleme adott feltételnek felel meg (pl. az e-mail cím tartalmazza a @ karaktert)
  • DEFAULT - alapértelmezett értéket ad azoknak az oszlopoknak, ahol nincs megadva érték (pl. ha a távolság mértékegységét kell megadni, akkor az alapértelmezett lehet méter)
CREATE TABLE Persons (
    ID int NOT NULL PRIMARY KEY,
    LastName varchar(255) NOT NULL,
    FirstName varchar(255),
    Age int CHECK (Age>=18)
    City varchar(255) DEFAULT 'Sandnes'
    CONSTRAINT UC_Person UNIQUE (ID,LastName)
);

CREATE TABLE Orders (
    OrderID int NOT NULL,
    OrderNumber int NOT NULL,
    OrderDate date DEFAULT GETDATE()
    PersonID int,
    PRIMARY KEY (OrderID),
    CONSTRAINT FK_PersonOrder FOREIGN KEY (PersonID)
    REFERENCES Persons(PersonID)
);

ALTER TABLE Orders ADD CONSTRAINT FK_PersonOrder FOREIGN KEY (PersonID) REFERENCES Persons(PersonID);

Index

Az index olyan keresési tábla, melynek segítségével a kereső fel tudja gyorsítani az adatletöltést.

CREATE [UNIQUE] INDEX index_name ON table_name (column1, column2, ...);
DROP INDEX index_name;

A UNIQUE oszlopokra (így az elsődleges kulcsokra is) automatikusan készül index. A tárolási mód platformfüggő, pl. az Oracle adatbázisban ALL_INDEXES, USER_IND_COLUMNS.

SQL kiegészítések

Az alap SQL szabványos, és többé-kevésbé ugyanúgy működik mindegyik rendszeren. Ez viszont nem alkalmas minden cél megvalósítására, pl. bonyolultabb logika megfogalmazására. Így minden gyártó arra kényszerül, hogy saját, tehát platformfüggő kiegészítést hozzon létre. Ebben a részben ezt nézzük meg.

PL/SQL

Ez az Oracle procedurális SQL kiegészítője.

Alap szintaxis

-- comment
DECLARE 
   declarations;
BEGIN 
   statements;
END;
/

Kiírás

dbms_output.enable
dbms_output.put_line('String: ' || value);

Változók

variable_name [CONSTANT] datatype [NOT NULL] [:= | DEFAULT initial_value]

A leggyakoribb adattípusok:

INTEGER, REAL, VARCHAR2(length), BOOLEAN, DATE, TIMESTAMP

A felhasználó is definiálhat típust:

SUBTYPE newType IS type;

A változók hatóköre

DECLARE 
    -- Global variables  
    declarations;
BEGIN
    statements;
    DECLARE
        -- Local variables 
        declarations;
    BEGIN 
        statements;
    END;  
END;

Oszlop típus használata

DECLARE 
    variable table.column%type;
BEGIN 
    SELECT column INTO variable FROM table where id=15;
    ...
END;

A leggyakoribb string műveletek:

CONCAT(x, y), LENGTH(x), LOWER(x), TRIM(x), UPPER(x)

Adatszerkezetek (collections)

Asszociatív tömb: kulcs-érték párok

TYPE typeName IS TABLE OF elementType INDEX BY subscriptType; 
myAssicateiveArray(mySubscript):= myElement;

Beágyzott távlázat: dinamikus növekedés, lehet ritka.

TYPE typeName IS TABLE OF elementType;

Változó méretű tömb:

TYPE typeName IS VARRAY(n) of elementType;

Eljárások:

EXISTS(n), COUNT, LIMIT, FIRST, LAST, NEXT(n), DELETE, ...

Feltételkezelés

IF condition1 THEN  
    statements; 
ELSIF condition2 THEN 
    statements; 
ELSE  
    statements; 
END IF; 

CASE selector 
    WHEN 'value1' THEN statements1; 
    WHEN 'value2' THEN statements2; 
    ... 
    ELSE statementsN;  -- default case 
END CASE;

CASE 
    WHEN selector = 'value1' THEN statements1; 
    WHEN selector = 'value2' THEN statements2; 
    ...
    ELSE statementsN;  -- default case 
END CASE;

Ciklus

LOOP
    ...
        EXIT;
    ...
        CONTINUE;
    ...
END LOOP; 

WHILE condition LOOP 
    statements; 
END LOOP; 

FOR counter IN initial_value .. final_value LOOP 
    statements; 
END LOOP;

<< label >>
statements;
...
GOTO label;

Eljárások, függvények

CREATE [OR REPLACE] PROCEDURE procedureName(parameters) {IS | AS} 
BEGIN
    statements;
END procedureName;

DROP PROCEDURE procedureName; 

CREATE [OR REPLACE] FUNCTION functionName(parameters) RETURN return_datatype {IS | AS} 
BEGIN 
    statements;
END functionName;

Paraméterek:

parameterName [IN | OUT | IN OUT] type [, ...]

Végrehajtás:

EXECUTE procedure_name;
EXECUTE function_name(parameters);
BEGIN
     ...
     procedure_name;
     result := function_name(parameters);
     ...
END;

Kurzorok

Implicit kurzor:

SQL%FOUND, SQL%NOTFOUND, SQL%ISOPEN, SQL%ROWCOUNT

Explicit kurzor:

DECLARE 
    var1 table.column1%type; 
    var2 table.column2%type; 
    CURSOR cursor_name IS SELECT column1, column2 FROM table; 
BEGIN 
    OPEN cursor_name;
    LOOP
        FETCH cursor_name INTO var1, var2; 
        EXIT WHEN cursor_name%notfound; 
        dbms_output.put_line(var1 || ' ' || var2); 
    END LOOP; 
    CLOSE cursor_name;
END;

Rekordok

DECLARE 
    myRecord table%rowtype; 
BEGIN 
    SELECT * into myRecord FROM table WHERE ...;  
    dbms_output.put_line(myRecord.column1 || ' ' || myRecord.column1); 
END;

Kurzort is használhatunk. Felhasználó által definiált rekord:

TYPE typeName IS RECORD (
    fieldName1 datatype1,
    fieldName2 datatype2,
    ...
    fieldNameN datatypeN
);
recordName typeName;

Kivételkezelés

DECLARE
    declarations;
    myException EXCEPTION; 
BEGIN 
    statements;
    IF condition THEN 
        RAISE myException; 
    END IF; 
EXCEPTION 
    WHEN exception1 THEN  
        statements1;
    ...
    WHEN exceptionN THEN 
        statementsN;
    WHEN myException THEN 
        statementsMy;
END;

Rendszer kivételek:

CASE_NOT_FOUND, COLLECTION_IS_NULL, INVALID_CURSOR, INVALID_NUMBER, LOGIN_DENIED, NO_DATA_FOUND, PROGRAM_ERROR, ZERO_DIVIDE, ...

Kiváltó utasítások (trigger)

CREATE [OR REPLACE ] TRIGGER trigger_name {BEFORE | AFTER | INSTEAD OF} 
{INSERT [OR] | UPDATE [OR] | DELETE} [OF col_name] ON table_name  
[REFERENCING OLD AS o NEW AS n] [FOR EACH ROW]  
WHEN (condition)   
DECLARE 
    declarations; 
BEGIN  
    statements 
EXCEPTION
    exceptionHandlingStatements 
END;

Tranzakciók

COMMIT;
ROLLBACK;
SAVEPOINT < savepoint_name >;
...
ROLLBACK [TO SAVEPOINT < savepoint_name>];

Csomagok

CREATE PACKAGE myPackage AS
    PROCEDURE myProcedure(parameters);
    PROCEDURE ...;
END myPackage;

CREATE OR REPLACE PACKAGE BODY myPackage AS  
    PROCEDURE myProcedure(parameters) IS 
        declarations;
    BEGIN
        ...
    END myProcedure;
    ...
END myPackage;

Objektumorientáltság

CREATE OR REPLACE TYPE MyClass AS OBJECT (
    myAttribute1 type1;
    myAttribute2 type2;    
    MEMBER FUNCTION myFunction(parameters) RETURN type;
    MEMBER PROCEDURE myProcedure;
); 

CREATE OR REPLACE TYPE BODY MyClass AS 
    MEMBER FUNCTION myFunction(parameters) RETURN type IS 
    BEGIN 
        statements;
        RETURN result;
    END myFunction;

    MEMBER PROCEDURE myProcedure IS
    BEGIN 
        statements;
    END myProcedure;
END;

DECLARE
    myInstance MyClass;
BEGIN 
    myInstance1 := myClass(param1, param2); 
    ...
END;

Összehasonlítás

MAP MEMBER FUNCTION measure RETURN NUMBER; -- absolute value
ORDER MEMBER FUNCTION measure(other MyClass) RETURN NUMBER; -- -1, 0 or +1

myInstance1 MyClass;
myInstance2 MyClass;
...
IF (myInstance1 > myInstance2) ...

Öröklődés

CREATE OR REPLACE TYPE MyChildClass UNDER MyClass(   
    myAttribute3 type3, 
    OVERRIDING MEMBER PROCEDURE myProcedure
) 

CREATE OR REPLACE TYPE BODY MyChildClass AS 
    OVERRIDING MEMBER PROCEDURE myProcedure IS 
    BEGIN
        statements;
    END myProcedure;

Absztrakt osztály

CREATE OR REPLACE TYPE MyAbstractClass AS OBJECT (
    myAttribute type,
    NOT INSTANTIABLE NOT FINAL MEMBER PROCEDURE myAbstractprocedure
) NOT INSTANTIABLE NOT FINAL

T-SQL

TODO: kidolgozni

Az adatbázis rendszerek továbbgondolásai

A relációs adatbázis sok esetben nem eléggé hatékony, emiatt számos, kisebb-nagyobb népszerűségű továbbgondolása történt; ebben a részben ezekből szemezgetünk.

NoSQL

MongoDB

JCR

DWH

SSAS

A Microsoft SQL Server telepítő része. Kétféle módon lehet telepíteni az SSAS-t:

  • Multidimenziós: ez volt előbb. Fő fogalma a kocka (cube), ami tények, dimenziók és mértékek halmazát jelenti. A lekérdezési nyelve az MDX, ami az SQL-ből nőtte ki magát.
  • Táblázatos: célja az, hogy az informatikához, azon belül az Excelhez értő, ám nem programozó gazdasági emberek felé nyisson. A lekérdező nyelve a DAX, ami az Excel függvényekre hasonlít.

Vonatkozó szoftverek:

  • Amint azt fent már említettem, a Microsoft SQL server része.
  • A Microsoft SQL Server Management Studio (SSMS) segítségével tudunk hozzá csatlakozni lekérdezéseket végrehajtani.
  • Fejleszteni a Microsoft Visual Studio segítségével lehet, ha feltelepítjük hozzá az SQL Server Data Tools (SSDT) kiegészítőt. (A tapasztalatom az, hogy azzal a kiegészítővel problémák voltak, viszont maga a kiegészítő felkínálja, hogy feltelepítsen egy Visual Studio-t is; azzal működött.)
  • A végeredményt lekérdezni - legalábbis tesztelési céllal - az Excel-lel lehetséges és érdemes. (Ehhez telepíteni kell a kliens könyvtárakat; keressünk rá arra, hogy SSAS client libraries.)
  • Az adatokat olyan üzleti intelligencia eszközzel érdemes megjeleníteni, mint pl. a PowerBI.

Tranzakciók kezelése

A tranzakció fogalma független ugyan az adatbázisoktól, de mivel leggyakrabban ezen a területen találkozunk vele, itt tárgyaljuk.

CRUD

Az adatbázisok használata során - ahogy fent láthattuk - műveleteket hajtunk végre. Kis kitérővel, itt ismerkedjünk meg a CRUD betűszóval, melynek jelentése az alábbi:

  • C: create (létrehoz)
  • R: read (olvas)
  • U: update (módosít)
  • D: delete (töröl)

Egy banszámlás példa

Egy tipikus programban egy művelet többnyire nem egy, hanem több lépésből áll. Vegyünk egy klasszikus példát: a banki átutalást. Valójában itt (legalább) két lépésre osztjuk a műveletet:

  • az egyik bankszámláról levonjuk a pénzt, azaz az ottani egyenleget lecsökkentjük egy adott értékkel, majd
  • a másik bankszámlára jóváírjuk, azaz megnöveljük az ottani egyenleget.

Nem nehéz belátni, hogy a műveletet csak akkor szabad sikeresnek elkönyvelnünk, ha mindkét művelet sikeresen végrehajtódott. Ha ugyanis az első művelet sikeres, a másik sikertelen, akkor a pénz elvész. Ráadásul ne feledjük, hogy az "egyik bankszámláról levonjuk a pénzt" szintén nem atomi művelet, hanem (legalább) az alábbiakból:

  • Kiolvassuk az aktuális egyenleget, ez tehát egy R művelet.
  • Leellenőrizzük, hogy az aktuális össze a levonandó összegnél magasabb-e. Ha nem, hibával leállunk, ha igen, folytatjuk.
  • Kivonjuk az aktuális értékből az átutalandó összeget, majd az agy kapott új értékkel felülírjuk az adatbázis aktuális értékét, ez tehát egy U művelet.

Hasonlóan levezethetjük a jóváírás műveletet is, sőt, még a fentieket is tovább tudjuk bontani. Ill. a fentiek előtt és után is műveleteket kell végrehajtani, pl. az utalás előtt azt ellenőrizni, hogy a célszámla létezik-e.

Most lássuk, milyen problémák léphetnek fel!

  • Miután leellenőriztük, hogy a célszámla létezik, a célszámlát megszüntetik, mielőtt még a jóváírás műveletet végrehajtanánk.
  • Az egyenleg kiolvasása és az új egyenleg beírása között lezajlik egy teljes vásárlási művelet, melynek során megváltozik az egyenleg. Az újraírás során ez a változás elvész.
  • A célszámlán a jóváírás során, az egyenleg kiolvasása ill. felülírása között lezajlik egy művelet.

Ez csak néhány kiragadott példa, a gyakorlatban ennél sokkal több problémával kell szembesülnünk.

Tranzakció

Azt, hogy a fenti kettő (ill. akárhány) műveletet egyszerre szeretnénk végrehajtani, úgy fogalmazhatjuk meg, hogy egy tranzakcióban szeretnénk ezeket végrehajtani. Magyarán azt szeretnénk, hogy akár csak egyetlen lépés is sikertelen, az egész legyen sikertelen, és álljon vissza az eredeti állapot. Valamint biztosítani szeretnénk, azt is, hogy amíg a mi műveletünk zajlik, aközben ne futhasson le egy másik művelet (azaz tranzakció).

Látszólag egyszerű a megoldás: ha fut egy tranzakció, akkor minden más művelet várjon a sorára. Elvileg ez működik, a gyakorlatban viszont aránytalan belassulást okozhat. Egy egyszerű példa: miért akadjon meg egy szál egy adatbázis művelet következtében csak amiatt, mert egy másik szál egy másik adatbázison hajt végre műveletet? Emiatt nem nyilvánvaló a tranzakciók kezelése, és ez az oka annak, hogy igen komoly elmélet alakult ki e probléma kapcsán.

ACID

A tranzakcióknak a következő 4 feltételt kell teljesíteniük, amit az ACID betűszóval rövidítünk:

  • A: atomicity (atomicitás): azt jelenti, hogy a kód egy része ugyanannak a tranzakciónak a része. Ha az egyik lépés hibára fut, akkor vissza kell állni a tranzakció előtti állapotba. Ha mindegyik lépés sikeresen lefutott, akkor lehet lementeni az új állapotot.
  • C: consistency (konzisztencia): ez azt jelenti, hogy ha a rendszer a tranzakció elején konzisztens állapotban volt, azaz érvényesek voltak rá bizonyos üzleti szabályok, akkor ez az állapot a tranzakciót követően is megmarad.
  • I: isolation (izoláció): ez azt jelenti, hogy a párhuzamosan futó tranzakciók ne lépjenek egymás lábujjára, azaz tranzakció közben ne lehessen hozzáférni az ideiglenes adatokhoz.
  • D: durability (tartósság): azt jelenti, hogy ha a sikeresen végrehajtott tranzakciót követően (angolul: ha a commit végrehajtódott) a változások állandóvá, permanenssé válnak.

A tranzakció kezelésből fakadó problémák

Fent már említettünk pár lehetséges problémát. Ezeket a következő fő csoportokba sorolhatjuk:

  • Piszkos olvasás (dirty read): akkor következik be, ha párhuzamosan futó tranzakció olyan adatot olvas ki, ami egy nem befejezett tranzakció része. Probléma ebből akkor adódik, ha az a tranzakció, amelyik a módosítást végrehajtotta, hibára fut, és visszakerül az eredeti állapotba. A pénzátutalásos példában ez mondjuk akkor következik be, ha a levonás után, de még a jóváírás előtt egy párhuzamos szál kiolvassa az új értéket, a jóváírás viszont nem sikerül, a rendszer visszakerül a tranzakció előtti állapotba, a párhuzamos szál viszont olyan értékkel dolgozik tovább, ami csak a sikeres tranzakció esetén lenne érvényben.
  • Nem megismételhető olvasás (nonrepeatable read): azt jelenti, hogy műveleten belül ugyanaz az olvasás eltérő értéket ad. A probléma megértéséhez terjesszük ki az átutalásos példát a következőképpen: először végrehajtunk egy ellenőrzést, hogy van-e elég pénz a forrás számlán, létezik-e a célszámla stb.; ez legyen egy tranzakció, majd hajtsuk végre egy újabb tranzakcióban az átutalást. A probléma akkor lép fel, ha a két tranzakció között történik egy költés egy másik szálon. Ez esetben az ellenőrzés ellenére a második tranzakció sikertelen lesz.
  • Fantom olvasás (phantom read): hasonló a piszkos olvasáshoz; a különbség annyi, hogy míg a piszkos olvasás során egy adott sor értékéről van szó, ebben az esetben magáról a sor létezéséről. Az átutalásos példát a következőképpen gondoljuk tovább: tegyük fel, hogy az átutalást követően, de még a jóváírás előtt, az átutalás bekerül egy tranzakció listába. Ugyanakkor ha a jóváírás során hiba lép fel, akkor visszaáll az eredeti állapot, azaz az átutalás törlődik az átutalás listából is. Ha egy párhuzamos szál az átutalás során, de a sikertelen jóváírás előtt olvassa ki az átutalások listáját, akkor egy olyan értéket is látni fog, ami a tranzakció végeztével már nem lesz ott.

Izolációs szintek

A fent felsorolt 3 probléma valójában 4 izolációs szintet definiál:

  • Nem véglegesített érték olvasása (read uncommitted): ez esetben a piszkos olvasás is engedélyezett, tehát az olyan értékek olvasása, amely egy másik, olyan tranzakción belül módosult, ami még nem lett véglegesítve (szakzsargonnal: "még nem lett komittálva"). Többszálú környezetben ezt el kell kerülni, mert - ahogy a fenti példában láthattuk - adatvesztéshez vezethet.
  • Véglegesített érték olvasása (read committed): a tranzakció sohasem fog olyan értéket kiolvasni amit egy másik szál módosított, de még nem hagyott jóvá (szakzsargonnal: csak a "komittált" adatokat olvassa ki). A legtöbb adatbázisban ez az alapértelmezett izolációs szint.
  • Megismételhető olvasás (repeatable read): ezen a szinten a rendszer garantálja azt, hogy a tranzakció során ha többször olvassuk ugyanazt a sort, akkor ugyanazt az értéket kapjuk. Ha jól belegondolunk, erre a szintre nem feltétlenül van szükség. A fenti példán illusztrálva, az előző szinten előfordulhat a következő: először megbizonyosodunk arról, hogy van annyi pénz a számlánkat, hogy át tudjuk utalni az összeget, de az átutalás meghiúsul. Mikor következhet ez be? Ha mondjuk egy átutalást szeretnék végrehajtani a bankszámláról, és pont ugyanabban a pillanatban valamelyik családtagom vásárol. Valójában veszteség nem ér senkit, legfeljebb egy kis bosszankodás, hogy az elején még engedte az átutalást, de az meghiúsult. De mindennek ára van: ezzel az izolációs szinttel valószínűleg túl sokszor váratjuk meg a másikat feleslegesen. Valójában nagyon pici annak az esélye, hogy pont abban a pillanatban történik a vásárlás.
  • Sorosítható (serializalbe): ezzel a fantom olvasást is kiküszöböljük. Ez a legnagyobb izolációs szint. Itt a rendszer megakadályozza azt, hogy a tranzakció során bárki más hozzányúljon az adattáblához. Gondoljuk végig a fenti példában, hogy ez mit jelent: amíg az A → B utalás nem fejeződött be, a C sem utalhat D-nek, mivel ugyanazt az adattáblát használják. Ebből is érezzük, hogy a legtöbb esetben valószínűleg felesleges az ilyen szintű izoláció, és szinte biztos, hogy még alacsony terheltség mellett is be fog lassulni a program (performance bottleneck).

Elosztott tranzakciók

Más néven kétfázisú jóváhagyás (two-phase commit), vagy XA-tranzakció (az angol eXtended Architecture-ből származik). Ha a tranzakcióban kettő vagy több rendszer vesz részt, pl. két adatbázis, akkor a hagyományos (egyfázisú) tranzakcióval nehéz kezelni. Példaként vegyük ismét az átutalást, de a két fél legyen külön banknál, azaz két különböző adatbázisban kelljen módosítani. Itt hogyan kezeljük azt az esetet, amikor a levonás rendben lefut, de a jóváírás nem? Itt jön képbe a kétfázisú jóváhagyás: az első fázisban minden adatbázistól (ill. általában: minden érintett rendszertől) lekérdezzük, hogy ha ezt a tranzakciót végrehajtanánk, akkor az sikeres lenne-e. Ha mindegyiktől pozitv válasz érkezik, akkor végrehajtjuk, egyébként nem.

Optimista lefoglalás

Angolul optimistic locking. Az izoláció során a tranzakciót végrehajtó szál lefoglalja (szakzsargonnal "lockolja") az erőforrást: amíg a tranzakció be nem fejeződik, addig a többi nem férhet hozzá, várakozniuk kell. Nyilván itt a legrosszabb forgatókönyvre készülünk: mi történik akkor, ha pont abban az ezredmásodpercben vásárol valamelyik családtagunk a boltban, amikor mi próbálunk pénzt utalni, és a rendszer már leellenőrizte, hogy van-e elegendő keret, de még nem utalta át? A példából is érezzük, hogy valójában a legtöbb esetben ez nagyon-nagyon ritkán fordul elő. Az erőforrás lefoglalás viszont igen drága: tegyük fel, hogy egy webes alkalmazást nagyon sokan használják, és mindenki azt tapasztalja, hogy lassú, mert legalul, az adatbázis műveleteknél egymásra várnak a folyamatok, mert egyszerre csak egy férhet hozzá, de az esete közel 100%-ában semmi problémát nem okozna, ha többen is hozzáférnének. Erre a problémára alkották meg az optimista erőforrás lefoglalás technikáját: itt feltételezzük, hogy nem lesz probléma, és ha mégis, akkor kicsit nagyobb erőforrásokat megmozgatva visszacsináljuk az eredeti állapotot. Ha véletlenül mégis bekövetkezik az összeütközés, akkor az eredeti állapot visszaállítása erőforrás igényesebb, mintha hagyományosan lefoglalnánk az erőforrást a tranzakció elején, majd felszabadítanánk annak végén, de össz-egészébe véve a rendszer átlagosan sokkal gyorsabb lesz.

Holtpont

Angolul deadlock. A tranzakció kezelésnél tehát erőforrásokat foglalunk le, és ha egy másik szál ugyanarra az erőforrásra pályázik, akkor ki kell várnia a sorát. Most tegyük fel, hogy van két szál: A és B, és két erőforrás: X és Y. Az A szál lefoglalja X erőforrást, a B szál az Y-t, majd az A megpróbálja lefoglalni az Y erőforrást, a B pedig az X-et. Egy kevésbé absztrakt példával: X legyen az A ember számlája, az Y pedig a B emberé, és ugyanabban a pillanatban szeretnének egymásnak utalni. Ebben az esetben az A arra vár, hogy B "elengedje" Y-t, a B pedig arra, hogy A elengedje X-et. A B csak akkor fogja elengedni Y-t, miután az A elengedte X-et, és az A is csak akkor fogja elengedni az X-et, miután elengedte Y-t, ezért a végtelenségig várnak egymásra.

A holtpont felderítése, reprodukálása és megoldása nem egyszerű feladat. A legegyszerűbb talán elkerülni: szigorú sorba kell rendezni az erőforrásokat és a szálakat is, és úgy engedélyezni a szálaknak az erőforrás elérést, hogy még elméletileg se alakulhasson ki kör. Persze a példában ez azt jelentené, hogy a B sohasem utalhatna A-nak, szóval sok esetben ez a módszer nem működik.

Adatbázis kezelők

Az adatbázis kezelőkkel tipikusan az alábbi műveleteket lehet végrehajtani:

  • adatok megtekintése és módosítása,
  • a megszorítások, indexek, kiváltó utasítások, nézetek stb. megtekintése,
  • adatbázis mentése, betöltése,
  • SQL kód generálása (pl. adattábla létrehozására),
  • adatmodell rajzolása,
  • jogosultság kezelés,
  • és sok minden más.

Néhány adatbázis kezelő szoftver:

  • SQL developer: az Oracle terméke, elsősorban az ő adatbázisuk kezeléséhez, bár más adatbázishoz is tud csatlakozni. Személyes véleményem róla az, hogy ahhoz képest, hogy az Oracle AZ adatbázis, ez a kezelőfelület igen szerényre sikeredett, persze funkcionálisan megfelelően működik.
  • Microsoft SQL Server Management Studio: a Microsoft SQL Server és egyéb Microsoft termékek (Analysis Services, Integration Services, Reporting Services) kezelésére szolgál. Más gyártók adatbázisához szerintem felesleges próbálkozni vele csatlakozni. Szerintem eléggé komplikált, sok helyen logikátlan, de ez legalább illik a nehézsúlyú fő termékéhez.
  • MySQL Workbench: a MySQL adatbázis kezelő felülete. Szerintem ez ey egyszerű és nagyszerű szoftver; ilyennek kell lennie!
  • HeidiSQL: a MariaDB adatbázis kezelője. Őszintén szólva komoly tapasztalatom nincs benne, mert a MySQL-lel elégedett vagyok. Egyszer használtam, akkor rendesen működött.
  • pgAdmin: a PostgreSQL kezelőfelülete. Kissé furcsa, hogy webes felületként működik, ennek megfelelően a használata is kissé nehézkesebb.
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License