SQL directo

Puede trabajar con Nette Database de dos formas: escribiendo consultas SQL directamente (Acceso Directo) o dejando que SQL se genere automáticamente(Acceso Explorador). El Acceso Directo le permite crear consultas de forma segura manteniendo un control total sobre su estructura.

Para obtener información sobre cómo crear una conexión y configurarla, consulte la página correspondiente.

Consultas básicas

El método query() ejecuta consultas a la base de datos y devuelve un objeto ResultSet que representa el resultado. Si la consulta falla, el método lanza una excepción. Puede recorrer el resultado de la consulta mediante un bucle foreach o utilizar una de las funciones de ayuda.

$result = $database->query('SELECT * FROM users');

foreach ($result as $row) {
	echo $row->id;
	echo $row->name;
}

Para insertar valores de forma segura en las consultas SQL, utilice consultas parametrizadas. Nette Database lo hace muy sencillo: basta con añadir una coma y el valor a la consulta SQL.

$database->query('SELECT * FROM users WHERE name = ?', $name);

Para varios parámetros, puede intercalar la consulta SQL con los parámetros:

$database->query('SELECT * FROM users WHERE name = ?', $name, 'AND age > ?', $age);

O escribir primero toda la consulta SQL y luego intercalar todos los parámetros:

$database->query('SELECT * FROM users WHERE name = ? AND age > ?', $name, $age);

Protección contra la inyección SQL

¿Por qué es importante utilizar consultas parametrizadas? Porque le protegen de los ataques de inyección SQL, en los que los atacantes pueden inyectar comandos SQL maliciosos para manipular o acceder a los datos de la base de datos.

**Utiliza siempre consultas parametrizadas para protegerte de la inyección SQL.

// ❌ CÓDIGO INSEGURO - vulnerable a la inyección SQL.
$database->query("SELECT * FROM users WHERE name = '$name'");

// ✅ Consulta parametrizada segura
$database->query('SELECT * FROM users WHERE name = ?', $name);

Familiarícese con los posibles riesgos de seguridad.

Técnicas de consulta

Condiciones WHERE

Puede escribir las condiciones de WHERE como una matriz asociativa, donde las claves son nombres de columnas y los valores son los datos a comparar. Nette Database selecciona automáticamente el operador SQL más apropiado en función del tipo de valor.

$database->query('SELECT * FROM users WHERE', [
	'name' => 'John',
	'active' => true,
]);
// WHERE `name` = 'John' AND `active` = 1

También puede especificar explícitamente el operador en la clave:

$database->query('SELECT * FROM users WHERE', [
	'age >' => 25,           // utiliza el operador >.
	'name LIKE' => '%John%', // utiliza el operador LIKE
	'email NOT LIKE' => '%example.com%', // utiliza el operador NOT LIKE
]);
// WHERE `edad` > 25 AND `nombre` LIKE '%John%' AND `email` NOT LIKE '%ejemplo.com%'

Los casos especiales, como los valores de null o las matrices, se gestionan automáticamente:

$database->query('SELECT * FROM products WHERE', [
	'name' => 'Laptop',         // utiliza el operador =
	'category_id' => [1, 2, 3], // utiliza IN
	'description' => null,      // usos IS NULL
]);
// WHERE `name` = 'Laptop' AND `category_id` IN (1, 2, 3) AND `description` IS NULL

Para condiciones negativas, utilice el operador NOT:

$database->query('SELECT * FROM products WHERE', [
	'name NOT' => 'Laptop',         // utiliza el operador <>
	'category_id NOT' => [1, 2, 3], // utiliza NOT IN
	'description NOT' => null,      // utiliza IS NOT NULL
	'id' => [],                     // omitido
]);
// WHERE `name` <> 'Laptop' AND `category_id` NOT IN (1, 2, 3) AND `description` IS NOT NULL

Por defecto, las condiciones se combinan utilizando el operador AND. Puede cambiar este comportamiento utilizando el marcador de posición ?or.

Reglas ORDER BY

La cláusula ORDER BY puede definirse como una matriz, donde las claves representan columnas y los valores son booleanos que indican un orden ascendente:

$database->query('SELECT id FROM author ORDER BY', [
	'id' => true,  // ascendente
	'name' => false, // descendente
]);
// SELECT id FROM autor ORDER BY `id`, `nombre` DESC

