Una crítica al lenguaje SQL (parte 3)

Definiciones
jueves, 13 de abril de 2017

Cada vez que repites código, Dios mata un gatito

Hace 4 años escribí el segundo artículo de una serie, y lo hice prometiendo que próximamente finalizaría esa reflexión crítica sobre el lenguaje SQL. Nunca lo hice. Me estoy refiriendo concretamente a estos dos artículos imprescindibles:

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. Para justificar esas afirmaciones ponía los siguientes 5 ejemplos, que repito solo por completar la infromación (y por supuesto no es necesario que analicéis ahora...):


-- Ejemplo 1
SELECT
  DimProduct.EnglishProductName AS Producto,
  sum(sales.OrderQuantity) AS Unidades,
  sum(sales.SalesAmount) AS Importe,
  sum(sales.SalesAmount)/sum(sales.OrderQuantity) AS [Precio Medio]
FROM FactResellerSales sales
INNER JOIN DimProduct ON (sales.ProductKey=DimProduct.ProductKey)
INNER JOIN DimProductSubCategory ON (DimProduct.ProductSubcategoryKey=DimProductSubCategory.ProductSubcategoryKey)
INNER JOIN DimProductCategory ON (DimProductSubCategory.ProductCategoryKey=DimProductCategory.ProductCategoryKey)
INNER JOIN DimDate ON (sales.OrderDateKey=DimDate.DateKey)
WHERE
  DimProductCategory.SpanishProductCategoryName='Bicicleta'
  AND DimDate.SpanishMonthName='Enero'
  AND DimDate.CalendarYear=2008
GROUP BY DimProduct.EnglishProductName
ORDER BY sum(sales.SalesAmount)/sum(sales.OrderQuantity) DESC

-- Ejemplo 2
SELECT
  DimProduct.EnglishProductName AS EnglishProductName,
  sum(sales.OrderQuantity) AS Unidades
FROM FactResellerSales sales
INNER JOIN DimReseller ON (sales.ResellerKey=DimReseller.ResellerKey)
INNER JOIN DimGeography ON (DimReseller.GeographyKey=DimGeography.GeographyKey)
INNER JOIN DimProduct ON (sales.ProductKey=DimProduct.ProductKey)
INNER JOIN DimProductSubCategory ON (DimProduct.ProductSubcategoryKey=DimProductSubCategory.ProductSubcategoryKey)
INNER JOIN DimProductCategory ON (DimProductSubCategory.ProductCategoryKey=DimProductCategory.ProductCategoryKey)
INNER JOIN DimDate ON (sales.OrderDateKey=DimDate.DateKey)
WHERE
  DimGeography.SpanishCountryRegionName='Alemania'
  AND DimProductCategory.SpanishProductCategoryName='Bicicleta'
  AND DimDate.CalendarYear=2008
GROUP BY DimProduct.EnglishProductName

--Ejemplo 3
SELECT
  DimGeography.SpanishCountryRegionName AS SpanishCountryRegionName,
  sum(sales.OrderQuantity) AS Unidades
FROM FactResellerSales sales
INNER JOIN DimReseller ON (sales.ResellerKey=DimReseller.ResellerKey)
INNER JOIN DimGeography ON (DimReseller.GeographyKey=DimGeography.GeographyKey)
INNER JOIN DimDate ON (sales.OrderDateKey=DimDate.DateKey)
WHERE DimDate.CalendarYear=2008
GROUP BY DimGeography.SpanishCountryRegionName

