Las operaciones de agregación en la vista ignoran el índice [cerrado]

8

El escenario

Había una vez una base de datos provisional en una pequeña empresa que participaba en un proceso ETL, que actuaba como un catálogo de recepción para los diversos formatos de archivos de varias fuentes de terceros. La E se manejó a través de paquetes DTS, con pocas estructuras de control para auditoría o control, pero se consideró "lo suficientemente bueno" y, a todos los efectos, lo fue.

Los datos proporcionados por la parte E fueron destinados al consumo por una aplicación singular, desarrollada y administrada por un puñado de programadores jóvenes y capaces. Aunque carecían de experiencia o conocimiento de las técnicas de almacenamiento de datos de la época, establecieron y crearon sus propios procesos T y L a partir del código de la aplicación. Arrancando, estos ingenieros de software novatos inventaron lo que los extraños podrían llamar una "rueda menos que ideal", pero con "Lo suficientemente bueno" como un nivel de servicio siempre presente, pudieron proporcionar un marco operativo.

Durante un tiempo, todo fue bueno en el ámbito estrechamente acoplado, con el catálogo de Staging deleitándose con los datos de una docena de terceros, a su vez alimentados por la aplicación. A medida que la aplicación creció, también lo hizo su apetito, pero con los hábiles desarrolladores de caballeros blancos vigilando el sistema, estos apetitos se abordaron rápidamente y, en muchos casos, incluso bien.

Pero la edad de oro no podía durar para siempre, por supuesto. Con la prosperidad otorgada por la aplicación exitosa, el negocio creció y creció. A medida que crecía, el entorno y la aplicación de Staging se vieron obligados a crecer con él. A pesar de su vigilancia, el mero puñado de desarrolladores de héroes no pudo mantenerse al día con el sistema ahora expansivo, y los consumidores tenían derecho a sus datos. Ya no era una cuestión de lo que necesitaban o incluso querían, sino que la población sentía que simplemente lo merecían, exigiendo aún más.

Armado con poco más que cofres llenos de botín, la empresa llegó al mercado, contratando desarrolladores y administradores para ayudar a respaldar el sistema en constante crecimiento. Mercenarios de todos los valores se congregaron en la empresa, pero con este crecimiento repentino se produjo poca orientación experta disponible. Los nuevos desarrolladores y administradores lucharon por comprender las complejidades de la suite casera, hasta que las frustraciones resultaron en una guerra total. Cada departamento comenzó a intentar resolver cada problema solo, haciendo más para trabajar uno contra el otro que trabajar entre ellos. Se implementaría un solo proyecto o iniciativa de varias maneras diferentes, cada una ligeramente diferente de la siguiente. La tensión de todo resultó ser demasiado para algunos de los caballeros blancos y cuando cayeron, el imperio se derrumbó. Pronto, el sistema estaba en ruinas,

A pesar de la transformación de estos campos de promesa en sangrientos códigos de espagueti, la compañía aguantó. Era, después de todo, "lo suficientemente bueno".

El reto

Unos pocos cambios de régimen más y juergas de contratación más tarde, me encuentro en el empleo de la empresa. Han pasado muchos años desde las grandes guerras, pero el daño hecho aún es muy visible. Me las arreglé para abordar algunas de las debilidades en la parte E del sistema y agregar algunas tablas de control con el pretexto de actualizar los paquetes DTS a SSIS, que ahora están siendo utilizados por algunos profesionales reales de almacenamiento de datos a medida que crean una normalidad. y reemplazo de T y L documentado.

El primer obstáculo fue importar los datos de los archivos de terceros de una manera que no truncara los valores o cambiara los tipos de datos nativos, sino que también incluyera algunas teclas de control para recargas y purgas. Todo esto estaba muy bien, pero las aplicaciones necesitaban poder acceder a estas nuevas tablas de una manera transparente y transparente. Un paquete DTS puede llenar una tabla, que luego la aplicación lee directamente. Las actualizaciones de SSIS deben realizarse en paralelo por razones de control de calidad, pero estos nuevos paquetes incluyen varias claves de control y también aprovechan un esquema de partición, sin mencionar que los cambios de metadatos reales por sí solos pueden ser lo suficientemente significativos como para garantizar una nueva tabla por completo de todos modos, por lo que un Se utilizó una nueva tabla para los nuevos paquetes SSIS.

