Używaj danych JSON z mssql-python

Microsoft SQL 2016 i nowsze wersje oraz Azure SQL zapewniają wsparcie dla JSON poprzez funkcje działające na nvarchar kolumnach. Sterownik mssql-python wysyła i odbiera JSON jako zwykłe ciągi znaków Python. Masz następujące możliwości:

  • Zapisz JSON jako ciągi znaków w nvarchar kolumnach.
  • Wysyłaj zapytania do formatu JSON za pomocą wyrażeń ścieżki przy użyciu JSON_VALUE, JSON_QUERY i OPENJSON.
  • Przekształc dane relacyjne do JSON za pomocą FOR JSON.
  • Parsuj JSON w formacie relacyjnym za pomocą OPENJSON.

Note

Microsoft SQL przechowuje dane JSON w kolumnachnvarchar, a nie w dedykowanym typie kolumny JSON. Sterownik mssql-python wysyła i odbiera JSON jako regularne ciągi znaków. Użyj wbudowanego json modułu Python do serializacji i deserializacji po stronie klienta.

Przechowuj dane JSON

Serializować słowniki Pythona do ciągów znaków za pomocą json.dumps() przed wstawieniem ich do kolumn nvarchar.

Wstaw ciąg JSON

Zapisz słownik Python jako tekst JSON w tabeli bazy danych:

import json
import mssql_python

conn = mssql_python.connect(
    "Server=<server>.database.windows.net;"
    "Database=<database>;"
    "Authentication=ActiveDirectoryDefault;"
    "Encrypt=yes"
)
cursor = conn.cursor()

# Create table with JSON column
cursor.execute("""
    CREATE TABLE #JsonProducts (
        ProductID INT IDENTITY PRIMARY KEY,
        Name NVARCHAR(100),
        JsonData NVARCHAR(MAX)
    )
""")

# Python dict to JSON string
product_data = {
    "name": "Widget Pro",
    "specs": {
        "weight": 2.5,
        "dimensions": {"width": 10, "height": 5, "depth": 3}
    },
    "tags": ["electronics", "gadgets", "bestseller"]
}

cursor.execute("""
    INSERT INTO #JsonProducts (Name, JsonData)
    VALUES (%(name)s, %(json)s)
""", {"name": "Widget Pro", "json": json.dumps(product_data)})
conn.commit()

Validuj JSON przy wstawieniu

Użyj ISJSON() funkcji do weryfikacji składni JSON przed wstawieniem:

data = {"name": "Widget", "specs": {"weight": 1.5}}

cursor.execute("""
    CREATE TABLE #JsonValidate (
        ProductID INT IDENTITY, Name NVARCHAR(100), JsonData NVARCHAR(MAX)
    )
""")
cursor.execute("""
    INSERT INTO #JsonValidate (Name, JsonData)
    SELECT %(name)s, %(json)s
    WHERE ISJSON(%(json)s) = 1
""", {"name": "Widget", "json": json.dumps(data)})

if cursor.rowcount == 0:
    raise ValueError("Invalid JSON data")

Wykonywanie zapytań dotyczących danych JSON

Użyj funkcji ścieżki JSON Microsoft SQL, aby wyodrębnić wartości na serwerze przed zwróceniem ich klientowi.

Ekstrakcja wartości skalarnych

Zastosowanie JSON_VALUE do wyodrębniania pojedynczych wartości:

# Create table with sample JSON data
cursor.execute("""
    CREATE TABLE #JsonExtract (
        ProductID INT IDENTITY, Name NVARCHAR(100), JsonData NVARCHAR(MAX)
    )
""")
cursor.execute("""
    INSERT INTO #JsonExtract (Name, JsonData) VALUES (
        'Widget Pro',
        '{"name":"Widget Pro","specs":{"weight":2.5,"dimensions":{"width":10,"height":5,"depth":3}},"tags":["electronics","gadgets"]}'
    )
""")

cursor.execute("""
    SELECT 
        Name,
        JSON_VALUE(JsonData, '$.specs.weight') AS Weight,
        JSON_VALUE(JsonData, '$.specs.dimensions.width') AS Width
    FROM #JsonExtract
    WHERE JSON_VALUE(JsonData, '$.name') = %(name)s
""", {"name": "Widget Pro"})

row = cursor.fetchone()
print(f"Weight: {row.Weight}, Width: {row.Width}")

Ekstrakcja obiektów lub tablic

