MOC — dny 1 a 2
1. Představení Azure Databricks 2. Výběr a konfigurace compute 3. Objekty v Unity Catalogu 4. Zabezpečení Unity Catalogu 5. Governance Unity Catalogu 6. Datové modelování 7. Ingest dat
MOC — pokračování
8. Čištění a transformace dat 9. Data quality constraints · mimo osnovu 10. Datové pipelines 11. Lakeflow Jobs 12. Vývojový lifecycle · mimo osnovu 13. Monitoring a optimalizace
🏠 Rozcestník kurzu
Pod čarou — den 3
14. Průřez Microsoft Fabric 15. Power BI + Fabric + Databricks 16. Migrace z Oracle
🏠 Rozcestník kurzu
DP-750 · Kapitola 8

Čištění, transformace a load dat

Profiling, datové typy, duplicity a NULL, joins, denormalizace, pivoty a MERGE
🗂 Plnění dat do Unity Catalogu, ETL a Pipelines

Cíle kapitoly

  • Profilovat data pomocí SQL příkazů, DataFrame API a data quality monitoringu v Unity Catalogu
  • Zvolit vhodné datové typy sloupců pro ceny, časová razítka a vnořené struktury
  • Identifikovat a vyřešit duplicity a NULL hodnoty v SQL i PySparku
  • Kombinovat datasety pomocí joinů, set operátorů a pivot/unpivot transformací
  • Načíst transformovaná data do cílových tabulek přes INSERT, MERGE a overwrite strategie
8.1

Profiling dat: nejdřív poznat, pak čistit

Cesta z bronze do silver vrstvy není jeden příkaz, ale sekvence kroků — a první z nich je profiling: zjistit, co v datech vlastně je, než začnete cokoliv měnit. Kolik je NULLů, jaké jsou rozsahy hodnot, kde jsou duplicity. Bez toho čistíte naslepo.

BRONZE surová data 1. Profiling ANALYZE TABLE summarize, describe 2. Typy CAST na DECIMAL, TIMESTAMP, DATE 3. Dedup + NULL dropDuplicates, fillna, COALESCE 4. Transformace filtr, agregace, join, pivot SILVER čistá data 5. MERGE / INSERT Čištění a transformace: bronze → silver
Typický tok čištění dat: profiling odhalí problémy, kroky 2–4 je opraví a load přes MERGE nebo INSERT uloží výsledek do silver vrstvy.

Nejrychlejší nástroje máte přímo v SQL a DataFrame API. ANALYZE TABLE spočítá statistiky (počty řádků, min/max, počty NULLů, distinct counts) — mimochodem stejné statistiky, které používá query optimizer pro plánování dotazů:

SQL
-- statistiky pro vsechny sloupce
ANALYZE TABLE sales.transactions COMPUTE STATISTICS FOR ALL COLUMNS;

-- detail jednoho sloupce: min, max, pocet NULL, distinct count
DESC EXTENDED sales.transactions amount;
PYTHON
df = spark.table("sales.transactions")

# count, mean, stddev, min, max pro numericke sloupce
df.describe().show()

# interaktivni profil vsech sloupcu vcetne histogramu
dbutils.data.summarize(df)

Utilita dbutils.data.summarize vykreslí interaktivní přehled distribucí, missing values a častých hodnot pro celý DataFrame — pro ad-hoc průzkum v notebooku je to nejrychlejší start.

Nad rámec příkazů nabízí Unity Catalog data quality monitoring se dvěma větvemi: anomaly detection (zapnete na úrovni schématu, automaticky hlídá freshness a completeness všech tabulek) a data profiling (hloubková per-table analýza distribucí, NULL procent a driftu, konfiguruje se pro jednotlivé tabulky přes záložku Quality v Catalog Exploreru nebo přes SDK). Profil generuje dvě metrické tabulky — profile metrics a drift metrics (KS test pro numerické, chi-kvadrát pro kategorické sloupce).

💡
Tip: praktická dělba práce — anomaly detection jako plošný early-warning systém přes celé schéma, data profiling jen pro kritické tabulky, kde potřebujete historické trendy. U Unity Catalog managed tabulek navíc statistiky průběžně aktualizuje predictive optimization.

