2009-09-24 7 views
2

(Advantage Database Server) J'ai une table de fournisseurs de services qui, à des fins d'audit, ne sont jamais supprimés. Ils ont une date de début et une date de fin; Dans le cas de modifications telles que le nom ou l'adresse, la ligne existante est finie, une nouvelle ligne est créée et une nouvelle date de début est affectée aux données modifiées.Correspondance d'une seule ligne spécifique dans un JOIN où plusieurs existent

Lors du traitement des paiements à ces fournisseurs, j'ai besoin d'une page récapitulative indiquant le nom du fournisseur, son adresse, son identifiant (ProvID) et le montant total payé. Ceci est fait dans une requête assez simple avec un SUM() et GROUP BY.

Le problème apparaît lorsqu'il existe deux ou plusieurs lignes pour un identificateur de fournisseur spécifié. Je me retrouve avec des lignes en double (ce qui pourrait entraîner des paiements multiples à ce fournisseur s'il n'est pas intercepté).

Ma première pensée était d'utiliser quelque chose (laid, mais réalise assez rapidement) comme une sous-sélection:

SELECT ... FROM service s 
INNER JOIN provider p ON p.ProvID = s.ProvID 
AND (p.EndDate IS NULL or p.EndDate = (SELECT Max(EndDate) FROM 
    provider lu WHERE lu.ProvID = s.ProvID)) 

Malheureusement, cela a fini par trouver encore deux rangées; une ligne pour le NULL EndDate et un pour le MAX (EndDate).

Je gérer cela dans d'autres cas (par exemple., La localisation du ProvID approprié pour un service fourni à une date précise) en utilisant

p.EndDate is null or (s.ServiceDate BETWEEN p.StartDate AND p.EndDate) 

Malheureusement, étant donné que la requête de problème est GROUP BY avec un agrégat, la La date de service n'est pas disponible.

Des suggestions?

EDIT: Ce que je recherche est soit la ligne avec le NULL EndDate si elle existe, OU la ligne avec le Max (EndDate) si la ligne NULL n'existe pas. Cela couvre le cas, par exemple, où un fournisseur a été licencié hier, mais a travaillé la semaine dernière, et nous le paierons la semaine prochaine.

Répondre

3

Je suppose que s'il y a une ligne avec date de fin NULL , vous voulez celui-là, sinon vous voulez celui avec la plus grande date de fin?

Je ne suis pas sûr de l'ADS, mais ce qui suit fonctionnerait sur SQL Server:

SELECT ... FROM service s 
INNER JOIN provider p ON p.ProvID = s.ProvID 
AND (COALESCE(p.EndDate, '2037-01-01') = (
    SELECT Max(COALESCE(EndDate, '2037-01-01')) FROM 
    provider lu WHERE lu.ProvID = s.ProvID) 
) 

L'opérateur COALESCE renvoie le premier paramètre non nul, donc cela est essentiellement suffit de positionner la nulls à un temps loin dans le futur, de sorte que SELECT MAX vous donnera celui avec la date de fin NULL s'il y en a un.

+0

J'accepte cette réponse parce que c'est assez joli, et c'est légèrement plus rapide que la réponse NOT EXISTS de najmeddine (mais seulement légèrement en raison de la bonne sélection d'index). Merci, Kip. :-) –

+0

Juste fusionné dans mon code et testé contre les données réelles. Fonctionne à 100%, sans perte de performance notable. Merci encore, Kip! NOTE POUR LES AUTRES UTILISATEURS ADS: Les appels COALESCE() nécessitent un ajout: Changez les deux à COALESCE (EndDate, CAST ('2037-01-01' AS SQL_DATE)), car ADS ne fait pas automatiquement la conversion à partir de la date littéraux comme les autres bases de données. –

+0

@Ken White: Si vous avez plus d'une ligne avec la date de fin NULL, vous obtiendrez plus d'une ligne comme résultat. Je suppose que cela serait considéré comme des données corrompues dans votre schéma. – Kip

