web-dev-qa-db-fra.com

SQL gauche Joindre la première correspondance seulement

J'ai une requête sur un grand nombre de grandes tables (lignes et colonnes) avec un certain nombre de jointures. Cependant, l'une des tables contient des lignes de données dupliquées, ce qui pose des problèmes pour ma requête. Puisqu'il s'agit d'un flux en temps réel en lecture seule provenant d'un autre département, je ne peux pas corriger ces données. Cependant, j'essaie d'éviter les problèmes dans ma requête.

Compte tenu de cela, je dois ajouter ces données de merde en tant que jointure gauche à ma bonne requête. L'ensemble de données ressemble à:

IDNo    FirstName   LastName    ...
-------------------------------------------
uqx     bob     smith
abc     john        willis
ABC     john        willis
aBc     john        willis
WTF     jeff        bridges
sss     bill        doe
ere     sally       abby
wtf     jeff        bridges
...

(environ 2 douzaines de colonnes et 100 000 lignes)

Mon premier instinct a été de jouer un disque distinct, il m’a donné environ 80 000 lignes:

SELECT DISTINCT P.IDNo
FROM people P

Mais quand j'essaye ce qui suit, je récupère toutes les lignes:

SELECT DISTINCT P.*
FROM people P

OR

SELECT 
    DISTINCT(P.IDNo) AS IDNoUnq 
    ,P.FirstName
    ,P.LastName
    ...etc.    
FROM people P

J'ai alors pensé que je ferais une fonction d'agrégat FIRST () sur toutes les colonnes, même si ça ne va pas. Syntaxiquement, est-ce que je fais quelque chose de mal ici?

Mise à jour: Je voulais juste noter: Ces enregistrements sont des doublons basés sur un champ d'ID non clé/non indexé énuméré ci-dessus. L'ID est un champ de texte qui, même s'il a la même valeur, est un cas différent des autres données à l'origine du problème.

37
Dave

Il s'avère que je me suis trompé. Je devais d'abord effectuer une sélection imbriquée des seules colonnes importantes, puis effectuer une sélection distincte pour empêcher les colonnes de la corbeille de données "uniques" de corrompre mes bonnes données. Ce qui suit semble avoir résolu le problème ... mais je vais essayer sur l'ensemble de données complet plus tard.

SELECT DISTINCT P2.*
FROM (
  SELECT
      IDNo
    , FirstName
    , LastName
  FROM people P
) P2

Voici quelques données de jeu demandées: http://sqlfiddle.com/#!3/050e0d/

CREATE TABLE people
(
       [entry] int
     , [IDNo] varchar(3)
     , [FirstName] varchar(5)
     , [LastName] varchar(7)
);

INSERT INTO people
    (entry,[IDNo], [FirstName], [LastName])
VALUES
    (1,'uqx', 'bob', 'smith'),
    (2,'abc', 'john', 'willis'),
    (3,'ABC', 'john', 'willis'),
    (4,'aBc', 'john', 'willis'),
    (5,'WTF', 'jeff', 'bridges'),
    (6,'Sss', 'bill', 'doe'),
    (7,'sSs', 'bill', 'doe'),
    (8,'ssS', 'bill', 'doe'),
    (9,'ere', 'sally', 'abby'),
    (10,'wtf', 'jeff', 'bridges')
;
2
Dave

distinct est pas une fonction. Il fonctionne toujours sur toutes les colonnes de la liste de sélection.

Votre problème est un problème typique du "plus grand nombre N par groupe" qui peut facilement être résolu en utilisant une fonction de fenêtre:

select ...
from (
  select IDNo,
         FirstName,
         LastName,
         ....,
         row_number() over (partition by lower(idno) order by firstname) as rn 
  from people 
) t
where rn = 1;

En utilisant le order by vous pouvez sélectionner les doublons à sélectionner.

Ce qui précède peut être utilisé dans une jointure gauche:

select ...
from x
  left join (
    select IDNo,
           FirstName,
           LastName,
           ....,
           row_number() over (partition by lower(idno) order by firstname) as rn 
    from people 
  ) p on p.idno = x=idno and p.rn = 1
where ...
39

Ajoutez une colonne d'identité (PeopleID), puis utilisez une sous-requête corrélée pour renvoyer la première valeur pour chaque valeur.

SELECT *
FROM People p
WHERE PeopleID = (
    SELECT MIN(PeopleID) 
    FROM People 
    WHERE IDNo = p.IDNo
)
3
T8RB

En fonction de la nature des lignes en double, il semble que tout ce que vous voulez, c'est que les colonnes respectent la casse. Définir le classement sur ces colonnes devrait être ce que vous cherchez:

SELECT DISTINCT p.IDNO COLLATE SQL_Latin1_General_CP1_CI_AS, p.FirstName COLLATE SQL_Latin1_General_CP1_CI_AS, p.LastName COLLATE SQL_Latin1_General_CP1_CI_AS
FROM people P

http://msdn.Microsoft.com/en-us/library/ms184391.aspx

2
Fiddles

Après mûre réflexion, ce dillema propose différentes solutions:

Tout regrouper Utilisez un agrégat sur chaque colonne pour obtenir la valeur de champ la plus grande ou la plus petite. C’est ce que je suis en train de faire car il faut 2 enregistrements partiellement remplis et "fusionne" les données.

http://sqlfiddle.com/#!3/59cde/1

SELECT
  UPPER(IDNo) AS user_id
, MAX(FirstName) AS name_first
, MAX(LastName) AS name_last
, MAX(entry) AS row_num
FROM people P
GROUP BY 
  IDNo

Obtenir le premier (ou le dernier enregistrement)

http://sqlfiddle.com/#!3/59cde/2

-- ------------------------------------------------------
-- Notes
-- entry: Auto-Number primary key some sort of unique PK is required for this method
-- IDNo:  Should be primary key in feed, but is not, we are making an upper case version
-- This gets the first entry to get last entry, change MIN() to MAX()
-- ------------------------------------------------------

SELECT 
   PC.user_id
  ,PData.FirstName
  ,PData.LastName
  ,PData.entry
FROM (
  SELECT 
      P2.user_id
     ,MIN(P2.entry) AS rownum
  FROM (
    SELECT
        UPPER(P.IDNo) AS user_id 
      , P.entry 
    FROM people P
  ) AS P2
  GROUP BY 
    P2.user_id
) AS PC
LEFT JOIN people PData
ON PData.entry = PC.rownum
ORDER BY 
   PData.entry
1
Dave

Essaye ça

 SELECT *
 FROM people P 
 where P.IDNo in (SELECT DISTINCT IDNo
              FROM people)
1
Ramppy Dumppy