Inserción de datos (INSERT)

Para insertar registros, utilice la sentencia SQL INSERT.

$values = [
	'name' => 'John Doe',
	'email' => 'john@example.com',
];
$database->query('INSERT INTO users ?', $values);
$userId = $database->getInsertId();

El método getInsertId() devuelve el ID de la última fila insertada. Para determinadas bases de datos (por ejemplo, PostgreSQL), debe especificar el nombre de la secuencia utilizando $database->getInsertId($sequenceId).

También puede pasar valores especiales, como archivos, objetos DateTime o tipos enum, como parámetros.

Inserción de varios registros a la vez:

$database->query('INSERT INTO users ?', [
	['name' => 'User 1', 'email' => 'user1@mail.com'],
	['name' => 'User 2', 'email' => 'user2@mail.com'],
]);

Realizar un INSERT por lotes es mucho más rápido porque sólo se ejecuta una consulta a la base de datos en lugar de varias consultas individuales.

Nota de seguridad: Nunca utilice datos no validados como $values. Familiarícese con los posibles riesgos.

Actualización de datos (UPDATE)

Para actualizar registros, utilice la sentencia SQL UPDATE.

// Actualizar un único registro
$values = [
	'name' => 'John Smith',
];
$result = $database->query('UPDATE users SET ? WHERE id = ?', $values, 1);

Puede comprobar el número de filas afectadas utilizando $result->getRowCount().

Puede utilizar los operadores += y -= en UPDATE:

$database->query('UPDATE users SET ? WHERE id = ?', [
	'login_count+=' => 1, // increment login_count
], 1);

Para insertar o actualizar un registro si ya existe, utilice la técnica ON DUPLICATE KEY UPDATE:

$values = [
	'name' => $name,
	'year' => $year,
];
$database->query('INSERT INTO users ? ON DUPLICATE KEY UPDATE ?',
	$values + ['id' => $id],
	$values,
);
// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978)
//  ON DUPLICATE KEY UPDATE `nombre` = 'Jim', `año` = 1978

Observe que Nette Database reconoce el contexto del comando SQL en el que se utiliza un parámetro con una matriz y genera el código SQL en consecuencia. Por ejemplo, construyó (id, name, year) VALUES (123, 'Jim', 1978) a partir del primer array, mientras que convirtió el segundo en name = 'Jim', year = 1978. Esto se explica con más detalle en la sección Sugerencias para la construcción de SQL.

Borrado de datos (DELETE)

Para borrar registros, utilice la sentencia SQL DELETE. Ejemplo con el número de filas borradas:

$count = $database->query('DELETE FROM users WHERE id = ?', 1)
	->getRowCount();

Sugerencias de construcción SQL

Los marcadores de posición SQL permiten controlar cómo se incorporan los valores de los parámetros en las expresiones SQL:

Sugerencia Descripción Usado Automáticamente Para
?name Se utiliza para nombres de tabla o columna
?values Genera (key, ...) VALUES (value, ...) INSERT ... ?, REPLACE ... ?
?set Genera asignaciones key = value, ... SET ?, KEY UPDATE ?
?and Une condiciones en un array con AND WHERE ?, HAVING ?
?or Une condiciones en una matriz con OR
?order Genera la cláusula ORDER BY ORDER BY ?, GROUP BY ?

Para insertar dinámicamente nombres de tabla o columna, utilice el marcador de posición ?name. Nette Database garantiza el correcto escape de acuerdo con las convenciones de la base de datos (p. ej., encierre entre corchetes para MySQL).

$table = 'users';
$column = 'name';
$database->query('SELECT ?name FROM ?name WHERE id = 1', $column, $table);
// SELECT `name` FROM `users` WHERE id = 1 (en MySQL)

Advertencia: Utilice el marcador de posición ?name únicamente para nombres de tabla y columna validados. De lo contrario, se arriesga a sufrir vulnerabilidades de seguridad.

Normalmente no es necesario especificar otras sugerencias, ya que Nette utiliza la autodetección inteligente al construir consultas SQL (véase la tercera columna de la tabla). Sin embargo, puede utilizarlas en situaciones en las que desee combinar condiciones utilizando OR en lugar de AND:

$database->query('SELECT * FROM users WHERE ?or', [
	'name' => 'John',
	'email' => 'john@example.com',
]);
// SELECT * FROM usuarios WHERE `nombre` = 'Juan' OR `email` = 'john@example.com'

Valores especiales

Además de los tipos escalares estándar (por ejemplo, string, int, bool), también puede pasar valores especiales como parámetros:

  • Ficheros: Utilice fopen('file.png', 'r') para insertar el contenido binario de un archivo.
  • Fecha y hora: los objetos DateTime se convierten automáticamente al formato de fecha de la base de datos.
  • Valores Enum: Las instancias de enum se convierten a sus valores correspondientes.
  • Literales SQL: Creados con Connection::literal('NOW()'), se insertan directamente en la consulta.
$database->query('INSERT INTO articles ?', [
	'title' => 'My Article',
	'published_at' => new DateTime,
	'content' => fopen('image.png', 'r'),
	'state' => Status::Draft,
]);

Para las bases de datos que carecen de soporte nativo para el tipo datetime (por ejemplo, SQLite y Oracle), los valores de DateTime se convierten según la opción de configuración formatDateTime (por defecto: U para Unix timestamp).

Literales SQL

En algunos casos, puede necesitar insertar código SQL sin procesar como un valor sin tratarlo como una cadena o escaparlo. Para ello, utilice objetos de la clase Nette\Database\SqlLiteral, que pueden crearse utilizando el método Connection::literal().

$result = $database->query('SELECT * FROM users WHERE', [
	'name' => $name,
	'year >' => $database::literal('YEAR()'),
]);
// SELECT * FROM usuarios WHERE (`nombre` = 'Jim') AND (`año` > AÑO())

Como alternativa:

$result = $database->query('SELECT * FROM users WHERE', [
	'name' => $name,
	$database::literal('year > YEAR()'),
]);
// SELECT * FROM usuarios WHERE (`nombre` = 'Jim') AND (año > YEAR())

Los literales SQL también pueden contener parámetros:

$result = $database->query('SELECT * FROM users WHERE', [
	'name' => $name,
	$database::literal('year > ? AND year &lt; ?', $min, $max),
]);
// SELECT * FROM usuarios WHERE `nombre` = 'Jim' AND (año > 1978 AND año < 2017)

Esto permite combinaciones flexibles:

$result = $database->query('SELECT * FROM users WHERE', [
	'name' => $name,
	$database::literal('?or', [
		'active' => true,
		'role' => $role,
	]),
]);
// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin')

Obtención de datos

Atajos para consultas SELECT

Para simplificar la recuperación de datos, la clase Connection proporciona varios métodos abreviados que combinan una llamada a query() con otra posterior a fetch*(). Estos métodos aceptan los mismos parámetros que query(), es decir, una consulta SQL y parámetros opcionales.
A continuación se ofrece una descripción detallada de los métodos de fetch*().

fetch($sql, ...$params): ?Row Ejecuta la consulta y obtiene la primera fila como un objeto Row.
fetchAll($sql, ...$params): array Ejecuta la consulta y obtiene todas las filas como una matriz de objetos Row.
fetchPairs($sql, ...$params): array Ejecuta la consulta y obtiene una matriz asociativa en la que la primera columna es la clave y la segunda el valor.
fetchField($sql, ...$params): mixed Ejecuta la consulta y obtiene el valor de la primera celda de la primera fila.
fetchList($sql, ...$params): ?array Ejecuta la consulta y obtiene la primera fila como una matriz indexada.

Ejemplo:

// fetchField() - devuelve el valor de la primera celda
$count = $database->query('SELECT COUNT(*) FROM articles')
	->fetchField();

foreach – Iteración sobre filas

Después de ejecutar una consulta, se devuelve un objeto ResultSet, que permite iterar sobre los resultados de varias maneras. La forma más sencilla y eficiente de recuperar filas es iterar en un bucle foreach. Este método procesa las filas de una en una y evita almacenar todos los datos en memoria a la vez.

$result = $database->query('SELECT * FROM users');

foreach ($result as $row) {
	echo $row->id;
	echo $row->name;
	//...
}

El bucle ResultSet sólo puede iterarse una vez. Si necesita iterar sobre él varias veces, primero debe cargar los datos en un array, por ejemplo, utilizando el método fetchAll().

