Domanda

Ho 2 tavoli - una tabella di account e una tabella Utenti. Ogni account può avere più utenti. Ho uno scenario in cui voglio eseguire una singola query / unirsi contro queste due tabelle, ma voglio tutti i dati conto (. *) E solo il prima insieme dei dati degli utenti (in particolare il loro nome ).

Invece di fare un "min" o "max" sul mio gruppo aggregato, ho voluto fare un "prima". Ma, a quanto pare, non c'è un "prima" funzione di aggregazione in TSQL.

Qualche suggerimento su come fare per ottenere questa ricerca? Ovviamente, è facile ottenere il prodotto cartesiano di conto utenti x:

 SELECT User.Name, Account.* FROM Account, User
 WHERE Account.ID = User.Account_ID

Ma come potrei ottenuto circa ottenendo soltanto il primo utente dal prodotto in base all'ordine del loro User.ID?

È stato utile?

Soluzione

Invece di raggruppamento, andare su di esso come questo ...

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

Altri suggerimenti

So che la mia risposta è un po 'in ritardo, ma che potrebbe aiutare gli altri. C'è un modo per ottenere un First () e Last () in SQL Server, e qui è:

Stuff(Min(Convert(Varchar, DATE_FIELD, 126) + Convert(Varchar, DESIRED_FIELD)), 1, 23, '')

Utilizzare Min () per First () e Max () per Last (). Il DATE_FIELD dovrebbe essere la data che determina se è il primo o l'ultimo record. Il DESIRED_FIELD è il campo che si desidera che il primo o l'ultimo valore. Ciò che fa è:

  1. Aggiungi la data in formato ISO, all'inizio della stringa (lunga 23 caratteri)
  2. Aggiungere il DESIRED_FIELD a quella stringa
  3. Prendi il valore MIN / MAX per quel campo (dal momento che inizia con la data, si otterrà il primo o l'ultimo record)
  4. Roba che concatened stringa per rimuovere i primi 23 caratteri (la parte data)

Ecco a te!

EDIT: ho avuto problemi con la prima formula: quando il DATE_FIELD ha .000 come millisecondi, SQL Server restituisce la data come stringa con NO millisecondi a tutti, eliminando così i primi 4 caratteri del DESIRED_FIELD. Ho semplicemente cambiato il formato a "20" (senza millisecondi) e funziona tutto fantastico. L'unico lato negativo è che se si dispone di due campi che sono stati creati negli stessi secondi, il genere può eventualmente essere disordinato ... in cui CAS si può tornare a "126" per il formato.

Stuff(Max(Convert(Varchar, DATE_FIELD, 20) + Convert(Varchar, DESIRED_FIELD)), 1, 19, '')

EDIT 2: Il mio intento originale era quello di restituire l'ultima (o la prima) fila NON NULL. Mi sono chiesto come restituire l'ultima o la prima fila, montone castrato che sia nullo o non è. Basta aggiungere un ISNULL al DESIRED_FIELD. Quando è concatenare due stringhe con un operatore di +, quando uno di loro è NULL, il risultato è nullo. Quindi utilizzare il seguente:

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

Questo può essere semplificata utilizzando la partizione clausola. In precedenza, se un account ha tre utenti, allora i numeri subquery loro 1,2 e 3, e per un AccountKey diversa, si resetta la numnbering. Questo significa che per ogni AccountKey unica, ci sarà sempre un 1, e potenzialmente 2,3,4, ecc.

In questo modo di filtrare il Ranking = 1 per afferrare il primo di ogni gruppo.

Questo vi darà una riga per conto, e se c'è almeno un utente per l'account, allora vi darà l'utente con il tasto più basso (perché io uso una sinistra unirsi, è sempre ottenere un elenco conto anche se esiste nessun utente). Sostituire Order By u.UserKey con un altro campo, se si preferisce che il primo utente essere scelto in ordine alfabetico o altri criteri.

La risposta STUFF da Dominic Goulet è chiazza di petrolio. Ma, se il vostro DATE_FIELD è SMALLDATETIME (invece di DATETIME), allora la lunghezza ISO 8601 saranno 19 invece di 23 (perché SMALLDATETIME ha millisecondi) - in modo da regolare il parametro STUFF conseguenza o il valore restituito dalla funzione STUFF non sarà corretto ( mancano i primi quattro caratteri).

Cognome e non esistono in SQL Server 2005 o 2008, ma in SQL Server 2012 è disponibile una funzione di FIRST_VALUE, LAST_VALUE. Ho cercato di implementare l'aggregato Cognome e per SQL Server 2005 e sono giunto alla ostacolo che SQL Server non garantisce il calcolo del totale in un ordine definito. (Vedere attributo SqlUserDefinedAggregateAttribute.IsInvariantToOrder immobile, che non è implementata.) Questo potrebbe essere dovuto alla Query Analyzer tenta di eseguire il calcolo dell'aggregato sul più thread e combinare i risultati, che accelera l'esecuzione, ma non garantisce un ordine in quali elementi sono aggregati.

È possibile utilizzare ESTERNO APPLICA, vedi documentazione .

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

Ho benchmark tutti i metodi, il metodo simpelest e veloce per raggiungere questo obiettivo è quello di utilizzare esterno / cross applicare

SELECT u.Name, Account.* FROM Account
OUTER APPLY (SELECT TOP 1 * FROM User WHERE Account.ID = Account_ID ) as u

CROSS APPLY funziona proprio come INNER JOIN e recupera le righe in cui entrambe le tabelle sono legate, mentre OUTER APPLY opere come LEFT OUTER JOIN e recupera tutte le righe della tabella di sinistra (account qui)

SELECT (SELECT TOP 1 Name 
        FROM User 
        WHERE Account_ID = a.AccountID 
        ORDER BY UserID) [Name],
       a.*
FROM Account a

Ci sono un certo numero di modi di fare questo, qui a a una rapida e sporca.

Select (SELECT TOP 1 U.Name FROM Users U WHERE U.Account_ID = A.ID) AS "Name,
    A.*
FROM Account A

Definisci "First". Cosa ne pensi di come in primo luogo è un caso che normalmente ha a che fare con ordine indice cluster ma non dovrebbe essere fatta valere (si può escogitare esempi che romperlo).

Hai ragione di non usare MAX () o MIN (). Mentre allettante, prendere in considerazione lo scenario in cui si il nome e il cognome sono in campi separati. Si potrebbe ottenere i nomi di diversi record.

Dal momento che suona come tutti i tuoi interessa davvero è che si ottiene esattamente un record arbitrario per ogni gruppo, che cosa si può fare è solo MIN o MAX un campo ID per il record, e poi unirsi al tavolo nella query su quel ID .

(Leggermente off-topic, ma) spesso eseguire query di aggregazione per elencare sommari di eccezione, e poi voglio sapere perché un cliente è nei risultati, in modo da utilizzare MIN e MAX per dare 2 campioni semi-casuali che posso guardare in dettaglio ad esempio

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

Crea e unirsi a una selezione secondaria 'FirstUser' che restituisce il primo utente per ogni account

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
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top