Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
De mssql-python-driver koppelt SQL Server decimale, numerieke en gelddatatypes aan Python's decimal.Decimal voor nauwkeurige financiële en wetenschappelijke berekeningen. SQL Server biedt de volgende precieze numerieke types:
| SQL-type | Python-type | Precisie | Gebruikssituatie |
|---|---|---|---|
| decimal(p,s) | decimal.Decimal |
1-38 cijfers | Exacte berekeningen |
| numeriek(p,s) | decimal.Decimal |
1-38 cijfers | Hetzelfde als decimaal |
| money | decimal.Decimal |
19 cijfers, 4 decimale | Valuta |
| smallmoney | decimal.Decimal |
10 cijfers, 4 decimale | Kleine valuta |
| float | float |
~15 cijfers | Ongeveer |
| real | float |
~7 cijfers | Ongeveer |
Python Decimal-gegevenstype
Ontvang decimale waarden
Decimale kolommen geven Python-objecten decimal.Decimal terug:
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')
Stuur decimale waarden
Geef Decimal objecten door voor nauwkeurige inbrenging:
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()
Vermijd float voor financiële gegevens
Vlottertypes hebben precisieproblemen waardoor ze ongeschikt zijn voor financiële berekeningen. Gebruik het altijd Decimal voor nauwkeurige resultaten:
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
Geldtype
Werk met valutakolommen
Haal en werk de SQL Server-geldkolommen op met behulp van Python Decimal-objecten:
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()
Valutaopmaak
Formatteer decimale waarden als valutastrings met symbolen en komma-scheiders:
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
Precisie van decimalen en schaal
Begrijp precisie en schaal
- Precisie (p): Totaal aantal cijfers
- Schaal (s): Aantal cijfers na de decimale komma
Kan bijvoorbeeld decimal(10, 2) waarden opslaan van -99999999,99 tot 99999999,99, terwijl decimal(5, 4) waarden van -9,9999 tot 9,9999 kan worden opgeslagen.
Geef de precisie van parameters op
De mssql-python-driver leidt precisie en schaal automatisch af van decimale waarden:
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()
Afronding beheren
Gebruik de quantize() methode om decimale waarden af te ronden op een specifieke schaal:
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')
Financiële berekeningen
Veilige rekenkundige bewerkingen
Voer financiële berekeningen uit met decimale methoden en correcte afronding:
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")})
Percentageberekeningen
Bereken procentuele kortingen en aanpassingen:
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}")
Verdeelde bedragen
Verdeel het totaalbedrag gelijkmatig over meerdere partijen en regel de restanten:
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
Aggregatiebewerkingen
SUM en AVG met decimalen
SQL Server-aggregatiefuncties geven decimale types terug voor precisie:
# 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"))
Omgaan met NULL in aggregaties
SQL Server-aggregatiefuncties kunnen terugkeren None wanneer geen enkele rij overeenkomt met de query:
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")
Uitgangsomzetters voor decimale
Outputconverters gebruiken het Python-type dat in cursor.description wordt weergegeven als sleutel, niet het ODBC SQL-type integer. Registreer de converter met decimal.Decimal zodat het werkt voor decimal, numeric, money, en smallmoney kolommen, die allemaal worden toegewezen aan decimal.Decimal.
Converteer naar float (wanneer precisie niet cruciaal is)
Voor compatibiliteit met bibliotheken die float-waarden verwachten, definieer een outputconverter:
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'>
Aangepaste converter voor valutaopmaak
Definieer een aangepaste uitvoerconverter om decimale waarden automatisch te formatteren als valutastrings:
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"
Algemene patronen
Valutaomrekening
Wissel geldbedragen tussen valuta's om met wisselkoersen:
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}")
Saldo-updates met transacties
Gebruik transacties met passende lock-hints om atomic fund-overdrachten te garanderen en verloren updates te voorkomen:
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"))
De overdracht vergrendelt beide rijen vooraan, met het laagste ID eerst, door voor elk account een aparte SELECT ... WITH (UPDLOCK) rij in gesorteerde volgorde uit te geven.
UPDLOCK voorkomt het verloren updatepatroon waarbij twee transacties hetzelfde saldo lezen en updates toepassen op basis van verouderde data. Door de vergrendelingen als afzonderlijke instructies in gesorteerde volgorde uit te geven, wordt de verwervingsvolgorde gegarandeerd. Daardoor wordt voorkomen dat tegengestelde overboekingen tussen dezelfde twee rekeningen de vergrendelingen in omgekeerde volgorde verwerven en een deadlock veroorzaken.
Bulkinvoeging met decimalen
Voeg meerdere rijen in met decimale waarden met 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()
Beste praktijken
Gebruik altijd decimaal voor financiële wiskunde
Gebruik nooit 'float' voor financiële berekeningen; gebruik altijd het decimale type voor precisie:
# 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')
Gebruik tekenreeksconstructoren
Construeer decimale waarden uit strings om float-onnauwkeurigheid te voorkomen:
# Good: exact value
amount = Decimal("123.45")
# Risky: might inherit float imprecision
amount = Decimal(123.45) # Could be 123.4500000000000028...
Altijd afronden vóór weergave of opslag
Ronde decimale waarden op de juiste schaal voordat ze worden weergegeven of behouden in de database:
from decimal import Decimal, ROUND_HALF_UP
calculated = Decimal("123.456789")
display = calculated.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
Stel destijds de decimale context in indien nodig
De decimale contextprecisie beïnvloedt de resultaten van berekeningen, niet waarden die uit strings worden geanalyseerd of uit de database worden gelezen. Een query retourneert altijd een money- of decimal-waarde met de volledige opgeslagen schaal, ongeacht getcontext().prec. De context wordt pas van kracht als je ermee rekent. Om de precisie te laten werken, voer je een operatie uit zoals deling.
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)