Prikaži sadržaj

Programiranje baza podataka

2. Osnovni koncepti programiranja C/SQL aplikacija

Ova stranica je pod konstrukcijom!
Ako pronađete grešku ili propust, molimo Vas da nam skrenete pažnju otvaranjem primedbe na zvaničnom GitHub repozitorijumu.

U ovom poglavlju ćemo se detaljnije upustiti u diskusiju konstrukcije aplikacija, napisanih u programskom jeziku C, koje koriste Db2 RSUBP. S obzirom da preprostavljamo da imamo podešeno okruženje, govorićemo o prevođenju od izvornog koda do izvršne aplikacije i uvešćemo elemente neophodne za kompilaciju naših programa, kao što su Db2 preprocesorske naredbe, upravljanje greškama, ugnežđavanje najrazličitijih vrsta upita i druge. Sve ovo će biti demonstrirano kroz veliki broj primera koji podrazumevaju da postoji baza podataka STUD2020.

2.1 Prevođenje programa

Pre nego što započnemo diskusiju o načinu konstrukcije C/SQL aplikacija, neophodno je da se upoznamo sa specifičnostima prevođenja C/SQL aplikacija. Za početak, potrebno je da razumemo da Db2 očekuje da naši programi budu napisani u datotekama čije su ekstenzije .sqc. U ovim datotekama se, dakle, nalazi izvorni kod napisan u programskom jeziku C u kojem su ugnežđeni SQL upiti. Izvorni kodovi napisani na ovaj način definišu jednu SQL-ugnežđenu aplikaciju.

Za aplikaciju kažemo da je SQL-ugnežđena (engl. SQL-embedded) ukoliko postoji barem jedna SQL naredba koja je ugnežđena u matični jezik, kao što su C, C++, Java, i dr.

Proces prevođenja C programa sa ugnežđenim SQL naredbama obično podrazumeva korake koji su dati u nastavku. Ovi koraci pretpostavljaju da u terminalu iz kojeg se pozivaju postoji definisana promenljiva okruženja (engl. environment variable) čiji je identifikator DB2PATH, a čija je vrednost putanja na kojoj je instaliran Db2 SUBP. Pretpostavimo da je na našem operativnom sistemu ova lokacija /opt/ibm/db2/V11.5/. Dodatno, pretpostavimo da je naš sistem 64-bitni. U slučaju da nije, onda je potrebno zameniti sva pojavljivanja niske lib64 niskom lib32 u koracima ispod:

  1. Ostvarivanje konekcije na bazu podataka. Ovo podrazumeva pokretanje naredne naredbe iz terminala:
db2 connect to IMEBAZE user KORISNIK using LOZINKA
  1. Preprocesiranje aplikacije koju izvodi specijalan program koji se naziva db2 preprocesor. Ovaj program kao ulaz prihvata napisanu .sqc datoteku, a kao izlaz konstruiše dve datoteke:

Ovo podrazumeva pokretanje naredne naredbe iz terminala:

db2 PRECOMPILE IMEDATOTEKE.sqc BINDFILE
  1. Ako je kreirana IMEDATOTEKE.bnd datoteka (korišćenjem BINDFILE opcije u PRECOMPILE naredbi u koraku 2), vrši se vezivanje te datoteke sa bazom podataka da bi se kreirao aplikacioni paket.

Vezivanje predstavlja proces kojim se od datoteke sa ekstenzijom .bnd kreira paket koji se čuva u bazi podataka.

Paket sadrži izvršivu formu svakog SQL upita iz izvornog koda aplikacije. Za svaku SQL naredbu se čuvaju razne informacije, kao što su: koji se postojeći indeksi koriste, načini njihovog korišćenja, pristupi tabelama i dr. U suštini, paket predstavlja pristupni plan podacima i igra ključnu ulogu u komunikaciji klijentske aplikacije i Db2 servera.

Ovo podrazumeva pokretanje naredne naredbe iz terminala:

db2 BIND IMEDATOTEKE.bnd
  1. Kompiliranje modifikovanog aplikacionog izvornog koda i izvornih datoteka koji ne sadrže ugnežđeni SQL da bi se kreirale objektne datoteke (jedna ili više njih, zavisno od modularnosti koda; kod nas će uglavnom biti jedna objektna datoteka po primeru). Potrebno je kompilatoru specifikovati gde se nalaze zaglavlja koje nam Db2 sistem nudi. Ovo podrazumeva pokretanje naredne naredbe iz terminala:
cc -I$DB2PATH/include -c IMEDATOTEKE.c
  1. Linkovanje aplikacionih objektnih datoteka sa Db2 bibliotekama i bibliotekama matičnog jezika da bi se kreirao izvršni program. Ovo podrazumeva pokretanje naredne naredbe iz terminala:
cc -o IMEDATOTEKE IMEDATOTEKE.o -Wl,-rpath,$DB2PATH/lib64 -L$DB2PATH/lib64 -ldb2
  1. Raskidanje konekcije sa bazom podataka:
db2 connect reset

S obzirom da je ovaj proces identičan za svaku aplikaciju koju ćemo pisati (uz jednocifren broj izuzetaka), dobro bi bilo automatizovati ga. U tu svrhu, kreirali smo skript prevodjenje čiji je sadržaj dat u nastavku.

Datoteka: vezbe/primeri/poglavlje_2/prevodjenje:

#!/bin/bash

DB2PATH="/opt/ibm/db2/V11.5"
EXTRA_LFLAG="-Wl,-rpath,${DB2PATH}/lib64"

if (test $# -lt 3)
then
    db2 connect to $2 user student using abcdef
else
    db2 connect to $2 user $3 using $4
fi

db2 precompile $1.sqc bindfile
db2 bind $1.bnd
cc -I$DB2PATH/include -c $1.c
cc -o $1 $1.o $EXTRA_LFLAG -L$DB2PATH/lib64 -ldb2

db2 connect reset

Ovaj skript u svojoj osnovnoj varijanti zahteva 4 argumenta:

  1. Naziv datoteke koji se prevodi bez ekstenzije .sqc (u prethodnim koracima vrednost IMEDATOTEKE).

  2. Naziv baze podataka na koju se vrši konekcija (u prethodnim koracima vrednost IMEBAZE).

  3. Naziv korisnika sa kojim se pristupa bazi podataka (u prethodnim koracima vrednost KORISNIK).

  4. Lozinka za pristup bazi podataka za korisnika KORISNIK iz koraka 3 (u prethodnim koracima vrednost LOZINKA).

2.2 Osnovni elementi programiranja C/SQL aplikacija

Kao što smo videli do sada, prilikom prevođenja naših C/SQL programa, očekujemo da je prvi korak procesiranje izvornog koda od strane Db2 preprocesora. Tokom ovog procesa, DB2 preprocesor prolazi kroz izvorni kod .sqc datoteke i izvršava određene akcije kada naiđe na određene DB2 naredbe. Ovakvih naredbi ima dosta, a mi ćemo objasniti neke od njih.

Sve DB2 procesorske naredbe počinju ključnim rečima EXEC SQL, za kojima slede SQL naredbe. U daljem tekstu ćemo podrazumevati postojanje ovih ključnih reči ukoliko se ne navedu eksplicitno.

2.2.1 Naredba INCLUDE

Naredba INCLUDE služi za uključivanje zaglavlja u izvorni kod programa.

Razlikuje se od standardne C preprocesorske direktive #include po tome što se izvršava tokom preprocesiranja od strane DB2 preprocesora. Za razliku od nje, direktiva #include se izvršava tokom prevođenja C programa. Ovo je ključna razlika jer želimo da neka zaglavlja budu dostupna pre nego što se dođe do faze prevođenja C programa.

Da bismo mogli da radimo sa SQL upitima, potrebno je da uključimo zaglavlje SQLCA.h (SQL Communication Area) i to u fazi DB2 preprocesiranja, što se čini naredbom:

EXEC SQL INCLUDE SQLCA;

Primetimo da se ne navodi ekstenzija datoteke. Zaglavlje SQLCA.h predstavlja kolekciju promenljivih koje se ažuriraju pri izvršavanju svakog SQL upita. Kako se budemo susretali sa raznim zahtevima, tako ćemo objašnjavati svaku od neophodnih promenljivih koje su nam dostupne uključivanjem ovog zaglavlja.

2.2.2 Naredbe BEGIN DECLARE SECTION i END DECLARE SECTION

Vrlo često će nam biti neophodno da koristimo promenljive da bismo smeštali rezultate izvršavanja SQL naredbi ili da bismo rezultate nekih izračunavanja u matičnom jeziku koristili kao ulaz SQL naredbi. Međutim, za razliku od regularnih promenljivih u višim programskim jezicima, programiranje SQL naredbi koje zahtevaju korišćenje promenljivih zahteva uvođenje pojma matične promenljive.

Matične promenljive su promenljive koje su definisane u višem programskom jeziku, a koje se ugnežđavaju u SQL naredbe da bi se iskoristile njihove vrednosti ili da bi se rezultati SQL naredbi smestili u njih.

U programskom jeziku C, da bismo deklarisali ovakve promenljive, potrebno je da njihove deklaracije smestimo između para naredbi BEGIN DECLARE SECTION i END DECLARE SECTION. Na primer:

EXEC SQL BEGIN DECLARE SECTION;
double hAverageGrade;
EXEC SQL END DECLARE SECTION;

Par ovih naredbi je moguće smestiti na bilo koje mesto u kodu gde bismo mogli da deklarišemo i standardne C promenljive. U našem slučaju, mi ćemo deklarisati matične promenljive u globalnom opsegu, ali, naravno, to ne mora biti slučaj.

Postoje neka pravila koja se moraju ispoštovati pri korišćenju ovih naredbi. Naredbe BEGIN DECLARE SECTION i END DECLARE SECTION moraju biti uparene, i nije ih je moguće ugnežđavati. Takođe, nije moguće ugnežđavati SQL naredbe između para ovih naredbi. Promenljive koje se deklarišu izvan sekcija definisanih parom ovim naredbama ne smeju imati isti identifikator kao i matične promenljive u tim sekcijama. Naravno, sve promenljive koje se koriste u SQL upitima moraju biti deklarisane u sekciji definisanoj nekim parom ovih naredbi.

Kako se sada ovako definisane matične promenljive koriste? Matičnu promenljivu možemo koristiti u izračunavanjima u matičnom jeziku kao i bilo koje druge promenljive, na primer:

printf("Prosecna ocena studenata je %lf\n", hAverageGrade);

Ukoliko želimo da iskoristimo matičnu promenljivu u SQL naredbi, onda ispred identifikatora promenljive moramo staviti karakter dvotačke, na primer:

EXEC SQL 
    SELECT  AVG(CAST(OCENA AS DOUBLE)) 
    INTO    :hAverageGrade 
    FROM    DA.ISPIT;

2.2.3 DB2 tipovi promenljivih

Kao što znamo, programski jezik C definiše određeni broj osnovnih tipova. Neki od ovih tipova korespondiraju sa DB2 tipovima kolona u tabelama. U narednoj tabeli prikazan je pregled nekih najčešćih tipova kolona u DB2, kao i odgovarajući C tipovi. Ovi tipovi se mogu koristiti za deklaraciju matičnih promenljivih. Kada DB2 preprocesor naiđe na deklaraciju matične promenljive, on određuje prikladni SQL tip. RSUBP zatim koristi ovu vrednost za konverziju podataka koji se razmenjuju između aplikacije i njega. Napomenimo da se tipovi označeni zvezdicom preporučuju u odnosu na druge zbog kompatibilnosti između operativnih sistema.

DB2 tip kolone C tip Opis DB2 tipa
SMALLINT short
short int
sqlint16
16-bitni označeni ceo broj
INTEGER int
long
long int
sqlint32*
32-bitni označeni ceo broj
BIGINT long long
long
__int64
sqlint64*
64-bitni označeni ceo broj
REAL float Broj u pokretnom zarezu jednostruke preciznosti
DOUBLE double Broj u pokretnom zarezu dvostruke preciznosti
DECIMAL(p,s) Nema precizan ekvivalentan tip; koristiti double Pakovan decimalni zapis
CHAR(1) char Jedan karakter
CHAR(n) Nema precizan ekvivalentan tip; koristiti char[n+1] gde je n dovoljno veliko da sadrži podatak, tj. 1 <= n <= 254 Niska nepromenljive dužine
VARCHAR(n) struct tag {
    short int;
    char[n];
}

gde je 1 <= n <= 32672
Niska promenljive dužine bez terminirajuće nule sa 2-bajtnim indikatorom dužine
DATE Nula-terminirajuća karakterna forma Dopustiti barem 11 karaktera da bi se smestio terminirajući karakter
DATE VARCHAR strukturna forma Dopustiti barem 10 karaktera
TIME Nula-terminirajuća karakterna forma Dopustiti barem 9 karaktera da bi se smestio terminirajući karakter
TIME VARCHAR strukturna forma Dopustiti barem 8 karaktera
TIMESTAMP(p) Nula-terminirajuća karakterna forma Dopustiti barem 20-33 karaktera da bi se smestio terminirajući karakter
TIMESTAMP(p) VARCHAR strukturna forma Dopustiti barem 19-32 karaktera

2.2.4 Naredba SELECT INTO

U ovom poglavlju ćemo demonstrirati najjednostavniji slučaj dohvatanja podataka iz baze podataka - u pitanju je slučaj kada rezultat SQL upita sadrži najviše 1 red. Da bismo izvršili ovakve upite, potrebno je da koristimo naredbu SELECT INTO. Njena sintaksa odgovara regularnoj SELECT naredbi, sa time da se za svaku kolonu u jednom redu rezultata navodi u koju se matičnu promenljivu taj rezultat smešta. Na primer, neka su definisane naredne dve matične promenljive:

EXEC SQL BEGIN DECLARE SECTION;
char hFirstExamDate[11];
char hLastExamDate[11];
EXEC SQL END DECLARE SECTION;

Ukoliko želimo da iz tabele ISPIT dohvatimo datume o prvom i poslednjem ispitu, to možemo uraditi narednom naredbom:

EXEC SQL 
    SELECT  MIN(DATPOLAGANJA), 
            MAX(DATPOLAGANJA) 
    INTO    :hFirstExamDate,
            :hLastExamDate
    FROM    DA.ISPIT;

2.2.5 Indikatorske promenljive

Jedno pitanje koje se prirodno postavlja u radu sa SQL podacima jeste kako se rukuje podacima za koje znamo da mogu imati nedostajuće vrednosti u bazi podataka. Na primer, u tabeli DOSIJE kolona MESTORODJENJA može imati nedostajuće vrednosti. Potrebno je da se na neki način indikuje ukoliko se naiđe na vrednost NULL. Za to nam služe tzv. indikatorske promenljive.

Kako su ove promenljive deljene između RSUBP-a i višeg programskog jezika, to je i njih potrebno deklarisati kao matične promenljive. Ove promenljive uzimaju SQL tip vrednosti SMALLINT, odnosno, možemo koristiti tip short u SQL/C kodu.

Indikatorska promenljiva se u SQL naredbi navodi odmah nakon matične promenljive. S obzirom da je i ona sama matična promenljiva, moramo je prefiksovati karakterom dvotačke. Na primer:

EXEC SQL BEGIN DECLARE SECTION;
char hBirthPlace[51];
short hIndBirthPlace;
EXEC SQL END DECLARE SECTION;

// ...

EXEC SQL
    SELECT  MESTORODJENJA
    INTO    :hBirthPlace :hIndBirthPlace
    FROM    DA.DOSIJE
    WHERE   INDEKS = ...;

Ispitivanje da li je neka vrednost NULL ili ne, može se izvršiti proverom vrednosti indikatorske promenljive. Ako je njena vrednost negativan broj, dohvaćena vrednost je NULL i odgovarajuću matičnu promenljivu ne bi trebalo koristiti. U suprotnom, matična promenljiva sadrži odgovarajuću vrednost iz tabele. RSUBP neće promeniti vrednost matične promenljive u slučaju da je dohvaćena vrednost NULL. Primer provere nedostajuće vrednosti se može ilustrovati narednim delom koda:

if (hIndBirthPlace < 0) {
    printf("Podatak ne postoji!\n");
} else {
    printf("Dohvacen je podatak: %s\n", hBirthPlace);
}

2.2.6 Obrada grešaka

Pri radu sa Db2 SQL bazom podataka, možemo očekivati da će se potencijalno pojaviti tri vrste grešaka:

  1. Upozorenjem NOT FOUND se signalizira da upitom nije pronađen nijedan red u tabeli koji je zadovoljavajući.

  2. Upozorenjem SQLWARNING se signalizira da postoji neka akcija koja nije očekivana ili da potencijalno postoji nekakav propust.

  3. Greškom SQLERROR se signalizira da postoji veliki problem koji je neophodno rešiti.

DB2 sistem nam nudi razne načine za obradu grešaka. U osnovi svih ovih načina nalazi se zaglavlje SQLCA.h i razne promenljive, strukture i funkcije koje ona nudi. Komunikacija sa DB2 sistemom je moguća zato što, prilikom preprocesiranja programa, Db2 preprocesor umeće deklaracije raznih matičnih promenljivih na mesto INCLUDE naredbe. Sistem zatim komunicira sa našim programom koristeći promenljive za postavljanje zastavica prilikom upozorenja, kodova za greške i drugih informacija za dijagnozu.

Nakon izvršavanja svake SQL naredbe, sistem daje povratni kod kroz C makroe SQLCODE i SQLSTATE. SQLCODE predstavlja makro koji se razvija u celi broj koji na neki način sumira izvršavanje SQL naredbe, dok je SQLSTATE makro koji se razvija u nisku od 5 karaktera, koja detaljnije opisuje česte kodove za greške među različitim softverskim rešenjima vezane za relacione baze od strane IBM-a. Mi ćemo se najčešće oslanjati na makro SQLCODE prilikom obrade grešaka.

U zavisnosti od vrednosti u koju se razvije makro SQLCODE, razlikujemo naredna tri slučaja:

Da bismo otkrili šta znači određena greška, potrebno je da u komandnoj liniji izvršimo komandu:

db2 "? sql<KOD GRESKE>"

Na primer, ukoliko želimo da vidimo šta znači greška čiji je kod -502, potrebno je da izvršimo:

db2 "? sql-502"

2.2.7 Tok pisanja programa

Ovo poglavlje završavamo sekcijom koja opisuje koji su to osnovni koraci u programiranju C/SQL aplikacija na jednostavnom uvodnom primeru.

Pisanje C programa sa ugnežđenim SQL naredbama obično podrazumeva naredne korake:

  1. Uključivanje potrebnih zaglavlja
  2. Deklaracija matičnih promenljivih
  3. Povezivanje na bazu podataka
  4. Izvršavanje SQL naredbi
  5. Obrada SQL grešaka
  6. Ostvarivanje transakcija
  7. Prekidanje konekcije sa bazom podataka

Zadatak 2.1: Napisati C/SQL program koji ispisuje maksimalni indeks iz tabele ISPIT.

Rešenje. Potrebno je da pratimo prethodno opisane korake za pisanje C programa. U ovom primeru, ignorisaćemo korak 6. Na njega ćemo posvetiti posebnu pažnju u poglavljima koji slede. Dakle, krenimo redom:

Uključivanje potrebnih zaglavlja

Ovaj korak bi trebalo da nam bude jasan do sada:

#include <stdio.h>

EXEC SQL INCLUDE SQLCA;

Deklaracija matičnih promenljivih

Kako je indeks definisan tipom INTEGER, možemo birati neki od tipova int, long, long int ili sqlint32. Pošto je preporučeno koristiti tip sqlint32 zbog kompatibilnosti između operativnih sistema različite bit-nosti, matičnu promenljivu koja će sadržati traženi indeks deklarišemo na sledeći način:

EXEC SQL BEGIN DECLARE SECTION;
sqlint32 hMaxIndex;
EXEC SQL END DECLARE SECTION;

Ostale korake ćemo izvršavati u funkciji int main().

Povezivanje na bazu podataka

Da bismo se povezali na bazu podataka, koristićemo SQL naredbu oblika

CONNECT TO imeBP USER imeKorisnika USING korisnickaLozinka;

U našem slučaju:

EXEC SQL CONNECT TO stud2020 USER student USING abcdef;

Izvršavanje SQL naredbi

U ovom koraku je potrebno da napišemo odgovarajući SQL upit za izračunavanje najvećeg indeksa iz tabele ISPIT i da rezultat smestimo u prethodno definisanu matičnu promenljivu hMaxIndex. Zatim, potrebno je da ispišemo rezultat. To se može uraditi narednim delom koda:

EXEC SQL 
    SELECT  MAX(INDEKS) 
    INTO    :hMaxIndex 
    FROM    DA.ISPIT;

printf("Najveci indeks je %d\n", hMaxIndex);

Obrada SQL grešaka

Naredni deo koda ilustruje najjednostavniji način provere da li je došlo do greške. Definišemo funkciju checkSQL koja će izvršavati proveru grešaka i njihovu obradu. Ova funkcija se poziva nakon svake SQL naredbe koja dolazi do izražaja u fazi izvršavanja. Funkcija kao argument prihvata nisku koja sadrži opis SQL naredbe nakon koje se poziva. Ovo ima smisla zbog toga što, u slučaju da dođe do greške, možemo vrlo jednostavno videti u kom delu izvornog koda je došlo do greške.

#include <stdio.h>
#include <stdlib.h>

EXEC SQL INCLUDE SQLCA;

// Definicija funkcije za obradu gresaka
void checkSQL(const char *str) {
    if(SQLCODE < 0) {
        // Ispisujemo kod greske na standardni izlaz za greske, zajedno sa porukom koju smo prosledili
        fprintf(stderr, "Greska %d: %s\n", SQLCODE, str);

        // Zatvaramo konekciju sa bazom podataka i zavrsavamo program sa neuspehom
        EXEC SQL CONNECT RESET;
        exit(EXIT_FAILURE);
    }
}

int main() {
    EXEC SQL CONNECT TO stud2020 USER student USING abcdef;
    checkSQL("Konekcija na bazu podataka");

    // ...

    EXEC SQL CONNECT RESET;
    checkSQL("Prekidanje konekcije sa bazom podataka");

    return 0;
}

U svakom našem programu ćemo definisati funkciju checkSQL, koja je prikazana iznad. Dodatno, nakon svake EXEC SQL naredbe (koja će se pozvati u fazi izvršavanja programa, dakle, sve naredbe osim EXEC SQL INCLUDE, EXEC SQL BEGIN DECLARE SECTION i EXEC SQL END DECLARE SECTION) neophodno je da pozovemo funkciju checkSQL, pri čemu ćemo joj proslediti nisku koja sadrži opis te SQL naredbe, kako bismo lakše znali na kom mestu u programu je došlo do problema.

Prekidanje konekcije sa bazom podataka

Prekidanje konekcije je vrlo jednostavno. Dovoljno je izvršiti SQL naredbu CONNECT RESET:

EXEC SQL CONNECT RESET;

Time smo ispunili zahtev zadatka. U nastavku je dato celokupno rešenje.

Datoteka: vezbe/primeri/poglavlje_2/zadatak_2_1.sqc:

#include <stdio.h>
#include <stdlib.h>

EXEC SQL INCLUDE SQLCA;

// Deklaracija potrebnih maticnih promenljivih
EXEC SQL BEGIN DECLARE SECTION;
sqlint32 hMaxIndex;
EXEC SQL END DECLARE SECTION;

// Definicija funkcije za obradu gresaka
void checkSQL(const char *str) {
    if(SQLCODE < 0) {
        // Ispisujemo kod greske na standardni izlaz za greske, zajedno sa porukom koju smo prosledili
        fprintf(stderr, "Greska %d: %s\n", SQLCODE, str);

        // Zatvaramo konekciju sa bazom podataka i zavrsavamo program sa neuspehom
        EXEC SQL CONNECT RESET;
        exit(EXIT_FAILURE);
    }
}

int main() {
    // Konekcija na bazu
    EXEC SQL CONNECT TO stud2020 USER student USING abcdef;
    checkSQL("Konekcija na bazu podataka");

    // Izdvajamo maksimalni indeks i smestamo u maticnu promenljivu hMaxIndex
    EXEC SQL 
        SELECT  MAX(INDEKS) 
        INTO    :hMaxIndex 
        FROM    DA.ISPIT;
    checkSQL("Izdvajanje podatka o najvecem indeksu");

    // Ispisujemo podatak koji se sada nalazi u maticnoj promenljivoj hMaxIndex
    printf("Najveci indeks je %d\n", hMaxIndex);

    // Diskonekcija sa baze
    EXEC SQL CONNECT RESET;
    checkSQL("Prekidanje konekcije sa bazom podataka");
    
    return 0;
}

Prevođenje rešenja zadatka 2.1 se vrši pomoću naredne naredbe koja se poziva u terminalu:

./prevodjenje zadatak_2_1 stud2020 student abcdef

Tokom ovog procesa možemo videti da li je došlo do nekih grešaka u fazi prevođenja. Ukoliko je sve prošlo kako treba, kreirana je izvršna datoteka zadatak_2_1 koja se pokreće kao i svaki drugi izvršni C program:

./zadatak_2_1

Zadatak 2.2: Napisati C/SQL program koji ispisuje indeks, ime, prezime, mesto rođenja (ukoliko je navedeno u bazi) i datum diplomiranja (ukoliko je navedeno u bazi) za studenta sa maksimalnim indeksom iz tabele ISPIT.

Rešenje:

Datoteka: vezbe/primeri/poglavlje_2/zadatak_2_2.sqc:

#include <stdio.h>
#include <stdlib.h>

EXEC SQL INCLUDE SQLCA;

EXEC SQL BEGIN DECLARE SECTION;
sqlint32 hIndex;
char hName[51];
char hSurname[51];
char hBirthPlace[51];
short hIndBirthPlace;
char hGradDate[11];
short hIndGradDate;
EXEC SQL END DECLARE SECTION;

void checkSQL(const char *str) {
    if(SQLCODE < 0) {
        fprintf(stderr, "Greska %d: %s\n", SQLCODE, str);

        EXEC SQL CONNECT RESET;
        exit(EXIT_FAILURE);
    }
}

int main() {
    EXEC SQL CONNECT TO stud2020 USER student USING abcdef;
    checkSQL("Konekcija na bazu podataka");

    EXEC SQL 
        SELECT  INDEKS, 
                IME, 
                PREZIME, 
                MESTORODJENJA, 
                DATDIPLOMIRANJA
        INTO    :hIndex, 
                :hName, 
                :hSurname, 
                :hBirthPlace :hIndBirthPlace, 
                :hGradDate :hIndGradDate
        FROM    DA.DOSIJE
        WHERE   INDEKS = (SELECT MAX(INDEKS) FROM DA.DOSIJE);
    checkSQL("Izdvajanje podatka o studentu sa najvecim indeksom");

    printf("+--------------+------------------------------+\n");
    printf("|INDEKS        |%-30d|\n", hIndex);
    printf("|--------------|------------------------------|\n");
    printf("|IME           |%-30s|\n", hName);
    printf("|--------------|------------------------------|\n");
    printf("|PREZIME       |%-30s|\n", hSurname);
    printf("|--------------|------------------------------|\n");
    printf("|MESTO RODJENJA|%-30s|\n", (hIndBirthPlace < 0) ? "NULL" : hBirthPlace);
    printf("|--------------|------------------------------|\n");
    printf("|DATUM DIPL.   |%-30s|\n", (hIndGradDate < 0) ? "NULL" : hGradDate);
    printf("+--------------+------------------------------+\n");

    EXEC SQL CONNECT RESET;
    checkSQL("Prekidanje konekcije sa bazom podataka");

    return 0;
}

Zadatak 2.3: Napisati C/SQL program koji ispisuje indeks, ime, prezime, mesto rođenja (ukoliko je navedeno u bazi) i datum diplomiranja (ukoliko je navedeno u bazi) za studenta čiji se broj indeksa učitava sa standardnog ulaza.

Rešenje:

Datoteka: vezbe/primeri/poglavlje_2/zadatak_2_3.sqc:

#include <stdio.h>
#include <stdlib.h>

EXEC SQL INCLUDE SQLCA;

EXEC SQL BEGIN DECLARE SECTION;
sqlint32 hIndex;
char hName[51];
char hSurname[51];
char hBirthPlace[51];
short hIndBirthPlace;
char hGradDate[11];
short hIndGradDate;
EXEC SQL END DECLARE SECTION;

void checkSQL(const char *str) {
    if(SQLCODE < 0) {
        fprintf(stderr, "Greska %d: %s\n", SQLCODE, str);

        EXEC SQL CONNECT RESET;
        exit(EXIT_FAILURE);
    }
}

int main()
{
    EXEC SQL CONNECT TO stud2020 USER student USING abcdef;
    checkSQL("Konekcija na bazu podataka");

    printf("Unesite broj hIndexa: ");
    scanf("%d", &hIndex);

    EXEC SQL 
        SELECT  INDEKS, 
                IME, 
                PREZIME, 
                MESTORODJENJA, 
                DATDIPLOMIRANJA
        INTO    :hIndex, 
                :hName, 
                :hSurname, 
                :hBirthPlace :hIndBirthPlace, 
                :hGradDate :hIndGradDate
        FROM    DA.DOSIJE
        WHERE   INDEKS = :hIndex;
    checkSQL("Izdvajanje podatka o studentu sa ucitanim indeksom");

    printf("+--------------+------------------------------+\n");
    printf("|INDEKS        |%-30d|\n", hIndex);
    printf("|--------------|------------------------------|\n");
    printf("|IME           |%-30s|\n", hName);
    printf("|--------------|------------------------------|\n");
    printf("|PREZIME       |%-30s|\n", hSurname);
    printf("|--------------|------------------------------|\n");
    printf("|MESTO RODJENJA|%-30s|\n", (hIndBirthPlace < 0) ? "NULL" : hBirthPlace);
    printf("|--------------|------------------------------|\n");
    printf("|DATUM DIPL.   |%-30s|\n", (hIndGradDate < 0) ? "NULL" : hGradDate);
    printf("+--------------+------------------------------+\n");

    EXEC SQL CONNECT RESET;
    checkSQL("Prekidanje konekcije sa bazom podataka");

    return 0;
}

2.2.8 Naredbe INSERT, UPDATE i DELETE

Do sada smo diskutovali o programima u kojima smo dohvatali tačno jedan red u tabeli (ili, eventualno, nijedan), korišćenjem naredbe SELECT INTO. U nastavku sledi zadatak u kojem ćemo demonstrirati upotrebu naredbe INSERT, zatim pretražujućih varijanti naredbi UPDATE i DELETE.

Naredbe UPDATE ili DELETE zovemo pretražujućim ukoliko se podaci koji se ažuriraju, odnosno, brišu pronalaze na osnovu nekih uslova restrikcije u klauzi WHERE.

Ove naredbe se jednostavno implementiraju njihovim navođenjem nakon EXEC SQL. Za razliku od SELECT INTO naredbe, nije važno koliko redova će biti dodato, ažurirano ili obrisano naredbama INSERT, UPDATE i DELETE, redom. Dakle, ovim naredbama možemo promeniti proizvoljan broj redova.

Zadatak 2.4: Napisati naredne funkcije:

  • Napisati funkciju void insertNewCourse() koja sa standardnog ulaza učitava podatke o identifikatoru, oznaci, nazivu i bodovima za novi predmet na fakultetu. Potrebno je uneti te podatke u tabelu PREDMET. Nakon toga, odgovarajućom naredbom proveriti da li su podaci dobro uneti i ispisati ih.

  • Napisati funkciju void updateNewCourse() koja izvršava ažuriranje podataka za novouneseni predmet, tako što se broj bodova duplo povećava i nakon čega se podaci izlistavaju ponovo.

  • Napisati funkciju void deleteNewCourse() koja briše novouneseni predmet iz baze podataka.

Napisati i C/SQL program koji testira napisane funkcije.

Rešenje: S obzirom da se operacija ispisivanja podataka ponavlja u prve dve funkcije, onda ćemo pretragu i ispis podataka o novom predmetu izdvojiti u pomoćnu funkciju printNewCourseInfo, koja će biti pozvana u tim funkcijama.

Datoteka: vezbe/primeri/poglavlje_2/zadatak_2_4.sqc:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

EXEC SQL INCLUDE SQLCA;

EXEC SQL BEGIN DECLARE SECTION;
sqlint32 hCourseId;
char hCourseLabel[21];
char hCourseName[151];
short hESPB;
EXEC SQL END DECLARE SECTION;

void checkSQL(const char *str) {
    if(SQLCODE < 0) {
        fprintf(stderr, "Greska %d: %s\n", SQLCODE, str);

        EXEC SQL CONNECT RESET;
        exit(EXIT_FAILURE);
    }
}

// Deklaracije funkcija za svaki deo zadatka 
void insertNewCourse();
void updateNewCourse();
void deleteNewCourse();

// Pomocna funkcija koja se poziva nakon unosa i izmene novog predmeta
void printNewCourseInfo();

// Glavni deo zadatka
int main() {
    EXEC SQL CONNECT TO stud2020 USER student USING abcdef;
    checkSQL("Konekcija na bazu");

    insertNewCourse();
    updateNewCourse();
    deleteNewCourse();

    EXEC SQL CONNECT RESET;
    checkSQL("Diskonekcija sa baze");

    return 0;
}

// Definicije funkcija za svaki deo zadatka
void insertNewCourse() {
    printf("Identifikator predmeta: ");
    scanf("%d", &hCourseId);

    printf("Oznaka predmeta: ");
    scanf("%s", hCourseLabel);
    // Funkcija scanf() nece procitati karakter za novi red,
    // pa pozivamo jos jednom getchar() kako ga poziv funkcije fgets() ispod ne bi procitao.
    getchar(); 

    printf("Naziv predmeta: ");
    // Funkcija fgets() ce procitati i karakter za novi red na kraju reda,
    // tako da moramo da ga postavimo na terminirajucu nulu,
    // kako ne bismo u bazu uneli karakter za novi red.
    fgets(hCourseName, sizeof(hCourseName), stdin);
    hCourseName[strcspn(hCourseName, "\n")] = '\0';

    printf("Bodovi predmeta: ");
    scanf("%hd", &hESPB);
    
    printf("\nUnosim novi predmet u bazu podataka...\n");

    EXEC SQL 
        INSERT  INTO DA.PREDMET
        VALUES  (:hCourseId, :hCourseLabel, :hCourseName, :hESPB);
    checkSQL("Unos podataka o predmetu");
    
    printf("\nINSERT naredba se uspesno izvrsila!\n");

    printNewCourseInfo();
}

void updateNewCourse()
{
    printf("\nMenjam podatke o novom predmetu u bazi podataka...\n");
    
    EXEC SQL 
        UPDATE  DA.PREDMET
        SET     ESPB = ESPB * 2
        WHERE   ID = :hCourseId;
    checkSQL("Azuriranje podataka o predmetu");
    
    printf("\nUPDATE naredba se uspesno izvrsila!\n");

    printNewCourseInfo();
}

void deleteNewCourse()
{
    printf("\nBrisem podatke o novom predmetu iz baze podataka...\n");
    
    EXEC SQL 
        DELETE  FROM DA.PREDMET
        WHERE   ID = :hCourseId;
    checkSQL("Brisanje podataka o predmetu");

    printf("\nDELETE naredba se uspesno izvrsila!\n");
}

void printNewCourseInfo()
{
    EXEC SQL 
    SELECT  ID, 
            OZNAKA, 
            NAZIV,  
            ESPB
    INTO    :hCourseId, 
            :hCourseLabel, 
            :hCourseName, 
            :hESPB
    FROM    DA.PREDMET
    WHERE   ID = :hCourseId;
    checkSQL("Izlistavanje podataka");

    putc('\n', stdout);
    printf("+--------------+------------------------------+\n");
    printf("|IDENTIFIKATOR |%-30d|\n", hCourseId);
    printf("|--------------|------------------------------|\n");
    printf("|OZNAKA        |%-30s|\n", hCourseLabel);
    printf("|--------------|------------------------------|\n");
    printf("|NAZIV         |%-30s|\n", hCourseName);
    printf("|--------------|------------------------------|\n");
    printf("|ESPB          |%-30hd|\n", hESPB);
    printf("+--------------+------------------------------+\n");
}

2.5 Zadaci za vežbu

Zadatak 2.5: Napisati C/SQL program koji ispisuje podatke za predmet čiji je identifikator 1693.

Zadatak 2.6: Napisati C/SQL program koji ispisuje broj studenata koji su upisali studije u godini koja se unosi sa standardnog ulaza.

Zadatak 2.7: Napisati C/SQL program koji za svaku ocenu od 6 do 10 ispisuje ime i prezime studenta koji je poslednji položio neki ispit sa tom ocenom. U slučaju da ima više takvih studenata, klauzom LIMIT 1 naredbe SELECT INTO se osigurati da rezultat vrati najviše 1 rezultat. (Pomoć: Koristiti for petlju za menjanje vrednosti matične promenljive koja sadrži ocenu, pa u svakoj iteraciji dohvatiti informaciju za tekuću vrednost te matične promenljive.)

Zadatak 2.8: Napisati C/SQL program kojim se dodaje da je za polaganje predmeta čiji je identifikator 2343 uslov da se položi predmet čiji je identifikator 2327, na studijskom programu sa identifikatorom 103.

Zadatak 2.9: Napisati C/SQL program kojim se dodaje novi studijski program prvog stepena čiji se podaci unose sa standardnog ulaza.

Zadatak 2.10: Napisati C/SQL program kojim se svim obaveznim predmetima na studijskom programu 'Informatika' povećava broj bodova za 2.

Zadatak 2.11: Napisati C/SQL program kojim se, za sve položene ispite čiji se naziv predmeta unosi sa standardnog ulaza, ocena uvećava za 1.

Zadatak 2.12: Napisati C/SQL program kojim se brišu svi podaci o ispitima za studenta čiji se indeks unosi sa standardnog ulaza.

Zadatak 2.13: Napisati C/SQL program kojim se ukida uslovnost svih predmeta koji su uslovni da bi se položio predmet čiji se identifikator unosi sa standardnog ulaza.