Bienvenido

Esto es un blog personal sobre Business Intelligence.

El blog se llama “Business Intelligence fácil” porque considero que las soluciones de BI deberían ser más fáciles de usar de lo que son. Si decides quedarte, verás que es un tema recurrente en este sitio.

Últimos artículos

Cargar las dimensiones de un data warehouse

Comparativa con ejemplos de la carga de una dimension Tipo 1. Si existe algún registro nuevo, lo añadiremos. Si algún registro ha cambiado, lo actualizaremos. 300 líneas de código pero con buen humor.

LEER

Lecturas BI recomendadas (Sant Jordi 2017)

Para este Sant jordi, si te dedicas al Business Intelligence, te recomendamos estas lecturas imprescindibles.

LEER

SQL sin repeticiones (y 5)

Terminamos hoy, por fin, esta serie de 5 artículos mostrando nuestras cartas. Ahora es posible escribir consultas complejas sin incurrir en las redundancias típicas del SQL. Lo único que hemos hecho ha sido pasar la segadora por encima de cada una de las repeticiones que encontrábamos en el código SQL.

LEER

Un SQL con superpoderes (parte 4)

Es un orgullo para nosotros presentar el lenguaje Crono SQL. El lenguaje Crono SQL pretende simplificar la sintaxis del SQL evitando las repeticiones de código y automatizando la generación del código más farragoso y repetitivo.

LEER

Una crítica al lenguaje SQL (parte 3)

En estos artículos, básicamente, criticaba al lenguaje SQL por ser demasiado verboso. Era poco imperativo y el código contenía demasiadas repeticiones y era difícil de escribir. Sirva este recordatorio para continuar la serie.

LEER

Por dónde empezar

Los artículos están clasificados por meses y por categorías. Tal vez quieras dar un vistazo al blog navegando entre los distintos artículos. Lee lo que te interese y comenta si quieres.

También puedes utilizar el cuadro de búsqueda del margen superior. Si aún así no encuentras lo que buscas, acude a nuestro flamante nuevo foro de BI para exponer tu cuestión u opinión:

Sobre Crono

CRONO es un software asequible y fácil de usar para construir informes, cuadros de mando, y hojas Excel. Puedes leer más sobre CRONO y descargarte la versión de evaluación en la página de este software de Business Intelligence. Intentaré no aburriros con nuestras batallitas ni exagerar sobre sus bondades pero inevitablemente hablaré de Crono en este blog personal. Si en alguna ocasión hablo en exceso sobre Crono, hacédmelo saber, que tampoco quiero aburriros :-)

LEER

Suscríbete

Si te gusta el Business Intelligence y quieres mantenerte informado, suscríbete para recibir las actualizaciones de este sitio.

¡SÍ, QUIERO!

Sobre mi

Soy Pau, consultor BI, autor de este blog, programador y socio de Crono. Tengo la suerte de que me gusta lo que hago y encima me pagan por ello.

LEER

Último artículo

Cargar las dimensiones de un data warehouse

Crono
jueves, 27 de abril de 2017

TL;DR;

OK. Pedazo de artículo con 300 líneas de código y más de 1000 palabras que no apetece leer. Te lo resumo para que decidas si continuar: SQL es un lenguaje difícil de escribir, las herramientas ETL tradicionales asi asá, y Crono SQL mola.

Todo argumentado con ejemplos y buen humor. Nada debe tomarse demasiado en serio. :-)

Sáltale la introducción si ya sabes lo que es una SCD.

Introducción

En otros artículos he hablado sobre la carga de dimensiones y he explicado que las tablas de dimensión se caracterizan por cambiar su contenido poco a poco. Por ese motivo, se las conoce como "dimensiones lentamente cambiantes" o "dimensiones de variación lenta". En inglés se llaman "slowly changing dimensions" o simplemente SCD.

Me refiero, concretamente, a estos artículos antiguos:

De todos modos, estas consideraciones son bastante irrelevantes, ya que todas las dimensiones son lentamente cambiantes, y aunque fuesen "rápidamente cambiantes" se cargarían exactamente igual. Por ese motivo, hablaremos simplemente de la carga de las dimensiones de un data warehouse.

Existen 2 maneras de cargar una dimensión:

  • SCD Tipo 1: Se sobreescriben los cambios
  • SCD Tipo 2: Se guarda la historia de cambios

