Pergunta

Suponha que eu tenha uma tabela de clientes:

CREATE TABLE customers (
    customer_number  INTEGER,
    customer_name    VARCHAR(...),
    customer_address VARCHAR(...)
)

Esta tabela faz não tem uma chave primária. No entanto, customer_name e customer_address deve Seja único para qualquer customer_number.

Não é incomum que esta tabela contenha muitos clientes duplicados. Para contornar essa duplicação, a consulta a seguir é usada para isolar apenas os clientes exclusivos:

SELECT
  DISTINCT customer_number, customer_name, customer_address
FROM customers

Felizmente, a tabela tradicionalmente continha dados precisos. Isto é, nunca houve um conflitante customer_name ou customer_address para qualquer customer_number. No entanto, suponha que dados conflitantes tenham entrado na tabela. Desejo escrever uma consulta que falhe, em vez de devolver várias linhas para o customer_number em questão.

Por exemplo, tentei esta consulta sem sucesso:

SELECT
  customer_number, DISTINCT(customer_name, customer_address)
FROM customers
GROUP BY customer_number

Existe uma maneira de escrever essa consulta usando SQL padrão? Caso contrário, existe uma solução no SQL específico do Oracle?

Editar: A lógica por trás da consulta bizarra:

Verdade seja dita, esta tabela de clientes realmente não existe (graças a Deus). Eu o criei esperando que fosse claro o suficiente para demonstrar as necessidades da consulta. No entanto, as pessoas estão (felizmente) sobre que a necessidade de tal consulta seja a menor das minhas preocupações, com base nesse exemplo. Portanto, agora devo retirar parte da abstração e, esperançosamente, restaurar minha reputação de sugerir tal abominação de uma mesa ...

Recebo um arquivo plano contendo faturas (uma por linha) de um sistema externo. Eu li este arquivo, linha por linha, inserindo seus campos nessa tabela:

CREATE TABLE unprocessed_invoices (
    invoice_number   INTEGER,
    invoice_date     DATE,
    ...
    // other invoice columns
    ...
    customer_number  INTEGER,
    customer_name    VARCHAR(...),
    customer_address VARCHAR(...)
)

Como você pode ver, os dados que chegam do sistema externo são desnormalizados. Ou seja, o sistema externo inclui os dados da fatura e seus dados associados ao cliente na mesma linha. É possível que várias faturas compartilhem o mesmo cliente; portanto, é possível ter dados duplicados do cliente.

O sistema não pode começar a processar as faturas até que todos os clientes sejam registrados no sistema. Portanto, o sistema deve identificar os clientes exclusivos e registrá -los conforme necessário. É por isso que eu queria a consulta: Porque eu estava trabalhando com dados desnormalizados que não tinha controle.

SELECT
  customer_number, DISTINCT(customer_name, customer_address)
FROM unprocessed_invoices
GROUP BY customer_number

Espero que isso ajude a esclarecer a intenção original da pergunta.

Editar: Exemplos de dados bons/ruins

Esclarecer: customer_name e customer_address só tem que ser único para um determinado customer_number.

 customer_number | customer_name | customer_address
----------------------------------------------------
 1               | 'Bob'         | '123 Street'
 1               | 'Bob'         | '123 Street'
 2               | 'Bob'         | '123 Street'
 2               | 'Bob'         | '123 Street'
 3               | 'Fred'        | '456 Avenue'
 3               | 'Fred'        | '789 Crescent'

As duas primeiras linhas são boas porque é a mesma customer_name e customer_address por customer_number 1.

As duas linhas do meio são boas porque são as mesmas customer_name e customer_address por customer_number 2 (embora outro customer_number Tem o mesmo customer_name e customer_address).

As duas últimas linhas são não está bem Porque existem dois diferentes customer_addresses para customer_number 3.

A consulta que estou procurando falharia se corresse contra todas as seis linhas. No entanto, se apenas as quatro primeiras linhas realmente existissem, a visão deve retornar:

 customer_number | customer_name | customer_address
----------------------------------------------------
 1               | 'Bob'         | '123 Street'
 2               | 'Bob'         | '123 Street'

Espero que isso esclareça o que eu quis dizer com "conflitante customer_name e customer_address"Eles têm que ser únicos por customer_number.

Agradeço aqueles que estão explicando como importar adequadamente dados de sistemas externos. Na verdade, já estou fazendo a maior parte disso. EU propositalmente escondeu todos os detalhes do que estou fazendo para que fosse mais fácil se concentrar na questão em questão. Esta consulta não deve ser a única forma de validação. Eu apenas pensei que faria um bom toque final (uma última defesa, por assim dizer). Esta pergunta foi simplesmente projetada para investigar exatamente o que era possível com o SQL. :)

Foi útil?

Solução

Um sub-quadro escalar deve retornar apenas uma linha (por resultado de resultado, linha ...) para que você possa fazer algo como:

select distinct
       customer_number,
       (
       select distinct
              customer_address
         from customers c2
        where c2.customer_number = c.customer_number
       ) as customer_address
  from customers c

Outras dicas

Sua abordagem é falha. Você não deseja que os dados que pudessem ser armazenados com êxito sejam então lançados um erro em uma seleção - que é uma mina terrestre esperando para acontecer e significa que você nunca sabe quando uma seleção pode falhar.

