Vulnerabilidade de Segurança no ASP.NET

Recentemente, em um congresso internacional, descobriu-se um problema de vulnerabilidade em sistemas ASP.NET através do web.config. O problema afeta todas as versões do framework... A matéria completa de como é o ataque e de como se proteger podem ser lidos nesses posts de Scott Guthrie:
  1. Important: ASP.NET Security Vulnerability
  2. Frequently Asked Questions about the ASP.NET Security Vulnerability
A priori, para impedir o ataque, proteja sua aplicação fazendo com que os erros não sejam exibidos para o cliente. No web.config adicione as seguintes tags:

<configuration>
   <system.web>
      <customErrors mode="On" defaultRedirect="~/Erro.aspx" />
   </system.web>
</configuration>

Assim, toda vez que ocorrer um erro, irá ser redirecionado para uma tela específica.

URL Rewriter - Escreva URL Amigáveis em ASP.NET

A URL Rewriter permite mascarar o endereço original por um novo endereço mais seguro e amigável. Se temos o endereço:

http://thiagomarcal.blogspot.com/noticas.aspx?id_noticia=123

Podemos re-escrevê-la da seguinte forma:

http://thiagomarcal.blogspot.com/noticias/123

Ou de outras formas conforme o mascaramento desejado. Para fazer o mascaramento é preciso do componente URL Rewriter instalado no IIS. Ele é gratuito e é instalado facilmente. Para cada web-site é configurado um mascaramento de endereços que automaticamente ele faz a conversão logo que requisitado.
Não vou entrar em detalhes do processo de instalação e configuração aqui pois no site oficial tem um vídeo (logo na Home), bastando seguir o passo-a-passo, que irá conseguir deixar conforme deseja. Também há disponível vários tutoriais que auxiliam no processo bem como funções avançadas. Clique aqui para visitar o site da Micrososft URL Rewriter.

PagSeguro com ASP.NET (C#)

O PagSeguro é uma das melhores soluções para pagamento on-line que já usei. Muito fácil de usá-la e integrá-la em qualquer sistema. Vantagens são inúmeras e facilidade bem além da conta. No site você encontra bastante informações de como utilizá-la bem como códigos-fonte. Teoni Valois mostra no vídeo abaixo como realizar a integração de forma muito fácil então dispensarei minhas explicações.


Nesse link aqui você obtêm o Framework, Loja-Exemplo e o Test Server. Para quem estiver com problema do retorno automático dando FALSO, siga os procedimentos desse post.

* Todos os links e vídeo aqui mostrado são de seus respectivos autores. Apenas estou divulgando seus artigos já que, se eu escrevesse, seria redundante ;)

Gerando relatórios com o Crystal Reports

O Crystal Reports é um componente que permite a criação de relatórios personalizados de alto nível. Em substituto de relatórios em HTML puro, ele fornece uma assistência inovadora desde a criação até exportação. Para quem utiliza Java, há também uma versão específica para a linguagem. Mostraremos abaixo como gerar um simples relatório buscando dados de uma tabela em banco usando C#.

Abra/Crie um projeto do tipo Web Site no Visual Studio e no Solution Explorer clique com o direito no diretório onde deseja criar o relatório. Escolha Add New Item e opte pelo template Crystal Reports (note que a extensão de relatório do Crystal é .rtp). Nessa primeira tela escolha a primeira opção conforme a figura abaixo:


Clique em OK. Expanda o nó do Create New Connection e novamente expanda o OLE DB (ADO) para configuração de conexão (essa é a primeira conexão com o banco para início, depois mostraremos como usar para outro banco). Na configuração do ADO, escolha o tipo de provider a ser utilizado (no caso optei pelo SQL Server, então SQL Native Client) e dê Next.


Agora, em Connection Information, informe os dados de conexão.


Clique em Next e na última tela revise os dados. Se estiver tudo certo, clique em Finish. Você voltará à tela anterior já com a configuração realizada e o banco estará apto a selecionar os objetos. Procure qual objeto irá ser manipulado e exibido seus dados, podendo ser: uma tabela, um schema, uma view ou stored procedure. Clique no objeto desejado e, em seguida, clique no botão com a seta para direita conforme figura abaixo:


Clique em Next. Agora informe quais campos irão ser exibidos e escolha Next.