Użyj JSON_QUERY w przypadku obiektów i tablic.

cursor.execute("""
    CREATE TABLE #JsonQuery (
        ProductID INT IDENTITY, Name NVARCHAR(100), JsonData NVARCHAR(MAX)
    )
""")
cursor.execute("""
    INSERT INTO #JsonQuery (Name, JsonData) VALUES (
        'Widget Pro',
        '{"specs":{"weight":2.5,"color":"blue"},"tags":["electronics","gadgets"]}'
    )
""")

cursor.execute("""
    SELECT 
        Name,
        JSON_QUERY(JsonData, '$.specs') AS Specs,
        JSON_QUERY(JsonData, '$.tags') AS Tags
    FROM #JsonQuery
""")

for row in cursor:
    specs = json.loads(row.Specs) if row.Specs else {}
    tags = json.loads(row.Tags) if row.Tags else []
    print(f"{row.Name}: {specs}, Tags: {tags}")

Analizuj tablicę JSON na wiersze

Rozwiń tablicę JSON do wierszy, używając:OPENJSON

cursor.execute("""
    CREATE TABLE #JsonArray (
        ProductID INT IDENTITY, Name NVARCHAR(100), JsonData NVARCHAR(MAX)
    )
""")
cursor.execute("""
    INSERT INTO #JsonArray (Name, JsonData) VALUES 
        ('Widget Pro', '{"tags":["electronics","gadgets","bestseller"]}'),
        ('Gadget X', '{"tags":["tools","gadgets"]}')
""")

cursor.execute("""
    SELECT p.Name, t.value AS Tag
    FROM #JsonArray p
    CROSS APPLY OPENJSON(p.JsonData, '$.tags') t
""")

for row in cursor:
    print(f"Product: {row.Name}, Tag: {row.Tag}")

Analizuj obiekt JSON na kolumny

Użyj JSON_VALUE(), aby wyodrębnić poszczególne pola z obiektów JSON i rzutować wyniki na odpowiednie typy SQL.

cursor.execute("""
    CREATE TABLE #JsonCols (
        ProductID INT IDENTITY, Name NVARCHAR(100), JsonData NVARCHAR(MAX)
    )
""")
cursor.execute("""
    INSERT INTO #JsonCols (Name, JsonData) VALUES (
        'Widget Pro',
        '{"name":"Widget Pro","specs":{"weight":2.5,"dimensions":{"width":10,"height":5}}}'
    )
""")

cursor.execute("""
    SELECT 
        p.ProductID,
        j.name AS ProductName,
        j.weight,
        j.width,
        j.height
    FROM #JsonCols p
    CROSS APPLY OPENJSON(p.JsonData)
    WITH (
        name NVARCHAR(100) '$.name',
        weight DECIMAL(5,2) '$.specs.weight',
        width INT '$.specs.dimensions.width',
        height INT '$.specs.dimensions.height'
    ) j
""")

Modyfikacja danych JSON

Użyj JSON_MODIFY, aby zaktualizować określoną ścieżkę w dokumencie JSON bez ponownego zapisywania całej wartości.

Aktualizacja wartości JSON

Zmodyfikuj pojedynczą właściwość JSON za użyciem JSON_MODIFY:

cursor.execute("""
    CREATE TABLE #JsonMod (
        ProductID INT IDENTITY, Name NVARCHAR(100), JsonData NVARCHAR(MAX)
    )
""")
cursor.execute("""
    INSERT INTO #JsonMod (Name, JsonData) VALUES (
        'Widget Pro',
        '{"specs":{"weight":2.5,"dimensions":{"width":10}},"tags":["electronics"]}'
    )
""")

cursor.execute("""
    UPDATE #JsonMod
    SET JsonData = JSON_MODIFY(JsonData, '$.specs.weight', %(weight)s)
    WHERE ProductID = %(id)s
""", {"weight": 3.0, "id": 1})
conn.commit()

Dodaj własność JSON

Wstaw nową właściwość do istniejącego obiektu JSON:

cursor.execute("""
    CREATE TABLE #JsonAdd (
        ProductID INT IDENTITY, Name NVARCHAR(100), JsonData NVARCHAR(MAX)
    )
""")
cursor.execute("""
    INSERT INTO #JsonAdd (Name, JsonData) VALUES (
        'Widget Pro', '{"specs":{"weight":2.5}}'
    )
""")

