Radobjekt i mssql-python

mssql-python-drivrutinen returnerar raddata som Row objekt från hämtaoperationer. Dessa objekt erbjuder flexibla åtkomstmönster:

  • Attributåtkomst (row.ColumnName) är det mest läsbara valet för namngivna kolumner. Använd den när din fråga har en känd, stabil kolumnlista.
  • Strängnyckelåtkomst (row['ColumnName']) ger lexikonliknande åtkomst till kolumner efter namn, användbart när du behöver programmatisk kolumnuppslagning eller när kolumnnamn innehåller mellanslag eller specialtecken.
  • Indexåtkomst (row[0]) är användbar för dynamiska frågor där kolumnnamn inte är kända vid utvecklingstillfället, eller vid bearbetning SELECT * av resultat.
  • Tuple unpacking (a, b, c = row) är det mest koncisa valet för loopkroppar med ett litet, fast antal kolumner.

Attributåtkomst

Åtkomst till kolumnvärden direkt efter namn:

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

Skiftlägesberoende

Åtkomst till kolumnnamn är kasuskänslig och matchar kolumnnamnen som returneras av SQL Server. Om din databas använder inkonsekvent casing, använd SQL-aliaser AS för att normalisera namn, eller aktivera modulinställningen lowercase (se 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

Kolumnaliaser

Använd SQL-alias för att skapa vänliga attributnamn:

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})")

Indexåtkomst

Åtkomstvärden efter nollbaserat kolumnindex:

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

Negativ indexering

Drivrutinen stödjer negativ indexering i Python-stil:

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)

Strängnyckelåtkomst

Åtkomst till kolumnvärden efter namn med hjälp av ordboksliknande syntax:

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')

Detta är användbart när kolumnnamn innehåller mellanslag eller specialtecken, eller när du behöver komma åt kolumner programmatiskt:

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

Skivning

Extrahera flera värden med hjälp av skivor:

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-uppackning

Packa upp radvärden direkt till variabler:

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}")

Delvis upppackning

Använd * för att fånga återstående värden:

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}")

Radlängd och iteration

Arbeta med raddimensioner och iterera genom kolumnvärden:

Få kolumnräkning

Använd len() för att få antalet kolumner i rad:

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

print(len(row))  # Number of columns

Iterera över värden

Loop genom kolumnvärden i ordning:

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

for value in row:
    print(value)

Kontrollera om kolumnen finns

Använd hasattr() för att testa förekomsten av ett kolumnnamn:

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

Konvertera till inbyggda typer

Radobjekt kan konverteras till standardtyper i Python för integration med andra bibliotek och API:er:

Konvertera till tupel

Konvertera en rad till en tuple med konstruktorn tuple() :

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

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

Konvertera till lista

Konvertera en rad till en lista med konstruktören list() :

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

Konvertera till ordlista

Konvertera en rad till en ordbok när du behöver serialisera den till JSON, skicka den till en mallmotor eller slå ihop den med annan data. Bygg ordboken från cursor.description och radvärden:

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

# Create dict from description and values
columns = [col[0] for col in cursor.description]
row_dict = dict(zip(columns, row))
print(row_dict)  # {'ProductID': 1, 'Name': 'Adjustable Race', 'ListPrice': Decimal('0.00')}

Hjälpfunktion för diktomvandling

Skapa en återanvändbar hjälpfunktion för att konvertera alla hämtade rader till ordböcker:

def rows_to_dicts(cursor):
    """Convert fetched rows to list of dictionaries."""
    columns = [col[0] for col in cursor.description]
    return [dict(zip(columns, row)) 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'])

Arbete med nullbara värden

Drivrutinen returnerar NULL-värden som PythonNone:

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}"

Arbeta med cursor.description

Åtkomst till kolumnmetadata tillsammans med raddata:

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]}")

Vanliga mönster

Här är praktiska mönster för att arbeta med radobjekt i verkliga tillämpningar:

Processrader med namngiven åtkomst

Processrader med attributåtkomst för läsbar, underhållbar kod:

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}")

Bygg objekt från rader

För applikationer med en domänmodell, mappa rader till dataklasser eller typade objekt. Denna mappning ger dig IDE-autokomplettering, typkontroll och en tydlig gräns mellan databasrader och applikationslogik.

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 till JSON

Serialisera radobjekt till JSON med anpassad typhantering för datetime och decimalvärden:

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):
    columns = [col[0] for col in cursor.description]
    rows = [dict(zip(columns, row)) 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")

Aggregera i grupper

Gruppera rader efter ett kolumnvärde och samla dem i en ordbok för analys eller visning:

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']}")