10 KiB
Creazione tabelle
Dopo aver creato il database, il secondo passo della progettazione fisica è stato quello di convertire lo schema logico in un database Postgres.
In generale:
- Le entità sono diventate TABLES (tabelle);
- Gli attributi opzionali sono diventati COLUMNS (colonne);
- Gli attributi obbligatori sono diventati COLUMNS con il vincolo NOT NULL;
- Le chiavi primarie sono state implementate come PRIMARY KEYS (chiavi primarie);
- Le chiavi esterne sono state implementate come FOREIGN KEYS (chiavi esterne);
- Le chiavi surrogate sono state implementate come PRIMARY KEYS autoincrementate tramite SEQUENCES (sequenze);
- I dati derivati sono stati implementati come COLUMNS aventi dei TRIGGER che le aggiornino.
Schema dei nomi delle tabelle
Tutte le tabelle sono state istanziate con il nome che le corrispondenti entità avevano nello schema logico, sostituendo tutte le lettere maiuscole con lettere minuscole a-z
, spazi con underscore _
e rimuovendo le parentesi con il loro contenuto.
Inoltre, a tutte le tabelle tranne utente
è stato dato un nome prefissato da libro_
, audiolibro_
, film_
e gioco_
per indicare la categoria a cui le entità appartenevano nello schema logico.
Esempi
Entità | Tabella |
---|---|
Utente |
utente |
Libro |
libro |
Edizione (libro) |
libro_edizione |
Cast |
film_cast |
Creazione tabelle
Si riportano solo le tabelle con qualche particolarità; le tabelle per la quale la conversione è banale sono omesse da questo file (ma non dal file 5-database.sql
).
audiolibro_edizione
CREATE TABLE public.audiolibro_edizione (
isbn integer NOT NULL,
titolo character varying NOT NULL,
durata interval,
immagine bytea,
relativa_a integer NOT NULL
);
ALTER TABLE ONLY public.audiolibro_edizione
ADD CONSTRAINT audiolibro_edizione_pkey PRIMARY KEY (isbn);
L'immagine relativa all'audiolibro è archiviata nella base di dati come un blob binario di dati.
audiolibro_recensione
CREATE TABLE public.audiolibro_recensione (
id bigint NOT NULL,
commento text NOT NULL,
valutazione smallint NOT NULL,
data timestamp without time zone NOT NULL,
CONSTRAINT audiolibro_recensione_valutazione_check CHECK (((valutazione >= 0) AND (valutazione <= 100)))
);
ALTER TABLE ONLY public.audiolibro_recensione
ADD CONSTRAINT audiolibro_recensione_pkey PRIMARY KEY (id);
La valutazione delle recensioni deve essere obbligatoriamente tra 0 e 100: a tale scopo, è stato introdotto un CHECK sulla tabella.
La data di pubblicazione è rappresentata da un timestamp.
film
CREATE TABLE public.film (
eidr character(34) NOT NULL,
titolo character varying NOT NULL,
sinossi text,
locandina bytea,
durata integer,
CONSTRAINT film_durata_check CHECK ((durata >= 0))
);
ALTER TABLE ONLY public.film
ADD CONSTRAINT film_pkey PRIMARY KEY (eidr);
I film hanno un CHECK che impedisce alla loro durata di essere minore di 0 minuti, nel caso essa sia definita.
Il loro eidr
è una stringa di lunghezza costante char, in quanto gli EIDR sono sempre lunghi 34 caratteri.
Inoltre, come per gli audiolibri, la loro locandina è immagazzinata nel database come bytea.
film_correlazioni
CREATE TABLE public.film_correlazioni (
eidr_1 character(34) NOT NULL,
eidr_2 character(34) NOT NULL
);
ALTER TABLE ONLY public.film_correlazioni
ADD CONSTRAINT film_correlazioni_pkey PRIMARY KEY (eidr_1, eidr_2);
ALTER TABLE ONLY public.film_correlazioni
ADD CONSTRAINT eidr_1 FOREIGN KEY (eidr_1) REFERENCES public.film(eidr);
ALTER TABLE ONLY public.film_correlazioni
ADD CONSTRAINT eidr_2 FOREIGN KEY (eidr_2) REFERENCES public.film(eidr);
L'autoassociazione delle correlazioni è stata implementata attraverso una tabella ponte che collega due film attraverso i loro eidr
.
eidr_1
ed eidr_2
sono due chiavi esterne separate, e insieme formano la chiave primaria composta della tabella.
film_vi_ha_preso_parte
CREATE TABLE public.film_vi_ha_preso_parte (
eidr character(34) NOT NULL,
id_cast integer NOT NULL,
id_ruolo integer NOT NULL
);
ALTER TABLE ONLY public.film_vi_ha_preso_parte
ADD CONSTRAINT film_vi_ha_preso_parte_pkey PRIMARY KEY (eidr, id_cast, id_ruolo);
ALTER TABLE ONLY public.film_vi_ha_preso_parte
ADD CONSTRAINT eidr FOREIGN KEY (eidr) REFERENCES public.film(eidr);
ALTER TABLE ONLY public.film_vi_ha_preso_parte
ADD CONSTRAINT id_cast FOREIGN KEY (id_cast) REFERENCES public.film_cast(id);
ALTER TABLE ONLY public.film_vi_ha_preso_parte
ADD CONSTRAINT id_ruolo FOREIGN KEY (id_ruolo) REFERENCES public.film_ruolo(id);
L'associazione ternaria è stata realizzata con una tabella ponte, con una chiave primaria composta e tre chiavi esterne separate.
elemento_id_seq
CREATE SEQUENCE public.elemento_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
Si è deciso di rendere univoci gli id
di tutti gli elementi, qualsiasi fosse il loro tipo.
Per realizzare l'unicità si è creata una unica SEQUENCE, che viene usata da tutte le tabelle *_elemento
.
gioco_stato
e gioco_provenienza
CREATE TYPE public.gioco_provenienza AS ENUM (
'GRATUITO',
'ACQUISTATO',
'IN_ABBONAMENTO',
'PRESO_IN_PRESTITO',
'NON_PIU_POSSEDUTO',
'ALTRO'
);
CREATE TYPE public.gioco_stato AS ENUM (
'DA_INIZIARE',
'INIZIATO',
'FINITO',
'COMPLETATO',
'NON_APPLICABILE'
);
Gli stati e le provenienze degli elementi sono state realizzate tramite ENUM contenenti tutte le possibili opzioni selezionabili dall'utente.
gioco_elemento
CREATE TABLE public.gioco_elemento (
id bigint DEFAULT nextval('public.elemento_id_seq'::regclass) NOT NULL,
stato public.gioco_stato,
provenienza public.gioco_provenienza,
istanza_di integer NOT NULL,
appartiene_a character varying NOT NULL
);
ALTER TABLE ONLY public.gioco_elemento
ADD CONSTRAINT gioco_elemento_pkey PRIMARY KEY (id);
ALTER TABLE ONLY public.gioco_elemento
ADD CONSTRAINT appartiene_a FOREIGN KEY (appartiene_a) REFERENCES public.utente(username);
ALTER TABLE ONLY public.gioco_elemento
ADD CONSTRAINT istanza_di FOREIGN KEY (istanza_di) REFERENCES public.gioco_edizione(id);
CREATE FUNCTION public.update_n_giochi() RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
IF (TG_OP = 'DELETE') THEN
UPDATE utente
SET gioco_elementi_posseduti = gioco_elementi_posseduti - 1
FROM gioco_elemento
WHERE utente.username = old.appartiene_a;
RETURN old;
ELSIF (TG_OP = 'INSERT') THEN
UPDATE utente
SET gioco_elementi_posseduti = gioco_elementi_posseduti + 1
FROM gioco_elemento
WHERE utente.username = new.appartiene_a;
RETURN new;
END IF;
END;
$$;
CREATE TRIGGER numero_giochi_trigger
BEFORE INSERT OR DELETE
ON public.gioco_elemento
FOR EACH ROW EXECUTE PROCEDURE public.update_n_giochi();
Le tabelle degli elementi hanno due colonne stato
e provenienza
di tipo *_stato
e *_provenienza
rispettivamente: sono gli ENUM creati in precedenza, che impediscono che vengano inseriti valori non consentiti nella tabella.
La colonna id
invece ha un valore di DEFAULT particolare: nextval('public.elemento_id_seq'::regclass)
.
Significa che, se non viene specificato un id
durante un INSERT, alla riga verrà assegnato automaticamente il valore corrente della SEQUENCE, e il valore della sequenza sarà aumentato, garantendo l'unicità degli id
anche attraverso tabelle diverse.
Inoltre, nella tabella è presente un TRIGGER, che incrementa o decrementa il conteggio dei giochi di un utente quando egli rispettivamente crea o elimina nuovi elementi.
libro_edizione
create function is_numeric(text character varying) returns boolean
strict
language plpgsql
as
$$
DECLARE x NUMERIC;
BEGIN
x = $1::NUMERIC;
RETURN TRUE;
EXCEPTION WHEN others THEN
RETURN FALSE;
END;
$$;
CREATE TABLE public.libro_edizione (
isbn character(13) NOT NULL,
titolo_edizione character varying NOT NULL,
pagine integer,
copertina bytea,
relativa_a integer NOT NULL,
CONSTRAINT libro_edizione_isbn_check CHECK ((public.is_numeric(("substring"((isbn)::text, 1, 12))::character varying) AND (public.is_numeric(("right"((isbn)::text, 1))::character varying) OR ("right"((isbn)::text, 1) ~~ '%X'::text)))),
CONSTRAINT libro_edizione_pagine_check CHECK ((pagine >= 0))
);
ALTER TABLE ONLY public.libro_edizione
ADD CONSTRAINT libro_edizione_pkey PRIMARY KEY (isbn);
ALTER TABLE ONLY public.libro_edizione
ADD CONSTRAINT relativa_a FOREIGN KEY (relativa_a) REFERENCES public.libro(id);
La tabella delle edizioni di un libro include due CHECK: uno che controlla che le pagine, se specificate, siano un numero positivo, e un'altro che controlla che gli ISBN siano in un formato valido, assicurandosi che tutti i caratteri siano numerici e permettendo anche una X
sull'ultimo.
utente
CREATE TABLE public.utente (
username character varying NOT NULL,
password bytea NOT NULL,
email character varying,
is_admin boolean DEFAULT false NOT NULL,
is_banned boolean DEFAULT false NOT NULL,
libro_elementi_posseduti integer DEFAULT 0 NOT NULL,
audiolibro_elementi_posseduti integer DEFAULT 0 NOT NULL,
film_elementi_posseduti integer DEFAULT 0 NOT NULL,
gioco_elementi_posseduti integer DEFAULT 0 NOT NULL
);
ALTER TABLE ONLY public.utente
ADD CONSTRAINT username PRIMARY KEY (username);
La password, essendo un hash, è rappresentata come un dato binario (bytea).
Le colonne is_admin
e is_banned
hanno un valore di default di false, in quanto alla creazione gli utenti non saranno amministratori o bannati.
Le colonne *_elementi_posseduti
sono i dati derivati che rappresentano quanti elementi di ogni tipo possiede un dato utente: per i nuovi utenti, questo valore sarà 0.