none
Campo Sequencial em tabelas "Filho" RRS feed

  • 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?
    segunda-feira, 22 de dezembro de 2008 14:01

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

    http://www.plugmasters.com.br/sys/materias/536/1/SQL-Server-2005-New-Features%3A-As-fun%E7%F5es-de-ranqueamento

     

    Embora seja 2005, elas se aplicam.

     

    [ ]s,

     

    Gustavo

    segunda-feira, 22 de dezembro de 2008 16:11
  • 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

    segunda-feira, 22 de dezembro de 2008 16:41

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

     

    segunda-feira, 22 de dezembro de 2008 14:22
  • 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 Snippet

    DECLARE @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 @Clientes

    SELECT * FROM @Pedidos

     

    SELECT CodigoCliente, Sequencia,

    ROW_NUMBER() OVER (PARTITION BY CodigoCliente ORDER BY Sequencia)

    FROM @Pedidos

     

    Se 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

    segunda-feira, 22 de dezembro de 2008 15:26
  • 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?

     

     

    segunda-feira, 22 de dezembro de 2008 15:32
  • 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.
    segunda-feira, 22 de dezembro de 2008 15:59
  • Olá Cléber,

     

    Segue um exemplo do BOL de uso das funções citadas:

     

    Code Snippet
     
    USE 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
     
    segunda-feira, 22 de dezembro de 2008 16:10
  • 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

    http://www.plugmasters.com.br/sys/materias/536/1/SQL-Server-2005-New-Features%3A-As-fun%E7%F5es-de-ranqueamento

     

    Embora seja 2005, elas se aplicam.

     

    [ ]s,

     

    Gustavo

    segunda-feira, 22 de dezembro de 2008 16:11
  • Certo Gustavo, mas o que vc classifica como "um bom índice"?
    segunda-feira, 22 de dezembro de 2008 16:22
  • 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

    segunda-feira, 22 de dezembro de 2008 16:41