fetch(): ?Row

Ejecuta la consulta y obtiene una única fila como objeto Row. Si no hay más filas disponibles, devuelve null. Este método avanza el puntero interno a la siguiente fila.

$result = $database->query('SELECT * FROM users');
$row = $result->fetch(); // recupera la primera fila
if ($row) {
	echo $row->name;
}

fetchAll(): array

Obtiene todas las filas restantes de ResultSet como una matriz de objetos Row.

$result = $database->query('SELECT * FROM users');
$rows = $result->fetchAll(); // recupera todas las filas
foreach ($rows as $row) {
	echo $row->name;
}

fetchPairs (string|int|null $key = null, string|int|null $value = null)array

Obtiene los resultados como una matriz asociativa. El primer argumento especifica la columna a utilizar como clave, y el segundo especifica la columna a utilizar como valor:

$result = $database->query('SELECT id, name FROM users');
$names = $result->fetchPairs('id', 'name');
// [1 => 'Juan Pérez', 2 => 'Juana Pérez', ...]

Si sólo se proporciona el primer parámetro, el valor será la fila completa (como objeto Row ):

$rows = $result->fetchPairs('id');
// [1 => Fila(id: 1, nombre: 'Juan'), 2 => Fila(id: 2, nombre: 'Juana'), ...]

Si se pasa null como clave, la matriz se indexará numéricamente empezando por cero:

$names = $result->fetchPairs(null, 'name');
// [0 => 'Juan Nadie', 1 => 'Juana Nadie', ...]

fetchPairs (Closure $callback)array

Como alternativa, puede proporcionar una llamada de retorno que determine los pares clave-valor o los valores de cada fila.

$result = $database->query('SELECT * FROM users');
$items = $result->fetchPairs(fn($row) => "$row->id - $row->name");
// ['1 - Juan', '2 - Juana', ...]

// La llamada de retorno también puede devolver un array con un par clave y valor:
$names = $result->fetchPairs(fn($row) => [$row->name, $row->age]);
// ['Juan' => 46, 'Juana' => 21, ...]

fetchField(): mixed

Obtiene el valor de la primera celda de la fila actual. Si no hay más filas disponibles, devuelve null. Este método avanza el puntero interno a la siguiente fila.

$result = $database->query('SELECT name FROM users');
$name = $result->fetchField(); // obtiene el nombre de la primera fila

fetchList(): ?array

Obtiene la fila como una matriz indexada. Si no hay más filas disponibles, devuelve null. Este método avanza el puntero interno a la siguiente fila.

$result = $database->query('SELECT name, email FROM users');
$row = $result->fetchList(); // ['John', 'john@example.com']

getRowCount(): ?int

Devuelve el número de filas afectadas por la última consulta UPDATE o DELETE. En el caso de las consultas a SELECT, devuelve el número de filas obtenidas, pero es posible que no siempre se conozca, en cuyo caso devuelve null.

getColumnCount(): ?int

Devuelve el número de columnas de ResultSet.

Información de consulta

Para obtener detalles sobre la última consulta ejecutada, utilice:

echo $database->getLastQueryString(); // genera la consulta SQL

$result = $database->query('SELECT * FROM articles');
echo $result->getQueryString();    // muestra la consulta SQL
echo $result->getTime();           // muestra el tiempo de ejecución en segundos

Para mostrar el resultado en forma de tabla HTML, utilice:

$result = $database->query('SELECT * FROM articles');
$result->dump();

También puede obtener información sobre los tipos de columnas en ResultSet:

$result = $database->query('SELECT * FROM articles');
$types = $result->getColumnTypes();

foreach ($types as $column => $type) {
	echo "$column is of type $type->type"; // por ejemplo, 'id es de tipo int'
}

Registro de consultas

Puede implementar un registro de consultas personalizado. El evento onQuery es un array de callbacks que se invocan después de cada ejecución de consulta:

$database->onQuery[] = function ($database, $result) use ($logger) {
	$logger->info('Query: ' . $result->getQueryString());
	$logger->info('Time: ' . $result->getTime());

	if ($result->getRowCount() > 1000) {
		$logger->warning('Large result set: ' . $result->getRowCount() . ' rows');
	}
};
versión: 4.0