cursor.execute("""
    UPDATE #JsonAdd
    SET JsonData = JSON_MODIFY(JsonData, '$.specs.color', %(color)s)
    WHERE ProductID = %(id)s
""", {"color": "blue", "id": 1})

Usuń własność JSON

Usuń właściwość z obiektu JSON, ustawiając ją na:NULL

cursor.execute("""
    CREATE TABLE #JsonRem (
        ProductID INT IDENTITY, Name NVARCHAR(100), JsonData NVARCHAR(MAX)
    )
""")
cursor.execute("""
    INSERT INTO #JsonRem (Name, JsonData) VALUES (
        'Widget Pro', '{"specs":{"weight":2.5,"color":"blue"}}'
    )
""")

cursor.execute("""
    UPDATE #JsonRem
    SET JsonData = JSON_MODIFY(JsonData, '$.specs.color', NULL)
    WHERE ProductID = %(id)s
""", {"id": 1})

Dołącz do tablicy JSON

Dodaj nową wartość na końcu tablicy JSON za pomocą append dyrektywy w JSON_MODIFY:

cursor.execute("""
    CREATE TABLE #JsonAppend (
        ProductID INT IDENTITY, Name NVARCHAR(100), JsonData NVARCHAR(MAX)
    )
""")
cursor.execute("""
    INSERT INTO #JsonAppend (Name, JsonData) VALUES (
        'Widget Pro', '{"tags":["electronics","gadgets"]}'
    )
""")

cursor.execute("""
    UPDATE #JsonAppend
    SET JsonData = JSON_MODIFY(
        JsonData, 
        'append $.tags', 
        %(tag)s
    )
    WHERE ProductID = %(id)s
""", {"tag": "new-arrival", "id": 1})

Konwertowanie danych relacyjnych na dane JSON

Klauzula przekształca FOR JSON wyniki zapytań w ciąg JSON po stronie serwera.

DLA JSON AUTO

Wygeneruj JSON na podstawie wyników zapytań:

cursor.execute("""
    SELECT TOP 5 o.SalesOrderID, p.LastName AS CustomerName, o.TotalDue
    FROM Sales.SalesOrderHeader o
    JOIN Sales.Customer c ON o.CustomerID = c.CustomerID
    JOIN Person.Person p ON c.PersonID = p.BusinessEntityID
    FOR JSON AUTO
""")

# Result is a single string containing JSON
json_result = cursor.fetchval()
orders = json.loads(json_result)
print(json.dumps(orders, indent=2))

DLA ŚCIEŻKI JSON

Uzyskaj większą kontrolę nad strukturą JSON:

cursor.execute("""
    SELECT 
        o.SalesOrderID AS 'order.id',
        o.OrderDate AS 'order.date',
        p.LastName AS 'customer.name',
        e.EmailAddress AS 'customer.email'
    FROM Sales.SalesOrderHeader o
    JOIN Sales.Customer c ON o.CustomerID = c.CustomerID
    JOIN Person.Person p ON c.PersonID = p.BusinessEntityID
    JOIN Person.EmailAddress e ON p.BusinessEntityID = e.BusinessEntityID
    WHERE o.SalesOrderID = %(id)s
    FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
""", {"id": 43659})

json_result = cursor.fetchval()
order = json.loads(json_result)
# Structure: {"order": {"id": 43659, "date": "..."}, "customer": {"name": "...", "email": "..."}}

Zagnieżdżony kod JSON

Wykonuj zapytania dotyczące struktur danych zawierających zagnieżdżone tablice i obiekty JSON, używając podzapytań z FOR JSON do tworzenia hierarchicznych danych wyjściowych JSON.

cursor.execute("""
    SELECT TOP 3
        c.CustomerID,
        p.LastName AS CustomerName,
        (SELECT TOP 3 o.SalesOrderID, o.TotalDue
         FROM Sales.SalesOrderHeader o
         WHERE o.CustomerID = c.CustomerID
         FOR JSON PATH) AS Orders
    FROM Sales.Customer c
    JOIN Person.Person p ON c.PersonID = p.BusinessEntityID
    WHERE c.PersonID IS NOT NULL
    FOR JSON PATH
""")

json_result = cursor.fetchval()
customers = json.loads(json_result)
# Each customer has nested Orders array

Wzorce integracji z Python

Te wzorce pokazują, jak budować abstrakcie w Python na tabelach wspieranych przez JSON.

Wzorzec repozytorium z JSON

