AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / dba / Perguntas / 18238
Accepted
mdoyle
mdoyle
Asked: 2012-05-22 12:51:04 +0800 CST2012-05-22 12:51:04 +0800 CST 2012-05-22 12:51:04 +0800 CST

Substitua o cursor por uma abordagem baseada em conjunto

  • 772

Estou procurando substituir minha solução baseada em cursor, se possível, em um determinado procedimento armazenado. Se fizer alguma diferença, isso está sendo executado no SQL Server 2008 R2. Estou procurando mais um algoritmo do que um código preciso.

Fundo:

O SP faz parte de um sistema para uma empresa que envia correspondências por mala direta ou e-mail. As correspondências contêm um código personalizado que o destinatário pode inserir ao visitar o comerciante para obter descontos ou ofertas especiais. O uso do código é rastreado e relatórios agregados sobre a resposta a várias correspondências são fornecidos aos comerciantes. Um "cliente" é definido como o alvo de uma das correspondências com um nome, sobrenome e endereço exclusivos; se não houver endereço, o e-mail substituirá o endereço.

As tabelas em causa são as seguintes (versões simplificadas):

CREATE TABLE job (
    id INT PRIMARY KEY IDENTITY (1,1),
    job_num VARCHAR(32) NOT NULL,
    mailing_id INT NOT NULL,
    personal_code NVARCHAR(50) NOT NULL,
    fname NVARCHAR(50) NOT NULL,
    lname NVARCHAR(50) NOT NULL,
    email NVARCHAR(50),
    address NVARCHAR(50),
    city NVARCHAR(50),
    state CHAR(2),
    zip NVARCHAR(10),
    extra NVARCHAR(150)
);

CREATE TABLE customer (
    id INT PRIMARY KEY IDENTITY (1,1),
    fname NVARCHAR(50) NOT NULL,
    lname NVARCHAR(50) NOT NULL,
    email NVARCHAR(50),
    address NVARCHAR(50),
    city NVARCHAR(50),
    state CHAR(2),
    zip NVARCHAR(10)
);

CREATE TABLE personal_code (
    id INT PRIMARY KEY IDENTITY (1,1),
    customer_id INT NOT NULL,
    mailing_id INT NOT NULL,
    personal_code NVARCHAR(50) NOT NULL,
    email NVARCHAR(50),
    FOREIGN KEY (customer_id) REFERENCES customer(id)
);

CREATE TABLE personal_code_extra (
    personal_code_id INT PRIMARY KEY,
    extra NVARCHAR(150),
    FOREIGN KEY (personal_code_id) REFERENCES personal_code(id)
);

A tabela de tarefas é preenchida por um processo externo que está envolvido na criação das listas de endereços para as quais a correspondência será enviada. Em seguida, chama o SP que desejo otimizar (espero), passando o job_num. O SP então lê todos os registros da tabela de trabalho com esse job_num e insere os dados armazenados nas outras três tabelas.

A tabela de clientes é, como esperado, onde os dados dos vários destinatários de correspondência são armazenados.

A tabela personal_code é um armazenamento para dados nos códigos pessoais associados às correspondências. Cada código está vinculado a uma correspondência específica, bem como a um cliente específico. Atualmente, o SP abre um cursor para a tabela de tarefas. Em seguida, itera por cada linha e, para cada uma, faz o seguinte:

  1. Se address não for nulo, ele definirá @customer_id igual a customer.id onde fname, lname e address correspondem; caso contrário, ele o define igual a customer.id onde fname, lname e email correspondem e o endereço é nulo ou vazio.
  2. Neste ponto, se @customer_id for nulo, não houve registro correspondente e um novo registro será adicionado ao cliente. @customer_id é definido como scope_identity().
  3. Um registro é inserido em personal_code.
  4. Se job.extra não for nulo, um registro é inserido em personal_code_extra com (é claro) o id gerado pela inserção na Etapa 3.

