🎯 Definición Teórica: El Por Qué Técnico
SQL Puro: El Maestro Ancestral
-- Consulta directa, potencia total
SELECT u.name, COUNT(o.id) as ordenes
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.created_at > '2024-01-01'
GROUP BY u.id
HAVING COUNT(o.id) > 5
ORDER BY ordenes DESC
LIMIT 10;
Ventajas:
- ✅ Control total sobre consultas
- ✅ Máximo rendimiento
- ✅ Sin abstracción
- ✅ Portabilidad entre DBs (con adaptadores)
Desventajas:
- ❌ Código verbose
- ❌ Propenso a SQL Injection
- ❌ Difícil de mantener en proyectos grandes
- ❌ Dependencia fuerte del dialecto SQL
ORM: El Ninja Moderno
// Equivalente en Eloquent (Laravel)
$usuarios = User::where('created_at', '>', '2024-01-01')
->withCount('ordenes')
->having('ordenes', '>', 5)
->orderByDesc('ordenes')
->limit(10)
->get();
Ventajas:
- ✅ Abstracción de la base de datos
- ✅ Seguridad contra inyecciones por defecto
- ✅ Código legible y mantenible
- ✅ Relaciones como objetos
- ✅ Migraciones y versionamiento de esquema
Desventajas:
- ❌ Overhead en consultas complejas
- ❌ Puede generar N+1 queries
- ❌ Curva de aprendizaje
🗡️ Guía de Implementación
Comparativa Práctica
| Operación | SQL Puro | ORM (Eloquent) |
|---|---|---|
| Crear registro | INSERT | Model::create() |
| Leer datos | SELECT | Model::find() |
| Actualizar | UPDATE | $model->save() |
| Eliminar | DELETE | $model->delete() |
| Relaciones | JOIN manual | with(), hasMany() |
| Transacciones | BEGIN/COMMIT | DB::transaction() |
Ejemplo Completo: CRUD Completo
SQL Puro (PHP/PDO)
class UsuarioDAO {
private $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
public function crear(array $datos): int {
$sql = "INSERT INTO users (name, email, password)
VALUES (:name, :email, :password)";
$stmt = $this->pdo->prepare($sql);
$stmt->execute([
':name' => $datos['name'],
':email' => $datos['email'],
':password' => password_hash($datos['password'], PASSWORD_BCRYPT)
]);
return (int) $this->pdo->lastInsertId();
}
public function buscar(int $id): ?array {
$sql = "SELECT * FROM users WHERE id = :id";
$stmt = $this->pdo->prepare($sql);
$stmt->execute([':id' => $id]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
return $result ?: null;
}
public function actualizar(int $id, array $datos): bool {
$sql = "UPDATE users SET name = :name, email = :email
WHERE id = :id";
$stmt = $this->pdo->prepare($sql);
return $stmt->execute([
':name' => $datos['name'],
':email' => $datos['email'],
':id' => $id
]);
}
public function eliminar(int $id): bool {
$sql = "DELETE FROM users WHERE id = :id";
$stmt = $this->pdo->prepare($sql);
return $stmt->execute([':id' => $id]);
}
}
ORM (Eloquent)
// Modelo (User.php)
class User extends Model {
protected $fillable = ['name', 'email', 'password'];
protected $hidden = ['password'];
public function ordenes() {
return $this->hasMany(Orden::class);
}
}
// Uso
$user = User::create([
'name' => 'Shinobi',
'email' => 'ninja@dojo.com',
'password' => 'secreto123'
]);
$usuario = User::find(1);
$usuario->name = 'Nuevo nombre';
$usuario->save();
User::destroy(1);
🥷 Reto Ninja
Nivel: Chunin → Jonin
Misión 1: Convierte SQL a ORM
Traduce estas consultas SQL a Eloquent:
-- 1. SELECT * FROM posts WHERE status = 'published' ORDER BY created_at DESC;
$posts = Post::where('status', 'published')
->orderBy('created_at', 'desc')
->get();
-- 2. SELECT * FROM users WHERE email LIKE '%@gmail.com' AND active = 1;
$usuarios = User::where('email', 'like', '%@gmail.com')
->where('active', true)
->get();
-- 3. SELECT users.*, COUNT(orders.id) as total FROM users
-- LEFT JOIN orders ON users.id = orders.user_id
-- GROUP BY users.id;
$usuarios = User::withCount('orders')
->withCount(['orders' => function($q) {
$q->where('status', 'completed');
}])
->get();
Misión 2: Optimización N+1
// ❌ PROBLEMA: N+1 Query
$usuarios = User::all();
foreach ($usuarios as $user) {
echo $user->ordenes->count(); // Una query por cada usuario!
}
// ✅ SOLUCIÓN: Eager Loading
$usuarios = User::with('ordenes')->get();
foreach ($usuarios as $user) {
echo $user->ordenes->count(); // Solo 2 queries total!
}
Misión 3: Transacciones Seguras
// SQL Puro
try {
$pdo->beginTransaction();
$pdo->exec("UPDATE accounts SET balance = balance - 100 WHERE id = 1");
$pdo->exec("UPDATE accounts SET balance = balance + 100 WHERE id = 2");
$pdo->commit();
} catch (Exception $e) {
$pdo->rollBack();
}
// ORM (Eloquent) - Más legible
try {
DB::transaction(function () {
$cuentaOrigen = Cuenta::find(1);
$cuentaDestino = Cuenta::find(2);
$cuentaOrigen->decrement('balance', 100);
$cuentaDestino->increment('balance', 100);
});
} catch (Exception $e) {
// Rollback automático
}
📜 Tabla de Decisión
| Escenario | Recomendación |
|---|---|
| Proyecto pequeño/simple | SQL puro |
| Proyecto mediano/grande | ORM |
| Consultas muy complejas | SQL puro + Query Builder |
| Seguridad prioritaria | ORM |
| Portabilidad entre DBs | ORM |
| Máximo rendimiento crítico | SQL puro optimizado |
| Equipo junior | ORM |
🎓 Conclusión del Maestro
“El SQL puro es la katana afilada del maestro: poderosa pero peligrosa. El ORM es la shuriken del ninja moderno: práctica y efectiva. Domina ambos.” — Maestro de las Bases de Datos
✅ Checklist de Dominio
- Crear modelo Eloquent con relaciones
- Implementar CRUD completo con ORM
- Resolver problema N+1 con eager loading
- Implementar transacciones
- Entender diferencia entre SQL y Query Builder
Recompensa XP: 75 XP ⚔️