Zum Inhalt springen

Referenz

NQL, die Abfragesprache für Kennzahlen

Jede Kennzahl-Kachel in nexxus ist eine NQL-Abfrage. Diese Seite erklärt, wie eine Abfrage aufgebaut ist, welche Quellen und Felder es gibt, wie sich Werte beschriften lassen, was die Antwort enthält und warum niemand über NQL mehr sieht, als er sehen darf.

Für
AlleAdminsEntwickler
Dauer
25 Min.

NQL ist die Abfragesprache hinter jeder Kennzahl in nexxus: hinter den Kacheln auf dem Dashboard, auf den Auswertungen und hinter den Antworten des KI-Assistenten auf Fragen nach Zahlen. Eine Abfrage ist ein kleines JSON-Objekt. Sie nennt eine Quelle, die Kennzahlen, die gezählt oder summiert werden, die Merkmale, nach denen gegliedert wird, und Filter.

{
"source": "project",
"measures": [{"name": "anzahl", "as": "anzahl"}],
"dimensions": [{"field": "dateCreated", "grain": "month", "as": "monat"}],
"filters": [{"field": "isActive", "op": "eq", "value": true}],
"orderBy": [{"as": "monat", "dir": "asc"}]
}

Diese Abfrage zählt die aktiven Projekte je Monat, in dem sie angelegt wurden, und sortiert nach Monat.

NQL ist absichtlich kein SQL. Eine Abfrage kann keine Tabelle und keine Spalte frei benennen. Sie wählt aus, was eine Quelle veröffentlicht hat: Merkmale, Kennzahlen, Operatoren und Zeitraster aus festen Listen. Alles andere lehnt nexxus ab und nennt dabei, was gültig gewesen wäre.

Sie müssen NQL nicht schreiben können. Der KI-Assistent und der Kachel-Dialog schreiben die Abfrage für Sie. Diese Seite hilft, wenn Sie eine Abfrage lesen, von Hand anpassen oder aus einem Skript stellen wollen.

  • Im Kachel-Dialog. Unter der Vorschau öffnet Für Fortgeschrittene: Abfrage als JSON die NQL-Abfrage der Kachel. Sie bearbeiten sie dort direkt; sie Wird erst mit „Vorschau“ ausgeführt.
  • Beim KI-Assistenten. Fragen Sie ihn nach Zahlen, schreibt er eine Abfrage und führt sie mit Ihren Rechten aus. Auf einer Auswertung baut er daraus Kacheln, die Sie bestätigen.
  • In den Werkzeugen des KI-Assistenten und in KI-Clients. Der eingebaute Connector „Auswertungen“ bringt die Werkzeuge nx_nql_schema (was Sie auswerten dürfen) und nx_nql_query (eine Abfrage ausführen) mit. Sie laufen mit den Rechten der Person der Unterhaltung, auch in einem verbundenen Client wie Claude Code.
  • Über die API. GET /api/nql/schema liefert die Quellen, die Sie lesen dürfen, mit ihren Merkmalen und Kennzahlen; POST /api/nql/query führt eine Abfrage aus. Ein Skript authentifiziert sich dafür mit einem persönlichen API-Token und hat dann dieselben Rechte wie Sie.

Die tragende Regel von NQL: Niemand bekommt Zahlen, die er sich nicht auch einzeln hätte holen dürfen. Eine Kachel läuft bei der Person, die sie ansieht, nicht bei der, die sie angelegt hat. Deshalb zeigt dieselbe Kachel verschiedenen Personen verschiedene Zahlen, und eine geteilte Auswertung gibt niemandem Daten.

  1. Recht „Auswertungen“

    Ohne core.analytics.read gibt es keine Abfrage.

  2. Leserecht der Quelle

    Etwa project.project.read für Projekte. Manche Quellen nur für Administratoren.

  3. Veröffentlichte Felder

    Nur Merkmale und Kennzahlen, die die Quelle anbietet.

  4. Mandant und Zeilen

    Nur Ihr Mandant, nur nicht gelöschte Datensätze, dazu der Zeilenfilter der Quelle.

