Zeilenobjekte in mssql-python

Der mssql-python-Treiber liefert Zeilendaten als Row Objekte aus Abrufoperationen zurück. Diese Objekte bieten flexible Zugriffsmuster:

  • Attributzugriff (row.ColumnName) ist die lesbarste Wahl für benannte Spalten. Verwenden Sie es, wenn Ihre Abfrage eine bekannte, stabile Spaltenliste hat.
  • String-Key-Zugriff (row['ColumnName']) bietet einen wörterbuchähnlichen Zugriff auf Spalten nach Namen, was nützlich ist, wenn Sie programmatische Spaltensuche benötigen oder wenn Spaltennamen Leerzeichen oder Sonderzeichen enthalten.
  • Indexzugriff (row[0]) ist nützlich für dynamische Abfragen, bei denen Spaltennamen zur Entwicklungszeit nicht bekannt sind, oder bei der Verarbeitung SELECT * von Ergebnissen.
  • Tuple-Unpacking (a, b, c = row) ist die prägnanteste Wahl für Schleifenkörper mit einer kleinen, festen Anzahl von Spalten.
  • Mapping access (row._mapping) liefert eine schreibgeschützte Abbildung von Spaltennamen auf Werte. Nutze es, um eine Zeile in ein Wörterbuch umzuwandeln oder um die Zeile an Code zu übergeben, der eine Zuordnung erwartet.

Attributzugriff

Zugriff auf Spaltenwerte direkt nach Namen:

import mssql_python

conn = mssql_python.connect(connection_string)
cursor = conn.cursor()

cursor.execute("SELECT ProductID, Name, ListPrice FROM Production.Product WHERE ProductID = 1")
row = cursor.fetchone()

print(row.ProductID)     # 1
print(row.Name)          # 'Adjustable Race'
print(row.ListPrice)     # 0.00

Groß- und Kleinschreibung

Der Zugriff auf den Spaltennamen ist groß- und schreibungsabhängig und entspricht den von SQL Server zurückgegebenen Spaltennamen. Wenn Ihre Datenbank inkonsistente Gehäuse verwendet, verwenden Sie SQL-Aliase AS zur Normalisierung von Namen oder aktivieren Sie die lowercase Moduleinstellung (siehe Modulkonfiguration):

cursor.execute("SELECT FirstName, LastName, EmailPromotion FROM Person.Person WHERE BusinessEntityID < 10")
row = cursor.fetchone()

print(row.FirstName)      # Works
print(row.LastName)       # Works
print(row.EmailPromotion) # Works
print(row.firstname)      # AttributeError - wrong case

Spaltenaliase

Verwenden Sie SQL-Alias, um freundliche Attributnamen zu erstellen:

cursor.execute("""
    SELECT 
        p.ProductID,
        p.Name,
        c.Name AS Category
    FROM Production.Product p
    JOIN Production.ProductSubcategory c ON p.ProductSubcategoryID = c.ProductSubcategoryID
    WHERE p.ProductSubcategoryID IS NOT NULL
""")

for row in cursor:
    print(f"{row.Name} ({row.Category})")

Indexzugriff

Zugriffswerte nach nullbasiertem Spaltenindex:

cursor.execute("SELECT ProductID, Name, ListPrice FROM Production.Product WHERE ProductID < 5")
row = cursor.fetchone()

print(row[0])  # ProductID
print(row[1])  # Name
print(row[2])  # ListPrice

Negative Indexierung

Der Treiber unterstützt Python-ähnliche negative Indizierung:

cursor.execute("""
    SELECT Demo.A, Demo.B, Demo.C, Demo.D
    FROM (VALUES (1, 2, 3, 4)) AS Demo(A, B, C, D)
""")
row = cursor.fetchone()

print(row[-1])  # Last column (D)
print(row[-2])  # Second to last (C)

String-Key-Zugang

Greifen Sie auf Spaltenwerte nach Namen mit einer ordbuchähnlichen Syntax zu:

cursor.execute("SELECT ProductID, Name, ListPrice FROM Production.Product WHERE ProductID = 1")
row = cursor.fetchone()

print(row['ProductID'])   # 1
print(row['Name'])        # 'Adjustable Race'
print(row['ListPrice'])   # Decimal('0.00')

Dies ist nützlich, wenn Spaltennamen Leerzeichen oder Sonderzeichen enthalten oder wenn Sie programmatisch auf Spalten zugriffen müssen:

column_name = 'ListPrice'
value = row[column_name]  # Programmatic column access

Aufteilen

Mehrere Werte mit Slices extrahieren:

cursor.execute("""
    SELECT Demo.A, Demo.B, Demo.C, Demo.D, Demo.E
    FROM (VALUES (1, 2, 3, 4, 5)) AS Demo(A, B, C, D, E)
""")
row = cursor.fetchone()

