Zeitpunkte, Zahlen und Namen
Drei Regeln entscheiden, ob ein Wert so ankommt, wie er gemeint ist: Werte werden nach dem Typ ihrer
Spalte gebunden, nicht als Text; ein Zeitpunkt ohne Zone erscheint über OData mit Z; und Namen mit
Zeichen, die OData nicht erlaubt, reisen kodiert. Diese Seite erklärt alle drei an einer Stelle; die
Referenzen verweisen hierher.
Wichtige Funktionen
- Datum, Zeitpunkt, Betrag und Gleitkommawert kommen exakt an, unabhängig von der Spracheinstellung.
- Lesen und Schreiben über OData sind beim Zeitpunkt symmetrisch.
- Jeder Name, den SQL Server annimmt, ist erreichbar, solange seine kodierte Form höchstens 128 Zeichen hat.
Grundkonzepte
Werte nach dem Spaltentyp
Werte werden nach dem Spaltentyp gebunden, nicht als Text. Ein Datum im Rumpf geht als
Datum an SQL Server. Als Text deutete SQL Server es nach der Spracheinstellung der Verbindung, und
aus 2023-06-01 würde bei deutscher Einstellung der 6. Januar – dauerhaft gespeichert. Daraus folgen
diese Regeln:
- Ein Datum wird nur in ISO-Form angenommen;
01.06.2023ist mehrdeutig. - Ein Zeitpunkt mit Zonenangabe für eine Spalte ohne Zone wird auf der eigenen Route abgewiesen und über OData umgerechnet (siehe Zeitpunkte ohne Zone).
- Ein Betrag wird exakt gebunden, auch in Exponentschreibweise: Ein JSON-Wert wie
1.5E2wird für eine Dezimalspalte aus seiner Ziffernfolge gelesen und behält jede Stelle. Das gilt für bis zu 38 Stellen, so viele, wiedecimal(38, s)hält, beim Schreiben wie beim Lesen; eine längere Zahl wird für eine Dezimalspalte abgewiesen. - Ein Wahrheitswert für eine Textspalte wird abgewiesen.
- Ein Gleitkommawert kommt Bit für Bit an. Er wird aus seiner Ziffernfolge gelesen, nicht über eine Umwandlung aus
decimal, die nicht korrekt rundet.
Ein Zeitpunkt wird im Typ seiner Spalte gebunden, beim Schreiben wie im Filter: für eine
datetime2-Spalte als datetime2, für eine date-Spalte als date. Aus dem Wert allein
abgeleitet wäre jeder Zeitpunkt ein datetime – auf 1/300 Sekunde gerundet und erst ab dem
Jahr 1753 darstellbar. So trifft ein Filter auch einen gespeicherten Wert mit sieben
Nachkommastellen, und ein geschriebener Wert kommt mit allen sieben Stellen an, auch im Jahr 1.
Ein fehlender Wert für eine Binärspalte wird als Binärwert gebunden. Ohne Typ ginge null
als nvarchar an SQL Server, und SQL Server wandelt nvarchar nicht stillschweigend in binary,
varbinary oder image um – auch keinen fehlenden Wert; ein Anlegen scheiterte dann mit Fehler 257.
Access nennt nicht gesetzte Felder gar nicht; der CommandBuilder von ADO.NET nennt jede Spalte, auch
eine varbinary-Spalte mit null.
Eine Datumsangabe gehört als Zeitpunkt in den Filter, nicht als Zeichenkette. Auf der OData-Oberfläche bindet
ge 2023-06-01T00:00:00Z(Literal von OData 4) oderge datetime'2023-06-01T00:00:00'(Literal von OData 3, das Access und Power Query schreiben) einen Zeitpunkt;ge '2023-06-01'bindet Text. Für eine Spalte vom Typdatetimeodersmalldatetimedeutet SQL Server diesen Text nach der Spracheinstellung der Verbindung: Gegen eine Datenbank mit deutscher Spracheinstellung wird aus'2023-06-01'der 6. Januar – ohne Fehler, ohne Hinweis, nur mit einer anderen Zeilenmenge.
Ein Bruchwert wird exakt gebunden, nicht als Gleitkommazahl. SQL Server stellt Gleitkomma über Dezimal (Microsoft: Data type precedence): ein Gleitkomma-Parameter gegen eine
decimal- odermoney-Spalte wandelt die Spalte um – der Index darauf ist damit unbenutzbar, und verglichen wird eine Näherung des gespeicherten Werts. Zwei verschiedene Dezimalwerte können dabei auf denselben Gleitkommawert fallen:DecimalField eq 123456789012.345678trifft eine Zeile, dasselbe mit…678dtrifft zwei. Gleitkomma wird nur dort verwendet, wo das Literal es verlangt – Endungfoderd, Exponentenschreibweise, oder ein Wert außerhalb des exakten Bereichs.
Ein abgewiesenes Literal ist teurer als es aussieht. Ein Tabellenkalkulations- oder Berichtsclient meldet die
400nicht weiter – er lässt die Bedingung fallen und liest stattdessen das ganze Objekt. Aus einer gezielten Abfrage wird so ein Lesen der ganzen Tabelle, das bei großen Tabellen an das Zeitlimit stoßen kann. Die Literale des Protokolls gehören deshalb zum Vertrag des Dienstes; sie sind keine Bequemlichkeit.
Zeitpunkte ohne Zone
Ein Zeitpunkt ohne Zone erscheint mit
Z. OData v4 kennt keinen Typ für einen Zeitpunkt ohne Zone. Der Dienst schreibt die gespeicherte Wanduhrzeit und hängtZan – dasZist eine Folge des Protokolls, keine Aussage über die gespeicherte Zeit, denn die Datenbank weiß nicht, in welcher Zone ihre Werte entstanden. Passend dazu wird ein Filterwert mit Zone für eine solche Spalte in koordinierte Weltzeit umgerechnet:ge 2023-01-01T00:00:00+01:00vergleicht mit 31.12.2022, 23:00. Beide Regeln ändern sich nur gemeinsam.
Über OData wird ein Zeitpunkt mit Zone beim Schreiben in koordinierte Weltzeit umgerechnet. Die Leseseite hängt für eine Spalte ohne Zone ein
Zan, also schickt ein Client, der einen Satz liest und geändert zurückschickt, diesesZmit; es wird wieder abgenommen. Ein anderer Versatz wird ebenso umgerechnet:17:00+02:00wird als 15:00 gespeichert, also derselbe Zeitpunkt, wie ihn auch ein Filter mit Zone meint. Die eigene Route (/api/v1/cf/…) weist einen Zeitpunkt mit Zone für eine solche Spalte dagegen ab, weil sie keine solche Abmachung kennt.
Typen
| Spaltentyp | Edm-Typ | Anmerkung |
|---|---|---|
bit, tinyint, smallint, int, bigint | Boolean, Byte, Int16, Int32, Int64 | Int64 steht als JSON-Zahl; ein Client, der Zahlen als IEEE-754-Gleitkomma liest (etwa JavaScript), verliert Stellen jenseits von 2^53 |
decimal, numeric, money, smallmoney | Decimal | Genauigkeit und Nachkommastellen im Schema; als JSON-Zahl exakt geschrieben – mit derselben Einschränkung für Gleitkomma-Leser |
real, float | Single, Double | |
date, time | Date, TimeOfDay | |
datetime, datetime2, smalldatetime | DateTimeOffset | als Wanduhrzeit mit Z, siehe Zeitpunkte ohne Zone |
datetimeoffset | DateTimeOffset | mit der gespeicherten Zone |
Text- und xml-Typen | String | Länge: bei nchar/nvarchar in Zeichen, bei char/varchar in Bytes des Speichers (bei UTF-8-Sortierungen mehr als Zeichen) |
uniqueidentifier | Guid | |
| Binärtypen, Zeilenversion | Binary | |
hierarchyid, geography, geometry | Binary | die gespeicherten Bytes, dieselben, die ODBC Driver 18 liefert; ein geschriebener Wert wird als Bytes übergeben und von SQL Server zurückverwandelt |
sql_variant | String | der gespeicherte Wert als Text in der Form seines Grundtyps, wie ODBC Driver 18 ihn liefert: 42, 12.345, 2026-10-29 08:00:00.000; ein geschriebener Wert wird als Text gespeichert |
vector, json, CLR-Typen einer Anwendung | – | nicht angeboten: weder über OData noch beim Lesen ohne Feldliste oder beim Rücklesen nach dem Schreiben auf der eigenen Route |
Vergleichen und sortieren lässt sich nicht jeder Typ. SQL Server vergleicht und sortiert
text, ntext, image, xml, geography und geometry nicht. Einen sql_variant vergleicht
es nach seinem Grundtyp: Ein Wert mit der Zahl 42 ist nicht gleich dem Text '42', und der
Filter fände still nichts. Über OData weist der Dienst deshalb einen Vergleich in $filter und
eine Sortierung in $orderby über eine solche Spalte mit 400 ab und nennt die Spalte; die
Prüfung auf einen fehlenden Wert (eq null) und die Textsuche bleiben erlaubt. hierarchyid
ist nach seinen Bytes geordnet und lässt sich vergleichen und sortieren, mit einem Binärwert in
Hexadezimalschreibweise: binary'5B40'. Die eigene Route gibt einen Vergleich unverändert an
SQL Server weiter; ein Vergleich über sql_variant findet dort einen Wert nur, wenn er im
selben Grundtyp gespeichert ist.
vector (SQL Server 2025) meldet als Systemtyp varbinary, verweigert aber die Umwandlung
in varbinary(max). Der Katalog nennt ihn deshalb beim eigenen Namen: Als
varbinary geführt, würde er als Binärwert angeboten, ohne einer zu sein, und ein
Zeilenstempel über die Tabelle ließe jedes Lesen scheitern.
Namen mit Sonderzeichen
SQL Server nimmt in eckigen Klammern fast jedes Zeichen im Namen an: [F&E], [Ein/Aus],
[Prüf-Kennung], [Pos. Nr.]. OData erlaubt in Namen nur Buchstaben, Ziffern und den
Unterstrich, höchstens 128 Zeichen (SimpleIdentifier in
OData CSDL XML 4.01,
Abschnitt „SimpleIdentifier“). Der Dienst trägt solche Namen deshalb kodiert, in der
Form, die SQL Server bei FOR XML und SharePoint bei Feldnamen verwenden: Ein Zeichen, das an
seiner Stelle nicht stehen darf, wird zu _x, seinem Code in vier Hexadezimalziffern
(Großbuchstaben) und _; außerhalb der Basisebene von Unicode acht Ziffern.
| Gespeichert | Im Protokoll |
|---|---|
F&E | F_x0026_E |
Ein/Aus | Ein_x002F_Aus |
Prüf-Kennung | Prüf_x002D_Kennung |
Pos. Nr. | Pos_x002E__x0020_Nr_x002E_ |
Geräte Liste | Geräte_x0020_Liste |
1Nummer | _x0031_Nummer |
Nr# | Nr_x0023_ |
GeändertAm | GeändertAm (unverändert) |
A_x0020_B (wörtlich) | A_x005F_x0020_B |
| Regel | Warum |
|---|---|
| Unverändert bleiben Buchstaben (auch Umlaute), der Unterstrich und Ziffern ab dem zweiten Zeichen | Ein Name ohne Sonderzeichen bleibt, wie er ist |
Kodiert werden auch @, # und $ | SQL Server erlaubt sie in einem gewöhnlichen Namen, OData nicht; unkodiert machten sie das Schemadokument ungültig |
Ein Unterstrich, der wie der Anfang einer Kodierung aussieht, wird selbst kodiert (_x005F_) | Sonst trügen A B und eine Spalte, die wörtlich A_x0020_B heißt, denselben Namen |
Gilt für Schema, Tabelle, Sicht und Spalte: in $metadata, im Pfad, in $select, $filter, $orderby, im Schlüssel und im Rumpf | Ein Aufrufer nennt ein Objekt überall gleich |
Die Konfiguration nennt ein Objekt ebenso (RowStampTables, KeylessWriteTables, Obergrenzen) | Sie nennt Objekte so, wie eine Anfrage sie nennt |
Auf der eigenen Route (/api/v1/cf/…) nennt die Anfrage den kodierten Namen, die Antwort trägt weiter den gespeicherten | Die Antworten dieser Route nennen die Namen so, wie die Datenbank sie führt; siehe Darstellungen |
| Wegen des Namens ausgelassen wird nur ein Objekt oder eine Spalte, deren kodierter Name länger als 128 Zeichen wäre (daneben fehlen Spalten nicht angebotener Typen und Objekte ohne eine angebotene Spalte) | Mehr lässt ein Bezeichner nicht zu; ein langer Name aus vielen Leerzeichen kann darüber kommen |
In eine Anweisung geht weiterhin nur die Schreibweise des Katalogs: Der kodierte Name wird im
Katalog nachgeschlagen und durch den gespeicherten ersetzt, bevor er – in Klammern, mit
verdoppelter ] – in die Anweisung geschrieben wird. Der ODBC-Treiber übersetzt die Namen
für Access zurück; dort erscheint F&E wieder als F&E.
Wie ein Name sicher in die Anweisung kommt. Namen mit Umlauten und Akzenten sind zugelassen,
obwohl Datenbanken Bezeichner unter Sortierungen vergleichen, die Akzente unterschiedlich behandeln:
Sonst könnte niemand in eine Tabelle mit einer Pflichtspalte wie GeändertAm schreiben. Die
Grenze sichern zwei Schritte: Ein Name wird in dem Katalog nachgeschlagen, den der Dienst aus der
Datenbank gelesen hat, und in die Anweisung geht nur die Schreibweise des Katalogs, in Klammern
und mit verdoppelter schließender Klammer. Verglichen wird ordinal – Groß- und Kleinschreibung werden gefaltet,
Akzente nicht. Ein kodierter Name wird dabei gegen die kodierten Namen des Katalogs
verglichen, nie gegen die gespeicherten: Sonst könnte eine Spalte, die wörtlich
F_x0026_E heißt, mit F&E verwechselt werden.