Jede Stufe prüft der Server bei jeder Abfrage, auch wenn die Kachel schon seit Monaten auf einer Auswertung steht.
StufeWas bei Nein passiert
Recht Auswertungen (core.analytics.read)Die Abfrage scheitert mit 403. Ohne das Recht gibt es weder Kennzahl-Kacheln noch Auswertungen.
Leserecht der QuelleDie Quelle fehlt im Schema der Person, eine Abfrage darauf scheitert mit 403. Die Kachel zeigt Keine Berechtigung für diese Daten.
Veröffentlichte FelderDie Abfrage scheitert mit 400 und nennt die gültigen Felder.
Mandant und LöschstatusZeilen anderer Mandanten und gelöschte Datensätze sind schlicht nicht da.
Zeilenfilter der QuelleZeilen, die die Person nicht sehen darf, sind schlicht nicht da (bei KI-Sitzungen die fremden, bei Abwesenheiten alle außer den eigenen und denen der geleiteten Teams).

Administratoren bestehen jede Rechteprüfung, aber nicht die Trennung der Mandanten. Ein .write-Recht für Auswertungen gibt es nicht, denn NQL schreibt nichts.

FeldPflichtFormWirkung
sourcejaSchlüssel, z. B. "project"Die Quelle. Die Liste steht unter Die Quellen.
measureseins von beiden[{name, as?, percentOfTotal?}] oder ["anzahl"]Die Kennzahlen. name ist eine Kennzahl der Quelle, as der Spaltenname in der Antwort (Vorgabe: name).
dimensionseins von beiden[{field, grain?, as?, format?, template?, labels?, ids?}] oder ["status"]Die Merkmale, nach denen gegliedert wird. grain nur bei Datumsmerkmalen, die Beschriftung unter Werte beschriften.
filtersnein[{field, op, value}], auch mit GruppenZeilen einschränken, bevor gezählt wird. op ist ohne Angabe eq. Gruppen mit ODER und NICHT unter Filter.
havingnein[{as, op, value}], auch mit GruppenGruppen einschränken, nachdem gezählt wurde. as ist eine Kennzahl-Spalte.
orderBynein[{as, dir}]Sortieren nach einer Spalte der Antwort, dir ist asc (Vorgabe) oder desc.
limitneinZahlHöchstens so viele Zeilen. Vorgabe 1000, höchstens 5000.
topNnein{by?, limit?, otherLabel?, within?}Die stärksten Gruppen behalten, den Rest zusammenfassen.
totalsneintrue oder {grandTotal, subtotalsFor}Gesamt- und Zwischensummen neben den Zeilen.
splitneinSpaltenname eines MerkmalsDie Werte dieses Merkmals werden zu Spalten.

Eine Kennzahl ist eine fest definierte Rechnung der Quelle: anzahl zählt Projekte, stunden summiert die Stunden von Zeiteinträgen, kunden zählt verschiedene Kunden. Die Abfrage wählt die Kennzahl beim Namen, nicht die Rechnung. Deshalb kann keine Abfrage eine Spalte summieren, die die Quelle nicht dafür freigegeben hat.

Jede Kennzahl hat eine Funktion: count, count_distinct, sum, avg, min oder max. Das Schema nennt sie unter fn, dazu "additive": true oder false. Additiv sind count und sum: zwei Teilsummen ergeben zusammen die Gesamtsumme. Durchschnitte, Höchstwerte und das Zählen verschiedener Werte lassen sich nicht so zusammenfassen. nexxus lehnt sie deshalb dort ab, wo zusammengefasst werden müsste: neben einem Verweis-Merkmal, bei topN mit Rest, bei totals und bei percentOfTotal. Eine Absage ist besser als eine plausible falsche Zahl.