Con importaciones de datos confiables que ahora funcionan y están siendo utilizadas por el equipo de almacenamiento, el verdadero desafío es entregar los nuevos datos a las aplicaciones que acceden directamente al entorno de ensayo, con un impacto mínimo (también conocido como "No") en el código de la aplicación. Para esto, he elegido a puntos de vista de uso, cambiar el nombre de una tabla como dbo.DailyTransactiona dbo.DailyTranscation_LEGACYy reutilizar el dbo.DailyTransactionnombre de objeto para una vista, que en efecto sólo selecciona todo, desde el momentoLEGACYmesa designada. Dado que la recarga de los años de datos contenidos en estas tablas no es una opción desde la perspectiva del negocio, ya que las nuevas tablas particionadas y SSIS entran en producción, las antiguas importaciones de DTS se desactivan y las aplicaciones deben poder acceder a los nuevos datos en las nuevas tablas también. En este punto, las vistas se actualizan para seleccionar los datos de las nuevas tablas ( dbo.DailyTransactionCompletepor ejemplo, por ejemplo) cuando está disponible y seleccionar de las tablas heredadas cuando no lo está.

En efecto, se está haciendo algo como lo siguiente:

CREATE VIEW dbo.DailyTransaction
AS  SELECT  DailyTransaction_PK, FileDate, Foo
    FROM    dbo.DailyTransactionComplete
    UNION ALL
    SELECT  DailyTransaction_PK, FileDate, Foo
    FROM    dbo.DailyTransaction_LEGACY l
    WHERE NOT EXISTS (  SELECT  1
                        FROM    dbo.DailyTransactionComplete t
                        WHERE   t.FileDate = l.FileDate );

Si bien es lógico, esto no funciona en absoluto en una serie de casos de agregación, lo que generalmente resulta en un plan de ejecución que realiza una exploración de índice completa contra los datos en la tabla heredada. Esto probablemente esté bien para unas pocas docenas de millones de registros, pero no tanto para unas pocas docenas de cientos de millones de registros. Como este último es el caso, tuve que recurrir a ser ... "creativo", lo que me llevó a crear una vista indizada.

Este es el pequeño caso de prueba que he configurado, incluida la FileDateclave de control que se ha portado al DateCode_FKpuerto compatible con Data Warehouse para ilustrar cuán poco me importa que las consultas sobre la nueva tabla sean modificables por el momento:

USE tempdb;
GO

SET NOCOUNT ON;
GO

IF NOT EXISTS ( SELECT  1
                FROM    sys.objects
                WHERE   name = 'DailyTransaction_LEGACY'
                    AND type = 'U' )
BEGIN
    --DROP TABLE dbo.DailyTransaction_LEGACY;
    CREATE TABLE dbo.DailyTransaction_LEGACY
    (
        DailyTransaction_PK         BIGINT IDENTITY( 1, 1 ) NOT NULL,
        FileDate                    DATETIME NOT NULL,
        Foo                         INT NOT NULL
    );

    INSERT INTO dbo.DailyTransaction_LEGACY ( FileDate, Foo )
    SELECT  DATEADD( DAY, ( 1 - ROW_NUMBER() 
                OVER( ORDER BY so1.object_id ) - 800 ) % 1000, 
                CONVERT( DATE, GETDATE() ) ),
            so1.object_id % 1000 + so2.object_id % 1000
    FROM    sys.all_objects so1
    CROSS JOIN sys.all_objects so2;

    ALTER TABLE dbo.DailyTransaction_LEGACY
    ADD CONSTRAINT PK__DailyTrainsaction
        PRIMARY KEY CLUSTERED ( DailyTransaction_PK )
    WITH ( DATA_COMPRESSION = PAGE, FILLFACTOR = 100 );
END;
GO

IF NOT EXISTS ( SELECT  1
                FROM    sys.objects
                WHERE   name = 'DailyTransactionComplete'
                    AND type = 'U' )
