> [!TIP] > Recept z [kuchárky exekučných plánov](script.php?id=64&s=exekucne-plany-kucharka-zacni-tu). Ukážky bežia v testovacej databáze [PlanLab](script.php?id=65&s=planlab-testovacia-databaza-pre-recepty-exekucnych-planov) – nikdy ich nespúšťaj na produkcii. ## Ako to vyzerá v pláne  V SSMS: **Nested Loops** s dvoma vstupmi. **Horný** vstup je *outer* (vonkajší), **spodný** je *inner* (vnútorný). Logická operácia môže byť Inner Join, Left Outer Join, Left Semi Join (EXISTS), Left Anti Semi Join (NOT EXISTS)… ## Čo to znamená Je to dvojitý cyklus: pre **každý riadok** z horného vstupu sa vykoná **celý** spodný vstup. Ak horný vstup dá 10 riadkov a spodný je rýchly seek, je to najrýchlejší spôsob spojenia. Ak horný vstup dá milión riadkov, spodná vetva beží miliónkrát. ### Do hĺbky - **Number of Executions** na operátoroch spodnej vetvy = počet riadkov z horného vstupu (pokiaľ nie je medzi nimi spool alebo optimalizácia rewind). - **Outer References** (vlastnosť Nested Loops) – hodnoty z horného riadku, ktoré sa posielajú dolu ako parameter (typicky do Seek Predicates vnútorného seeku). - **Estimated Number of Rows Per Execution** na vnútornej strane je odhad na **jedno** vykonanie. Celkový odhad = odhad × *Estimated Number of Executions*. Bez tohto prepočtu vyzerajú odhady na inner strane falošne zle (alebo dobre). - **WithUnorderedPrefetch / WithOrderedPrefetch** – SQL Server si dopredu načítava stránky pre vnútornú stranu (pomáha pri čítaní z disku). - Optimalizátor volí Nested Loops, keď **odhaduje málo** riadkov na hornom vstupe. Zlý odhad (1 namiesto 100 000) je najčastejší dôvod pomalých Nested Loops. - **Row goal** (TOP, EXISTS, `OPTION (FAST n)`) zvýhodňuje Nested Loops, lebo prvé riadky prídu hneď. - Nested Loops nepotrebuje memory grant (na rozdiel od Hash Match a Sort). ## Kedy je to v poriadku / kedy problém | V poriadku | Problém | |---|---| | horný vstup malý (desiatky, stovky riadkov) | horný vstup obrovský, hoci odhad bol malý | | vnútorná strana je seek | vnútorná strana je sken (sken × počet vykonaní) | | odhad horného vstupu ≈ skutočnosť | Number of Executions v desaťtisícoch | | TOP / EXISTS potrebuje prvé riadky rýchlo | dotaz potrebuje všetky riadky a vnútorná strana je drahá | ## Ako si to vyskúšať ```sql USE PlanLab; SET STATISTICS IO ON; -- 1) Málo zákazníkov → Nested Loops so seekom na vnútornej strane SELECT c.Name, o.OrderID, o.OrderDate FROM dbo.Customers AS c JOIN dbo.Orders AS o ON o.CustomerID = c.CustomerID WHERE c.CustomerID BETWEEN 100 AND 110; -- 2) Všetci zákazníci → optimalizátor zvolí Hash Match SELECT c.Name, o.OrderID FROM dbo.Customers AS c JOIN dbo.Orders AS o ON o.CustomerID = c.CustomerID; -- 3) Vynútené Nested Loops pre všetkých (len na ukážku): pozri Number of Executions a logical reads SELECT c.Name, o.OrderID FROM dbo.Customers AS c JOIN dbo.Orders AS o ON o.CustomerID = c.CustomerID OPTION (LOOP JOIN); -- 4) EXISTS → Left Semi Join (stačí prvý nájdený riadok) SELECT c.CustomerID FROM dbo.Customers AS c WHERE EXISTS (SELECT 1 FROM dbo.Orders AS o WHERE o.CustomerID = c.CustomerID AND o.Status = 4); ``` Spusti s **Include Actual Execution Plan** (Ctrl+M) a pri vnútornom operátore porovnaj *Number of Executions*, *Estimated Number of Rows Per Execution* a *Actual Number of Rows for All Executions*. ## Čo s tým 1. Over **odhad horného vstupu**. Ak je 1 a skutočnosť 50 000, rieš príčinu odhadu ([zlé odhady](script.php?id=84&s=zle-odhady-riadkov-odhad-vs-skutocnost-a-statistiky), [parameter sniffing](script.php?id=87&s=parameter-sniffing-jeden-plan-pre-rozne-hodnoty-psp-2022-oppo-2025), [table variable](script.php?id=86&s=table-variable-vs-temp-tabulka-odhady-a-deferred-compilation-2019)). 2. Ak je vnútorná strana sken, chýba index na stĺpce spojenia (alebo je spojenie [non-SARGable](script.php?id=89&s=non-sargable-podmienky-funkcia-na-stlpci-like-x-or)). 3. Ak na vnútornej strane vidíš **Index Spool**, optimalizátor si stavia dočasný index sám – trvalý index pomôže ([Spools](script.php?id=78&s=spools-table-index-row-count-a-window-spool-eager-vs-lazy)). 4. Join hint je posledná možnosť a pri zmene dát škodí: `OPTION (HASH JOIN)` zakáže ostatné typy spojení v celom dotaze, hint priamo vo FROM (`INNER LOOP JOIN`) navyše zafixuje aj poradie spojení. ## Súvisiace - [Key Lookup a RID Lookup](script.php?id=70&s=key-lookup-a-rid-lookup-dohladanie-chybajucich-stlpcov) - [Hash Match](script.php?id=73&s=hash-match-hash-join-a-hash-aggregate-build-a-probe) - [Merge Join](script.php?id=72&s=merge-join-spajanie-zoradenych-vstupov) - [Adaptive Join](script.php?id=74&s=adaptive-join-hash-match-alebo-nested-loops-az-za-behu-2017) - [Zlé odhady riadkov](script.php?id=84&s=zle-odhady-riadkov-odhad-vs-skutocnost-a-statistiky) ## Zdroje - [Microsoft Learn – Showplan Logical and Physical Operators Reference](https://learn.microsoft.com/sql/relational-databases/showplan-logical-and-physical-operators-reference) - [Hugo Kornelis – SQL Server Execution Plan Reference (SQLServerFast)](https://sqlserverfast.com/epr/) - [Paul White – SQLkiwi (články o optimalizátore a operátoroch)](https://www.sql.kiwi/) - [Grant Fritchey – SQL Server Execution Plans, 3. vydanie (Redgate, e-kniha zadarmo)](https://www.red-gate.com/simple-talk/books/sql-server-execution-plans-third-edition/) _Texty sú vlastné, tvrdenia overené proti uvedeným zdrojom (11/2026). T-SQL je staticky skontrolovaný, nie spustený na živom serveri – vyskúšaj ho najprv v PlanLab._
Nested Loops – vnorené cykly (outer a inner vstup)
Nested Loops v pláne: outer vs. inner vstup, Number of Executions, Outer References, odhad na jedno vykonanie, prefetch, row goal a kedy je vnorený cyklus problém.