Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Der mssql-python-Treiber ordnet die SQL Server-Datentypen decimal, numeric und money dem Python-decimal.Decimal zu, um präzise Finanz- und wissenschaftliche Berechnungen zu ermöglichen. SQL Server bietet die folgenden präzisen numerischen Typen:
| SQL-Typ | Python-Typ | Präzision | Anwendungsfall |
|---|---|---|---|
| decimal(p,s) | decimal.Decimal |
1–38 Ziffern | Exakte Berechnungen |
| numeric(p,s) | decimal.Decimal |
1–38 Ziffern | Dasselbe wie Dezimalmal. |
| money | decimal.Decimal |
19 Ziffern, 4 Dezimalstellen | Währungen |
| smallmoney | decimal.Decimal |
10 Ziffern, 4 Dezimalstellen | Kleinwährung |
| float | float |
~15 Ziffern | Ungefähr |
| real | float |
~7 Ziffern | Ungefähr |
Python-Dezimaltyp
Dezimalwerte empfangen
Dezimalspalten liefern Python-Objekte decimal.Decimal zurück:
from decimal import Decimal
import mssql_python
conn = mssql_python.connect(connection_string)
cursor = conn.cursor()
cursor.execute(
"SELECT SubTotal, TaxAmt, TotalDue "
"FROM Sales.SalesOrderHeader WHERE SalesOrderID = 43659"
)
row = cursor.fetchone()
print(type(row.SubTotal)) # <class 'decimal.Decimal'>
print(row.SubTotal) # Decimal('20565.6206')
print(row.TaxAmt) # Decimal('1971.5149')
print(row.TotalDue) # Decimal('23153.2339')
Senden Sie Dezimalwerte
Übergeben Sie Decimal Objekte für präzises Einfügen:
from decimal import Decimal
cursor.execute("""
CREATE TABLE #ProductDemo (
Name NVARCHAR(50),
Price DECIMAL(10,2),
Cost DECIMAL(10,2)
)
""")
cursor.execute("""
INSERT INTO #ProductDemo (Name, Price, Cost)
VALUES (%(name)s, %(price)s, %(cost)s)
""", {
"name": "Widget",
"price": Decimal("199.99"),
"cost": Decimal("87.50")
})
conn.commit()
Vermeiden Sie Float für Finanzdaten
Float-Typen haben Präzisionsprobleme, die sie für Finanzberechnungen ungeeignet machen. Verwenden Sie für präzise Ergebnisse immer Decimal:
from decimal import Decimal
# Bad: float has precision issues
price = 0.1 + 0.2 # 0.30000000000000004
# Good: Decimal is exact
price = Decimal("0.1") + Decimal("0.2") # Decimal('0.3')
# Use string constructor for literal values
correct = Decimal("19.99") # Exact
avoid = Decimal(19.99) # Might introduce float imprecision
Geldtyp
Arbeiten mit Währungsspalten
Abrufen und aktualisieren Sie die Geldspalten von SQL Server mit Python Decimal-Objekten:
from decimal import Decimal
# Create and populate a temp table with a money column
cursor.execute("""
CREATE TABLE #Accounts (
ID INT,
AccountID NVARCHAR(20),
Balance MONEY
)
""")
cursor.execute(
"INSERT INTO #Accounts (ID, AccountID, Balance) VALUES (1, %(acct)s, %(bal)s)",
{"acct": "ACCT-001", "bal": Decimal("1234.5678")}
)
conn.commit()
cursor.execute("SELECT AccountID, Balance FROM #Accounts WHERE ID = 1")
row = cursor.fetchone()
print(row.Balance) # Decimal('1234.5678')
print(type(row.Balance)) # <class 'decimal.Decimal'>
# Money arithmetic
cursor.execute("""
UPDATE #Accounts
SET Balance = Balance + %(amount)s
WHERE ID = %(id)s
""", {"amount": Decimal("100.00"), "id": 1})
conn.commit()
Geldformatierung
Formatiere Dezimalwerte als Währungsstrings mit Symbolen und Kommatrennern:
from decimal import Decimal
def format_currency(value: Decimal, symbol: str = "$") -> str:
"""Format decimal as currency string."""
return f"{symbol}{value:,.2f}"
cursor.execute("SELECT TotalDue FROM Sales.SalesOrderHeader")
for row in cursor:
print(format_currency(row.TotalDue)) # $1,234.56
Dezimale Präzision und Skala
Präzision und Maßstab verstehen
- Präzision (p): Gesamtzahl der Ziffern
- Skala(n): Anzahl der Ziffern nach Dezimalstellen
Zum Beispiel decimal(10, 2) kann Werte von -999999999,99 bis 999999999,99 speichern, während decimal(5, 4) Werte von -9,9999 bis 9,9999 gespeichert werden können.
Spezifizieren Sie die Genauigkeit in den Parametern
Der mssql-python-Treiber leitet automatisch Präzision und Skalierung aus Dezimalwerten ab:
from decimal import Decimal
# Create a temp table for the measurement value
cursor.execute("CREATE TABLE #Measurements (Value DECIMAL(18,6))")
# The driver infers precision from the Decimal value
value = Decimal("123.456789")
cursor.execute("INSERT INTO #Measurements (Value) VALUES (%(v)s)", {"v": value})
conn.commit()
Kontrollrundung
Verwenden Sie die Methode quantize() , um Dezimalwerte auf eine bestimmte Skala abzurunden:
from decimal import Decimal, ROUND_HALF_UP, ROUND_DOWN
price = Decimal("19.999")
# Round to 2 decimal places
rounded = price.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
print(rounded) # Decimal('20.00')
# Round down (truncate)
truncated = price.quantize(Decimal("0.01"), rounding=ROUND_DOWN)
print(truncated) # Decimal('19.99')
Finanzielle Berechnungen
Sichere Arithmetik
Führen Sie finanzielle Berechnungen mit Dezimalform mit korrekter Rundung um:
from decimal import Decimal, ROUND_HALF_UP
def calculate_total(price: Decimal, quantity: int, tax_rate: Decimal) -> Decimal:
"""Calculate order total with tax."""
subtotal = price * quantity
tax = (subtotal * tax_rate).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
return subtotal + tax
cursor.execute("SELECT ListPrice FROM Production.Product WHERE ProductID = 1")
price = cursor.fetchval()
total = calculate_total(
price=price,
quantity=3,
tax_rate=Decimal("0.0825") # 8.25% tax
)
cursor.execute("""
CREATE TABLE #OrderCalc (
ProductID INT, Quantity INT, Total DECIMAL(10,2), Tax DECIMAL(10,2)
)
""")
cursor.execute("""
INSERT INTO #OrderCalc (ProductID, Quantity, Total, Tax)
VALUES (%(prod)s, %(qty)s, %(total)s, %(tax)s)
""", {"prod": 1, "qty": 3, "total": total, "tax": total * Decimal("0.0825")})
Prozentberechnungen
Berechnen Sie prozentuale Rabatte und Anpassungen:
from decimal import Decimal
def calculate_discount(price: Decimal, discount_percent: Decimal) -> Decimal:
"""Calculate discounted price."""
discount = (price * discount_percent / 100).quantize(Decimal("0.01"))
return price - discount
original = Decimal("99.99")
discounted = calculate_discount(original, Decimal("15")) # 15% off
print(f"Original: ${original}, After discount: ${discounted}")
Aufteilungsbeträge
Einen Gesamtbetrag gleichmäßig auf mehrere Beteiligte verteilen und Restbeträge berücksichtigen:
from decimal import Decimal, ROUND_DOWN
def split_amount(total: Decimal, ways: int) -> list[Decimal]:
"""Split amount evenly, handling remainder."""
per_person = (total / ways).quantize(Decimal("0.01"), rounding=ROUND_DOWN)
remainder = total - (per_person * ways)
amounts = [per_person] * ways
# Add remainder to first person
amounts[0] += remainder
return amounts
total = Decimal("100.00")
split = split_amount(total, 3)
print(split) # [Decimal('33.34'), Decimal('33.33'), Decimal('33.33')]
print(sum(split)) # Decimal('100.00') - always equals original
Aggregationsvorgänge
SUM und AVG mit Dezimalzahlen
SQL Server-Aggregationsfunktionen geben Dezimaltypen für Genauigkeit zurück:
# SUM preserves decimal type
cursor.execute("SELECT SUM(TotalDue) AS TotalSales FROM Sales.SalesOrderHeader")
total_sales = cursor.fetchval()
print(type(total_sales)) # <class 'decimal.Decimal'>
# AVG might return more decimal places
cursor.execute("SELECT AVG(ListPrice) AS AvgPrice FROM Production.Product")
avg_price = cursor.fetchval()
# Round to desired precision
avg_price = avg_price.quantize(Decimal("0.01"))
NULL bei Aggregationen behandeln
SQL Server-Aggregatfunktionen können None zurückgeben, wenn keine Zeilen der Abfrage entsprechen:
cursor.execute("SELECT SUM(TotalDue) FROM Sales.SalesOrderHeader WHERE CustomerID = 0")
total_discount = cursor.fetchval()
# SUM returns NULL if no rows match
if total_discount is None:
total_discount = Decimal("0.00")
Ausgabekonverter für Dezimalzahlen
Ausgabekonverter verwenden den in der Definition angegebenen cursor.description Python-Typ als Schlüssel, nicht die ODBC-SQL-Ganzzahl. Registrieren Sie den Konverter mit decimal.Decimal, damit er für decimal-, numeric-, money- und smallmoney-Spalten funktioniert, die alle auf decimal.Decimal abgebildet werden.
Konvertiere in Float (wenn Präzision nicht kritisch ist)
Zur Kompatibilität mit Bibliotheken, die Float-Werte erwarten, definieren Sie einen Ausgangswandler:
import mssql_python
from decimal import Decimal
def decimal_to_float(value):
"""Convert Decimal to float."""
if value is None:
return None
return float(value) # value is already a Decimal object
conn = mssql_python.connect(connection_string)
# Convert decimals to float for compatibility with libraries that expect float
conn.add_output_converter(Decimal, decimal_to_float)
cursor = conn.cursor()
cursor.execute("SELECT ListPrice FROM Production.Product WHERE ProductID = 1")
row = cursor.fetchone()
print(type(row.ListPrice)) # <class 'float'>
Benutzerdefinierter Geldformatierungs-Konverter
Definieren Sie einen benutzerdefinierten Ausgabewandler, der Dezimalwerte automatisch als Währungsstrings formatiert:
from decimal import Decimal
def money_to_string(value):
"""Convert Decimal to formatted string."""
if value is None:
return None
return f"${value:,.2f}" # value is already a Decimal object
conn.add_output_converter(Decimal, money_to_string)
cursor = conn.cursor()
cursor.execute("CREATE TABLE #Accounts (Balance DECIMAL(19,4))")
cursor.execute(
"INSERT INTO #Accounts (Balance) VALUES (%(bal)s)",
{"bal": Decimal("1234.56")}
)
conn.commit()
cursor.execute("SELECT Balance FROM #Accounts")
for row in cursor:
print(row.Balance) # "$1,234.56"
Allgemeine Muster
Währungskonvertierung
Umrechnen Sie Geldbeträge zwischen Währungen mithilfe von Wechselkursen:
from decimal import Decimal
def convert_currency(amount: Decimal, rate: Decimal) -> Decimal:
"""Convert currency at given exchange rate."""
return (amount * rate).quantize(Decimal("0.01"))
usd_amount = Decimal("100.00")
eur_rate = Decimal("0.92")
eur_amount = convert_currency(usd_amount, eur_rate)
print(f"${usd_amount} = €{eur_amount}")
Kontostandaktualisierungen mit Transaktionen
Verwenden Sie Transaktionen mit geeigneten Lock-Hinweisen, um Atomfonds-Überweisungen sicherzustellen und verlorene Updates zu vermeiden:
from decimal import Decimal
def transfer_funds(conn, from_account: int, to_account: int, amount: Decimal):
"""Transfer funds between accounts atomically."""
cursor = conn.cursor()
conn.autocommit = False
try:
balances = {}
for account_id in sorted((from_account, to_account)):
cursor.execute(
"SELECT Balance FROM #Accounts WITH (UPDLOCK) WHERE ID = %(id)s",
{"id": account_id}
)
row = cursor.fetchone()
if row is None:
raise ValueError(f"Account {account_id} not found")
balances[account_id] = row.Balance
if balances[from_account] < amount:
raise ValueError("Insufficient funds")
# Debit source
cursor.execute("""
UPDATE #Accounts SET Balance = Balance - %(amount)s
WHERE ID = %(id)s
""", {"amount": amount, "id": from_account})
# Credit destination
cursor.execute("""
UPDATE #Accounts SET Balance = Balance + %(amount)s
WHERE ID = %(id)s
""", {"amount": amount, "id": to_account})
conn.commit()
except Exception:
conn.rollback()
raise
finally:
conn.autocommit = True
# Set up demo accounts
cursor = conn.cursor()
cursor.execute("CREATE TABLE #Accounts (ID INT, Balance MONEY)")
cursor.executemany(
"INSERT INTO #Accounts (ID, Balance) VALUES (%(id)s, %(bal)s)",
[{"id": 1, "bal": Decimal("1000.00")}, {"id": 2, "bal": Decimal("500.00")}]
)
conn.commit()
# Usage
transfer_funds(conn, from_account=1, to_account=2, amount=Decimal("500.00"))
Die Überweisung sperrt beide Zeilen von vornherein, beginnend mit der niedrigsten ID, indem für jedes Konto in sortierter Reihenfolge ein separates SELECT ... WITH (UPDLOCK) ausgeführt wird.
UPDLOCK verhindert das verlorene Aktualisierungsmuster, bei dem zwei Transaktionen denselben Saldo lesen und Updates basierend auf veralteten Daten anwenden. Die Ausgabe der Sperren als separate Auszüge in sortierter Reihenfolge garantiert die Erwerbsreihenfolge und verhindert, dass gegensätzliche Überweisungen zwischen denselben beiden Konten Sperren in umgekehrter Reihenfolge erwerben und Deadlocks entstehen.
Masseneinfügung mit Dezimalzahlen
Fügen Sie mehrere Zeilen mit Dezimalwerten ein:executemany()
from decimal import Decimal
cursor.execute("""
CREATE TABLE #BulkProducts (
Name NVARCHAR(50),
Price DECIMAL(10,2),
Cost DECIMAL(10,2)
)
""")
products = [
{"name": "Widget A", "price": Decimal("19.99"), "cost": Decimal("8.50")},
{"name": "Widget B", "price": Decimal("29.99"), "cost": Decimal("12.75")},
{"name": "Widget C", "price": Decimal("39.99"), "cost": Decimal("18.00")},
]
cursor.executemany("""
INSERT INTO #BulkProducts (Name, Price, Cost)
VALUES (%(name)s, %(price)s, %(cost)s)
""", products)
conn.commit()
Bewährte Methoden
Verwenden Sie immer Dezimalstellen für Finanzmathematik
Verwenden Sie niemals Float für Finanzberechnungen; Verwenden Sie immer die Dezimalschrift zur Präzision:
# Wrong: float precision issues
price = 0.10
quantity = 3
total = price * quantity # 0.30000000000000004
# Correct: Decimal is exact
price = Decimal("0.10")
quantity = 3
total = price * quantity # Decimal('0.30')
Verwenden Sie Stringkonstruktoren
Konstruiere Dezimalwerte aus Strings, um Float-Ungenauigkeit zu vermeiden:
# Good: exact value
amount = Decimal("123.45")
# Risky: might inherit float imprecision
amount = Decimal(123.45) # Could be 123.4500000000000028...
Immer vor der Anzeige oder Speicherung runden
Runden Sie Dezimalwerte auf die entsprechende Genauigkeit, bevor sie angezeigt oder in der Datenbank gespeichert werden:
from decimal import Decimal, ROUND_HALF_UP
calculated = Decimal("123.456789")
display = calculated.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
Setze dezimalen Kontext bei Bedarf
Die dezimale Kontextgenauigkeit beeinflusst die Ergebnisse der Berechnungen, nicht Werte, die aus Strings gelesen oder aus der Datenbank gelesen werden. Eine Abfrage gibt immer einen money- oder decimal-Wert in seiner vollständig gespeicherten Skalierung zurück, unabhängig von getcontext().prec. Der Kontext wirkt erst, wenn man mit ihm rechnet. Um zu sehen, dass die Präzision wirkt, führen Sie eine Operation wie Division durch.
from decimal import Decimal, getcontext
# A money value read from SQL Server arrives at its full stored scale,
# regardless of the context precision.
cursor.execute(
"SELECT SubTotal FROM Sales.SalesOrderHeader WHERE SalesOrderID = 43659"
)
subtotal = cursor.fetchone().SubTotal
print(subtotal) # Decimal('20565.6206')
# The context precision governs the calculation, not the fetched value.
# Split the subtotal into three equal installments.
getcontext().prec = 10
print(subtotal / 3) # 6855.206867 (10 significant digits)
getcontext().prec = 28 # Reset to default
print(subtotal / 3) # 6855.206866666666666666666667 (28 significant digits)