Creazione e gestione di relazioni con le chiavi esterne

Questa pagina descrive come gestire le relazioni di chiave esterna nel tuo database.

Una chiave esterna è una colonna condivisa tra le tabelle per stabilire un collegamento tra i dati correlati. Quando usi una chiave esterna, Spanner garantisce che questa relazione venga mantenuta.

Il seguente diagramma mostra uno schema di database semplice in cui i dati di una tabella hanno una relazione con i dati di un'altra tabella.

Esempio di schema di database che mostra le relazioni di chiave esterna tra le tabelle.

Figura 1. Diagramma di uno schema di database di elaborazione degli ordini

Nello schema mostrato nella Figura 1 sono presenti tre tabelle:

  • La tabella Customers registra i nomi di ciascun cliente.
  • Le tabelle Orders tengono traccia di tutti gli ordini effettuati.
  • La tabella Products archivia le informazioni per ciascun prodotto.

Esistono due relazioni di chiave esterna tra queste tabelle:

  • Viene definita una relazione di chiave esterna tra la tabella Orders e la tabella Customers per garantire che non sia possibile creare un ordine a meno che non esista un cliente corrispondente.

  • Una relazione di chiave esterna tra la tabella Orders e la tabella Products garantisce che non sia possibile creare un ordine per un prodotto che non esiste.

Utilizzando lo schema precedente come esempio, questo argomento illustra le istruzioni Data Definition Language (DDL) CONSTRAINT che puoi utilizzare per gestire le relazioni tra le tabelle in un database.

Aggiunta di una chiave esterna durante la creazione di una nuova tabella

Supponiamo di aver creato una tabella Customers nel semplice database di ordinamento dei prodotti. Ora hai bisogno di una tabella Orders per archiviare le informazioni relative agli ordini effettuati dai clienti. Per garantire che tutti gli ordini siano validi, non permettere al sistema di inserire righe nella tabella Orders, a meno che non esista una voce corrispondente anche nella tabella Customers. Pertanto, hai bisogno di una chiave esterna per stabilire una relazione tra le due tabelle. Puoi scegliere di aggiungere una colonna CustomerID alla nuova tabella e utilizzarla come chiave esterna per creare una relazione con la colonna CustomerID nella tabella Customers.

Quando crei una nuova tabella con una chiave esterna, puoi utilizzare REFERENCE per stabilire una relazione con un'altra tabella. La tabella che contiene l'istruzione REFERENCE è chiamata tabella riferimento. La tabella indicata nell'istruzione REFERENCE è la tabella a cui viene fatto riferimento. La colonna denominata nell'istruzione REFERENCE è denominata colonna riferimento.

L'esempio seguente mostra come utilizzare l'istruzione DDL CREATE TABLE per creare la tabella Orders con un vincolo di chiave esterna che fa riferimento a CustomerID nella tabella Customers.

GoogleSQL

CREATE TABLE Orders (
  OrderID INT64 NOT NULL,
  CustomerID INT64 NOT NULL,
  Quantity INT64 NOT NULL,
  ProductID INT64 NOT NULL,
  CONSTRAINT FK_CustomerOrder FOREIGN KEY (CustomerID) REFERENCES Customers (CustomerID)
) PRIMARY KEY (OrderID);

PostgreSQL

CREATE TABLE Orders (
  OrderID BIGINT NOT NULL,
  CustomerID BIGINT NOT NULL,
  Quantity BIGINT NOT NULL,
  ProductID BIGINT NOT NULL,
  CONSTRAINT FK_CustomerOrder FOREIGN KEY (CustomerID) REFERENCES Customers (CustomerID),
  PRIMARY KEY (OrderID)
);

L'istruzione precedente contiene una clausola CONSTRAINT con le seguenti caratteristiche:

  • Utilizzo della sintassi CONSTRAINT per assegnare un nome a un vincolo, in modo da semplificare l'eliminazione della tabella utilizzando il nome scelto.

  • Il nome del vincolo è FK_CustomerOrder. I nomi dei vincoli hanno come ambito lo schema e devono essere univoci all'interno dello schema.

  • La tabella Orders, in cui definisci il vincolo, è la tabella di riferimento. La tabella Customers è la tabella a cui viene fatto riferimento.

  • La colonna di riferimento nella tabella di riferimento è CustomerID. Fa riferimento al campo CustomerID nella tabella Customers. Se qualcuno cerca di inserire una riga in Orders con un valore CustomerID che non esiste in Customers, l'inserimento non va a buon fine.

L'esempio seguente mostra un'istruzione alternativa per la creazione di una tabella. Qui, il vincolo di chiave esterna viene definito senza nome. Quando utilizzi questa sintassi, Spanner genera un nome per te. Per scoprire i nomi di tutte le chiavi esterne, consulta Visualizzare le proprietà di una relazione di chiave esterna.

GoogleSQL

CREATE TABLE Orders (
  OrderID INT64 NOT NULL,
  CustomerID INT64 NOT NULL,
  ProductID INT64 NOT NULL,
  FOREIGN KEY (CustomerID) REFERENCES Customers (CustomerID)
) PRIMARY KEY (OrderID);

PostgreSQL

CREATE TABLE Orders (
  OrderID BIGINT NOT NULL,
  CustomerID BIGINT NOT NULL,
  Quantity BIGINT NOT NULL,
  ProductID BIGINT NOT NULL,
  FOREIGN KEY (CustomerID) REFERENCES Customers (CustomerID),
  PRIMARY KEY (OrderID)
);

Aggiunta di una chiave esterna a una tabella esistente

Devi inoltre assicurarti che i clienti possano ordinare solo i prodotti esistenti. Innanzitutto, devi eliminare il vincolo esistente. Quindi, puoi utilizzare ALTER TABLE per aggiungere un altro vincolo di chiave esterna alla tabella Orders, come mostrato di seguito:

ALTER TABLE Orders
  ADD CONSTRAINT DB_ProductOrder FOREIGN KEY (ProductID) REFERENCES Products (ProductID);

La colonna di riferimento in Orders è ProductID e fa riferimento alla colonna ProductID in Prodotti. Anche in questo caso, se vuoi che Spanner denomina questi vincoli per te, utilizza la seguente sintassi:

ALTER TABLE Orders
  ADD FOREIGN KEY (ProductID) REFERENCES Products (ProductID);

Aggiungi una chiave esterna con un'azione di eliminazione durante la creazione di una nuova tabella

Ricorda l'esempio precedente in cui hai una tabella Customers in un database di ordinamento dei prodotti che richiede una tabella Orders. Vuoi aggiungere un vincolo di chiave esterna che faccia riferimento alla tabella Customers. Tuttavia, è opportuno assicurarti che, quando in futuro elimini il record di un cliente, Spanner elimini anche tutti gli ordini per quel cliente. In questo caso, dovrai utilizzare l'azione ON DELETE CASCADE con il vincolo di chiave esterna.

La seguente istruzione DDL CREATE TABLE per la tabella Orders include il vincolo di chiave esterna che fa riferimento alla tabella Customers con un'azione ON DELETE CASCADE.

GoogleSQL

CREATE TABLE Orders (
  OrderID INT64 NOT NULL,
  CustomerID INT64 NOT NULL,
  Quantity INT64 NOT NULL,
  ProductID INT64 NOT NULL,
  CONSTRAINT FK_CustomerOrder FOREIGN KEY (CustomerID)
    REFERENCES Customers (CustomerID) ON DELETE CASCADE
) PRIMARY KEY (OrderID);

PostgreSQL

CREATE TABLE Orders (
  OrderID BIGINT NOT NULL,
  CustomerID BIGINT NOT NULL,
  Quantity BIGINT NOT NULL,
  ProductID BIGINT NOT NULL,
  FOREIGN KEY (CustomerID)
    REFERENCES Customers (CustomerID) ON DELETE CASCADE,
  PRIMARY KEY (OrderID)
);

L'istruzione precedente contiene un vincolo di chiave esterna con una clausola ON DELETE CASCADE. La colonna CustomerID è una chiave esterna che fa riferimento al campo CustomerID nella tabella Customers. Ciò significa che ogni valore CustomerID della tabella Orders deve esistere anche nella tabella Customers. Se un utente tenta di eliminare una riga dalla tabella Customers, nella stessa transazione vengono eliminate anche tutte le righe della tabella Orders che fanno riferimento al valore CustomerID eliminato.

Aggiungere una chiave esterna con azione di eliminazione a una tabella esistente

Devi inoltre assicurarti che gli ordini vengano creati solo per i prodotti esistenti. Puoi utilizzare ALTER TABLE per aggiungere un altro vincolo di chiave esterna con l'azione ON DELETE CASCADE alla tabella degli ordini, in questo modo:

ALTER TABLE Orders
  ADD CONSTRAINT DB_ProductOrder FOREIGN KEY (ProductID)
    REFERENCES Products (ProductID) ON DELETE CASCADE;

Se elimini una riga dalla tabella Products, vengono eliminate tutte le righe nella tabella Orders che fanno riferimento al valore ProductID eliminato.

Query sui dati delle relazioni di chiave esterna

SELECT * FROM Orders
  INNER JOIN Customers ON Orders.CustomerID = Customers.CustomerID
  INNER JOIN Products ON Orders.ProductsID = Products.ProductID;

Esempi di come Spanner mantiene l'integrità referenziale

Il motivo principale dell'aggiunta di relazioni di chiave esterna è che Spanner può mantenere l'integrità referenziale dei dati. Se modifichi i dati in modo da violare un vincolo di chiave esterna, l'aggiornamento non riesce e genera un errore.

Considera i dati della Figura 2. Alcuni clienti hanno ordinato dei prodotti, come mostrato nella tabella degli ordini. A causa delle chiavi esterne attive, puoi garantire che i dati inseriti nella tabella Orders abbiano l'integrità referenziale.

Dati di esempio per le tabelle Clienti, Prodotti e Ordini.

Figura 2. Dati di esempio per nel nostro database per gli ordini.

I seguenti esempi mostrano cosa succede quando provi a modificare i dati in un modo che compromette l'integrità referenziale.

  • Aggiungi una riga alla tabella Orders con un valore CustomerID che non esiste in Customers

    Cosa succede se provi la seguente modifica, in base ai dati di esempio del diagramma precedente?

    INSERT INTO Orders (OrderID, ProductID, Quantity, CustomerID)
      VALUES (19, 337876, 4, 447);
    

    In questo caso, il sistema proverà a inserire una riga in Orders con un valore CustomerID (447) che non esiste nella tabella Customers. In questo caso, significa che l'ordine non è valido. Tuttavia, con il vincolo di chiave esterna che hai aggiunto alla tabella Orders, la tabella è protetta. INSERT restituisce un errore con il messaggio seguente, supponendo che il vincolo sia chiamato FK_CustomerOrder.

    Foreign key constraint `FK_CustomerOrder` is violated on table `Orders`.
    Cannot find referenced values in Customers(CustomerID).
    
  • Tentativo di eliminare una riga dalla tabella Customers quando al cliente viene fatto riferimento in un vincolo di chiave esterna.

    Immagina una situazione in cui un cliente annulla l'iscrizione al tuo negozio online. Vuoi rimuovere il cliente dal tuo backend, quindi prova a eseguire la seguente operazione.

    DELETE FROM Customers WHERE CustomerID = 721;
    

    In questo esempio, Spanner rileva tramite il vincolo di chiave esterna che nella tabella Orders sono ancora presenti record che fanno riferimento alla riga del cliente che stai cercando di eliminare. In questo caso viene visualizzato il seguente errore.

    Foreign key constraint violation when deleting or updating referenced row(s): referencing row(s) found in table `Orders`.

    Per risolvere il problema, devi prima eliminare tutte le voci che fanno riferimento in Orders. Puoi anche definire la chiave esterna con l'azione ON DELETE CASCADE per consentire a Spanner di gestire l'eliminazione delle voci di riferimento.

Visualizzazione delle proprietà di una relazione di chiave esterna

Il valore INFORMATION_SCHEMA di Spanner contiene informazioni sulle chiavi esterne e sui relativi indici di supporto. Di seguito sono riportati alcuni esempi di domande a cui puoi rispondere eseguendo una query su INFORMATION SCHEMA.

Per ulteriori informazioni sugli indici di supporto, consulta la sezione Indici di supporto di chiavi esterne.

Quali vincoli sono definiti nel mio database?

SELECT tc.CONSTRAINT_NAME, tc.TABLE_NAME, tc.CONSTRAINT_TYPE
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS as tc
WHERE tc.CONSTRAINT_TYPE = 'FOREIGN KEY';

Quali chiavi esterne sono definite nel mio database?

SELECT rc.CONSTRAINT_NAME, rc.UNIQUE_CONSTRAINT_NAME, rc.SPANNER_STATE
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS as rc;

Quali indici sono indici secondari per le chiavi esterne, noti anche come indici di supporto?

Gli indici di supporto delle chiavi esterne sono gestiti da Spanner , quindi eseguire query per SPANNER_IS_MANAGED nella vista INDEXES restituisce tutti gli indici di supporto.

