2012-02-12 79 views
9

Respondido en esta pregunta: Cardinality in PostgreSQL, la cardinalidad se aplica usando constraints.PostgreSQL: ¿Cómo implementar la cardinalidad mínima?

Las reglas de cardinalidad definen los recuentos permitidos de las relaciones: uno-a-muchos, muchos-a-muchos, etc. Muchos-a-muchos se logra usando tablas-de-unión y uno-a-muchos usando la CLAVE EXTRAÑA.

Pero, ¿cómo se puede implementar una relación one-to-one_or_many (one-to-1 +). Lo cual es lo mismo que preguntar: ¿Cómo puedo hacer cumplir la cardinalidad mínima en PostgreSQL?

Una situación práctica sería donde se necesita almacenar la dirección (o número de teléfono) que DEBE proporcionarse (pero puede ser más de una) por la persona (por ejemplo, usuario o cliente).

Editar:

la situación mencionada anteriormente es un caso especial (con cardinalidad uno) de un problema general. El problema general es: ¿Cómo imponer la cardinalidad de un número arbitrario?

Como answered by jug un no nulo EXTERIOR referencia clave se puede utilizar como un trabajo en torno, si mínimo-cardinalidad es uno. También proporcionará una función adicional para seleccionar el valor predeterminado entre muchos.

Pero considerar otra situación de relación entre equipo de Cricket y sus jugadores . Cada equipo DEBE tener un MÍNIMO de 11 jugadores para calificar como equipo. Aquí la cardinalidad mínima es once (11).

Similarmente, una relación entre un curso y un estudiante en una escuela, donde cada estudiante debe inscribirse en AT-menos 5 cursos y cada curso deben tener un mínimo de 10 alumnos.

+1

Ambos ejemplos agregados muestran la misma calidad. ¿Cómo comienzas a construir un equipo de cricket desde cero? ¿Cómo se inscribe un estudiante en su primer curso? Los estudiantes con menos de 5 cursos deben poder existir, al menos temporalmente. Entonces este es ** no ** un buen lugar para usar restricciones. Use consultas, funciones o activadores para este fin. –

Respuesta

3

No hay manera de especificar esto usando una restricción CHECK, así que creo que el mejor enfoque es un disparador:

http://www.postgresql.org/docs/9.1/static/sql-createtrigger.html
http://www.postgresql.org/docs/9.1/static/plpgsql-trigger.html

acabaría con algo como (yo no tengo probamos o cualquier cosa):

CREATE TRIGGER at_least_one before INSERT, UPDATE, DELETE ON the_one_table FOR EACH ROW EXECUTE PROCEDURE check_at_least_one(); 

CREATE OR REPLACE FUNCTION check_at_least_one() RETURNS trigger AS $$ 
    BEGIN 
    nmany := select count(*) from the_many_table where the_many_table.the_one_id=NEW.id; 
    IF nmany > 0 THEN 
     RETURN NEW; 
    END IF; 
    RETURN NULL; 
END; 
+0

¿Cómo se realiza el INSERT inicial? Debido a que la tabla UNO debe insertarse primero (para asegurarse de que exista la clave externa referenciada), se puede insertar la primera fila en la tabla MUCHAS que hace referencia a la tabla UNO. Pero tu disparador prohibiría esto. –

+0

Respondiendo a mí mismo: utilizando dos (!!) activadores 'CONSTRAINT' que son' INITIALLY DEFERRED'. Estos desencadenadores no deben devolver 'NULL', pero usan' RAISE EXCEPTION'. El primer activador mira la tabla "uno", la otra la tabla "muchos" (para detectar eliminaciones después de la primera transacción). –

1

Si tiene las direcciones en una tabla direcciones, se puede definir una columna "defau lt_address "(en la tabla clientes) que es non-null referencia de clave externa a la dirección que debe proporcionarse.

Si usted tiene una mesa envíos que proporciona una relación opcional de muchos a muchos haciendo referencia a una persona, una dirección y tal vez una orden (-Item), entonces se podría utilizar para sobrescribir se unen los nulos para las direcciones que entrar en una combinación externa entre (clientes combinación interna órdenes) y shipping_addresses (una visión de unirse envíos con direcciones).Sin embargo, para evitar problemas con tal vez un número diferente de componentes no son nulos de direcciones, Stephane Faroult recomienda en su (muy recomendable!) Libro El arte de SQL a utilizar la técnica de "clave de ordenación oculto" (suponiendo que customers_with_default_address es una vista de unirse clientes con direcciones usando "DEFAULT_ADDRESS":.

select * 
    from (select 1 as sortkey, 
       line_1, 
       line_2, 
       city, 
       state, 
       postal_code, 
       country 
     from shipping_addresses 
      where customer_id = ? 
     union 
     select 2 as sortkey, 
       line_1, 
       line_2, 
       city, 
       state, 
       postal_code, 
       country 
     from customers_with_default_address 
      where customer_id = ? 
     order by 1) actual_shipping_address 
     limit 1 
+0

Ah, la idea de la columna _default_address_ es práctica y tiene sentido solo si se quiere imponer la cardinalidad mínima de uno (1). Lo que presenté como una situación práctica, como parte de la pregunta es solo una de las numerosas situaciones posibles. Considere otra situación de relación entre el equipo de [Cricket] (http://en.wikipedia.org/wiki/Cricket) y sus jugadores. Cada equipo DEBE tener un MÍNIMO de 11 jugadores para calificar como equipo. Aquí la cardinalidad mínima es once (11). – user1144616

+0

@ user1144616: Agregue este ejemplo a la pregunta en sí, describe el problema muy claramente. –

+0

@ A.H .: Agregado, junto con un ejemplo más. – user1144616

3

no hay manera de hacer cumplir dichas normas utilizando sólo FOREIGN KEY limitaciones

1) Una forma es permitir referencias circulares entre tablas (la columna "predeterminada", recomendada por jarra). Esto da como resultado problemas de gallina y huevo que son difíciles de manejar, tendrá que usar restricciones diferibles. Además, esta opción simplemente no está disponible en algunos DBMS. Otra desventaja es que para un equipo de fútbol, ​​tendrás que agregar 11 columnas "predeterminadas" (¡y tendrás que lidiar con un problema de pollo y 11 huevos)!

2) Otra opción es utilizar activadores.

