Basta un po' di logica:
1) fra i campi della SELECT estrai anche uno con la funzione MAX("campo numero tessera"), che supponiamo risulti = X (...oppure prima fai un'altra query)
2) crea un array con X+1 elementi, di contenuto vuoto
3) cicla sul DataReader e -visto che si tratta di numeri interi- usa ogni valore come indice per l'array di appoggio, riempendo il valore dell'elemento dell'array di appoggio con una stringa (es. hai trovato il numero di tessera 2, e l'elemento con indice 2 dell'array di appoggio conterra' una "Tessera 2 trovata")
4) cicla nell'array di appoggio e saprai quali numeri tessera sono in tabella e quali no: se l'elemento è vuoto (null) vuol dire che è un numero tessera mancante.
Non so se mi sono spiegato.
================================
Esempio che funziona:
(con un array chiamato Array_Select al posto del DataReader)
codice:
protected void Page_Load(object sender, EventArgs e)
{
// simulazione del risultato della query principale
int[] Array_Select = new int[6] { 0, 1, 3, 9, 10, 11 };
// array dimensionato con il numero massimo trovato nel risultato
string[] Array_Appoggio = new string[Array_Select.Max() + 1];
// Costruzione dell'array che conterrà gli elementi trovati e quelli mancanti
foreach (int TesseraTrovata in Array_Select) Array_Appoggio[TesseraTrovata] = "Tessera " + TesseraTrovata + " esistente";
int n = 0;
foreach (string valore in Array_Appoggio)
{
if (valore != null) Response.Write(valore + "<br />");
else Response.Write("Tessera " + n.ToString() + " non esistente.<br />");
n++;
}
}