Etiqueta: Bases de Datos

  • Cómo hacer un join en Doctrine

    Cómo hacer un join en Doctrine

    Parece algo bien simple, ¿no? Después de todo, hacerlo en SQL lo es. Pero si estás usando Doctrine, lo correcto es usar las capacidades del ORM.

    Recientemente tenía que resolver un problema de un cliente: se le habían duplicado registros en la base y era necesario eliminarlos.

    El punto es que no se tenían que eliminar todos los registros, si no sólo los problemáticos.

    El modelo de datos es algo así:

    Lo que yo necesitaba hacer era eliminar aquellas transacciones que, entre otros criterios de filtro, pertenecieran a un proveedor en particular.

    La forma en que lo hice fue mediante una consulta que busque todos los objetos necesarios y luego los elimine:

    $qb = $this->em
                ->createQueryBuilder()
                ->select('t')
                ->from(Transaction::class, 't')
                ->where('t.date BETWEEN :f AND :t')
                ->setParameter('f', $fromDate)
                ->setParameter('t', $toDate)
                ->orderBy('t.date')
            ;

    Hasta aquí se arma la consulta genérica, sólo incluyendo los filtros obligatorios (Las fechas básicamente).

    Luego, si se desea usar el filtro por proveedor tenemos:

    $qb->innerJoin(Account::class, 'a')
       ->andWhere('a.provider = :p')
       ->setParameter('p', $provider);

    Todo normal, ¿cierto?

    Pues aquí arrancaron los problemas.

    Cuando fuí a verificar en la pantalla de la aplicación cuántos registros correspondían al rango de fecha y al proveedor encontré unos 3000, sin embargo, el script me estaba diciendo que iba a eliminar unas 100k transacciones.

    De modo que decidí agregarle al script una opción para ver el SQL que estaba a punto de ejecutar y esto es lo que encontré:

    SELECT t0_.id AS id_1, t0_.amount AS amount_5, t0_.account_id AS account_id_10 FROM transaction t0_ INNER JOIN account a1_ WHERE (t0_.date BETWEEN ? AND ?) AND a1_.provider_id = ? ORDER BY t0_.date ASC

    Nuevamente me quedé un rato mirando el SQL. Todo se veía bien.

    ¿Qué podía estar pasando?

    Bueno… la verdad es que no estaba tan bien el SQL. Si le prestás un poco de atención notarás que al INNER JOIN le falta un detalle: la cláusula ON.

    El SQL que debería haberse generado debía ser más parecido a:

    SELECT t0_.id AS id_1, t0_.amount AS amount_5, t0_.account_id AS account_id_10 FROM transaction t0_ INNER JOIN account a1_ ON a1_.id = t0.account_id WHERE (t0_.date BETWEEN ? AND ?) AND a1_.provider_id = ? ORDER BY t0_.date ASC

    Sí. Ese simple detalle estaba haciendo una diferencia fundamental.

    Sin la cláusula ON el INNER JOIN se convierte en el producto cartesiano de las dos tablas, con lo cual se pierde totalmente el sentido de usar un JOIN y el resultado tiene muy poco sentido

    Perfecto, el problema está identificado. ¿Cómo lo solucionamos?

    Como de costumbre, se trata de volver a las fuentes. La documentación de Doctrine es bastante clara al respecto.

    Se puede hacer algo como:

    $qb->innerJoin(Account::class, 'a', Join::ON, 't.account_id = a.id')
       ->andWhere('a.provider = :p')
       ->setParameter('p', $provider);

    Y el resultado será correcto, pero si lo dejamos así estamos haciendo trabajo extra… y ya que tenemos un ORM, ¿por qué hacerlo?

    Siendo t el alias de la clase Transaction y estando la relación definida como parte del mapeo objeto-relacional una mejor versión es esta:

    $qb->innerJoin('t.a', 'a')
       ->andWhere('a.provider = :p')
       ->setParameter('p', $provider);

    En conclusión SQL y DQL son parecidos pero no tanto. Vale la pena conocer las diferencias y usar lo mejor de cada uno en cada ocasión.

  • Cómo llevar los cambios de una base de datos de desarrollo a producción

    Cómo llevar los cambios de una base de datos de desarrollo a producción

    Cuando tenemos una aplicación en producción (Es decir, siendo utilizada por usuarios reales) es muy común que nos encontremos con necesidades que no han sido cubiertas por el desarrollo original.

    Esto puede deberse a diversos factores como la falta de análisis, una pobre comprensión de la problemática a encarar o simplemente al hecho de que la realidad va cambiando conforme pasa el tiempo.

    Independientemente de cuál de éstas haya sido, cuando se detecta una necesidad que el software no cubre se requiere realizarle modificaciones para adaptarlo al nuevo escenario.

    Algunas veces los cambios en el código vienen de la mano de cambios en la estructura de la base de datos utilizada para dar soporte a la aplicación.

    Y ahí comienzan los problemas 🙂

    En un ambiente de trabajo profesional es muy común contar con diferentes entornos de ejecución.

    Al menos deberíamos tener dos:

    • Desarrollo (Donde trabajamos y los datos no son precisamente valiosos)
    • Producción (Donde trabajan los usuarios y los datos son sagrados).

    Parte de nuestro trabajo será realizar las modificaciones al código de la aplicación, probarlo y eventualmente desplegarlo en el servidor de producción.

    Para llevar el control de los cambios realizados sobre el código existen muchos sistemas de control de versiones, como por ejemplo Git o Subversion.

    Utilizar uno de estos sistemas simplifica mucho conseguir la perfecta sincronización entre el código que ejecutamos en nuestro entorno de desarollo y el de producción, pero… ¿cómo hacemos para que la estructura de la base de datos también esté sincronizada?

    Actualizar la base de datos manualmente

    El enfoque más simple consiste en aplicar los cambios a la base en forma manual.

    Esto significa entrar al servidor de bases de datos (Usando phpMyAdmin por ejemplo) y ejecutar los comandos que dejen la base en el estado deseado.

    Para usar esta estrategia no se necesita mucho más que buena memoria o buenas notas.

    Claro que la desventaja de este mecanismo es que es sumamente riesgoso y propenso a errores.

    Utilizar scripts SQL

    Otra opción más interesante es generar los scripts SQL que se utilizan durante el desarrollo y almacenarlos como parte del código, de modo de aprovechar el versionado de la propia aplicación.

    Este enfoque es mejor que el anterior ya que disminuye el riesgo de ejecutar comandos diferentes en cada ambiente y, de ese modo, minimiza las chances de terminar con estructuras de bases de datos distintas.

    Uno de los problemas que aún no resuelve este enfoque es cómo determinar de un modo simple cuáles cambios ya han sido aplicados y cuáles no.

    Versionar la base de datos

    Un enfoque alternativo es el uso de alguna herramienta para el versionado de la base de datos.

    En este esquema los cambios en el ambiente de desarrollo no se realizan escribiendo SQL, si no mediante código.

    Liquibase

    Liquibase es una herramienta basada en conjuntos de cambios (changeSets) descriptos usando diferentes lenguajes posibles (SQL, XML, Json o YAML).

    Una vez definidos los cambios que deben realizarse se utiliza el ejecutor de los cambios que es el encargado de verificar el estado actual, traducir los nuevos cambios a SQL e impactarlos.

    Migraciones de Doctrine

    Otra herramienta similar (y mi favorita :)) es el mecanismo de migraciones de Doctrine.

    Las migraciones son una parte de este gran ORM que describen los cambios que deben realizarse a la base de datos.

    Una gran ventaja de este mecanismo sobre Liquibase o similares es que todo el código se escribe utilizando puro PHP y al momento de ejecutar la migración queda a cargo de Doctrine realizar la traducción a SQL.

    Notas finales

    Como siempre, cuanto más automatizada sea nuestra operación más confiable será ya que podremos realizar tantos ensayos como necesitemos hasta contar con un mecanismo correcto.

    La contracara de utilizar una herramienta como esta es que es un camino de una sola vía: si no mantenemos la disciplina de escribir todos nuestros cambios del mismo modo nos vamos a encontrar con más problemas de soluciones… aunque, en la realidad, una vez que domines una de estas técnicas dificulto que quieras volver atrás 🙂

  • ¿SQL vs. NoSQL?

    ¿SQL vs. NoSQL?

    Se escucha mucho últimamente que SQL pasó de moda, que hoy lo cool es usar NoSQL (MongoDB, CouchDB, etc…) pero… ¿es siempre así?

    Empecemos por entender qué es una base de datos NoSQL (formalmente no estoy muy convencido de que un almacenamiento que no respete las reglas ACID pueda llamarse base de datos, pero bueno… como para no entrar en más de una discusión a la vez, digamos que sí).

    De lo que estamos hablando es de un medio de almacenamiento no estructurado, comúnmente conocido como base de datos documental.

    Más allá de las formalidades (los registros de la base de datos relacional se llaman documentos en una base NoSQL, las tablas pasan a ser colecciones, etc..), existen diferencias muy concretas:

    1. Los documentos no tienen estructura (Pueden guardar literalmente cualquier cosa)
    2. No existen operaciones tipo JOIN

    Su razón de ser se encuentra en la premisa de que, en un ambiente como es la web, la cantidad de escrituras vs. lecturas es prácticamente despreciable, con lo cual, si se busca darle a un sitio la mejor performance posible, no tiene sentido optimizar el almacenamiento en función de la operación menos frecuente (como sería el caso de usar una base de datos relacional).

    Si bien esto es cierto, es muchas veces un arma de doble filo. Muchos desarrolladores poco experimentados (o seducidos por el último shinny object) olvidan hacerse la pregunta clave: ¿la premisa es válida en mi contexto?.

    En aplicaciones web que reciben una cantidad enorme de visitas de usuarios no registrados (Un diario, Wikipedia, etc…) es claro que la respuesta es un sí rotundo. Sin embargo, cuando se trata de aplicaciones transaccionales (Un home banking, un sistema de adminstración, etc…), lo contrario es lo que sucede.

    En estos casos la consistencia eventual que prometen las bases NoSQL no es un aliado si no más bien una carga pesada (Sí, me tocó trabajar con más de un sistema transaccional soportado por una NoSQL… no se lo deseo a nadie).

    Y si bien las operaciones JOIN son costosas, el no usar una base relacional no las vuelve innecesarias, es el objetivo de la aplicación el que determina si vamos a requerir cruzar tablas o no.

    Otra ventaja con la que se vende NoSQL es que, al no tener estructura, se puede desarrollar más rápido (No hay que perder tiempo en complejos diseños de estructuras de datos, migraciones y demás)… Mi opinión es que esto constituye un boomerang (Pero bueno, puede que ya esté poniéndome viejo…), ya que, por más que la base de datos no tenga siempre los mismos campos, la aplicación seguramente se beneficie de tener una estructura conocida (No habrá que andar llenando de if para ver qué tiene el objeto que estás queriendo manipular).

    En mi experiencia, lo mejor es, en los escenarios que lo ameriten, usar un mix de ambas, para tener lo mejor de los dos mundos: una base de datos relacional para el almacenamiento duro y alguna caché tipo clave-valor (Memcached por ejemplo) para acceso rápido a los objetos más comúnmente consultados.

    Los frameworks modernos ya vienen preparados para manejar la complejidad de invalidar los cachés en los momentos de escritura y aprovecharlos en los momentos de lectura, con lo cual… ¿para qué pelear? Todo en su justa medida y armoniosamente ayuda.

  • Cómo evitar la inyección SQL  en PHP

    Cómo evitar la inyección SQL en PHP

    Uno de los fantasmas más temidos por quienes contratan servicios de desarrollo (especialmente cuando se trata de su primera experiencia) es el de los ataques de hackers.

    Si bien es imposible asegurar al 100% un sistema (de software o de cualquier otro tipo), existe una serie de buenas prácticas que disminuyen sensiblemente la probabilidad de ocurrencia de tales ataques (o al menos, su probabilidad de éxito).

    Por lo general, los ataques se basan en la explotación de código vulnerable como ser algún caso raro que el desarrollador no tuvo en cuenta.

    Uno de los ataques más usuales es el conocido como sql injection.

    De lo que se trata es de ejecutar código sql sin autorización.

    Los scripts de PHP que no están bien escritos pueden ser atacados de esta forma.

    Veamos un ejemplo:

    <?php
    
    $sql = "SELECT * FROM users WHERE nombre = '".$_POST['nombre']."'";
    
    $db->query($sql);

    Si el $_POST se llena normalmente no habría problema (Si lo hace un usuario legítimo de nuestra aplicación), pero… ¿qué pasa si un usuario malicioso lo hace?

    Por ejemplo, qué tal si alguien pusiera algo como esto en el campo nombre:

    ';DROP TABLE users;--

    El sql total quedaría así:

    SELECT * FROM users WHERE nombre = '';DROP TABLE users;--'

    Por si lo querés ver más gráfico, te dejo este excelente comic.

    Bueno… formas de protegerse de esto hay muchas, una muy práctica es utilizar los prepared statements de PDO.

    El ejemplo quedaría de esta forma:

    <?php
    
    $sql = "SELECT * FROM users WHERE nombre = :nombre";
    $st = $db->prepare( $sql );
    $st->execute( [ ':nombre' => $_POST['nombre'] ] );

    De este modo, dejamos en manos de PDO la realización de las validaciones y el agregado de comillas donde corresponda, de modo que el sql a ejecutar quede de esta forma:

    SELECT * FROM users WHERE nombre = '\';DROP TABLE users;--\''

    Con lo cual se vuelve completamente inofensivo 🙂

  • Cómo conectarse a bases de datos distintas de MySQL desde PHP

    Cómo conectarse a bases de datos distintas de MySQL desde PHP

    Si bien es casi una redundancia hablar de PHP+MySQL (Algo así como GNU y Linux), la realidad es que esta santa asociación es casi casual.

    En PHP no existe un motor de base de datos preferido y otros de segunda. No voy a decir que PHP puede conectarse a cualquier motor de bases de datos (Habiendo pasado por la FCEyN aprendí bien a no usar los absolutos con ligereza :)), la realidad es que puede conectarse, de forma muy simple, a una amplia cantidad.


    A su vez, las opciones son varias, dependiendo principalmente del nivel técnico de quien deba implementar la solución. Paso a explicar:

    En el escalón más bajo están las funciones propias de php para realizar las conexiones:

    Y un largo etcétera (Si tenés que conectarte a una base de datos diferente de estas, podés encontrar información útil acá).

    Este es el modo de realizar la conexión si estás buscando programar en modo procedural o estructurado.

    Si estás un poco más ducho y buscás un modo de realizar tus conexiones usando objetos, podés usar PDO. Al usar esta interfase, las cosas se hacen mucho más simples (Particularmente cuando se trata de migrar a otro motor, ya que lo único que cambia es el string con el que establecés la conexión).

    Un ejemplo de conexión a un SqlServer que está en un servidor identificado como DB_SERVER:

    <?php
    
    try {
      $conexion = new PDO("sqlsrv:Server=DB_SERVER;Database=midb", "Usuario", "Contraseña");
    } catch ( PDOException $e ) {
      echo $e->getMessage();
    }
    • Y una vez que tenés las conexión lista, las operaciones se realizan del mismo modo si se trata de un MySQL, SqlServer, Oracle, etc…
    •  
    • Por último, si estás realmente decidido a llevar el tema a su máximo nivel, te recomiendo usar algún ORM (Si me preguntás a mí, con Doctrine deberías andar muy bien).

    ¿Te quedó alguna duda? ¡Posteala en los comentarios!