Crear y administrar relaciones de claves externas

En esta página, se describe cómo administrar las relaciones de claves externas en la base de datos.

Una clave externa es una columna que se comparte entre las tablas para establecer un vínculo entre los datos relacionados. Cuando usas una clave externa, Spanner se asegura de que esta relación se mantenga.

En el siguiente diagrama, se muestra un esquema de base de datos simple en el que los datos de una tabla tienen una relación con los datos de otra tabla.

Ejemplo de esquema de base de datos que muestra relaciones de claves externas entre tablas.

Figura 1. Diagrama de un esquema de base de datos de procesamiento de pedidos

En el esquema que se muestra en la Figura 1, hay tres tablas:

  • En la tabla Customers, se registran los nombres de cada cliente.
  • En las tablas Orders, se realiza un seguimiento de todos los pedidos realizados.
  • En la tabla Products, se almacena la información de cada producto.

Existen dos relaciones de claves externas entre estas tablas:

  • Una relación de clave externa se define entre la tabla Orders y la tabla Customers para garantizar que no se pueda crear un pedido a menos que haya un cliente correspondiente.

  • Una relación de clave externa entre la tabla Orders y la Products garantiza que no se pueda crear un pedido para un producto que no existe.

Con el esquema anterior como ejemplo, en este tema se analizan las instrucciones CONSTRAINT del lenguaje de definición de datos (DDL) que puedes usar para administrar las relaciones entre las tablas en una base de datos.

Agregar una clave externa durante la creación de una tabla nueva

Supongamos que creaste una tabla Customers en tu base de datos de pedidos de productos simple. Ahora necesitas una tabla Orders para almacenar información sobre los pedidos que realizan los clientes. Para garantizar que todos los pedidos sean válidos, no quieres permitir que el sistema inserte filas en la tabla Orders, a menos que también haya una entrada coincidente en la tabla Customers. Por lo tanto, necesitas una clave externa para establecer una relación entre las dos tablas. Una opción es agregar una columna CustomerID a la tabla nueva y usarla como clave externa para crear una relación con la columna CustomerID en la tabla Customers.

Cuando creas una tabla nueva con una clave externa, usas REFERENCE para establecer una relación con otra tabla. La tabla que contiene la instrucción REFERENCE se denomina tabla de referencia. La tabla nombrada en la declaración REFERENCE es la tabla a la que se hace referencia. La columna que se nombra en la declaración REFERENCE se denomina columna de referencia.

En el siguiente ejemplo, se muestra cómo usar la declaración DDL CREATE TABLE para crear la tabla Orders con una restricción de clave externa que hace referencia a CustomerID en la tabla 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)
);

La declaración anterior contiene una cláusula CONSTRAINT que tiene las siguientes características:

  • Uso de la sintaxis CONSTRAINT para nombrar una restricción, lo que facilita la eliminación de la tabla con el nombre que elegiste.

  • La restricción tiene el nombre FK_CustomerOrder. Los nombres de las restricciones se definen según el esquema y deben ser únicos dentro de este.

  • La tabla Orders, en la que defines la restricción, es la tabla de referencia. La tabla Customers es la tabla de referencia.

  • La columna de referencia en la tabla de referencia es CustomerID. Hace referencia al campo CustomerID en la tabla Customers. Si alguien intenta insertar una fila en Orders con un CustomerID que no existe en Customers, la inserción falla.

En el siguiente ejemplo, se muestra una instrucción de creación de tablas alternativa. Aquí, la restricción de clave externa se define sin un nombre. Cuando usas esta sintaxis, Spanner genera un nombre por ti. Para descubrir los nombres de todas las claves externas, consulta Visualización de las propiedades de una relación de clave externa.

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)
);

Agregar una clave externa a una tabla existente

También debes asegurarte de que los clientes solo puedan pedir los productos que existen. Primero, debes descartar la restricción existente. Luego, puedes usar ALTER TABLE para agregar otra restricción de clave externa a la tabla Orders, como se muestra a continuación:

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

La columna referente en Orders es ProductID y hace referencia a la columna ProductID en Products. Una vez más, si estás de acuerdo con que Spanner nombra estas restricciones por ti, usa la siguiente sintaxis:

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

Agregar una clave externa con una acción de borrar cuando se crea una tabla nueva

Recuerda el ejemplo anterior en el que tienes una tabla Customers en una base de datos de pedido de productos que necesita una tabla Orders. Quieres agregar una restricción de clave externa que haga referencia a la tabla Customers. Sin embargo, debes asegurarte de que, cuando borres el registro de un cliente en el futuro, Spanner también borre todos los pedidos de ese cliente. En este caso, debes usar la acción ON DELETE CASCADE con la restricción de clave externa.

La siguiente declaración DDL CREATE TABLE para la tabla Orders incluye la restricción de clave externa que hace referencia a la tabla Customers con una acción 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)
);

La declaración anterior contiene una restricción de clave externa con una cláusula ON DELETE CASCADE. La columna CustomerID es una clave externa que hace referencia al campo CustomerID en la tabla Customers. Esto significa que cada valor CustomerID de la tabla Orders también debe existir en la tabla Customers. Si alguien intenta borrar una fila de la tabla Customers, todas las filas de la tabla Orders que hacen referencia al valor CustomerID borrado también se borran en la misma transacción.

Agregar una clave externa con una acción de borrar a una tabla existente