3) Otra opción es utilizar una restricción de nivel de base de datos entre las 2 tablas. No estoy seguro de si hay algún DBMS que tenga dicha funcionalidad. Además de las típicas restricciones UNIQUE, PRIMARY y FOREIGN KEY, la mayoría de los DBMS solo tienen restricciones de nivel de fila y con limitaciones (sin subconsultas, etc.).

4) Otra opción es hacer cumplir dichas reglas mediante la creación de los procedimientos INSERT, UPDATE y DELETE apropiados que solo pueden acceder a las dos tablas relacionadas y aplicar la integridad de acuerdo con estas reglas. Este es el mejor enfoque (en mi opinión).

5) Una opción más, más simple de implementar es usar restricciones estándar de clave externa, aplicar la relación 1 a muchos y tener una vista que muestre los equipos que realmente tienen 11 o más jugadores. Esto significa que no aplicas las reglas que pides. Pero es posible (y puedo decir probable) que no puedas pagarlo también. Si los jugadores de fútbol mueren en un accidente, por ejemplo, el equipo ya no puede jugar en los torneos, pero sigue siendo un equipo. Entonces, puedes definir dos entidades, el Equipo (la Tabla base) y el Equipo Propia (Vista), que pueden jugar juegos. Ejemplo:

CREATE VIEW AS ProperTeam 
(SELECT * 
    FROM Team 
    WHERE (SELECT COUNT(*) 
      FROM Player 
      WHERE Player.TeamId = Team.TeamId 
     ) >= 11 
) 

opciones 1 y 2 de aspecto bastante "sucio", pero eso es sólo una opinión personal, muchas personas como factores desencadenantes.

yo elegiría la opción 4, a menos que pueda ("trampa" y) en realidad no imponer la restricción con la Opción 5.

+0

Las opciones 5 y 4 son buenas desde la vista de la base de datos, pero los marcos de persistencia más comunes en el lado del cliente no pueden hacer frente a ellos; simplemente esperan 'INSERT' simple,' UPDATE', 'SELECT' para una entidad. Diciendo "hacer creación con este procedimiento, actualizar con otro y obtener datos a través de una vista" - diviértete ;-) –

+2

@ A.H .: Ese es un ejemplo de por qué los marcos de persistencia más comunes son muletas malvadas. Su promesa es simplificar las cosas, o incluso hacer que su aplicación db-agnostic (que en realidad nunca funciona). Efectivamente, lo encadenan en el marco, que a su vez garantiza que sea menos portátil que el SQL simple y corta la mitad de las características de su RDBMS. –

+1

@ErwinBrandstetter: los marcos de persistencia son como los impuestos: pocas personas les gustan, muchos los odian, (casi) nadie puede evadirlos a la larga. : -] –

1

El enfoque que trabajó para mí y requiere una cantidad razonable de codificación era (traducido a su equipo/jugador cuestión):

  • crear una restricción de clave externa diferible para hacer cumplir el " un equipo de cada jugador tiene" relación.
  • crea un solo gatillo en el equipo de la mesa, que verifica que al menos n jugadores estén conectados al equipo. El disparador debería arrojar una excepción si no se respeta una cardinalidad, como lo señala AdamKG.

Esto imponer la restricción, sino como un efecto secundario de esto también permitirá que una sola manera de codificar un nuevo equipo de jugadores (es decir, sin rechazarla como una violación fundamental)

start transaction; 
set constraints all deferred; 
insert player_1 with teamPK --teamPK is not yet in table team, will be below 
... 
insert player_n with teamPK 
insert teamPK --this fires the trigger which is successful. 
commit transaction; --this fires the foreign key deferred check, successful. 

Tenga en cuenta que lo hago utilizando claves principales autodefinidas (para teamPK), por ejemplo un nombre de equipo único, de modo que pueda conocer el teamPK antes de insertar realmente la línea en el equipo de la tabla (en lugar de usar la identificación autoincrementada).

Cuestiones relacionadas