Domanda

Dopo aver attraversato tutto il duro lavoro di scrivere una query CTE ricorsiva per soddisfare le mie esigenze, mi rendo conto che non posso usare perché non funziona in una vista indicizzata. Quindi ho bisogno di qualcos'altro per sostituire il CTE di seguito. (Sì, è possibile utilizzare un CTE in una vista non indicizzato, ma questo è troppo lento per me).

I requisiti:

  1. Il mio obiettivo finale è quello di avere un auto aggiornamento vista indicizzata (che non deve essere una visione, ma qualcosa di simile) ... cioè, se le modifiche dei dati in una qualsiasi delle tabelle della vista si unisce su , poi la vista deve aggiornarsi.

  2. La vista deve essere indicizzato perché deve essere molto veloce, e il dato non cambia molto frequentemente. Purtroppo, la vista non indicizzata utilizzando un CTE richiede 3-5 secondi per eseguire che è troppo lungo per le mie esigenze. Ho bisogno la query per l'esecuzione in millisecondi. La tabella ricorsiva ha poche centinaia di migliaia di record in esso.

Per quanto riguarda la mia ricerca mi ha portato, la soluzione migliore per soddisfare tutte queste esigenze è una vista indicizzata, ma sono aperto a qualsiasi soluzione.

Il CTE può essere trovato nella risposta alla mia altri post . O qui è ancora una volta:

DECLARE @tbl TABLE ( 
     Id INT 
    ,[Name] VARCHAR(20) 
    ,ParentId INT 
    ) 

INSERT INTO @tbl( Id, Name, ParentId ) 
VALUES 
 (1, 'Europe', NULL) 
,(2, 'Asia',   NULL) 
,(3, 'Germany', 1) 
,(4, 'UK',      1) 
,(5, 'China',   2) 
,(6, 'India',   2) 
,(7, 'Scotland', 4) 
,(8, 'Edinburgh', 7) 
,(9, 'Leith', 8) 

; 
DECLARE @tbl2 table (id int, abbreviation varchar(10), tbl_id int)
INSERT INTO @tbl2( Id, Abbreviation, tbl_id ) 
VALUES 
 (100, 'EU', 1) 
,(101, 'AS', 2) 
,(102, 'DE', 3) 
,(103, 'CN', 5)

;WITH abbr AS (
    SELECT a.*, isnull(b.abbreviation,'') abbreviation
    FROM @tbl a
    left join @tbl2 b on a.Id = b.tbl_id
), abcd AS ( 
          -- anchor 
        SELECT  id, [Name], ParentID,
                CAST(([Name]) AS VARCHAR(1000)) [Path],
                cast(abbreviation as varchar(max)) abbreviation
        FROM    abbr
        WHERE   ParentId IS NULL 
        UNION ALL
          --recursive member 
        SELECT  t.id, t.[Name], t.ParentID, 
                CAST((a.path + '/' + t.Name) AS VARCHAR(1000)) [Path],
                isnull(nullif(t.abbreviation,'')+',', '') + a.abbreviation
        FROM    abbr AS t 
                JOIN abcd AS a 
                  ON t.ParentId = a.id 
       )
SELECT *, [Path] + ':' + abbreviation
FROM abcd
È stato utile?

Soluzione

