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.
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
| Error | Causa | Solución |
|---|---|---|
| mssql-tools/bin/sqlcmd: no such file | Path viejo de mssql-tools | Usar mssql-tools18 |
| gzip: stdin: not in gzip format | URL de descarga incorrecta | Descargar desde GitHub releases |
| Could not resolve host | Sin acceso a esa URL | Buscar la URL correcta en el repo oficial |
| Query no ejecuta (interactivo) | Falta GO al final | Escribir GO en línea nueva y Enter |
| -No flag | Certificado SSL no confiable | Normal en desarrollo, -No lo ignora |
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áusula | Para qué sirve | Ejemplo |
|---|---|---|
| SELECT TOP N | Limitar filas devueltas | SELECT TOP 10 * |
| WHERE | Filtrar filas | WHERE Color = 'Red' |
| AND / OR | Combinar condiciones | WHERE Price > 100 AND Color = 'Red' |
| BETWEEN | Rango de valores | WHERE Price BETWEEN 100 AND 500 |
| IN | Lista de valores | WHERE Color IN ('Red','Blue') |
| LIKE | Búsqueda parcial | WHERE Name LIKE '%Bike%' |
| IS NULL | Detectar nulos | WHERE Color IS NULL |
| ORDER BY DESC | Ordenar descendente | ORDER BY Price DESC |
| GROUP BY | Agrupar para agregar | GROUP BY Color |
| HAVING | Filtrar grupos | HAVING COUNT(*) > 5 |
| INNER JOIN | Solo filas con match | JOIN Tabla b ON a.id = b.id |
| LEFT JOIN | Todas las filas izquierda | LEFT JOIN Tabla b ON a.id = b.id |
| CASE WHEN | Lógica condicional | CASE WHEN x > 0 THEN 'SI' ELSE 'NO' END |
| DATEADD | Sumar / restar fechas | DATEADD(MONTH, -1, GETDATE()) |
| DATEDIFF | Diferencia de fechas | DATEDIFF(DAY, FechaInicio, FechaFin) |
07 · Funciones de agregación
| Función | Qué 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 |