Na co se v profilu dívat: vysoká procenta NULL ve sloupcích, které mají být povinné; „unikátní" identifikátory, které unikátní nejsou; hodnoty mimo očekávané meze (záporné ceny); a změny distribucí v čase (data drift).

8.2

Volba datových typů sloupců

Špatný typ znamená ztrátu přesnosti, zbytečné úložiště nebo zablokované operace. Bronze vrstva často drží vše jako string — v silver je čas typy narovnat.

U celých čísel volte podle rozsahu hodnot: TINYINT (1 B, do 127) pro stavové kódy, INT (4 B) pro běžné identifikátory, BIGINT (8 B) pro velká ID. U desetinných čísel je rozhodnutí zásadnější:

TypCharakterPoužití
DECIMAL(p,s)exaktní přesnost (p číslic celkem, s za čárkou)finanční data, ceny
DOUBLE / FLOATaproximace v binární reprezentacivědecké výpočty, senzory
⚠️
Pozor: ceny nikdy do DOUBLE. Floating point ukládá hodnoty binárně a u desítkových čísel typu 0.1 zavádí zaokrouhlovací chyby — v účetnictví se pak nedopočítáte. DECIMAL(10,2) pokryje částky do 99 999 999,99 zcela přesně.

U časových údajů rozhoduje, zda potřebujete časovou zónu: DATE pro kalendářní data bez času, TIMESTAMP pro události napříč zónami (interně normalizuje do UTC a při čtení aplikuje session timezone) a TIMESTAMP_NTZ, když má čas zůstat přesně tak, jak byl zadán (např. lokální čas schůzky). Kdo čeká, že TIMESTAMP vrátí přesně to, co uložil, může být překvapen — s jinou session timezone uvidí jinou hodnotu téhož okamžiku.

Pro vnořená data slouží komplexní typy: ARRAY<STRING> (seznam hodnot stejného typu — tagy produktu), MAP<STRING, STRING> (dynamické klíč–hodnota páry) a STRUCT (pevná sada pojmenovaných polí — adresa). Šetří joiny, ale komplikují některé operace; rozhodujte podle dotazovacích vzorů.

SQL
-- explicitni CAST hned pri loadu: chyby se projevi brzy, ne az v reportech
INSERT INTO silver.listings
SELECT
    CAST(raw_id AS INT) AS id,
    CAST(raw_price AS DECIMAL(12,2)) AS price,
    CAST(raw_listed_at AS TIMESTAMP) AS listed_at
FROM bronze.listings;
💡
Tip: castujte explicitně a co nejdřív v pipeline. Implicitní konverze umí projít tiše i tam, kde se data ztrácejí. Explicitní CAST je zároveň dokumentace záměru — a typová chyba při loadu je levnější než špatné číslo v dashboardu.
8.3

Řešení duplicit a NULL hodnot

Duplicity vznikají opakovaným ingestem, integračními chybami nebo souběžnými zápisy. Nejdřív je najděte — klasicky přes GROUP BY ... HAVING COUNT(*) > 1, elegantněji přes QUALIFY s window funkcí, která vrátí rovnou celé duplicitní řádky:

SQL
-- najit duplicitni radky (vcetne vsech sloupcu)
SELECT * FROM sales.customers
QUALIFY COUNT(*) OVER (PARTITION BY customer_id, email) > 1;

-- ponechat jen nejnovejsi verzi kazdeho zakaznika
SELECT * FROM sales.customers
QUALIFY ROW_NUMBER() OVER (
    PARTITION BY customer_id ORDER BY updated_at DESC
) = 1;
PYTHON
# odstraneni duplicit podle klicovych sloupcu (necha prvni vyskyt)
df_clean = df.dropDuplicates(["customer_id", "email"])

# uplna deduplicace pres vsechny sloupce
df_unique = df.distinct()

# zobrazit jen prebytecne duplicity: original minus deduplikovana verze
df_duplicates = df.exceptAll(df.dropDuplicates(["customer_id", "email"]))
⚠️
Pozor: dropDuplicates() nechá „první" výskyt — ale který to je, není deterministické. Když záleží na tom, která verze přežije (typicky ta nejnovější), použijte vzor s ROW_NUMBER() ... ORDER BY updated_at DESC. Přesně tohle je rozdíl mezi „smazali jsme duplicity" a „nechali jsme si tu správnou".