percentOfTotal: true an einer Kennzahl ergänzt eine Spalte <as>Anteil mit ihrem Anteil am Ganzen in Prozent, mit einer Nachkommastelle. Die Anteile ergeben zusammen genau 100.

Ein Merkmal ist ein Feld, nach dem gegliedert und gefiltert werden kann: der Status, die Abrechnungsart, das Anlagedatum. Sein Typ steht im Schema:

TypBeispielBesonderheit
stringProjektname, ModellText
numberAuswirkung eines Risikos (1 bis 4)Zahl
booleanAktiv, AbrechenbarDie Kacheln zeigen Ja und Nein
enumStatus, ArtFeste Werte; das Schema nennt sie unter values und ihre Namen unter valueLabels (OPEN heißt „Offen“)
dateAngelegt am, DatumNimmt ein Zeitraster grain: day, week, month, quarter oder year. Ein Wert ist der Beginn des Zeitraums
Verweis ("reference": true)Kunde eines Projekts, Projekt eines ZeiteintragsZeigt auf ein anderes Modul. Die Antwort enthält den Namen, ein Filter braucht die Nummer

Ein Verweis-Merkmal wird erst nach dem Zählen in Namen übersetzt. Haben zwei Kunden denselben Namen, landen sie in einer Zeile; die Antwort sagt das mit meta.merged. Deshalb verträgt ein Verweis nur additive Kennzahlen.

Mit "ids": true am Merkmal behält die Antwort stattdessen die Nummer: In den Zeilen stehen Nummern, in valueLabels der Spalte ihre Namen, und zwei Kunden gleichen Namens bleiben zwei Zeilen. In dieser Form braucht es ein Filter, denn ein Filter nennt die Nummer.

Ein Filter ist eine Liste von Bedingungen, und eine Liste heißt: alle ihre Einträge.

"filters": [
{"field": "status", "op": "in", "value": ["OPEN", "MITIGATED"]},
{"field": "dateCreated", "op": "gte", "value": "2026-01-01"},
{"field": "reviewDate", "op": "isNull"}
]

Ein Eintrag kann auch eine Gruppe sein. So kommen ODER und NICHT in eine Abfrage:

GruppeTrifft zu, wenn
{"all": [...]}alle Einträge zutreffen
{"any": [...]}mindestens ein Eintrag zutrifft
{"not": ...}der Eintrag darin nicht zutrifft

Offene Risiken, die schwer wiegen oder noch keinen Prüftermin haben:

"filters": [
{"field": "status", "op": "eq", "value": "OPEN"},
{"any": [
{"field": "impact", "op": "gte", "value": 3},
{"not": {"field": "reviewDate", "op": "isNotNull"}}
]}
]

Gruppen lassen sich bis zu 5 Ebenen tief schachteln (ein not zählt nicht als eigene Ebene). Eine Bedingung fasst höchstens 50 Vergleiche, ein Vergleich höchstens 500 Werte. having nimmt dieselben Gruppen, mit as statt field.

OperatorBedeutungvalue
eq, negleich, ungleichein Wert
gt, gte, lt, ltegrößer, größer gleich, kleiner, kleiner gleichein Wert
between, notBetweeninnerhalb oder außerhalb zweier Grenzen; die Grenzen zählen als innerhalbgenau zwei Werte
in, notIneiner der Werte, keiner der Werteeine Liste
contains, notContainsenthält, enthält nichtein Text
startsWith, endsWithbeginnt mit, endet mitein Text
isNull, isNotNullleer, nicht leerkeiner
inLast, inNextvon so lange her bis heute, von heute bis so weit voraus{"amount": 7, "unit": "day"}; unit ist day, week, month, quarter oder year
inPeriodin einem Kalenderzeitraum um heute; Wochen beginnen am Montageins von yesterday, today, tomorrow, oder last, this bzw. next mit Week, Month, Quarter, Year, z. B. lastMonth

