2015 Databázové systémy I PROJEKT 2 ČÁST MIROSLAV POKORNÝ Stránka 0 z 21
Zadání Implementujte datový model vytvořený v první části projektu do relační databáze a vytvořte pohledy a uloženou proceduru dle připojeného zadání pro jednotlivé scénáře. Nejdříve ala opravte chyby z první části projektu. Některé projekty obsahují natolik závažné chyby v návrhu datového modelu, že splnění dalších bodů je nemožné. Proto prosím opravujte chyby, které Vám byly vytknuty. 1. Vytvořte tabulky dle datového modelu se správnými datovými typy. 2. Vytvořte primární a cizí klíče. Pro vytvoření alespoň jednoho primárního klíče použijte sekvenci. 3. Vytvořte indexy. Některé indexy musí být na neklíčových sloupcích. 4. Vložte testovací data - hodnotí se objem (1 řádek nestačí) a kvalita (budou dávat smysl) Konkrétní zadání Stránka 1 z 21
Obsah Zadání... 1 Konkrétní zadání... 1 Obsah... 2 Vytvoření databáze (SQL dotaz)... 3 Naplnění databáze daty (SQL dotazy)... 6 Naplnění dat tabulky DruhKontaktu... 6 Naplnění dat tabulky DruhPokoje... 7 Naplnění tabulky Pokoj... 8 Naplnění tabulky Zakaznik... 9 Naplnění tabulky Kontakt... 10 Naplnění tabulky Rezervace... 11 Naplnění tabulky DenRezervace... 12 Naplnění tabulky Pobyt... 13 Naplnění tabulky DenPobytu... 14 Naplnění tabulky Faktura... 15 Specifické požadavky na projekt hotel... 16 Procedura... 16 Pohled dostupné pokoje... 18 Pohled nejčastější zákazník... 19 Vlastní pohled... 20 Přílohy... 21 Stránka 2 z 21
Vytvoření databáze (SQL dotaz) create table Zakaznik ( idzakaznik integer not null identity(1,1), pohlavi nvarchar(4), jmeno nvarchar(50) not null, prijmeni nvarchar(50) not null, datumnarozeni datetime not null, constraint pk_zakaznik primary key (idzakaznik), constraint chk_pohlavi check (pohlavi in ('muž', 'žena', null)) ); create table DruhKontaktu ( iddruhkontaktu integer not null identity(1,1), nazevkontaktu nvarchar(50) not null, constraint pk_druhkontaktu primary key (iddruhkontaktu), constraint uq_nazevkontaktu unique (nazevkontaktu) ); create table Kontakt ( idzakaznik integer not null, iddruhkontaktu integer not null, hodnota nvarchar(100) not null, constraint pk_kontakt primary key (idzakaznik, iddruhkontaktu) ); create table Rezervace ( idrezervace integer not null identity(1,1), idpokoj integer not null, idzakaznik integer not null, storno bit not null default 0, constraint pk_rezervace primary key (idrezervace) ); create table DenRezervace ( iddenrezervace integer not null identity(1,1), den datetime not null, idrezervace integer not null, constraint pk_denrezervace primary key (iddenrezervace) ); create table Pokoj ( idpokoj integer not null identity(1,1), iddruhpokoje integer not null, constraint pk_pokoj primary key (idpokoj) ); create table DruhPokoje ( iddruhpokoje integer not null identity(1,1), cena money not null, nazevpokoje nvarchar(50), constraint pk_druhpokoje primary key (iddruhpokoje) ); create table Pobyt ( idpobyt integer not null identity(1,1), idzakaznik integer not null, idpokoj integer not null, constraint pk_pobyt primary key (idpobyt) ); Stránka 3 z 21
create table DenPobytu ( iddenpobytu integer not null identity(1,1), den datetime not null, idpobyt integer not null, constraint pk_denpobytu primary key (iddenpobytu) ); create table Faktura ( idfaktura integer not null identity(1,1), cenapobytu money not null, idpobyt integer not null, constraint pk_faktura primary key (idfaktura), constraint uq_idpobyt unique (idpobyt) ); alter table Faktura add constraint [fk_faktura(idpobyt)_pobyt(idpobyt)] foreign key (idpobyt) references Pobyt(idPobyt); alter table Kontakt add constraint [fk_kontakt(iddruhkontaktu)_druhkontaktu(iddruhkontaktu)] foreign key (iddruhkontaktu) references DruhKontaktu(idDruhKontaktu); alter table Kontakt add constraint [fk_kontakt(idzakaznik)_zakaznik(idzakaznik)] foreign key (idzakaznik) references Zakaznik(idZakaznik); alter table DenPobytu add constraint [fk_denpobytu(idpobyt)_pobyt(idpobyt)] foreign key (idpobyt) references Pobyt(idPobyt); alter table Pobyt add constraint [fk_pobyt(idzakaznik)_zakaznik(idzakaznik)] foreign key (idzakaznik) references Zakaznik(idZakaznik); alter table Pobyt add constraint [fk_pobyt(idpokoj)_pokoj(idpokoj)] foreign key (idpokoj) references Pokoj(idPokoj); alter table Pokoj add constraint [fk_pokoj(iddruhpokoje)_druhpokoje(iddruhpokoje)] foreign key (iddruhpokoje) references DruhPokoje(idDruhPokoje); alter table DenRezervace add constraint [fk_denrezervace(idrezervace)_rezervace(idrezervace)] foreign key (idrezervace) references Rezervace(idRezervace); alter table Rezervace add constraint [fk_rezervace(idzakaznik)_zakaznik(idzakaznik)] foreign key (idzakaznik) references Zakaznik(idZakaznik); alter table Rezervace add constraint [fk_rezervace(idpokoj)_pokoj(idpokoj)] foreign key (idpokoj) references Pokoj(idPokoj); Výsledek spuštění dotazu lze vidět na následujícím obrázku (Obrázek 1 Výsledné schéma databáze po spuštění vytvářecího Obrázek 1). Stránka 4 z 21
Kontakt Zakaznik idzakaznik idzakaznik pohlavi iddruhkontaktu hodnota jmeno prijmeni datumnarozeni Rezervace idrezervace idpokoj idzakaznik storno Pokoj idpokoj iddruhpokoje DruhPokoje iddruhpokoje cena nazevpokoje DruhKontaktu iddruhkontaktu nazevkontaktu DenRezervace iddenrezervace den idrezervace Pobyt DenPobytu idpobyt iddenpobytu idzakaznik den idpokoj idpobyt Faktura idfaktura cenapobytu idpobyt Obrázek 1 Výsledné schéma databáze po spuštění vytvářecího dotazu Stránka 5 z 21
Naplnění databáze daty (SQL dotazy) Pozn. skripty jsou pro demonstraci zkráceny, plná verze skriptu je v přiloženém souboru Pozn. pro vyjádření data (datetime) je použit formát timestamp, což může na některých SQL serverech způsobit chybu při vkládání data do tabulky. Pokud nastane tato chyba, je nutné spustit následující dotaz, pomocí kterého se změní aktuální jazyk a zároveň s ním i formát data. SET LANGUAGE us_english; Naplnění dat tabulky DruhKontaktu INSERT INTO dbo.druhkontaktu (nazevkontaktu) VALUES (N'PSČ'), (N'Ulice'), (N'Město'), (N'Telefon'), (N'Email'); Obrázek 2 Zobrazení vložených dat z tabulky "DruhKontaktu" Stránka 6 z 21
Naplnění dat tabulky DruhPokoje INSERT INTO dbo.druhpokoje (cena, nazevpokoje) VALUES (500, N'Single'), (750, N'Double'), (800, N'Twin'), (2500, N'Apartmán'), (10000, N'Presidentský apartmán'); Obrázek 3 Zobrazení vložených dat z tabulky "DruhPokoje" Stránka 7 z 21
Naplnění tabulky Pokoj INSERT INTO dbo.pokoj (iddruhpokoje) VALUES (1), (1), (1), (1), --zkráceno (4), (4), (5); Obrázek 4 Zobrazení vložených dat z tabulky "Pokoj" Stránka 8 z 21
Naplnění tabulky Zakaznik INSERT INTO Zakaznik (pohlavi, jmeno, prijmeni, datumnarozeni) VALUES (N'muž', N'Vojtěch', N'Princ', '1975-12-28 00:00:00'), (N'žena', N'Veronika', N'Balážová', '1935-02-05 00:00:00'), (N'muž', N'David', N'Dědek', '1960-04-30 00:00:00'), (N'žena', N'Marie', N'Matušková', '1963-06-23 00:00:00'), (N'muž', N'Jiří', N'Kozubík', '1932-08-27 00:00:00'), (N'žena', N'Magda', N'Novotná', '1977-06-28 00:00:00'), (N'žena', N'Daniela', N'Kalinová', '1933-10-11 00:00:00'), (N'muž', N'Ivan', N'Špičák', '1987-03-26 00:00:00'), (N'žena', N'Alena', N'Škvařilová', '1965-11-29 00:00:00'), (N'žena', N'Eva', N'Šedová', '1933-05-01 00:00:00'), (N'muž', N'Jan', N'Kout', '1974-09-29 00:00:00'), --zkráceno Obrázek 5 Zobrazení vložených dat z tabulky "Zakaznik" Stránka 9 z 21
Naplnění tabulky Kontakt INSERT INTO Kontakt (idzakaznik, iddruhkontaktu, hodnota) VALUES (1, 2, N'Pardubská 1608'), (1, 3, N'Vizovice'), (1, 1, N'763 12'), (1, 5, N'VojtechPrinc@gustr.com'), (1, 4, N'737 830 031'), (2, 2, N'Na Průhonu 725'), (2, 3, N'Plzen 12'), (2, 1, N'312 00'), (2, 5, N'VeronikaBalazova@fleckens.hu'), (2, 4, N'375 935 213'), (3, 2, N'Bedřicha Smetany 1461'), (3, 3, N'Nedakonice'), (3, 1, N'687 38'), (3, 5, N'DavidDedek@armyspy.com'), (3, 4, N'601 244 130'), --zkráceno Obrázek 6 Zobrazení vložených dat z tabulky "Kontakt" Stránka 10 z 21
Naplnění tabulky Rezervace INSERT INTO Rezervace (idpokoj, idzakaznik, storno) VALUES (1, 971, 0), (1, 905, 0), (1, 1340, 0), (1, 2339, 1), (1, 1476, 0), (1, 1272, 1), (1, 1158, 0), (1, 1920, 0), (1, 1835, 0), (1, 2950, 0), (1, 2306, 0), (1, 766, 1), (1, 101, 0), (1, 962, 0), (1, 623, 0), (1, 1856, 1), (1, 1274, 0), (1, 2819, 0), (1, 112, 0), (1, 551, 0), --zkráceno Obrázek 7 Zobrazení vložených dat z tabulky "Rezervace" Stránka 11 z 21
Naplnění tabulky DenRezervace INSERT INTO DenRezervace (idrezervace, den) VALUES (1, '2014-07-04 00:00:00'), (1, '2014-07-05 00:00:00'), (1, '2014-07-06 00:00:00'), (1, '2014-07-07 00:00:00'), (1, '2014-07-08 00:00:00'), (1, '2014-07-09 00:00:00'), (1, '2014-07-10 00:00:00'), (1, '2014-07-11 00:00:00'), (1, '2014-07-12 00:00:00'), (1, '2014-07-13 00:00:00'), (1, '2014-07-14 00:00:00'), (2, '2014-07-25 00:00:00'), (2, '2014-07-26 00:00:00'), (2, '2014-07-27 00:00:00'), --zkráceno Obrázek 8 Zobrazení vložených dat z tabulky "DenRezervace" Stránka 12 z 21
Naplnění tabulky Pobyt INSERT INTO Pobyt (idpokoj, idzakaznik) VALUES (1, 971), (1, 905), (1, 1340), (1, 1476), (1, 1158), (1, 1920), (1, 1835), (1, 2950), (1, 2306), (1, 101), (1, 962), (1, 623), (1, 1274), --zkráceno Obrázek 9 Zobrazení vložených dat z tabulky "Pobyt" Stránka 13 z 21
Naplnění tabulky DenPobytu INSERT INTO DenPobytu (idpobyt, den) VALUES (1, '2014-07-04 00:00:00'), (1, '2014-07-05 00:00:00'), (1, '2014-07-06 00:00:00'), (1, '2014-07-07 00:00:00'), (1, '2014-07-08 00:00:00'), (1, '2014-07-09 00:00:00'), (1, '2014-07-10 00:00:00'), (1, '2014-07-11 00:00:00'), (1, '2014-07-12 00:00:00'), (1, '2014-07-13 00:00:00'), (1, '2014-07-14 00:00:00'), (2, '2014-07-25 00:00:00'), (2, '2014-07-26 00:00:00'), --zkraceno Obrázek 10 Zobrazení vložených dat z tabulky "DenPobytu" Stránka 14 z 21
Naplnění tabulky Faktura INSERT INTO dbo.faktura (cenapobytu, idpobyt) SELECT count(p.idpobyt)*dp2.cena AS cena, p.idpobyt FROM dbo.pobyt p INNER JOIN dbo.pokoj p2 ON p2.idpokoj = p.idpokoj INNER JOIN dbo.denpobytu dp ON dp.idpobyt = p.idpobyt INNER JOIN dbo.druhpokoje dp2 ON dp2.iddruhpokoje = p2.iddruhpokoje GROUP BY p.idpobyt, dp2.cena ORDER BY p.idpobyt; Obrázek 11 Zobrazení vložených dat z tabulky "Faktura" Stránka 15 z 21
Specifické požadavky na projekt hotel Proceduru lze nalézt v přiloženém souboru. Pohledy lze nalézt také v přiložených souborech. Procedura CREATE PROCEDURE vytvor_rezervaci @idpokoj integer, @datumod datetime = null, @datumdo datetime = null, @pohlavi nvarchar(4) = null, @jmeno nvarchar(50), @prijmeni nvarchar(50), @datumnarozeni datetime, @ulice nvarchar(100) = null, @mesto nvarchar(100) = null, @psc nvarchar(100) = null, @email nvarchar(100) = null, @telefon nvarchar(100) = null AS DECLARE @idulice integer, @idmesto integer, @idpsc integer, @idemail integer, @idtelefon integer, @idzakaznik integer = NULL, @idrezervace integer, @datumtmp datetime BEGIN TRANSACTION BEGIN TRY BEGIN SET @idulice = (SELECT dk.iddruhkontaktu FROM dbo.druhkontaktu dk WHERE dk.nazevkontaktu = 'ulice') SET @idmesto = (SELECT dk.iddruhkontaktu FROM dbo.druhkontaktu dk WHERE dk.nazevkontaktu = 'město') SET @idpsc = (SELECT dk.iddruhkontaktu FROM dbo.druhkontaktu dk WHERE dk.nazevkontaktu = 'psč') SET @idtelefon = (SELECT dk.iddruhkontaktu FROM dbo.druhkontaktu dk WHERE dk.nazevkontaktu = 'telefon') SET @idemail = (SELECT dk.iddruhkontaktu FROM dbo.druhkontaktu dk WHERE dk.nazevkontaktu = 'email') SET @idzakaznik = (SELECT z.idzakaznik FROM dbo.zakaznik z WHERE z.jmeno = @jmeno AND z.prijmeni = @prijmeni AND z.datumnarozeni = cast(@datumnarozeni AS date)) IF @idzakaznik IS NULL BEGIN --Uživatel neexistuje, vytvoření uživatele CREATE TABLE #tmp (idzakaznik integer) INSERT INTO dbo.zakaznik (pohlavi, jmeno, prijmeni, datumnarozeni) OUTPUT INSERTED.idZakaznik INTO #tmp VALUES (@pohlavi, @jmeno, @prijmeni, @datumnarozeni) SET @idzakaznik = (SELECT b.idzakaznik FROM #tmp b) DROP TABLE #tmp; IF @psc IS NOT NULL INSERT INTO dbo.kontakt(idzakaznik, iddruhkontaktu, hodnota) VALUES (@idzakaznik, @idpsc, @psc) IF @mesto IS NOT NULL INSERT INTO dbo.kontakt(idzakaznik, iddruhkontaktu, hodnota) VALUES (@idzakaznik, @idmesto, @mesto) IF @ulice IS NOT NULL INSERT INTO dbo.kontakt(idzakaznik, iddruhkontaktu, hodnota) VALUES (@idzakaznik, @idulice, @ulice) IF @telefon IS NOT NULL INSERT INTO dbo.kontakt(idzakaznik, iddruhkontaktu, hodnota) VALUES (@idzakaznik, @idtelefon, @telefon) IF @email IS NOT NULL INSERT INTO dbo.kontakt(idzakaznik, iddruhkontaktu, hodnota) VALUES (@idzakaznik, @idemail, @email) SELECT z.*, k.* FROM dbo.zakaznik z INNER JOIN dbo.kontakt k ON k.idzakaznik = z.idzakaznik WHERE z.idzakaznik = @idzakaznik END Stránka 16 z 21
--ELSE uživatel již existuje není nutné ho vytvářet IF (@idpokoj IS NULL) --THROW 50003, 'id Pokoje nesmí nabývat hodnoty NULL', 1 RAISERROR ('id Pokoje nesmí nabývat hodnoty NULL', 11, 1) IF (@datumod IS NULL OR @datumdo IS NULL) --THROW 50001, 'Datumy nesmí nabývat hodnoty NULL', 1 RAISERROR ('Datumy nesmí nabývat hodntoy NULL', 11, 1) SET @datumod = cast(@datumod AS date) SET @datumdo = cast(@datumdo AS date) IF (@datumod > @datumdo) --THROW 50000, 'Datum od je více v budoucnosti než datum do', 1 RAISERROR ('Datum od je více v budoucnosti než datum do', 11, 1) IF EXISTS ( SELECT dr.den FROM dbo.denrezervace dr INNER JOIN dbo.rezervace r ON r.idrezervace = dr.idrezervace WHERE r.idpokoj = @idpokoj AND (dr.den >= @datumod AND dr.den <= @datumdo)) --THROW 50002, 'V zadaném datumu již rezervace na tento pokoj existuje', 1 RAISERROR ('V zadaném datumu již rezervace na tento pokoj existuje', 11, 1) ELSE BEGIN --Rezervace na pokoj v tomto datu neexsituje CREATE TABLE #tmp2 (idrezervace integer) INSERT INTO dbo.rezervace (idpokoj,idzakaznik, storno) OUTPUT INSERTED.idRezervace INTO #tmp2 VALUES (@idpokoj,@idzakaznik,0) SET @idrezervace = (SELECT #tmp2.idrezervace FROM #tmp2) DROP TABLE #tmp2 SET @datumtmp = @datumod WHILE (@datumtmp <= @datumdo) BEGIN INSERT INTO dbo.denrezervace (den, idrezervace) VALUES (@datumtmp, @idrezervace) SET @datumtmp = dateadd(day, 1, @datumtmp) END END END END TRY BEGIN CATCH SELECT ERROR_NUMBER() AS ErrorNumber, ERROR_SEVERITY() AS ErrorSeverity, ERROR_STATE() AS ErrorState, ERROR_PROCEDURE() AS ErrorProcedure, ERROR_LINE() AS ErrorLine, ERROR_MESSAGE() AS ErrorMessage IF @@TRANCOUNT > 0 BEGIN ROLLBACK TRANSACTION; RETURN -1 END END CATCH IF @@TRANCOUNT > 0 COMMIT TRANSACTION; GO Stránka 17 z 21
Pohled dostupné pokoje CREATE VIEW dostupne_pokoje AS SELECT p.idpokoj,dp.nazevpokoje, CASE WHEN p.idpokoj NOT IN ( SELECT idpokoj FROM dbo.denrezervace dr INNER JOIN dbo.rezervace r ON r.idrezervace = dr.idrezervace WHERE den = cast(getdate() AS date) AND NOT (storno = 1) ) then 'Dostupný' else 'Nedostupný' END as dostupnost FROM dbo.pokoj p INNER JOIN dbo.druhpokoje dp ON dp.iddruhpokoje = p.iddruhpokoje Obrázek 12 Výsledek selekčního dotazu nad pohledem "dostupne_pokoje" Stránka 18 z 21
Pohled nejčastější zákazník Pozn. dle zadání má pohled vracet seřazené zákazníky od největšího počtu návštěv po nejmenší, ale dle specifikací Transact SQL nelze použít příkaz order by v příkazu create view, místo toho se má order by použít až v selekčním dotazu na daný pohled. Viz https://msdn.microsoft.com/en-us/library/ms187956.aspx CREATE VIEW nejcastejsi_zakaznici AS SELECT z.idzakaznik, z.jmeno, z.prijmeni, min(dp.den) AS prvninavsteva, count(distinct p.idpobyt) AS pocetpobytu FROM dbo.zakaznik z LEFT JOIN dbo.pobyt p ON p.idzakaznik = z.idzakaznik LEFT JOIN dbo.denpobytu dp ON dp.idpobyt = p.idpobyt GROUP BY z.idzakaznik, z.jmeno, z.prijmeni --ORDER BY count(p.idpobyt) DESC, prvninavsteva ASC, z.jmeno ASC, z.prijmeni ASC Obrázek 13 Výsledek selekčního dotazu nad pohledem "nejcastejsi_zakaznici" Stránka 19 z 21
Vlastní pohled Následující pohled zobrazí rezervaci včetně, jména osoby, na kterou je rezervace uskutečněna, čísla pokoje, názvu pokoje, data od kdy je pokoj rezervován, data do kdy je pokoj rezervován a počtu dní na které je pokoj rezervován. CREATE VIEW rezervace_od_do AS SELECT r.idrezervace, z.jmeno, z.prijmeni, p.idpokoj, dp.nazevpokoje, min(dr.den) AS od, max(dr.den) AS do, count(dr.den) AS pocetdnu FROM dbo.rezervace r INNER JOIN dbo.denrezervace dr ON dr.idrezervace = r.idrezervace INNER JOIN dbo.pokoj p ON p.idpokoj = r.idpokoj INNER JOIN dbo.druhpokoje dp ON dp.iddruhpokoje = p.iddruhpokoje INNER JOIN dbo.zakaznik z ON z.idzakaznik = r.idzakaznik GROUP BY r.idrezervace, z.jmeno, z.prijmeni, p.idpokoj, dp.nazevpokoje Obrázek 14 Výsledek selekčního dotazu nad pohledem "rezervace_od_do" Stránka 20 z 21
Přílohy K tomuto dokumentu byly přidány tyto soubory: SQL dotazy pro vytvoření tabulek 01_create.sql SQL dotazy pro naplnění tabulek daty (všechny data, nezkráceno) 02_insert_data.sql SQL dotaz pro vytvoření procedury 03_procedura.sql SQL dotaz pro vytvoření pohledu dostupné pokoje 04_view_dostupne_pokoje.sql SQL dotaz pro vytvoření pohledu nejčastější zákazník 05_view_nejcastejsi_zakaznik.sql SQL dotaz pro vytvoření pohledu rezervace od do 06_view_rezervace_od_do.sql Stránka 21 z 21