En este artículo me referiré solo a la carga de dimensiones Tipo 1, que son las más sencillas. Tal vez otro día hablaré de las de Tipo 2, que mostrará aún mejor el argumento de este artículo.

Conceptualmente, cargar una dimensión tipo 1 es trivial. Sencillamente queremos sobrescribir los valores del data warehouse con la información vigente en nuestro ERP. Si existe algún registro nuevo, lo añadiremos. Si algún registro ha cambiado, lo actualizaremos. Eso es todo. Parece trivial. Pero, ¿Lo es? Lamentablemente, no tanto...

Para mostrarlo, veremos las alternativas que existen para desnormalizar la información de producto de la base de datos AdventureWorks. El modelo de datos es el siguiente:

SCD1 con SSIS

Cargar dimensiones SCD Tipo 1 con SQL

La consulta para obtener los campos que necesitamos es muy sencilla. A esta operación se llama "desnormalizar". En esto el lenguaje SQL es muy bueno. La consulta es esta:

SELECT
  ProductID,
  Product.Name Product,
  ProductCategory.name ProductCategory,
  ProductSubCategory.name ProductSubCategory,
  ProductNumber,
  ProductModel.name ProductModel,
  Color,
  StandardCost,
  ListPrice,
  Size,
  SizeUnitMeasureCode,
  Weight,
  WeightUnitMeasureCode,
  ProductLine,
  DiscontinuedDate,
  MakeFlag,
  FinishedGoodsFlag
FROM staging.Product
LEFT JOIN staging.ProductSubCategory ON Product.ProductSubcategoryID=ProductSubCategory.ProductSubcategoryID
LEFT JOIN staging.ProductCategory ON ProductSubCategory.ProductCategoryId=ProductCategory.ProductCategoryId
LEFT JOIN staging.ProductModel ON Product.ProductModelID=ProductModel.ProductModelID

Sin embargo, cargar estos datos en la tabla DimProducts es absurdamente complejo. Por este motivo, cada programador lo hace distinto. Algunos priorizan el rendimiento, otros valoran la facilidad mantenimiento, otros son más pragmáticos y priorizan terminar el desarrollo as soon as possible. El lenguaje SQL es terrible para esto.

Lo más sencillo es eliminar la tabla y recargarla cada día. Este seria el código:

-- Elimino la tabla y la vuelvo a cargar (así es más fácil) :-)
DELETE FROM dwh.DimProducts

-- Vaaaaaaamos allá.
INSERT dwh.DimProducts(ProductID,Product,ProductCategory,ProductSubCategory,ProductNumber,ProductModel,Color,StandardCost,ListPrice,Size,SizeUnitMeasureCode,Weight,WeightUnitMeasureCode,ProductLine,DiscontinuedDate,MakeFlag,FinishedGoodsFlag)
SELECT
  ProductID,
  Product.Name AS Product,
  ProductCategory.name AS ProductCategory,
  ProductSubCategory.name AS ProductSubCategory,
  ProductNumber,
  ProductModel.name AS ProductModel,
  Color,
  StandardCost,
  ListPrice,
  Size,
  SizeUnitMeasureCode,
  Weight,
  WeightUnitMeasureCode,
  ProductLine,
  DiscontinuedDate,
  MakeFlag,
  FinishedGoodsFlag
FROM staging.Product
LEFT JOIN staging.ProductSubCategory ON (Product.ProductSubcategoryID=ProductSubCategory.ProductSubcategoryID)
LEFT JOIN staging.ProductCategory ON (ProductSubCategory.ProductCategoryId=ProductCategory.ProductCategoryId)
LEFT JOIN staging.ProductModel ON (Product.ProductModelID=ProductModel.ProductModelID)

Este método lo considero muy desaconsejable. Por un lado, obtendría el peor rendimiento, y además impediría mantener la integridad con el resto de tablas (...existen ventas asociadas a estos productos que estamos eliminando), por lo que se deberían desactivar las claves foráneas o incluso prescindir de ellas totalmente. Tampoco te permitiría mantener la clave subrogada ni mantener campos de auditoría. Por lo tanto, por favor, no lo hagas así.

Si yo tuviese que escribir el código SQL para cargar la tabla DimProducts probablemente haría un INSERT para añadir los registros nuevos y un UPDATE para actualizar los cambios.