customer e personal_code são as maiores tabelas do banco de dados, com 40 milhões e 64 milhões de registros, respectivamente. Mesmo que as consultas no cliente tenham índices de cobertura, fazer uma pesquisa separada para cada linha no trabalho deve atrasar as coisas. Eu gostaria muito de abandonar o cursor e substituir essa abordagem RBAR por uma abordagem baseada em conjunto. O que está me impedindo de fazer isso é ter que usar um customer_id recém-criado para a inserção de personal_code, bem como um personal_code_id recém-criado para a tabela "extras". Se não fosse por isso, eu poderia fazer algo como

INSERT INTO personal_code (fields)
SELECT (fields)
FROM job j
INNER JOIN customer c ON j.fname = c.fname AND j.lname = c.lname AND j.address = c.address
WHERE j.address IS NOT NULL;

INSERT INTO personal_code (fields)
SELECT (fields)
FROM job j
INNER JOIN customer c ON j.fname = c.fname AND j.lname = c.lname AND j.email = c.email
WHERE j.address IS NULL;

INSERT INTO personal_code (fields)
SELECT (fields) -- but won't have a value for customer_id !
FROM job j
LEFT JOIN customer c ON either_address_or_email
WHERE c.id IS NULL;

E como lidar com a inserção do material "extra" em uma abordagem baseada em conjunto, não tenho ideia no momento. Agradeço antecipadamente quaisquer ideias.

EDIT: Código do cursor adicionado por solicitação. Isso é simplificado, mas tem todos os elementos essenciais - espero que minhas edições sejam precisas.

DECLARE job_cur CURSOR FOR
SELECT mailing_id, personal_code, email, fname, lname, address, city, state, zip, extra
FROM job
WHERE job_num = @job_no;

OPEN job_cur;

FETCH NEXT FROM job_cur INTO @mailing_id, @personal_code, @email, @fname, @lname, @address, @city, @state, @zip, @extra;

WHILE @@FETCH_STATUS = 0
BEGIN

    IF ISNULL(@address, '') != ''
    SET @customer_id = (
        SELECT id 
        FROM customer 
        WHERE fname = @fname
        AND lname = @lname
        AND address = @address
    );
    ELSE
    SET @customer_id = (
        SELECT id 
        FROM customer 
        WHERE fname = @fname
        AND lname = @lname
        AND email = @email
        AND (address IS NULL OR address = '')
    ); 

    IF @customer_id IS NULL
    BEGIN

        INSERT INTO customer (
            fname, lname, address, city, state, zip, email
        )
        VALUES (
            @fname, @lname, @address, @city, @state, @zip, @email
        );

        SET @customer_id = SCOPE_IDENTITY();

    END

    INSERT INTO personal_code (
        customer_id, mailing_id, personal_code, email
    )
    VALUES (
        @customer_id, @mailing_id, @personal_code, @email
    );
    SET @personal_code_id = SCOPE_IDENTITY();

    IF @extra IS NOT NULL           
        INSERT INTO personal_code_extra (
            personal_code_id, extra
        )
        VALUES (
            @personal_code_id, @extra
        );

    FETCH NEXT FROM job_cur INTO @mailing_id, @personal_code, @email, @fname, @lname, @address, @city, @state, @zip, @extra;

END
sql-server sql-server-2008-r2
  • 2 2 respostas
  • 4929 Views

