TripPin časť 10 — Základné skladanie dotazov

Tento viacdielny návod zahŕňa vytvorenie nového rozšírenia zdroja údajov pre Power Query. Výukový program je určený na postupné — každej lekcie, ktorá stavia na konektore vytvorenom v predchádzajúcich lekciách a postupne pridáva nové možnosti do konektora.

V tejto lekcii budete:

  • Naučte sa základy skladania dotazov
  • Informácie o Table.View funkcii
  • Replikovať skladacie manipulátory dotazu OData pre:
  • $top
  • $skip
  • $count
  • $select
  • $orderby

Jednou z výkonných vlastností jazyka M je jeho schopnosť posunúť transformačnú prácu na základné zdroje údajov. Táto funkcia sa označuje ako skladanie dotazov (iné nástroje/technológie sa vzťahujú aj na podobnú funkciu ako Predikate Pushdown alebo Delegovanie dotazov). Pri vytváraní vlastného konektora, ktorý používa funkciu M so vstavanými možnosťami skladania dotazov, ako je napríklad OData.Feed alebo , konektor automaticky Odbc.DataSource zdedí túto možnosť zadarmo.

Tento výukový program bude replikovať vstavané správanie skladania dotazov pre OData implementáciou manipulátorov funkcií pre Table.View túto funkciu. Táto časť tutoriálu bude implementovať niektoré z jednoduchších manipulátorov na implementáciu (to znamená tie, ktoré nevyžadujú analýzanie výrazu a sledovanie stavu).

Ďalšie informácie o možnostiach dotazu, ktoré môže služba OData ponúkať, nájdete v téme Konvencie URL služby OData v4.

Poznámka

Ako je uvedené vyššie, OData.Feed funkcia automaticky poskytne možnosti skladania dotazov. Keďže rad TripPin zaobchádza so službou OData ako s pravidelným rozhraním REST API, Web.Contents pričom namiesto toho budete musieť OData.Feed implementovať skladacie manipulátory dotazu sami. Pre skutočné použitie odporúčame používať vždy, OData.Feed keď je to možné.

Ďalšie informácie o skladaní dotazov v M nájdete v dokumentácii Table.View.

Používanie položky Tabuľka.Zobraziť

Funkcia Table.View umožňuje vlastnému konektoru prepísať predvolené obslužné zariadenia transformácie pre zdroj údajov. Implementácia Table.View bude poskytovať funkciu pre jedného alebo viacerých podporovaných manipulátorov. Ak je obsluha nezaťažená alebo vráti error počas hodnotenia, motor M sa vráti späť na predvolený obslužný nástroj.

Ak vlastný konektor používa funkciu, ktorá nepodporuje implicitné skladanie dotazov, napríklad Web.Contents predvolené obslužné zariadenia transformácie sa budú vždy vykonávať lokálne. Ak rozhranie REST API, ku ktorému sa pripájate, podporuje parametre dotazu ako súčasť dotazu, Table.View umožní vám pridať optimalizácie, ktoré umožňujú tlačiť transformačné práce do služby.

Table.ViewFunkcia má nasledujúci podpis:

Table.View(table as nullable table, handlers as record) as table

Vaša implementácia zabalí vašu hlavnú funkciu zdroja údajov. Existujú dva potrebné manipulátory Table.View pre:

  • GetType—vráti očakávané table type výsledky dotazu
  • GetRows—vráti skutočný table výsledok funkcie zdroja údajov

Najjednoduchšia implementácia by bola podobná:

TripPin.SuperSimpleView = (url as text, entity as text) as table =>
    Table.View(null, [
        GetType = () => Value.Type(GetRows()),
        GetRows = () => GetEntity(url, entity)
    ]);

Aktualizujte TripPinNavTable funkciu na volanie TripPin.SuperSimpleView GetEntity namiesto:

withData = Table.AddColumn(rename, "Data", each TripPin.SuperSimpleView(url, [Name]), type table),

Ak znova spustíte jednotkové testy, uvidíte, že správanie vašej funkcie sa nezmenilo. V tomto prípade vaša implementácia Table.View jednoducho prechádza výzvou na GetEntity . Keďže ste ešte neimplementovali žiadne transformačné manipulátory (zatiaľ), pôvodný url parameter zostáva nedotknutý.

Úvodná implementácia tabuľky.View

