Volver
10 de dic de 2024 · 12 min de lectura

Guía SQL

Las queries más usadas en entornos laborales reales: setup con Docker, reportes de ventas, análisis de clientes, inventario, limpieza de datos y referencia de sintaxis. SQL Server + AdventureWorks.

SQLDataSQL ServerReferencia

00 · Setup del entorno de trabajo

Levantar SQL Server con Docker y cargar AdventureWorks en Linux.

Paso 0 — Instalar Docker

Ubuntu / Debian:

sudo apt update
sudo apt install -y docker.io
sudo systemctl enable --now docker
sudo usermod -aG docker $USER
newgrp docker

Arch Linux:

sudo pacman -S docker
sudo systemctl enable --now docker
sudo usermod -aG docker $USER
newgrp docker

Verificar con `docker ps`. Si no hay contenedores muestra una tabla vacía — eso es correcto.

Paso 1 — Instalar y levantar SQL Server

Un único comando descarga SQL Server y lo deja corriendo:

docker run -e "ACCEPT_EULA=Y" -e "SA_PASSWORD=Admin1234!" \
  -p 1433:1433 --name sqlserver \
  -d mcr.microsoft.com/mssql/server:2019-latest

La primera vez descarga la imagen (1–2 min); después es instantáneo. Para parar o reiniciar:

docker stop sqlserver    # parar
docker start sqlserver   # volver a levantar

Paso 2 — Descargar AdventureWorks

wget https://github.com/Microsoft/sql-server-samples/releases/download/adventureworks/AdventureWorks2019.bak

Paso 3 — Copiar el .bak al contenedor

docker cp AdventureWorks2019.bak sqlserver:/var/opt/mssql/data/

Paso 4 — Restaurar la base de datos

Usar mssql-tools18 (no mssql-tools, ese path no existe en SQL Server 2019 reciente):

docker exec -it sqlserver /opt/mssql-tools18/bin/sqlcmd \
  -S localhost -U SA -P "Admin1234!" -No \
  -Q "RESTORE DATABASE AdventureWorks2019 FROM DISK='/var/opt/mssql/data/AdventureWorks2019.bak'
  WITH MOVE 'AdventureWorks2019' TO '/var/opt/mssql/data/AdventureWorks2019.mdf',
  MOVE 'AdventureWorks2019_log' TO '/var/opt/mssql/data/AdventureWorks2019_log.ldf'"

Paso 5 — Conectarse a la base

Modo interactivo (cada bloque termina con GO):

docker exec -it sqlserver /opt/mssql-tools18/bin/sqlcmd \
  -S localhost -U SA -P "Admin1234!" -No \
  -d AdventureWorks2019

Modo directo con -Q (ejecuta y sale, cómodo para queries largas):

docker exec -it sqlserver /opt/mssql-tools18/bin/sqlcmd \
  -S localhost -U SA -P "Admin1234!" -No \
  -d AdventureWorks2019 \
  -Q "SELECT TOP 5 Name FROM Production.Product"

Errores comunes

ErrorCausaSolución
mssql-tools/bin/sqlcmd: no such filePath viejo de mssql-toolsUsar mssql-tools18
gzip: stdin: not in gzip formatURL de descarga incorrectaDescargar desde GitHub releases
Could not resolve hostSin acceso a esa URLBuscar la URL correcta en el repo oficial
Query no ejecuta (interactivo)Falta GO al finalEscribir GO en línea nueva y Enter
-No flagCertificado SSL no confiableNormal en desarrollo, -No lo ignora
Verificar que todo funciona: ejecutá SELECT TOP 3 Name, ListPrice FROM Production.Product. Si ves filas con nombres de productos, el entorno está listo.

01 · Explorar la base de datos

Lo primero al entrar a un trabajo nuevo: entender la estructura de la base.

Ver todas las tablas

SELECT SCHEMA_NAME(schema_id) AS esquema, name AS tabla
FROM sys.tables
ORDER BY esquema, tabla

