🎯 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ónSQL PuroORM (Eloquent)
Crear registroINSERTModel::create()
Leer datosSELECTModel::find()
ActualizarUPDATE$model->save()
EliminarDELETE$model->delete()
RelacionesJOIN manualwith(), hasMany()
TransaccionesBEGIN/COMMITDB::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

EscenarioRecomendación
Proyecto pequeño/simpleSQL puro
Proyecto mediano/grandeORM
Consultas muy complejasSQL puro + Query Builder
Seguridad prioritariaORM
Portabilidad entre DBsORM
Máximo rendimiento críticoSQL puro optimizado
Equipo juniorORM

🎓 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 ⚔️