Vyššie uvedená implementácia Table.View je jednoduchá, ale nie veľmi užitočná. Nasledujúca implementácia sa použije ako základ, — neimplementuje žiadnu funkciu skladania, ale má lešenie, ktoré budete musieť urobiť.

TripPin.View = (baseUrl as text, entity as text) as table =>
    let
        // Implementation of Table.View handlers.
        //
        // We wrap the record with Diagnostics.WrapHandlers() to get some automatic
        // tracing if a handler returns an error.
        //
        View = (state as record) => Table.View(null, Diagnostics.WrapHandlers([
            // Returns the table type returned by GetRows()
            GetType = () => CalculateSchema(state),

            // Called last - retrieves the data from the calculated URL
            GetRows = () => 
                let
                    finalSchema = CalculateSchema(state),
                    finalUrl = CalculateUrl(state),

                    result = TripPin.Feed(finalUrl, finalSchema),
                    appliedType = Table.ChangeType(result, finalSchema)
                in
                    appliedType,

            //
            // Helper functions
            //
            // Retrieves the cached schema. If this is the first call
            // to CalculateSchema, the table type is calculated based on
            // the entity name that was passed into the function.
            CalculateSchema = (state) as type =>
                if (state[Schema]? = null) then
                    GetSchemaForEntity(entity)
                else
                    state[Schema],

            // Calculates the final URL based on the current state.
            CalculateUrl = (state) as text => 
                let
                    urlWithEntity = Uri.Combine(state[Url], state[Entity])
                in
                    urlWithEntity
        ]))
    in
        View([Url = baseUrl, Entity = entity]);

Ak sa pozriete na hovor Table.View na , okolo záznamu sa zobrazí ďalšia funkcia obalu handlersDiagnostics.WrapHandlers . Táto funkcia pomocníka sa nachádza v module Diagnostika (ktorý bol zavedený v predchádzajúcom tutoriále) a poskytuje vám užitočný spôsob, ako automaticky sledovať všetky chyby vznesené jednotlivými obsluhami.

GetTypeFunkcie a funkcie boli aktualizované GetRows tak, aby využívali dve nové funkcie pomocníka — CalculateSchema a CaculateUrl . Práve teraz sú implementácie týchto funkcií pomerne — jednoduché, všimnete si, že obsahujú časti toho, čo predtým GetEntity vykonala funkcia.

Nakoniec si všimnete, že definujete vnútornú funkciu ( View ), ktorá akceptuje state parameter. Keď implementujete viac manipulátorov, budú rekurzívne volať vnútornú View funkciu, aktualizovať a prechádzať, state keď idú.

Znova aktualizujte TripPinNavTable funkciu, nahradíte hovor TripPin.SuperSimpleView hovorom na novú TripPin.View funkciu a znova spustite testy jednotky. Zatiaľ neuvidíte žiadnu novú funkčnosť, ale teraz máte solídny základ pre testovanie.

Implementácia skladacieho dotazu

Keďže motor M sa automaticky vráti k lokálnemu spracovaniu, keď dotaz nie je možné zložiť, musíte vykonať niekoľko ďalších krokov na overenie Table.View správneho fungovania vašich psovodov.

Manuálny spôsob overenia skladacieho správania je sledovať url požiadavky, ktoré testy jednotky vykonajú pomocou nástroja, ako je Fiddler. Prípadne diagnostické zapisovanie do denníka, ku ktorému ste TripPin.Feed pridali, bude emitovať úplnú webovú adresu, ktorá sa spúšťa, čo by malo obsahovať parametre reťazca dotazu OData, ktoré budú pridávať obsluhy.

Automatizovaný spôsob overenia skladania dotazu je vynútiť, aby sa vykonanie testu jednotky nepodarilo, ak sa dotaz úplne nezloží. Môžete to urobiť otvorením vlastností projektu a nastavením chyby pri skladaní zlyhania na true. Ak je toto nastavenie povolené, každý dotaz, ktorý vyžaduje lokálne spracovanie, má za následok nasledujúcu chybu:

Nedokázali sme zložiť výraz k zdroju. Skúste jednoduchší výraz.

Môžete to otestovať pridaním nového Fact testovacieho súboru jednotky, ktorý obsahuje jednu alebo viac transformácií tabuľky.