Dopo aver colpito tutti i blocchi stradali con viste indicizzate (self join, cte, UDF l'accesso ai dati, ecc), propongo alla sotto come una soluzione per voi.

Crea funzione di supporto

Sulla base profondità massima di 4 dalla radice (5 totale). Oppure utilizzare un CTE

CREATE FUNCTION dbo.GetHierPath(@hier_id int) returns varchar(max)
WITH SCHEMABINDING
as
begin
return (
    select FullPath =
               isnull(H5.Name+'/','') + 
               isnull(H4.Name+'/','') +
               isnull(H3.Name+'/','') +
               isnull(H2.Name+'/','') +
               H1.Name
             +
               ':'
             +
               isnull(STUFF(
               isnull(','+A1.abbreviation,'') +
               isnull(','+A2.abbreviation,'') + 
               isnull(','+A3.abbreviation,'') +
               isnull(','+A4.abbreviation,'') +
               isnull(','+A5.abbreviation,''),1,1,''),'')
    from dbo.HIER H1
    left join dbo.ABBR A1 on A1.hier_id = H1.Id
    left join dbo.HIER H2 on H1.ParentId = H2.Id
    left join dbo.ABBR A2 on A2.hier_id = H2.Id
    left join dbo.HIER H3 on H2.ParentId = H3.Id
    left join dbo.ABBR A3 on A3.hier_id = H3.Id
    left join dbo.HIER H4 on H3.ParentId = H4.Id
    left join dbo.ABBR A4 on A4.hier_id = H4.Id
    left join dbo.HIER H5 on H4.ParentId = H5.Id
    left join dbo.ABBR A5 on A5.hier_id = H5.Id
    where H1.id = @hier_id)
end
GO

Aggiungi colonne alla tabella stessa

Per esempio la colonna fullpath, se avete bisogno, aggiungere gli altri 2 colonne della CTE dividendo il risultato di dbo.GetHierPath su ':' (left=>path, right=>abbreviations)

-- index maximum key length is 900, based on your data, 400 is enough
ALTER TABLE HIER ADD FullPath VARCHAR(400)

Mantenere le colonne

A causa della natura gerarchica, record di X potrebbe essere cancellata che colpisce una Y e Z discendente degli antenati, che è abbastanza difficile da individuare in uno di sostituzione o trigger AFTER. Quindi l'approccio alternativo si basa sulle condizioni

  • se le modifiche dei dati in una qualsiasi delle tabelle della vista si unisce, poi le esigenze di visualizzazione per aggiornare se stesso.
  • la vista non indicizzata utilizzando un CTE richiede 3-5 secondi per corsa che è troppo lungo per le mie esigenze

Noi manteniamo i dati semplicemente che attraversa l'intera tabella di nuovo, prendendo 3-5 secondi per l'aggiornamento (o più veloce se il 5-join query di funziona meglio).

CREATE TRIGGER TG_HIER
ON HIER
AFTER INSERT, UPDATE, DELETE
AS
UPDATE HIER
SET FullPath = dbo.GetHierPath(HIER.Id)

Infine, indicizzare la nuova colonna (s) sulla tavola stessa

create index ix_hier_fullpath on HIER(FullPath)

Se si intende accedere ai dati di percorso tramite l'id, allora è già nella tabella stessa, senza l'aggiunta di un indice aggiuntivo.

È possibile che questo riferimenti TSQL questi oggetti

Modifica i nomi delle tabelle e delle colonne per soddisfare il vostro schema.

CREATE TABLE dbo.HIER (Id INT Primary Key Clustered, [Name] VARCHAR(20) ,ParentId INT)
;
INSERT dbo.HIER( Id, Name, ParentId ) VALUES 
     (1, 'Europe', NULL) 
    ,(2, 'Asia',   NULL) 
    ,(3, 'Germany', 1) 
    ,(4, 'UK',      1) 
    ,(5, 'China',   2) 
    ,(6, 'India',   2) 
    ,(7, 'Scotland', 4) 
    ,(8, 'Edinburgh', 7) 
    ,(9, 'Leith', 8)
    ,(10, 'Antartica', NULL) 
; 
CREATE TABLE dbo.ABBR (id int primary key clustered, abbreviation varchar(10), hier_id int)
;
INSERT dbo.ABBR( Id, Abbreviation, hier_id ) VALUES 
     (100, 'EU', 1) 
    ,(101, 'AS', 2) 
    ,(102, 'DE', 3) 
    ,(103, 'CN', 5)
GO

Modifica - Forse più veloce alternativa

Dato che tutti i record vengono ricalcolate ogni volta, non v'è alcun reale bisogno di una funzione che restituisce il FullPath per un singolo HIER.ID. La query nella support function può essere utilizzato senza il filtro where H1.id = @hier_id alla fine. Inoltre, l'espressione per FullPath può essere suddiviso in PathOnly e Abbreviation facilmente nel mezzo. O semplicemente usare l'originale CTE, a seconda di quale è più veloce.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top