-- Ejemplo 4
WITH
sales1 AS (
  SELECT
    DimProduct.EnglishProductName AS EnglishProductName,
    sum(sales.OrderQuantity) AS auxcol_2_
  FROM FactResellerSales sales
  INNER JOIN DimProduct ON (sales.ProductKey=DimProduct.ProductKey)
  INNER JOIN DimProductSubCategory ON (DimProduct.ProductSubcategoryKey=DimProductSubCategory.ProductSubcategoryKey)
  INNER JOIN DimProductCategory ON (DimProductSubCategory.ProductCategoryKey=DimProductCategory.ProductCategoryKey)
  INNER JOIN DimDate ON (sales.OrderDateKey=DimDate.DateKey)
  WHERE
    DimDate.CalendarYear=2007
    AND DimProductCategory.SpanishProductCategoryName='Bicicleta'
    AND DimDate.SpanishMonthName='Enero'
  GROUP BY DimProduct.EnglishProductName
),
sales2 AS (
  SELECT
    DimProduct.EnglishProductName AS EnglishProductName,
    sum(sales.OrderQuantity) AS auxcol_3_
  FROM FactResellerSales sales
  INNER JOIN DimProduct ON (sales.ProductKey=DimProduct.ProductKey)
  INNER JOIN DimProductSubCategory ON (DimProduct.ProductSubcategoryKey=DimProductSubCategory.ProductSubcategoryKey)
  INNER JOIN DimProductCategory ON (DimProductSubCategory.ProductCategoryKey=DimProductCategory.ProductCategoryKey)
  INNER JOIN DimDate ON (sales.OrderDateKey=DimDate.DateKey)
  WHERE
    DimDate.CalendarYear=2008
    AND DimProductCategory.SpanishProductCategoryName='Bicicleta'
    AND DimDate.SpanishMonthName='Enero'
  GROUP BY DimProduct.EnglishProductName
),
COMUN AS (
  SELECT DISTINCT EnglishProductName FROM sales1
  UNION SELECT DISTINCT EnglishProductName FROM sales2
)
SELECT
  COMUN.EnglishProductName,
  sales1.auxcol_2_ AS [Ventas Enero 2007],
  sales2.auxcol_3_ AS [Ventas Enero 2008]
FROM COMUN
LEFT JOIN sales1 ON COMUN.EnglishProductName=sales1.EnglishProductName
LEFT JOIN sales2 ON COMUN.EnglishProductName=sales2.EnglishProductName

-- Ejemplo 5
WITH
isales1 AS (
  SELECT
    DimDate.CalendarYear AS CalendarYear,
    sum(isales.SalesAmount) AS ventasInternet
  FROM FactInternetSales isales
  INNER JOIN DimDate ON (isales.OrderDateKey=DimDate.DateKey)
  INNER JOIN DimCustomer ON (isales.CustomerKey=DimCustomer.CustomerKey)
  INNER JOIN DimGeography ON (DimCustomer.GeographyKey=DimGeography.GeographyKey)
  WHERE DimGeography.SpanishCountryRegionName='Francia'
  GROUP BY DimDate.CalendarYear
),
sales1 AS (
  SELECT
    DimDate.CalendarYear AS CalendarYear,
    sum(sales.SalesAmount) AS ventasReseller
  FROM FactResellerSales sales
  INNER JOIN DimReseller ON (sales.ResellerKey=DimReseller.ResellerKey)
  INNER JOIN DimGeography ON (DimReseller.GeographyKey=DimGeography.GeographyKey)
  INNER JOIN DimDate ON (sales.OrderDateKey=DimDate.DateKey)
  WHERE DimGeography.SpanishCountryRegionName='Francia'
  GROUP BY DimDate.CalendarYear
),
COMUN AS (
  SELECT DISTINCT CalendarYear FROM isales1
  UNION SELECT DISTINCT CalendarYear FROM sales1
)
SELECT
  COMUN.CalendarYear,
  isales1.ventasInternet AS ventasInternet,
  sales1.ventasReseller AS ventasReseller,
  coalesce(sales1.ventasReseller,0)+coalesce(isales1.ventasInternet,0) AS ventas
FROM COMUN
LEFT JOIN isales1 ON COMUN.CalendarYear=isales1.CalendarYear
LEFT JOIN sales1 ON COMUN.CalendarYear=sales1.CalendarYear

En definitiva, apuntaba a estas deficiencias del lenguaje:

  1. Repetición de las mismas fórmulas en las distintas cláusulas de la sentencia
  2. La insensatez de las cláusulas GROUP BY. ¿En qué estarían pensando los creadores del lenguaje cuando decidieron crear la cláusula GROUP BY?
  3. Repetición de los JOIN en las distintas sentencias
  4. Dificultad de realizar operaciones entre filas
  5. Dificultad de realizar operaciones entre tablas de hecho

Si releéis los comentarios de entonces, veréis que algunos me daban la razón, mientras otros decían que ellos escribirían las SQL de otro modo. Algunos proponían utilizar vistas, o procedimientos, otros no lo aclararon... Son comentarios interesantes, también.

No quiero alargarme por no repetirme (DRY...). Lo que quería exponer en su momento creo que está claramente expresado en las parte 1 y parte 2 de esta serie. Hoy sigo manteniendo la misma opinión, y aunque dejase la serie colgada, no me olvidé de esos artículos ni de este "temita" del SQL. De hecho, no he dejado de pensar en ello desde entonces...

Sirva este recordatorio para continuar la serie. Y avanzo que el próximo martes publicaré un nuevo artículo con la solución a todos los problemas planteados. Y un GRAN ANUNCIO.

KEEP TUNED.