Il problema di attuare questo come aggregazione (che effettivamente potrebbe fare se, ad esempio, è implementato un "primo non nullo" SQL CLR aggregato) è la sprecata IO leggere ogni riga quando si in genere sono interessato solo alle prime righe. L'aggregazione non si fermerà solo dopo il primo non null, anche se la sua implementazione ignorerebbe ulteriori valori. Anche le aggregazioni non sono ordinate, quindi il risultato dipende dall'ordinamento dell'indice selezionato dal motore di query.
La soluzione di subquery, al contrario, legge le righe minime per ogni query (poiché è necessaria solo la prima riga corrispondente) e supporta qualsiasi ordine. Funzionerà anche su piattaforme di database in cui non è possibile definire aggregati personalizzati.
Il rendimento migliore dipenderà probabilmente dal numero di righe e colonne nella tabella e da quanto scarsi siano i dati. Le righe aggiuntive richiedono la lettura di più righe per l'approccio aggregato. Le colonne aggiuntive richiedono subquery aggiuntive. I dati di Sparser richiedono il controllo di più righe all'interno di ciascuna subquery.
Ecco alcuni risultati di varie dimensioni da tavolo:
Rows Cols Aggregation IO CPU Subquery IO CPU
3 3 2 0 6 0
1728 3 8 63 6 0
1728 8 12 266 16 0
L'IO misurata qui è il numero di letture logiche. Si noti che il numero di letture logiche per l'approccio di subquery non cambia con il numero di righe nella tabella. Inoltre, tieni presente che le letture logiche eseguite da ogni subquery aggiuntiva saranno probabilmente per le stesse pagine di dati (contenenti le prime poche righe). L'aggregazione, d'altra parte, deve elaborare l'intera tabella e richiede del tempo di CPU per farlo.
Questo è il codice che ho usato per testare ...l'indice cluster su SortCol è richiesto poiché (in questo caso) determinerà l'ordine dell'aggregazione.
Definizione del tavolo e l'inserimento dei dati di test:
CREATE TABLE Table1 (Col1 int null, Col2 int null, Col3 int null, SortCol int);
CREATE CLUSTERED INDEX IX_Table1 ON Table1 (SortCol);
WITH R (i) AS
(
SELECT null
UNION ALL
SELECT 0
UNION ALL
SELECT i + 1
FROM R
WHERE i < 10
)
INSERT INTO Table1
SELECT a.i, b.i, c.i, ROW_NUMBER() OVER (ORDER BY NEWID())
FROM R a, R b, R c;
Interrogazione tabella:
SET STATISTICS IO ON;
--aggregation
SELECT TOP(0) * FROM Table1 --shortcut to convert columns back to their types
UNION ALL
SELECT
dbo.FirstNonNull(Col1),
dbo.FirstNonNull(Col2),
dbo.FirstNonNull(Col3),
null
FROM Table1;
--subquery
SELECT
(SELECT TOP(1) Col1 FROM Table1 WHERE Col1 IS NOT NULL ORDER BY SortCol) AS Col1,
(SELECT TOP(1) Col2 FROM Table1 WHERE Col2 IS NOT NULL ORDER BY SortCol) AS Col2,
(SELECT TOP(1) Col3 FROM Table1 WHERE Col3 IS NOT NULL ORDER BY SortCol) AS Col3;
CLR "primo non nullo" aggregato di prova:
[Serializable]
[SqlUserDefinedAggregate(
Format.UserDefined,
IsNullIfEmpty = true,
IsInvariantToNulls = true,
IsInvariantToDuplicates = true,
IsInvariantToOrder = false,
#if(SQL90)
MaxByteSize = 8000
#else
MaxByteSize = -1
#endif
)]
public sealed class FirstNonNull : IBinarySerialize
{
private SqlBinary Value;
public void Init()
{
Value = SqlBinary.Null;
}
public void Accumulate(SqlBinary next)
{
if (Value.IsNull && !next.IsNull)
{
Value = next;
}
}
public void Merge(FirstNonNull other)
{
Accumulate(other.Value);
}
public SqlBinary Terminate()
{
return Value;
}
#region IBinarySerialize Members
public void Read(BinaryReader r)
{
int Length = r.ReadInt32();
if (Length < 0)
{
Value = SqlBinary.Null;
}
else
{
byte[] Buffer = new byte[Length];
r.Read(Buffer, 0, Length);
Value = new SqlBinary(Buffer);
}
}
public void Write(BinaryWriter w)
{
if (Value.IsNull)
{
w.Write(-1);
}
else
{
w.Write(Value.Length);
w.Write(Value.Value);
}
}
#endregion
}
Hai bisogno della prima colonna non nulla o della prima riga non nulla? – feihtthief
Hai sempre solo bisogno della prima riga o potresti aver bisogno dell'intero set. SortCol è unico? – feihtthief
@feihtthief: il primo valore non nullo in ogni colonna. Penso che l'output di esempio dovrebbe mostrare bene l'effetto desiderato. @Mark Byers: Dal momento che non ho una soluzione che funzionerà in un singolo passaggio, posso solo intuire le sue prestazioni, ma l'approccio della sub-query lascia molto a desiderare. Nel mio tavolo attuale, ho circa 20 file che devo arrotolare in questo modo. Con l'approccio sub-query, gli indici non sono particolarmente utili. Credo che un approccio di scansione singola abbia il potenziale di essere molto più veloce con molte colonne. – EvilRyry