--Añado los nuevos clientes. Solo cargo el ID porque luego actualizo el resto
INSERT dwh.DimProducts(ProductID)
SELECT ProductID,
FROM staging.Product
WHERE NOT EXISTS (SELECT 1 FROM dwh.DimProducts WHERE Product.ProductID=DimProducts.ProductID)

-- Actualizamos todos los datos de PRODUCTO (así seguro que no me dejo ningún campo, espero...jeje)
;WITH
query AS (
  SELECT
    target.*,
    Product.ProductID SourceProductId,
    Product.Name AS	SourceProduct,
    ProductCategory.name AS SourceProductCategory,
    ProductSubCategory.name AS SourceProductSubCategory,
    Product.ProductNumber as SourceProductNumber,
    ProductModel.name AS SourceProductModel,
    Product.Color SourceColor,
    Product.StandardCost SourceStandardCost,
    Product.ListPrice sourceListPrice,
    Product.Size SourceSize,
    Product.SizeUnitMeasureCode SourceSizeUnitMeasureCode,
    Product.Weight SourceWeight,
    Product.WeightUnitMeasureCode SourceWeightUnitMeasureCode,
    Product.ProductLine SourceProductLine,
    Product.DiscontinuedDate SourceDiscontinuedDate,
    Product.MakeFlag SourceMakeFlag,
    Product.FinishedGoodsFlag SourceFinishedGoodsFlag 
  FROM  dwh.DimProducts target
  LEFT JOIN staging.Product on Product.ProductId=target.ProductId
  LEFT JOIN staging.ProductSubCategory ON (Product.ProductSubcategoryID=ProductSubCategory.ProductSubcategoryID)
  LEFT JOIN staging.ProductCategory ON (ProductSubCategory.ProductCategoryId=ProductCategory.ProductCategoryId)
  LEFT JOIN staging.ProductModel ON (Product.ProductModelID=ProductModel.ProductModelID)
)
UPDATE query
SET
  Product=SourceProduct,
  ProductCategory=SourceProductCategory,
  ProductSubCategory=SourceProductSubCategory,
  ProductNumber=SourceProductNumber,
  ProductModel=SourceProductModel,
  Color=SourceColor,
  StandardCost=query.StandardCost,
  ListPrice=SourceListPrice,
  Size=SourceSize,
  SizeUnitMeasureCode=SourceSizeUnitMeasureCode,
  Weight=SourceWeight,
  WeightUnitMeasureCode=SourceWeightUnitMeasureCode,
  ProductLine=SourceProductLine,
  DiscontinuedDate=SourceDiscontinuedDate,
  MakeFlag=SourceMakeFlag,
  FinishedGoodsFlag=SourceFinishedGoodsFlag

Me gusta este método porque el código no es excesivamente complejo. Sin embargo, el rendimiento no sería óptimo, porque estamos actualizando todos los registros, hayan cambiado sus valores o no. De todos modos, incluso con dimensiones de pocos millones de registros el rendimiento puede ser aceptable (si tenemos buen hardware y no excesivas columnas....).

Si el rendimiento es un problema, deberemos modificar el código para actualizar solo los registros que han cambiado. Aquí la cosas se empiezan a poner feas. ¿Os he dicho que el lenguaje SQL es ridículamente complejo para esta necesidad taaaaaaan habitual en DWH?

Este es el código necesario para hacerlo "bien":

--Añado los nuevos clientes. Solo cargo el ID porque luego actualizo lo demás.
INSERT dwh.DimProducts(ProductID)
SELECT ProductID,
FROM staging.Product
WHERE NOT EXISTS (SELECT 1 FROM dwh.DimProducts WHERE Product.ProductID=DimProducts.ProductID)