Nicht jeder Operator passt zu jedem Merkmal. Jedes Merkmal nimmt eq, ne, in, notIn, isNull und isNotNull. Zahlen, Daten und Text nehmen außerdem gt, gte, lt, lte, between und notBetween. contains, notContains, startsWith und endsWith gehören zu Text, inLast, inNext und inPeriod zu Daten. Alles andere endet mit 400.

Werte schreiben Sie so, wie der Typ des Merkmals es verlangt:

TypWert
date"2026-01-01" (dieser ganze Tag), ein Zeitpunkt wie "2026-01-01T00:00:00Z", Millisekunden seit 1970 oder {"relativeDays": -7}
numbereine Zahl; Nachkommastellen bleiben erhalten
booleantrue oder false
enumeiner der Rohwerte aus values, z. B. "OPEN"
stringein Text
Verweisdie Nummer des Datensatzes, nicht sein Name; bei einem Merkmal, das auf eine Person zeigt, auch {"currentUser": true}, die fragende Person

Ein Tag meint den ganzen Tag, in der Zeitzone des Mandanten, auch bei einem Zeitpunkt: eq "2026-03-31" nimmt den ganzen Tag, lte "2026-03-31" schließt seinen Abend ein, gt "2026-03-31" beginnt am 1. April.

Relative Werte werden bei jedem Lauf neu berechnet, eine gespeicherte Abfrage stimmt also auch morgen noch. {"relativeDays": 0} ist heute, -7 vor einer Woche, 3 in drei Tagen. inLast, inNext und inPeriod gelten genauso, und immer in ganzen Tagen.

Ein anderes Merkmal als Wert: {"field": "dateCreated"} vergleicht zwei Merkmale einer Zeile, mit eq, ne, gt, gte, lt und lte. Beide müssen denselben Typ haben, und zwei Verweise müssen auf dieselbe Art von Datensatz zeigen. Trifft ein Tag auf einen Zeitpunkt, zählt der Tag dieses Zeitpunkts in der Zeitzone des Mandanten.

{"field": "followUpDate", "op": "lt", "value": {"field": "dateCreated"}}

Leere Felder: ne, notIn, notContains, notBetween und alles unter not finden auch Zeilen, in denen das Merkmal leer ist, denn ein leeres Feld ist nicht „Offen“. Die Größenvergleiche finden keine, deshalb ist {"not": {"field": "impact", "op": "gt", "value": 3}} nicht dasselbe wie lte 3: Es findet auch Risiken ohne Auswirkung.

Text ignoriert Groß- und Kleinschreibung bei eq, ne, in, notIn und den Operatoren für Teile eines Textes.

filters oder having? „Projekte aus 2026“ ist ein Filter: Er schränkt die Zeilen ein, bevor gezählt wird. „Kunden mit mehr als fünf Projekten“ ist ein having: Es schränkt die Gruppen ein, nachdem gezählt wurde.

{"source": "project", "measures": ["anzahl"], "dimensions": ["kunde"],
"having": [{"as": "anzahl", "op": "gt", "value": 5}]}

topN behält die stärksten Gruppen und fasst den Rest in einer Zeile zusammen. Ein Diagramm mit vierzig Reihen liest niemand; so bleibt der Rest als ehrlicher Block sichtbar, statt stillschweigend zu fehlen.

"topN": {"by": "stunden", "limit": 5, "otherLabel": "Sonstige", "within": ["monat"]}
  • by: die Kennzahl, nach der gerankt wird. Vorgabe ist die erste Kennzahl.
  • limit: wie viele Gruppen bleiben. Vorgabe 5.
  • otherLabel: der Name der Rest-Zeile, Vorgabe „Sonstige“. null lässt den Rest weg.
  • within: Merkmale, innerhalb derer gerankt wird. ["monat"] heißt „die stärksten fünf je Monat“. Hat die Abfrage außer dem gerankten Merkmal noch ein Datum, muss es hier stehen; sonst würde „Sonstige“ in die Datumsspalte geschrieben, und nexxus lehnt die Abfrage ab.