Na próxima tela, se precisar agrupar alguns campos, informe quais são. Se não precisar dê apenas Next. Se o Crystal identificar que está trabalhando com números, ele cria um passo adicional que é a opção de inserir gráficos. Outra tela que é exibida é a de Filtros. Se for ter filtros, informe-os. Caso contrário dê apenas Next. Ou seja, as duas últimas telas são parâmetros opcionais logo, se não desejar configurá-los, dê Next duas vezes. O último passo é escolher o estilo. Opte pelo mais parecido que quer e dê Finish.


Basicamente seu relatório está pronto, bastando ajustes da posição dos campos, design, cabeçalho, etc. Em Field Explorer, contêm as principais funcionalidades para fórmulas e cálculos avançados bem como a adição de novos campos. Como estamos elaborando um relatório simples, vamos parar por aí. Para ver um preview de como estará seu relatório com os dados, clique em Main Report Preview. Agora vamos ver como chamar pelo ASP.NET (C#).
Adicione uma página Impressao.aspx e nele adicione um objeto CrystalReportViewer que automaticamente irá ser registrado o assembly na página (puxando do ToolBox ele insere automaticamente). Se preferível configure alguns atributos do objeto como remoção da logo da BO, tema, etc. No code-behind, adicione os seguintes namespaces:

using CrystalDecisions.ReportSource;
using CrystalDecisions.Shared;
using CrystalDecisions.CrystalReports.Engine;
 
E para exibir os dados do relatório utilize o seguinte código:
 
DataSet ds = new DataSet();
// Antes, faço uma consulta no banco e jogo no DataSet para ser usado logo abaixo
ReportDocument relatorio = new ReportDocument();
relatorio.Load(Server.MapPath("Relatorio.rpt"));
relatorio.Database.Tables[0].SetDataSource(ds.Tables[0]);
// Esse é Viewer que puxamos antes
CrystalReportViewerRelatorio.ReportSource = relatorio;
CrystalReportViewerRelatorio.DataBind();
 
Pronto! Seu relatório será exibido...
 
 
Agora vamos aos problemas:
 
1) Fiz todo o processo mas ao exibir o relatório está pedindo login e senha do SQL Server
 
Se estiver usando DataSet, veja que só pude passar uma tabela por vez usando ds.Tables[0] e não apenas ds. Se quiser passar várias tabelas crie um DataSet tipado no App_Code ou informe uma a uma no atributo Database.
 
2) As imagens dos botões do relatório não estão aparecendo e ao clicar não funciona nada
 
Ao ser colocado em produção, verifique se o Crystal Reports está instalado corretamente na máquina. Veja se a pasta wwwroot\aspnet_client\system_web\ contêm a pasta CrystalReportWebFormViewer (depois da pasta system_web vem a versão do framework - exemplo 2_0_50727 - e em seguida vem ela). Copie a pasta aspnet_client e adicione na raiz de sua aplicação.
 
3) Ao exibir o relatório está aparecendo o erro: Não foi possível carregar arquivo ou assembly 'System.Web.Extensions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' ou uma de suas dependências. A definição do manifesto do assembly localizado não corresponde à referência do assembly. (Exceção de HRESULT: 0x80131040)
 
Faça os procedimentos informados nesse post.
 
4) Depois que é exibido o relatório, ao clicar no botão de imprimir ou exportar ou qualquer outro botão, não executa nenhum dos processos que deveria fazer
 
Se estiver usando ASP.NET AJAX e o CrystalReportViewer estiver dentro de um UpdatePanel, remova-o. Pois, ao clicar em desses botões, é feito um PostBack na página (além de tentar abrir uma janela pop-up) e o Crystal não funciona adequadamente quando utiliza o AJAX.
 
5) Ao tentar exibir o relatório, ocorre o seguinte erro: O inicializador de tipo de 'CrystalDecisions.CrystalReports.Engine.ReportDocument' acionou uma exceção
 
Reinstale o Crystal Reports ou tente as soluções sugeridas nesse post.
 
6) Minhas imagens e/ou gráficos não estão aparendo ao exibir o relatório (fica aquele X de imagem não carregada) ou ficam distorcidas/baixa resolução
 