print(row[1:4])    # Columns B, C, D (indices 1, 2, 3)
print(row[:2])     # First two columns (A, B)
print(row[2:])     # From C to end

Tuple-Auspacken

Entpacken Sie Zeilenwerte direkt in Variablen:

cursor.execute("SELECT ProductID, Name, ListPrice FROM Production.Product WHERE ProductID < 10")

for product_id, name, price in cursor:
    print(f"#{product_id}: {name} - ${price}")

Teilteiliges Auspacken

Verwenden * Sie diese zur Erfassung der verbleibenden Werte:

cursor.execute("""
    SELECT TOP 2 ProductID, Name, ProductNumber, Color, Size, Weight
    FROM Production.Product
    WHERE ProductNumber IS NOT NULL
    ORDER BY ProductID
""")

for product_id, name, *rest in cursor:
    print(f"{product_id}: {name}, extra columns: {rest}")

Zeilenlänge und Iteration

Arbeiten Sie mit Zeilendimensionen und iterieren Sie durch Spaltenwerte:

Erhalten Sie die Kolonnenzahl

Verwenden len() Sie, um die Anzahl der Spalten in einer Reihe zu erhalten:

cursor.execute("SELECT * FROM Production.Product WHERE ProductID < 5")
row = cursor.fetchone()

print(len(row))  # Number of columns

Iterieren Sie über Werte

Durchlaufe die Spaltenwerte in der Reihenfolge:

cursor.execute("SELECT ProductID, Name, ListPrice FROM Production.Product WHERE ProductID < 5")
row = cursor.fetchone()

for value in row:
    print(value)

Überprüfen Sie, ob eine Spalte existiert

Verwenden hasattr() Sie, um auf das Vorhandensein eines Spaltennamens zu testen:

# Use hasattr to check for column name
if hasattr(row, 'DiscountPrice'):
    print(f"Discount: {row.DiscountPrice}")
else:
    print("No discount available")

Konvertieren auf integrierte Typen

Zeilenobjekte können in Standard-Python-Typen konvertiert werden, um mit anderen Bibliotheken und APIs zu integrieren:

Konvertiere in Tupel

Konvertiere eine Zeile mit dem tuple() Konstruktor in ein Tupel:

cursor.execute("SELECT ProductID, Name FROM Production.Product WHERE ProductID = 1")
row = cursor.fetchone()

row_tuple = tuple(row)
print(row_tuple)  # (1, 'Adjustable Race')

Zur Liste konvertieren

Konvertiere eine Zeile mit dem list() Konstruktor in eine Liste:

row_list = list(row)
print(row_list)  # [1, 'Adjustable Race']

Zu einem Wörterbuch konvertieren

Konvertiere eine Zeile in ein Wörterbuch, wenn du sie in JSON serialisieren musst, übergebe sie an eine Template-Engine oder verschmelze sie mit anderen Daten. Die Eigenschaft row._mapping gibt eine schreibgeschützte Abbildung von Spaltennamen auf Werte in ergebnisgesetzter Spaltenreihenfolge zurück. Gib es weiter, dict() um ein normales Wörterbuch zu bekommen:

cursor.execute("SELECT ProductID, Name, ListPrice FROM Production.Product WHERE ProductID = 1")
row = cursor.fetchone()

row_dict = dict(row._mapping)
print(row_dict)  # {'ProductID': 1, 'Name': 'Adjustable Race', 'ListPrice': Decimal('0.0000')}

Die Eigenschaft _mapping weist folgende Verhaltensweisen auf:

  • Die Zuordnung ist schreibgeschützt. Das Zuweisen einer Taste erhöht TypeError. Konvertiere sie zuerst mit Option dict() , wenn du die Werte ändern musst.
  • Schlüssel sind die Spaltennamen aus .cursor.description Wichtige Nachschlage sind groß- und kleinschreibungssensitiv. Wenn du die Moduleinstellung lowercase aktivierst, sind die Tasten Kleinbuchstaben, und die Zuordnung passt nur zu diesen Kleinbuchstaben. Attribut- und String-Tastenzugriff auf der Zeile selbst werden bei aktiviertem Schalten nicht groß- und kleinschreibungssensitiv lowercase , aber row._mapping Lookups nicht.
  • Wenn die Ergebnismenge doppelte Spaltennamen enthält, behält die Abbildung den letzten Wert für jeden Namen. Verwenden Sie Transact-SQL (T-SQL)- AS Alias, um jeder Spalte einen eindeutigen Namen zu geben, oder lesen Sie die Werte nach Index.
  • Die Eigenschaft hat Vorrang gegenüber einer Ergebnisspalte namens _mapping, sodass row._mapping die Abbildung immer zurückgegeben wird. Lies diese Kolumne mit row['_mapping'].