0

Peut-être utiliser un sous-requête en place de la deuxième table:

SELECT ... FROM service s 
INNER JOIN (SELECT ..., Max(EndDate) FROM 
    provider lu WHERE lu.ProvID = s.ProvID GROUP BY ...) p ON p.ProvID = s.ProvID 

Ceci est en supposant que vous obtiendriez NULL de retour s'il n'y a pas enddate max.

0

Ce à quoi vous faites référence est une dimension de type 2 d'un entrepôt de données.

Vous devez rejoindre par l'ID et par les StartDate et EndDate pour obtenir les données correctes.

Code OTTOMH

SELECT TransactionId, TransactionType 
FROM TransactionList Tx 
    INNER JOIN TransactionType TxType 
     ON Tx.TransactionTypeId = TxType.TxTypeId 
     AND Tx.TransactionDate Between TxType.StartDate and TxType.EndDate 
+0

Comme je l'ai mentionné, je ne peux pas le faire parce que j'utilise une fonction d'agrégat et un GROUP BY. La date de transaction n'est pas disponible en raison de l'agrégation. –

+0

Excuses. J'avais évidemment passé sous silence cette partie. –

+0

Pas de problème. Merci d'avoir essayé, Raj. –

3

dans la 2ème condition, vous devez obtenir le maximum que s'il n'y a pas EndDate NULL

SELECT ... FROM service s 
INNER JOIN provider p ON p.ProvID = s.ProvID 
AND ( p.EndDate IS NULL 
    or (p.EndDate = (SELECT Max(EndDate) 
         FROM provider lu 
         WHERE lu.ProvID = s.ProvID) 
     AND NOT EXISTS (SELECT NULL 
          FROM provider lu 
          WHERE lu.ProvID = s.ProvID 
          AND lu.EndDate IS NULL) 
     ) 
    ) 
+0

@najmeddine: J'ai accepté la réponse de Kip car elle était ** légèrement ** plus rapide que la vôtre (car il utilisait deux appels de fonction COALESCE() au lieu d'ajouter un autre sous-menu pour le test NOT EXISTS). Le vôtre a bien fonctionné aussi, cependant, donc je suis upvoting aussi bien. Merci! –

0

Ce qui dans votre table de fournisseur indique le actuel date? EndDate = NULL, EndDate = Max (EndDate) ou EndDate = '9999-01-01'? Tous les trois sont des choix valides, mais cela devrait vraiment être sans ambiguïté, car si ce n'est pas le cas, vous allez vous retrouver avec des lignes dupliquées dans les requêtes tout le temps, peu importe comment vous construisez intelligemment cette requête particulière. Donc, je suggère de corriger cela dans la table des fournisseurs, puis quelque chose comme ça devrait fonctionner:

select p.name, p.address, p.id, sum(s.amount) 
    from provider p 
    join service s on p.id=s.provider_id 
where p.endDate is NULL 
group by p.name, p.address, p.id 
+0

Impossible de réparer la table du fournisseur. Ce sont des données historiques et actuelles, et les vérificateurs ne nous permettent pas de changer cela pour quelque raison que ce soit. Si la restructuration des données était une option, je n'aurais pas eu besoin de poster ici. Merci quand même. –

+0

Penser un peu plus à ce sujet, vous _do_ ont une condition non ambiguë. Vous voulez expédier à l'adresse actuelle, non? Ensuite, vous n'avez pas besoin de s.ServiceDate. Utilisez simplement "où p.EndDate est null ou (sysdate entre p.StartDate et p.EndDate)". est-ce que cela aide? – wallenborn

+0

Malheureusement, non. Voir ma modification à l'article d'origine sur le fournisseur résilié avec des services en cours avant la date d'échéance, mais être payé après la date d'échéance. SysDate ne tomberait pas entre le début et la fin, et il n'y aurait pas de ligne de date de fin NULL au moment où le paiement a été traité. –