totals liefert Summen neben den Zeilen, nicht zwischen ihnen. Ein Diagramm, das eine Zeile „Gesamt“ zwischen seinen Daten bekäme, würde sie als riesigen Balken zeichnen.

"totals": {"grandTotal": true, "subtotalsFor": ["kategorie"]}

totals: true ist die Kurzform für die Gesamtsumme. Die Antwort enthält dann grandTotal ({spalte: summe}) und subtotals (je Merkmal die Gruppen mit ihren Summen).

split dreht ein Merkmal in Spalten. Aus monat | status | anzahl wird monat | Offen | Behandelt, die Form, die ein Diagramm mit mehreren Reihen braucht. Höchstens 50 Spalten; bei mehr Werten erst topN anwenden.

Nach dem Zählen laufen die Stufen immer in dieser Reihenfolge: having, topN, percentOfTotal, totals, orderBy, Beschriftung, split. Eine Abfrage kann sie also nicht in einer anderen Reihenfolge verlangen, und das Ergebnis hängt nicht davon ab, in welcher Reihenfolge die Felder im JSON stehen.

Manche Werte sehen in den Daten technisch aus: der Objekttyp im Änderungsprotokoll ist app.modules.core.migration.MigrationStatus, eine Abwesenheitsart heißt intern SPECIAL_LEAVE. Drei Angaben an einem Merkmal bestimmen, wie seine Werte in Legenden, Achsen, Tabellen, Filterlisten und im PDF dastehen. Die Daten ändern sie nicht: Die Antwort enthält weiter den Rohwert, und ein Filter nennt weiter den Rohwert.

"dimensions": [
{"field": "className", "as": "typ", "format": ["class", "words"]},
{"field": "impact", "as": "auswirkung", "template": "Stufe {value}"},
{"field": "isActive", "as": "aktiv", "labels": {"true": "Aktiv", "false": "Inaktiv"}}
]
AngabeFormWirkung
formatein Format oder eine Liste von höchstens vier, der Reihe nach angewandtFormt den Wert um, siehe die Tabelle darunter
templateText mit {value}, höchstens 80 ZeichenSetzt Text um den Wert: aus 3 wird „Stufe 3“. "" entfernt die Vorlage der Quelle
labels{rohwert: name}, höchstens 200, je höchstens 80 ZeichenFeste Namen für einzelne Werte. Bei einem enum nur seine Werte, bei Ja/Nein nur true und false
FormatAusWird
rawder Wert, wie er gespeichert ist; schaltet die Vorgabe der Quelle ab
classapp.modules.core.migration.MigrationStatusMigrationStatus (der Teil nach dem letzten Punkt)
wordsMigrationStatus, SPECIAL_LEAVEMigration Status, Special leave
upperOffenOFFEN
lowerOffenoffen
capitalizeoffenOffen (nur der erste Buchstabe)

So entsteht ein Name: Steht der Wert in labels, gilt dieser Name. Sonst beginnt nexxus mit dem Namen, den die Quelle für den Wert kennt (bei einem enum etwa „Offen“ für OPEN), oder mit dem Wert selbst, wendet die Formate der Reihe nach an und setzt das Ergebnis in die Vorlage.

Eine Quelle kann ein Format vorgeben. Der Objekttyp des Änderungsprotokolls steht deshalb ohne jede Angabe als MigrationStatus da; das Schema zeigt die Vorgabe unter format. Die Abfrage ersetzt format und template der Quelle, "format": "raw" schaltet die Vorgabe ab.