O que eu recomendo é que você adicione uma chave exclusiva à tabela e comece lentamente a modificar seu aplicativo para usar essa chave em vez de confiar em qualquer combinação de dados significativos.

Você pode parar de se preocupar com dados duplicados, que não são realmente duplicados em primeiro lugar. É inteiramente possível para duas pessoas com o mesmo nome compartilhar o mesmo endereço.

Você também obterá melhorias de desempenho com essa abordagem.

Como um aparte, eu recomendo que você normalize seus dados, isso é dividir o nome no primeiro nome e no último nome (opcionalmente no nome do meio) e dividir o campo de endereço em campos separados para cada componente (endereço1, endereço2, cidade, estado, país, país , Zip, ou o que quer que seja)

Atualizar: Se eu entendi sua situação corretamente (o que não tenho certeza de), você deseja impedir que combinações duplicadas de nome e endereço ocorram na tabela (mesmo que essa seja uma possível ocorrência na vida real). Isso é melhor feito por uma restrição ou índice exclusivo nesses dois campos para impedir que os dados sejam inseridos. Isto é, pegue o erro antes da você insere isso. Isso informará o arquivo de importação ou sua lógica de aplicativo resultante é ruim e você pode optar por tomar as medidas apropriadas.

Eu ainda mantenho que lançar o erro quando você consulta é tarde demais no jogo para fazer qualquer coisa a respeito.

Fazer a falha da consulta pode ser complicado ...

Isso mostrará se houver algum registro duplicado na tabela:

select customer_number, customer_name, customer_address
from customers
group by customer_number, customer_name, customer_address
having count(*) > 1

Se você apenas adicionar um índice exclusivo para todos os três campos, ninguém poderá criar um registro duplicado na tabela.

A tecla Defacto é nome+endereço, e é por isso que você precisa agrupar.

SELECT
  Customer_Name,
  Customer_Address,
  CASE WHEN Count(DISTINCT Customer_Number) > 1
    THEN 1/0 ELSE 0 END as LandMine
FROM Customers
GROUP BY Customer_Name, Customer_Address

Se você quiser fazer isso do ponto de vista de um cliente_number, isso também é bom.

SELECT *, 
CASE WHEN Exists((
  SELECT top 1 1
  FROM Customers c2
  WHERE c1.Customer_Number != c2.Customer_Number
    AND c1.Customer_Name = c2.Customer_Name
    AND c1.Customer_Address = c2.Customer_Address
)) THEN 1/0 ELSE 0 END as LandMine
FROM Customers c1
WHERE Customer_Number = @Number

Se você tiver dados sujos, eu o limparia primeiro.

Use isso para encontrar os registros duplicados do cliente ...

Select * From customers
Where customer_number in 
  (Select Customer_number from customers
  Group by customer_number Having count(*) > 1)

Se você deseja que falhe, precisará ter um índice. Se você não deseja ter um índice, basta criar uma tabela de temperatura para fazer isso tudo.

CREATE TABLE #temp_customers 
    (customer_number int, 
    customer_name varchar(50), 
    customer_address varchar(50),
    PRIMARY KEY (customer_number),
     UNIQUE(customr_name, customer_address))

)

INSERT INTO #temp_customers
SELECT DISTINCT customer_number, customer_name, customer_address
FROM customers

SELECT customer_number, customer_name, customer_address
FROM #temp_customers

DROP TABLE #temp_customers

Isso falhará se houver problemas, mas impedirá que seus registros duplicados causem problemas.

Vamos colocar os dados em uma tabela temporária ou variável de tabela com sua consulta distinta

select distinct customer_number, customer_name, customer_address, 
  IDENTITY(int, 1,1) AS ID_Num
into #temp 
from unprocessed_invoices

Pessoalmente, eu acrescentaria uma indetidade às faturas não cessadas, se possível, também. Eu nunca faço uma importação sem criar uma tabela de preparação que tenha uma coluna de identidade apenas porque é mais fácil excluir registros duplicados.

Agora vamos consultar a tabela para encontrar seus registros de problemas. Suponho que você gostaria de ver o que está causando o problema, não apenas falhando com eles.

Select t1.* from #temp t1
join #temp t2 
  on t1.customer_name = t2.customer_name and t1.customer_address = t2.customer_address 
where t1.customer_number <> t2.customer_number

select t1.* from #temp t1
join 
(select customer_number from #temp group by customer_number having count(*) >1) t2
  on t1.customer_number = t2.customer_number

Você pode usar uma variação dessas consultas para excluir os registros do problema do #Temp (depende se você optar por manter um ou excluir todos os problemas possíveis) e, em seguida, insira do #Temp na tabela de produção. Você também pode desviar os registros do problema de volta a quem está fornecendo dados para serem corrigidos na extremidade deles.

Select t1.* from #temp t1
join #temp t2 
  on t1.customer_name = t2.customer_name and t1.customer_address = t2.customer_address 
where t1.customer_number <> t2.customer_number

select t1.* from #temp t1
join 
(select customer_number from #temp group by customer_number having count(*) >1) t2
  on t1.customer_number = t2.customer_number
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top