Creazione e gestione di relazioni con le chiavi esterne

Questa pagina descrive come gestire le relazioni con le chiavi esterne nel database.

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

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

Schema di database di esempio che mostra le relazioni tra le chiavi esterne delle tabelle.

Figura 1. Diagramma di uno schema di database per l'elaborazione degli ordini

Lo schema mostrato nella Figura 1 contiene tre tabelle:

  • La tabella Customers registra i nomi di ciascun cliente.
  • Le tabelle Orders monitorano tutti gli ordini effettuati.
  • La tabella Products memorizza le informazioni su ogni prodotto.

Esistono due relazioni con chiavi esterne tra queste tabelle:

  • È definita una relazione con 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 CONSTRAINT DDL (Data Definition Language, linguaggio di definizione dei dati) che puoi utilizzare per gestire le relazioni tra le tabelle di un database.

Per impostazione predefinita, tutte le chiavi esterne in Spanner sono chiavi esterne obbligatorie, che applicano l'integrità referenziale. In Spanner puoi anche scegliere di utilizzare chiavi esterne informative, che non convalidano o applicano l'integrità referenziale. Per saperne di più, consulta Confronto delle chiavi esterne e Scegliere il tipo di chiave esterna da utilizzare. Se non specificate, le chiavi esterne negli esempi di questa pagina sono chiavi esterne obbligatorie.

Aggiungere una chiave esterna a una nuova tabella

Supponiamo che tu abbia creato una tabella Customers nel database di ordinamento dei prodotti di base. Ora hai bisogno di una tabella Orders per memorizzare le informazioni sugli ordini effettuati dai clienti. Per assicurarti che tutti gli ordini siano validi, non devi consentire al sistema di inserire righe nella tabella Orders, a meno che non esista anche una voce corrispondente nella tabella Customers. Pertanto, è necessaria una chiave esterna applicata per stabilire una relazione tra le due tabelle. Una possibilità è 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, utilizzi REFERENCE per stabilire una relazione con un'altra tabella. La tabella che contiene l'istruzione REFERENCE è chiamata tabella di riferimento. La tabella menzionata nell'istruzione REFERENCE è la tabella a cui si fa riferimento. La colonna REFERENCE menzionata nell'istruzione è chiamata colonna di riferimento.

L'esempio seguente mostra come utilizzare l'istruzione DDL CREATE TABLE per creare la tabella Orders con una limitazione della 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'inserimento della tabella utilizzando il nome scelto.

  • Il vincolo ha il nome FK_CustomerOrder. I nomi delle limitazioni hanno come ambito lo schema e devono essere univoci al suo interno.

  • La tabella Orders su cui definisci la limitazione è la tabella di riferimento. La tabella Customers è la tabella a cui si fa riferimento.

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

L'esempio seguente mostra un'istruzione di creazione della tabella alternativa. Qui il vincolo della chiave esterna è definito senza un 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

Inoltre, devi assicurarti che i clienti possano ordinare solo i prodotti esistenti. Se la tabella ha vincoli esistenti, devi eliminarli tutti. In Spanner, tutti i vincoli applicati in una tabella devono essere implementati contemporaneamente in un unico statement DDL batch.

Se la tabella non ha vincoli esistenti, puoi utilizzare l'istruzione DDL ALTER TABLE per aggiungere un vincolo di chiave esterna applicato alla tabella Orders esistente, come mostrato nell'esempio seguente:

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 Products. Se non ti dispiace che sia Spanner a nominare questi vincoli, utilizza la seguente sintassi:

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

Aggiungere una chiave esterna con un'azione di eliminazione a una nuova tabella

Ricorda l'esempio precedente in cui hai una tabella Customers in un database di ordini di prodotti che richiede una tabella Orders. Vuoi aggiungere un vincolo di chiave esterna che faccia riferimento alla tabella Customers. Tuttavia, vuoi assicurarti che, in futuro, quando elimini un record cliente, Spanner elimini anche tutti gli ordini per quel cliente. In questo caso, devi utilizzare l'azione ON DELETE CASCADE con la limitazione della 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 una limitazione della 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 nella tabella Orders deve esistere anche nella tabella Customers. Se un utente tenta di eliminare una riga dalla tabella Customers, tutte le righe della tabella Orders che fanno riferimento al valore CustomerID eliminato vengono eliminate anche nella stessa transazione.

Aggiungere una chiave esterna con un'azione di eliminazione a una tabella

Inoltre, devi assicurarti che gli ordini vengano creati solo per i prodotti esistenti. Puoi utilizzare ALTER TABLE per aggiungere un altro vincolo di chiave esterna con ON DELETE CASCADE alla tabella ordini come segue:

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

L'eliminazione di una riga dalla tabella Products comporta l'eliminazione di tutte le righe della tabella Orders che fanno riferimento al valore ProductID eliminato.

Utilizzare chiavi esterne informative (solo GoogleSQL)

Le chiavi esterne informative consentono all'ottimizzatore delle query di utilizzare la relazione con la chiave esterna senza il sovraccarico derivante dai controlli di integrità referenziale eseguiti dalle chiavi esterne applicate. Le chiavi esterne informative sono utili quando l'applicazione dell'integrità referenziale rigorosa non è praticabile o comporta un overhead significativo del rendimento.

Continuando con l'esempio precedente, immagina di voler modellare le relazioni tra le tabelle Customers, Orders e Products. Tuttavia, l'applicazione di un'integrità referenziale rigorosa nei dati delle tabelle potrebbe introdurre colli di bottiglia delle prestazioni, soprattutto durante i periodi di picco degli acquisti con volumi elevati di ordini. Inoltre, i clienti potrebbero effettuare ordini di prodotti che sono stati ritirati e rimossi dalla tabella Products.

Puoi creare la tabella Orders utilizzando chiavi esterne informative:

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) NOT ENFORCED,
    CONSTRAINT FK_ProductOrder FOREIGN KEY (ProductID) REFERENCES Products (ProductID) NOT ENFORCED
) PRIMARY KEY (OrderID);

Creando una chiave esterna informativa con NOT ENFORCED, consenti la possibilità che un ordine faccia riferimento a un cliente o un prodotto inesistenti. L'utilizzo di una chiave esterna informativa anziché di una limitazione della chiave esterna applicata è una buona scelta se è possibile che un account cliente venga eliminato o un prodotto interrotto. Con una chiave esterna informativa, Spanner non esegue la convalida dell'integrità referenziale. Ciò riduce l'overhead di scrittura, potenzialmente migliorando le prestazioni durante i picchi di tempo di elaborazione degli ordini.

Puoi consentire all'ottimizzatore delle query di utilizzare le relazioni per generare piani di query efficienti. In questo modo puoi migliorare le prestazioni delle query che uniscono le tabelle sulle colonne delle chiavi esterne. Per ulteriori informazioni, consulta la chiave esterna informativa per l'ottimizzazione delle query.

Esegui query sui dati nelle relazioni con chiavi esterne

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

Integrità referenziale con chiavi esterne applicate

Il motivo principale per cui aggiungere relazioni di chiavi esterne applicate è che Spanner possa mantenere la integrità referenziale dei dati. Se modifichi i dati in modo da violare una limitazione della chiave esterna, l'aggiornamento non va a buon fine e viene visualizzato un errore.

Considera i dati nella Figura 2. Alcuni clienti hanno ordinato dei prodotti, come mostrato nella tabella Orders. A causa del vincolo di chiave esterna applicato in vigore, i dati inseriti nella tabella Orders hanno integrità referenziale.

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

Figura 2. Dati di esempio per il nostro database degli ordini.

Gli esempi riportati di seguito mostrano cosa succede quando provi a modificare i dati in modo da violare 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, dati i dati di esempio del diagramma precedente?

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

    In questo caso, il sistema tenterà di inserire una riga in Orders con un valore CustomerID (447) che non esiste nella tabella Customers. Se il sistema avesse eseguito questa operazione, nel sistema avresti un ordine non valido. Tuttavia, con il vincolo di chiave esterna applicato che hai aggiunto alla tabella Orders, la tabella è protetta. INSERT non va a buon fine con il seguente messaggio, 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).
    

    A differenza delle chiavi esterne applicate, le chiavi esterne informative non applicano l'integrità referenziale. Se FK_CustomerOrder è una chiave esterna informativa, l'istruzione insert ha esito positivo perché Spanner non convalida l'esistenza di CustomerID corrispondente nella tabella Customers. Di conseguenza, i dati potrebbero non essere conformi all'integrità referenziale definita da FK_CustomerOrder.

  • Tenta di eliminare una riga dalla tabella Customers quando il cliente è citato in un vincolo di chiave esterna applicato.

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

    DELETE FROM Customers WHERE CustomerID = 721;
    

    In questo esempio, Spanner rileva tramite il vincolo della chiave esterna che nella tabella Orders sono ancora presenti record che fanno riferimento alla riga del cliente che stai tentando 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, elimina prima tutte le voci di 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.

    Analogamente, se FK_CustomerOrder è una chiave esterna informativa, l'azione di eliminazione va a buon fine perché Spanner non garantisce l'integrità referenziale delle chiavi esterne informative.

Visualizzazione delle proprietà di una relazione di chiave esterna

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 query su INFORMATION SCHEMA.

Per ulteriori informazioni sugli indici di supporto, consulta Indici di supporto delle 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 , pertanto la 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';

Che cos'è l'azione di referenza definita con la limitazione della chiave esterna?

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

Una chiave esterna è applicata o non applicata?

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

Per ulteriori informazioni, consulta la sezione Information Schema.

Rimozione di una relazione di chiave esterna

Il seguente DDL elimina una limitazione della chiave esterna dalla tabella Orders.

ALTER TABLE Orders
  DROP CONSTRAINT FK_CustomerOrder;

Gli indici di supporto delle chiavi esterne vengono eliminati automaticamente quando viene eliminato il vincolo stesso.

Supporto per relazioni di chiave esterna più complesse

I seguenti argomenti mostrano come utilizzare le chiavi esterne per applicare relazioni più complesse tra le tabelle.

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 le chiavi esterne della chiave della tabella a cui viene fatto riferimento.

Nell'esempio seguente, le definizioni delle chiavi esterne applicate indicano che:

  • Ogni valore SongName nella tabella TopHits deve avere un valore corrispondente nella tabella Songs.

  • 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 per la denormalizzazione. Le chiavi esterne di Spanner consentono i riferimenti circolari. Poiché una tabella a cui fare riferimento deve esistere prima che una chiave esterna possa farvi riferimento, una delle chiavi esterne deve essere aggiunta con un'istruzione ALTER TABLE. Ecco un esempio

  1. Crea TableA senza una chiave esterna.
  2. Crea TableB con una limitazione della chiave esterna su TableA.
  3. Utilizza ALTER TABLE in TableA per creare un riferimento a una chiave esterna a 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 seguente mostra una chiave esterna per imporre che il valore ManagerId di un dipendente sia 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