Grenzen:

  • Datumsmerkmale nehmen keine der drei Angaben. Eine Kachel schreibt ein Datum nach seinem Zeitraster („Okt. 26“, „Q3 2026“).
  • Ja/Nein-Merkmale nehmen nur labels.
  • Den Rest von topN („Sonstige“) beschriftet nichts.
  • split benennt seine Spalten nach den Namen, der Schlüssel jeder Spalte (as) bleibt der Rohwert. Eine Sortierung oder eine Reihenfarbe wandert also nicht mit, wenn Sie die Beschriftung ändern.
  • Ergeben zwei Rohwerte denselben Namen, stehen sie in Diagrammen zusammen.
{
"columns": [
{"as": "typ", "role": "dimension", "type": "string", "label": "Objekttyp", "grain": null,
"valueLabels": {"app.modules.communication.email.Email": "Email"}},
{"as": "anzahl", "role": "measure", "type": "number", "label": "Anzahl Änderungen", "grain": null, "valueLabels": null}
],
"rows": [["app.modules.communication.email.Email", 1800], ["Sonstige", 412]],
"meta": {"rowCount": 2, "truncated": false, "durationMs": 37, "merged": false, "foldedIntoOther": true, "split": null},
"subtotals": [],
"grandTotal": null
}
  • columns beschreibt die Spalten einmal, rows enthält die Zeilen als Listen in dieser Reihenfolge. Ein Diagramm braucht die Spalten nur einmal, nicht in jeder Zeile.
  • label ist der Name der Spalte, valueLabels die Beschriftung der Werte eines Merkmals: Rohwert und Name, für die Werte in den Zeilen, deren Name vom Rohwert abweicht. Ganze Zahlen stehen als Text ohne Nachkommastellen ("3"). Mit ids an einem Verweis steht dort der Name jeder Nummer in den Zeilen. Sonst null.
  • Datumswerte kommen immer als Millisekunden seit 1970, gleich welches Zeitraster.
  • meta.truncated: true heißt: Die Zeilengrenze hat Zeilen abgeschnitten. Wer die Antwort weiterverwendet, sollte das sagen, statt Vollständigkeit vorzutäuschen.
  • meta.merged: Verweis-Werte gleichen Namens wurden zusammengefasst. meta.foldedIntoOther: topN hat einen Rest gebildet. meta.split: das Merkmal, das zu Spalten wurde.
StatusWannBeispiel
403Das Recht für Auswertungen fehlt, oder das Leserecht der Quelle; die Meldung nennt den Schlüssel
400Unbekannte Quelle, unbekanntes Merkmal, unbekannte Kennzahl, unbekannter Operator oder unbekanntes Zeitraster, ein ungültiger Enum-Wert, ein Verweis auf eine Spalte, die die Abfrage nicht hat, eine nicht additive Kennzahl, wo addiert werden müsste, eine Beschriftung, die das Merkmal nicht nimmt„Unknown dimension ‘kundenname’ for source ‘project’. Available: billable, billingModel, dateCreated, designation, isActive, isInternal, kunde, projektgruppe“

Die Meldungen sind für Menschen und für Sprachmodelle geschrieben: Sie zählen auf, was gültig gewesen wäre. Der KI-Assistent korrigiert damit seine eigene Abfrage, bevor Sie eine Kachel zu sehen bekommen.

Welche Quellen eine Person sieht, hängt von ihren Rechten ab. Die folgende Liste ist die eines Administrators. Merkmale vom Typ date bieten alle Zeitraster an. Die Namen der Quellen, Merkmale und Kennzahlen stehen in jeder Sprache auf Deutsch, so wie der Server sie liefert. Die erste Spalte nennt den Schlüssel der Quelle, ihren Namen und das Leserecht, das sie verlangt.

