= select()
:description: Führt ein parametrisiertes SQL-Statement via ado.ExecuteSQL aus. Gibt eine Liste von Dicts zurück. Unterstützt %s-Platzhalter für sichere Parameterbindung.
:keywords: select, ado.ExecuteSQL, SQL, Platzhalter, Parameterbindung, Dict, SQL-Injection
:page-tags: api, db

Sendet ein SQL-Statement über `ado.ExecuteSQL` an den Server und gibt das geparste
ADO Recordset XML als `ECMDbResult` zurück. Positionsparameter werden sicher in die
Abfrage eingebunden — niemals String-Formatierung für Benutzereingaben verwenden.

NOTE: Ohne zusätzliche Serverkonfiguration werden nur SELECT-Abfragen unterstützt.
Datenmanipulationsoperationen (INSERT, UPDATE, DELETE) müssen über eine Registry-Einstellung
am Applikationsserver explizit aktiviert werden.

== Signatur

[tabs]
====
Sync::
+
[source,python]
----
ecm.db.select(sql: str, *params: int | str | float, flags: int = 0) -> ECMDbResult
----

Async::
+
[source,python]
----
await ecm.db.select(sql: str, *params: int | str | float, flags: int = 0) -> ECMDbResult
----
====

== Parameter

[cols="1,1,1,3"]
|===
|Parameter |Typ |Standard |Beschreibung

|`sql`
|`str`
|—
|SQL-Template-String mit optionalen Platzhaltern.

|`*params`
|`int \| str \| float`
|—
|Werte, die der Reihe nach in die Platzhalter eingesetzt werden. Die Anzahl muss mit der Anzahl der Nicht-`%%`-Platzhalter übereinstimmen.

|`flags`
|`int`
|`0`
|Bitmaske für `Flags` von `ado.ExecuteSQL`. `0` = automatisches `N`-Präfix für String-Literale auf Unicode-Servern (Standard). `2` = kein automatisches Präfixing.
|===

== Platzhalter

[cols="1,1,3"]
|===
|Platzhalter |Typ |Verhalten

|`%s`
|String
|Anführungszeichen werden escaped (`'` → `''`); Wert wird in `'...'` eingeschlossen.

|`%w`
|Word-String
|Wie `%s`, aber zusätzlich werden Leerzeichen und SQL-Kommentarzeichen (`--`, `/*`, `*/`) abgelehnt.

|`%d`
|Integer
|Wert wird auf `int` gecastet; nicht-numerische Werte werfen `ValueError`.

|`%f`
|Float
|Wert wird auf `float` gecastet; nicht-numerische Werte werfen `ValueError`.

|`%u`
|Unquoted Identifier
|Nur Buchstaben, Ziffern, `_`, `@`, `$`, `#` sind erlaubt. Wert wird ohne Anführungszeichen eingefügt. Für Tabellen- oder Spaltennamen.

|`%%`
|Literal `%`
|Kein Parameter wird konsumiert.
|===

== Rückgabewert

Eine `ECMDbResult`-Instanz mit Spalten und Zeilen.

=== ECMDbResult

[cols="1,1,3"]
|===
|Attribut / Methode |Typ |Beschreibung

|`.columns`
|`list[ECMDbColumn]`
|Geordnete Liste von Spaltendefinitionen, sortiert nach 1-basiertem Spaltenindex.

|`.rows`
|`list[ECMDbRow]`
|Liste von Ergebniszeilen, eine pro `<z:row>`-Element.

|`len(result)`
|`int`
|Anzahl der Zeilen.

|`for row in result`
|—
|Iteriert direkt über `.rows`.
|===

=== Felder von ECMDbColumn

[cols="1,1,3"]
|===
|Feld |Typ |Beschreibung

|`name`
|`str`
|Spaltenname wie vom Server zurückgegeben.

|`number`
|`int`
|1-basierter Spaltenindex.

|`data_type`
|`str`
|ADO-Datentypstring, z. B. `"string"`, `"int"`, `"dateTime"`.

|`max_length`
|`int \| None`
|Maximale Bytelänge laut Schema oder `None`, wenn nicht angegeben.

