1
Fork 0
mirror of https://github.com/Steffo99/alexandria.git synced 2024-11-21 21:34:19 +00:00
bdd-2020-alexandria/5-3-creazione-tabelle.md

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.