Ver columnas de una tabla

SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'NombreDeTuTabla'
ORDER BY ORDINAL_POSITION

Ver cuántas filas tiene cada tabla

SELECT t.TABLE_NAME, p.rows AS CantFilas
FROM INFORMATION_SCHEMA.TABLES t
JOIN sys.tables st ON st.name = t.TABLE_NAME
JOIN sys.partitions p ON p.object_id = st.object_id
WHERE t.TABLE_TYPE = 'BASE TABLE' AND p.index_id IN (0,1)
ORDER BY p.rows DESC

02 · Reportes de ventas

La query más pedida en cualquier empresa con datos comerciales.

Ventas por categoría

SELECT c.Name AS Categoria,
       SUM(od.LineTotal) AS TotalVentas,
       COUNT(DISTINCT oh.SalesOrderID) AS CantOrdenes
FROM Sales.SalesOrderDetail od
JOIN Production.Product p ON od.ProductID = p.ProductID
JOIN Production.ProductSubcategory s ON p.ProductSubcategoryID = s.ProductSubcategoryID
JOIN Production.ProductCategory c ON s.ProductCategoryID = c.ProductCategoryID
JOIN Sales.SalesOrderHeader oh ON od.SalesOrderID = oh.SalesOrderID
GROUP BY c.Name
ORDER BY TotalVentas DESC

Ventas por mes y año

SELECT YEAR(OrderDate) AS Anio,
       MONTH(OrderDate) AS Mes,
       SUM(TotalDue) AS TotalVentas
FROM Sales.SalesOrderHeader
GROUP BY YEAR(OrderDate), MONTH(OrderDate)
ORDER BY Anio, Mes

Top 10 productos más vendidos

SELECT TOP 10
    p.Name AS Producto,
    SUM(od.OrderQty) AS UnidadesVendidas,
    SUM(od.LineTotal) AS TotalVentas
FROM Sales.SalesOrderDetail od
JOIN Production.Product p ON od.ProductID = p.ProductID
GROUP BY p.Name
ORDER BY UnidadesVendidas DESC

Ventas por vendedor

SELECT
    p.FirstName + ' ' + p.LastName AS Vendedor,
    COUNT(oh.SalesOrderID) AS CantOrdenes,
    SUM(oh.TotalDue) AS TotalVentas
FROM Sales.SalesOrderHeader oh
JOIN Sales.SalesPerson sp ON oh.SalesPersonID = sp.BusinessEntityID
JOIN Person.Person p ON sp.BusinessEntityID = p.BusinessEntityID
GROUP BY p.FirstName, p.LastName
ORDER BY TotalVentas DESC

03 · Análisis de clientes

Entender quién compra, cuánto gasta y con qué frecuencia.

Top clientes por gasto total

SELECT TOP 10
    p.FirstName + ' ' + p.LastName AS Cliente,
    COUNT(oh.SalesOrderID) AS CantOrdenes,
    SUM(oh.TotalDue) AS TotalGastado
FROM Sales.SalesOrderHeader oh
JOIN Sales.Customer c ON oh.CustomerID = c.CustomerID
JOIN Person.Person p ON c.PersonID = p.BusinessEntityID
GROUP BY p.FirstName, p.LastName
ORDER BY TotalGastado DESC

Clientes que no compraron en el último año

SELECT p.FirstName + ' ' + p.LastName AS Cliente,
       MAX(oh.OrderDate) AS UltimaCompra
FROM Sales.Customer c
JOIN Person.Person p ON c.PersonID = p.BusinessEntityID
LEFT JOIN Sales.SalesOrderHeader oh ON c.CustomerID = oh.CustomerID
GROUP BY p.FirstName, p.LastName
HAVING MAX(oh.OrderDate) < DATEADD(YEAR, -1, GETDATE())
    OR MAX(oh.OrderDate) IS NULL