-- Actualizamos todos registros en los que haya cambiado
-- cualquier campo (lo he revisado, ¡están todos! ) (15-ene-1984)
WITH
query AS (
  SELECT
    target.*,
    Product.ProductID SourceProductId,
    Product.Name AS	SourceProduct,
    ProductCategory.name AS SourceProductCategory,
    ProductSubCategory.name AS SourceProductSubCategory,
    Product.ProductNumber as SourceProductNumber,
    ProductModel.name AS SourceProductModel,
    Product.Color SourceColor,
    Product.StandardCost SourceStandardCost,
    Product.ListPrice sourceListPrice,
    Product.Size SourceSize,
    Product.SizeUnitMeasureCode SourceSizeUnitMeasureCode,
    Product.Weight SourceWeight,
    Product.WeightUnitMeasureCode SourceWeightUnitMeasureCode,
    Product.ProductLine SourceProductLine,
    Product.DiscontinuedDate SourceDiscontinuedDate,
    Product.MakeFlag SourceMakeFlag,
    Product.FinishedGoodsFlag SourceFinishedGoodsFlag 
  FROM  dwh.DimProducts target
  LEFT JOIN staging.Product on Product.ProductId=target.ProductId
  LEFT JOIN staging.ProductSubCategory ON (Product.ProductSubcategoryID=ProductSubCategory.ProductSubcategoryID)
  LEFT JOIN staging.ProductCategory ON (ProductSubCategory.ProductCategoryId=ProductCategory.ProductCategoryId)
  LEFT JOIN staging.ProductModel ON (Product.ProductModelID=ProductModel.ProductModelID)
)
UPDATE query
SET
  Product=SourceProduct,
  ProductCategory=SourceProductCategory,
  ProductSubCategory=SourceProductSubCategory,
  ProductNumber=SourceProductNumber,
  ProductModel=SourceProductModel,
  Color=SourceColor,
  StandardCost=SourceStandardCost,
  ListPrice=SourceListPrice,
  Size=SourceSize,
  SizeUnitMeasureCode=SourceSizeUnitMeasureCode,
  Weight=SourceWeight,
  WeightUnitMeasureCode=SourceWeightUnitMeasureCode,
  ProductLine=SourceProductLine,
  DiscontinuedDate=SourceDiscontinuedDate,
  MakeFlag=SourceMakeFlag,
  FinishedGoodsFlag=SourceFinishedGoodsFlag
WHERE (
  Product<>SourceProduct OR (Product IS NULL AND SourceProduct IS NOT NULL) OR  (Product IS NOT NULL AND SourceProduct IS NULL)
  OR ProductCategory<>SourceProductCategory OR (ProductCategory IS NULL AND SourceProductCategory IS NOT NULL) OR  (ProductCategory IS NOT NULL AND SourceProductCategory IS NULL)
  OR ProductSubCategory<>SourceProductSubCategory OR (ProductSubCategory IS NULL AND SourceProductSubCategory IS NOT NULL) OR  (ProductSubCategory IS NOT NULL AND SourceProductSubCategory IS NULL)
  OR ProductNumber<>SourceProductNumber OR (ProductNumber IS NULL AND SourceProductNumber IS NOT NULL) OR  (ProductNumber IS NOT NULL AND SourceProductNumber IS NULL)
  OR ProductModel<>SourceProductModel OR (ProductModel IS NULL AND SourceProductModel IS NOT NULL) OR  (ProductModel IS NOT NULL AND SourceProductModel IS NULL)
  OR Color<>SourceColor OR (Color IS NULL AND SourceColor IS NOT NULL) OR  (Color IS NOT NULL AND SourceColor IS NULL)
  OR StandardCost<>SourceStandardCost OR (StandardCost IS NULL AND SourceStandardCost IS NOT NULL) OR  (StandardCost IS NOT NULL AND SourceStandardCost IS NULL)
  OR ListPrice<>SourceListPrice OR (ListPrice IS NULL AND SourceListPrice IS NOT NULL) OR  (ListPrice IS NOT NULL AND SourceListPrice IS NULL)
  OR Size<>SourceSize OR (Size IS NULL AND SourceSize IS NOT NULL) OR  (Size IS NOT NULL AND SourceSize IS NULL)
  OR SizeUnitMeasureCode<>SourceSizeUnitMeasureCode OR (SizeUnitMeasureCode IS NULL AND SourceSizeUnitMeasureCode IS NOT NULL) OR  (SizeUnitMeasureCode IS NOT NULL AND SourceSizeUnitMeasureCode IS NULL)
  OR Weight<>SourceWeight OR (Weight IS NULL AND SourceWeight IS NOT NULL) OR  (Weight IS NOT NULL AND SourceWeight IS NULL)
  OR WeightUnitMeasureCode<>SourceWeightUnitMeasureCode OR (WeightUnitMeasureCode IS NULL AND SourceWeightUnitMeasureCode IS NOT NULL) OR  (WeightUnitMeasureCode IS NOT NULL AND SourceWeightUnitMeasureCode IS NULL)
  OR ProductLine<>SourceProductLine OR (ProductLine IS NULL AND SourceProductLine IS NOT NULL) OR  (ProductLine IS NOT NULL AND SourceProductLine IS NULL)
  OR DiscontinuedDate<>SourceDiscontinuedDate OR (DiscontinuedDate IS NULL AND SourceDiscontinuedDate IS NOT NULL) OR  (DiscontinuedDate IS NOT NULL AND SourceDiscontinuedDate IS NULL)
  OR MakeFlag<>SourceMakeFlag OR (MakeFlag IS NULL AND SourceMakeFlag IS NOT NULL) OR  (MakeFlag IS NOT NULL AND SourceMakeFlag IS NULL)
  OR FinishedGoodsFlag<>SourceFinishedGoodsFlag OR (FinishedGoodsFlag IS NULL AND SourceFinishedGoodsFlag IS NOT NULL) OR  (FinishedGoodsFlag IS NOT NULL AND SourceFinishedGoodsFlag IS NULL)
  )

