Sqlserver
 sql >> Database >  >> RDS >> Sqlserver

Unire 100 tavoli

L'ottimizzatore di SQL Server contiene la logica per rimuovere i join ridondanti, ma ci sono delle restrizioni e i join devono essere probabilmente ridondante . Per riassumere, un join può avere quattro effetti:

  1. Può aggiungere colonne extra (dalla tabella unita)
  2. Può aggiungere righe extra (la tabella unita potrebbe corrispondere a una riga di origine più di una volta)
  3. Può rimuovere righe (la tabella unita potrebbe non avere una corrispondenza)
  4. Può introdurre NULL s (per un RIGHT o FULL JOIN )

Per rimuovere correttamente un join ridondante, la query (o vista) deve tenere conto di tutte e quattro le possibilità. Quando questo è fatto, correttamente, l'effetto può essere sorprendente. Ad esempio:

USE AdventureWorks2012;
GO
CREATE VIEW dbo.ComplexView
AS
    SELECT
        pc.ProductCategoryID, pc.Name AS CatName,
        ps.ProductSubcategoryID, ps.Name AS SubCatName,
        p.ProductID, p.Name AS ProductName,
        p.Color, p.ListPrice, p.ReorderPoint,
        pm.Name AS ModelName, pm.ModifiedDate
    FROM Production.ProductCategory AS pc
    FULL JOIN Production.ProductSubcategory AS ps ON
        ps.ProductCategoryID = pc.ProductCategoryID
    FULL JOIN Production.Product AS p ON
        p.ProductSubcategoryID = ps.ProductSubcategoryID
    FULL JOIN Production.ProductModel AS pm ON
        pm.ProductModelID = p.ProductModelID

L'ottimizzatore può semplificare con successo la seguente query:

SELECT
    c.ProductID,
    c.ProductName
FROM dbo.ComplexView AS c
WHERE
    c.ProductName LIKE N'G%';

A:

Rob Farley ha scritto in modo approfondito queste idee nel libro originale MVP Deep Dives , e c'è una registrazione della sua presentazione sull'argomento in SQLBit.

Le principali restrizioni sono che le relazioni di chiave esterna devono essere basati su un'unica chiave per contribuire al processo di semplificazione, e il tempo di compilazione delle query relative a tale vista può diventare piuttosto lungo, soprattutto con l'aumento del numero di join. Potrebbe essere una vera sfida scrivere una vista a 100 tabelle che ottenga tutta la semantica esattamente corretta. Sarei propenso a trovare una soluzione alternativa, magari utilizzando SQL dinamico .

Detto questo, le qualità particolari della tua tabella denormalizzata possono significare che la vista è abbastanza semplice da assemblare, richiedendo solo FOREIGN KEYs forzate non NULL colonne di riferimento abili e UNIQUE appropriato vincoli per far funzionare questa soluzione come speri, senza il sovraccarico di 100 operatori di join fisici nel piano.

Esempio

Utilizzando dieci tabelle anziché cento:

-- Referenced tables
CREATE TABLE dbo.Ref01 (col01 tinyint PRIMARY KEY, item varchar(50) NOT NULL UNIQUE);
CREATE TABLE dbo.Ref02 (col02 tinyint PRIMARY KEY, item varchar(50) NOT NULL UNIQUE);
CREATE TABLE dbo.Ref03 (col03 tinyint PRIMARY KEY, item varchar(50) NOT NULL UNIQUE);
CREATE TABLE dbo.Ref04 (col04 tinyint PRIMARY KEY, item varchar(50) NOT NULL UNIQUE);
CREATE TABLE dbo.Ref05 (col05 tinyint PRIMARY KEY, item varchar(50) NOT NULL UNIQUE);
CREATE TABLE dbo.Ref06 (col06 tinyint PRIMARY KEY, item varchar(50) NOT NULL UNIQUE);
CREATE TABLE dbo.Ref07 (col07 tinyint PRIMARY KEY, item varchar(50) NOT NULL UNIQUE);
CREATE TABLE dbo.Ref08 (col08 tinyint PRIMARY KEY, item varchar(50) NOT NULL UNIQUE);
CREATE TABLE dbo.Ref09 (col09 tinyint PRIMARY KEY, item varchar(50) NOT NULL UNIQUE);
CREATE TABLE dbo.Ref10 (col10 tinyint PRIMARY KEY, item varchar(50) NOT NULL UNIQUE);