QuelleMerkmaleKennzahlen
project
Projekte
project.project.read
designation Projekt, kunde Kunde (Verweis), projektgruppe Projektgruppe (Verweis), isActive Aktiv, isInternal Intern, billable Abrechenbar, billingModel Abrechnungsart, dateCreated Angelegt amanzahl Anzahl Projekte, kunden Kunden (verschiedene)
timesheetEntry
Zeiteinträge
timesheet.entry.read
entryDate Datum, projekt Projekt (Verweis), source Herkunft, billable Abrechenbareintraege Einträge, stunden Stunden
vacationRequest
Abwesenheiten
absence.vacationRequest.read
mitarbeiter Mitarbeiter (Verweis), type Art, status Status, fromDate Beginnantraege Anträge, tage Tage
partner
Kunden und Lieferanten
partner.partner.read
name Name, partnerType Art, isActive Aktiv, isInternal Intern, currency Währung, dateCreated Angelegt amanzahl Anzahl
ismsRisk
ISMS-Risiken
isms.risk.read
status Status, likelihood Eintrittswahrscheinlichkeit, impact Auswirkung, reviewDate Nächste Prüfung, dateCreated Angelegt amanzahl Anzahl Risiken, wahrscheinlichkeitSchnitt und auswirkungSchnitt (Durchschnitte)
ismsMeasure
ISMS-Maßnahmen
isms.measure.read
category TOM-Kategorie, measureType Art, status Status, recurrence Turnus, approvalRequired Freigabe nötig, reviewDate Erste Fälligkeit, dateCreated Angelegt amanzahl Anzahl Maßnahmen
form
Formulare
formBuilder.read
name Formular, type Typ, status Status, version Version, dateCreated Angelegt amanzahl Anzahl Formulare, hoechsteVersion Höchste Version
user
Benutzerkonten
authentication.user.read
username Benutzername, accountType Kontotyp, invitationPending Einladung offen, lastLoginAt Letzte Anmeldung, dateCreated Angelegt amanzahl Anzahl Konten
aiSession
KI-Sitzungen
ai.session.use
dateCreated Begonnen am, model Modell, status Status, origin Herkunft, createdById Benutzer-IDsitzungen Sitzungen, nachrichten Nachrichten, nachrichtenSchnitt Ø Nachrichten je Sitzung
aiScheduleRun
Schedule-Läufe
aiAssistant.schedule.read
status Status, trigger Auslöser, scheduleId Schedule-ID, model Modell, scheduledFor Solltermin, dateCreated Angelegt amlaeufe Läufe, dauerMs Ø Dauer (ms), werkzeugaufrufe Werkzeugaufrufe, werkzeugfehler Werkzeugfehler
auditLog
Protokoll (Änderungen)
nur Administratoren
className Objekttyp (Format class), eventName Vorgang, actor Person, dateCreated Zeitpunktanzahl Anzahl Änderungen, personen Aktive Personen (verschiedene)

Einige Quellen sind bewusst knapp: Die Risiken kommen ohne Titel und Beschreibung, die Partner ohne Kontakt- und Bankdaten, das Änderungsprotokoll ohne alte und neue Werte, die Benutzerkonten ohne persönliche Angaben. Was eine Quelle nicht veröffentlicht, kann niemand auswerten.

Zwei Quellen schränken die Zeilen je Person ein: KI-Sitzungen zeigen jeder Person nur ihre eigenen, Administratoren alle; Abwesenheiten zeigen die eigenen und die der Teams, die die Person leitet, wie die Liste der Abwesenheiten.

Jedes Fachobjekt eines aktiven Paket-Moduls ist außerdem eine eigene Quelle mit dem Schlüssel <paket>:<fachobjekt>. Die Liste oben enthält sie nicht, weil sie davon abhängen, was Ihr Mandant installiert hat. Das Paket muss dafür nichts tun.

WasRegel
NameDie Mehrzahl des Fachobjekts, dahinter das Paket in Klammern, zum Beispiel Kunden (Vorgänge).
RechtDas Leserecht des Fachobjekts. Ein Datensatz hat keine eigenen Rechte: Wer das Fachobjekt lesen darf, sieht alle seine Zeilen.
MerkmaleDie indizierten Felder des Fachobjekts und jeder Verweis, dazu id, createdAt, updatedAt und createdBy. Ein Feld ohne Index fehlt auch hier.
Kennzahlenanzahl, dazu je Zahlenfeld summe_<feld>, schnitt_<feld>, min_<feld> und max_<feld>.

