sql >> Databáze >  >> RDS >> Sqlserver

SQL, Pomocná tabulka čísel

Heh... omlouvám se, že reaguji tak pozdě na starý příspěvek. A ano, musel jsem odpovědět, protože nejoblíbenější odpověď (v té době odpověď Rekurzivní CTE s odkazem na 14 různých metod) v tomto vláknu je, ehm... výkon je přinejlepším zpochybněn.

Za prvé, článek se 14 různými řešeními je v pořádku, protože vidíte různé metody vytváření tabulky Numbers/Tally za chodu, ale jak je uvedeno v článku a v citovaném vláknu, existuje velmi důležitý citát...

"Návrhy týkající se efektivity a výkonu jsou často subjektivní. Bez ohledu na to, jak je dotaz používán, určuje efektivitu dotazu fyzická implementace. Proto raději než se spoléhat na neobjektivní pokyny, je nutné dotaz otestovat a určit, který z nich funguje lépe."

Je ironií, že samotný článek obsahuje mnoho subjektivních prohlášení a „objektivních pokynů“, jako je „rekurzivní CTE může generovat číselný výpis velmi efektivně " a „Toto je účinná metoda použití smyčky WHILE z diskusní skupiny od Itzika Ben-Gena" (což jsem si jistý, že zveřejnil jen pro účely srovnání). No tak lidi... Pouhá zmínka o Itzikově dobrém jménu může vést některého ubohého flákače k ​​tomu, aby skutečně použil tu strašlivou metodu. Autor by si měl procvičit to, co káže, a měl by udělat malý test výkonu, než učiní taková směšně nesprávná prohlášení, zejména tváří v tvář jakékoli škálovatelnosti.

S myšlenkou na to, že skutečně provedete nějaké testování před tím, než začnete subjektivně tvrdit, co který kód dělá nebo co se někomu „líbí“, zde je nějaký kód, se kterým můžete provádět vlastní testování. Nastavte profilovač pro SPID, ze kterého spouštíte test, a sami si to ověřte... jednoduše zadejte "Search'n'Replace" číslo 1000000 pro své "oblíbené" číslo a uvidíte...

--===== Test for 1000000 rows ==================================
GO
--===== Traditional RECURSIVE CTE method
   WITH Tally (N) AS 
        ( 
         SELECT 1 UNION ALL 
         SELECT 1 + N FROM Tally WHERE N < 1000000 
        ) 
 SELECT N 
   INTO #Tally1 
   FROM Tally 
 OPTION (MAXRECURSION 0);
GO
--===== Traditional WHILE LOOP method
 CREATE TABLE #Tally2 (N INT);
    SET NOCOUNT ON;
DECLARE @Index INT;
    SET @Index = 1;
  WHILE @Index <= 1000000 
  BEGIN 
         INSERT #Tally2 (N) 
         VALUES (@Index);
            SET @Index = @Index + 1;
    END;
GO
--===== Traditional CROSS JOIN table method
 SELECT TOP (1000000)
        ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS N
   INTO #Tally3
   FROM Master.sys.All_Columns ac1
  CROSS JOIN Master.sys.ALL_Columns ac2;
GO
--===== Itzik's CROSS JOINED CTE method
   WITH E00(N) AS (SELECT 1 UNION ALL SELECT 1),
        E02(N) AS (SELECT 1 FROM E00 a, E00 b),
        E04(N) AS (SELECT 1 FROM E02 a, E02 b),
        E08(N) AS (SELECT 1 FROM E04 a, E04 b),
        E16(N) AS (SELECT 1 FROM E08 a, E08 b),
        E32(N) AS (SELECT 1 FROM E16 a, E16 b),
   cteTally(N) AS (SELECT ROW_NUMBER() OVER (ORDER BY N) FROM E32)
 SELECT N
   INTO #Tally4
   FROM cteTally
  WHERE N <= 1000000;
GO
--===== Housekeeping
   DROP TABLE #Tally1, #Tally2, #Tally3, #Tally4;
GO

Když jsme u toho, zde jsou čísla, která získám z SQL Profiler pro hodnoty 100, 1000, 10000, 100000 a 1000000...