La definizione della tabella padre (con compressione della pagina):

CREATE TABLE dbo.Normalized
(
    pk      integer IDENTITY NOT NULL,
    col01   tinyint NOT NULL REFERENCES dbo.Ref01,
    col02   tinyint NOT NULL REFERENCES dbo.Ref02,
    col03   tinyint NOT NULL REFERENCES dbo.Ref03,
    col04   tinyint NOT NULL REFERENCES dbo.Ref04,
    col05   tinyint NOT NULL REFERENCES dbo.Ref05,
    col06   tinyint NOT NULL REFERENCES dbo.Ref06,
    col07   tinyint NOT NULL REFERENCES dbo.Ref07,
    col08   tinyint NOT NULL REFERENCES dbo.Ref08,
    col09   tinyint NOT NULL REFERENCES dbo.Ref09,
    col10   tinyint NOT NULL REFERENCES dbo.Ref10,

    CONSTRAINT PK_Normalized
        PRIMARY KEY CLUSTERED (pk)
        WITH (DATA_COMPRESSION = PAGE)
);

La vista:

CREATE VIEW dbo.Denormalized
WITH SCHEMABINDING AS
SELECT
    item01 = r01.item,
    item02 = r02.item,
    item03 = r03.item,
    item04 = r04.item,
    item05 = r05.item,
    item06 = r06.item,
    item07 = r07.item,
    item08 = r08.item,
    item09 = r09.item,
    item10 = r10.item
FROM dbo.Normalized AS n
JOIN dbo.Ref01 AS r01 ON r01.col01 = n.col01
JOIN dbo.Ref02 AS r02 ON r02.col02 = n.col02
JOIN dbo.Ref03 AS r03 ON r03.col03 = n.col03
JOIN dbo.Ref04 AS r04 ON r04.col04 = n.col04
JOIN dbo.Ref05 AS r05 ON r05.col05 = n.col05
JOIN dbo.Ref06 AS r06 ON r06.col06 = n.col06
JOIN dbo.Ref07 AS r07 ON r07.col07 = n.col07
JOIN dbo.Ref08 AS r08 ON r08.col08 = n.col08
JOIN dbo.Ref09 AS r09 ON r09.col09 = n.col09
JOIN dbo.Ref10 AS r10 ON r10.col10 = n.col10;

Hackera le statistiche per far pensare all'ottimizzatore che la tabella sia molto grande:

UPDATE STATISTICS dbo.Normalized WITH ROWCOUNT = 100000000, PAGECOUNT = 5000000;

Esempio di query utente:

SELECT
    d.item06,
    d.item07
FROM dbo.Denormalized AS d
WHERE
    d.item08 = 'Banana'
    AND d.item01 = 'Green';

Ci fornisce questo piano di esecuzione:

La scansione della tabella Normalizzata sembra scadente, ma entrambe le bitmap del filtro Bloom vengono applicate durante la scansione dal motore di archiviazione (quindi le righe che non possono corrispondere non emergono nemmeno fino al Query Processor). Questo potrebbe essere sufficiente per fornire prestazioni accettabili nel tuo caso e sicuramente migliori rispetto alla scansione della tabella originale con le sue colonne traboccanti.

Se sei in grado di eseguire l'aggiornamento a SQL Server 2012 Enterprise in una fase, hai un'altra opzione:creare un indice di archivio colonne nella tabella Normalizzata:

CREATE NONCLUSTERED COLUMNSTORE INDEX cs 
ON dbo.Normalized (col01,col02,col03,col04,col05,col06,col07,col08,col09,col10);

Il piano di esecuzione è:

Probabilmente ti sembra peggio, ma l'archiviazione delle colonne fornisce una compressione eccezionale e l'intero piano di esecuzione viene eseguito in modalità batch con filtri per tutte le colonne che contribuiscono. Se il server dispone di thread e memoria adeguati, questa alternativa potrebbe davvero volare.

In definitiva, non sono sicuro che questa normalizzazione sia l'approccio corretto considerando il numero di tabelle e le possibilità di ottenere un piano di esecuzione scadente o richiedere tempi di compilazione eccessivi. Probabilmente correggerei prima lo schema della tabella denormalizzata (tipi di dati corretti e così via), eventualmente applicherei la compressione dei dati... le solite cose.

Se i dati appartengono veramente a uno schema a stella, probabilmente è necessario più lavoro di progettazione che dividere semplicemente gli elementi di dati ripetuti in tabelle separate.