Helferfunktion für die Diktierumwandlung

Erstelle eine wiederverwendbare Hilfsfunktion, um alle abgerufenen Zeilen in Wörterbücher umzuwandeln:

def rows_to_dicts(cursor):
    """Convert fetched rows to list of dictionaries."""
    return [dict(row._mapping) for row in cursor.fetchall()]

cursor.execute("SELECT ProductID, Name, ListPrice FROM Production.Product WHERE ProductID < 10")
products = rows_to_dicts(cursor)
for p in products:
    print(p['Name'])

Arbeit mit nullbaren Werten

Der Treiber gibt NULL-Werte als Python Nonezurück:

cursor.execute("SELECT FirstName, MiddleName, LastName FROM Person.Person WHERE BusinessEntityID = 1")
row = cursor.fetchone()

if row.MiddleName is None:
    full_name = f"{row.FirstName} {row.LastName}"
else:
    full_name = f"{row.FirstName} {row.MiddleName} {row.LastName}"

Arbeit mit cursor.description

Greifen Sie auf Spaltenmetadaten neben Zeilendaten zu:

cursor.execute("SELECT ProductID, Name, ListPrice FROM Production.Product WHERE ProductID < 5")

# Column information
for col in cursor.description:
    print(f"Name: {col[0]}, Type: {col[1]}")

# Fetch with metadata
row = cursor.fetchone()
for i, col in enumerate(cursor.description):
    print(f"{col[0]}: {row[i]}")

Allgemeine Muster

Hier sind praktische Muster für die Arbeit mit Zeilenobjekten in realen Anwendungen:

Prozesszeilen mit benanntem Zugriff

Prozesszeilen mit Attributzugriff für lesbaren, wartbaren Code:

def process_orders(conn):
    cursor = conn.cursor()
    cursor.execute("""
        SELECT SalesOrderID, CustomerID, OrderDate, TotalDue 
        FROM Sales.SalesOrderHeader 
        WHERE Status = 5
    """)
    
    for order in cursor:
        print(f"Order #{order.SalesOrderID}")
        print(f"  Customer: {order.CustomerID}")
        print(f"  Date: {order.OrderDate}")
        print(f"  Total: ${order.TotalDue:.2f}")

Baue Objekte aus Zeilen

Für Anwendungen mit einem Domänenmodell werden Zeilen auf Datenklassen oder typisierte Objekte abgebildet. Dieses Mapping bietet Ihnen IDE-Autovervollständigung, Typprüfung und eine klare Grenze zwischen Datenbankzeilen und Anwendungslogik.

from dataclasses import dataclass
from datetime import date
from decimal import Decimal

@dataclass
class Product:
    id: int
    name: str
    price: Decimal
    created: date

def get_products(conn) -> list[Product]:
    cursor = conn.cursor()
    cursor.execute("SELECT ProductID, Name, ListPrice, SellStartDate FROM Production.Product WHERE ProductID < 10")
    
    return [
        Product(
            id=row.ProductID,
            name=row.Name,
            price=row.ListPrice,
            created=row.SellStartDate
        )
        for row in cursor
    ]

Export zu JSON

Serialisiere Zeilenobjekte zu JSON mit individueller Typbehandlung für Datetime- und Dezimalwerte:

import json
from datetime import date, datetime
from decimal import Decimal

def json_serializer(obj):
    """Custom serializer for non-JSON types."""
    if isinstance(obj, (date, datetime)):
        return obj.isoformat()
    if isinstance(obj, Decimal):
        return float(obj)
    raise TypeError(f"Type {type(obj)} not serializable")

def export_to_json(cursor, filename):
    rows = [dict(row._mapping) for row in cursor.fetchall()]
    
    with open(filename, 'w') as f:
        json.dump(rows, f, default=json_serializer, indent=2)

cursor.execute("SELECT ProductID, Name, ListPrice FROM Production.Product WHERE ProductID < 10")
export_to_json(cursor, "products.json")

Aggregieren in Gruppen

Gruppieren Sie Zeilen nach einem Spaltenwert und sammeln Sie sie in einem Wörterbuch zur Analyse oder Darstellung:

from collections import defaultdict

cursor.execute("""
    SELECT c.Name AS CategoryName, p.Name AS ProductName, p.ListPrice 
    FROM Production.Product p
    JOIN Production.ProductSubcategory c ON p.ProductSubcategoryID = c.ProductSubcategoryID
    WHERE p.ProductSubcategoryID IS NOT NULL
    ORDER BY c.Name
""")

products_by_category = defaultdict(list)
for row in cursor:
    products_by_category[row.CategoryName].append({
        'name': row.ProductName,
        'price': row.ListPrice
    })

for category, products in products_by_category.items():
    print(f"\n{category}:")
    for p in products:
        print(f"  - {p['name']}: ${p['price']}")