SQL-Konventionen
Die SQL-Queries in einem Plugin sind T-SQL (Microsoft SQL Server / JTL-Wawi eazybusiness). Sie werden über ADO.NET-Parameter ausgeführt.
Entwickler-Handbuch · Plugin-Authoring
Grundregeln
| Regel | Begründung |
|---|---|
| Genau ein SELECT-Statement pro Datei | Validator lehnt Batches ab |
| Keine Semikolons | Werden als Multi-Statement interpretiert |
Keine Kommentare (--, /* */) | Werden beim Packen entfernt |
Parametrisiert via @{name} | Vermeidet SQL-Injection |
Nur SELECT, kein INSERT/UPDATE/DELETE (außer automation-Plugins) | Schutz vor Datenmutation |
| Timeout = 30s, Row-Limit = 10.000 (überschreibbar) | Performance-Schutz |
Parameter
Parameter werden im SQL als @{paramName} referenziert. Beim Packen ersetzt der Hub sie durch @paramName — das ist exakt die ADO.NET-Syntax, kein zusätzlicher Templating-Schritt.
Beispiel
Manifest:
{
"id": "sales-by-period",
"parameters": [
{ "name": "fromDate", "type": "date", "required": true },
{ "name": "minAmount", "type": "number", "required": false }
]
}SQL (sql/sales-by-period.sql):
SELECT
YEAR(dErstellt) AS Jahr,
MONTH(dErstellt) AS Monat,
SUM(fGesamtsumme) AS Umsatz
FROM tBestellung
WHERE dErstellt >= @{fromDate}
AND (@{minAmount} IS NULL OR fGesamtsumme >= @{minAmount})
GROUP BY YEAR(dErstellt), MONTH(dErstellt)
ORDER BY Jahr DESC, Monat DESCDer Hub macht daraus zur Laufzeit:
SELECT
YEAR(dErstellt) AS Jahr,
MONTH(dErstellt) AS Monat,
SUM(fGesamtsumme) AS Umsatz
FROM tBestellung
WHERE dErstellt >= @fromDate
AND (@minAmount IS NULL OR fGesamtsumme >= @minAmount)
GROUP BY YEAR(dErstellt), MONTH(dErstellt)
ORDER BY Jahr DESC, Monat DESCUnd übergibt die Parameter typisiert als ADO.NET- SqlParameter:
new SqlParameter("@fromDate", SqlDbType.Date) { Value = fromDate },
new SqlParameter("@minAmount", SqlDbType.Decimal) { Value = (object?)minAmount ?? DBNull.Value }NULL-Handling
Wenn ein Parameter required: false ist und der User ihn leer lässt:
string→DBNull.Valuenumber→DBNull.Valuedate→DBNull.Valuebool→false(kein Nullable)article/customer→DBNull.Value
Prüfe im SQL immer explizit auf NULL, wenn der Parameter optional ist:
AND (@{minAmount} IS NULL OR fGesamtsumme >= @{minAmount})Spalten-Aliasse
Die Spaltennamen im Resultat werden genau so an die DataTable weitergegeben, wie sie im SQL stehen:
SELECT
cName AS Artikelname,
fPreis AS Netto,
fVKNetto AS Brutto
FROM tArtikel→ DataTable-Header: Artikelname, Netto, Brutto
Faustregel: Verwende sprechende AS-Aliasse, keine technischen Spaltennamen aus JTL.
Sortierung
Die DataTable ist clientseitig sortierbar (TanStack Table). Trotzdem: Im SQL eine sinnvolle Default-Sortierung setzen — der CSV-Export nutzt die Reihenfolge aus der DB.
ORDER BY Umsatz DESCPerformance-Tipps
| Tipp | Warum |
|---|---|
TOP N oder OFFSET ... FETCH NEXT | Verhindert riesige Results |
| Aggregat-Operationen in SQL, nicht im Frontend | Schneller, weniger Traffic |
| EXISTS statt IN für Sub-Queries | Besser optimierbar |
| Indizes nutzen | JTL hat die wichtigsten schon — siehe IX_tBestellung_kKunde etc. |
Was NICHT funktioniert
| Pattern | Warum abgelehnt |
|---|---|
SELECT 1; SELECT 2 | Multi-Statement |
-- Kommentar | Werden entfernt |
/* Kommentar */ | Werden entfernt |
EXEC sp_who | System-Prozeduren verboten |
xp_cmdshell '...' | Sicherheits-Risiko |
OPENROWSET(...) | Verbindung nach außen |
INSERT INTO ... (in report-Plugins) | Nur Lesezugriff |
BULK INSERT | Sicherheits-Risiko |
Mehr dazu in Security-Richtlinien.
Testen vor dem Packen
Teste Queries immer zuerst in SSMS mit dem echten JTL-Mandanten. Erst wenn sie dort laufen, kommen sie ins Plugin.
DECLARE @fromDate DATE = '2026-01-01'
DECLARE @minAmount DECIMAL(18,2) = NULL
SELECT
YEAR(dErstellt) AS Jahr,
MONTH(dErstellt) AS Monat,
SUM(fGesamtsumme) AS Umsatz
FROM tBestellung
WHERE dErstellt >= @fromDate
AND (@minAmount IS NULL OR fGesamtsumme >= @minAmount)
GROUP BY YEAR(dErstellt), MONTH(dErstellt)
ORDER BY Jahr DESC, Monat DESCWenn die Query im SSMS läuft, ist sie plugin-ready.