Se for uma imagem que está sendo inserida do disco (que você tem no computador e está inserindo através de Insert > Picture) verifique o formato dessa imagem (preferível JPG ou BMP) e as cores utilizadas bem como efeitos (fundo transparente, camadas). Quanto mais simples a imagem for, melhor será sua visualização. Se apesar de tudo ainda não estiver aparecendo, verifique no web.config se há as seguintes entradas:
  1. Em system.web > httpHandlers:

    <add verb="GET" path="CrystalImageHandler.aspx" type="CrystalDecisions.Web.CrystalImageHandler, CrystalDecisions.Web, Version=10.5.3700.0, Culture=neutral, PublicKeyToken=692fbea5521e1304"/>
    <add verb="*" path="GenerateImage.ashx" type="GenerateDynamicImage"/>
     
  2. E em system.webServer > handlers:

    <add name="CrystalImageHandler.aspx_GET" verb="GET" path="CrystalImageHandler.aspx" type="CrystalDecisions.Web.CrystalImageHandler, CrystalDecisions.Web, Version=10.5.3700.0, Culture=neutral, PublicKeyToken=692fbea5521e1304" preCondition="integratedMode"/>
7) Meu relatório tem mais de 2 páginas mas só fica exibindo 2/2+ quando clico em avançar

Coloque todo o processo de geração do relatório dentro de Page_Init ou invés de Page_Load.

8) Meu relatório não exibe, a princípio, a quantidade total de páginas. Fica exibindo 1/1+...

O Crystal faz isso para não ter de renderizar todo o relatório e sobrecarregar, então faça o seguinte artifício depois de dar o Bind():

CrystalReportViewerRelatorio.ShowLastPage();
CrystalReportViewerRelatorio.ShowFirstPage();

Ou seja, vou rapidamente para a última página e volto apenas para o Crystal renderizar a última página e saber quantas temos ao todo.

Exportar Banco de Dados (BAK ou MDF) do SQL Server 2008 para o 2005

A forma mais fácil de transferir um banco para outro é fazer um BAK dele e depois recuperar do outro lado. Do 2005 para o 2008 é possível realizar a recuperação, mas do 2008 para o 2005 não é possível realizar o downgrade. Qual a solução? Gerar o script geral do banco e executá-lo no banco de destino. Mas se meu banco é muito grande, como fazer? A dica está no final do post, mas façamos tudo desde o início.
Abra o SQL Server Management Studio (ou Express) e conecte-se ao Server. Clique com o botão direito sobre a base de dados e siga em Task (Tarefas) e escolha Generate Scritps (Gerar Scripts). Na tela de seleção de base, marque a opção Script all objects in the selected database (Scritp de todos os objetos da base selecionada) e avance. Nessa próxima tela procure os itens abaixo e atribua os seguintes valores:
  1. Script for Server Version (Versão de Script do Server): SQL Server 2005
  2. Script Data (Script de Dados): True
  3. Script USE DATABASE: False
Demais opções fica a seu critério optar... Agora avance e na próxima tela escolha salvar o arquivo em disco com as opções Simples (Single file) e Unicode text. Avance, revise o que foi selecionado e finalize. Aguarde até que o processo seja finalizado. Quando terminado seu script já estará pronto para ser executado no destino.
O próximo passo é executar o script gerado na base de destino. Crie a base de destino vazia e agora temos duas opções:
  • Se o script gerado for pequeno (ou seja base pequena) podemos simplesmente abrir pelo próprio SQL Server Management e executar como se fosse Querys consecutivas;
  • Se o script for grande vamos usar um artifícío para isso.
Como a primeira opção é a mais simples (e também porque gosto de aventurar-me) vamos optar pela segunda (e que também é nossa realidade). No meu exemplo, o script gerado ficou em torno de 1,5Gb. Se desejar reduzir o tamanho um pouco, às vezes, reduzir o log ajuda (saiba como reduzir aqui) antes de gerar o script.
Abra o Prompt de Comando (emulador do DOS no Windows) e digite o seguinte comando:


sqlcmd -U thiago -P blog -S THIAGOMARCAL\SQLEXPRESS -d BaseBlog -i C:\ScriptSQL.sql -o C:\LogExecucao.txt

Explicando os atributos:
  • U: informa o nome do usuário que está conectando (no caso, o usuário thiago);
  • P: informa a senha do usuário (a senha é blog);
  • S: nome do servidor (como está na minha máquina a instância é THIAGOMARCAL\SQLEXPRESS);
  • d: nome da base nova que receberá o resultado do script (BaseBlog é o nome da nova base criada);
  • i: local onde salvei o script de dados e que usarei como entrada;
  • o: arquivo de texto contendo o resultado da operações (log para análise - opcional).