También existe la instrucción MERGE que te permite realizar el INSERT y el UPDATE en un solo paso. Así debes escribirlo si quieres hacerlo "bien bien":

-- El hermano de Cándida decía que esto actualiza todos los registros que 
-- hayan cambiado (Hagámoslo siempre así).Era MVP Microsoft y estudió 
-- en el colegio de las madres lazaristas de San Gervasio. 
-- Preguntad a Pedro si hay dudas (¡se lo explicó a él antes
-- de aquello con el doctor Sugrañés!)  
WITH
query AS (
  SELECT
    ProductID,
    Product.Name AS Product,
    ProductCategory.name AS ProductCategory,
    ProductSubCategory.name AS ProductSubCategory,
    ProductNumber,
    ProductModel.name AS ProductModel,
    Color,
    StandardCost,
    ListPrice,
    Size,
    SizeUnitMeasureCode,
    Weight,
    WeightUnitMeasureCode,
    ProductLine,
    DiscontinuedDate,
    MakeFlag,
    FinishedGoodsFlag
  FROM staging.Product
  LEFT JOIN staging.ProductSubCategory ON (Product.ProductSubcategoryID=ProductSubCategory.ProductSubcategoryID)
  LEFT JOIN staging.ProductCategory ON (ProductSubCategory.ProductCategoryId=ProductCategory.ProductCategoryId)
  LEFT JOIN staging.ProductModel ON (Product.ProductModelID=ProductModel.ProductModelID)
)
MERGE dwh.DimProducts AS DimProducts
USING query ON query.ProductID=DimProducts.ProductID
WHEN MATCHED AND ((DimProducts.Product<>query.Product OR (DimProducts.Product IS NULL AND query.Product IS NOT NULL) OR  (DimProducts.Product IS NOT NULL AND query.Product IS NULL)
                  OR DimProducts.ProductCategory<>query.ProductCategory OR (DimProducts.ProductCategory IS NULL AND query.ProductCategory IS NOT NULL) OR  (DimProducts.ProductCategory IS NOT NULL AND query.ProductCategory IS NULL)
                  OR DimProducts.ProductSubCategory<>query.ProductSubCategory OR (DimProducts.ProductSubCategory IS NULL AND query.ProductSubCategory IS NOT NULL) OR  (DimProducts.ProductSubCategory IS NOT NULL AND query.ProductSubCategory IS NULL)
                  OR DimProducts.ProductNumber<>query.ProductNumber OR (DimProducts.ProductNumber IS NULL AND query.ProductNumber IS NOT NULL) OR  (DimProducts.ProductNumber IS NOT NULL AND query.ProductNumber IS NULL)
                  OR DimProducts.ProductModel<>query.ProductModel OR (DimProducts.ProductModel IS NULL AND query.ProductModel IS NOT NULL) OR  (DimProducts.ProductModel IS NOT NULL AND query.ProductModel IS NULL)
                  OR DimProducts.Color<>query.Color OR (DimProducts.Color IS NULL AND query.Color IS NOT NULL) OR  (DimProducts.Color IS NOT NULL AND query.Color IS NULL)
                  OR DimProducts.StandardCost<>query.StandardCost OR (DimProducts.StandardCost IS NULL AND query.StandardCost IS NOT NULL) OR  (DimProducts.StandardCost IS NOT NULL AND query.StandardCost IS NULL)
                  OR DimProducts.ListPrice<>query.ListPrice OR (DimProducts.ListPrice IS NULL AND query.ListPrice IS NOT NULL) OR  (DimProducts.ListPrice IS NOT NULL AND query.ListPrice IS NULL)
                  OR DimProducts.Size<>query.Size OR (DimProducts.Size IS NULL AND query.Size IS NOT NULL) OR  (DimProducts.Size IS NOT NULL AND query.Size IS NULL)
                  OR DimProducts.SizeUnitMeasureCode<>query.SizeUnitMeasureCode OR (DimProducts.SizeUnitMeasureCode IS NULL AND query.SizeUnitMeasureCode IS NOT NULL) OR  (DimProducts.SizeUnitMeasureCode IS NOT NULL AND query.SizeUnitMeasureCode IS NULL)
                  OR DimProducts.Weight<>query.Weight OR (DimProducts.Weight IS NULL AND query.Weight IS NOT NULL) OR  (DimProducts.Weight IS NOT NULL AND query.Weight IS NULL)
                  OR DimProducts.WeightUnitMeasureCode<>query.WeightUnitMeasureCode OR (DimProducts.WeightUnitMeasureCode IS NULL AND query.WeightUnitMeasureCode IS NOT NULL) OR  (DimProducts.WeightUnitMeasureCode IS NOT NULL AND query.WeightUnitMeasureCode IS NULL)
                  OR DimProducts.ProductLine<>query.ProductLine OR (DimProducts.ProductLine IS NULL AND query.ProductLine IS NOT NULL) OR  (DimProducts.ProductLine IS NOT NULL AND query.ProductLine IS NULL)
                  OR DimProducts.DiscontinuedDate<>query.DiscontinuedDate OR (DimProducts.DiscontinuedDate IS NULL AND query.DiscontinuedDate IS NOT NULL) OR  (DimProducts.DiscontinuedDate IS NOT NULL AND query.DiscontinuedDate IS NULL)
                  OR DimProducts.MakeFlag<>query.MakeFlag OR (DimProducts.MakeFlag IS NULL AND query.MakeFlag IS NOT NULL) OR  (DimProducts.MakeFlag IS NOT NULL AND query.MakeFlag IS NULL)
                  OR DimProducts.FinishedGoodsFlag<>query.FinishedGoodsFlag OR (DimProducts.FinishedGoodsFlag IS NULL AND query.FinishedGoodsFlag IS NOT NULL) OR  (DimProducts.FinishedGoodsFlag IS NOT NULL AND query.FinishedGoodsFlag IS NULL))) THEN
  UPDATE SET
    Product=query.Product,
    ProductCategory=query.ProductCategory,
    ProductSubCategory=query.ProductSubCategory,
    ProductNumber=query.ProductNumber,
    ProductModel=query.ProductModel,
    Color=query.Color,
    StandardCost=query.StandardCost,
    ListPrice=query.ListPrice,
    Size=query.Size,
    SizeUnitMeasureCode=query.SizeUnitMeasureCode,
    Weight=query.Weight,
    WeightUnitMeasureCode=query.WeightUnitMeasureCode,
    ProductLine=query.ProductLine,
    DiscontinuedDate=query.DiscontinuedDate,
    MakeFlag=query.MakeFlag,
    FinishedGoodsFlag=query.FinishedGoodsFlag