Drei Dinge verhalten sich anders als bei den Quellen oben:

  • Felder mit mehreren Werten: Ein Feld mit multiple führt einen Datensatz unter jedem seiner Werte. Eine Summe über solche Gruppen zählt ihn einmal je Wert.
  • Text: Vergleiche achten nicht auf Groß- und Kleinschreibung und reichen bis zum 500. Zeichen eines Felds.
  • Ausgeschaltet: Solange ein Paket deaktiviert ist, fehlen seine Quellen. Kacheln darauf zeigen Dieser Bereich ist ausgeschaltet, Benachrichtigungsregeln stehen auf kann nicht prüfen. Nach dem Aktivieren arbeiten beide wieder wie vorher.
  • Eine Quelle je Abfrage. NQL verbindet keine Quellen. Werte aus anderen Modulen kommen nur als Verweis-Merkmal, etwa der Kunde eines Projekts.
  • Keine zweite Y-Achse. Zwei Größen mit verschiedener Skala sind zwei Kacheln.
  • Zeilen: Vorgabe 1000, höchstens 5000; vor topN und Zusammenfassungen höchstens 5000 Gruppen aus der Datenbank. split höchstens 50 Spalten.

Die Fachobjekte eines Pakets werden von selbst zu Quellen, siehe Quellen aus Paketen. Alle anderen Quellen entstehen im Code von nexxus.

Im Regelfall macht eine Annotation eine Entität auswertbar, und zwar genau so weit, wie sie es aufzählt:

@NqlExposed(
label = "Protokoll (Änderungen)",
permission = "core.auditLog.read",
adminOnly = true,
description = "Änderungen im System: Anzahl nach Objekttyp, Vorgang, Person und Zeit.",
dimensions = [
NqlDim(field = "className", label = "Objekttyp", format = ["class"]),
NqlDim(field = "eventName", label = "Vorgang"),
NqlDim(field = "dateCreated", label = "Zeitpunkt"),
],
measures = [
NqlMeas(fn = "count", name = "anzahl", label = "Anzahl Änderungen"),
])
@Entity
open class AuditLog { ... }
  • Positivliste, absichtlich. Abgefragt werden kann nur, was unter dimensions und measures steht. Sonst wäre jedes neue Feld sofort auswertbar, beim nächsten passwordHash ein Datenleck durch Unterlassung.
  • permission ist Pflicht und derselbe Schlüssel, den die Liste des Moduls verlangt. adminOnly = true öffnet eine Quelle nur Administratoren.
  • Der Typ wird aus dem Feld gelesen. Werte bekommen Namen über valueLabels = ["OPEN=Offen", ...]; trägt ein Enum eine Eigenschaft label, gilt diese. format und template geben die Beschriftung vor, solange eine Abfrage nichts anderes sagt.
  • Der Schlüssel der Quelle ist der Klassenname mit kleinem Anfangsbuchstaben, solange key nichts anderes sagt.

Braucht eine Quelle einen Zeilenfilter unterhalb des Mandanten (nur eigene Datensätze) oder ein Merkmal aus einem anderen Modul, schreiben Sie sie als @Component, das NqlSource implementiert. Merkmale aus anderen Modulen gruppieren nach der Nummer und werden danach über einen NqlRefResolver beschriftet, mit einer Abfrage je Ergebnis:

NqlDimension.ref("kunde", "partnerId", NqlEntityLabelResolver(entityManager, Partner::class.java) { it.name }).label("Kunde")

Mandant und Löschstatus verantwortet keine Quelle selbst. Die setzt nexxus für jede Abfrage. Fehlt der Mandant, liefert eine Abfrage nichts statt alles.