Wprowadź warstwę dostępu do danych, która serializuje i deserializuje obiekty Python do kolumn JSON, zapewniając bezpieczny interfejs typowy do bazy danych.

from dataclasses import dataclass, asdict
from typing import Optional
import json

@dataclass
class ProductSpecs:
    weight: float
    color: str
    dimensions: dict

@dataclass
class Product:
    id: Optional[int]
    name: str
    specs: ProductSpecs

class ProductRepository:
    def __init__(self, connection):
        self.conn = connection
        cursor = self.conn.cursor()
        cursor.execute("""
            IF OBJECT_ID('#JsonRepo') IS NULL
                CREATE TABLE #JsonRepo (
                    ProductID INT IDENTITY PRIMARY KEY,
                    Name NVARCHAR(100),
                    JsonData NVARCHAR(MAX)
                )
        """)
        self.conn.commit()
    
    def save(self, product: Product) -> int:
        cursor = self.conn.cursor()
        specs_json = json.dumps(asdict(product.specs))
        
        if product.id:
            cursor.execute("""
                UPDATE #JsonRepo SET Name = %(name)s, JsonData = %(json)s
                WHERE ProductID = %(id)s
            """, {"name": product.name, "json": specs_json, "id": product.id})
        else:
            cursor.execute("""
                INSERT INTO #JsonRepo (Name, JsonData)
                OUTPUT INSERTED.ProductID
                VALUES (%(name)s, %(json)s)
            """, {"name": product.name, "json": specs_json})
            product.id = cursor.fetchval()
        
        self.conn.commit()
        return product.id
    
    def get(self, product_id: int) -> Optional[Product]:
        cursor = self.conn.cursor()
        cursor.execute("""
            SELECT ProductID, Name, JsonData FROM #JsonRepo WHERE ProductID = %(id)s
        """, {"id": product_id})
        
        row = cursor.fetchone()
        if row is None:
            return None
        
        specs_data = json.loads(row.JsonData)
        return Product(
            id=row.ProductID,
            name=row.Name,
            specs=ProductSpecs(**specs_data)
        )

Połącz się z bazą danych, następnie utwórz repozytorium i użyj go do zapisu i pobrania produktu. Metoda save() wybiera gałąź INSERT, gdy id ma wartość None, a w przeciwnym razie gałąź UPDATE:

conn = mssql_python.connect(connection_string)

repo = ProductRepository(conn)

# id is None, so save() inserts a new row and returns the generated ProductID.
product = Product(
    id=None,
    name="Widget Pro",
    specs=ProductSpecs(weight=2.5, color="black", dimensions={"width": 10, "height": 5})
)
product_id = repo.save(product)
print(f"Saved product {product_id}")

# Read the product back into a typed Product object.
loaded = repo.get(product_id)
print(loaded)

conn.close()

Repozytorium tworzy #JsonRepo jako lokalną tabelę tymczasową powiązaną z przekazanym połączeniem, więc save() i get() muszą współdzielić to samo połączenie. Tabela jest usuwana po zamknięciu połączenia.

Efektywna obsługa dużych wyników JSON

Gdy wyniki JSON są duże, pobieraj je częściowo w kilku wierszach.

def fetch_json_in_parts(cursor, query: str, params: dict) -> list:
    """Handle JSON results that might span multiple rows."""
    cursor.execute(query, params)
    
    # FOR JSON might split large results across rows
    json_parts = []
    for row in cursor:
        json_parts.append(row[0])
    
    # Combine parts
    json_string = "".join(json_parts)
    return json.loads(json_string) if json_string else []

# Usage
data = fetch_json_in_parts(cursor, "SELECT TOP 100 * FROM Production.Product FOR JSON AUTO", {})

Przekonwertowanie wyników zapytań do JSON w Pythonie

Przekształc wyniki zapytań relacyjnych do formatu JSON w Python, konwertując każdy wiersz na słownik, a następnie serializując do JSON.

def query_to_json(cursor, query: str, params: dict = None) -> str:
    """Execute query and return results as JSON string."""
    cursor.execute(query, params or {})
    columns = [col[0] for col in cursor.description]
    
    rows = []
    for row in cursor:
        rows.append(dict(zip(columns, row)))
    
    return json.dumps(rows, default=str, indent=2)

# Usage
json_output = query_to_json(cursor, "SELECT TOP 5 ProductID, Name, ListPrice FROM Production.Product WHERE ProductSubcategoryID = %(cat)s", {"cat": 1})
print(json_output)