WHEN NOT MATCHED THEN
  INSERT (ProductID,Product,ProductCategory,ProductSubCategory,ProductNumber,ProductModel,Color,StandardCost,ListPrice,Size,SizeUnitMeasureCode,Weight,WeightUnitMeasureCode,ProductLine,DiscontinuedDate,MakeFlag,FinishedGoodsFlag) VALUES (
    query.ProductID,
    query.Product,
    query.ProductCategory,
    query.ProductSubCategory,
    query.ProductNumber,
    query.ProductModel,
    query.Color,
    query.StandardCost,
    query.ListPrice,
    query.Size,
    query.SizeUnitMeasureCode,
    query.Weight,
    query.WeightUnitMeasureCode,
    query.ProductLine,
    query.DiscontinuedDate,
    query.SourceMakeFlag,
    query.FinishedGoodsFlag

Como se observa claramente, la dificultad aparece en el momento de comparar, campo a campo, si algún registro ha cambiado. Es una dificultad solo para el programador, ya que con esta sintaxis el motor de la base de datos está más feliz que un gínjol. No hay relación entre longitud y complejidad del código y rendimiento. Cualquier motor de base de datos es muy eficiente con este tipo de comparaciones. Es solo trabajo de CPU. Comparar unos y ceros para confirmar que la dimensión apenas ha cambiado desde la carga anterior... Actualizar los cambios, insertar los nuevos, ¡y listos!.

Si miras una vez más el último código, que consideramos "fetén", entenderás que nadie lo haga así. Nadie. Los programadores -los conozco bien- harán todo tipo de asunciones equivocadas para evitar eso, o se montarán una macro Excel que se lo genere, o harán una metaquery de una query que genera parte del código, o defenderán con razón la necesidad de adquirir una herramienta ETL... (nadie es perfecto)

Cargar dimensiones SCD Tipo 1 con herramientas ETL

Las herramientas gráficas son a los procesos ETL/DWH lo mismo que el WYSIWYG a la programación de páginas web. Deberás diagramar en un flujo de procesos la lógica de la carga. Tras haberlo intentado con SQL, te convencerán fácilmente de las bondades de este tipo de herramientas:

SCD1 con SSIS

Debo aclarar que estoy hablando siempre de herramientas ETL destinadas a la construcción de un DWH. Para otras necesidades de integración, las herramientas ETL no son solo fantásticas, sino que son imprescindibles. Sin embargo, para ETL/DWH, mi opinión sobre las herramientas gráficas ETL no es extraordinariamente positiva y, de hecho, hay algunas cosas que sería necesario mejorar. No son siempre tan tan buenas como nos las pintan. Asi es como yo veo el anterior diagrama:

Opinión sobre SCD1 con SSIS

De acuerdo. Se me ha visto el plumero. Soy muy crítico con las herramientas ETL y me cuesta encontrar sus virtudes. Además tengo intereses comerciales. En cualquier caso, en mi defensa, debo decir que las conozco y que opinaba exactamente igual hace 15 años cuando me tocó trabajar con ellas. Pero quiero ser ecuánime y contrarestar mi evidente sesgo. Por eso, queridos lectores, agradeceré si utilizáis los comentarios para completar el artículo con un link a un buen tutorial sobre como cargar dimensiones con SSIS, Kettle, ODI o PowerCenter... No lo hago yo por no parecer tendencioso en la elección. :-)

