Las declaraciones SQL DDL (lenguaje de definición de datos) podrían verse así:
CREATE TABLE product (
product_id serial PRIMARY KEY -- implicit primary key constraint
, product text NOT NULL
, price numeric NOT NULL DEFAULT 0
);
CREATE TABLE bill (
bill_id serial PRIMARY KEY
, bill text NOT NULL
, billdate date NOT NULL DEFAULT CURRENT_DATE
);
CREATE TABLE bill_product (
bill_id int REFERENCES bill (bill_id) ON UPDATE CASCADE ON DELETE CASCADE
, product_id int REFERENCES product (product_id) ON UPDATE CASCADE
, amount numeric NOT NULL DEFAULT 1
, CONSTRAINT bill_product_pkey PRIMARY KEY (bill_id, product_id) -- explicit pk
);
Hice algunos ajustes:
-
La relación n:m normalmente se implementa mediante una tabla separada -
bill_product
en este caso. -
Agregué
serial
columnas como claves primarias sustitutas . En Postgres 10 o posterior considere unaIDENTITY
columna en su lugar. Ver:- Cambie el nombre de las tablas de forma segura utilizando columnas de clave principal en serie
- Columna de tabla de incremento automático
- https://www.2ndquadrant.com/en/blog/postgresql-10-identity-columns/
Lo recomiendo mucho, porque el nombre de un producto difícilmente es único (no es una buena "clave natural"). Además, hacer cumplir la unicidad y hacer referencia a la columna en claves foráneas suele ser más económico con un
integer
de 4 bytes (o incluso unbigint
de 8 bytes ) que con una cadena almacenada comotext
ovarchar
. -
No use nombres de tipos de datos básicos como
date
como identificadores . Si bien esto es posible, es de mal estilo y conduce a errores y mensajes de error confusos. Use identificadores legales, en minúsculas y sin comillas. Nunca use palabras reservadas y evite los identificadores de mayúsculas y minúsculas entre comillas dobles si puede. -
"nombre" no es un buen nombre. Cambié el nombre de la columna de la tabla
product
serproduct
(oproduct_name
o similar). Esa es una mejor convención de nomenclatura . De lo contrario, cuando une un par de tablas en una consulta, lo que hace mucho en una base de datos relacional:termina con varias columnas llamadas "nombre" y tiene que usar alias de columna para solucionar el problema. Eso no es útil. Otro antipatrón generalizado sería simplemente "id" como nombre de columna.
No estoy seguro de cuál es el nombre de unabill
sería.bill_id
probablemente será suficiente en este caso. -
price
es de tipo de datosnumeric
para almacenar números fraccionarios exactamente como se ingresaron (tipo de precisión arbitraria en lugar de tipo de coma flotante). Si trata exclusivamente con números enteros, convierta eseinteger
. Por ejemplo, podría guardar precios como centavos . -
El
amount
("Products"
en su pregunta) va a la tabla de enlacebill_product
y es de tiponumeric
así como. De nuevo,integer
si trabaja con números enteros exclusivamente. -
Ves las claves foráneas en
bill_product
? Creé ambos para cambios en cascada:ON UPDATE CASCADE
. Si unproduct_id
obill_id
debe cambiar, el cambio se aplica en cascada a todas las entradas dependientes enbill_product
y nada se rompe. Esas son solo referencias sin significado propio.
También uséON DELETE CASCADE
parabill_id
:Si se elimina una factura, sus detalles mueren con ella.
No sucede lo mismo con los productos:no desea eliminar un producto que se usa en una factura. Postgres arrojará un error si intenta esto. Agregaría otra columna aproduct
para marcar filas obsoletas ("eliminación temporal") en su lugar. -
Todas las columnas en este ejemplo básico terminan siendo
NOT NULL
, entoncesNULL
Los valores no están permitidos. (Sí, todos columnas:las columnas de clave principal están definidasUNIQUE NOT NULL
automáticamente.) Eso es porqueNULL
los valores no tendrían sentido en ninguna de las columnas. Hace la vida de un principiante más fácil. Pero no te escaparás tan fácilmente, necesitas entenderNULL
manejo de todos modos. Las columnas adicionales pueden permitirNULL
valores, funciones y uniones pueden introducirNULL
valores en consultas, etc. -
Lea el capítulo sobre
CREATE TABLE
en el manual. -
Las claves primarias se implementan con un índice único en las columnas clave, eso hace que las consultas con condiciones en la(s) columna(s) PK sean rápidas. Sin embargo, la secuencia de las columnas clave es relevante en las claves de varias columnas. Desde el PK en
bill_product
está en(bill_id, product_id)
en mi ejemplo, es posible que desee agregar otro índice solo enproduct_id
o(product_id, bill_id)
si tiene consultas en busca de unproduct_id
determinado y sinbill_id
. Ver:- Clave principal compuesta de PostgreSQL
- ¿Un índice compuesto también es bueno para consultas en el primer campo?
- Funcionamiento de índices en PostgreSQL
-
Lea el capítulo sobre índices en el manual.