BEGIN
    --DROP TABLE dbo.DailyTransactionComplete;
    CREATE TABLE dbo.DailyTransactionComplete
    (
        DailyTransaction_PK            BIGINT IDENTITY( 1, 1 ) NOT NULL,
        DateCode_FK                    INTEGER NOT NULL,
        Foo                            INTEGER NOT NULL
    );

    INSERT INTO dbo.DailyTransactionComplete ( DateCode_FK, Foo )
    SELECT  TOP 100000
            CONVERT( INTEGER, CONVERT( VARCHAR( 8 ), DATEADD( DAY, 
                ( 1 - ROW_NUMBER() OVER( ORDER BY so1.object_id ) ) % 100, 
                GETDATE() ), 112 ) ),
            so1.object_id % 1000
    FROM    sys.all_objects so1
    CROSS JOIN sys.all_objects so2;

    ALTER TABLE dbo.DailyTransactionComplete
    ADD CONSTRAINT PK__DailyTransaction
        PRIMARY KEY CLUSTERED ( DateCode_FK, DailyTransaction_PK )
    WITH ( DATA_COMPRESSION = PAGE, FILLFACTOR = 100 );        
END;
GO

En mi sandbox local, lo anterior me da una tabla heredada con aproximadamente 4.4 millones de filas y una nueva tabla que contiene 0.1 millones de filas, con cierta superposición de los valores DateCode_FK/ FileDate.

Se MAX( FileDate )ejecuta una tabla contra el legado sin índices adicionales sobre lo que esperaría.

SET STATISTICS IO, TIME ON;

DECLARE @ConsumeOutput        DATETIME;
SELECT  @ConsumeOutput = MAX( FileDate )
FROM    dbo.DailyTransaction_LEGACY;

SET STATISTICS IO, TIME OFF;
GO

Tabla 'DailyTransaction_LEGACY'. Cuenta de escaneo 1, lecturas lógicas 9228, lecturas físicas 0, lecturas de lectura anticipada 0, lecturas lógicas lob 0, lecturas físicas lob 0, lecturas de lectura lob 0.

Tiempos de ejecución de SQL Server: tiempo de CPU = 889 ms, tiempo transcurrido = 886 ms.

Índice agrupado, legado

Lanzar un índice simple sobre la mesa hace que las cosas sean mucho mejores. Sigue siendo un escaneo, pero escanea un registro en lugar de los 4,4 millones de registros. Estoy bien con eso.

CREATE NONCLUSTERED INDEX IX__DailyTransaction__FileDate
    ON    dbo.DailyTransaction_LEGACY ( FileDate );

SET STATISTICS IO, TIME ON;

DECLARE @ConsumeOutput        DATETIME;
SELECT  @ConsumeOutput = MAX( FileDate )
FROM    dbo.DailyTransaction_LEGACY;

SET STATISTICS IO, TIME OFF;
GO

Tiempo de análisis y compilación de SQL Server: tiempo de CPU = 0 ms, tiempo transcurrido = 1 ms. Tabla 'DailyTransaction_LEGACY'. Cuenta de escaneo 1, lecturas lógicas 3, lecturas físicas 0, lecturas de lectura anticipada 0, lecturas lógicas lob 0, lecturas físicas lob 0, lecturas de lectura lob 0.

Tiempos de ejecución de SQL Server: tiempo de CPU = 0 ms, tiempo transcurrido = 0 ms.

Índice no agrupado, legado

Y ahora, creando la vista para que los desarrolladores no tengan que cambiar ningún código porque aparentemente sería el fin del mundo tal como lo conocemos. Un cataclismo de tipo.

IF NOT EXISTS ( SELECT  1
                FROM    sys.objects
                WHERE   name = 'DailyTransaction'
                    AND type = 'V' )
BEGIN
    EXEC( 'CREATE VIEW dbo.DailyTransaction AS SELECT x = 1;' );
END;
GO

ALTER VIEW dbo.DailyTransaction
AS  SELECT  DailyTransaction_PK, FileDate = CONVERT( 
                DATETIME, CONVERT( VARCHAR( 8 ), DateCode_FK ), 112 ), Foo
    FROM    dbo.DailyTransactionComplete
    UNION ALL    
    SELECT  DailyTransaction_PK, FileDate, Foo
    FROM    dbo.DailyTransaction_LEGACY l
    WHERE   NOT EXISTS (    SELECT  1
                            FROM    dbo.DailyTransactionComplete t
                            WHERE   CONVERT( DATETIME, CONVERT( VARCHAR( 8 ),
                                        t.DateCode_FK ), 112 ) = l.FileDate );
GO

Sí, la subconsulta es abismal, pero este no es el problema y probablemente simplemente crearé una columna computada persistente y arrojaré un índice para ese propósito cuando se resuelva el problema real. Así que sin más preámbulos,

El problema

SET STATISTICS IO, TIME ON;

DECLARE @ConsumeOutput1        DATETIME;
SELECT  @ConsumeOutput1 = MAX( FileDate )
FROM    dbo.DailyTransaction;