|`nullable`
|`bool`
|Gibt an, ob die Spalte `NULL`-Werte erlaubt.

|`base_table`
|`str \| None`
|Quelltabellenname oder `None`, wenn nicht im Schema angegeben.

|`base_column`
|`str \| None`
|Quellspaltenname oder `None`, wenn nicht im Schema angegeben.
|===

=== Zugriff auf ECMDbRow

[cols="1,1,3"]
|===
|Methode / Syntax |Rückgabetyp |Beschreibung

|`row["spaltenname"]`
|`str \| None`
|Raw-String-Wert oder `None` bei `NULL`.

|`row.get("spaltenname")`
|`str \| None`
|Wie `__getitem__` — Raw-String oder `None` bei fehlendem / `NULL`-Wert.

|`row.typed("spaltenname", str)`
|`str \| None`
|Wert in den angegebenen Typ konvertiert. Unterstützt: `str`, `int`, `float`, `bool`, `datetime`.
|===

`NULL`-Werte werden immer als `None` zurückgegeben, unabhängig von der Zugriffsmethode.

== Beispiele

=== Einfache Abfrage

[tabs]
====
Sync::
+
[source,python]
----
result = ecm.db.select("SELECT benutzer, osemail FROM benutzer")
print(f"{len(result)} Zeilen")
for row in result:
    print(row["benutzer"], row["osemail"])
----

Async::
+
[source,python]
----
result = await ecm.db.select("SELECT benutzer, osemail FROM benutzer")
print(f"{len(result)} Zeilen")
for row in result:
    print(row["benutzer"], row["osemail"])
----
====

=== Abfrage mit Parametern

[tabs]
====
Sync::
+
[source,python]
----
result = ecm.db.select(
    "SELECT id, benutzer FROM benutzer WHERE benutzer = %s AND aktiv = %d",
    "john",
    1,
)
for row in result:
    print(row["id"], row["benutzer"])
----

Async::
+
[source,python]
----
result = await ecm.db.select(
    "SELECT id, benutzer FROM benutzer WHERE benutzer = %s AND aktiv = %d",
    "john",
    1,
)
for row in result:
    print(row["id"], row["benutzer"])
----
====

=== Typisierter Wertzugriff

[tabs]
====
Sync::
+
[source,python]
----
result = ecm.db.select("SELECT benutzer, aktiv, logincount FROM benutzer")
for row in result:
    username: str | None = row.typed("benutzer", str)
    active: bool | None = row.typed("aktiv", bool)
    login_count: int | None = row.typed("logincount", int)
    print(username, active, login_count)
----

Async::
+
[source,python]
----
result = await ecm.db.select("SELECT benutzer, aktiv, logincount FROM benutzer")
for row in result:
    username: str | None = row.typed("benutzer", str)
    active: bool | None = row.typed("aktiv", bool)
    login_count: int | None = row.typed("logincount", int)
    print(username, active, login_count)
----
====

=== Dynamischer Tabellenname

[tabs]
====
Sync::
+
[source,python]
----
# %u fügt einen Identifier ohne Anführungszeichen ein — nur sichere Zeichen erlaubt
table = "benutzer"
result = ecm.db.select("SELECT * FROM %u WHERE id = %d", table, 42)
----

Async::
+
[source,python]
----
table = "benutzer"
result = await ecm.db.select("SELECT * FROM %u WHERE id = %d", table, 42)
----
====

=== Spalten-Metadaten auslesen

[tabs]
====
Sync::
+
[source,python]
----
result = ecm.db.select("SELECT * FROM benutzer WHERE 1 = 0")
for col in result.columns:
    print(col.name, col.data_type, col.max_length, col.nullable)
----

Async::
+
[source,python]
----
result = await ecm.db.select("SELECT * FROM benutzer WHERE 1 = 0")
for col in result.columns:
    print(col.name, col.data_type, col.max_length, col.nullable)
----
====

== Siehe auch

* xref:api/db/index.adoc[ecm.db] — Namespace-Übersicht mit SQL-Injection-Schutzhinweisen