SPID TextData                                 Dur(ms) CPU   Reads   Writes
---- ---------------------------------------- ------- ----- ------- ------
  51 --===== Test for 100 rows ==============       8     0       0      0
  51 --===== Traditional RECURSIVE CTE method      16     0     868      0
  51 --===== Traditional WHILE LOOP method CR      73    16     175      2
  51 --===== Traditional CROSS JOIN table met      11     0      80      0
  51 --===== Itzik's CROSS JOINED CTE method        6     0      63      0
  51 --===== Housekeeping   DROP TABLE #Tally      35    31     401      0

  51 --===== Test for 1000 rows =============       0     0       0      0
  51 --===== Traditional RECURSIVE CTE method      47    47    8074      0
  51 --===== Traditional WHILE LOOP method CR      80    78    1085      0
  51 --===== Traditional CROSS JOIN table met       5     0      98      0
  51 --===== Itzik's CROSS JOINED CTE method        2     0      83      0
  51 --===== Housekeeping   DROP TABLE #Tally       6    15     426      0

  51 --===== Test for 10000 rows ============       0     0       0      0
  51 --===== Traditional RECURSIVE CTE method     434   344   80230     10
  51 --===== Traditional WHILE LOOP method CR     671   563   10240      9
  51 --===== Traditional CROSS JOIN table met      25    31     302     15
  51 --===== Itzik's CROSS JOINED CTE method       24     0     192     15
  51 --===== Housekeeping   DROP TABLE #Tally       7    15     531      0

  51 --===== Test for 100000 rows ===========       0     0       0      0
  51 --===== Traditional RECURSIVE CTE method    4143  3813  800260    154
  51 --===== Traditional WHILE LOOP method CR    5820  5547  101380    161
  51 --===== Traditional CROSS JOIN table met     160   140     479    211
  51 --===== Itzik's CROSS JOINED CTE method      153   141     276    204
  51 --===== Housekeeping   DROP TABLE #Tally      10    15     761      0

  51 --===== Test for 1000000 rows ==========       0     0       0      0
  51 --===== Traditional RECURSIVE CTE method   41349 37437 8001048   1601
  51 --===== Traditional WHILE LOOP method CR   59138 56141 1012785   1682
  51 --===== Traditional CROSS JOIN table met    1224  1219    2429   2101
  51 --===== Itzik's CROSS JOINED CTE method     1448  1328    1217   2095
  51 --===== Housekeeping   DROP TABLE #Tally       8     0     415      0

Jak můžete vidět, metoda rekurzivního CTE je druhá nejhorší za smyčkou While z hlediska trvání a CPU a má 8krát větší tlak na paměť ve formě logických čtení než smyčka While . Je to RBAR na steroidech a je třeba se mu za každou cenu vyhnout pro výpočty s jedním řádkem, stejně jako je třeba se vyhnout smyčkám While. Existují místa, kde je rekurze docela cenná, ale tohle NENÍ jedno z nich .

Jako postranní lišta je pan Denny naprosto na místě... pro většinu věcí je správná velikost stálé tabulky čísel nebo sčítání. Co znamená správná velikost? Většina lidí používá tabulku sčítání ke generování dat nebo k rozdělení na VARCHAR(8000). Pokud vytvoříte tabulku Tally s 11 000 řádky se správným seskupeným indexem na „N“, budete mít dostatek řádků k vytvoření dat za více než 30 let (s hypotékami pracuji docela dost, takže 30 let je pro mě klíčové číslo ) a určitě dost na to, aby zvládl rozdělení VARCHAR(8000). Proč je „správná velikost“ tak důležitá? Pokud je tabulka Tally hodně používána, snadno se vejde do mezipaměti, díky čemuž je neuvěřitelně rychlá bez velkého tlaku na paměť.

V neposlední řadě každý ví, že pokud vytvoříte trvalou tabulku Tally, nezáleží na tom, jakou metodou ji sestavíte, protože 1) bude vytvořena pouze jednou a 2) pokud je to něco jako 11 000 řádek tabulky, všechny metody poběží "dost dobře". Tak proč tolik rozhořčení z mé strany ohledně toho, kterou metodu použít???

Odpověď zní, že nějaký chudák/holka, která se v ničem lépe nevyzná a potřebuje jen dokončit svou práci, může vidět něco jako metodu rekurzivního CTE a rozhodnout se ji použít pro něco mnohem většího a mnohem častěji používaného, ​​než je stavba. trvalou tabulku Tally a já se snažím chránit tyto lidi, servery, na kterých běží jejich kód, a společnost, která vlastní data na těchto serverech . Jo... je to tak velký problém. Mělo by to být i pro všechny ostatní. Učte správný způsob, jak dělat věci místo „dost dobré“. Před odesláním nebo použitím něčeho z příspěvku nebo knihy proveďte nějaké testování... život, který zachráníte, může být ve skutečnosti váš vlastní, zvláště pokud si myslíte, že rekurzivní CTE je způsob, jak se k něčemu takovému vydat.;-)

Děkujeme za poslech...



  1. Jak vypočítat průběžný celkový počet v Redshift

  2. Frustrace SQLite onUpgrade().

  3. mysql PDO jak svázat LIKE

  4. Jak vytvořit vztah v MySQL Workbench