// Query folding tests
Fact("Fold $top 1 on Airlines", 
    #table( type table [AirlineCode = text, Name = text] , {{"AA", "American Airlines"}} ), 
    Table.FirstN(Airlines, 1)
)

Poznámka

Nastavenie Chyba pri skladaní zlyhania je prístup "všetko alebo nič". Ak chcete otestovať dotazy, ktoré nie sú určené na zkladanie ako súčasť jednotkových testov, budete musieť pridať určitú podmienenú logiku, aby ste podľa toho povolili alebo zakázali testy.

Zostávajúce časti tohto tutoriálu pridajú nový obslužný program Table.View. Budete mať prístup Test Driven Development (TDD), kde najprv pridáte neúspešné jednotkové testy a potom implementujete kód M na ich vyriešenie.

V každej sekcii popisu popisu funkcií, ktoré poskytuje obsluha, ekvivalentná syntax dotazu OData, testy jednotiek a implementácia. Pomocou vyššie popísaného lešenia si každá implementácia obslužného programu vyžaduje dve zmeny:

  • Pridanie obslužného osnovača do table.view, ktorý aktualizuje state záznam.
  • Úprava CalculateUrl na načítanie hodnôt z state parametrov url a/alebo reťazca dotazu a pridanie do parametrov reťazca URL alebo dotazu.

Manipulačný stôl.FirstN s OnTake

Obslužný prostriedok OnTake dostane count parameter, čo je maximálny počet riadkov, ktoré sa majú prijať. V výrazoch OData to môžete preložiť do parametra dotazu $top.

Použijete nasledujúce jednotkové testy:

// Query folding tests
Fact("Fold $top 1 on Airlines", 
    #table( type table [AirlineCode = text, Name = text] , {{"AA", "American Airlines"}} ), 
    Table.FirstN(Airlines, 1)
),
Fact("Fold $top 0 on Airports", 
    #table( type table [Name = text, IataCode = text, Location = record] , {} ), 
    Table.FirstN(Airports, 0)
),

Tieto testy sa používajú Table.FirstN na filtrovanie na výsledok nastavený na prvé číslo X riadkov. Ak spustíte tieto testy s chybou pri skladaní, ktorá je nastavená na False (predvolená), testy by mali byť úspešné, ale ak spustíte Fiddler (alebo skontrolujete denníky sledovania), uvidíte, že odoslaná požiadavka neobsahuje žiadne parametre dotazu OData.

Sledovanie diagnostiky.

Ak nastavíte chybu pri sklopení zlyhanie , True zlyhajú s "Skúste jednoduchší výraz." . Ak to chcete opraviť, definujete svoj prvý obslužný prostriedok Table.View pre OnTake .

Obslužný prostriedok OnTake vyzerá takto:

OnTake = (count as number) =>
    let
        // Add a record with Top defined to our state
        newState = state & [ Top = count ]
    in
        @View(newState),

CalculateUrlFunkcia sa aktualizuje tak, aby extrahovala hodnotu zo Top state záznamu a nastavila správny parameter v reťazci dotazu.