SELECT i.TABLE_NAME, i.INDEX_NAME, i.INDEX_TYPE, i.INDEX_STATE,
  i.IS_UNIQUE, i.IS_NULL_FILTERED, i.SPANNER_IS_MANAGED
FROM INFORMATION_SCHEMA.INDEXES as i
WHERE SPANNER_IS_MANAGED = 'YES';

Qual è l'azione referenziale definita con il vincolo di chiave esterna?

SELECT rc.CONSTRAINT_NAME, rc.UNIQUE_CONSTRAINT_NAME, rc.DELETE_RULE,
  rc.UPDATE_RULE
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS as rc;

Per saperne di più, consulta la sezione Schema delle informazioni.

Rimozione di una relazione di chiave esterna

Il seguente DDL elimina un vincolo di chiave esterna dalla tabella Orders.

ALTER TABLE Orders
  DROP CONSTRAINT FK_CustomerOrder;

Gli indici di supporto della chiave esterna vengono eliminati automaticamente quando il vincolo stesso viene eliminato.

Supporto per relazioni di chiave esterna più complesse

Più colonne

Le chiavi esterne possono fare riferimento a più colonne. L'elenco di colonne forma una chiave che corrisponde alla chiave primaria di una tabella o a un indice di supporto. La tabella di riferimento contiene chiavi esterne della chiave della tabella di riferimento.

Nell'esempio seguente, le definizioni della chiave esterna indicano a Spanner che ogni valore SongName nella tabella TopHits deve avere un valore corrispondente nella tabella Brani, mentre ogni coppia di valori SingerFirstName e SingerLastName deve avere una coppia di valori FirstName e LastName corrispondente nella tabella Singers.

GoogleSQL

CREATE TABLE TopHits (
  Rank INT64 NOT NULL,
  SongName STRING(MAX),
  SingerFirstName STRING(MAX),
  SingerLastName STRING(MAX),

  -- Song names must either be NULL or have matching values in Songs.
  FOREIGN KEY (SongName) REFERENCES Songs (SongName),

  -- Singer names must either be NULL or have matching values in Singers.
  FOREIGN KEY (SingerFirstName, SingerLastName)
  REFERENCES Singers (FirstName, LastName)

) PRIMARY KEY (Rank);

PostgreSQL

CREATE TABLE TopHits (
  Rank BIGINT NOT NULL,
  SongName VARCHAR,
  SingerFirstName VARCHAR,
  SingerLastName VARCHAR,

  -- Song names must either be NULL or have matching values in Songs.
  FOREIGN KEY (SongName) REFERENCES Songs (SongName),

  -- Singer names must either be NULL or have matching values in Singers.
  FOREIGN KEY (SingerFirstName, SingerLastName)
  REFERENCES Singers (FirstName, LastName),

  PRIMARY KEY (Rank)
);

Riferimenti circolari

A volte le tabelle hanno dipendenze circolari, forse per motivi legacy o a causa della denormalizzazione. Le chiavi esterne di Spanner consentono i riferimenti circolari. Poiché una tabella di riferimento deve esistere prima che una chiave esterna possa farvi riferimento, è necessario aggiungere una delle chiavi esterne con un'istruzione ALTER TABLE. Ecco un esempio

  1. Crea TableA, senza una chiave esterna
  2. Crea la tabella B con un vincolo di chiave esterna nella tabella A.
  3. Usa ALTER TABLE in TableA per creare un riferimento a una chiave esterna nella TableB.

Tabelle autoreferenziali

Un tipo speciale di riferimento circolare è una tabella che definisce una chiave esterna che fa riferimento alla stessa tabella. Ad esempio, lo snippet riportato di seguito mostra una chiave esterna per indicare che l'ID gestore di un dipendente è anche un dipendente.

GoogleSQL

CREATE TABLE Employees (
  EmployeeId INT64 NOT NULL,
  EmployeeName STRING(MAX) NOT NULL,
  ManagerId INT64,
  FOREIGN KEY (ManagerId) REFERENCES Employees (EmployeeId)
) PRIMARY KEY (EmployeeId);

PostgreSQL

CREATE TABLE Employees (
  EmployeeId BIGINT NOT NULL,
  EmployeeName VARCHAR NOT NULL,
  ManagerId BIGINT,
  FOREIGN KEY (ManagerId) REFERENCES Employees (EmployeeId),
  PRIMARY KEY (EmployeeId)
);

Passaggi successivi