NULL hodnoty reprezentují chybějící data a strategie závisí na kontextu: NULLy v povinných polích (identifikátory) → řádek zahodit; NULLy v numerických polích pro agregace → doplnit nulou či průměrem; NULLy v nepovinných kategoriích → placeholder „Unknown".

PYTHON
# zahodit radky s NULL v klicovych sloupcich
df_clean = df.dropna(subset=["customer_id", "amount"])

# doplnit ruzne defaulty pro ruzne sloupce
df_filled = df.fillna({
    "amount": 0,
    "status": "Unknown",
    "quantity": 1
})

V SQL zajistí totéž COALESCE(amount, 0). A jedna zrada navíc: v Pythonu nefiltrujte NULLy přes != None — NULL porovnání v SQL sémantice nevrací true/false, ale zase NULL. Vždy isNull() / isNotNull(), v SQL IS [NOT] NULL.

exceptAll vs. except — v čem je rozdíl▼

exceptAll() je množinový rozdíl, který zachovává násobnost: pokud je záznam v originálu třikrát a v deduplikované verzi jednou, vrátí přesně dva přebytečné výskyty. Obyčejný except() by vrátil distinct výsledek — o počtech duplicit byste přišli. Proto se pro audit duplicit („kolik řádků deduplicace reálně odstraní?") hodí právě exceptAll.

Dokumentujte, co jste s NULLy a duplicitami udělali — budoucí analytik musí vědět, proč v datech vidí „Unknown" a kam zmizely řádky.

8.4

Transformace filtry a agregacemi

Filtrování, seskupování a agregace jsou základní trojice tvarování dat. V PySparku jsou filter() a where() identické metody; podmínky se skládají operátory & a | — každou podmínku do závorek:

PYTHON
from pyspark.sql.functions import col, count, sum, avg, max

# retezeni: filtr, seskupeni, agregace, razeni v jedne pipeline
df_result = (
    df_order
    .filter((col("o_orderstatus") == "F") & (col("o_totalprice") > 50000))
    .groupBy("o_orderpriority")
    .agg(
        count("o_orderkey").alias("n_orders"),
        sum("o_totalprice").alias("total_revenue"),
        avg("o_totalprice").alias("avg_order_value")
    )
    .sort(col("n_orders").desc())
)
display(df_result)

Díky lazy evaluation se celý řetězec vyhodnotí až při akci (display(), show()) a Spark si předtím celou sekvenci zoptimalizuje. SQL ekvivalent je klasika — WHERE pro řádky, GROUP BY pro skupiny, HAVING pro filtrování až po agregaci:

SQL
SELECT id, SUM(quantity) AS total_quantity
FROM dealer
GROUP BY id
HAVING SUM(quantity) > 20
ORDER BY total_quantity DESC;

Pravidlo z GROUP BY: každý sloupec v SELECT musí být buď v GROUP BY, nebo obalený agregační funkcí. Běžné agregace: count(), sum(), avg(), min(), max().

💡
Tip: filtry dávejte co nejdřív v řetězci — každá další operace pak zpracovává méně dat. Spark leccos přeuspořádá sám (predicate pushdown), ale čitelnosti a výkonu pomůžete, když pipeline logicky teče od redukce k agregaci.
8.5

Joins a set operátory

Dvě rodiny kombinování datasetů: joiny spojují tabulky horizontálně (přidávají sloupce podle klíče), set operátory vertikálně (skládají řádky dotazů se stejným schématem).

JOINY — kombinace sloupců podle klíče INNER jen shody LEFT celá levá + NULLy FULL vše z obou stran ANTI levá bez shody SET OPERÁTORY — skládání řádků stejného schématu dotaz A dotaz B UNION [ALL] A + B (bez/s duplicitami) INTERSECT jen společné řádky EXCEPT / MINUS A minus řádky z B
Joiny kombinují tabulky horizontálně podle klíče (zvýrazněná oblast = výsledek), set operátory pracují vertikálně nad celými výsledky dotazů.
JoinVracíTypický případ
INNERjen shodné řádky obou tabulekobjednávky s platným zákazníkem
LEFTcelou levou; NULL kde není shodavšichni zákazníci, i bez objednávek
FULLvše z obou; NULL na obou stranáchhledání děr na obou stranách
SEMIlevé řádky se shodou (bez sloupců pravé)zákazníci, kteří objednali
ANTIlevé řádky bez shodyzákazníci, kteří nikdy neobjednali
CROSSkartézský součingenerování všech kombinací
PYTHON
# left join; spolecny nazev klice jako string = bez duplicitniho sloupce
df_left = df_employee.join(df_department, on="deptno", how="left")

# anti join: zamestnanci bez existujiciho oddeleni (data quality check)
df_anti = df_employee.join(df_department, on="deptno", how="anti")

Set operátory: UNION defaultně odstraňuje duplicity, UNION ALL je nechává (a je rychlejší — přeskočí deduplikaci). INTERSECT vrací společné řádky, EXCEPT (synonymum MINUS) řádky prvního dotazu bez druhého.

⚠️
Pozor: PySpark metoda union() se chová jako SQL UNION ALL — duplicity nechává. Kdo čeká SQL sémantiku, musí doplnit .distinct(). A při řetězení set operací má INTERSECT vyšší prioritu než UNION a EXCEPT — závorky nejsou okrasa.
8.6

Denormalizace a pivoty

Denormalizace zplošťuje normalizované tabulky do širších celků: dimenze se předjoinují do faktové tabulky, takže dotazy na ni už žádné joiny nepotřebují. Typicky se dělá při stavbě gold vrstvy a datových martů — cena je vyšší úložiště a nutnost refreshovat při změně zdrojů.

💡
Tip: denormalizujte v gold vrstvě, kde vládne rychlost dotazů; v bronze a silver držte normalizované struktury kvůli flexibilitě. Viz medallion architektura v úvodních kapitolách.

PIVOT otáčí unikátní hodnoty sloupce do samostatných sloupců („long" → „wide") — ideální pro křížové reporty. Vyžaduje agregační funkci, protože víc zdrojových řádků může mířit do téže buňky:

SQL
-- kvartaly z radku do sloupcu
SELECT year, region, Q1, Q2, Q3, Q4
FROM quarterly_sales
PIVOT (
  SUM(revenue)
  FOR quarter IN (1 AS Q1, 2 AS Q2, 3 AS Q3, 4 AS Q4)
);

Výsledek: každý region má kvartální tržby vedle sebe v jednom řádku. V jednom pivotu lze použít i víc agregací najednou (SUM(revenue) AS total, AVG(revenue) AS avg) — vzniknou sloupce Q1_total, Q1_avg atd.

UNPIVOT je opačný směr („wide" → „long"): měsíční sloupce se přetočí do párů název–hodnota, což je formát vhodný pro agregace a ML pipelines:

SQL
SELECT * FROM regional_targets
UNPIVOT (
  target_amount FOR month IN (jan, feb, mar, apr)
);

Z každého měsíčního sloupce vznikne řádek s hodnotou target_amount a názvem měsíce ve sloupci month. NULL hodnoty se defaultně vynechávají — pokud je chcete, přidejte UNPIVOT INCLUDE NULLS. Sloupcům lze dát aliasy (jan AS `January`).

Kdy který tvar dat▼

Dlouhý (long) formát je přátelský k agregacím, window funkcím a strojovému učení — jeden fakt na řádek. Široký (wide) formát je přátelský k lidem — srovnání vedle sebe v reportu. V praxi pipeline často unpivotuje zdrojová „excelová" data do dlouhého formátu, transformuje je, a na konci pivotuje výsledek pro prezentaci. UNPIVOT klauzule vyžaduje Databricks Runtime 12.2 LTS+ — na aktuálních runtimech to není omezení, ale u starých clusterů to ověřte.

8.7

Load: INSERT, append a MERGE

Poslední krok pipeline: dostat vyčištěná data do cílové tabulky. Volba strategie závisí na vztahu nových dat k existujícím:

ScénářOperace
jen nové záznamy (denní logy, event streamy)INSERT INTO / mode("append")
full refresh (číselníky, agregace)INSERT OVERWRITE / mode("overwrite")
mix updatů a insertů (CDC, dimenze)MERGE INTO
selektivní náhrada podle podmínkyREPLACE WHERE / REPLACE USING

MERGE INTO je nejuniverzálnější: spáruje zdrojové řádky s cílovými podle klíče v ON a pro každou větev (matched / not matched) definuje akci — proto se mu říká upsert:

source customer_updates target customers (Delta) shoda klíče? ON target.id = source.id WHEN MATCHED UPDATE SET … / DELETE volitelně s podmínkou AND WHEN NOT MATCHED INSERT (…) VALUES (…) nebo INSERT * ano ne target po upsertu MERGE INTO — jedna transakce, dvě větve
MERGE páruje zdroj s cílem podle klíče: shodné řádky projdou větví WHEN MATCHED (UPDATE/DELETE), nové větví WHEN NOT MATCHED (INSERT) — vše v jedné atomické operaci.
SQL
MERGE INTO customers AS target
USING customer_updates AS source
ON target.customer_id = source.customer_id
WHEN MATCHED AND source.price <> target.price THEN
    UPDATE SET target.price = source.price,
               target.updated_at = current_timestamp()
WHEN MATCHED AND source.discontinued = true THEN
    DELETE
WHEN NOT MATCHED THEN
    INSERT *;

V PySparku poskytuje totéž Delta Lake API — DeltaTable.forName(...).merge(...) s metodami whenMatchedUpdateAll() a whenNotMatchedInsertAll() pro případ shodných schémat.

⚠️
Pozor: MERGE vyžaduje unikátní shodu — pokud na jeden cílový řádek míří víc zdrojových řádků, operace selže. Zdroj proto před mergem deduplikujte (sekce 8.3). A stejně tak: INSERT INTO duplicity nekontroluje — stejný batch načtený dvakrát znamená dvojnásobné řádky.
Selektivní overwrite: REPLACE WHERE a REPLACE USING▼

Mezi „append" a „přepsat vše" existuje střední cesta. INSERT INTO ... REPLACE WHERE podmínka atomicky smaže řádky odpovídající podmínce a vloží nové — ideální pro opravu konkrétního časového okna. Novější REPLACE USING (sloupce) nahradí jen ty „partition" hodnot, kterých se příchozí data dotýkají, a podle dokumentace funguje na všech typech compute včetně SQL warehouse a serverless; dokumentace ho doporučuje před klasickým INSERT OVERWRITE PARTITION s partitionOverwriteMode=dynamic, který je omezen na classic compute. Varianty REPLACE ON a přesné požadavky na runtime verze se vyvíjejí — aktuální stav ověřte v dokumentaci.

✓
Shrnutí: profiling → typy → dedup/NULL → transformace → load. Append pro nové záznamy, overwrite pro full refresh, MERGE pro upserty, REPLACE WHERE/USING pro chirurgické zásahy. Tenhle řetězec je jádro každé bronze→silver pipeline — v deklarativních pipelines ho pak jen zabalíte do spravovaného rámce (viz kapitola 7).
8.8

Lab 8: Cleansing and Transforming Real Estate Data in Unity Catalog (45 minut)

Realitní kancelář Pristine Properties působící ve velkých nizozemských městech nasbírala inzeráty nemovitostí z poboček a satelitních databází — s nepřesnými cenami, duplicitními inzeráty, NULLy v klíčových polích a statistikami trhu v širokém formátu. Vaším úkolem je data zprofilovat, narovnat datové typy cen a timestampů, vyřešit duplicity a chybějící hodnoty v PySparku, spojit inzeráty s makléři a prodeji přes inner/left joiny a přetvarovat statistiky pomocí SQL PIVOT/UNPIVOT.

▶ Otevřít lab na GitHubu (EN) · Learn modul k tématu — s krátkými výukovými videy

ℹ️
Poznámka: na kurzu lab prochází lektor jako demo. Postup je krokový a samoobslužný — pokud se na něj v rámci dne nedostane, projdete si ho samostatně.
✓
Sebekontrola: 1) Proč pro ceny nemovitostí použijete DECIMAL a ne DOUBLE, a co rozhoduje mezi TIMESTAMP a TIMESTAMP_NTZ? 2) Jak zajistíte, že po deduplikaci přežije právě nejnovější verze inzerátu? 3) Kdy sáhnete po LEFT JOIN místo INNER JOIN při spojování inzerátů s prodeji — a co udělá UNPIVOT se statistikami trhu v širokém formátu?