// Calculates the final URL based on the current state.
CalculateUrl = (state) as text => 
    let
        urlWithEntity = Uri.Combine(state[Url], state[Entity]),

        // Uri.BuildQueryString requires that all field values
        // are text literals.
        defaultQueryString = [],

        // Check for Top defined in our state
        qsWithTop =
            if (state[Top]? <> null) then
                // add a $top field to the query string record
                defaultQueryString & [ #"$top" = Number.ToText(state[Top]) ]
            else
                defaultQueryString,

        encodedQueryString = Uri.BuildQueryString(qsWithTop),
        finalUrl = urlWithEntity & "?" & encodedQueryString
    in
        finalUrl

Pri opätovnom vrátení jednotkových testov môžete vidieť, že adresa URL, ku ktorému teraz pristupujete, obsahuje $top parameter. (Všimnite si, že v dôsledku kódovania URL $top sa zobrazuje ako , ale služba %24top OData je dostatočne inteligentná na to, aby ju automaticky konvertovala).

Sledovanie diagnostiky s vrcholom.

Manipulačný stôl.Preskočiť s OnSkip

OnSkip handler je veľa ako OnTake. Dostane count parameter, čo je počet riadkov, ktoré sa majú preskočiť zo množiny výsledkov. To sa pekne prekladá do parametra dotazu OData $skip.

Jednotkové skúšky:

// OnSkip
Fact("Fold $skip 14 on Airlines",
    #table( type table [AirlineCode = text, Name = text] , {{"EK", "Emirates"}} ), 
    Table.Skip(Airlines, 14)
),
Fact("Fold $skip 0 and $top 1",
    #table( type table [AirlineCode = text, Name = text] , {{"AA", "American Airlines"}} ),
    Table.FirstN(Table.Skip(Airlines, 0), 1)
),

Realizácia:

// OnSkip - handles the Table.Skip transform.
// The count value should be >= 0.
OnSkip = (count as number) =>
    let
        newState = state & [ Skip = count ]
    in
        @View(newState),

Zodpovedajúce aktualizácie CalculateUrl k:

qsWithSkip = 
    if (state[Skip]? <> null) then
        qsWithTop & [ #"$skip" = Number.ToText(state[Skip]) ]
    else
        qsWithTop,

Manipulačné stoly.SelectColumns s OnSelectColumns

Obslužný nástroj OnSelectColumns sa nazýva, keď používateľ vyberie alebo odstráni stĺpce zo množiny výsledkov. Obsluha dostane list text hodnoty predstavujúce stĺpce, ktoré sa majú vybrať. V podmienkach OData sa táto operácia prikáza $select možnosti dotazu. Výhoda výberu skladacieho stĺpca sa stáva zrejmou, keď máte čo do činenia s tabuľkami s mnohými stĺpcami. $selectOperátor odstráni nezvolené stĺpce zo množiny výsledkov, čo bude mať za následok efektívnejšie dotazy.

Jednotkové skúšky:

// OnSelectColumns
Fact("Fold $select single column", 
    #table( type table [AirlineCode = text] , {{"AA"}} ),
    Table.FirstN(Table.SelectColumns(Airlines, {"AirlineCode"}), 1)
),
Fact("Fold $select multiple column", 
    #table( type table [UserName = text, FirstName = text, LastName = text],{{"russellwhyte", "Russell", "Whyte"}}), 
    Table.FirstN(Table.SelectColumns(People, {"UserName", "FirstName", "LastName"}), 1)
),
Fact("Fold $select with ignore column", 
    #table( type table [AirlineCode = text] , {{"AA"}} ),
    Table.FirstN(Table.SelectColumns(Airlines, {"AirlineCode", "DoesNotExist"}, MissingField.Ignore), 1)
),

Prvé dva testy vyberú rôzne čísla stĺpcov s Table.SelectColumns a zahŕňajú Table.FirstN výzvu na zjednodušenie testovacieho puzdra.

Poznámka

Ak by test jednoducho vrátil názvy stĺpcov Table.ColumnNames (pomocou) a nie žiadne údaje, žiadosť službe OData sa nikdy skutočne nepošle. Je to preto, že výzva na GetType vrátenie schémy, ktorá obsahuje všetky informácie, ktoré motor M potrebuje na výpočet výsledku.

Tretí test používa MissingField.Ignore možnosť, ktorá hovorí motoru M, aby ignoroval všetky vybraté stĺpce, ktoré v množine výsledkov neexistujú. OnSelectColumnsObsluha sa nemusí obávať tejto — možnosti, motor M spracuje automaticky (to znamená, že chýbajúce stĺpce nebudú zahrnuté do columns zoznamu).

Poznámka

Druhá možnosť pre Table.SelectColumns , vyžaduje konektor na implementáciu MissingField.UseNull OnAddColumn obsluhy. To sa uskutoční v nasledujúcej lekcii.

Realizácia OnSelectColumns robí dve veci:

  • Pridá zoznam vybratých stĺpcov do state .
  • Prečísli Schema hodnotu, aby ste mohli nastaviť správny typ tabuľky.
OnSelectColumns = (columns as list) =>
    let
        // get the current schema
        currentSchema = CalculateSchema(state),
        // get the columns from the current schema (which is an M Type value)
        rowRecordType = Type.RecordFields(Type.TableRow(currentSchema)),
        existingColumns = Record.FieldNames(rowRecordType),
        // calculate the new schema
        columnsToRemove = List.Difference(existingColumns, columns),
        updatedColumns = Record.RemoveFields(rowRecordType, columnsToRemove),
        newSchema = type table (Type.ForRecord(updatedColumns, false))
    in
        @View(state & 
            [ 
                SelectColumns = columns,
                Schema = newSchema
            ]
        ),

CalculateUrl aktualizuje, aby sa získal zoznam stĺpcov zo stavu a skombinuje ich (s oddeľovačom) pre $select parameter.

// Check for explicitly selected columns
qsWithSelect =
    if (state[SelectColumns]? <> null) then
        qsWithSkip & [ #"$select" = Text.Combine(state[SelectColumns], ",") ]
    else
        qsWithSkip,

Manipulačný stôl.Zoradenie pomocou onsortu

Manipulátor OnSort dostane list record hodnoty. Každý záznam obsahuje Name pole označujúce názov stĺpca a Order pole, ktoré sa rovná Order.Ascending alebo Order.Descending . V podmienkach OData sa táto operácia prikáza $orderby možnosti dotazu. $orderbySyntax má názov stĺpca, za ktorým nasleduje asc desc vzostupné alebo zostupné poradie alebo označuje vzostupné alebo zostupné poradie. Pri zoraďovaní viacerých stĺpcov sú hodnoty oddelené čiarkou. Všimnite si, že ak columns parameter obsahuje viac ako jednu položku, je dôležité zachovať poradie, v akom sa zobrazujú.

Jednotkové skúšky:

// OnSort
Fact("Fold $orderby single column",
    #table( type table [AirlineCode = text, Name = text], {{"TK", "Turkish Airlines"}}),
    Table.FirstN(Table.Sort(Airlines, {{"AirlineCode", Order.Descending}}), 1)
),
Fact("Fold $orderby multiple column",
    #table( type table [UserName = text], {{"javieralfred"}}),
    Table.SelectColumns(Table.FirstN(Table.Sort(People, {{"LastName", Order.Ascending}, {"UserName", Order.Descending}}), 1), {"UserName"})
)

Realizácia:

// OnSort - receives a list of records containing two fields: 
//    [Name]  - the name of the column to sort on
//    [Order] - equal to Order.Ascending or Order.Descending
// If there are multiple records, the sort order must be maintained.
//
// OData allows you to sort on columns that do not appear in the result
// set, so we do not have to validate that the sorted columns are in our 
// existing schema.
OnSort = (order as list) =>
    let
        // This will convert the list of records to a list of text,
        // where each entry is "<columnName> <asc|desc>"
        sorting = List.Transform(order, (o) => 
            let
                column = o[Name],
                order = o[Order],
                orderText = if (order = Order.Ascending) then "asc" else "desc"
            in
                column & " " & orderText
        ),
        orderBy = Text.Combine(sorting, ", ")
    in
        @View(state & [ OrderBy = orderBy ]),

Aktualizácie: CalculateUrl

qsWithOrderBy = 
    if (state[OrderBy]? <> null) then
        qsWithSelect & [ #"$orderby" = state[OrderBy] ]
    else
        qsWithSelect,

Manipulačné stoly.RowCount s GetRowCount

Na rozdiel od ostatných obslužných programovačov dotazov, ktoré ste implementovali, obslužný program GetRowCount vráti jednu hodnotu — počtu riadkov očakávaných v množine výsledkov. V dotaze M by to bolo zvyčajne výsledkom Table.RowCount transformácie. Máte niekoľko rôznych možností, ako to zvládnuť ako súčasť dotazu OData.

Nevýhodou prístupu parametra dotazu je, že stále musíte odoslať celý dotaz do služby OData. Keďže počet sa vráti do vnoreného súboru výsledkov, budete musieť spracovať prvú stranu údajov zo množiny výsledkov. Aj keď je to stále efektívnejšie ako čítanie celej množiny výsledkov a počítanie riadkov, pravdepodobne je to stále viac práce, než chcete urobiť.

Výhodou prístupu segmentu cesty je, že vo výsledku získate iba jednu skalárnu hodnotu. Vďaka tomu je celá operácia oveľa efektívnejšia. Ako je však popísané v špecifikácii OData, segment cesty /$count vráti chybu, ak zahrniete iné parametre dotazu, napríklad $top alebo , ktoré obmedzujú jeho $skip užitočnosť.

V tomto tutoriále budete implementovať GetRowCount obsluhu pomocou prístupu segmentu cesty. Ak sa chcete vyhnúť chybám, ktoré sa vyskytnú, ak sú zahrnuté iné parametre dotazu, skontrolujete iné hodnoty stavu a ak nejaké nájdete, vrátite "nezaradenú ... chybu"( ) . Vrátenie akejkoľvek chyby z Table.View obsluhy povie motoru M, že operáciu nie je možné zložiť, a namiesto toho by sa mala vrátiť k predvolenému obsluhe (čo by v tomto prípade počítalo celkový počet riadkov).

Najprv pridajte jednoduchý jednotkový test:

// GetRowCount
Fact("Fold $count", 15, Table.RowCount(Airlines)),

Keďže /$count segment cesty vráti jednu hodnotu (vo formáte obyčajného textu) namiesto množiny výsledkov JSON, budete tiež musieť pridať novú internú funkciu ( TripPin.Scalar ) na vytvorenie žiadosti a spracovanie výsledku.

// Similar to TripPin.Feed, but is expecting back a scalar value.
// This function returns the value from the service as plain text.
TripPin.Scalar = (url as text) as text =>
    let
        _url = Diagnostics.LogValue("TripPin.Scalar url", url),

        headers = DefaultRequestHeaders & [
            #"Accept" = "text/plain"
        ],

        response = Web.Contents(_url, [ Headers = headers ]),
        toText = Text.FromBinary(response)
    in
        toText;

Implementácia potom použije túto funkciu (ak sa v state ):

GetRowCount = () as number =>
    if (Record.FieldCount(Record.RemoveFields(state, {"Url", "Entity", "Schema"}, MissingField.Ignore)) > 0) then
        ...
    else
        let
            newState = state & [ RowCountOnly = true ],
            finalUrl = CalculateUrl(newState),
            value = TripPin.Scalar(finalUrl),
            converted = Number.FromText(value)
        in
            converted,

CalculateUrlFunkcia sa aktualizuje tak, aby sa priloďila k adrese /$count URL, ak je pole nastavené v RowCountOnly poli state .

// Check for $count. If all we want is a row count,
// then we add /$count to the path value (following the entity name).
urlWithRowCount =
    if (state[RowCountOnly]? = true) then
        urlWithEntity & "/$count"
    else
        urlWithEntity,

Nový Table.RowCount test jednotky by mal prejsť.

Ak chcete otestovať záložný prípad, pridáte ďalší test, ktorý vynúti chybu. Najprv pridajte pomocnú metódu, ktorá skontroluje výsledok try operácie pre chybu pri sklopení.

// Returns true if there is a folding error, or the original record (for logging purposes) if not.
Test.IsFoldingError = (tryResult as record) =>
    if ( tryResult[HasError]? = true and tryResult[Error][Message] = "We couldn't fold the expression to the data source. Please try a simpler expression.") then
        true
    else
        tryResult;

Potom pridajte test, ktorý používa oboje Table.RowCount a Table.FirstN vynúti chybu.

// test will fail if "Fail on Folding Error" is set to false
Fact("Fold $count + $top *error*", true, Test.IsFoldingError(try Table.RowCount(Table.FirstN(Airlines, 3)))),

Dôležitou poznámkou je, že tento test teraz vráti chybu, ak je nastavená chyba pri skladaní chyby false , pretože operácia sa vráti na lokálny Table.RowCount (predvolený) obsluhu. Spustenie testov s chybou pri skladaní chyba nastaviť true spôsobí Table.RowCount zlyhanie, a umožní test uspieť.

Záver

Implementácia Table.View konektora pridáva do kódu značné množstvo zložitosti. Keďže motor M dokáže spracovať všetky transformácie lokálne, pridanie Table.View obsluhy neumožňuje nové scenáre pre vašich používateľov, ale povedie k efektívnejšiemu spracovaniu (a potenciálne šťastnejším používateľom). Jednou z hlavných výhod Table.View voliteľného manipulátora je to, že umožňuje postupne pridávať nové funkcie bez toho, aby to malo vplyv na spätnú kompatibilitu konektora.

Pre väčšinu konektorov je dôležitý (a základný) manipulátor na implementáciu OnTake (čo sa prekladá $top v OData), pretože obmedzuje množstvo vrátených riadkov. Prostredie doplnku Power Query bude vždy vykonávať niekoľko OnTake 1000 riadkov pri zobrazovaní náhľadov v navigátore a editore dotazov, takže používatelia môžu pri práci s väčšími množinami údajov vidieť významné vylepšenia výkonu.