ORDER BY UltimaCompra

04 · Productos e inventario

Controlar el stock y detectar productos sin movimiento.

Productos sin ventas

SELECT p.Name, p.ListPrice
FROM Production.Product p
LEFT JOIN Sales.SalesOrderDetail od ON p.ProductID = od.ProductID
WHERE od.ProductID IS NULL
ORDER BY p.Name

Stock bajo mínimo

SELECT p.Name,
       pi.Quantity AS StockActual,
       p.ReorderPoint AS StockMinimo
FROM Production.Product p
JOIN Production.ProductInventory pi ON p.ProductID = pi.ProductID
WHERE pi.Quantity < p.ReorderPoint
ORDER BY pi.Quantity ASC

Productos más caros por categoría

SELECT c.Name AS Categoria,
       p.Name AS Producto,
       p.ListPrice
FROM Production.Product p
JOIN Production.ProductSubcategory s ON p.ProductSubcategoryID = s.ProductSubcategoryID
JOIN Production.ProductCategory c ON s.ProductCategoryID = c.ProductCategoryID
WHERE p.ListPrice = (
    SELECT MAX(p2.ListPrice)
    FROM Production.Product p2
    JOIN Production.ProductSubcategory s2 ON p2.ProductSubcategoryID = s2.ProductSubcategoryID
    WHERE s2.ProductCategoryID = c.ProductCategoryID
)
ORDER BY c.Name

05 · Limpieza y calidad de datos

En cualquier empresa vas a encontrar datos sucios. Estas queries los detectan.

Filas duplicadas

SELECT EmailAddress, COUNT(*) AS Repeticiones
FROM Person.EmailAddress
GROUP BY EmailAddress
HAVING COUNT(*) > 1
ORDER BY Repeticiones DESC

Valores nulos por columna

SELECT
    COUNT(*) AS TotalFilas,
    SUM(CASE WHEN Color IS NULL THEN 1 ELSE 0 END) AS Color_Nulos,
    SUM(CASE WHEN Size IS NULL THEN 1 ELSE 0 END) AS Size_Nulos,
    SUM(CASE WHEN Weight IS NULL THEN 1 ELSE 0 END) AS Weight_Nulos
FROM Production.Product

06 · Referencia rápida de sintaxis

CláusulaPara qué sirveEjemplo
SELECT TOP NLimitar filas devueltasSELECT TOP 10 *
WHEREFiltrar filasWHERE Color = 'Red'
AND / ORCombinar condicionesWHERE Price > 100 AND Color = 'Red'
BETWEENRango de valoresWHERE Price BETWEEN 100 AND 500
INLista de valoresWHERE Color IN ('Red','Blue')
LIKEBúsqueda parcialWHERE Name LIKE '%Bike%'
IS NULLDetectar nulosWHERE Color IS NULL
ORDER BY DESCOrdenar descendenteORDER BY Price DESC
GROUP BYAgrupar para agregarGROUP BY Color
HAVINGFiltrar gruposHAVING COUNT(*) > 5
INNER JOINSolo filas con matchJOIN Tabla b ON a.id = b.id
LEFT JOINTodas las filas izquierdaLEFT JOIN Tabla b ON a.id = b.id
CASE WHENLógica condicionalCASE WHEN x > 0 THEN 'SI' ELSE 'NO' END
DATEADDSumar / restar fechasDATEADD(MONTH, -1, GETDATE())
DATEDIFFDiferencia de fechasDATEDIFF(DAY, FechaInicio, FechaFin)

07 · Funciones de agregación

FunciónQué hace
COUNT(*)Cuenta todas las filas
COUNT(columna)Cuenta valores no nulos
SUM(columna)Suma los valores
AVG(columna)Promedio de los valores
MIN(columna)Valor mínimo
MAX(columna)Valor máximo
ROUND(n, decimales)Redondea un número
CAST(x AS tipo)Convierte tipo de dato
Volver