Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
Az mssql-python meghajtó az SQL Server decimal, numeric és money adattípusait a Python decimal.Decimal típusához rendeli, hogy pontos pénzügyi és tudományos számításokat tegyen lehetővé. Az SQL Server a következő pontos numerikus típusokat biztosítja:
| SQL-típus | Python-típus | Precision | Használati eset |
|---|---|---|---|
| decimális(p,s) | decimal.Decimal |
1-38 számjegy | Pontos számítások |
| numerikus(p,s) | decimal.Decimal |
1-38 számjegy | Megegyezik a tizedessel |
| money | decimal.Decimal |
19 számjegy, 4 tizedes | Valuta |
| smallmoney | decimal.Decimal |
10 számjegy, 4 tizedesjegy | Kis valuta |
| float | float |
~15 számjegy | Közelítő |
| real | float |
~7 számjegy | Hozzávetőleges |
Python tizedes típus
Tizedes értékek vétele
A tizedes oszlopok Python decimal.Decimal objektumokat adnak vissza:
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')
Tizedes értékek küldése
Adj át Decimal objektumokat a pontos beszúráshoz:
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()
Kerüld a float adattípust pénzügyi adatokhoz
Az úszótípusok pontossági problémákkal rendelkeznek, ezért nem alkalmasak pénzügyi számításokhoz. Mindig használd Decimal a pontos eredményekért:
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
Pénz típus
Munka pénzoszlopokkal
Az SQL Server pénzoszlopainak lekérése és frissítése Python tizedes objektumokkal:
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()
Pénzformázás
A tizedes értékeket formázza pénznemet tartalmazó karakterláncokká szimbólumokkal és vesszős elválasztókkal:
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
Tizedes pontosság és skálázás
A precizitás és a skála megértése
- Precizitás (p): Számjegyek összesített száma
- Skála (s): A tizedespont után következő számjegyek száma
Például decimal(10, 2) tárolhat értékeket -99999999.99.99 és 9999999.99 között, míg decimal(5, 4) -99999 és 9.9999 közötti értékek tárolására.
Paraméterek pontosságának meghatározása
Az mssql-python illesztőprogram automatikusan következteti a precizitást és skálázást a tizedes értékekből:
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()
Kontrollkerekítés
Használd a quantize() módszert a tizedes értékek kerekítésére egy adott skálára:
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')
Pénzügyi számítások
Biztonságos aritmetika
Hajtsa végre pénzügyi számításokat tizedes rendszerrel, megfelelő kerekítéssel:
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")})
Százalékos számítások
Számolja ki százalékos alapú kedvezményeket és korrekciókat:
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}")
Osztott összegek
Az összösszeget egyenlően oszd meg több fél között, kezelve a maradékokat:
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
Összesítési műveletek
SUM és AVG tizedesekkel
SQL Server aggregációs függvényei pontosság érdekében tizedes típusokat adnak vissza:
# 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"))
A NULL kezelése aggregációkban
Az SQL Server aggregációs függvényei akkor is visszatérhetnekNone, ha egyetlen sor sem egyezik meg a lekérdezéssel:
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")
Kimeneti konvertálók tizedes számokhoz
A kimeneti konverterek a kulcsként a benne látható cursor.description Python típust használják, nem az ODBC SQL típusú egész számot. Regisztrálja az átalakítót ezzel: decimal.Decimal, hogy működjön a(z) decimal, numeric, money és smallmoney oszlopokhoz, amelyek mind a(z) decimal.Decimal elemre vannak leképezve.
Átalakítás lebegőre (amikor a pontosság nem kritikus)
A lebegő értékeket váró könyvtárakhoz való kompatibilitás érdekében definiáljunk egy kimeneti átalakítót:
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'>
Egyedi pénznemformázó konverter
Definiáljunk egy egyedi kimeneti konvertert, amely automatikusan formázza a tizedes értékeket valutaszövegként:
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"
Gyakori minták
Pénznemátváltás
Pénzösszegek átváltása valuták között az árfolyam alapján:
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}")
Egyenlegfrissítések tranzakciókkal
Használj megfelelő zárolási tippeket tartalmazó tranzakciókat az atomi pénzátutalások biztosítására és a frissítések elvesztésének megelőzésére:
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"))
Az átutalás már az elején zárolja mindkét sort, a legalacsonyabb azonosítójúval kezdve, úgy, hogy rendezett sorrendben minden számlához külön SELECT ... WITH (UPDLOCK) utasítást ad ki.
UPDLOCK Megakadályozza az elveszett frissítési mintázatot, amikor két tranzakció ugyanazt az egyenleget olvasza, és elavult adatok alapján alkalmazza a frissítéseket. A zárak különálló utasításokként, rendezett sorrendben történő kiadása garantálja a megszerzés sorrendjét, megakadályozva, hogy az ugyanazon két számla közötti ellentétes átutalások fordított sorrendben szerezzék meg a zárakat, és ezzel holtpontot okozzanak.
Tömeges beszúrás tizedesjegyekkel
Több, Decimal értékeket tartalmazó sor beszúrása a(z) executemany() használatával:
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()
Bevált gyakorlatok
Mindig használd a tizedes rendszert pénzügyi matematikához
Pénzügyi számításokhoz soha ne használj float típust; a kellő pontosság biztosítása érdekében mindig a Decimal típust használd:
# 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')
Használjon stringkonstruktorokat
Tizedes értékeket karakterláncokból hozzon létre a lebegőpontos pontatlanság elkerülése érdekében:
# Good: exact value
amount = Decimal("123.45")
# Risky: might inherit float imprecision
amount = Decimal(123.45) # Could be 123.4500000000000028...
Mindig kerekítsen megjelenítés vagy tárolás előtt
Kerekítsd a tizedes értékeket a megfelelő skálára, mielőtt megjelenítenénk vagy az adatbázisban maradnának:
from decimal import Decimal, ROUND_HALF_UP
calculated = Decimal("123.456789")
display = calculated.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
Szükség esetén állíts be tizedes kontextust
A tizedes kontextus pontossága befolyásolja a számítások eredményét, nem pedig a stringekből elemzött vagy az adatbázisból olvasott értékeket. Egy lekérdezés mindig egy money vagy decimal értéket ad vissza a teljes tárolt skálával, függetlenül attól, hogy .getcontext().prec A kontextus csak akkor válik hatásra, ha vele számolsz. A pontosság hatásának megtekintéséhez végezz egy olyan műveletet, mint például osztás.
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)