SET STATISTICS IO, TIME OFF;
GO

Tiempo de análisis y compilación de SQL Server: tiempo de CPU = 0 ms, tiempo transcurrido = 4 ms. Tabla 'DailyTransaction_LEGACY'. Recuento de escaneo 1, lecturas lógicas 11972, lecturas físicas 0, lecturas de lectura anticipada 0, lecturas lógicas lob 0, lecturas físicas lob 0, lecturas de lectura lob 0. 0. Tabla 'Mesa de trabajo'. Cuenta de escaneo 0, lecturas lógicas 0, lecturas físicas 0, lecturas de lectura anticipada 0, lecturas lógicas lob 0, lecturas físicas lob 0, lecturas lob de lectura anticipada 0. Tabla 'Archivo de trabajo'. Recuento de escaneo 0, lecturas lógicas 0, lecturas físicas 0, lecturas de lectura anticipada 0, lecturas lógicas lob 0, lecturas físicas lob 0, lecturas de lectura anticipada lob 0. Tabla 'DailyTransactionComplete'. Cuenta de escaneo 2, lecturas lógicas 620, lecturas físicas 0, lecturas de lectura anticipada 0, lecturas lógicas lob 0, lecturas físicas lob 0, lecturas de lectura lob 0.

Tiempos de ejecución de SQL Server: tiempo de CPU = 983 ms, tiempo transcurrido = 983 ms.

Ver plan

Ah, ya veo, SQL Server está tratando de decirme que lo que estoy haciendo es idiota. Si bien estoy de acuerdo en gran medida, eso no cambia mi situación. En realidad, esto funciona de manera brillante para consultas en las que FileDatela dbo.DailyTransactionvista está incluida en el predicado, pero si bien el MAXplan es lo suficientemente malo, el TOPplan envía todo corriendo hacia el sur. Real sur.

SET STATISTICS IO, TIME ON;

SELECT  TOP 10 FileDate
FROM    dbo.DailyTransaction
GROUP BY FileDate 
ORDER BY FileDate DESC

SET STATISTICS IO, TIME OFF;
GO

Tabla 'DailyTransactionComplete'. Cuenta de escaneo 2, lecturas lógicas 1800110, lecturas físicas 0, lecturas anticipadas 0, lecturas lógicas lob 0, lecturas físicas lob 0, lecturas anticipadas lob 0. Tabla 'DailyTransaction_LEGACY'. Cuenta de escaneo 1, lecturas lógicas 1254, lecturas físicas 0, lecturas de lectura anticipada 0, lecturas lógicas lob 0, lecturas físicas lob 0, lecturas de lectura lob 0. 0. Tabla 'Mesa de trabajo'. Cuenta de escaneo 0, lecturas lógicas 0, lecturas físicas 0, lecturas de lectura anticipada 0, lecturas lógicas lob 0, lecturas físicas lob 0, lecturas lob de lectura anticipada 0. Tabla 'Archivo de trabajo'. Recuento de exploración 0, lecturas lógicas 0, lecturas físicas 0, lecturas de lectura anticipada 0, lecturas lógicas lob 0, lecturas físicas lob 0, lecturas de lectura lob 0.

Tiempos de ejecución de SQL Server: tiempo de CPU = 109559 ms, tiempo transcurrido = 109664 ms.

Parte superior

Mencioné ser "creativo" antes, lo que probablemente fue engañoso. Lo que quise decir fue "más estúpido", por lo que mis intentos de hacer que esta vista funcione durante las operaciones de agregación han sido crear vistas en las tablas dbo.DailyTransactionCompletey dbo.DailyTransaction_LEGACY, vincular esquemas e indexar la última, luego usar esas vistas en otra vista con una NOEXPANDpista en la vista heredada. Si bien está funcionando más o menos por lo que debe hacer por ahora, encuentro que la "solución" completa es bastante molesta, y que culmina con lo siguiente:

IF NOT EXISTS ( SELECT  1
                FROM    sys.objects
                WHERE   name = 'v_DailyTransactionComplete'
                    AND type = 'V' )
BEGIN
    EXEC( 'CREATE VIEW dbo.v_DailyTransactionComplete AS SELECT x = 1;' );
END;
GO

ALTER VIEW dbo.v_DailyTransactionComplete
AS  SELECT  DailyTransaction_PK, FileDate = CONVERT( DATETIME, 
                CONVERT( VARCHAR( 8 ), DateCode_FK ), 112 ), 
            Foo
    FROM    dbo.DailyTransactionComplete;