Dê ENTER e aguarde toda a operação ser executada. Para quaisquer tamanho do arquivo ele será processado sem problemas. Se tentasse abrir pelo SQL Server um arquivo muito grande daria erro de alocação de memória, ou demoraria e nem conseguiria executar exibindo alguns dos seguintes erros:
  • Cannot Open Datafile
  • Insufficient memory
  • Out of Memory Exception
Dessa forma é mais tranquilo...

HTTP 417 Expectation failed (falha de expectativa)

Dica rápida! Esse erro ocorre quando tenta fazer uma requisição por WebRequest, Web-Service ou outra requisição entre computadores. Para sanar o problema use a chamada antes de realizar o processo:

System.Net.ServicePointManager.Expect100Continue = false;

Para saber mais detalhes sobre erros HTTP 417, acesse aqui.

Localização de Usuário: Obter Cidade e Estado pelo IP em ASP.NET

A um certo tempo atrás isso era um pouco complicado de ser feito ou ficávamos dependentes (muito) de ferramentas de terceiros. Hoje, claramente, posso afirmar que temos mais flexibilidade porém continuamos atrelados. Mas não seja por isso! A Google disponibilizou uma API inovadora que nos ajuda nesse caso e em muitos outros (relativo ao GoogleMaps). O GoogleMaps.Subgurim.NET é um avançado controle do GoogleMaps para o ASP.NET e permite uma gama de manipulações sobre suas funcionalidades e a que iremos demonstrar aqui é a de obter informações do usuário pelo IP (no site deles pode encontrar bem como códigos avançados e demais exemplos). Em contrapartida, é preciso de um arquivo extra contendo faixas de IP distribuídas pelo mundo. Então a MaxMind disponibilizou também um arquivo binário contendo essas informações gratuitamente. Você pode obter o arquivo aqui ou aqui (a diferença é que um é o link direto e o outro é a página geral que pode-se obter em outros formatos). Esse arquivo é mensalmente atualizando inserindo novas faixas, logo, deixo o dica para dar sempre uma atualizada quando possível (com isso aumenta mais a precisão mas hoje, com esse arquivo, já realiza uma boa cobertura).
Para início dos trabalhos, baixe o componente GoogleMaps.Subgurim.NET e o arquivo de faixas GeoLiteCity.dat. Crie um novo Web Site. Adicione a GMaps.dll na pasta Bin do projeto e copie o GeoLiteCity.dat também (coloque em outra pasta afim de organizá-los - no meu caso coloquei em uma pasta chamada Data). Agora escreva o seguinte código abaixo:

using Subgurim.Controles;

private void ObtemInfoUser()
{
    string database = Server.MapPath("~/Data/GeoLiteCity.dat");
    LookupService servico = new LookupService(database);
    Location localizacao = servico.getLocation(Request.ServerVariables["REMOTE_ADDR"]);
    if (localizacao != null)
    {
        Response.Write("Cidade: " + localizacao.city + "<br />");
        Response.Write("País: " + localizacao.countryName + "<br />");
        Response.Write("Código do País: " + localizacao.countryCode + "<br />");
        Response.Write("Região: " + localizacao.region + "<br />");
        Response.Write("Código da Área: " + localizacao.area_code + "<br />");
        Response.Write("Latitude: " + localizacao.latitude + "<br />");
        Response.Write("Longitudade: " + localizacao.longitude);
    }
}
 
Você deve obter a saída algo do tipo:
 
Cidade: Salvador
País: Brazil
Código do País: BR
Região: 5
Código da Área: 0
Latitude: -12,7636
Longitude: -15,2821
 
* Obs: a latitude e longitude são dados fictícios que eu coloquei ;)
 
Se você estiver testando localmente, não terá resultado pois o Request.ServerVariables["REMOTE_ADDR"] irá trazer o valor 127.0.0.1. Para saber seu IP externo acesse o MeuIp e substitua-o para visualizar os valores e validá-los. Em breve estarei escrevendo outros artigos mostrando como aproveitar bem a API do GoogleMaps bem como demais funcionalidades. Até mais!

