2 Consultas

Ahora que ya sabemos cómo establecer una conexión con el servidor MySQL y cómo cerrar esa conexión, veamos cómo podemos hacer consultas SQL desde nuestros programas C/C++.

Seleccionar una base de datos

Para acceder a la información almacenada en una base de datos, debemos hacer referencia a la base de datos mediante su nombre. En el cliente MySQL usamos la orden USE para seleccionar una base de datos por defecto.

Usando el API hemos visto que podemos seleccionar la base de datos en la llamada a la función mysql_real_connect(). Pero si queremos cambiar la base de datos por defecto durante la ejecución no será necesario cerrar la conexión y abrir una nueva, podemos usar en su lugar la función mysql_select_db():

   if(mysql_select_db(myData, "prueba")) {
      // Error al seleccionar base de datos.
      cout << "ERROR: " << mysql_error(myData) << endl;
      mysql_close(myData);
      rewind(stdin);
      getchar();
      return 2;
   }

La función mysql_select_db() hace que la base de datos especificada como segundo parámetro sea considerada la base de datos por defecto en las siguientes consultas. En rigor esto no es imprescindible, ya que podemos acceder a una tabla mediante el especificador de base de datos y el nombre de la tabla, pero si todas las consultas se van a hacer sobre la misma base de datos, esto nos ahorrará mucho trabajo (de escritura).

Si el valor de retorno de la función es distinto de cero, indica que se ha producido un error. En este ejemplo hemos usardo la función mysql_error para mostrar un mensaje de error que indique el motivo.

Esta función puede fallar por varios motivos, pero de momento sólo nos preocupa si el error es provocado porque la base de datos no existe, en ese caso obtendremos el error ER_BAD_DB_ERROR.

Seleccionar datos de una tabla

La primera operación que intentaremos sobre la base de datos es leer algunos registros de una tabla. Empezaremos por una consulta sencilla, leyendo todos los registros de la tabla 'gente'.

Nota:

Supondremos que tal tabla ya existe y que contiene datos, pero si no es así en tu caso, puedes crearla y añadir datos desde la consola siguiendo los pasos del curso de MySQL: crear base de datos, crear una tabla, insertar filas.

La consulta que vamos a hacer es:

SELECT * FROM gente;

Necesitamos usar una sentencia SQL, en este caso una sentencia SELECT.

Para incluir consultas dentro de programas C/C++ se usan dos funciones del API C de MySQL, mysql_query o mysql_real_query. En ambos casos, primer parámetro es, como siempre, un manipulador de un objeto MYSQL iniciado; el segundo contiene la cadena con la consulta o instrucción SQL a ejecutar.

La función mysql_real_query usa un tercer parámetro, que el la longitud de la cadena que contiene la consulta. Esta función permite usar consultas binarias, es decir, que la cadena de la consulta puede contener caracteres nulos.

mysql_query(myData, "SELECT * FROM gente");

Veamos un ejemplo de cómo lanzar esta consulta:

   // Hacer una consulta con el comando "SELECT * FROM gente":
   if(mysql_query(myData, "SELECT * FROM gente")) {
      // Error al realizar la consulta:
      cout << "ERROR: " << mysql_error(myData) << endl;
      mysql_close(myData);
      rewind(stdin);
      getchar();
      return 2;
   }

Recuperar datos de una base de datos

Ya hemos hecho una consulta mediante SELECT, ahora mostraresmos el proceso para recuperar los resultados de esa consulta. Por cierto, otras consultas también generan conjuntos de resultados, como SHOW_TABLES o SHOW DATABASES, y el tratamiento de esos resultados es el mismo.

Lo primero es recuperar el conjunto de resultados procedente del servidor MySQL, para ello se usa la función mysql_store_result.

La función mysql_store_result requiere como parámetro un objeto MYSQL, con la conexión actual y devuelve un puntero a una estructura MYSQL_RES.

Del mismo modo, una vez procesados los resultados, se deben liberar mediante una llamada a la función mysql_free_result. Esto liberará la memoria usada por el conjunto de resultados, es decir, por la estructura MYSQL_RES.

// Almacenar el resultado de la consulta, lo necesitaremos después:
if((res = mysql_store_result(myData))) {
   // Procesar resultados
   ...
   // Liberar el resultado de la consulta:
   mysql_free_result(res);
}

Una vez obtenido el conjunto de resultados, disponemos de varias funciones que podemos aplicar sobre él.

Procesar un conjunto de resultados

Hay un conjunto de funciones que se aplican a un conjunto de resultados para obtener información sobre él.

Número de elementos de un conjunto de resultados

Una de las primeras cosas que puede interesarnos es saber cuantos elementos contiene el conjunto de resultados. Para obtener ese valor se puede usar la función mysql_num_rows.

Número de columnas por fila de un conjunto de resultados

Otro parámetro interesante es el número de columnas por fila, para obtenerlo se puede usar la función mysql_num_fields.

Ambas funciones requieren como parámetro un puntero a una estructura MYSQL_RES, y ambas retornan un entero.

   // Obtener el número de registros seleccionados:
   i = (int) mysql_num_rows(res);
   j = (int) mysql_num_fields(res);
   // Mostrar el número de registros seleccionados:
   cout << "Consulta:  SELECT * FROM gente" << endl;
   cout << "Numero de filas encontradas:  " << i << endl;
   cout << "Numero de columnas por fila:  " << j << endl;

Información sobre columnas

También podemos obtener información sobre cada una de las columnas del conjunto de resultados. Para ello disponemos de las funciones mysql_fetch_field, mysql_fetch_fields y mysql_fetch_field_direct.

La primera obtiene información sobre una de las columnas, en una estructura MYSQL_FIELD. La primera vez que se use devuelve la información sobre la primera columna, la segunda vez sobre la siguiente, etc.

   for(l = 0; l < j; l++) {
      columna = mysql_fetch_field(res);
      cout << "Nombre: " << columna->name << endl;
      cout << "Longitud: " << columna->length << endl;
      cout << "Valor por defecto: " << (columna->def ? columna->def : "NULL") << endl;
   }

La función mysql_fetch_fields devuelve la información sobre todas las columnas a la vez, en un array de estructuras MYSQL_FIELD.

   columna = mysql_fetch_fields(res);
   for(l = 0; l < j; l++) {
      cout << "Nombre: " << columna[l].name << endl;
      cout << "Longitud: " << columna[l].length << endl;
      cout << "Valor por defecto: " << (columna[l].def ? columna[l].def : "NULL") << endl;
   }

La tercera función, mysql_fetch_field_direct, devuelve la información sobre la columna indicada por el número especificado como segundo parámetro:

   // Información sobre columnas n:
   cout << endl << "Informacion sobre columna 1:" << endl;
   columna = mysql_fetch_field_direct(res, 1);
   cout << "Nombre: " << columna->name << endl;
   cout << "Longitud: " << columna->length << endl;
   cout << "Valor por defecto: " << (columna->def ? columna->def : "NULL") << endl;
   cout << endl;

Contenido de las filas de un conjunto de resultados

Por supuesto, también nos interesa obtener cada una de las filas contenidas en el conjunto de resultados. Para esa tarea se usa se la función mysql_fetch_row. Esta función también requiere como parámetro una estructura MYSQL_RES, y da como salida una estructura MYSQL_ROW.

   for(l = 0; l < i; l++) {
      row = mysql_fetch_row(res);
      cout << "Registro no. " << l+1 << endl;
      // Mostrar cada campo:
      for(k = 0 ; k < j ; k++)
         cout << ((row[k]==NULL) ? "NULL" : row[k]) << endl;
   }

También podemos usar como condición el valor de retorno de la función mysql_fetch_row, ya que tal valor es NULL si no quedan filas por recuperar.

El tipo MYSQL_ROW no es más que un array de cadenas, (un char**), que contiene los valores de todas las columnas en la forma de un array de cadenas. Si alguno de los valores de un atributo es NULL, el puntero correspondiente a esa columna será NULL.

Obtener longitudes de columnas

Mediante la función mysql_fetch_lengths podemos obtener las longitudes de todas las columnas de la fila actual de un conjunto de resultados.

Estos valores se pueden usar para copiar esos valores sin necesidad de calcular sus longitudes, o cuando algunas de las columnas contengan valores binarios, en cuyo caso no podremos usar strlen para calcular esas longitudes.

   // Leer registro a registro y mostrar:
   l=1;
   for(l = 0; l < i; l++) {
      row = mysql_fetch_row(res);
      lon = mysql_fetch_lengths(res);
      cout << "Registro no. " << l+1 << endl;
      // Mostrar cada campo y su longitud:
      for(k = 0 ; k < j ; k++) {
         cout << ((row[k]==NULL) ? "NULL" : row[k]);
         cout << " longitud: " << lon[k] << endl;
      }
   }

Acceder a filas del conjunto de forma aleatoria

Mediante mysql_fetch_row accedemos a las filas del conjunto de resultados de forma secuencial. Sin embargo, todas las filas del conjunto de resultados están en memoria, de modo que podemos acceder a ellas en cualquier orden, o acceder sólo a algunas de ellas.

Para acceder a una fila arbitraria se usa la función mysql_data_seek. Esta función precisa dos parámetros. El primero es el conjunto de resultados y el segundo un desplazamiento entre 0 y mysql_num_rows(result)-1.

   // 3ª fila:
   mysql_data_seek(res, 2);
   row = mysql_fetch_row(res);
   cout << "Tercera fila" << endl;
   for(k = 0 ; k < j ; k++) {
      cout << ((row[k]==NULL) ? "NULL" : row[k]) << endl;
   }

También podemos usar la función mysql_row_tell para obtener el valor de desplazamiento de la fila actual.

Ese valor se puede usar como argumento en llamadas a la función mysql_row_seek, pero el valor usado por estas dos funciones no es un número de fila, por lo tanto, no se puede usar en la función mysql_data_seek.

   MYSQL_ROW_OFFSET pos;
   pos = mysql_row_tell(res);
   // lecturas de filas
   mysql_row_seek(res, pos);
   row = mysql_fetch_row(res);
   cout << "Fila guardada" << endl;
   for(k = 0 ; k < j ; k++) {
      cout << ((row[k]==NULL) ? "NULL" : row[k]) << endl;
   }

Trabajar con conjuntos de resultados muy grandes

Si el conjunto de resultados contiene muchas filas puede ser poco práctico usar la función mysql_store_result, ya que se requerirá mucha memoria para almacenar el conjunto completo, posiblemente más de la disponible.

Cuando eso suceda, o cuando preveamos que esa situación puede suceder, podemos usar la función mysql_use_result en su lugar.

Esta función no carga el conjunto de resultados en memoria, y se usa la función mysql_fetch_row para recuperar las filas una por una.

MySQL recomienda no realizar procesos muy largos con cada fila, ya que la tabla está bloqueada para otros usuarios hasta que se llame a mysql_free_result.

   // El mismo proceso usando mysql_use_result:
   // Hacer una consulta con el comando "SELECT * FROM gente":
   if(mysql_query(myData, "SELECT * FROM gente")) {
      // Error al realizar la consulta:
      cout << "ERROR: " << mysql_error(myData) << endl;
      mysql_close(myData);
      return 2;
   }
   if((res = mysql_use_result(myData))) {
      j = (int) mysql_num_fields(res);
      while(row = mysql_fetch_row(res)) {
         for(k = 0 ; k < j ; k++)
            cout << ((row[k]==NULL) ? "NULL" : row[k]) << endl;
      }

      // Liberar el resultado de la consulta:
      mysql_free_result(res);
   }

En este ejemplo hacemos uso del hecho de que el valor de retorno de mysql_fetch_row es NULL si no quedan filas por leer. No tenemos otra opción, ya que la función mysql_num_rows no funcionará correctamente hasta que se terminen de procesar todas las filas.

Las funciones mysql_data_seek, mysql_row_seek y mysql_row_tell no se pueden usar cuando se recupera un conjunto de resultados con la función mysql_use_result.

Ejemplo

Veamos un ejemplo completo en el que se aplican estas funciones.

/*
  Name: MySQL Ejemplo 1
  Author: Salvador Pozo Coronado, salvador@conclase.net
  Date: 07/05/2005
  Description: Ejemplo para mostrar contenidos de bases de datos
  usando MySQL.
  Nota: incluir la opción "-lmysql" en las opciones del linker
  dentro de las opciones de proyecto.
*/

// Includes...
#include <iostream>
#include <windows.h>
#include <mysql/mysql.h>
#include <mysql/mysqld_error.h>
#include <cstring>
#include <cstdio>

using namespace std;

// Programa principal
int main()
{
   // Variables
   MYSQL           *myData;
   MYSQL_RES       *res;
   MYSQL_ROW        row;
   MYSQL_FIELD     *columna;
   int              i, j, k, l;
   unsigned long   *lon;
   MYSQL_ROW_OFFSET pos;

   // Intentar iniciar MySQL:
   if(!(myData = mysql_init(0))) {
      // Imposible crear el objeto myData
      cout << "ERROR: imposible crear el objeto myData." << endl;
      rewind(stdin);
      getchar();
      return 1;
   }

   // Debe existir un usuario "curso" con clave de acceso "clave" y
   // con al menos el privilegio "SELECT" sobre la tabla
   // "prueba.gente"
   if(!mysql_real_connect(myData, NULL, "curso", "clave", "prueba", MYSQL_PORT, NULL, 0)) {
      // No se puede conectar con el servidor en el puerto especificado.
      cout << "Imposible conectar con servidor mysql en el puerto "
           << MYSQL_PORT << " Error: " << mysql_error(myData) << endl;
      mysql_close(myData);
      rewind(stdin);
      getchar();
      return 1;
   }

   // Conectar a base de datos.
   if(mysql_select_db(myData, "prueba")) {
      // Imposible seleccionar la base de datos, posiblemente no existe.
      cout << "ERROR: " << mysql_error(myData) << endl;
      mysql_close(myData);
      rewind(stdin);
      getchar();
      return 2;
   }

   // Hacer una consulta con el comando "SELECT * FROM gente":
   if(mysql_query(myData, "SELECT * FROM gente")) {
      // Error al realizar la consulta:
      cout << "ERROR: " << mysql_error(myData) << endl;
      mysql_close(myData);
      rewind(stdin);
      getchar();
      return 2;
   }

   // Almacenar el resultado de la consulta:
   if((res = mysql_store_result(myData))) {
      // Obtener el número de registros seleccionados:
      i = (int) mysql_num_rows(res);
      // Obtener el número de columnsa por fila:
      j = (int) mysql_num_fields(res);

      // Mostrar el número de registros seleccionados:
      cout << "Consulta:  SELECT * FROM gente" << endl;
      cout << "Numero de filas encontradas:  " << i << endl;
      cout << "Numero de columnas por fila:  " << j << endl;

      // Información sobre columnas usando mysql_fetch_field:
      cout << endl << "Informacion sobre columnas:" << endl;
      for(l = 0; l < j; l++) {
         columna = mysql_fetch_field(res);
         cout << "Nombre: " << columna->name << endl;
         cout << "Longitud: " << columna->length << endl;
         cout << "Valor por defecto: " << (columna->def ? columna->def : "NULL") << endl;
      }
      cout << endl;

      // Información sobre columnas usando mysql_fetch_fields:
      cout << endl << "Informacion sobre columnas:" << endl;
      columna = mysql_fetch_fields(res);
      for(l = 0; l < j; l++) {
         cout << "Nombre: " << columna[l].name << endl;
         cout << "Longitud: " << columna[l].length << endl;
         cout << "Valor por defecto: " << (columna[l].def ? columna[l].def : "NULL") << endl;
      }
      cout << endl;

      // Información sobre columnas n, usando mysql_fetch_field_direct:
      cout << endl << "Informacion sobre columna 1:" << endl;
      columna = mysql_fetch_field_direct(res, 1);
      cout << "Nombre: " << columna->name << endl;
      cout << "Longitud: " << columna->length << endl;
      cout << "Valor por defecto: " << (columna->def ? columna->def : "NULL") << endl;
      cout << endl;

      // Leer registro a registro y mostrar:
      l=1;
      for(l = 0; l < i; l++) {
         row = mysql_fetch_row(res);
         lon = mysql_fetch_lengths(res);
         cout << "Registro no. " << l+1 << endl;
         // Mostrar cada campo y su longitud:
         for(k = 0 ; k < j ; k++) {
            cout << ((row[k]==NULL) ? "NULL" : row[k]);
            cout << " longitud: " << lon[k] << endl;
         }
      }

      cout << endl;

      // Mostrar sólo 3ª fila:
      mysql_data_seek(res, 2);
      row = mysql_fetch_row(res);
      cout << "Tercera fila" << endl;
      for(k = 0 ; k < j ; k++) {
         cout << ((row[k]==NULL) ? "NULL" : row[k]) << endl;
      }

      // Almacenar posición de la fila actual (la 4ª):
      pos = mysql_row_tell(res);
      // Lectura de filas hasta el final:
      while(mysql_fetch_row(res));
      // Recuperar la posición guardada:
      mysql_row_seek(res, pos);
      row = mysql_fetch_row(res);
      cout << "Cuarta fila" << endl;
      for(k = 0 ; k < j ; k++) {
         cout << ((row[k]==NULL) ? "NULL" : row[k]) << endl;
      }

      cout << endl;

      // Liberar el resultado de la consulta:
      mysql_free_result(res);
   }

   // El mismo proceso usando mysql_use_result:
   // Hacer una consulta con el comando "SELECT * FROM gente":
   if(mysql_query(myData, "SELECT * FROM gente")) {
      // Error al realizar la consulta:
      cout << "ERROR: " << mysql_error(myData) << endl;
      mysql_close(myData);
      rewind(stdin);
      getchar();
      return 2;
   }

   // Mostrar todas las filas:
   if((res = mysql_use_result(myData))) {
      j = (int) mysql_num_fields(res);
      while(row = mysql_fetch_row(res)) {
         for(k = 0 ; k < j ; k++)
            cout << ((row[k]==NULL) ? "NULL" : row[k]) << endl;
      }

      // Liberar el resultado de la consulta:
      mysql_free_result(res);
   }
   // Cerrar la conexión
   mysql_close(myData);

   // Esperar a que se pulse una tecla y salir.
   rewind(stdin);
   getchar();
   return 0;
}