Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Parametrizované dotazy jsou nezbytné pro:
- Bezpečnost: Prevence útoků injekcí SQL
- Výkon: Umožnění opětovného použití plánu dotazů
- Správnost: Správné zacházení se speciálními znaky a datovými typy
Ovladač mssql-python používá pyformat styl parametrů s %(name)s placeholdery ve výchozím nastavení, ale podporuje i jiné styly parametrů, pokud preferujete tento formát.
Základní parametrizované dotazy
Pojmenované parametry
Použijte pojmenované zástupné symboly k předávání parametrů do dotazů:
import mssql_python
conn = mssql_python.connect(
"Server=<server>.database.windows.net;"
"Database=<database>;"
"Authentication=ActiveDirectoryDefault;"
"Encrypt=yes"
)
cursor = conn.cursor()
# Single parameter
cursor.execute(
"SELECT * FROM Production.Product WHERE ProductSubcategoryID = %(category)s",
{"category": 5}
)
# Multiple parameters
cursor.execute(
"SELECT * FROM Production.Product WHERE ProductSubcategoryID = %(cat)s AND ListPrice > %(price)s",
{"cat": 5, "price": 10.00}
)
Opětovné použití parametrů
Stejný parametr můžete použít vícekrát:
cursor.execute("""
SELECT * FROM Production.Product
WHERE (Name LIKE %(search)s OR ProductNumber LIKE %(search)s)
AND ProductSubcategoryID = %(cat)s
""", {"search": "%Road%", "cat": 2})
Datové typy v parametrech
Parametry řetězce
Řetězce jsou automaticky uzavřeny do uvozovek a speciální znaky jsou bezpečně escapovány:
# Strings are automatically quoted
cursor.execute(
"SELECT * FROM Person.EmailAddress WHERE EmailAddress = %(email)s",
{"email": "ken0@adventure-works.com"}
)
# Special characters are escaped
cursor.execute(
"SELECT * FROM Person.Person WHERE LastName = %(name)s",
{"name": "O'Brien"} # Apostrophe handled safely
)
Numerické parametry
Předávejte číselné hodnoty jako celá čísla, desetinná čísla nebo plovoucí v závislosti na požadované přesnosti:
from decimal import Decimal
# Integer
cursor.execute("SELECT * FROM Production.Product WHERE ProductID = %(id)s", {"id": 42})
# Decimal for financial precision
cursor.execute(
"SELECT * FROM Production.Product WHERE ListPrice >= %(min)s AND ListPrice <= %(max)s",
{"min": Decimal("10.00"), "max": Decimal("100.00")}
)
# Float
cursor.execute(
"SELECT * FROM Production.Product WHERE Weight > %(threshold)s",
{"threshold": 15.0}
)
Parametry datu/času
Pomocí modulu datetime jazyka Python můžete předat hodnoty typu date, datetime a time:
from datetime import date, datetime, time
# Date
cursor.execute(
"SELECT * FROM Sales.SalesOrderHeader WHERE OrderDate >= %(date)s AND OrderDate < DATEADD(day, 1, %(date)s)",
{"date": date(2014, 3, 15)}
)
# Datetime
cursor.execute(
"SELECT * FROM Sales.SalesOrderHeader WHERE ModifiedDate >= %(start)s AND ModifiedDate < %(end)s",
{"start": datetime(2014, 3, 1), "end": datetime(2014, 4, 1)}
)
# Time
cursor.execute(
"SELECT * FROM HumanResources.Shift WHERE StartTime >= %(time)s",
{"time": time(9, 0, 0)}
)
Žádná hodnota pro NULL
Předejte None pro vložení nebo aktualizaci NULL hodnot v databázi:
# Insert NULL
cursor.execute("""
CREATE TABLE #NullDemo (ID INT IDENTITY, Name NVARCHAR(50), Email NVARCHAR(100))
""")
cursor.execute(
"INSERT INTO #NullDemo (Name, Email) VALUES (%(name)s, %(email)s)",
{"name": "Guest", "email": None}
)
# Query with NULL
cursor.execute(
"UPDATE #NullDemo SET Email = %(email)s WHERE ID = %(id)s",
{"email": None, "id": 1}
)
Binární parametry
Vložte binární data jako objekty typu bytes:
# Binary data
hash_value = b'\x00\x01\x02\x03'
cursor.execute("""
CREATE TABLE #HashDemo (ID INT IDENTITY, DocumentHash VARBINARY(256))
""")
cursor.execute(
"INSERT INTO #HashDemo (DocumentHash) VALUES (%(hash)s)",
{"hash": hash_value}
)
Vytváření dynamických dotazů
Podmíněné WHERE klauzule
Dynamicky budujte klauzule WHERE na základě volitelných kritérií vyhledávání:
def search_products(cursor, name: str | None = None,
category: int | None = None,
min_price: float | None = None) -> list:
"""Build query with optional conditions."""
conditions = []
params = {}
if name:
conditions.append("Name LIKE %(name)s")
params["name"] = f"%{name}%"
if category:
conditions.append("ProductSubcategoryID = %(category)s")
params["category"] = category
if min_price is not None:
conditions.append("ListPrice >= %(min_price)s")
params["min_price"] = min_price
query = "SELECT TOP 10 * FROM Production.Product"
if conditions:
query += " WHERE " + " AND ".join(conditions)
cursor.execute(query, params)
return cursor.fetchall()
# Usage
products = search_products(cursor, name="Road", min_price=10.0)
IN klauzule s více hodnotami
Stavte klauzuli IN dynamicky s placeholderem pro každou hodnotu. Nikdy nepoužívejte formátování řetězců k přímému vkládání hodnot:
def get_products_by_ids(cursor, product_ids: list[int]) -> list:
"""Query with IN clause using qmark (?) placeholders."""
if not product_ids:
return []
placeholders = ", ".join("?" for _ in product_ids)
query = f"SELECT * FROM Production.Product WHERE ProductID IN ({placeholders})"
cursor.execute(query, tuple(product_ids))
return cursor.fetchall()
# Usage
products = get_products_by_ids(cursor, [1, 5, 10, 15])
Stejný vzor funguje i s placeholdery pyformat (%(name)s):
def get_products_by_ids(cursor, product_ids: list[int]) -> list:
"""Query with IN clause using pyformat placeholders."""
if not product_ids:
return []
# Create named parameters for each ID
params = {f"id{i}": id for i, id in enumerate(product_ids)}
placeholders = ", ".join(f"%(id{i})s" for i in range(len(product_ids)))
query = f"SELECT * FROM Production.Product WHERE ProductID IN ({placeholders})"
cursor.execute(query, params)
return cursor.fetchall()
# Usage
products = get_products_by_ids(cursor, [1, 5, 10, 15])
Dynamický výběr sloupců
Použijte seznam povolených pro ověření sloupců před dynamickým vytvářením SELECT seznamů, přičemž hodnoty filtrů zůstávají parametry:
def get_employee(cursor, employee_id: int, columns: list[str] | None = None) -> dict:
"""Get employee with specified columns."""
# Allow list of permitted columns
allowed = {"BusinessEntityID", "LoginID", "JobTitle", "HireDate", "SalariedFlag"}
if columns:
# Validate columns against allow list
safe_columns = [c for c in columns if c in allowed]
if not safe_columns:
raise ValueError("No valid columns specified")
column_list = ", ".join(safe_columns)
else:
column_list = "*"
# ID is always a parameter, never interpolated
query = f"SELECT {column_list} FROM HumanResources.Employee WHERE BusinessEntityID = %(id)s"
cursor.execute(query, {"id": employee_id})
return cursor.fetchone()
Řazení
Použijte seznam povolených pro ověření řazení sloupců před interpolací:
def get_products_sorted(cursor, sort_by: str = "Name",
descending: bool = False) -> list:
"""Get products with validated sort order."""
# Allow list of permitted sort columns
allowed_sorts = {"Name", "ListPrice", "SellStartDate", "ProductID"}
if sort_by not in allowed_sorts:
sort_by = "Name" # Default
direction = "DESC" if descending else "ASC"
# sort_by and direction are validated, safe to interpolate
query = f"SELECT TOP 10 * FROM Production.Product ORDER BY {sort_by} {direction}"
cursor.execute(query)
return cursor.fetchall()
INSERT operací
Samostatná vložka
Vložte jeden řádek s parametrizovanými hodnotami:
cursor.execute("""
CREATE TABLE #ParamInsert (ID INT IDENTITY, Name NVARCHAR(50), Price DECIMAL(10,2), CategoryID INT)
""")
cursor.execute("""
INSERT INTO #ParamInsert (Name, Price, CategoryID)
VALUES (%(name)s, %(price)s, %(category)s)
""", {"name": "New Widget", "price": 29.99, "category": 5})
conn.commit()
Vložit s návratem identity
Použijte OUTPUT k získání generované hodnoty identity po vložení nového řádku:
cursor.execute("""
CREATE TABLE #IdentDemo (ProductID INT IDENTITY, Name NVARCHAR(50), Price DECIMAL(10,2), CategoryID INT)
""")
cursor.execute("""
INSERT INTO #IdentDemo (Name, Price, CategoryID)
OUTPUT INSERTED.ProductID
VALUES (%(name)s, %(price)s, %(category)s)
""", {"name": "New Widget", "price": 29.99, "category": 5})
new_id = cursor.fetchval()
conn.commit()
print(f"Created product with ID: {new_id}")
Dávkové vkládání pomocí executemany
Použijte executemany() k efektivnímu vložení více řádků jediným parametrizovaným příkazem:
Tip
Pro velké objemy je bulkcopy() rychlejší než executemany(), protože používá protokol hromadného vkládání místo jednotlivých příkazů INSERT. Viz. Hromadné kopírování.
cursor.execute("""
CREATE TABLE #BatchDemo (ID INT IDENTITY, Name NVARCHAR(50), Price DECIMAL(10,2), CategoryID INT)
""")
products = [
{"name": "Widget A", "price": 19.99, "cat": 1},
{"name": "Widget B", "price": 29.99, "cat": 1},
{"name": "Widget C", "price": 39.99, "cat": 2},
]
cursor.executemany("""
INSERT INTO #BatchDemo (Name, Price, CategoryID)
VALUES (%(name)s, %(price)s, %(cat)s)
""", products)
conn.commit()
UPDATE operací
Aktualizujte jednotlivé řádky nebo více řádků na základě podmínek pomocí parametrizovaných klauzulí WHERE:
# Create temp table with sample data
cursor.execute("""
CREATE TABLE #UpdDemo (
ID INT IDENTITY, Name NVARCHAR(50),
Price DECIMAL(10,2), CategoryID INT, ModifiedAt DATETIME
)
""")
cursor.execute("""
INSERT INTO #UpdDemo (Name, Price, CategoryID)
VALUES ('Widget X', 25.00, 5), ('Widget Y', 30.00, 5), ('Gadget Z', 50.00, 3)
""")
# Single row update
cursor.execute("""
UPDATE #UpdDemo
SET Price = %(price)s, ModifiedAt = %(modified)s
WHERE ID = %(id)s
""", {"price": 34.99, "modified": datetime.now(), "id": 1})
# Conditional update
cursor.execute("""
UPDATE #UpdDemo
SET Price = Price * %(multiplier)s
WHERE CategoryID = %(category)s
""", {"multiplier": 1.1, "category": 5})
conn.commit()
DELETE operací
Mažte řádky z tabulek na základě parametrizovaných podmínek filtru:
# Create temp table with sample data
cursor.execute("""
CREATE TABLE #DelDemo (
ID INT IDENTITY, Name NVARCHAR(50), Status NVARCHAR(20), OrderDate DATE
)
""")
cursor.execute("""
INSERT INTO #DelDemo (Name, Status, OrderDate)
VALUES ('Order1', 'Active', '2024-06-01'), ('Order2', 'Cancelled', '2022-05-01'),
('Order3', 'Cancelled', '2022-11-01')
""")
# Delete single row
cursor.execute(
"DELETE FROM #DelDemo WHERE ID = %(id)s",
{"id": 1}
)
# Delete with conditions
cursor.execute("""
DELETE FROM #DelDemo
WHERE Status = %(status)s AND OrderDate < %(date)s
""", {"status": "Cancelled", "date": date(2023, 1, 1)})
conn.commit()
Bezpečnostní aspekty
Nikdy neinterpolujte uživatelský vstup
Vždy používejte parametry pro bezpečný únik z uživatelského vstupu:
# DANGEROUS - SQL injection vulnerability!
user_input = "'; DROP TABLE Users;--"
query = f"SELECT * FROM Person.Person WHERE LastName = '{user_input}'" # DON'T DO THIS
# SAFE - always use parameters
cursor.execute(
"SELECT * FROM Person.Person WHERE LastName = %(name)s",
{"name": user_input} # Input is safely escaped
)
Validujte názvy tabulek a sloupců
Použijte seznamy povolení k ověření identifikátorů tabulek a sloupců, které nelze parametrizovat:
def query_table(cursor, table: str, columns: list[str]):
"""Query with validated table and column names."""
# Allow list of permitted tables
allowed_tables = {"Person.Person", "Production.Product", "Sales.SalesOrderHeader"}
if table not in allowed_tables:
raise ValueError(f"Invalid table: {table}")
# Allow list of permitted columns per table
allowed_columns = {
"Person.Person": {"BusinessEntityID", "FirstName", "LastName"},
"Production.Product": {"ProductID", "Name", "ListPrice"},
"Sales.SalesOrderHeader": {"SalesOrderID", "CustomerID", "TotalDue"},
}
safe_columns = [c for c in columns if c in allowed_columns.get(table, set())]
if not safe_columns:
raise ValueError("No valid columns")
# Safe to interpolate after validation
query = f"SELECT TOP 5 {', '.join(safe_columns)} FROM {table}"
cursor.execute(query)
return cursor.fetchall()
Používejte uložené procedury pro složité operace
Uložené procedury přidávají další vrstvu ochrany a umožňují spouštění složité obchodní logiky na straně serveru:
cursor.execute("""
EXECUTE dbo.uspGetEmployeeManagers @BusinessEntityID = %(id)s
""", {"id": 5})
rows = cursor.fetchall()
Výhody výkonu
Ukládání plánu dotazu do mezipaměti
Když používáte parametrizované dotazy, SQL Server znovu používá stejný plán provedení napříč různými hodnotami parametrů místo toho, aby pro každý dotaz sestavoval nový plán.
for product_id in range(1, 100):
cursor.execute(
"SELECT * FROM Production.Product WHERE ProductID = %(id)s",
{"id": product_id}
)
Připravené příkazy
Používejte připravené výkazy pro dotazy, které často spouštíte. Ovladač automaticky připravuje příkazy, takže při použití stejné šablony dotazu s různými parametry je výhoda z přípravy.
query = "SELECT Name, ListPrice FROM Production.Product WHERE ProductSubcategoryID = %(cat)s"
for category in [1, 2, 3, 4, 5]:
cursor.execute(query, {"cat": category})
products = cursor.fetchall()