También debes asegurarte de que los pedidos solo se creen para los productos que existen. Puedes usar ALTER TABLE para agregar otra restricción de clave externa con la acción ON DELETE CASCADE a la tabla de pedidos de la siguiente manera:

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

Si borras una fila de la tabla Products, se borrarán todas las filas de la tabla Orders que hacen referencia al valor ProductID borrado.

Consulta datos en relaciones de claves externas

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

Ejemplos de cómo Spanner mantiene la integridad referencial

El motivo principal para agregar relaciones de clave externa es que Spanner pueda mantener la integridad referencial de tus datos. Si modificas los datos de una manera que rompe una restricción de clave externa, la actualización falla con un error.

Considera los datos de la Figura 2. Algunos clientes pidieron productos, como se muestra en la tabla de pedidos. Debido a las claves externas que se encuentran, puedes garantizar que los datos que se insertaron en la tabla Orders tengan integridad referencial.

Datos de muestra para las tablas Clientes, Productos y Pedidos.

Figura 2. Datos de muestra para la base de datos de pedidos.

En los siguientes ejemplos, se muestra lo que sucede cuando intentas modificar los datos de una manera que daña la integridad referencial.

  • Agrega una fila a la tabla Orders con un valor CustomerID que no exista en Customers.

    ¿Qué sucede si pruebas la siguiente modificación, a partir de los datos de muestra del diagrama anterior?

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

    En este caso, el sistema intentará insertar una fila en Orders con un CustomerID (447) que no existe en la tabla Customers. Si el sistema lo hizo, tendrás un pedido no válido en tu sistema. Sin embargo, con la restricción de clave externa que agregaste a la tabla Orders, la tabla estará protegida. INSERT falla con el siguiente mensaje, si suponemos que la restricción se llama FK_CustomerOrder.

    Foreign key constraint `FK_CustomerOrder` is violated on table `Orders`.
    Cannot find referenced values in Customers(CustomerID).
    
  • Intenta borrar una fila de la tabla Customers cuando se hace referencia al cliente en una restricción de clave externa.

    Imagina una situación en la que un cliente anula la suscripción a tu tienda en línea. Deseas quitar al cliente de tu backend, por lo que debes intentar la siguiente operación.

    DELETE FROM Customers WHERE CustomerID = 721;
    

    En este ejemplo, Spanner detecta a través de la restricción de clave externa que aún hay registros en la tabla Orders que hacen referencia a la fila del cliente que intentas borrar. En este caso, se muestra el siguiente error.

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

    Para solucionar este problema, primero borra todas las entradas de referencia en Orders. También puedes definir la clave externa con la acción ON DELETE CASCADE para permitir que Spanner controle la eliminación de las entradas de referencia.

Consultar las propiedades de las relaciones de claves externas

INFORMATION_SCHEMA de Spanner contiene información sobre claves externas y sus índices de copia de seguridad. Estos son algunos ejemplos de las preguntas que puedes responder si consultas INFORMATION SCHEMA.

Para obtener más información sobre los índices de respaldo, consulta Índices de respaldo de claves externas.

¿Qué restricciones se definen en la base de datos?

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

¿Qué claves externas se definen en la base de datos?

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

¿Qué índices son secundarios (de respaldo) para las claves externas?

Spanner administra los índices de respaldo de clave externa , por lo que consultar SPANNER_IS_MANAGED en la vista INDEXES muestra todos los índices de respaldo.

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';

¿Cuál es la acción referencial que se define con la restricción de la clave externa?

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

Para obtener más información, consulta Esquema de información.

Quitar relaciones de claves externas

Con la siguiente DDL, se descarta una restricción de clave externa de la tabla Orders.

ALTER TABLE Orders
  DROP CONSTRAINT FK_CustomerOrder;

Los índices de respaldo de claves externas se descartan automáticamente cuando se descarta la restricción.

Compatibilidad con relaciones más complejas de claves externas

Varias columnas

Las claves externas pueden hacer referencia a varias columnas. La lista de columnas forma una clave que corresponde a la clave primaria de una tabla o a un índice de respaldo. La tabla referente contiene claves externas de la clave de la tabla a la que se hace referencia.

En el siguiente ejemplo, las definiciones de clave externa le indican a Spanner que cada valor SongName de la tabla TopHits debe tener un valor coincidente en la tabla Canciones, y cada par de valores SingerFirstName y SingerLastName debe tener un par FirstName y LastName coincidente en la tabla 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)
);

Referencias circulares

En ocasiones, las tablas tienen dependencias circulares, tal vez por motivos heredados o debido a la desnormalización. Las claves externas de Spanner permiten referencias circulares. Dado que una tabla externa debe existir antes de que una clave externa pueda hacer referencia a ella, una de las claves externas debe agregarse con un estado ALTER TABLE. Por ejemplo:

  1. Crear TablaA, sin una clave externa
  2. Cree una TablaB con una restricción de clave externa en la TablaA.
  3. Usa ALTER TABLE en TablaA para crear una referencia de clave externa a TablaB.

Tablas que hacen referencia a sí mismas

Un tipo especial de referencia circular es una tabla que define una clave externa que hace referencia a la misma tabla. Por ejemplo, en el siguiente fragmento, se muestra una clave externa para garantizar que el ID del administrador de un empleado también sea un empleado.

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)
);

¿Qué sigue?