Tal vez, las herramientas ETL facilitan algo el desarrollo y bastante el mantenimiento posterior. Y digo "tal vez" porque conozco proyectos fallidos que me obligan a cuestionarlo.

Si hace tiempo que utilizas una herramienta ETL, tal vez has olvidado por que empezaste a utilizarlas. Tal vez es algo que te encontraste cuando llegaste y nunca has cuestionado... ¿Por qué usamos herramientas gráficas para programar procesos ETL/DWH? ¡Porque escribir SQL eficiente es difícil, farragoso, aburrido y propenso a errores! ¡Y porque el mantenimiento de un proyecto complejo porgramado en SQL es básicamente imposible! ¿Las seguirías utilizando si el SQL no tuviera estos defectos? ¿En serio?

Cargar dimensiones SCD Tipo 1 con Crono SQL

SQL Con superpoderes

Se hace así:

-- "Good code is its own best documentation." - Steve McConnell
LOAD  dwh.DimProducts WITH SCD1
SELECT
  #ProductID,
  Product.Name Product,
  ProductCategory.name ProductCategory,
  ProductSubCategory.name ProductSubCategory,
  ProductNumber,
  ProductModel.name ProductModel,
  Color,
  StandardCost,
  ListPrice,
  Size,
  SizeUnitMeasureCode,
  Weight,
  WeightUnitMeasureCode,
  ProductLine,
  DiscontinuedDate,
  MakeFlag,
  FinishedGoodsFlag
FROM staging.Product
LEFT JOIN staging.ProductSubCategory USING ProductSubcategoryID
LEFT JOIN staging.ProductCategory USING ProductSubCategory(ProductCategoryId)
LEFT JOIN staging.ProductModel USING ProductModelID

Fácil. QED.

Síguenos

Recuerda que podéis apuntaros a nuestra newletter sobre Business Intelligence o seguirnos en redes sociales:

Gracias por la atención. Gracias por difundir.

COMENTAR