GO

IF NOT EXISTS ( SELECT  1
                FROM    sys.objects
                WHERE   name = 'v_DailyTransaction_LEGACY'
                    AND type = 'V' )
BEGIN
    EXEC( 'CREATE VIEW dbo.v_DailyTransaction_LEGACY AS SELECT x = 1;' );
END;
GO

ALTER VIEW dbo.v_DailyTransaction_LEGACY
WITH SCHEMABINDING
AS  SELECT  l.DailyTransaction_PK,
            l.FileDate,
            l.Foo,
            CountBig = COUNT_BIG( * )
    FROM    dbo.DailyTransaction_LEGACY l
    INNER JOIN dbo.DailyTransactionComplete n
        ON  l.FileDate <> CONVERT( DATETIME, CONVERT( VARCHAR( 8 ), 
                n.DateCode_FK ), 112 )
    GROUP BY l.DailyTransaction_PK,
            l.FileDate,
            l.Foo;
GO

CREATE UNIQUE CLUSTERED INDEX CI__v_DailyTransaction_LEGACY
    ON dbo.v_DailyTransaction_LEGACY ( FileDate, DailyTransaction_PK )
WITH ( DATA_COMPRESSION = PAGE, FILLFACTOR = 80 );
GO

IF NOT EXISTS ( SELECT  1
                FROM    sys.objects
                WHERE   name = 'DailyTransaction'
                    AND type = 'V' )
BEGIN
    EXEC( 'CREATE VIEW dbo.DailyTransaction AS SELECT x = 1;' );
END;
GO

ALTER VIEW dbo.DailyTransaction
AS  SELECT  DailyTransaction_PK, FileDate, Foo
    FROM    dbo.v_DailyTransactionComplete
    UNION ALL    
    SELECT  DailyTransaction_PK, FileDate, Foo
    FROM    dbo.v_DailyTransaction_LEGACY WITH ( NOEXPAND );
GO

Obligar al optimizador a usar el índice proporcionado por la vista indexada hace que los problemas MAXy TOPdesaparezcan, pero debe haber una mejor manera de lograr lo que estoy tratando de hacer aquí. Absolutamente cualquier sugerencia / regaño sería muy apreciada!

SET STATISTICS IO, TIME ON;

DECLARE @ConsumeOutput1        DATETIME;
SELECT  @ConsumeOutput1 = MAX( FileDate )
FROM    dbo.DailyTransaction;

SET STATISTICS IO, TIME OFF;
GO

Tabla 'v_DailyTransaction_LEGACY'. Cuenta de escaneo 1, lecturas lógicas 3, lecturas físicas 0, lecturas de lectura anticipada 0, lecturas lógicas lob 0, lecturas físicas lob 0, lecturas de lectura anticipada lob 0. Tabla 'DailyTransactionComplete'. Cuenta de escaneo 1, lecturas lógicas 310, lecturas físicas 0, lecturas de lectura anticipada 0, lecturas lógicas lob 0, lecturas físicas lob 0, lecturas de lectura lob 0.

Tiempos de ejecución de SQL Server: tiempo de CPU = 31 ms, tiempo transcurrido = 36 ms.

SET STATISTICS IO, TIME ON;

DECLARE @ConsumeOutput1        DATETIME;
SELECT  TOP 10 @ConsumeOutput1 = FileDate
FROM    dbo.DailyTransaction
GROUP BY FileDate 
ORDER BY FileDate DESC

SET STATISTICS IO, TIME OFF;
GO

Tabla 'v_DailyTransaction_LEGACY'. Cuenta de escaneo 1, lecturas lógicas 101, lecturas físicas 0, lecturas de lectura anticipada 0, lecturas lógicas lob 0, lecturas físicas lob 0, lecturas lob de lectura anticipada 0. Tabla 'Mesa de trabajo'. Cuenta de escaneo 0, lecturas lógicas 0, lecturas físicas 0, lecturas de lectura anticipada 0, lecturas lógicas lob 0, lecturas físicas lob 0, lecturas lob de lectura anticipada 0. Tabla 'Archivo de trabajo'. Recuento de escaneo 0, lecturas lógicas 0, lecturas físicas 0, lecturas de lectura anticipada 0, lecturas lógicas lob 0, lecturas físicas lob 0, lecturas de lectura anticipada lob 0. Tabla 'DailyTransactionComplete'. Cuenta de escaneo 1, lecturas lógicas 310, lecturas físicas 0, lecturas de lectura anticipada 0, lecturas lógicas lob 0, lecturas físicas lob 0, lecturas de lectura lob 0.