UPDATE: Após ler, vejam aqui sobre o plágio que fizeram com meu artigo e depois acusaram que eu que copiei. Quem lê o blog sabe como eu escrevo e como expresso minhas idéias...

Utilizando função de uma DLL Externa no SQL Server

Esse é um tópico que irá mostrar algo que definitivamente você já precisou mas não sabia como fazer e que facilitará (e muito) alguns trabalhos. Programadores que usam o Bando de Dados para programar sendo por Stored Procedure, Functions e Triggers já passaram por maus bocados para preparar um algoritmo que realizasse um determinado processo, mas que esse mesmo algoritmo é mais fácil de ser escrito (ou possuir funcionalidades que o SGDB não possui) em outra linguagem de programação. Por exemplo: validar e-mail. É possível no SQL Server fazermos um Function que receba um VARCHAR e retorne um BIT informando que um e-mail é válido ou não, contudo seria uma função um tanto que trabalhosa para quem não está familiarizado com programação em banco. Para quem desenvolve em ASP.NET (C# ou VB.NET) basta usarmos expressão regular que, em duas linhas, resolve o problema. Portanto iremos utilizar uma função escrita em C# compilada em uma DLL e usarmos em um Function no SQL Server. Isso é possível através do CLR.
Primeiramente vamos criar a DLL contendo a função (no nosso exemplo vamos fazer a que valida e-mail - mas você pode desenvolver a que quiser). Abra o Visual Studio e crie um Project. Em Project Type escolha Visual C# Windows e o Template opte por uma Class Libary. Note que quando você cria uma classe vem com o nome Class1.cs. Para facilitar a compreensão, renomeie para FuncoesSql.cs (ou outra) e remova o namespace criado. Agora, escreva a função que deseja e adicione os namespaces necessários ficando da seguinte forma:

using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.SqlServer.Server;

public partial class FuncoesSql
{
    [Microsoft.SqlServer.Server.SqlFunction]
    public static SqlBoolean IsMailValido(SqlString email)
    {
        // Cria um objeto de expressões regulares para validar e-mail
        Regex expressaoRegular = new Regex(@"^(([^<>()[\]\\.,;:\s@\""]+"
        + @"(\.[^<>()[\]\\.,;:\s@\""]+)*)(\"".+\""))@"
        + @"((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}"
        + @"\.[0-9]{1,3}\])(([a-zA-Z\-0-9]+\.)+"
        + @"[a-zA-Z]{2,}))$");

        // Realiza um teste na validação da expressão
        return new SqlBoolean(expressaoRegular.IsMatch(email.ToString()));
    }
}

No caso, precisamos explicitar os tipos de entrada e saída utilizados pelo banco de dados para que não haja problemas de tipagem. Poderia ser qualquer função ou quantas que quisesse, contanto que respeite os tipos. Agora compile a DLL pressionando F6 ou pelo menu Build. Já temos a DLL com a função. Copie a DLL (que está na pasta bin\Debug de sua solução) e coloque-a em um local onde seu Banco de Dados possa buscá-la. Para exemplo, colocarei em C:\SqlDlls\ no Servidor.
O próximo passo é verificar se no SQL Server está ativo a funcionalidade CLR, para isso vá em Iniciar > Todos os Programas > Microsft SQL Server 2008 (ou 2005) > Configuration Tools > SQL Server Surface Area Configuration. Entre em Surface Area Configuration for Features e na guia CLR Integration verifique se a função está habilitada. Se não estiver, habilite.


Dê OK e agora já poderá usar CLR no SQL Server. Abra o SQL Server Management Studio, conecte-se na base desejada e execute as seguintes instruções SQL (abra um New Query para isso):

-- CRIA O ASSEMBLY INDICANDO SUA ORIGEM
CREATE ASSEMBLY FuncoesSql FROM 'C:\SqlDlls\FuncoesSqlServerASPNET.dll'
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- CRIA A FUNÇÃO
CREATE FUNCTION dbo.Fun_Valida_Mail (@email NVARCHAR(350))
RETURNS BIT
-- INDICA A ORIGEM DA FUNÇÃO SQL: ASSEMBLY > CLASSE > FUNÇÃO
AS EXTERNAL NAME [FuncoesSql].FuncoesSql.IsMailValido
GO

