Zanořené SQL a dynamické SQL Ondřej Štoček Petr Novák, Kateřina Zvánovcová
Historie Embedded SQL První implementace Embedded SQL byla použita v DB2 (IBM) v osmdesátých letech. Ta byla základem pro první ANSI SQL v roce 1986. Bohužel, ANSI SQL ‘86 a jeho oprava v roce 1989 definovaly jen podmnožinu Embedded SQL známý jako Static SQL.
Historie Emb. SQL – pokračování Implementace kompletního Embedded SQL (s Dynamic SQL) v DB2 sloužila jako de facto standard do devadesátých let. Sloužila jako základ pro další databázové systémy (Oracle, FirstSQL…).
CÍL Propojení jazyka SQL s vyšším programovacím jazykem. Přenositelnost zdrojového kódu databázové aplikace na různé databáze ... Přehlednost zdrojového kódu
Embedded SQL Místo volání patřičných funkcí příslušné databáze píšete SQL dotazy přímo do zdrojového kódu aplikace v jakémsi pseudojazyce. Před vlastní kompilací se dosadí na jejich místo nativní volání API daného SQL serveru.
Oracle preprocessor Spouští se ještě před vlastním překladem zdrojového kódu. Generuje z Embedded SQL datové struktury a série volání funkcí z knihovny Oracle SQLLIB. Pro* C/C++, Pro* Cobol, Pro* Fortran, … V dalším výkladu se zaměřím na Pro* C/C++
OCI (Oracle Call Interface) vs. Pro* C/C++ Menší velikost kódu Přímý přístup k funkcím, větší kontrola přístupu aplikace k databázi Poněkud vyšší výkon Pro* C/C++ Mnohem lepší čitelnost kódu Mnohem jednodušší konstrukce programu
Životní cyklus programu Vytvoření programu s vloženým (embedded) SQL (soubor.pc) Prekompilace pomocí Pro* C/C++ (soubor.c) Kompilace překladačem C/C++ (soubor.obj) Přilinkování Oracle run-time knihovny a vytvoření spustitelného souboru
Syntax Embedded SQL Každý příkaz je uvozen: EXEC SQL Zpracováván databázovým prekompilátorem. Příkaz končí středníkem (;). Komentáře se zapisují /* ... */. Komentáře uvedené //... jsou nepřípustné.
SQL a direktiva #include Prekompilátor se spouští ještě před vlastním preprocesorem hostitelského jazyka Nutnost vlastní direktivy pro vložení souboru Obsahuje-li header "EXEC SQL" příkazy, inkludujeme pomocí: EXEC SQL INCLUDE file.h;
Hostitelské proměnné Určeny pro komunikaci databáze s prostředím programu. Použití proměnné v SQL uvozeno dvojtečkou, jinak je považována za databázové pole. :promenna Deklarace takových proměnných Začíná: EXEC SQL BEGIN DECLARE SECTION; Končí: EXEC SQL END DECLARE SECTION; Povolené typy: char, char[n], int, short, long, float, double, VARCHAR[n]
Indikátorové proměnné jsou hostitelské proměnné (typu short) následující v SQL příkazu za jinou proměnnou. Může obsahovat následující hodnoty: 0: Proměnná koretně naplněna. -1: Vkládaná hodnota je NULL, proměnná má neurčitý obsah. -2: Vkládaná hodnota je větší než proměnná a není možné určit skutečnou velikost. >0: Vkládaná hodnota je větší než proměnná, skutečná velikost je hodnota indikátoru. (Při použití v INSERT / UPDATE jsou relevantní pouze hodnoty -1 a 0. )
Příklad použití hostitelských proměnných EXEC SQL BEGIN DECLARE SECTION; char mujtext[20 + 1]; short ind_mujtext; EXEC SQL END DECLARE SECTION; ... EXEC SQL SELECT pole FROM tabulka INTO :mujtext :ind_mujtext WHERE druhepole = 15; if (ind_mujtext == -1) printf("NULL"); else printf(mujtext);
Připojení / odpojení databáze EXEC SQL CONNECT :login/:passwd; EXEC SQL CONNECT :login IDENTIFIED BY :passwd; Odpojení: S uložením změn: EXEC SQL COMMIT WORK RELEASE; Bez uložení změn: EXEC SQL ROLLBACK WORK RELEASE;
Příklad použití CONNECT a INSERT EXEC SQL BEGIN DECLARE SECTION; char user[20 + 1]; char passwd[20 + 1]; float insval[50]; EXEC SQL END DECLARE SECTION; strcpy(user, "novak"); strcpy(passwd, "123456"); EXEC SQL CONNECT :user IDENTIFIED BY :pwd; ... EXEC SQL INSERT INTO tabulka (mojepole) VALUES (:insval); EXEC SQL COMMIT WORK RELEASE;
Kurzory Prostředek pro zpracovávání víceřádkových výsledků SQL dotazů Kurzor je pojmenovaný, můžeme mít několik různých kurzorů otevřených najednou.
Deklarace kurzoru Syntaxe: EXEC SQL DECLARE jmeno_kurzoru CURSOR FOR SELECT...; jmeno_kurzoru: Jméno deklarovaného kurzoru. Jméno není uváděno s dvojtečkou. Nejedná se o hostitelskou proměnnou nýbrž o identifikátor pro SQL prekompilátor SELECT... : Dotaz, pro který se kurzor připraví
Otevření / zavření kurzoru Syntaxe: EXEC SQL OPEN jmeno_kurzoru; ... EXEC SQL CLOSE jmeno_kurzoru;
Vyzvednutí záznamu a posun kurzoru Syntaxe: EXEC SQL FETCH jmeno_kurzoru INTO :polozka1 :ind_prom1, :polozka2 :ind_prom2, ... ; jmeno_kurzoru: jméno otevřeného kurzoru polozka1,... : proměnné pro vyzvednutá data ind_prom1,...: indikátorová proměnná
Ošetření chyb, varování Příkaz WHENEVER Nastaví od daného místa prekompilace způsob ošetřování varování, chyb a nepřítomnosti záznamu. Syntaxe: EXEC SQL WHENEVER podmínka akce
Ošetření chyb, varování Podmínka: NOT FOUND: je-li odpověď na FETCH nebo SELECT žádná položka, je vygenerována akce. SQLERROR: skončí-li jakákoliv akce SQL EXEC chybou, je provedena akce. SQLWARNING: skončí-li jakákoliv akce SQL EXEC varováním, je provedena akce.
Ošetření chyb, varování Řešení: CONTINUE: program pokračuje následujícím příkazem. GOTO label: program skočí na zadaný label. STOP: program se ukončí. DO routine: program zavolá funkci routine. DO BREAK: zavolá break pro opuštění cyklu. DO CONTINUE: zavolá continue pro pokračování dalšího kroku cyklu.
Příklad použití kurzoru a ošetření posledního řádku EXEC SQL BEGIN DECLARE SECTION; int cislo; short i_cislo; EXEC SQL END DECLARE SECTION; EXEC SQL DECLARE mujkurzor CURSOR FOR SELECT pole FROM tabulka WHERE druhepole > 20; EXEC SQL OPEN mujkurzor; EXEC SQL WHENEVER NOT FOUND DO BREAK; while (true) { EXEC SQL FETCH mujkurzor INTO :cislo :i_cislo; if (i_cislo == -1) printf("NULL\n"); else printf("Hodnota %i\n",cislo); } EXEC SQL CLOSE mujkurzor;
Dynamické SQL Co to je K čemu se to hodí Jak se to používá Jak se zpracovává SQL dotaz Proč statické SQL K čemu se to hodí Stavění podmínek podle zadání DDL v procedurách Jak se to používá Několik různě náročných způsobů
Zpracování SQL dotazu Lexikální, syntaktická a sémantická analýza SELECT jmeno, prijmeni FROM student st WHERE kruh_id = 13 AND (SELECT sum(castka) FROM platby pl WHERE pl.student_id = st.student_id AND mesic = CURRENT_MONTH) < 700 Lexikální, syntaktická a sémantická analýza Překlad do p-kódu p-kód – posloupnost operací, která bude databáze opravdu provádět (“v tabulce xxx najdi řádek podle indexu yyy”) Spuštění
Zpracování SQL dotazu Analýza dotazu může být dosti náročná záležitost Možné optimalizace Cache I ta k rozumné funkci potřebuje používání proměnných Překlad předem Embedded SQL, procedury v databázi Dynamic SQL se sestavuje a spouští právě z tohoto předem přeloženého kódu
Omezení statického SQL Daná povahou p-kódu Samotný dotaz musí být vždy stejný Nelze tvořit věci ve stylu mysql_query(“SELECT id, nazev FROM clanek WHERE $podminka”); Proměnné mohou být v dotazech používány jen v klauzulích WHERE a VALUES Nelze SELECT :sloupce FROM student WHERE student_id = 1; ani CREATE TABLE :jmeno_tabulky;
Použití dynamického SQL Manipulace se strukturou databáze (DDL příkazy) Tvoření a úprava tabulek podle aktuální potřeby Sestavování složitějších podmínek podle vstupu Simulace SQL konzole ...
Nevýhody dynamického SQL Pomalejší Pokaždé se musí předkládat znovu Může zavléci chyby SQL Injection
Volání dynamického SQL V Embedded SQL jsou čtyři způsoby (metody) volání podle složitosti dotazu: Bez výstupu i proměnných Bez výstupu, s pevně daným počtem proměnných Se pevně daným počtem polí výstupu i vstupních proměnných S neznámým počtem polí výstupu a neznámým počtem proměnných
Metoda 1 Bez výstupu a bez proměnných Klauzule EXECUTE IMMEDIATE V embedded SQL EXEC SQL EXECUTE IMMEDIATE ... V PL/SQL EXECUTE IMMEDIATE { :proměnná | 'řetězec' }; Spustí dotaz v parametru Dotaz se pokaždé překládá znovu
EXECUTE IMMEDIATE char prikaz[250], tab[200]; ... printf("Ktera tabulka se ti nelibi? "); gets(tab); sprintf(prikaz, “drop table %s”, tab); EXEC SQL EXECUTE IMMEDIATE :prikaz; ...
Metoda 2 Známý počet proměnných, výstup není možný Dvě fáze PREPARE EXEC SQL PREPARE můj_dotaz FROM { :proměnná | 'řetězec' }; EXECUTE EXEC SQL EXECUTE můj_dotaz [ USING :proměnná:indikátor, ... ]; Jednou připravený dotaz je možno spouštět opakovaně
PREPARE/EXECUTE int sid; char vstup[150], dotaz[200]; printf(“Zadej podminku pro vyhozeni ”); gets(vstup); sprintf(dotaz, “delete from student where student_id = :id and %s”, vstup); EXEC SQL PREPARE muj_dotaz FROM :dotaz; for (;;) { printf(“ID? “); gets(vstup); sid = atoi(vstup); if (sid == 0) { break; } EXEC SQL EXECUTE muj_dotaz USING :sid; }
V PL/SQL V PL/SQL EXECUTE IMMEDIATE umí i proměnné CREATE PROCEDURE vyhodit_studenta (podm VARCHAR, st_id NUMBER) AS BEGIN EXECUTE IMMEDIATE 'DELETE FROM student WHERE student_id = :id AND ' || podm USING st_id; END;
Odkazy Oracle Oracle Dynamic SQL, Pro*C/C++ Programmer's Guide Performing SQL Operations with Native Dynamic SQL, PL/SQL User's Guide and Reference
Metoda 3 - úvod dynamické vytvoření dotazu vytvoření dotazu z řetězce příkaz PREPARE práce s kurzorem tím se liší od předchozích DECLARE, OPEN, FETCH, CLOSE
Metoda 3 – typické použití zadání podmínky ve WHERE klauzuli ne úplně obecně nutno dodržet pevný počet proměnných Příklad řetězce použitelného s metodou 3: "SELECT JmenoZam FROM Zam WHERE Plat > :mujplat"
Metoda 3 - příkazy EXEC SQL PREPARE jm_dotaz FROM { :host_retezec | retezec_literal }; EXEC SQL DECLARE jm_cursor CURSOR FOR jm_dotaz; EXEC SQL OPEN jm_cursor [USING host_var_sez]; EXEC SQL FETCH jm_cursor INTO host_var; EXEC SQL CLOSE jm_cursor;
PREPARE vytvoření dotazu z řetězce a jeho pojmenování jiná možnost char dotaz[132] = "SELECT JmenoZam, CisloOdd FROM Zam WHERE Plat > :mujplat"; EXEC SQL PREPARE sql_dotaz FROM : dotaz; jiná možnost EXEC SQL PREPARE sql_dotaz FROM SELECT JmenoZam, CisloOdd FROM EMP WHERE Plat > :mujplat;
DECLARE deklaruje kurzor, pojmenuje ho asociuje ho s daným dotazem EXEC SQL DECLARE zam_cursor CURSOR FOR sql_dotaz; zam_cursor není hostitelská proměnná je to SQL identifikátor přesto by měl být unikátní
OPEN výkonný příkaz alokuje kurzor váže hostitelské proměnné “dosadí proměnné” provede dotaz vytvoří jeho aktivní množinu výsledky Př.: EXEC SQL OPEN zam_cursor USING :mujplat;
FETCH vrací řádku z aktivní množiny dosadí vrácené hodnoty do daných hostitelských proměnných posune kurzor na další řádku nejsou-li další data - “no data found” nastaví chybový kód v sqlca.sqlcode Př.: EXEC SQL FETCH zam_cursor INTO :jmeno, :oddeleni;
CLOSE zavření kurzoru už se na něj nedá provést FETCH Př.: EXEC SQL CLOSE zam_cursor;
Metoda 3 - rekapitulace char dotaz[132] = "SELECT JmenoZam, CisloOdd FROM Zam WHERE Plat > :mujplat"; EXEC SQL PREPARE sql_dotaz FROM :dotaz; EXEC SQL DECLARE zam_cursor CURSOR FOR sql_dotaz; EXEC SQL OPEN zam_cursor USING :mujplat; EXEC SQL FETCH zam_cursor INTO :jmeno :oddeleni; EXEC SQL CLOSE zam_cursor;
Metoda 4 - úvod umožňuje zadat předem neznámý počet proměnných v příkazu umožňuje předem neznámý počet vybíraných položek (sloupců) v dotazu Př.: 'INSERT INTO Zam (<unknown>) VALUES (<unknown>)' 'SELECT <unknown> FROM Zam WHERE CisloOdd = 20‚ na tohle metoda 3 nestačí
Deskriptory dodatečná informace bind deskriptor select deskriptor počet proměnných a návratových hodnot jejich délka, typ adresy vazebních proměnných adresy výstupních proměnných na návratové hodnoty tyto údaje potřebujeme znát, ale metoda 4 je předem neurčuje bind deskriptor pro informace o vazebních proměnných select deskriptor pro informace o výstupních položkách výsledcích SELECTu
struktura SQLDA SQL Description Area struktura pro deskriptory deklarována v hlavičkovém souboru sqlda.h Př.: # include <sqlda.h> ... SQLDA * bind_descriptor; SQLDA * select_descriptor;
Položky SQLDA long N; /* počet položek deskriptoru */ char **V; /* ukazatel na pole adres proměnných */ struct SQLDA { long *L; /* ukazatel na pole délek bufferů */ short *T; /* ukazatel na pole typů bufferů */ short **I; /* ukaz. na pole adr. indikátorových p.*/ long F; /* počet prom. nalez. přík. DESCRIBE */ char **S; /* ukazatel na pole ukaz. na jm. prom.*/ short *M; /* ukaz. na pole max. délek jm. prom.*/ short *C; /* uk. na pole skutečných délek jm. pr.*/ char **X; /* ukaz. na pole jmen indikátorových pr */ short *Y; /*ukaz. na pole max. délek jmen ind. pr.*/ short *Z; /* uk. na pole skutečných délek jmen indikátorových poměnných */ };
Metoda 4 - kroky deklarování hostitelské proměnné pro text příkazu (string) deklarování struktur SQLDA pro select deskriptor a bind deskriptor alokování struktur SQLDA nastavení max. počtu návratových hodnot a proměnných v dotazu naplnění hostitelské proměnné textem SQL příkazu vytvoření dotazu z hostitelského řetězce PREPARE deklarování kurzoru pro dotaz DECLARE
Metoda 4 - kroky popsání vazebních proměnných DESCRIBE BIND VARIABLES FOR sql_dotaz INTO bind_des; nastavení počtu proměnných v dotazu na počet skutečně nalezený příkazem DESCRIBE získání hodnot a alokace místa pro vazební proměnné nalezené DESCRIBE otevření kurzoru (za použití bind deskriptoru) popsání výstupních hodnot DESCRIBE SELECT LIST FOR sql_prikaz INTO select_des;
Metoda 4 - kroky nastavení skutečného počtu výstupních hodnot (DESCRIBE). nastavení skutečných délek a datových typů výstupních hodnot provedení FETCH s použitím alokovaných bufferů na které ukazuje select descriptor zpracování vrácených hodnot dealokace prostoru pro návratové hodnoty, proměnné, indikátorové proměnné, deskriptory zavření kurzoru (CLOSE)
Možné zjednodušení pokud je počet a typ proměnných předem známý, nepoužijeme bind deskriptor a příslušnou část vyřešíme jako u metody 3 pokud je předem známý počet a typ výstupních hodnot, nepoužijeme select deskriptor příslušnou část řešíme jako u metody 3
Metoda 4 – příkazy 1. PREPARE, DECLARE stejně jako metoda 3: EXEC SQL PREPARE jm_prikazu FROM { :host_retezec | retezec_literal }; EXEC SQL DECLARE cursor_jm CURSOR FOR jm_prikazu; na konci CLOSE jako u metody 3 EXEC SQL CLOSE cursor_jm;
Metoda 4 – příkazy 2. EXEC SQL DESCRIBE BIND VARIABLES FOR jm_prikazu INTO jm_bind_descr; EXEC SQL OPEN jm_cursor [USING DESCRIPTOR jm_bind_descr]; EXEC SQL DESCRIBE [SELECT LIST FOR] jm_prikazu INTO jm_select_descr; EXEC SQL FETCH jm_cursor USING DESCRIPTOR jm_select_descr;
Závěr Metoda 4 poskytuje nejvíce flexibility, ale její použití je velmi pracné větší náchylnost k chybám zásada: vždy použít to nejjednodušší, co stačí existuje několik variant Oracle Dynamic SQL vs. ANSI Dynamic SQL ANSI – novější, větší možnosti podpora objektových typů, UNICODE, ....
Odkazy www.oracle.com Pro* C/++ Precompiler Programmers Guide a další dokumentace