Preskočiť na obsah
Návod

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.

4 min čítania 4 kroky 1 obrázok SQL Server 2016+
> [!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

![Plán s Nested Loops, outer a inner vstupom](uploads/2026/11/plan-o4.svg "Spodná vetva Nested Loops beží raz pre každý riadok hornej vetvy")

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._