Tiempos de ejecución de SQL Server: tiempo de CPU = 63 ms, tiempo transcurrido = 66 ms.

TL; DR:

Ayúdame a comprender lo que necesito hacer para hacer consultas de agregación en la primera vista que mencioné que se ejecuta en cantidades razonables de tiempo con una utilización razonable de los recursos de E / S.

Avarkx
fuente
3
Una vista no indexada no almacena ningún dato, y no puede indexar una vista con subconsultas, uniones, etc. Creo que debe considerar materializar los datos de una manera diferente, como dividir esa vista en dos vistas. y luego consultar en contra de ellos, o pasar por alto la vista por completo.
Aaron Bertrand
Creo que estoy tratando de lograr lo que estás sugiriendo, pero no entiendo completamente lo que necesito hacer. Estoy pensando que la vista heredada que he logrado indexar y materializar como una curita parece ser una solución bastante burda a la limitación de subconsultas, y aunque funciona más o menos en el estado actual, es muy susceptible a más alcance fluencia. Estoy luchando con la idea de configurar un proceso para poblar una tabla base completamente nueva después de que ocurra una importación y alterar la vista para hacer referencia a eso.
Avarkx

Respuestas:

4

Reescribir NOT EXISTScomo DISTINCTsobre una unión de desigualdad permite indexar la vista, pero hay buenas razones para que esto no se haga comúnmente.

El plan de ejecución generado para construir el índice en la vista es inevitablemente horrible. La desigualdad obliga a una unión física de bucles anidados, que con la excepción de un valor, es una unión cruzada. Colapsar el producto con un grupo distinto o equivalente producirá los resultados correctos, suponiendo que la columna de unión no sea anulable (como en el código de ejemplo), pero nunca será eficiente. Esta ineficiencia solo empeorará con el tiempo y las mesas involucradas se harán más grandes.

Problemas similares afectan el plan de ejecución de cualquier instrucción DML que afecte a una tabla a la que hace referencia la vista (porque la vista debe estar sincronizada con las tablas base en todo momento en SQL Server). Mire el plan de ejecución generado para agregar o modificar una sola fila en cualquier tabla para ver a qué me refiero.

En un nivel alto, el problema con el que está luchando es que el optimizador de consultas de SQL Server no siempre genera buenos planes sobre las vistas que incluyen a UNION ALL. Muchas de las optimizaciones que damos por sentado (como MAX-> TOP (1)) simplemente no se implementan en todo union.

Para cada problema que resuelva, encontrará otro caso en el que no se produce una optimización normal y esperada, lo que da como resultado un plan de ejecución con un rendimiento desesperado. La solución obvia es evitar el uso de la unión en las vistas. La forma de implementar esto en su caso depende de detalles que, a pesar de los detalles en la pregunta, probablemente solo los conozca usted.

Si tiene espacio, una solución es mantener completey legacybasar las tablas por separado (incluida la lógica no existe). Esto da como resultado la duplicación de datos y viene con problemas de sincronización, pero en mi experiencia, estos son mucho más fáciles de resolver de manera sólida que tratar de obtener vistas sindicales para generar buenos planes de ejecución para una amplia gama de consultas en todas (o incluso la mayoría) de las circunstancias.

SQL Server proporciona una serie de características para ayudar con la sincronización de datos, como estoy seguro de que sabe, incluido el seguimiento de cambios, la captura de datos, los desencadenantes ... y así sucesivamente. Los detalles de la implementación están más allá de este foro. El punto importante es presentar el optimizador con tablas base, no unir todas las vistas.

Paul White 9
fuente
Gracias a usted y a @AaronBertrand por su aporte. ¡Sus ideas y sugerencias son muy apreciadas! Probablemente termine migrando manualmente los datos de la tabla anterior a la nueva tabla para poder alterar la vista para que ya no necesite unirme desde la tabla anterior. Teóricamente, debería poder soltar la tabla heredada por completo. Habrá otros desafíos con este enfoque, pero como mencionó, quizás esos desafíos serán más manejables a largo plazo, ya que lo que estoy haciendo obviamente no va a funcionar bien, nunca.
Avarkx