Usuário com melhor resposta
Campo Sequencial em tabelas "Filho"

Pergunta
-
Olá
Tenho que criar uma estrutura Pai->Filho (Mestre->Detalhe), onde possuo um campo sequencial no pai e um no filho
Ex:
[Clientes] (Tabela Pai)
[Código do Cliente] (Primário e Ident)
[Nome do Cliente]
[Histórico do Cliente] (Tabela Filho)
[Código do Cliente] (Primário)
[Sequência] (Primário e Ident)
[Descrição]
O problema é o seguinte:
Quero que a cada novo registro na tabela de clientes o sequencial da tabela filho volte ao "um"
Ex.:
1 Maria 1 1 Teste 1
1 2 Teste 2
2 João 2 1 Teste 3
2 2 Teste 4
hj fica assim:
1 Maria 1 1 Teste 1
1 2 Teste 2
2 João 2 3 Teste 3
2 4 Teste 4
ou seja, o índice primário da tabela Histório do Cliente é composto por 2 campos mas o sequencial não está dependendo do outro campo.
Como resolver isso?
Respostas
-
Olá Cléber,
Normalmente o MAX + 1 tem diversos problemas de concorrência uma vez que duas inserções podem gerar o mesmo seqüencial (maiores detalhes em: Piores práticas - Geração de seqüênciais baseados no MAX + 1). Como dificilmente aparecerão duas inserções simultâneas do mesmo cliente, essa prática é aceitável)
O segredo então seria ter um bom índice sobre a coluna SeqPai para diminuir a lentidão. Fora isso, se o seqüencial por cliente for realmente necessário, não há mais o que fazer. O código pode até ser encapsulado em uma trigger ou SP, mas não será mais performático.
O uso do Row_Number, Rank, Dense_Rank, etc é útil, mas perceba que ele apenas "exibe" o seqüencial por cliente. A coluna armazenada, realmente continua com a seqüência independente. Se o critério for apenas de exibir, o Row_Number pode ser útil. Se o seqüencial realmente precisa ser gravado, então o Row_Number não é suficiente.
Você poderá obter maiores detalhes sobre essas funções no link abaixo:
SQL Server 2005 New Features: As funções de ranqueamento
Embora seja 2005, elas se aplicam.
[ ]s,
Gustavo
-
Olá Cléber,
Acho que para explicar um bom índice iremos gastar muito tempo (além de desfocar a dúvida). Por hora eu diria que um índice sobre a coluna que você está usando na cláusula WHERE (seqpai) ou ainda um índice sobre as colunas SeqPai, SeqFilho é suficiente. Se a chave primária for SeqPai e SeqFilho não é necessário criar um índice adicional.
[ ]s,
Gustavo
Todas as Respostas
-
Olá
,Não vejo a necessidade da criação de uma chave composta para [Histórico do Cliente]
, visto que isso nunca vai se repetir.Você poderia fazer da sxeguinte
forma:[Clientes]
(Tabela Pai)[Código do Cliente]
(Primário e Ident)[Nome do Cliente]
[Histórico do Cliente]
(Tabela Filho)ID_Histórico
(Primário e Ident)[Código do Cliente]
[Sequência]
--Essa numeração pode ser obitda no select através das novas cláusulas do SQL 2005. Rank, Dense Rank, etc.[Descrição]
No
entanto, caso realmente seja necessário armazenar esses dados, aconselho o uso de tabelas auxiliares para gerar campos sequenciais.No
Blog do Gustavo Maia você encontra como gerar sequenciais com tabelas auxiliares.Abraços
-
Olá Cléber,
O SQL Server não dispõe de mecanismos de seqüencias que levem em conta algum campo. O seqüencial é independente e varia somente em função da presença de novos registros.
Acredito que a explicação do Demétrio esteja muito bem colocada. Uma coisa é garantir unicidade e consistência, outra coisa é garantir algo "esteticamente" melhor. Se o objetivo é meramente garantir unicidade, não há mais nada a fazer. O ID não se repetirá e não será preciso montar um chave composta.
Se o desejo "estético" de ter um ID seqüencial por Codigo do Cliente for uma questão meramente de exibição, como ele bem disse, as funções do 2005 fazem isso muito bem. Ex:
Code SnippetDECLARE
@Clientes TABLE ([CodigoCliente]
INT IDENTITY(1,1),[NomeCliente]
VARCHAR(50))INSERT
INTO @Clientes VALUES ('Maria')INSERT
INTO @Clientes VALUES ('João')DECLARE
@Pedidos TABLE ([CodigoCliente]
INT,[Sequencia]
INT IDENTITY(1,1))INSERT
INTO @Pedidos VALUES (1)INSERT
INTO @Pedidos VALUES (1)INSERT
INTO @Pedidos VALUES (2)INSERT
INTO @Pedidos VALUES (2)SELECT
* FROM @ClientesSELECT
* FROM @PedidosSELECT
CodigoCliente, Sequencia,ROW_NUMBER
() OVER (PARTITION BY CodigoCliente ORDER BY Sequencia)FROM
@PedidosSe o "estético" for uma imposição do negócio, ou seja, tem que ter a chave composta, há como fazer através de triggers, mas haverá penalidades para o desempenho.
[ ]s,
Gustavo
-
Cleber,
As vezes algumas regras de negócios são importantes e devem ser respeitadas para garantir o sucesso, mas nem toda regra deve ser totalmente seguida, é importante destacar que este tipo de situação querer uma solução muitas vezes não relacionadas ao Banco de dados, mas sim aos processos ao qual este banco ou aplicação esta relacionado.
Como os colegas já destacaram, o SQL Server 2005 poderá contemplar as suas necessidades realizando este tipo de controle, mas isso representa uma necessidade para seu negócio, vejo como uma solução o exemplo postado pelos colegas.
Mas gostaria de perguntar, isso realmente é necessário?
-
Caros Junior, Gustavo e Demétrio, obrigado pelas explicações.
Estou obrigado a seguir essa regra de negócio, citei uma situação bem simplificada, mas preciso controlar isso em meu projeto dessa forma. Não tenho como fugir.
O método que utilizo atualmente é gravar no campo em questão o Max() + 1, isso vem funcionando, mas considero a função MAX() muito lenta, quero achar um meio melhor de fazer isso.
Insert Into TbFilho (SeqPai, SeqFilho)
Select @C, IsNull(Max(SeqFilho), 0) + 1 From Tabela Where SeqPai = @C
Se o sequencial sempre fosse mantido poderia analisar o número de linhas da tabela, mas isso não se aplica ao meu caso pois o usuário pode excluir uma linha na meio da tabela e isso não pode danificar o sequencial!
Não entendi direito como usar as funções ROW_NUMBER, RANK e DENSE_RANK, pode me dar uma exemplo melhor de quando usar essas funções. Uso SQL 2008 Express.
Obrigado a todos. -
Olá Cléber,
Segue um exemplo do BOL de uso das funções citadas:
Code SnippetUSE
AdventureWorks;GO
SELECT c.FirstName, c.LastName
,ROW_NUMBER() OVER (ORDER BY a.PostalCode) AS 'Row Number'
,RANK() OVER (ORDER BY a.PostalCode) AS 'Rank'
,DENSE_RANK() OVER (ORDER BY a.PostalCode) AS 'Dense Rank'
,NTILE(4) OVER (ORDER BY a.PostalCode) AS 'Quartile'
,s.SalesYTD, a.PostalCode
FROM Sales.SalesPerson s
INNER JOIN Person.Contact c ON s.SalesPersonID = c.ContactID
INNER JOIN Person.Address a ON a.AddressID = c.ContactID
WHERE
TerritoryID IS NOT NULL
AND SalesYTD <> 0;
"Se o sequencial sempre fosse mantido poderia analisar o número de linhas da tabela, mas isso não se aplica ao meu caso pois o usuário pode excluir uma linha na meio da tabela e isso não pode danificar o sequencial!"
Se entendi direito, mesmo o usuário excluindo algum registro, a numeração sequencial ficaria correta visto que os dados são numerados na hora do select.
Abraços
-
Olá Cléber,
Normalmente o MAX + 1 tem diversos problemas de concorrência uma vez que duas inserções podem gerar o mesmo seqüencial (maiores detalhes em: Piores práticas - Geração de seqüênciais baseados no MAX + 1). Como dificilmente aparecerão duas inserções simultâneas do mesmo cliente, essa prática é aceitável)
O segredo então seria ter um bom índice sobre a coluna SeqPai para diminuir a lentidão. Fora isso, se o seqüencial por cliente for realmente necessário, não há mais o que fazer. O código pode até ser encapsulado em uma trigger ou SP, mas não será mais performático.
O uso do Row_Number, Rank, Dense_Rank, etc é útil, mas perceba que ele apenas "exibe" o seqüencial por cliente. A coluna armazenada, realmente continua com a seqüência independente. Se o critério for apenas de exibir, o Row_Number pode ser útil. Se o seqüencial realmente precisa ser gravado, então o Row_Number não é suficiente.
Você poderá obter maiores detalhes sobre essas funções no link abaixo:
SQL Server 2005 New Features: As funções de ranqueamento
Embora seja 2005, elas se aplicam.
[ ]s,
Gustavo
-
-
Olá Cléber,
Acho que para explicar um bom índice iremos gastar muito tempo (além de desfocar a dúvida). Por hora eu diria que um índice sobre a coluna que você está usando na cláusula WHERE (seqpai) ou ainda um índice sobre as colunas SeqPai, SeqFilho é suficiente. Se a chave primária for SeqPai e SeqFilho não é necessário criar um índice adicional.
[ ]s,
Gustavo