2 respostas

  • Voted
  1. Best Answer
    Aaron Bertrand
    2012-05-22T14:08:53+08:002012-05-22T14:08:53+08:00

    Eu provavelmente faria isso da maneira de força bruta e adicionaria índices para dar suporte a essas junções onde elas não existem. Não há muito ganho em tratar novos clientes e clientes antigos de maneira diferente, uma vez que você inseriu todos os clientes que ainda não existem:

    INSERT dbo.Customer(fname, lname, address, city, state, zip, email)
        SELECT fname, lname, address, city, state, zip, email
         FROM dbo.job AS j
         WHERE job_no = @job_no
         AND NOT EXISTS
         (
            SELECT 1 FROM dbo.Customer
            WHERE fname = j.fname
            AND lname = j.lname
            AND (address = j.address OR email = j.email)
         );
    
    INSERT INTO dbo.personal_code (customer_id, mailing_id, personal_code, email)
    SELECT c.customer_id, j.mailing_id, j.personal_code, c.email
      FROM dbo.Customer AS c
      INNER JOIN dbo.job AS j
      ON c.fname = j.fname AND c.lname = j.lname 
      AND (c.address = j.address OR c.email = j.email)
      WHERE j.job_no = @job_no;
    
    INSERT dbo.personal_code_extra(personal_code_id, extra)
    SELECT pc.personal_code_id, j.extra
      FROM dbo.personal_code AS pc
      INNER JOIN dbo.Customer AS c
      ON pc.customer_id = c.customer_id
      INNER JOIN dbo.job AS j
      ON c.fname = j.fname AND c.lname = j.lname 
      AND (c.address = j.address OR c.email = j.email)
      WHERE j.job_no = @job_no;
    
    • 4
  2. HLGEM
    2012-05-22T13:37:17+08:002012-05-22T13:37:17+08:00

    Acho que você precisa da cláusula OUTPUT para retornar quaisquer novos recordids. Além disso, dependendo do que você deseja fazer, você pode estar interessado em usar uma instrução MERGE em vez de apenas uma inserção. Por exemplo, alguém cujo e-mail permaneceu o mesmo, mas cujo endereço mudou, pode ser atualizado em vez de ter um novo registro adicionado, como parece ser o caso no que você está fazendo.

    • 0

relate perguntas

  • SQL Server - Como as páginas de dados são armazenadas ao usar um índice clusterizado

  • Preciso de índices separados para cada tipo de consulta ou um índice de várias colunas funcionará?

  • Quando devo usar uma restrição exclusiva em vez de um índice exclusivo?

  • Quais são as principais causas de deadlocks e podem ser evitadas?

  • Como determinar se um Índice é necessário ou necessário

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Como ver a lista de bancos de dados no Oracle?

    • 8 respostas
  • Marko Smith

    Quão grande deve ser o mysql innodb_buffer_pool_size?

    • 4 respostas
  • Marko Smith

    Listar todas as colunas de uma tabela especificada

    • 5 respostas
  • Marko Smith

    restaurar a tabela do arquivo .frm e .ibd?

    • 10 respostas
  • Marko Smith

    Como usar o sqlplus para se conectar a um banco de dados Oracle localizado em outro host sem modificar meu próprio tnsnames.ora

    • 4 respostas
  • Marko Smith

    Como você mysqldump tabela (s) específica (s)?

    • 4 respostas
  • Marko Smith

    Como selecionar a primeira linha de cada grupo?

    • 6 respostas
  • Marko Smith

    Listar os privilégios do banco de dados usando o psql

    • 10 respostas
  • Marko Smith

    Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Como faço para listar todos os bancos de dados e tabelas usando o psql?

    • 7 respostas
  • Martin Hope
    Mike Walsh Por que o log de transações continua crescendo ou fica sem espaço? 2012-12-05 18:11:22 +0800 CST
  • Martin Hope
    Stephane Rolland Listar todas as colunas de uma tabela especificada 2012-08-14 04:44:44 +0800 CST
  • Martin Hope
    haxney O MySQL pode realizar consultas razoavelmente em bilhões de linhas? 2012-07-03 11:36:13 +0800 CST
  • Martin Hope
    qazwsx Como posso monitorar o andamento de uma importação de um arquivo .sql grande? 2012-05-03 08:54:41 +0800 CST
  • Martin Hope
    markdorison Como você mysqldump tabela (s) específica (s)? 2011-12-17 12:39:37 +0800 CST
  • Martin Hope
    pedrosanta Listar os privilégios do banco de dados usando o psql 2011-08-04 11:01:21 +0800 CST
  • Martin Hope
    Jonas Como posso cronometrar consultas SQL usando psql? 2011-06-04 02:22:54 +0800 CST
  • Martin Hope
    Jonas Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL? 2011-05-28 00:33:05 +0800 CST
  • Martin Hope
    Jonas Como faço para listar todos os bancos de dados e tabelas usando o psql? 2011-02-18 00:45:49 +0800 CST
  • Martin Hope
    bernd_k Quando devo usar uma restrição exclusiva em vez de um índice exclusivo? 2011-01-05 02:32:27 +0800 CST

Hot tag

sql-server mysql postgresql sql-server-2014 sql-server-2016 oracle sql-server-2008 database-design query-performance sql-server-2017

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve