J'ai 2 tables - une table de compte et une table d'utilisateurs. Chaque compte peut avoir plusieurs utilisateurs. J'ai un scénario dans lequel je veux exécuter une requête/jointure unique sur ces deux tables, mais je veux toutes les données du compte (Account. *) Et seulement le premier ensemble de données utilisateur (spécifiquement leur nom).
Au lieu de faire un "min" ou un "max" sur mon groupe agrégé, je voulais faire un "premier". Mais, apparemment, il n'y a pas de "première" fonction d'agrégat dans TSQL.
Des suggestions sur la façon d'obtenir cette requête? Évidemment, il est facile d’obtenir le produit cartésien de Account x Users:
SELECT User.Name, Account.* FROM Account, User
WHERE Account.ID = User.Account_ID
Mais comment pourrais-je obtenir seulement le premier utilisateur du produit en fonction de l'ordre de leur User.ID?
Plutôt que de grouper, agissez comme ça ...
select
*
from account a
join (
select
account_id,
row_number() over (order by account_id, id) -
rank() over (order by account_id) as row_num from user
) first on first.account_id = a.id and first.row_num = 0
Je sais que ma réponse est un peu tardive, mais cela pourrait aider les autres. Il existe un moyen de réaliser un First () et un Last () dans SQL Server, et le voici:
Stuff(Min(Convert(Varchar, DATE_FIELD, 126) + Convert(Varchar, DESIRED_FIELD)), 1, 23, '')
Utilisez Min () pour First () et Max () pour Last (). DATE_FIELD doit être la date qui détermine s'il s'agit du premier ou du dernier enregistrement. Le DESIRED_FIELD est le champ que vous voulez la première ou la dernière valeur. Qu'est-ce qu'il fait est:
Voici!
EDIT: J'ai eu des problèmes avec la première formule: lorsque le champ DATE_FIELD a une valeur de millisecondes, SQL Server renvoie la date sous forme de chaîne avec AUCUN millisecondes, supprimant ainsi les 4 premiers caractères du DESIRED_FIELD. J'ai simplement changé le format à "20" (sans millisecondes) et cela fonctionne très bien. Le seul inconvénient est que si vous avez deux champs créés à la même seconde, le tri peut éventuellement être compliqué ... Dans ce cas, vous pouvez revenir à "126" pour le format.
Stuff(Max(Convert(Varchar, DATE_FIELD, 20) + Convert(Varchar, DESIRED_FIELD)), 1, 19, '')
EDIT 2: Mon intention initiale était de renvoyer la dernière (ou la première) ligne NON NULL. On m'a demandé comment retourner la dernière ou la première ligne, qu'elle soit nulle ou non. Ajoutez simplement un ISNULL au DESIRED_FIELD. Lorsque vous concaténez deux chaînes avec un opérateur +, lorsque l'une d'entre elles est NULL, le résultat est NULL. Alors utilisez les éléments suivants:
Stuff(Max(Convert(Varchar, DATE_FIELD, 20) + IsNull(Convert(Varchar, DESIRED_FIELD), '')), 1, 19, '')
Select *
From Accounts a
Left Join (
Select u.*,
row_number() over (Partition By u.AccountKey Order By u.UserKey) as Ranking
From Users u
) as UsersRanked
on UsersRanked.AccountKey = a.AccountKey and UsersRanked.Ranking = 1
Ceci peut être simplifié en utilisant la clause Partition By. Dans ce qui précède, si un compte a trois utilisateurs, la sous-requête est numérotée 1, 2 et 3 et, pour une autre clé de compte, elle réinitialisera la numérotation. Cela signifie que pour chaque clé de compte unique, il y aura toujours un 1, et potentiellement 2,3,4, etc.
Vous filtrez donc sur Ranking = 1 pour récupérer le premier de chaque groupe.
Cela vous donnera une ligne par compte, et s'il y a au moins un utilisateur pour ce compte, il vous donnera l'utilisateur avec la clé la plus basse (parce que j'utilise une jointure à gauche, vous obtiendrez toujours une liste de comptes même si aucun l'utilisateur existe). Remplacez Order By u.UserKey
par un autre champ si vous préférez que le premier utilisateur soit choisi par ordre alphabétique ou par un autre critère.
La réponse de STUFF de Dominic Goulet est lisse. Mais, si votre champ DATE_FIELD est SMALLDATETIME (au lieu de DATETIME), la longueur ISO 8601 sera 19, au lieu de 23 (SMALLDATETIME n’ayant pas de millisecondes), ajustez donc le paramètre STUFF en conséquence ou la valeur renvoyée par la fonction STUFF sera incorrecte ( manque les quatre premiers caractères).
Vous pouvez utiliser OUTER APPLY, voir documentation .
SELECT User1.Name, Account.* FROM Account
OUTER APPLY
(SELECT TOP 1 Name
FROM [User]
WHERE Account.ID = [User].Account_ID
ORDER BY Name ASC) User1
First et Last n'existent pas dans Sql Server 2005 ou 2008, mais il existe une fonction First_Value, Last_Value dans Sql Server 2012. J'ai essayé d'implémenter l'agrégat First and Last pour SQL Server 2005 et je suis tombé sur l'obstacle voulant que SQL Server garantisse le calcul de l'agrégat dans un ordre défini. (Voir l'attribut SqlUserDefinedAggregateAttribute.IsInvariantToOrder, qui n'est pas implémenté.) Cela peut être dû au fait que l'analyseur de requête tente d'exécuter le calcul de l'agrégat sur plusieurs threads et combine les résultats, ce qui accélère l'exécution, mais ne garantit pas un ordre. quels éléments sont agrégés.
SELECT (SELECT TOP 1 Name
FROM User
WHERE Account_ID = a.AccountID
ORDER BY UserID) [Name],
a.*
FROM Account a
J'ai référencé toutes les méthodes, la méthode la plus simple et la plus rapide pour y parvenir consiste à utiliser une application externe/croisée.
SELECT u.Name, Account.* FROM Account
OUTER APPLY (SELECT TOP 1 * FROM User WHERE Account.ID = Account_ID ) as u
CROSS APPLY fonctionne comme INNER JOIN et extrait les lignes où les deux tables sont liées, tandis que OUTER APPLY fonctionne comme LEFT OUTER JOIN et extrait toutes les lignes de la table de gauche (Compte ici)
Il y a plusieurs façons de procéder, voici une solution rapide et sale.
Select (SELECT TOP 1 U.Name FROM Users U WHERE U.Account_ID = A.ID) AS "Name,
A.*
FROM Account A
Définir "premier". Ce que vous pensez en premier lieu est une coïncidence qui concerne normalement l'ordre des index en cluster, mais ne doit pas être invoqué (vous pouvez inventer des exemples qui le cassent).
Vous avez raison de ne pas utiliser MAX () ou MIN (). Bien que tentant, considérons le scénario dans lequel le prénom et le nom de famille sont dans des champs distincts. Vous pourriez obtenir des noms de différents enregistrements.
Comme il semble que tout ce qui vous préoccupe est que vous obteniez exactement un enregistrement arbitraire pour chaque groupe, vous pouvez simplement simplement MIN ou MAX un champ ID pour cet enregistrement, puis associer la table à la requête portant sur cet ID.
Créer et rejoindre une sous-sélection 'FirstUser' qui renvoie le premier utilisateur pour chaque compte
SELECT User.Name, Account.*
FROM Account, User,
(select min(user.id) id,account_id from User group by user.account_id) as firstUser
WHERE Account.ID = User.Account_ID
and User.id = firstUser.id and Account.ID = firstUser.account_id
(Un peu hors sujet, mais) Je lance souvent des requêtes globales pour répertorier les résumés des exceptions, puis je veux savoir POURQUOI un client figure dans les résultats, utilisez donc MIN et MAX pour donner 2 échantillons semi-aléatoires que je peux consulter dans détails par exemple.
SELECT Customer.Id, COUNT(*) AS ProblemCount
, MIN(Invoice.Id) AS MinInv, MAX(Invoice.Id) AS MaxInv
FROM Customer
INNER JOIN Invoice on Invoice.CustomerId = Customer.Id
WHERE Invoice.SomethingHasGoneWrong=1
GROUP BY Customer.Id