Not
Bu sayfaya erişim yetkilendirme gerektiriyor. Oturum açmayı veya dizinleri değiştirmeyi deneyebilirsiniz.
Bu sayfaya erişim yetkilendirme gerektiriyor. Dizinleri değiştirmeyi deneyebilirsiniz.
mssql-python sürücüsü, SQL Server ondalık, sayısal ve para veri türlerini doğru finansal ve bilimsel hesaplamalar için Python decimal.Decimal veri türlerine eşler. SQL Server aşağıdaki kesin sayısal tipleri sağlar:
| SQL Türü | Python Türü | Hassasiyet | Kullanım Örneği |
|---|---|---|---|
| decimal(p,s) | decimal.Decimal |
1-38 hane | Kesin hesaplamalar |
| sayısal(p,s) | decimal.Decimal |
1-38 hane | Ondalık ile aynı |
| money | decimal.Decimal |
19 rakam, 4 ondalık | Para birimi |
| smallmoney | decimal.Decimal |
10 rakam, 4 ondalık | Küçük para birimi |
| float | float |
~15 rakam | Yaklaşık |
| real | float |
~7 rakam | Yaklaşık |
Python Decimal veri türü
Ondalık değerleri alın
Ondalık sütunlar Python decimal.Decimal nesnelerini döndürür:
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')
Ondalık değerler gönder
Hassas ekleme için Decimal nesnelerini iletin:
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()
Finansal veriler için float kullanmaktan kaçının
Kayan noktalı veri türleri, hassasiyet sorunları olduğundan finansal hesaplamalar için uygun değildir. Her zaman doğru sonuçlar için kullanın 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
Para türü
Para sütunlarıyla çalışma
SQL Server para sütunlarını Python ondalık nesneler kullanarak alın ve güncelleyin:
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()
Para biçimlendirme
Ondalık değerleri semboller ve virgül ayırıcılarıyla para birimi dizileri olarak biçimlendirin:
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
Ondalık hassasiyet ve ölçek
Hassasiyeti ve ölçeği anlamak
- Hassasiyet (p): Toplam basamak sayısı
- Ölçek (s): Ondalık ayırıcıdan sonraki basamak sayısı
Örneğin, decimal(10, 2) -99999999.99 ile 9999999.99 arasında değerler depolayabilirken decimal(5, 4) , -99999 ile 9.9999 arasında olan değerleri depolayabilir.
Parametrelerde hassasiyeti belirtin
mssql-python sürücüsü, ondalık değerlerden otomatik olarak hassasiyet ve ölçek çıkarır:
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()
Denetim yuvarlaması
Ondalık değerleri belirli bir ölçekte yuvarlamak için bu quantize() yöntemi kullanın:
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')
Mali hesaplamalar
Güvenli aritmetik
Decimal kullanarak finansal hesaplamaları doğru yuvarlamayla uygulayın:
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")})
Yüzde hesaplamaları
Yüzde bazlı indirimler ve ayarlamalar hesaplayın:
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}")
Bölünmüş miktarlar
Toplam tutarı birden fazla taraf arasında eşit olarak paylaştırın, kalan kısmı yönetin:
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
Toplama işlemleri
Ondalıklı SUM ve AVG
SQL Server toplama işlevleri, hassasiyeti korumak için Decimal türleri döndürür:
# 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"))
Toplulaştırmalarda NULL işleme
SQL Server toplama işlevleri, sorguyla eşleşen satır olmadığında None döndürebilir:
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")
Ondalık için çıkış dönüştürücüleri
Çıktı dönüştürücüler, anahtar olarak gösterilen Python tipini cursor.description kullanır, ODBC SQL tipi tam sayıyı değil. Dönüştürücüyü decimal.Decimal ile kaydederek decimal, numeric, money ve smallmoney sütunlarında çalışmasını sağlayın; bunların tümü decimal.Decimal öğesine eşlenir.
Hassasiyet kritik olmadığında float'a dönüştürün)
Float değer bekleyen kütüphanelerle uyumluluk için, bir çıkış dönüştürücü tanımlayın:
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'>
Özel para formatlama dönüştürücüsü
Ondalık değerleri otomatik olarak para birimi dizileri olarak biçimlendirmek için özel bir çıkış dönüştürücü tanımlayın:
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"
Ortak desenler
Para birimi dönüştürme
Parasal tutarları döviz kurlarını kullanarak para birimleri arasında dönüştürü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}")
İşlemlerle birlikte bakiye güncellemeleri
Atomik fon transferlerini sağlamak ve güncellemelerin kaybını önlemek için uygun kilit ipuçları içeren işlemler kullanın:
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"))
Aktarım, sıralı düzende her hesap için ayrı bir SELECT ... WITH (UPDLOCK) göndererek, her iki satırı da en baştan ID'si en düşük olan önce olacak şekilde kilitler.
UPDLOCK iki işlemin aynı bakiyeyi okuyup güncel olmayan verilere dayanarak güncellemeler uyguladığı kayıp güncelleme örüntüsünü önler. Kilitlerin sıralanmış düzende ayrı komutlar halinde uygulanması, kilit edinme sırasını garanti eder; böylece aynı iki hesap arasındaki zıt yönlü transferlerin kilitleri ters sırayla edinip deadlock’a girmesi önlenir.
Ondalıklı toplu ekleme
Ondalık değerlerle birden fazla satır ekleyin: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()
En iyi uygulamalar
Finansal matematik için her zaman ondalık kullan
Finansal hesaplamalar için asla float kullanma; Hassasiyet için her zaman ondalık tipi kullanın:
# 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')
Dizi yapıcılar kullanın
Kayan nokta hassasiyetsizliğini önlemek için Decimal değerlerini dizelerden oluşturun:
# Good: exact value
amount = Decimal("123.45")
# Risky: might inherit float imprecision
amount = Decimal(123.45) # Could be 123.4500000000000028...
Görüntülemeden veya depolamadan önce her zaman yuvarlayın
Ondalık değerleri görüntülemeden veya veritabanına devam ettirmeden önce uygun ölçekte yuvarlayın:
from decimal import Decimal, ROUND_HALF_UP
calculated = Decimal("123.456789")
display = calculated.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
Gerekirse ondalık bağlam belirleyin
Ondalık bağlam hassasiyeti, hesaplamaların sonuçlarını etkiler, dizilerden ayrıştırılan veya veritabanından okunan değerleri değil. Bir sorgu, getcontext().prec değerinden bağımsız olarak, depolanan tam ölçeğiyle her zaman bir money veya decimal değeri döndürür. Bağlam ancak onunla hesaplama yaptığınızda etkili olur. Hassasiyetin etkisini görmek için, bölme gibi bir işlem gerçekleştirin.
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)