Agora execute a Query (ou pressione F5)... Foi criada a função! Pronto... agora é só usar como se fosse uam função do SQL Server normalmente. Abaixo tem alguns exemplos de consulta e seus retornos:

Execuções:
SELECT dbo.Fun_Valida_Mail('tmarcal@gmail')
SELECT dbo.Fun_Valida_Mail('tmarcal@gmail.com')
SELECT dbo.Fun_Valida_Mail('tmarcal.. @gmail.com')
 
Retorno:
0
1
0
 
Apesar do trabalho inicial, pode-se aproveitar muito de ambos os recursos e criar cada vez mais funções complexas. Quanto ao desempenho da CLR, em comparação ao T-SQL, deve-se ficar a mesma coisa. A diferença é praticamente imperceptível. Espero que tenham gostado... Até breve!

Não foi possível carregar arquivo ou assembly System.Web.Extensions, Version=2.0.0.0

Dica rápida para quem tiver esse erro...
Acontece quando está utilizando uma versão do ASP.NET Ajax diferente do Framework da aplicação. Muitas vezes, também, acontece quando está utilizando o Crystal Report ao renderizar um relatório. Então é exibido a seguinte mensagem:
A solução é simples: basta remover a System.Web.Extensions.dll e/ou System.Web.Extensions.Design.dll do projeto (na pasta Bin).
Não foi possível carregar arquivo ou assembly 'System.Web.Extensions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' ou uma de suas dependências. A definição do manifesto do assembly localizado não corresponde à referência do assembly. (Exceção de HRESULT: 0x80131040)

Dicas de Instruções SQL para facilitar o uso no dia-a-dia

Bom, abaixo separei algumas instruções SQL que muitos programadores tem dúvidas de como usá-las e que facilitam muitas tarefas.

1) INSERT de SELECT: Fazer inserções em uma tabela puxando dados de outra(s)

Bem parecido com o SELECT INTO porém você pode fazer várias manipulações conforme desejar usando um simples SELECT. No caso vou mostrar um exemplo bem fácil: irei adicionar vários Alunos numa tabela sendo que os dados estão em outra tabela (Cadastros), logo minha instrução ficaria assim:

INSERT INTO Alunos (nome, email) SELECT nome, email FROM Cadastros

O nome dos campos não precisam serem iguais, basta estar na mesma ordem e respeitar o tipo de dados (tamanho também).

2) UPDATE relacionado com outra tabela

Nessa situação o que desejamos é fazer um UPDATE na tabela sendo que é preciso fazer um JOIN com outra tabela. No nosso exemplo: desejamos alterar o preco de um produto sendo que o valor do mesmo está relacionado com seu tipo e ele está armazenado em outra tabela. Logo podemos usar a seguinte instrução:

UPDATE Produto SET preco = T.preco FROM Produto AS P, Tipo AS T WHERE P.Id_Tipo = T.Id

Meio complicado quando se vê, né? Mas se você reparar detalhadamente verá que a atualização do preço é feita, para cada produto, quando o Id_Tipo (da tabela Produto) for igual ao Id (da tabela Tipo) obtendo assim o preco (da tabela Tipo).

3) Remover registros duplicados deixando apenas um

A explicação desse objetivo é bem óbvia: remover registros duplicados em uma determinada tabela sob,algum critério. Porém, sua sintaxe é mais complicada, mas vamos lá... Para nosso exemplo iremos remover os registros duplicados (ou mais que um) de e-mails deixando apenas um na tabela Mailing:

;WITH Listagem(email, ranking)
AS
(
SELECT email
,ranking = DENSE_RANK() OVER(PARTITION BY email ORDER BY NEWID() ASC)
FROM Mailing WITH (NOLOCK) WHERE email IS NOT NULL 
)
DELETE Listagem WHERE ranking > 1

Esse emaranhado de instruções se resume em gerar um ranking indicando quantas vezes o registro se repete. Esse ranking é armazenado em uma tabela "temporária" (tabela Listagem) com referência à original (tabela Mailing)... Logo que é gerado o ranking, é feita a exclusão dos registros que possuem ranking maior que 1, ou seja, apaga todos aqueles que estão se repetindo deixando apenas um deles.

Bem, é isso! Espero ter ajudado... Lembrando que os exemplos acima foram os mais simples e vocês podem encontrar situações semelhantes ou piores mas que, usando-os, podem ajudar a chegar na resolução.