Ich habe 2 Tabellen - eine Kontentabelle und eine Benutzertabelle. Jedes Konto kann mehrere Benutzer haben. Ich habe ein Szenario, in dem ich eine einzelne Abfrage/einen einzigen Join für diese beiden Tabellen ausführen möchte, aber ich möchte alle Kontodaten (Account. *) Und nur die first - Benutzerdaten (insbesondere deren Namen).
Anstelle eines "min" oder "max" für meine aggregierte Gruppe wollte ich ein "erstes" machen. Anscheinend gibt es in TSQL jedoch keine Aggregatfunktion "First".
Irgendwelche Vorschläge, wie Sie diese Abfrage erhalten? Natürlich ist es einfach, das kartesische Produkt von Konto x Benutzer zu erhalten:
SELECT User.Name, Account.* FROM Account, User
WHERE Account.ID = User.Account_ID
Aber wie kann ich erreichen, dass ich nur den ersten Benutzer des Produkts in der Reihenfolge seiner User.ID erhält?
Statt zu gruppieren, gehen Sie so vor ...
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
Ich weiß, dass meine Antwort etwas spät ist, aber das könnte anderen helfen. Es gibt eine Möglichkeit, First () und Last () in SQL Server zu erreichen, und hier ist es:
Stuff(Min(Convert(Varchar, DATE_FIELD, 126) + Convert(Varchar, DESIRED_FIELD)), 1, 23, '')
Verwenden Sie Min () für First () und Max () für Last (). DATE_FIELD sollte das Datum sein, das bestimmt, ob es sich um den ersten oder letzten Datensatz handelt. DESIRED_FIELD ist das Feld, in dem der erste oder der letzte Wert eingegeben werden soll. Was es tut, ist:
Bitte schön!
BEARBEITEN: Ich habe Probleme mit der ersten Formel: Wenn DATE_FIELD .000 als Millisekunden hat, gibt SQL Server das Datum als Zeichenfolge ohne NO Millisekunden zurück und entfernt so die ersten 4 Zeichen aus DESIRED_FIELD. Ich habe einfach das Format auf "20" (ohne Millisekunden) geändert und es funktioniert alles super. Der einzige Nachteil ist, wenn Sie zwei Felder haben, die in den gleichen Sekunden erstellt wurden. Die Sortierung kann möglicherweise unordentlich sein ... in welchem Fall Sie für das Format auf "126" zurückkehren können.
Stuff(Max(Convert(Varchar, DATE_FIELD, 20) + Convert(Varchar, DESIRED_FIELD)), 1, 19, '')
EDIT 2: Meine ursprüngliche Absicht war es, die letzte (oder erste) NON NULL-Zeile zurückzugeben. Ich wurde gefragt, wie ich die letzte oder erste Reihe zurückgeben soll, ob sie null ist oder nicht. Fügen Sie dem DESIRED_FIELD einfach eine ISNULL hinzu. Wenn Sie zwei Zeichenfolgen mit einem + -Operator verketten und einer von ihnen NULL ist, ist das Ergebnis NULL. Verwenden Sie also Folgendes:
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
Dies kann durch Verwendung der Partition By-Klausel vereinfacht werden. Wenn in einem Konto drei Benutzer vorhanden sind, werden in der Unterabfrage die Nummern 1, 2 und 3 angegeben. Bei einem anderen AccountKey wird die Nummerierung zurückgesetzt. Dies bedeutet für jeden eindeutigen AccountKey, dass es immer eine 1 und möglicherweise 2,3,4 usw. gibt.
So filtern Sie nach Ranking = 1, um aus jeder Gruppe den ersten zu holen.
Dadurch erhalten Sie eine Zeile pro Konto. Wenn für dieses Konto mindestens ein Benutzer vorhanden ist, erhalten Sie den Benutzer mit dem niedrigsten Schlüssel (da ich einen Links-Join verwende, erhalten Sie immer eine Kontoauflistung, auch wenn nein.) Benutzer existiert). Ersetzen Sie Order By u.UserKey
durch ein anderes Feld, wenn Sie es vorziehen, dass der erste Benutzer alphabetisch oder nach anderen Kriterien ausgewählt wird.
Die STUFF-Antwort von Dominic Goulet ist glatt. Wenn Ihr DATE_FIELD jedoch SMALLDATETIME ist (anstelle von DATETIME), dann ist die Länge von ISO 8601 19 statt 23 (da SMALLDATETIME keine Millisekunden enthält). die ersten vier Zeichen fehlen).
Sie können OUTER APPLY verwenden, siehe 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 und Last sind in SQL Server 2005 oder 2008 nicht vorhanden, aber in SQL Server 2012 gibt es eine First_Value-, Last_Value-Funktion. Ich habe versucht, das Aggregat First und Last für SQL Server 2005 zu implementieren, und kam zu dem Hindernis, dass SQL Server die Berechnung des Aggregats in einer definierten Reihenfolge garantiert. (Siehe Attribut SqlUserDefinedAggregateAttribute.IsInvariantToOrder-Eigenschaft, das nicht implementiert ist.) Dies kann daran liegen, dass der Abfrageanalyse versucht, die Berechnung des Aggregats in mehreren Threads auszuführen und die Ergebnisse zu kombinieren, was jedoch die Reihenfolge nicht beschleunigt welche Elemente werden aggregiert.
SELECT (SELECT TOP 1 Name
FROM User
WHERE Account_ID = a.AccountID
ORDER BY UserID) [Name],
a.*
FROM Account a
Ich habe alle Methoden im Benchmarking getestet. Die einfachste und schnellste Methode, um dies zu erreichen, ist die Verwendung von äußeren/kreuzweisen Anwenden
SELECT u.Name, Account.* FROM Account
OUTER APPLY (SELECT TOP 1 * FROM User WHERE Account.ID = Account_ID ) as u
CROSS APPLY funktioniert genauso wie INNER JOIN und ruft die Zeilen ab, in denen beide Tabellen in Beziehung stehen, während OUTER APPLY wie LEFT OUTER JOIN funktioniert und alle Zeilen aus der linken Tabelle abruft (Konto hier).
Es gibt verschiedene Möglichkeiten, dies zu tun, hier eine schnelle und schmutzige.
Select (SELECT TOP 1 U.Name FROM Users U WHERE U.Account_ID = A.ID) AS "Name,
A.*
FROM Account A
Definieren Sie "zuerst". Was Sie als erstes meinen, ist ein Zufall, der normalerweise mit der Cluster-Index-Reihenfolge zu tun hat, auf den Sie sich aber nicht verlassen sollten (Sie können Beispiele dafür finden, die sie brechen).
Es ist richtig, dass Sie MAX () oder MIN () nicht verwenden. Berücksichtigen Sie während der Versuchung das Szenario, in dem sich Vorname und Nachname in separaten Feldern befinden. Möglicherweise erhalten Sie Namen aus verschiedenen Datensätzen.
Da es sich so anhört, als ob Sie wirklich genau darauf achten, dass Sie für jede Gruppe genau einen beliebigen Datensatz erhalten, können Sie nur ein ID-Feld für diesen Datensatz MIN oder MAX einstellen und dann die Tabelle mit der Abfrage dieser ID verknüpfen.
Erstellen Sie eine Subselect 'FirstUser', die den ersten Benutzer für jedes Konto zurückgibt
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
(Etwas außerhalb des Themas, aber) Ich führe häufig aggregierte Abfragen aus, um Ausnahmezusammenfassungen aufzulisten, und dann möchte ich wissen, WARUM ein Kunde in den Ergebnissen ist. Verwenden Sie also MIN und MAX, um zwei semi-random-Samples zu geben, die ich mir ansehen kann Details z.
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