{"id":55000517,"date":"2026-08-01T00:00:00","date_gmt":"2026-11-12T13:04:30","guid":{"rendered":"http:\/\/access-im-unternehmen.aix-dev.de\/aiu\/?p=517"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-30T00:00:00","slug":"TSQL_Teil_2_SELECT_in_der_Praxis","status":"publish","type":"post","link":"https:\/\/vbentwickler.de\/TSQL_Teil_2_SELECT_in_der_Praxis\/","title":{"rendered":"T-SQL, Teil 2: SELECT in der Praxis"},"content":{"rendered":"<p><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/vg07.met.vgwort.de\/na\/e073c1580d1f42d2a3902b18b72eb4f3\" width=\"1\" height=\"1\" alt=\"\"><b>Die Montagsauswertung f&uuml;r den Vertrieb entsteht in Access im Abfrageentwurf: Positionen, Kunden, Ums&auml;tze und einige Gruppierungen. Zieht das Backend auf den SQL Server um, laufen Teile davon nicht mehr oder liefern in unserer Konfiguration andere Zahlen. Ein <b>TOP 1<\/b> liefert dann eine Zeile statt zweier, ein Durchschnitt verliert seine Nachkommastellen, und eine Summe kommt als Text in Access an. Wer <b>SELECT<\/b> in T-SQL schreibt, kennt viel davon aus Access. Umdenken m&uuml;ssen wir an einigen Stellen, und einige davon zeigen keine Fehlermeldung, sondern nur andere Ergebnisse. Wie wir die Abfragen schreiben, die im Alltag gebraucht werden, und worauf wir dabei achten, zeigt dieser zweite Teil der Reihe.<\/b><\/p>\n<h2>Die Auswertungen des Vertriebs<\/h2>\n<p>Als Szenario dient wieder die Bestellverwaltung aus <b>T-SQL, Teil 1: Von Jet-SQL zu T-SQL<\/b>. Der Vertrieb m&ouml;chte w&ouml;chentlich sechs Fragen beantwortet haben:<\/p>\n<ul>\n<li>Welche Positionen hatten den h&ouml;chsten Betrag?<\/li>\n<li>Welche Kunden haben noch keine Bestellung aufgegeben?<\/li>\n<li>Wie viel Umsatz bringen die einzelnen Warengruppen?<\/li>\n<li>Welche Kunden haben die meisten Bestellungen?<\/li>\n<li>In welche Klasse fallen die Kunden nach ihrem Umsatz?<\/li>\n<li>Wie l&auml;sst sich die Kundenliste seitenweise anzeigen, damit nicht die gesamte Liste vom Server geholt wird?<\/li>\n<\/ul>\n<p>Die Beispiele laufen gegen die Datenbank <b>Bestellverwaltung_TSQL<\/b> mit den 330 Bestellungen, 32 Kunden und 825 Positionen aus Teil 1. Wer die Daten ver&auml;ndert hat, f&uuml;hrt das Skript <b>Bestellverwaltung_TSQL.sql<\/b> erneut aus. Es legt die Datenbank neu an und l&ouml;scht dabei eine vorhandene gleichen Namens. Die Hilfsfunktionen <b>PassThroughOeffnen<\/b>, <b>PassThroughWert<\/b>, <b>SqlText<\/b> und <b>SqlDatum<\/b> stammen aus dem Modul <b>mdlTSQLEinstiegBeispiele<\/b> aus Teil 1 und liegen auch im Frontend <b>TSQL_SELECTInDerPraxis.accdb<\/b> bereit. Die neuen VBA-Funktionen dieses Beitrags stehen im Modul <b>mdlSelectBeispiele<\/b>.<\/p>\n<h2>Die gr&ouml;&szlig;ten Positionen<\/h2>\n<p>Wir beginnen mit den f&uuml;nf Positionen mit dem h&ouml;chsten Betrag. Der Betrag einer Position ist <b>Menge<\/b> mal <b>Einzelpreis<\/b> mal (1 minus <b>Rabatt<\/b>). Der <b>Rabatt<\/b> steht als Anteil in der Tabelle, 0,05 entspricht f&uuml;nf Prozent. Berechnete Spalten benennen wir mit einem Alias, den wir wie in Access mit dem Schl&uuml;sselwort <b>AS<\/b> angeben. Die Positionen stehen in <b>tblBestelldetails<\/b>, den Namen der Produkte holen wir aus <b>tblProdukte<\/b> (siehe Listing 1).<\/p>\n<pre>SELECT TOP (5)\r\n    d.BestellungID,\r\n    p.Produkt,\r\n    d.Menge,\r\n    d.Einzelpreis,\r\n    d.Rabatt,\r\n    CAST(d.Menge * d.Einzelpreis * (1 - d.Rabatt)\r\n         AS decimal(10,2)) AS Zeilenbetrag\r\nFROM dbo.tblBestelldetails AS d\r\n     INNER JOIN dbo.tblProdukte AS p ON p.ProduktID = d.ProduktID\r\nORDER BY Zeilenbetrag DESC, d.BestelldetailID;<\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 1: Die f&uuml;nf gr&ouml;&szlig;ten Bestellpositionen<\/span><\/b><\/p>\n<p>Das Ergebnis sind die f&uuml;nf Positionen mit den Betr&auml;gen 399,00 Euro bis 3,00 Euro (siehe Bild 1). <b>TOP (5)<\/b> begrenzt die Ausgabe auf f&uuml;nf Zeilen. Die <b>ORDER BY<\/b>-Klausel sortiert nach dem Alias <b>Zeilenbetrag<\/b>. Als zweites Sortierkriterium dient <b>d.BestelldetailID<\/b>, damit die Reihenfolge bei gleichen Betr&auml;gen festliegt.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2026_04\/pic_517_001.png\" alt=\"Die f&uuml;nf gr&ouml;&szlig;ten Positionen im Ergebnisraster von SSMS\" width=\"550\" height=\"142,0833\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 1: Die f&uuml;nf gr&ouml;&szlig;ten Positionen im Ergebnisraster von SSMS<\/span><\/b><\/p>\n<p>Der <b>CAST<\/b> auf <b>decimal(10,2)<\/b> rundet den Betrag auf zwei Nachkommastellen. Bei Aggregatfunktionen hat er eine weitere Aufgabe, die im Abschnitt <b>Dezimalzahlen in Access<\/b> folgt.<\/p>\n<h2>Aliase und die Reihenfolge der Auswertung<\/h2>\n<p>In <b>ORDER BY<\/b> durften wir den Alias <b>Zeilenbetrag<\/b> verwenden. In <b>WHERE<\/b> funktioniert das nicht. Wir versuchen es mit den Positionen, deren Betrag &uuml;ber 300 Euro liegt:<\/p>\n<pre>SELECT Menge * Einzelpreis * (1 - Rabatt) AS Betrag\r\nFROM dbo.tblBestelldetails\r\nWHERE Betrag &gt; 300;<\/pre>\n<p>Der Server meldet <b>Ung&uuml;ltiger Spaltenname &#8222;Betrag&#8220;<\/b>. Woran liegt das? Er wertet eine Abfrage logisch in einer anderen Reihenfolge aus, als wir die Klauseln hinschreiben. Der tats&auml;chliche Ausf&uuml;hrungsplan kann davon noch einmal abweichen. Die logische Reihenfolge lautet:<\/p>\n<ul>\n<li><b>FROM\/JOIN:<\/b> welche Tabellen verkn&uuml;pft werden<\/li>\n<li><b>WHERE:<\/b> welche Zeilen &uuml;brig bleiben<\/li>\n<li><b>GROUP BY:<\/b> wie die Zeilen zusammengefasst werden<\/li>\n<li><b>HAVING:<\/b> welche Gruppen &uuml;brig bleiben<\/li>\n<li><b>SELECT:<\/b> welche Spalten entstehen, und damit erst hier auch die Aliase<\/li>\n<li><b>ORDER BY:<\/b> wie das Ergebnis sortiert wird, hier sind die Aliase schon bekannt<\/li>\n<li><b>TOP\/OFFSET:<\/b> wie viele Zeilen davon ausgegeben werden<\/li>\n<\/ul>\n<p>Ein Alias aus der <b>SELECT<\/b>-Liste ist in <b>WHERE<\/b>, <b>GROUP BY<\/b> und <b>HAVING<\/b> also noch nicht bekannt. In Access ist das nicht anders. Dort fragt die Abfrage allerdings nach einem Parameterwert, statt einen Fehler zu melden. Wir wiederholen deshalb den Ausdruck in <b>WHERE<\/b> oder kapseln die Abfrage als Unterabfrage, davon handelt der n&auml;chste Teil.<\/p>\n<p>Auch <b>DISTINCT<\/b> kennen wir aus Access. Es entfernt doppelte Zeilen aus dem Ergebnis. Die L&auml;nder der Kunden stehen so nur einmal in der Liste:<\/p>\n<pre>SELECT DISTINCT Land\r\nFROM dbo.tblKunden\r\nORDER BY Land;<\/pre>\n<p>Die Abfrage liefert drei Zeilen, obwohl <b>tblKunden<\/b> 32 Datens&auml;tze enth&auml;lt.<\/p>\n<h2>TOP und Gleichst&auml;nde<\/h2>\n<p>Die Frage nach den Kunden mit den meisten Bestellungen f&uuml;hrt zu <b>TOP<\/b>. In Access schreiben wir die Abfrage mit <b>TOP 1<\/b> und einer absteigenden Sortierung nach der Zahl der Bestellungen. In unseren Tests lieferte sie zwei Zeilen, weil zwei Kunden jeweils 19 Bestellungen haben. Access gab bei diesem Gleichstand beide Kunden zur&uuml;ck.<\/p>\n<p>In T-SQL liefert <b>TOP (1)<\/b> ohne weitere Angabe h&ouml;chstens eine Zeile. Welcher der beiden Kunden das ist, steht ohne ein weiteres Sortierkriterium nicht fest. Wer Gleichst&auml;nde wie Access behandeln m&ouml;chte, verwendet <b>WITH TIES<\/b> (siehe Listing 2).<\/p>\n<pre>SELECT TOP (1) WITH TIES\r\n    k.KundeID, k.Nachname, COUNT(*) AS Bestellungen\r\nFROM dbo.tblKunden AS k\r\n     INNER JOIN dbo.tblBestellungen AS b ON b.KundeID = k.KundeID\r\nGROUP BY k.KundeID, k.Nachname\r\nORDER BY COUNT(*) DESC;<\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 2: WITH TIES liefert die Gleichplatzierten<\/span><\/b><\/p>\n<p>Das Ergebnis sind die Kunden Schr&ouml;der und Vogel mit je 19 Bestellungen. <b>WITH TIES<\/b> setzt eine <b>ORDER BY<\/b>-Klausel voraus, und diese Klausel bestimmt, was als Gleichstand z&auml;hlt. H&auml;ngen wir dort <b>k.KundeID<\/b> an, sind die beiden nicht mehr gleich platziert, und <b>WITH TIES<\/b> liefert nur eine Zeile.<\/p>\n<p>Statt einer Anzahl l&auml;sst sich mit <b>TOP (n) PERCENT<\/b> auch ein Prozentsatz der Zeilen angeben.<\/p>\n<h2>Die Kundenliste seitenweise holen<\/h2>\n<p>Die Kundenliste soll ein Formular seitenweise zeigen. <b>TOP<\/b> liefert nur die ersten Zeilen und kennt keine Seiten. Daf&uuml;r gibt es <b>OFFSET<\/b> und <b>FETCH<\/b>: <b>OFFSET<\/b> &uuml;berspringt die angegebene Zahl von Zeilen, <b>FETCH NEXT<\/b> holt die folgenden.<\/p>\n<p>Die zweite Seite mit zehn Kunden je Seite lautet so:<\/p>\n<pre>SELECT KundeID, Nachname, Ort\r\nFROM dbo.tblKunden\r\nORDER BY Nachname, KundeID\r\nOFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY;<\/pre>\n<p>Das Ergebnis sind die Kunden K&ouml;hler bis Neumann. <b>OFFSET<\/b> geh&ouml;rt zu <b>ORDER BY<\/b> und ist ohne diese Klausel nicht erlaubt. Die Sortierung muss au&szlig;erdem eindeutig sein, sonst k&ouml;nnen Datens&auml;tze beim Bl&auml;ttern doppelt oder gar nicht erscheinen. Haben zwei Kunden denselben Nachnamen, entscheidet <b>KundeID<\/b> als zweites Kriterium, auf welcher Seite sie stehen.<\/p>\n<p>Das setzt voraus, dass sich die Daten zwischen den Seitenaufrufen nicht ver&auml;ndern. Neue, gel&ouml;schte oder umsortierte Datens&auml;tze k&ouml;nnen die Seitengrenzen verschieben.<\/p>\n<p>In VBA setzen wir die Anweisung aus der Seitenzahl und der Zahl der Zeilen je Seite zusammen. Das erledigt die Funktion <b>KundenSeite<\/b>. Sie setzt die Seitenzahl auf 1, wenn sie kleiner ist, und l&ouml;st bei einer Seitengr&ouml;&szlig;e unter 1 einen Fehler aus, weil <b>FETCH NEXT<\/b> mindestens eine Zeile verlangt (siehe Listing 3).<\/p>\n<pre><span style=\"color:blue;\">Public Function <\/span>KundenSeite(lngSeite<span style=\"color:blue;\"> As Long<\/span>, lngProSeite<span style=\"color:blue;\"> As Long<\/span>)<span style=\"color:blue;\"> As <\/span>DAO.Recordset\r\n    <span style=\"color:blue;\">Dim <\/span>strSQL<span style=\"color:blue;\"> As String<\/span>\r\n    <span style=\"color:blue;\">If <\/span>lngSeite &lt; 1<span style=\"color:blue;\"> Then<\/span> lngSeite = 1\r\n    <span style=\"color:blue;\">If <\/span>lngProSeite &lt; 1<span style=\"color:blue;\"> Then<\/span>\r\n        Err.Raise 5, \"KundenSeite\", \"Mindestens 1 Zeile je Seite\"\r\n    <span style=\"color:blue;\">End If<\/span>\r\n    strSQL = \"SELECT KundeID, Nachname, Ort FROM dbo.tblKunden \" & _\r\n             \"ORDER BY Nachname, KundeID \" & _\r\n             \"OFFSET \" & (lngSeite - 1) * lngProSeite & \" ROWS \" & _\r\n             \"FETCH NEXT \" & lngProSeite & \" ROWS ONLY\"\r\n    <span style=\"color:blue;\">Set<\/span> KundenSeite = PassThroughOeffnen(strSQL)\r\n<span style=\"color:blue;\">End Function<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 3: KundenSeite holt eine Seite der Kundenliste<\/span><\/b><\/p>\n<p>Der Ausdruck <b>(lngSeite &#8211; 1) * lngProSeite<\/b> liefert den Wert f&uuml;r <b>OFFSET<\/b>. Die Funktion <b>PassThroughOeffnen<\/b> aus Teil 1 schickt den Text als tempor&auml;re Pass-Through-Abfrage an den Server und gibt das Recordset zur&uuml;ck.<\/p>\n<p>Zum Ausprobieren gibt die Prozedur <b>KundenSeiteAusgeben<\/b> eine Seite im Direktbereich aus (siehe Listing 4).<\/p>\n<pre><span style=\"color:blue;\">Public Sub <\/span>KundenSeiteAusgeben(lngSeite<span style=\"color:blue;\"> As Long<\/span>, lngProSeite<span style=\"color:blue;\"> As Long<\/span>)\r\n    <span style=\"color:blue;\">Dim <\/span>rst<span style=\"color:blue;\"> As <\/span>DAO.Recordset\r\n    <span style=\"color:blue;\">Set<\/span> rst = KundenSeite(lngSeite, lngProSeite)\r\n    <span style=\"color:blue;\">Do While<\/span> <span style=\"color:blue;\">Not<\/span> rst.EOF\r\n        <span style=\"color:blue;\">Debug.Print<\/span> rst!KundeID, rst!Nachname, rst!Ort\r\n        rst.Move<span style=\"color:blue;\">Next<\/span>\r\n    <span style=\"color:blue;\">Loop<\/span>\r\n    rst.Close\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 4: KundenSeiteAusgeben zeigt eine Seite der Kundenliste an<\/span><\/b><\/p>\n<p>Der Aufruf <b>KundenSeiteAusgeben 2, 10<\/b> liefert die zehn Kunden der zweiten Seite (siehe Bild 2). Seite 4 enth&auml;lt die letzten beiden Kunden, Seite 5 ist leer.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2026_04\/pic_517_002.png\" alt=\"Die zweite Seite der Kundenliste, ausgegeben von KundenSeiteAusgeben im Direktbereich\" width=\"425,0002\" height=\"244,6971\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 2: Die zweite Seite der Kundenliste, ausgegeben von KundenSeiteAusgeben im Direktbereich<\/span><\/b><\/p>\n<h2>JOIN ohne Klammern<\/h2>\n<p>Bei Verkn&uuml;pfungen unterscheiden sich Access und T-SQL deutlich. Der Abfrageentwurf von Access setzt um die bisherigen Verkn&uuml;pfungen eine Klammer, sobald ein weiterer <b>INNER JOIN<\/b> hinzukommt. Ohne diese Klammern meldet die Datenbank-Engine einen Syntaxfehler.<\/p>\n<p>Die Abfrage, die die Positionen &uuml;ber f&uuml;nf Tabellen z&auml;hlt, sieht in der SQL-Ansicht so aus (siehe Listing 5).<\/p>\n<pre>SELECT Count(*) AS Anzahl\r\nFROM (((dbo_tblBestelldetails AS d\r\n  INNER JOIN dbo_tblBestellungen AS b ON d.BestellungID = b.BestellungID)\r\n  INNER JOIN dbo_tblKunden AS k ON b.KundeID = k.KundeID)\r\n  INNER JOIN dbo_tblProdukte AS p ON d.ProduktID = p.ProduktID)\r\n  INNER JOIN dbo_tblKategorien AS c ON p.KategorieID = c.KategorieID;<\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 5: F&uuml;nf Tabellen in Access-SQL mit Klammern<\/span><\/b><\/p>\n<p>Lassen wir die Klammern weg, meldet Access den Fehler 3075, dessen Text mit <b>Syntaxfehler (fehlender Operator) in Abfrageausdruck<\/b> beginnt.<\/p>\n<p>T-SQL braucht keine Klammern. Die Verkn&uuml;pfungen stehen hintereinander, und sie beziehen sich auf die Tabellen davor (siehe Listing 6).<\/p>\n<pre>SELECT COUNT(*) AS Anzahl\r\nFROM dbo.tblBestelldetails AS d\r\n     INNER JOIN dbo.tblBestellungen AS b ON b.BestellungID = d.BestellungID\r\n     INNER JOIN dbo.tblKunden AS k ON k.KundeID = b.KundeID\r\n     INNER JOIN dbo.tblProdukte AS p ON p.ProduktID = d.ProduktID\r\n     INNER JOIN dbo.tblKategorien AS c ON c.KategorieID = p.KategorieID;<\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 6: Dieselbe Verkn&uuml;pfung in T-SQL, ohne Klammern<\/span><\/b><\/p>\n<p>Der Wert <b>Anzahl<\/b> ist wie in der Access-Fassung 825. Das Schl&uuml;sselwort <b>INNER<\/b> d&uuml;rfen wir weglassen, <b>JOIN<\/b> allein bedeutet dasselbe. In diesem Beitrag steht es ausgeschrieben, damit der Unterschied zu den &auml;u&szlig;eren Verkn&uuml;pfungen erkennbar bleibt.<\/p>\n<h2>Kunden ohne Bestellung<\/h2>\n<p>F&uuml;r die Frage nach den Kunden ohne Bestellung brauchen wir den <b>LEFT JOIN<\/b>. Er beh&auml;lt die Zeilen der linken Tabelle, auch wenn in der rechten nichts dazu passt. Ausgeschrieben hei&szlig;t er <b>LEFT OUTER JOIN<\/b>. Die Spalten der rechten Tabelle sind f&uuml;r solche Zeilen <b>NULL<\/b>, und danach fragen wir in <b>WHERE<\/b> (siehe Listing 7).<\/p>\n<pre>SELECT k.KundeID, k.Nachname, k.Ort\r\nFROM dbo.tblKunden AS k\r\n     LEFT JOIN dbo.tblBestellungen AS b ON b.KundeID = k.KundeID\r\nWHERE b.BestellungID IS NULL\r\nORDER BY k.KundeID;<\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 7: Kunden ohne Bestellung per LEFT JOIN<\/span><\/b><\/p>\n<p>Die Abfrage liefert drei Kunden: Peters, Kleinschmidt und Zimmermann. Im Frontend liegt sie als Pass-Through-Abfrage <b>ptKundenOhneBestellung<\/b> vor (siehe Bild 3).<\/p>\n<p class=\"image\"><img loading=\"lazy\" decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2026_04\/pic_517_003.png\" alt=\"Die Pass-Through-Abfrage ptKundenOhneBestellung in der Datenblattansicht\" width=\"425\" height=\"119\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 3: Die Pass-Through-Abfrage ptKundenOhneBestellung in der Datenblattansicht<\/span><\/b><\/p>\n<p>F&uuml;r den Einsatz in VBA gen&uuml;gt oft die Zahl dieser Kunden. Die Funktion <b>KundenOhneBestellungZaehlen<\/b> schickt dieselbe Bedingung mit <b>COUNT(*)<\/b> an den Server und holt den Wert mit <b>PassThroughWert<\/b> (siehe Listing 8).<\/p>\n<pre><span style=\"color:blue;\">Public Function <\/span>KundenOhneBestellungZaehlen()<span style=\"color:blue;\"> As Long<\/span>\r\n    <span style=\"color:blue;\">Dim <\/span>strSQL<span style=\"color:blue;\"> As String<\/span>\r\n    strSQL = \"SELECT COUNT(*) FROM dbo.tblKunden k \" & _\r\n             \"LEFT JOIN dbo.tblBestellungen b ON b.KundeID = k.KundeID \" & _\r\n             \"WHERE b.BestellungID IS NULL\"\r\n    KundenOhneBestellungZaehlen = Nz(PassThroughWert(strSQL), 0)\r\n<span style=\"color:blue;\">End Function<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 8: KundenOhneBestellungZaehlen z&auml;hlt die Kunden ohne Bestellung<\/span><\/b><\/p>\n<p>Im Direktbereich liefert <b>?KundenOhneBestellungZaehlen()<\/b> die 3.<\/p>\n<h2>Bedingungen im ON oder im WHERE<\/h2>\n<p>Bei einem <b>LEFT JOIN<\/b> entscheidet der Ort einer Bedingung &uuml;ber das Ergebnis. Das zeigt ein Beispiel: Wir wollen f&uuml;r die Kunden die Zahl der Bestellungen seit Jahresbeginn 2026, auch f&uuml;r Kunden ohne eine solche Bestellung. Im ersten Versuch steht die Bedingung im <b>ON<\/b> (siehe Listing 9).<\/p>\n<pre>SELECT k.KundeID, COUNT(b.BestellungID) AS Bestellungen\r\nFROM dbo.tblKunden AS k\r\n     LEFT JOIN dbo.tblBestellungen AS b\r\n          ON b.KundeID = k.KundeID AND b.Bestelldatum &gt;= '20260101'\r\nGROUP BY k.KundeID;<\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 9: Die Bedingung steht im ON<\/span><\/b><\/p>\n<p>Das Ergebnis hat 32 Zeilen, also einen Eintrag je Kunde. Wer 2026 nicht bestellt hat, steht mit einer 0 in der Liste. Im zweiten Versuch verschieben wir die Bedingung in die <b>WHERE<\/b>-Klausel (siehe Listing 10).<\/p>\n<pre>SELECT k.KundeID, COUNT(b.BestellungID) AS Bestellungen\r\nFROM dbo.tblKunden AS k\r\n     LEFT JOIN dbo.tblBestellungen AS b ON b.KundeID = k.KundeID\r\nWHERE b.Bestelldatum &gt;= '20260101'\r\nGROUP BY k.KundeID;<\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 10: Dieselbe Bedingung im WHERE<\/span><\/b><\/p>\n<p>Jetzt sind es 27 Zeilen. Woran liegt das? <b>WHERE<\/b> wirkt auf das Ergebnis der Verkn&uuml;pfung. F&uuml;r die drei Kunden ohne Bestellung ist <b>Bestelldatum<\/b> dort <b>NULL<\/b>, und ein Vergleich mit <b>NULL<\/b> ist weder wahr noch falsch. Die Zeilen fallen heraus. Dasselbe gilt f&uuml;r die zwei Kunden, deren Bestellungen vor 2026 liegen. Allgemein entfernt ein <b>WHERE<\/b>-Kriterium auf der rechten Tabelle, das <b>NULL<\/b> ausschlie&szlig;t, auch die Kunden ohne passende Bestellung. Die Abfrage verh&auml;lt sich dann hinsichtlich dieser Zeilen wie ein <b>INNER JOIN<\/b>. Steht die Bedingung im <b>ON<\/b>, bleiben die Zeilen der linken Tabelle erhalten.<\/p>\n<h2>Selbstverkn&uuml;pfung<\/h2>\n<p>Eine Tabelle l&auml;sst sich auch mit sich selbst verkn&uuml;pfen. In <b>tblMitarbeiter<\/b> zeigt das Fremdschl&uuml;sselfeld <b>VorgesetzterID<\/b> auf <b>MitarbeiterID<\/b> derselben Tabelle. Um zu einem Mitarbeiter den Namen des Vorgesetzten zu bekommen, verwenden wir die Tabelle zweimal unter verschiedenen Aliasen. Mit <b>LEFT JOIN<\/b> bleibt die Gesch&auml;ftsf&uuml;hrung ohne Vorgesetzten im Ergebnis (siehe Listing 11).<\/p>\n<pre>SELECT m.Vorname + N' ' + m.Nachname AS Mitarbeiter,\r\n       m.Funktion,\r\n       ISNULL(v.Vorname + N' ' + v.Nachname, N'(oberste Ebene)') AS Vorgesetzter\r\nFROM dbo.tblMitarbeiter AS m\r\n     LEFT JOIN dbo.tblMitarbeiter AS v ON v.MitarbeiterID = m.VorgesetzterID\r\nORDER BY m.MitarbeiterID;<\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 11: Selbstverkn&uuml;pfung: Mitarbeiter und ihre Vorgesetzten<\/span><\/b><\/p>\n<p>Das Ergebnis hat acht Zeilen. Bei Claudia Hoffmann steht <b>(oberste Ebene)<\/b>, denn f&uuml;r sie ist <b>VorgesetzterID<\/b> leer und die rechte Seite des <b>LEFT JOIN<\/b> <b>NULL<\/b>.<\/p>\n<p>Weitere Verkn&uuml;pfungsarten brauchen wir seltener. <b>RIGHT JOIN<\/b> ist ein <b>LEFT JOIN<\/b> mit vertauschten Tabellen und l&auml;sst sich umschreiben. <b>FULL OUTER JOIN<\/b> beh&auml;lt die Zeilen beider Seiten und eignet sich f&uuml;r den Abgleich zweier Tabellen, etwa eines Imports gegen den Bestand. <b>CROSS JOIN<\/b> bildet das Kreuzprodukt beider Tabellen und kommt im n&auml;chsten Teil vor.<\/p>\n<h2>Umsatz je Warengruppe<\/h2>\n<p>Nun zum Umsatz der einzelnen Warengruppen. Beim Gruppieren gelten in T-SQL dieselben Grundregeln wie in Access. Eine Spalte der <b>SELECT<\/b>-Liste steht entweder in <b>GROUP BY<\/b> oder in einer Aggregatfunktion wie <b>SUM<\/b>, <b>COUNT<\/b>, <b>AVG<\/b>, <b>MIN<\/b> oder <b>MAX<\/b>. Im Abfrageentwurf von Access nehmen wir die Zuordnung &uuml;ber die Zeile <b>Funktion<\/b> vor. In T-SQL schreiben wir <b>GROUP BY<\/b> und die Aggregatfunktionen hin.<\/p>\n<p>Wir werten das Jahr 2025 aus, ohne stornierte Bestellungen, und behalten nur Gruppen mit mehr als 500 Euro Umsatz (siehe Listing 12).<\/p>\n<pre>SELECT c.Kategorie,\r\n       COUNT(*) AS Positionen,\r\n       CAST(SUM(d.Menge * d.Einzelpreis * (1 - d.Rabatt))\r\n            AS decimal(12,2)) AS Umsatz\r\nFROM dbo.tblBestellungen AS b\r\n     INNER JOIN dbo.tblBestelldetails AS d ON d.BestellungID = b.BestellungID\r\n     INNER JOIN dbo.tblProdukte AS p ON p.ProduktID = d.ProduktID\r\n     INNER JOIN dbo.tblKategorien AS c ON c.KategorieID = p.KategorieID\r\nWHERE b.Bestelldatum &gt;= '20250101' AND b.Bestelldatum &lt; '20260101'\r\n  AND b.BestellstatusID &lt;&gt; 5\r\nGROUP BY c.Kategorie\r\nHAVING SUM(d.Menge * d.Einzelpreis * (1 - d.Rabatt)) &gt; 500\r\nORDER BY Umsatz DESC;<\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 12: Umsatz je Warengruppe 2025 mit GROUP BY und HAVING<\/span><\/b><\/p>\n<p>Das Ergebnis zeigt sieben Warengruppen, die gr&ouml;&szlig;te ist <b>Zubeh&ouml;r<\/b> mit 7.205,65 Euro.<\/p>\n<p>Der Unterschied zwischen <b>WHERE<\/b> und <b>HAVING<\/b> ergibt sich aus der Auswertungsreihenfolge. <b>WHERE<\/b> wirkt auf die einzelnen Zeilen vor dem Gruppieren, <b>HAVING<\/b> auf die fertigen Gruppen. Eine Bedingung auf eine Aggregatfunktion in <b>WHERE<\/b> f&uuml;hrt zu einer Fehlermeldung. Eine Bedingung auf ein Gruppierungsfeld geh&ouml;rt in <b>WHERE<\/b>, weil dann weniger Zeilen in die Gruppierung gehen.<\/p>\n<h2>COUNT: Zeilen und Werte<\/h2>\n<p>Bei <b>COUNT<\/b> kommt es darauf an, was gez&auml;hlt wird. <b>COUNT(*)<\/b> z&auml;hlt Zeilen, <b>COUNT(Spalte)<\/b> z&auml;hlt die Zeilen, in denen die Spalte nicht <b>NULL<\/b> ist. Eine leere Zeichenkette ist nicht <b>NULL<\/b> und wird mitgez&auml;hlt.<\/p>\n<p>Die folgende Abfrage z&auml;hlt in <b>tblBestellungen<\/b> 330 Bestellungen, davon 14 mit einer <b>Bemerkung<\/b> und 312 mit einem <b>Lieferdatum<\/b>:<\/p>\n<pre>SELECT COUNT(*) AS Alle,\r\n       COUNT(Bemerkung) AS MitBemerkung,\r\n       COUNT(Lieferdatum) AS Geliefert\r\nFROM dbo.tblBestellungen;<\/pre>\n<h2>Die Fehlermeldung beim Gruppieren<\/h2>\n<p>Fehlt in <b>GROUP BY<\/b> eine Spalte aus der <b>SELECT<\/b>-Liste, nennt der Server die Spalte beim Namen. Im folgenden Beispiel fehlt <b>MitarbeiterID<\/b>:<\/p>\n<pre>SELECT KundeID, MitarbeiterID, COUNT(*) AS Anzahl\r\nFROM dbo.tblBestellungen\r\nGROUP BY KundeID;<\/pre>\n<p>Die Meldung lautet <b>Die dbo.tblBestellungen.MitarbeiterID-Spalte ist in der Auswahlliste ung&uuml;ltig, da sie nicht in einer Aggregatfunktion und nicht in der GROUP BY-Klausel enthalten ist<\/b> (siehe Bild 4). Welche L&ouml;sung passt, h&auml;ngt von der Frage ab: Mit <b>MitarbeiterID<\/b> in <b>GROUP BY<\/b> entsteht eine Zeile je Kunde und Mitarbeiter, mit <b>MAX(MitarbeiterID)<\/b> bleibt es bei einer Zeile je Kunde.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2026_04\/pic_517_004.png\" alt=\"Die Meldung von SSMS, wenn eine Spalte weder in GROUP BY noch in einer Aggregatfunktion steht\" width=\"700\" height=\"99,40002\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 4: Die Meldung von SSMS, wenn eine Spalte weder in GROUP BY noch in einer Aggregatfunktion steht<\/span><\/b><\/p>\n<h2>Eine Gesamtzeile mit ROLLUP<\/h2>\n<p>Oft soll die Auswertung eine Gesamtzeile enthalten. In Access erzeugen wir sie mit einer Ergebniszeile im Datenblatt oder mit einer zweiten Abfrage.<\/p>\n<p>In T-SQL erg&auml;nzt <b>ROLLUP<\/b> die gruppierte Abfrage um eine Zeile, in der das Gruppierungsfeld <b>NULL<\/b> ist und die Aggregate &uuml;ber die gesamte Abfrage laufen. Die Funktion <b>GROUPING<\/b> unterscheidet diese Zeile von einer echten <b>NULL<\/b>-Gruppe. Mit ihr sortieren wir die Gesamtzeile ans Ende (siehe Listing 13).<\/p>\n<pre>SELECT ISNULL(c.Kategorie, N'Gesamt') AS Kategorie,\r\n       CAST(SUM(d.Menge * d.Einzelpreis * (1 - d.Rabatt))\r\n            AS decimal(12,2)) AS Umsatz\r\nFROM dbo.tblBestellungen AS b\r\n     INNER JOIN dbo.tblBestelldetails AS d ON d.BestellungID = b.BestellungID\r\n     INNER JOIN dbo.tblProdukte AS p ON p.ProduktID = d.ProduktID\r\n     INNER JOIN dbo.tblKategorien AS c ON c.KategorieID = p.KategorieID\r\nWHERE b.Bestelldatum &gt;= '20250101' AND b.Bestelldatum &lt; '20260101'\r\n  AND b.BestellstatusID &lt;&gt; 5\r\nGROUP BY ROLLUP (c.Kategorie)\r\nORDER BY GROUPING(c.Kategorie), Umsatz DESC;<\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 13: ROLLUP erg&auml;nzt den Umsatz je Warengruppe um eine Gesamtzeile<\/span><\/b><\/p>\n<p>Ohne <b>HAVING<\/b> erscheinen die neun Warengruppen. Dazu kommt die Zeile <b>Gesamt<\/b> mit 15.280,51 Euro (siehe Bild 5). Sie summiert die ungerundeten Betr&auml;ge. Die Summe der angezeigten, gerundeten Zeilen ergibt deshalb 15.280,53 Euro und weicht um 0,02 Euro ab.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2026_04\/pic_517_005.png\" alt=\"Umsatz je Warengruppe mit der Gesamtzeile aus ROLLUP\" width=\"299,9999\" height=\"363,3333\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 5: Umsatz je Warengruppe mit der Gesamtzeile aus ROLLUP<\/span><\/b><\/p>\n<h2>Division mit Ganzzahlen<\/h2>\n<p>Zwei Besonderheiten beim Rechnen verursachen keine Fehlermeldung, sondern andere Ergebnisse. Die erste betrifft die Division. In Access liefert <b>\/<\/b> eine Dezimalzahl, und der Backslash <b>\\<\/b> teilt ganzzahlig: <b>7\/2<\/b> ergibt 3,5, <b>7\\2<\/b> ergibt 3. T-SQL kennt nur ein Divisionszeichen. Es teilt ganzzahlig, wenn beide Seiten ganze Zahlen sind:<\/p>\n<pre>SELECT 7 \/ 2 AS Ganzzahl, 7 \/ 2.0 AS Dezimal;<\/pre>\n<p>Das Ergebnis ist 3 und 3,5. Mit einer Dezimalzahl auf einer der beiden Seiten rechnet der Server mit Nachkommastellen.<\/p>\n<h2>AVG bei ganzzahligen Spalten<\/h2>\n<p>Bei <b>AVG<\/b> ist der R&uuml;ckgabetyp bei einer ganzzahligen Spalte ebenfalls ganzzahlig, und der Durchschnitt wird abgeschnitten. In <b>tblBestelldetails<\/b> ist <b>Menge<\/b> eine <b>smallint<\/b>-Spalte:<\/p>\n<pre>SELECT AVG(Menge) AS Mittel,\r\n       AVG(CAST(Menge AS decimal(9,2))) AS MittelDezimal\r\nFROM dbo.tblBestelldetails;<\/pre>\n<p>Die durchschnittliche Menge je Position betr&auml;gt 5,6. Der Server liefert f&uuml;r <b>AVG(Menge)<\/b> die 5, f&uuml;r die zweite Spalte 5,600000. Wer eine Dezimalzahl erwartet, wandelt vor dem Rechnen mit <b>CAST<\/b> um.<\/p>\n<p>Dasselbe gilt f&uuml;r die durchschnittliche Lieferzeit in Tagen, berechnet mit <b>DATEDIFF<\/b> und <b>AVG<\/b>:<\/p>\n<pre>SELECT AVG(DATEDIFF(DAY, Bestelldatum, Lieferdatum))\r\n           AS Ganzzahl,\r\n       AVG(CAST(DATEDIFF(DAY, Bestelldatum, Lieferdatum)\r\n                AS decimal(9,2))) AS Dezimal\r\nFROM dbo.tblBestellungen;<\/pre>\n<p>Das Ergebnis ist 4 und 4,487179, auf zwei Stellen gerundet 4, Tage. <b>AVG<\/b> &uuml;bergeht <b>NULL<\/b>-Werte. Die 18 Bestellungen ohne <b>Lieferdatum<\/b> gehen deshalb nicht in den Durchschnitt ein.<\/p>\n<h2>Dezimalzahlen in Access<\/h2>\n<p>Die zweite Besonderheit zeigt sich erst in Access. Aggregatfunktionen &uuml;ber <b>decimal<\/b>-Spalten liefern in T-SQL bis zu 38 Stellen. In unserer getesteten DAO- und ODBC-Konfiguration kommt ein Wert wie <b>AVG(CAST(Menge AS decimal(9,2)))<\/b> als Text an, hier als <b>5.600000<\/b>. Vermutlich liegt das an der Genauigkeit von 38 Stellen, die DAO mit seinen 28 Stellen nicht abbilden kann. Dasselbe beobachten wir bei <b>SUM<\/b>-Spalten &uuml;ber <b>decimal<\/b>-Felder.<\/p>\n<p>Zum Vergleich fragen wir den Durchschnitt zweimal ab, in der Spalte <b>OhneCast<\/b> direkt und in der Spalte <b>MitCast<\/b> mit einem &auml;u&szlig;eren <b>CAST<\/b> auf <b>decimal(9,2)<\/b>. Wir speichern die Abfrage als Pass-Through-Abfrage:<\/p>\n<pre>SELECT AVG(CAST(Menge AS decimal(9,2))) AS OhneCast,\r\n       CAST(AVG(CAST(Menge AS decimal(9,2)))\r\n            AS decimal(9,2)) AS MitCast\r\nFROM dbo.tblBestelldetails;<\/pre>\n<p>In der Datenblattansicht liefert sie zwei Spalten (siehe Bild 6).<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2026_04\/pic_517_009.png\" alt=\"Der Durchschnitt aus AVG ohne und mit CAST in der Datenblattansicht von Access\" width=\"299,9999\" height=\"79,5918\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 6: Der Durchschnitt aus AVG ohne und mit CAST in der Datenblattansicht von Access<\/span><\/b><\/p>\n<p>Die Spalte <b>OhneCast<\/b> steht linksb&uuml;ndig und enth&auml;lt <b>5.600000<\/b> als Text, die Spalte <b>MitCast<\/b> steht rechtsb&uuml;ndig als Zahl. Der Datentyp steht in <b>DAO.Field.Type<\/b>. Die Linksb&uuml;ndigkeit ist nur ein Hinweis.<\/p>\n<p>Rechnet VBA mit dem Text, kann das Ergebnis falsch sein. In einer deutschen Windows-Installation liest <b>CDbl<\/b> den Punkt in <b>5.600000<\/b> als Tausendertrenner und macht daraus 5.600.000.<\/p>\n<p>Bei <b>SUM<\/b> haben wir die Abhilfe in den Listings dieses Beitrags schon eingebaut: Wir wandeln die Summen mit <b>CAST(&#8230; AS decimal(12,2))<\/b> um. Der Typ bleibt damit unter der Grenze von 28 Stellen und kommt in unserer Konfiguration als <b>Decimal<\/b> an. F&uuml;r <b>AVG<\/b> zeigt das die Spalte <b>MitCast<\/b>.<\/p>\n<h2>Division durch Null<\/h2>\n<p>Die Division durch Null l&ouml;st in T-SQL einen Fehler aus: <b>1\/0<\/b> ergibt die Meldung <b>Fehler aufgrund einer Division durch Null<\/b>. <b>NULLIF(Wert, 0)<\/b> liefert <b>NULL<\/b>, wenn der Wert 0 ist, und eine Division durch <b>NULL<\/b> ergibt wieder <b>NULL<\/b>. Wir berechnen den Lagerbestand in Prozent des Mindestbestands. H&auml;tte ein Produkt den Mindestbestand 0, endete die Abfrage ohne <b>NULLIF<\/b> mit einer Fehlermeldung:<\/p>\n<pre>SELECT Produkt,\r\n       Lagerbestand * 100.0 \/ NULLIF(Mindestbestand, 0)\r\n           AS Prozent\r\nFROM dbo.tblProdukte;<\/pre>\n<p>In den Beispieldaten kommt das nicht vor. Der Faktor <b>100.0<\/b> sorgt daf&uuml;r, dass der Server nicht ganzzahlig teilt.<\/p>\n<h2>CASE statt IIF und Switch<\/h2>\n<p><b>Iif<\/b> kennen wir aus Access-Abfragen, <b>Switch<\/b> f&uuml;r F&auml;lle mit mehreren Bedingungen. T-SQL hat <b>IIF<\/b> f&uuml;r einen einfachen Entweder-oder-Fall und <b>CASE<\/b> f&uuml;r mehrere Bedingungen. <b>CASE<\/b> ist der allgemeinere Ausdruck. Der Server pr&uuml;ft die <b>WHEN<\/b>-Bedingungen der Reihe nach und liefert das <b>THEN<\/b> der ersten wahren Bedingung, sonst das <b>ELSE<\/b>. Fehlt das <b>ELSE<\/b>, ist das Ergebnis <b>NULL<\/b>.<\/p>\n<p>Damit teilen wir die Kunden anhand ihres Umsatzes in Klassen ein: ab 2.000 Euro <b>A<\/b>, ab 1.000 Euro <b>B<\/b>, der Rest <b>C<\/b> (siehe Listing 14).<\/p>\n<pre>SELECT k.KundeID, k.Nachname,\r\n       CAST(SUM(d.Menge * d.Einzelpreis * (1 - d.Rabatt))\r\n            AS decimal(12,2)) AS Umsatz,\r\n       CASE\r\n           WHEN SUM(d.Menge * d.Einzelpreis * (1 - d.Rabatt)) &gt;= 2000 THEN N'A'\r\n           WHEN SUM(d.Menge * d.Einzelpreis * (1 - d.Rabatt)) &gt;= 1000 THEN N'B'\r\n           ELSE N'C'\r\n       END AS Klasse\r\nFROM dbo.tblKunden AS k\r\n     INNER JOIN dbo.tblBestellungen AS b ON b.KundeID = k.KundeID\r\n     INNER JOIN dbo.tblBestelldetails AS d ON d.BestellungID = b.BestellungID\r\nWHERE b.BestellstatusID &lt;&gt; 5\r\nGROUP BY k.KundeID, k.Nachname\r\nORDER BY Umsatz DESC;<\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 14: Umsatz je Kunde mit einer Klasseneinteilung per CASE<\/span><\/b><\/p>\n<p>Von den 29 Kunden mit Bestellungen fallen acht in Klasse <b>A<\/b>, dreizehn in Klasse <b>B<\/b> und acht in Klasse <b>C<\/b>. Die Abfrage steht im Frontend als <b>ptKundenUmsatz<\/b> bereit. In der Datenblattansicht stehen die Ums&auml;tze rechtsb&uuml;ndig als Zahlen und nicht als Text (siehe Bild 7).<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2026_04\/pic_517_006.png\" alt=\"Die Pass-Through-Abfrage ptKundenUmsatz mit Umsatz und Klasse je Kunde\" width=\"424,9999\" height=\"286,2244\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 7: Die Pass-Through-Abfrage ptKundenUmsatz mit Umsatz und Klasse je Kunde<\/span><\/b><\/p>\n<h2>Kreuztabelle mit CASE<\/h2>\n<p><b>CASE<\/b> innerhalb einer Aggregatfunktion ersetzt die Kreuztabellenabfrage von Access, wenn die Spalten feststehen. Wir stellen die Ums&auml;tze der Jahre 2025 und 2026 nebeneinander, mit einer Zeile je Monat (siehe Listing 15).<\/p>\n<pre>SELECT MONTH(b.Bestelldatum) AS Monat,\r\n       CAST(SUM(CASE WHEN YEAR(b.Bestelldatum) = 2025\r\n                THEN d.Menge * d.Einzelpreis * (1 - d.Rabatt) END)\r\n            AS decimal(12,2)) AS Umsatz2025,\r\n       CAST(SUM(CASE WHEN YEAR(b.Bestelldatum) = 2026\r\n                THEN d.Menge * d.Einzelpreis * (1 - d.Rabatt) END)\r\n            AS decimal(12,2)) AS Umsatz2026\r\nFROM dbo.tblBestellungen AS b\r\n     INNER JOIN dbo.tblBestelldetails AS d ON d.BestellungID = b.BestellungID\r\nWHERE b.Bestelldatum &gt;= '20250101' AND b.Bestelldatum &lt; '20270101'\r\n  AND b.BestellstatusID &lt;&gt; 5\r\nGROUP BY MONTH(b.Bestelldatum)\r\nORDER BY Monat;<\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 15: Der Umsatz der Jahre 2025 und 2026 je Monat nebeneinander<\/span><\/b><\/p>\n<p><b>CASE<\/b> liefert nur f&uuml;r die Zeilen des jeweiligen Jahres einen Wert, und <b>SUM<\/b> &uuml;bergeht die <b>NULL<\/b>-Werte der &uuml;brigen Zeilen. F&uuml;r Oktober bis Dezember 2026 gibt es noch keine Bestellungen, dort steht <b>NULL<\/b> (siehe Bild 8).<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2026_04\/pic_517_007.png\" alt=\"Die Jahre 2025 und 2026 je Monat nebeneinander, bei fehlenden Bestellungen steht NULL\" width=\"324,9999\" height=\"341,2499\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 8: Die Jahre 2025 und 2026 je Monat nebeneinander, bei fehlenden Bestellungen steht NULL<\/span><\/b><\/p>\n<p>Wer das Datenblatt mit Nullen sehen m&ouml;chte, ersetzt die Summe durch <b>ISNULL(SUM(&#8230;), 0)<\/b>. Alternativ gibt es <b>COALESCE<\/b>, das mehr als zwei Argumente akzeptiert und im Gegensatz zu <b>ISNULL<\/b> zum SQL-Standard geh&ouml;rt. Der verwandte Operator <b>PIVOT<\/b> dreht die Werte einer Spalte in ausdr&uuml;cklich festgelegte Ergebnisspalten. Er kommt im n&auml;chsten Teil an die Reihe.<\/p>\n<h2>Listen mit STRING_AGG<\/h2>\n<p><b>STRING_AGG<\/b> fasst die Werte einer Spalte je Gruppe zu einer Zeichenkette zusammen. Access braucht daf&uuml;r eine eigene VBA-Funktion. <b>STRING_AGG<\/b> gibt es ab SQL Server 2017. Die Produkte der einzelnen Warengruppen stehen so in einer kommaseparierten Liste (siehe Listing 16). Das Ergebnis hat neun Zeilen, eine je Warengruppe. Die Klausel <b>WITHIN GROUP<\/b> sortiert die Produkte innerhalb der Liste.<\/p>\n<pre>SELECT c.Kategorie,\r\n       STRING_AGG(p.Produkt, N', ')\r\n            WITHIN GROUP (ORDER BY p.Produkt) AS Produkte\r\nFROM dbo.tblKategorien AS c\r\n     INNER JOIN dbo.tblProdukte AS p ON p.KategorieID = c.KategorieID\r\nGROUP BY c.Kategorie\r\nORDER BY c.Kategorie;<\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 16: STRING_AGG liefert die Produkte je Warengruppe als Liste<\/span><\/b><\/p>\n<h2>Adresszeilen mit CONCAT_WS<\/h2>\n<p>Zum Verketten von Spalten gibt es <b>CONCAT_WS<\/b>. Die Funktion setzt Werte mit einem Trennzeichen zusammen und &uuml;berspringt <b>NULL<\/b>-Werte. Bei den Privatkunden ohne Firma entsteht so kein &uuml;berfl&uuml;ssiges Komma. Eine Adresszeile aus <b>Firma<\/b>, Name, <b>Strasse<\/b> und Ort sieht so aus:<\/p>\n<pre>SELECT KundeID,\r\n       CONCAT_WS(N', ', Firma, Vorname + N' ' + Nachname,\r\n                 Strasse, PLZ + N' ' + Ort) AS Adresse\r\nFROM dbo.tblKunden;<\/pre>\n<p>F&uuml;r Hans M&uuml;ller steht dort <b>M&uuml;ller B&uuml;robedarf GmbH, Hans M&uuml;ller, Hauptstra&szlig;e 12, 80331 M&uuml;nchen<\/b>, f&uuml;r Petra Schmidt ohne Firma <b>Petra Schmidt, Lindenweg 5, 50667 K&ouml;ln<\/b>. In den Beispieldaten ist keine der Spalten <b>Vorname<\/b>, <b>Nachname<\/b>, <b>Strasse<\/b>, <b>PLZ<\/b> und <b>Ort<\/b> <b>NULL<\/b>. W&auml;re eine davon <b>NULL<\/b>, fiele das zusammengesetzte St&uuml;ck vollst&auml;ndig weg.<\/p>\n<h2>Textfunktionen<\/h2>\n<p>F&uuml;r die &uuml;brigen Textfunktionen gilt, was in Teil 1 in der &Uuml;bersicht steht: <b>LEFT<\/b>, <b>SUBSTRING<\/b>, <b>CHARINDEX<\/b> und <b>LEN<\/b> entsprechen <b>Left<\/b>, <b>Mid<\/b>, <b>InStr<\/b> und <b>Len<\/b>. <b>LEN<\/b> z&auml;hlt allerdings nachgestellte Leerzeichen nicht mit. Mit <b>TRIM<\/b> (ab SQL Server 2017) entfernen wir f&uuml;hrende und nachgestellte Leerzeichen, <b>UPPER<\/b> und <b>LOWER<\/b> wandeln die Schreibweise um.<\/p>\n<h2>Monatserster und Monatsletzter<\/h2>\n<p><b>EOMONTH<\/b> liefert den letzten Tag des Monats, <b>DATEFROMPARTS<\/b> baut ein Datum aus Jahr, Monat und Tag auf. Der Monatserste und der Monatsletzte einer Bestellung stehen so in zwei Spalten:<\/p>\n<pre>SELECT DATEFROMPARTS(YEAR(Bestelldatum),\r\n                     MONTH(Bestelldatum), 1) AS Erster,\r\n       EOMONTH(Bestelldatum) AS Letzter\r\nFROM dbo.tblBestellungen;<\/pre>\n<p>F&uuml;r eine Bestellung vom 2. Januar 2024 liefert die Abfrage den 1. Januar 2024 und den 31. Januar 2024.<\/p>\n<h2>Der Wochentag<\/h2>\n<p><b>DATEPART(WEEKDAY, &#8230;)<\/b> liefert die Nummer des Wochentags gem&auml;&szlig; der Einstellung <b>DATEFIRST<\/b> der Sitzung. Diese Einstellung folgt der Standardsprache der Anmeldung und l&auml;sst sich in der Sitzung mit <b>SET DATEFIRST<\/b> &auml;ndern.<\/p>\n<p>Bei deutscher Einstellung ist der Montag der erste Tag der Woche, bei englischer der Sonntag. Der Dienstag, 15. September 2026, ist dann einmal die 2 und einmal die 3.<\/p>\n<p>In Access ist der Sonntag der Vorgabewert von <b>Weekday<\/b>. Eine Formel mit <b>@@DATEFIRST<\/b> liefert unabh&auml;ngig von der Einstellung den Montag als 1:<\/p>\n<pre>SELECT DATEPART(WEEKDAY, '20260915') AS Wochentag,\r\n       (DATEPART(WEEKDAY, '20260915') + @@DATEFIRST - 2)\r\n            % 7 + 1 AS MontagIstEins;<\/pre>\n<p>Die Spalte <b>MontagIstEins<\/b> liefert f&uuml;r den Dienstag die 2, die Spalte <b>Wochentag<\/b> den Wert, der zur Einstellung der Sitzung geh&ouml;rt.<\/p>\n<h2>Zahlen in zusammengesetzten Anweisungen<\/h2>\n<p>Die Abfragen laufen im Frontend auf zwei Wegen. Zum einen als gespeicherte Pass-Through-Abfragen wie <b>ptKundenOhneBestellung<\/b> und <b>ptKundenUmsatz<\/b>, die wir wie in Teil 1 anlegen und als Datenquelle f&uuml;r Berichte und Listenfelder verwenden. Zum anderen als VBA-Funktionen, die die Anweisung aus Parametern zusammensetzen, wie <b>KundenSeite<\/b>.<\/p>\n<p>Die Funktion f&uuml;r den Umsatz je Warengruppe braucht einen Zeitraum und einen Mindestumsatz. Datumswerte formatiert <b>SqlDatum<\/b> aus Teil 1. Zahlen mit Nachkommastellen brauchen eine eigene Behandlung: Beim Verketten wandelt VBA eine Zahl in einer deutschen Windows-Installation mit einem Dezimalkomma in Text, T-SQL erwartet einen Dezimalpunkt. <b>SqlZahl<\/b> macht aus der Zahl ein Literal mit Dezimalpunkt (siehe Listing 17).<\/p>\n<pre><span style=\"color:blue;\">Public Function <\/span>SqlZahl(varWert<span style=\"color:blue;\"> As Variant<\/span>)<span style=\"color:blue;\"> As String<\/span>\r\n    <span style=\"color:blue;\">Dim <\/span>strZahl<span style=\"color:blue;\"> As String<\/span>\r\n    <span style=\"color:blue;\">If <\/span>IsNull(varWert)<span style=\"color:blue;\"> Then<\/span>\r\n        SqlZahl = \"NULL\"\r\n    <span style=\"color:blue;\">Else<\/span>\r\n        strZahl = <span style=\"color:blue;\">Trim<\/span>(Str(varWert))\r\n        strZahl = <span style=\"color:blue;\">Replace<\/span>(strZahl, \"-.\", \"-0.\")\r\n        <span style=\"color:blue;\">If <\/span><span style=\"color:blue;\">Left<\/span>(strZahl, 1) = \".\"<span style=\"color:blue;\"> Then<\/span> strZahl = \"0\" & strZahl\r\n        SqlZahl = strZahl\r\n    <span style=\"color:blue;\">End If<\/span>\r\n<span style=\"color:blue;\">End Function<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 17: SqlZahl schreibt eine Zahl mit Dezimalpunkt<\/span><\/b><\/p>\n<p>Die Funktion <b>Str<\/b> schreibt Zahlen mit Dezimalpunkt und stellt bei positiven Zahlen ein Leerzeichen voran, das <b>Trim<\/b> entfernt.<\/p>\n<p>Bei Werten kleiner als 1 l&auml;sst <b>Str<\/b> die Null vor dem Punkt weg, <b>SqlZahl<\/b> erg&auml;nzt sie. Der Aufruf <b>?SqlZahl(1234.5)<\/b> liefert <b>1234.5<\/b>, <b>?SqlZahl(Null)<\/b> liefert <b>NULL<\/b>.<\/p>\n<h2>Umsatz je Warengruppe als Funktion<\/h2>\n<p>Mit <b>SqlZahl<\/b> und <b>SqlDatum<\/b> entsteht die Funktion <b>UmsatzJeKategorie<\/b>. Sie setzt die Abfrage mit <b>GROUP BY<\/b> und <b>HAVING<\/b> aus Zeitraum und Mindestumsatz zusammen (siehe Listing 18).<\/p>\n<pre><span style=\"color:blue;\">Public Function <\/span>UmsatzJeKategorie(datVon<span style=\"color:blue;\"> As Date<\/span>, datBis<span style=\"color:blue;\"> As Date<\/span>, _\r\n                                  curMinimum<span style=\"color:blue;\"> As Currency<\/span>)<span style=\"color:blue;\"> As <\/span>DAO.Recordset\r\n    <span style=\"color:blue;\">Dim <\/span>strSQL<span style=\"color:blue;\"> As String<\/span>\r\n    strSQL = \"SELECT c.Kategorie, COUNT(*) AS Positionen, \" & _\r\n             \"CAST(SUM(d.Menge * d.Einzelpreis * (1 - d.Rabatt)) \" & _\r\n             \"AS decimal(12,2)) AS Umsatz \" & _\r\n             \"FROM dbo.tblBestellungen b \" & _\r\n             \"INNER JOIN dbo.tblBestelldetails d ON d.BestellungID = b.BestellungID \" & _\r\n             \"INNER JOIN dbo.tblProdukte p ON p.ProduktID = d.ProduktID \" & _\r\n             \"INNER JOIN dbo.tblKategorien c ON c.KategorieID = p.KategorieID \" & _\r\n             \"WHERE b.Bestelldatum &gt;= \" & SqlDatum(datVon) & _\r\n             \" AND b.Bestelldatum &lt; \" & SqlDatum(datBis) & _\r\n             \" AND b.BestellstatusID &lt;&gt; 5 \" & _\r\n             \"GROUP BY c.Kategorie \" & _\r\n             \"HAVING SUM(d.Menge * d.Einzelpreis * (1 - d.Rabatt)) &gt; \" & _\r\n             SqlZahl(curMinimum) & _\r\n             \" ORDER BY Umsatz DESC\"\r\n    <span style=\"color:blue;\">Set<\/span> UmsatzJeKategorie = PassThroughOeffnen(strSQL)\r\n<span style=\"color:blue;\">End Function<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 18: UmsatzJeKategorie liefert Warengruppen mit Mindestumsatz<\/span><\/b><\/p>\n<p>Der Zeitraum beginnt am <b>datVon<\/b> und endet vor <b>datBis<\/b>, der Tag <b>datBis<\/b> geh&ouml;rt nicht mehr dazu. Auch <b>curMinimum<\/b> ist eine Untergrenze im Sinne von mehr als. Eine Warengruppe mit einem Umsatz in dieser H&ouml;he f&auml;llt heraus.<\/p>\n<p>Der Aufruf <b>UmsatzJeKategorie(#1\/1\/2025#, #1\/1\/2026#, 500)<\/b> liefert die sieben Warengruppen der Abfrage (siehe Listing 12).<\/p>\n<h2>Die besten Kunden als Funktion<\/h2>\n<p>F&uuml;r die Kunden mit den meisten Bestellungen fehlt noch eine Funktion. Der Parameter <b>blnMitGleichstand<\/b> der Funktion <b>TopKunden<\/b> entscheidet &uuml;ber <b>WITH TIES<\/b> (siehe Listing 19).<\/p>\n<pre><span style=\"color:blue;\">Public Function <\/span>TopKunden(lngAnzahl<span style=\"color:blue;\"> As Long<\/span>, _\r\n                         blnMitGleichstand<span style=\"color:blue;\"> As Boolean<\/span>)<span style=\"color:blue;\"> As <\/span>DAO.Recordset\r\n    <span style=\"color:blue;\">Dim <\/span>strSQL<span style=\"color:blue;\"> As String<\/span>\r\n    strSQL = \"SELECT TOP (\" & lngAnzahl & \")\" & _\r\n             IIf(blnMitGleichstand, \" WITH TIES\", \"\") & _\r\n             \" k.KundeID, k.Nachname, COUNT(*) AS Bestellungen \" & _\r\n             \"FROM dbo.tblKunden k INNER JOIN dbo.tblBestellungen b \" & _\r\n             \"ON b.KundeID = k.KundeID \" & _\r\n             \"GROUP BY k.KundeID, k.Nachname \" & _\r\n             \"ORDER BY COUNT(*) DESC\" & IIf(blnMitGleichstand, \"\", \", k.KundeID\")\r\n    <span style=\"color:blue;\">Set<\/span> TopKunden = PassThroughOeffnen(strSQL)\r\n<span style=\"color:blue;\">End Function<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 19: TopKunden liefert die Kunden mit den meisten Bestellungen<\/span><\/b><\/p>\n<p>Mit <b>WITH TIES<\/b> bestimmen die Kriterien in <b>ORDER BY<\/b>, was als Gleichstand gilt. Ohne <b>WITH TIES<\/b> h&auml;ngt die Funktion <b>k.KundeID<\/b> an. Bei einem Gleichstand ist dann festgelegt, welche Zeile zur&uuml;ckkommt.<\/p>\n<p><b>TopKunden(1, False)<\/b> liefert deshalb den Kunden mit der niedrigsten <b>KundeID<\/b> unter den Gleichplatzierten, den Kunden <b>Schr&ouml;der<\/b>. <b>TopKunden(1, True)<\/b> liefert <b>Schr&ouml;der<\/b> und <b>Vogel<\/b>.<\/p>\n<h2>Zusammenfassung und Ausblick<\/h2>\n<p>Den Fragen des Vertriebs ordnen wir die L&ouml;sungen zu: Die gr&ouml;&szlig;ten Positionen liefert Listing 1, die Kunden ohne Bestellung <b>ptKundenOhneBestellung<\/b>, den Umsatz je Warengruppe <b>UmsatzJeKategorie<\/b>, die Kunden mit den meisten Bestellungen <b>TopKunden<\/b>, die Klassen nach Umsatz <b>ptKundenUmsatz<\/b>, und die Kundenliste holt <b>KundenSeite<\/b> seitenweise.<\/p>\n<p>Beim <b>SELECT<\/b> unterscheidet sich T-SQL an einigen Stellen von Access: Verkn&uuml;pfungen stehen ohne Klammern, ein <b>TOP<\/b> begrenzt das Ergebnis ohne <b>WITH TIES<\/b> auf h&ouml;chstens die angegebene Zeilenzahl, und beim Gruppieren stehen die Spalten der <b>SELECT<\/b>-Liste in <b>GROUP BY<\/b> oder in einer Aggregatfunktion. Zwei Besonderheiten verursachen keine Fehlermeldung: die Ganzzahldivision, die auch den ganzzahligen R&uuml;ckgabetyp von <b>AVG<\/b> bei Integer-Spalten betrifft, und die Aggregate &uuml;ber <b>decimal<\/b>-Spalten, die in unserer Konfiguration bei Access als Text ankommen, solange wir sie nicht mit <b>CAST<\/b> auf einen Typ mit h&ouml;chstens 28 Stellen bringen.<\/p>\n<p><b>STRING_AGG<\/b>, <b>ROLLUP<\/b> und <b>OFFSET\/FETCH<\/b> ersetzen L&ouml;sungen, die in Access eigene VBA-Funktionen oder mehrere Abfragen brauchen.<\/p>\n<p>Im n&auml;chsten Teil, <b>T-SQL, Teil 3: Unterabfragen, CTEs und Fensterfunktionen<\/b>, l&ouml;sen wir mit Unterabfragen, <b>CTE<\/b> und Fensterfunktionen die Aufgaben, f&uuml;r die in Access die Dom&auml;nenfunktionen <b>DLookup<\/b>, <b>DSum<\/b> und <b>DCount<\/b> zust&auml;ndig sind.<\/p>\n<p>Wir bauen dort au&szlig;erdem die Rangfolge der besten Kunden und laufende Summen. Wer vorab wissen m&ouml;chte, wie sich Abfragen aus Access heraus parametrisieren lassen, ohne den Text zusammenzusetzen, findet das in den Beitr&auml;gen <b>ADODB: Gespeicherte Prozeduren ausf&uuml;hren (www.vbentwickler.de\/503)<\/b> und <b>ADODB: Gespeicherte Prozeduren einfach aufrufen (www.vbentwickler.de\/505)<\/b>.<\/p>\n<h2>Downloads zu diesem Beitrag<\/h2>\n<p>Enthaltene Beispieldateien:<\/p>\n<p>Bestellverwaltung_TSQL.sql<\/p>\n<p>TSQL_SELECTInDerPraxis.accdb<\/p>\n<p><a href=\"..\/fileadmin\/beispiele\/42C1D0A7-7465-48BF-B4F6-6B0B2B78BFB8\/vbe_517.zip\">Download<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Die Montagsauswertung f&uuml;r den Vertrieb entsteht in Access im Abfrageentwurf: Positionen, Kunden, Ums&auml;tze und einige Gruppierungen. Zieht das Backend auf den SQL Server um, laufen Teile davon nicht mehr oder liefern in unserer Konfiguration andere Zahlen. Ein TOP 1 liefert dann eine Zeile statt zweier, ein Durchschnitt verliert seine Nachkommastellen, und eine Summe kommt als Text in Access an. Wer SELECT in T-SQL schreibt, kennt viel davon aus Access. Umdenken m&uuml;ssen wir an einigen Stellen, und einige davon zeigen keine Fehlermeldung, sondern nur andere Ergebnisse. Wie wir die Abfragen schreiben, die im Alltag gebraucht werden, und worauf wir dabei achten, zeigt dieser zweite Teil der Reihe.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"om_disable_all_campaigns":false,"_monsterinsights_skip_tracking":false,"footnotes":""},"categories":[662026,66042026,44000006],"tags":[],"yst_prominent_words":[],"class_list":["post-55000517","post","type-post","status-publish","format-standard","hentry","category-662026","category-66042026","category-SQL_Server_und_Co"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.5 - aioseo.com -->\n\t<meta name=\"description\" content=\"Die Montagsauswertung f\u00fcr den Vertrieb entsteht in Access im Abfrageentwurf: Positionen, Kunden, Ums\u00e4tze und einige Gruppierungen. Zieht das Backend auf den SQL Server um, laufen Teile davon nicht mehr oder liefern in unserer Konfiguration andere Zahlen. Ein TOP 1 liefert dann eine Zeile statt zweier, ein Durchschnitt verliert seine Nachkommastellen, und eine Summe kommt als Text in Access an. Wer SELECT in T-SQL schreibt, kennt viel davon aus Access. Umdenken m\u00fcssen wir an einigen Stellen, und einige davon zeigen keine Fehlermeldung, sondern nur andere Ergebnisse. Wie wir die Abfragen schreiben, die im Alltag gebraucht werden, und worauf wir dabei achten, zeigt dieser zweite Teil der Reihe.\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Andr\u00e9 Minhorst\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/vbentwickler.de\/TSQL_Teil_2_SELECT_in_der_Praxis\/\" \/>\n\t\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.5\" \/>\n\t\t<meta property=\"og:locale\" content=\"de_DE\" \/>\n\t\t<meta property=\"og:site_name\" content=\"Visual Basic Entwickler - Das Magazin f\u00fcr Datenbankentwickler auf Basis von Visual Studio und Co.\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"T-SQL, Teil 2: SELECT in der Praxis - Visual Basic Entwickler\" \/>\n\t\t<meta property=\"og:description\" content=\"Die Montagsauswertung f\u00fcr den Vertrieb entsteht in Access im Abfrageentwurf: Positionen, Kunden, Ums\u00e4tze und einige Gruppierungen. Zieht das Backend auf den SQL Server um, laufen Teile davon nicht mehr oder liefern in unserer Konfiguration andere Zahlen. Ein TOP 1 liefert dann eine Zeile statt zweier, ein Durchschnitt verliert seine Nachkommastellen, und eine Summe kommt als Text in Access an. Wer SELECT in T-SQL schreibt, kennt viel davon aus Access. Umdenken m\u00fcssen wir an einigen Stellen, und einige davon zeigen keine Fehlermeldung, sondern nur andere Ergebnisse. Wie wir die Abfragen schreiben, die im Alltag gebraucht werden, und worauf wir dabei achten, zeigt dieser zweite Teil der Reihe.\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/vbentwickler.de\/TSQL_Teil_2_SELECT_in_der_Praxis\/\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/datenbankentwickler.net\/wp-content\/uploads\/2022\/08\/cropped-header_vbe-1.png\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/datenbankentwickler.net\/wp-content\/uploads\/2022\/08\/cropped-header_vbe-1.png\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2026-11-12T13:04:30+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"-001-11-30T00:00:00+00:00\" \/>\n\t\t<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n\t\t<meta name=\"twitter:title\" content=\"T-SQL, Teil 2: SELECT in der Praxis - Visual Basic Entwickler\" \/>\n\t\t<meta name=\"twitter:description\" content=\"Die Montagsauswertung f\u00fcr den Vertrieb entsteht in Access im Abfrageentwurf: Positionen, Kunden, Ums\u00e4tze und einige Gruppierungen. Zieht das Backend auf den SQL Server um, laufen Teile davon nicht mehr oder liefern in unserer Konfiguration andere Zahlen. Ein TOP 1 liefert dann eine Zeile statt zweier, ein Durchschnitt verliert seine Nachkommastellen, und eine Summe kommt als Text in Access an. Wer SELECT in T-SQL schreibt, kennt viel davon aus Access. Umdenken m\u00fcssen wir an einigen Stellen, und einige davon zeigen keine Fehlermeldung, sondern nur andere Ergebnisse. Wie wir die Abfragen schreiben, die im Alltag gebraucht werden, und worauf wir dabei achten, zeigt dieser zweite Teil der Reihe.\" \/>\n\t\t<meta name=\"twitter:image\" content=\"https:\/\/datenbankentwickler.net\/wp-content\/uploads\/2022\/08\/cropped-header_vbe-1.png\" \/>\n\t\t<script type=\"application\/ld+json\" class=\"aioseo-schema\">\n\t\t\t{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"BlogPosting\",\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/TSQL_Teil_2_SELECT_in_der_Praxis\\\/#blogposting\",\"name\":\"T-SQL, Teil 2: SELECT in der Praxis - Visual Basic Entwickler\",\"headline\":\"T-SQL, Teil 2: SELECT in der Praxis\",\"author\":{\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/author\\\/andreminhorst-com\\\/#author\"},\"publisher\":{\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/#organization\"},\"image\":{\"@type\":\"ImageObject\",\"url\":\"http:\\\/\\\/vg07.met.vgwort.de\\\/na\\\/e073c1580d1f42d2a3902b18b72eb4f3\",\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/TSQL_Teil_2_SELECT_in_der_Praxis\\\/#articleImage\"},\"datePublished\":\"2026-08-01T00:00:00+00:00\",\"dateModified\":\"-0001-11-30T00:00:00+00:00\",\"inLanguage\":\"de-DE\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/TSQL_Teil_2_SELECT_in_der_Praxis\\\/#webpage\"},\"isPartOf\":{\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/TSQL_Teil_2_SELECT_in_der_Praxis\\\/#webpage\"},\"articleSection\":\"2026, 4\\\/2026, SQL Server und Co.\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/TSQL_Teil_2_SELECT_in_der_Praxis\\\/#breadcrumblist\",\"itemListElement\":[{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/#listItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/vbentwickler.de\\\/\",\"nextItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/category\\\/2026\\\/#listItem\",\"name\":\"2026\"}},{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/category\\\/2026\\\/#listItem\",\"position\":2,\"name\":\"2026\",\"item\":\"https:\\\/\\\/vbentwickler.de\\\/category\\\/2026\\\/\",\"nextItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/category\\\/2026\\\/4\\\/2026\\\/#listItem\",\"name\":\"4\\\/2026\"},\"previousItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/#listItem\",\"name\":\"Home\"}},{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/category\\\/2026\\\/4\\\/2026\\\/#listItem\",\"position\":3,\"name\":\"4\\\/2026\",\"item\":\"https:\\\/\\\/vbentwickler.de\\\/category\\\/2026\\\/4\\\/2026\\\/\",\"nextItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/TSQL_Teil_2_SELECT_in_der_Praxis\\\/#listItem\",\"name\":\"T-SQL, Teil 2: SELECT in der Praxis\"},\"previousItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/category\\\/2026\\\/#listItem\",\"name\":\"2026\"}},{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/TSQL_Teil_2_SELECT_in_der_Praxis\\\/#listItem\",\"position\":4,\"name\":\"T-SQL, Teil 2: SELECT in der Praxis\",\"previousItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/category\\\/2026\\\/4\\\/2026\\\/#listItem\",\"name\":\"4\\\/2026\"}}]},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/#organization\",\"name\":\"Datenbankentwickler\",\"description\":\"Das Magazin f\\u00fcr Datenbankentwickler auf Basis von Visual Studio und Co.\",\"url\":\"https:\\\/\\\/vbentwickler.de\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"url\":\"https:\\\/\\\/vbentwickler.de\\\/wp-content\\\/uploads\\\/2022\\\/08\\\/cropped-header_vbe-1.png\",\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/TSQL_Teil_2_SELECT_in_der_Praxis\\\/#organizationLogo\",\"width\":664,\"height\":225},\"image\":{\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/TSQL_Teil_2_SELECT_in_der_Praxis\\\/#organizationLogo\"}},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/author\\\/andreminhorst-com\\\/#author\",\"url\":\"https:\\\/\\\/vbentwickler.de\\\/author\\\/andreminhorst-com\\\/\",\"name\":\"Andr\\u00e9 Minhorst\",\"image\":{\"@type\":\"ImageObject\",\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/TSQL_Teil_2_SELECT_in_der_Praxis\\\/#authorImage\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/1b9d010cf1716692cb9c34f21554e07d17d461acaea5b61b8cb21cbec678d48a?s=96&d=mm&r=g\",\"width\":96,\"height\":96,\"caption\":\"Andr\\u00e9 Minhorst\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/TSQL_Teil_2_SELECT_in_der_Praxis\\\/#webpage\",\"url\":\"https:\\\/\\\/vbentwickler.de\\\/TSQL_Teil_2_SELECT_in_der_Praxis\\\/\",\"name\":\"T-SQL, Teil 2: SELECT in der Praxis - Visual Basic Entwickler\",\"description\":\"Die Montagsauswertung f\\u00fcr den Vertrieb entsteht in Access im Abfrageentwurf: Positionen, Kunden, Ums\\u00e4tze und einige Gruppierungen. Zieht das Backend auf den SQL Server um, laufen Teile davon nicht mehr oder liefern in unserer Konfiguration andere Zahlen. Ein TOP 1 liefert dann eine Zeile statt zweier, ein Durchschnitt verliert seine Nachkommastellen, und eine Summe kommt als Text in Access an. Wer SELECT in T-SQL schreibt, kennt viel davon aus Access. Umdenken m\\u00fcssen wir an einigen Stellen, und einige davon zeigen keine Fehlermeldung, sondern nur andere Ergebnisse. Wie wir die Abfragen schreiben, die im Alltag gebraucht werden, und worauf wir dabei achten, zeigt dieser zweite Teil der Reihe.\",\"inLanguage\":\"de-DE\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/#website\"},\"breadcrumb\":{\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/TSQL_Teil_2_SELECT_in_der_Praxis\\\/#breadcrumblist\"},\"author\":{\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/author\\\/andreminhorst-com\\\/#author\"},\"creator\":{\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/author\\\/andreminhorst-com\\\/#author\"},\"datePublished\":\"2026-08-01T00:00:00+00:00\",\"dateModified\":\"-0001-11-30T00:00:00+00:00\"},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/#website\",\"url\":\"https:\\\/\\\/vbentwickler.de\\\/\",\"name\":\"Datenbankentwickler\",\"description\":\"Das Magazin f\\u00fcr Datenbankentwickler auf Basis von Visual Studio und Co.\",\"inLanguage\":\"de-DE\",\"publisher\":{\"@id\":\"https:\\\/\\\/vbentwickler.de\\\/#organization\"}}]}\n\t\t<\/script>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"T-SQL, Teil 2: SELECT in der Praxis - Visual Basic Entwickler","description":"Die Montagsauswertung f\u00fcr den Vertrieb entsteht in Access im Abfrageentwurf: Positionen, Kunden, Ums\u00e4tze und einige Gruppierungen. Zieht das Backend auf den SQL Server um, laufen Teile davon nicht mehr oder liefern in unserer Konfiguration andere Zahlen. Ein TOP 1 liefert dann eine Zeile statt zweier, ein Durchschnitt verliert seine Nachkommastellen, und eine Summe kommt als Text in Access an. Wer SELECT in T-SQL schreibt, kennt viel davon aus Access. Umdenken m\u00fcssen wir an einigen Stellen, und einige davon zeigen keine Fehlermeldung, sondern nur andere Ergebnisse. Wie wir die Abfragen schreiben, die im Alltag gebraucht werden, und worauf wir dabei achten, zeigt dieser zweite Teil der Reihe.","canonical_url":"https:\/\/vbentwickler.de\/TSQL_Teil_2_SELECT_in_der_Praxis\/","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"BlogPosting","@id":"https:\/\/vbentwickler.de\/TSQL_Teil_2_SELECT_in_der_Praxis\/#blogposting","name":"T-SQL, Teil 2: SELECT in der Praxis - Visual Basic Entwickler","headline":"T-SQL, Teil 2: SELECT in der Praxis","author":{"@id":"https:\/\/vbentwickler.de\/author\/andreminhorst-com\/#author"},"publisher":{"@id":"https:\/\/vbentwickler.de\/#organization"},"image":{"@type":"ImageObject","url":"http:\/\/vg07.met.vgwort.de\/na\/e073c1580d1f42d2a3902b18b72eb4f3","@id":"https:\/\/vbentwickler.de\/TSQL_Teil_2_SELECT_in_der_Praxis\/#articleImage"},"datePublished":"2026-08-01T00:00:00+00:00","dateModified":"-0001-11-30T00:00:00+00:00","inLanguage":"de-DE","mainEntityOfPage":{"@id":"https:\/\/vbentwickler.de\/TSQL_Teil_2_SELECT_in_der_Praxis\/#webpage"},"isPartOf":{"@id":"https:\/\/vbentwickler.de\/TSQL_Teil_2_SELECT_in_der_Praxis\/#webpage"},"articleSection":"2026, 4\/2026, SQL Server und Co."},{"@type":"BreadcrumbList","@id":"https:\/\/vbentwickler.de\/TSQL_Teil_2_SELECT_in_der_Praxis\/#breadcrumblist","itemListElement":[{"@type":"ListItem","@id":"https:\/\/vbentwickler.de\/#listItem","position":1,"name":"Home","item":"https:\/\/vbentwickler.de\/","nextItem":{"@type":"ListItem","@id":"https:\/\/vbentwickler.de\/category\/2026\/#listItem","name":"2026"}},{"@type":"ListItem","@id":"https:\/\/vbentwickler.de\/category\/2026\/#listItem","position":2,"name":"2026","item":"https:\/\/vbentwickler.de\/category\/2026\/","nextItem":{"@type":"ListItem","@id":"https:\/\/vbentwickler.de\/category\/2026\/4\/2026\/#listItem","name":"4\/2026"},"previousItem":{"@type":"ListItem","@id":"https:\/\/vbentwickler.de\/#listItem","name":"Home"}},{"@type":"ListItem","@id":"https:\/\/vbentwickler.de\/category\/2026\/4\/2026\/#listItem","position":3,"name":"4\/2026","item":"https:\/\/vbentwickler.de\/category\/2026\/4\/2026\/","nextItem":{"@type":"ListItem","@id":"https:\/\/vbentwickler.de\/TSQL_Teil_2_SELECT_in_der_Praxis\/#listItem","name":"T-SQL, Teil 2: SELECT in der Praxis"},"previousItem":{"@type":"ListItem","@id":"https:\/\/vbentwickler.de\/category\/2026\/#listItem","name":"2026"}},{"@type":"ListItem","@id":"https:\/\/vbentwickler.de\/TSQL_Teil_2_SELECT_in_der_Praxis\/#listItem","position":4,"name":"T-SQL, Teil 2: SELECT in der Praxis","previousItem":{"@type":"ListItem","@id":"https:\/\/vbentwickler.de\/category\/2026\/4\/2026\/#listItem","name":"4\/2026"}}]},{"@type":"Organization","@id":"https:\/\/vbentwickler.de\/#organization","name":"Datenbankentwickler","description":"Das Magazin f\u00fcr Datenbankentwickler auf Basis von Visual Studio und Co.","url":"https:\/\/vbentwickler.de\/","logo":{"@type":"ImageObject","url":"https:\/\/vbentwickler.de\/wp-content\/uploads\/2022\/08\/cropped-header_vbe-1.png","@id":"https:\/\/vbentwickler.de\/TSQL_Teil_2_SELECT_in_der_Praxis\/#organizationLogo","width":664,"height":225},"image":{"@id":"https:\/\/vbentwickler.de\/TSQL_Teil_2_SELECT_in_der_Praxis\/#organizationLogo"}},{"@type":"Person","@id":"https:\/\/vbentwickler.de\/author\/andreminhorst-com\/#author","url":"https:\/\/vbentwickler.de\/author\/andreminhorst-com\/","name":"Andr\u00e9 Minhorst","image":{"@type":"ImageObject","@id":"https:\/\/vbentwickler.de\/TSQL_Teil_2_SELECT_in_der_Praxis\/#authorImage","url":"https:\/\/secure.gravatar.com\/avatar\/1b9d010cf1716692cb9c34f21554e07d17d461acaea5b61b8cb21cbec678d48a?s=96&d=mm&r=g","width":96,"height":96,"caption":"Andr\u00e9 Minhorst"}},{"@type":"WebPage","@id":"https:\/\/vbentwickler.de\/TSQL_Teil_2_SELECT_in_der_Praxis\/#webpage","url":"https:\/\/vbentwickler.de\/TSQL_Teil_2_SELECT_in_der_Praxis\/","name":"T-SQL, Teil 2: SELECT in der Praxis - Visual Basic Entwickler","description":"Die Montagsauswertung f\u00fcr den Vertrieb entsteht in Access im Abfrageentwurf: Positionen, Kunden, Ums\u00e4tze und einige Gruppierungen. Zieht das Backend auf den SQL Server um, laufen Teile davon nicht mehr oder liefern in unserer Konfiguration andere Zahlen. Ein TOP 1 liefert dann eine Zeile statt zweier, ein Durchschnitt verliert seine Nachkommastellen, und eine Summe kommt als Text in Access an. Wer SELECT in T-SQL schreibt, kennt viel davon aus Access. Umdenken m\u00fcssen wir an einigen Stellen, und einige davon zeigen keine Fehlermeldung, sondern nur andere Ergebnisse. Wie wir die Abfragen schreiben, die im Alltag gebraucht werden, und worauf wir dabei achten, zeigt dieser zweite Teil der Reihe.","inLanguage":"de-DE","isPartOf":{"@id":"https:\/\/vbentwickler.de\/#website"},"breadcrumb":{"@id":"https:\/\/vbentwickler.de\/TSQL_Teil_2_SELECT_in_der_Praxis\/#breadcrumblist"},"author":{"@id":"https:\/\/vbentwickler.de\/author\/andreminhorst-com\/#author"},"creator":{"@id":"https:\/\/vbentwickler.de\/author\/andreminhorst-com\/#author"},"datePublished":"2026-08-01T00:00:00+00:00","dateModified":"-0001-11-30T00:00:00+00:00"},{"@type":"WebSite","@id":"https:\/\/vbentwickler.de\/#website","url":"https:\/\/vbentwickler.de\/","name":"Datenbankentwickler","description":"Das Magazin f\u00fcr Datenbankentwickler auf Basis von Visual Studio und Co.","inLanguage":"de-DE","publisher":{"@id":"https:\/\/vbentwickler.de\/#organization"}}]},"og:locale":"de_DE","og:site_name":"Visual Basic Entwickler - Das Magazin f\u00fcr Datenbankentwickler auf Basis von Visual Studio und Co.","og:type":"article","og:title":"T-SQL, Teil 2: SELECT in der Praxis - Visual Basic Entwickler","og:description":"Die Montagsauswertung f\u00fcr den Vertrieb entsteht in Access im Abfrageentwurf: Positionen, Kunden, Ums\u00e4tze und einige Gruppierungen. Zieht das Backend auf den SQL Server um, laufen Teile davon nicht mehr oder liefern in unserer Konfiguration andere Zahlen. Ein TOP 1 liefert dann eine Zeile statt zweier, ein Durchschnitt verliert seine Nachkommastellen, und eine Summe kommt als Text in Access an. Wer SELECT in T-SQL schreibt, kennt viel davon aus Access. Umdenken m\u00fcssen wir an einigen Stellen, und einige davon zeigen keine Fehlermeldung, sondern nur andere Ergebnisse. Wie wir die Abfragen schreiben, die im Alltag gebraucht werden, und worauf wir dabei achten, zeigt dieser zweite Teil der Reihe.","og:url":"https:\/\/vbentwickler.de\/TSQL_Teil_2_SELECT_in_der_Praxis\/","og:image":"https:\/\/datenbankentwickler.net\/wp-content\/uploads\/2022\/08\/cropped-header_vbe-1.png","og:image:secure_url":"https:\/\/datenbankentwickler.net\/wp-content\/uploads\/2022\/08\/cropped-header_vbe-1.png","article:published_time":"2026-11-12T13:04:30+00:00","article:modified_time":"-001-11-30T00:00:00+00:00","twitter:card":"summary_large_image","twitter:title":"T-SQL, Teil 2: SELECT in der Praxis - Visual Basic Entwickler","twitter:description":"Die Montagsauswertung f\u00fcr den Vertrieb entsteht in Access im Abfrageentwurf: Positionen, Kunden, Ums\u00e4tze und einige Gruppierungen. Zieht das Backend auf den SQL Server um, laufen Teile davon nicht mehr oder liefern in unserer Konfiguration andere Zahlen. Ein TOP 1 liefert dann eine Zeile statt zweier, ein Durchschnitt verliert seine Nachkommastellen, und eine Summe kommt als Text in Access an. Wer SELECT in T-SQL schreibt, kennt viel davon aus Access. Umdenken m\u00fcssen wir an einigen Stellen, und einige davon zeigen keine Fehlermeldung, sondern nur andere Ergebnisse. Wie wir die Abfragen schreiben, die im Alltag gebraucht werden, und worauf wir dabei achten, zeigt dieser zweite Teil der Reihe.","twitter:image":"https:\/\/datenbankentwickler.net\/wp-content\/uploads\/2022\/08\/cropped-header_vbe-1.png"},"aioseo_meta_data":{"post_id":"55000517","title":null,"description":null,"keywords":null,"keyphrases":null,"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"","isEnabled":true},"graphs":[]},"schema_type":"default","schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"local_seo":null,"limit_modified_date":false,"created":"2026-11-12 12:04:39","updated":"2026-11-12 12:04:39","ai":null,"breadcrumb_settings":null,"seo_analyzer_scan_date":null,"focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"aioseo_breadcrumb":"<div class=\"aioseo-breadcrumbs\"><span class=\"aioseo-breadcrumb\">\n\t\t\t<a href=\"https:\/\/vbentwickler.de\/\" title=\"Home\">Home<\/a>\n\t\t<\/span><span class=\"aioseo-breadcrumb-separator\">\u00bb<\/span><span class=\"aioseo-breadcrumb\">\n\t\t\t<a href=\"https:\/\/vbentwickler.de\/category\/2026\/\" title=\"2026\">2026<\/a>\n\t\t<\/span><span class=\"aioseo-breadcrumb-separator\">\u00bb<\/span><span class=\"aioseo-breadcrumb\">\n\t\t\t<a href=\"https:\/\/vbentwickler.de\/category\/2026\/4\/2026\/\" title=\"4\/2026\">4\/2026<\/a>\n\t\t<\/span><span class=\"aioseo-breadcrumb-separator\">\u00bb<\/span><span class=\"aioseo-breadcrumb\">\n\t\t\tT-SQL, Teil 2: SELECT in der Praxis\n\t\t<\/span><\/div>","aioseo_breadcrumb_json":[{"label":"Home","link":"https:\/\/vbentwickler.de\/"},{"label":"2026","link":"https:\/\/vbentwickler.de\/category\/2026\/"},{"label":"4\/2026","link":"https:\/\/vbentwickler.de\/category\/2026\/4\/2026\/"},{"label":"T-SQL, Teil 2: SELECT in der Praxis","link":"https:\/\/vbentwickler.de\/TSQL_Teil_2_SELECT_in_der_Praxis\/"}],"_links":{"self":[{"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/posts\/55000517","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/comments?post=55000517"}],"version-history":[{"count":0,"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/posts\/55000517\/revisions"}],"wp:attachment":[{"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/media?parent=55000517"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/categories?post=55000517"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/tags?post=55000517"},{"taxonomy":"yst_prominent_words","embeddable":true,"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/yst_prominent_words?post=55000517"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}