As instruções SQL DDL (linguagem de definição de dados) podem ser assim:
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
);
Fiz alguns ajustes:
-
A relação n:m é normalmente implementado por uma tabela separada -bill_product
nesse caso.
-
Eu adicioneiserial
colunas como chaves primárias substitutas . No Postgres 10 ou posterior, considere umaIDENTITY
coluna em vez disso. Ver:
- Renomeie tabelas com segurança usando colunas de chave primária serial
- Coluna da tabela de incremento automático
- https://www.2ndquadrant.com/en/blog/postgresql-10-identity-columns/
Eu recomendo isso, porque o nome de um produto dificilmente é único (não é uma boa "chave natural"). Além disso, impor a exclusividade e fazer referência à coluna em chaves estrangeiras geralmente é mais barato com uminteger
de 4 bytes (ou mesmo umbigint
de 8 bytes ) do que com uma string armazenada comotext
ouvarchar
.
-
Não use nomes de tipos de dados básicos comodate
como identificadores . Embora isso seja possível, é um estilo ruim e leva a erros e mensagens de erro confusos. Use identificadores legais, minúsculos e sem aspas. Nunca use palavras reservadas e evite identificadores de maiúsculas e minúsculas entre aspas duplas, se puder.
-
"nome" não é um bom nome. Renomeei a coluna da tabelaproduct
para serproduct
(ouproduct_name
ou similar). Essa é uma convenção de nomenclatura melhor . Caso contrário, quando você junta algumas tabelas em uma consulta - o que você faz muito em um banco de dados relacional - você acaba com várias colunas chamadas "nome" e precisa usar aliases de coluna para resolver a bagunça. Isso não é útil. Outro antipadrão difundido seria apenas "id" como nome de coluna.
Não tenho certeza de qual é o nome de umabill
seria.bill_id
provavelmente será suficiente neste caso.
-
price
é de tipo de dadosnumeric
para armazenar números fracionários precisamente como digitados (tipo de precisão arbitrária em vez de tipo de ponto flutuante). Se você lida exclusivamente com números inteiros, torne esseinteger
. Por exemplo, você pode economizar preços como centavos .
-
Aamount
("Products"
na sua pergunta) vai para a tabela de vinculaçãobill_product
e é do tiponumeric
também. Novamente,integer
se você lida exclusivamente com números inteiros.
-
Você vê as chaves estrangeiras embill_product
? Eu criei ambos para cascata de mudanças:ON UPDATE CASCADE
. Se umproduct_id
oubill_id
deve mudar, a mudança é em cascata para todas as entradas dependentes embill_product
e nada quebra. Essas são apenas referências sem significado próprio.
Eu também useiON DELETE CASCADE
parabill_id
:Se uma fatura for excluída, seus detalhes morrerão com ela.
Não é assim para produtos:você não deseja excluir um produto que é usado em uma fatura. O Postgres lançará um erro se você tentar isso. Você adicionaria outra coluna aoproduct
para marcar linhas obsoletas ("exclusão reversível").
-
Todas as colunas neste exemplo básico acabam sendoNOT NULL
, entãoNULL
valores não são permitidos. (Sim, todos colunas - as colunas de chave primária são definidasUNIQUE NOT NULL
automaticamente.) Isso porqueNULL
valores não fariam sentido em nenhuma das colunas. Facilita a vida de um iniciante. Mas você não vai escapar tão facilmente, você precisa entenderNULL
manipulação de qualquer maneira. Colunas adicionais podem permitirNULL
valores, funções e junções podem introduzirNULL
valores em consultas etc.
-
Leia o capítulo sobreCREATE TABLE
no manual.
-
As chaves primárias são implementadas com um índice exclusivo nas colunas-chave, o que torna rápidas as consultas com condições na(s) coluna(s) PK. No entanto, a sequência de colunas de chave é relevante em chaves de várias colunas. Desde o PK embill_product
está em(bill_id, product_id)
no meu exemplo, você pode querer adicionar outro índice apenas emproduct_id
ou(product_id, bill_id)
se você tiver dúvidas procurando por um determinadoproduct_id
e sembill_id
. Ver:
- Chave primária composta PostgreSQL
- Um índice composto também é bom para consultas no primeiro campo?
- Trabalho de índices no PostgreSQL
-
Leia o capítulo sobre índices no manual.