Indeksowanie danych JSON

Stwórz kolumnę obliczeniową popartą wyrażeniem ścieżki JSON, aby umożliwić indeksowanie ścieżki.

Kolumna obliczeniowa z indeksem

Zdefiniuj obliczoną kolumnę, która wyodrębnia wartość JSON i zastosuje do niej indeks dla efektywnego filtrowania często zapytywanych ścieżek JSON. Poniższy przykład tworzy stałą tabelę, dodaje trwałą kolumnę obliczeniową na ścieżce JSON $.specs.weight i tworzy na niej indeks.

cursor.execute("""
    IF OBJECT_ID('dbo.ProductCatalog', 'U') IS NOT NULL
        DROP TABLE dbo.ProductCatalog
""")
cursor.execute("""
    CREATE TABLE dbo.ProductCatalog (
        ProductID   INT IDENTITY PRIMARY KEY,
        Name        NVARCHAR(100),
        JsonData    NVARCHAR(MAX)
    )
""")

# Insert sample rows with JSON data
rows = [
    ("Widget Pro",   '{"specs":{"weight":2.5,"color":"blue"}}'),
    ("Gadget X",     '{"specs":{"weight":0.8,"color":"red"}}'),
    ("Heavy Duty",   '{"specs":{"weight":9.1,"color":"gray"}}'),
]
cursor.executemany(
    "INSERT INTO dbo.ProductCatalog (Name, JsonData) VALUES (%(name)s, %(json)s)",
    [{"name": n, "json": j} for n, j in rows]
)
conn.commit()

# Add a persisted computed column that extracts weight from JSON
cursor.execute("""
    ALTER TABLE dbo.ProductCatalog
    ADD ProductWeight AS CAST(JSON_VALUE(JsonData, '$.specs.weight') AS DECIMAL(5,2)) PERSISTED
""")

# Index the computed column for efficient range queries
cursor.execute("""
    CREATE INDEX IX_ProductCatalog_Weight
    ON dbo.ProductCatalog (ProductWeight)
""")
conn.commit()

Zapytanie za pomocą indeksowanej kolumny obliczeniowej

Filtruj bezpośrednio według obliczonej kolumny. Silnik zapytań używa indeksu zamiast skanować i analizować każdy dokument JSON.

cursor.execute("""
    SELECT Name, ProductWeight
    FROM dbo.ProductCatalog
    WHERE ProductWeight > %(min_weight)s
    ORDER BY ProductWeight
""", {"min_weight": 1.0})

for row in cursor:
    print(f"{row.Name}: {row.ProductWeight} kg")

# Cleanup
cursor.execute("DROP TABLE dbo.ProductCatalog")
conn.commit()

Wybierz między kolumnami relacyjnymi a pamięcią JSON

Używaj kolumn relacyjnych, gdy dane mają stały schemat, wymagają integralności referencyjnej, uczestniczą w JOIN-ach lub często pojawiają się w klauzulach WHERE. Używaj kolumn JSON (nvarchar(max)), gdy dane są rzadkie, różnią się między wierszami lub reprezentują elastyczną konfigurację lub metadane.

Kiedy stosować przetwarzanie JSON po stronie serwera, a kiedy po stronie klienta

Używaj funkcji JSON Microsoft SQL (JSON_VALUE, JSON_QUERY, ), OPENJSONgdy musisz filtrować, indeksować lub agregować pola JSON bez pobierania każdego wiersza do klienta. Ten wybór jest słuszny, gdy tylko podzbiór wierszy spełnia Twoje kryteria lub gdy chcesz obliczyć indeksy kolumnowe na ścieżkach JSON.

Używaj przetwarzania Python po stronie klienta (json.loads()), gdy pobierasz całe dokumenty i przetwarzasz je w logice aplikacji. To podejście sprawdza się dobrze, gdy potrzebujesz pełnego dokumentu i nie filtrujesz pól JSON w bazie danych.

Procesy w stylu dokumentu

Gdy Twoja aplikacja przechowuje i pobiera całe dokumenty, używaj serializacji po stronie Python i traktuj kolumnę JSON jako nieprzezroczystą pamięć. Przetwarzaj i zapytuj dokumenty w Python, pobierając i deserializując kompletne bloby JSON:

import json

# Create the settings table
cursor.execute("""
    CREATE TABLE #Settings (
        UserID INT PRIMARY KEY,
        ConfigJson NVARCHAR(MAX)
    )
""")

# Store a configuration document
config = {
    "theme": "dark",
    "notifications": {"email": True, "sms": False},
    "custom_fields": {"department": "Engineering", "cost_center": "CC-100"}
}

cursor.execute(
    "INSERT INTO #Settings (UserID, ConfigJson) VALUES (%(uid)s, %(cfg)s)",
    {"uid": 1, "cfg": json.dumps(config)}
)

# Retrieve and process in Python
cursor.execute("SELECT ConfigJson FROM #Settings WHERE UserID = %(uid)s", {"uid": 1})
row = cursor.fetchone()
config = json.loads(row.ConfigJson)
print(config["notifications"]["email"])  # True

Zapytania JSON po stronie serwera

Używaj funkcji Microsoft SQL JSON, gdy musisz filtrować, indeksować lub agregować pola JSON bez pobierania każdego wiersza. To podejście jest bardziej efektywne niż ładowanie wszystkich wierszy do Python, aby filtrować w pamięci:

  • JSON_VALUE wyodrębnia wartości skalarne i może stanowić podstawę dla indeksów kolumn obliczanych.
  • JSON_QUERY Ekstrahuje obiekty i tablice.
  • OPENJSON rozwija dane JSON do wierszy na potrzeby operacji JOIN i agregacji.
  • JSON_MODIFY Aktualizuje konkretne ścieżki bez przepisywania całego dokumentu.
# Filter by a JSON field server-side
cursor.execute("""
    SELECT UserID, ConfigJson
    FROM #Settings
    WHERE JSON_VALUE(ConfigJson, '$.custom_fields.department') = %(dept)s
""", {"dept": "Engineering"})

Dla często zapytywanych ścieżek JSON utwórz kolumnę obliczeniową z indeksem:

ALTER TABLE Settings
ADD Department AS JSON_VALUE(ConfigJson, '$.custom_fields.department');

CREATE INDEX IX_Settings_Department ON Settings(Department);

Najlepsze rozwiązania

Stosuj te wytyczne, aby niezawodnie korzystać z kolumn JSON.

Zweryfikowaj JSON przed przechowywaniem

Przed przechowywaniem zweryfikuj identyfikatory JSON oraz tabel/kolumn, aby zapobiec atakom iniekcyjnym.

def store_json_safely(cursor, table: str, json_column: str, data: dict):
    """Store JSON with validation."""
    # Validate identifiers to prevent SQL injection
    import re
    if not re.match(r'^[A-Za-z_][A-Za-z0-9_.]*$', table):
        raise ValueError(f"Invalid table name: {table}")
    if not re.match(r'^[A-Za-z_][A-Za-z0-9_]*$', json_column):
        raise ValueError(f"Invalid column name: {json_column}")

    json_str = json.dumps(data)
    
    # Check if valid JSON in Microsoft SQL
    cursor.execute("SELECT ISJSON(%(json)s)", {"json": json_str})
    if cursor.fetchval() != 1:
        raise ValueError("Invalid JSON")
    
    cursor.execute(f"INSERT INTO {table} ({json_column}) VALUES (%(json)s)", {"json": json_str})

Nie nadużywaj JSON

Używaj kolumn JSON do elastycznych lub rzadkich danych, takich jak preferencje użytkownika czy pola niestandardowe. Używaj kolumn relacyjnych do:

  • Często zapytywane dane.
  • Dane wymagające integralności referencyjnej.
  • Kolumny używane w klauzulach WHERE.

Poprawnie obsługuj None/NULL

Radzić sobie z brakującymi lub opcjonalnymi polami JSON, wstawiając wartości NULL dla kolumn, które nie zawierają danych.

cursor.execute("""
    CREATE TABLE #JsonOpt (
        ProductID INT IDENTITY, Name NVARCHAR(100), JsonData NVARCHAR(MAX)
    )
""")
cursor.execute("""
    INSERT INTO #JsonOpt (Name, JsonData) VALUES (
        'Widget Pro', '{"required_field":"value"}'
    )
""")

cursor.execute("""
    SELECT 
        Name,
        JSON_VALUE(JsonData, '$.optional_field') AS OptionalValue
    FROM #JsonOpt
""")

for row in cursor:
    # JSON_VALUE returns NULL if path doesn't exist
    value = row.OptionalValue or "default"
    print(f"{row.Name}: {value}")