diff --git a/docs/guide-es/README.md b/docs/guide-es/README.md index a70ca71f49..bd8132d39b 100644 --- a/docs/guide-es/README.md +++ b/docs/guide-es/README.md @@ -76,7 +76,7 @@ Trabajar con bases de datos * [Objeto de acceso a datos](db-dao.md) - Conexión a una base de datos, consultas básicas, transacciones y manipulación de esquemas -* **TBD** [Constructor de consultas](db-query-builder.md) - Consulta de la base de datos utilizando una simple capa de +* [Constructor de consultas](db-query-builder.md) - Consulta de la base de datos utilizando una simple capa de abstracción * **TBD** [Active Record](db-active-record.md) - ORM Active Record, recuperación y manipulación de registros y definición de relaciones diff --git a/docs/guide-es/db-query-builder.md b/docs/guide-es/db-query-builder.md new file mode 100644 index 0000000000..5630f60429 --- /dev/null +++ b/docs/guide-es/db-query-builder.md @@ -0,0 +1,470 @@ +Constructor de Consultas +======================== + +> Nota: Esta sección está en desarrollo. + +Yii proporciona una capa de acceso básico a bases de datos como se describe en la sección +[Objetos de Acceso a Bases de Datos](db-dao.md). La capa de acceso a bases de datos proporciona un método de bajo +nivel (low-level) para interaccionar con la base de datos. Aunque a veces puede ser útil la escritura de sentencias +SQLs puras, en otras situaciones puede ser pesado y propenso a errores. Otra manera de tratar con bases de datos puede +ser el uso de Constructores de Consultas (Query Builder). El Constructor de Consultas proporciona un medio orientado a +objetos para generar las consultas que se ejecutarán. + +Un uso típico de Constructor de Consultas puede ser el siguiente: + +```php +$rows = (new \yii\db\Query()) + ->select('id, name') + ->from('user') + ->limit(10) + ->all(); + +// que es equivalente al siguiente código: + +$query = (new \yii\db\Query()) + ->select('id, name') + ->from('user') + ->limit(10); + +// Crear un comando. Se puede obtener la consulta SQL actual utilizando $command->sql +$command = $query->createCommand(); + +// Ejecutar el comando: +$rows = $command->queryAll(); +``` + +Métodos de Consulta +------------------- + +Como se puede observar, primero se debe tratar con [[yii\db\Query]]. En realidad, `Query` sólo se encarga de +representar diversa información de la consulta. La lógica para generar la consulta se efectúa mediante +[[yii\db\QueryBuilder]] cuando se llama al método `createCommand()`, y la ejecución de la consulta la efectúa +[[yii\db\Command]]. + +Se ha establecido, por convenio, que [[yii\db\Query]] proporcione un conjunto de métodos de consulta comunes que +construirán la consulta, la ejecutarán, y devolverán el resultado. Por ejemplo: + +- [[yii\db\Query::all()|all()]]: construye la consulta, la ejecuta y devuelve todos los resultados en formato de array. +- [[yii\db\Query::one()|one()]]: devuelve la primera fila del resultado. +- [[yii\db\Query::column()|column()]]: devuelve la primera columna del resultado. +- [[yii\db\Query::scalar()|scalar()]]: devuelve la primera columna en la primera fila del resultado. +- [[yii\db\Query::exists()|exists()]]: devuelve un valor indicando si la el resultado devuelve algo. +- [[yii\db\Query::count()|count()]]: devuelve el resultado de la consulta `COUNT`. Otros métodos similares incluidos + son `sum($q)`, `average($q)`, `max($q)`, `min($q)`, que soportan las llamadas funciones de agregación. El parámetro + `$q` es obligatorio en estos métodos y puede ser el nombre de la columna o expresión. + +Construcción de Consultas +------------------------- + +A continuación se explicará como construir una sentencia SQL que incluya varias clausulas. Para simplificarlo, usamos +`$query` para representar el objeto [[yii\db\Query]]: + +### `SELECT` + +Para formar una consulta `SELECT` básica, se necesita especificar que columnas y de que tablas se seleccionarán: + +```php +$query->select('id, name') + ->from('user'); +``` + +Las opciones de select se pueden especificar como una cadena de texto (string) separada por comas o como un array. La +sintaxis del array es especialmente útil cuando se forma la selección dinámicamente. + +```php +$query->select(['id', 'name']) + ->from('user'); +``` + +> Información: Se debe usar siempre el formato array si la clausula `SELECT` contiene expresiones SQL. Esto se debe a + que una expresión SQL como `CONCAT(first_name, last_name) AS full_name` puede contener comas. Si se junta con otra + cadena de texto de otra columna, puede ser que la expresión se divida en varias partes por comas, esto puede + conllevar a errores. + +Cuando se especifican columnas, se pueden incluir los prefijos de las tablas o alias de columnas, p. ej. `user.id`, +`user.id AS user_id`. Si se usa un array para especificar las columnas, también se pueden usar las claves del array +para especificar los alias de columna, p. ej. `['user_id' => 'user.id', 'user_name' => 'user.name']`. + +A partir de la versión 2.0.1, también se pueden seleccionar subconsultas como columnas. Por ejemplo: + +```php +$subQuery = (new Query)->select('COUNT(*)')->from('user'); +$query = (new Query)->select(['id', 'count' => $subQuery])->from('post'); +// $query representa la siguiente sentencia SQL: +// SELECT `id`, (SELECT COUNT(*) FROM `user`) AS `count` FROM `post` +``` + +Para seleccionar filas distintas, se puede llamar a `distinct()`, como se muestra a continuación: + +```php +$query->select('user_id')->distinct()->from('post'); +``` + +### `FROM` + +Para especificar de que tabla(s) se quieren seleccionar los datos, se llama a `from()`: + +```php +$query->select('*')->from('user'); +``` + +Se pueden especificar múltiples tablas usando una cadena de texto separado por comas o un array. Los nombres de tablas +pueden contener prefijos de esquema (p. ej. `'public.user'`) y/o alias de tablas (p. ej. `'user u'). El método +entrecomillara automáticamente los nombres de tablas a menos que contengan algún paréntesis (que significa que se +proporciona la tabla como una subconsulta o una expresión de BD). Por ejemplo: + +```php +$query->select('u.*, p.*')->from(['user u', 'post p']); +``` + +Cuando se especifican las tablas como un array, también se pueden usar las claves de los arrays como alias de tablas +(si una tabla no necesita alias, no se usa una clave en formato texto). Por ejemplo: + +```php +$query->select('u.*, p.*')->from(['u' => 'user', 'p' => 'post']); +``` + +Se puede especificar una subconsulta usando un objeto `Query`. En este caso, la clave del array correspondiente se +usará como alias para la subconsulta. + +```php +$subQuery = (new Query())->select('id')->from('user')->where('status=1'); +$query->select('*')->from(['u' => $subQuery]); +``` + +### `WHERE` + +Habitualmente se seleccionan los datos basándose en ciertos criterios. El Constructor de Consultas tiene algunos +métodos útiles para especificarlos, el más poderoso de estos es `where`, y se puede usar de múltiples formas. + +La manera más simple para aplicar una condición es usar una cadena de texto: + +```php +$query->where('status=:status', [':status' => $status]); +``` + +Cuando se usan cadenas de texto, hay que asegurarse que se unen los parámetros de la consulta, no crear una consulta +mediante concatenación de cadenas de texto. El enfoque anterior es seguro, el que se muestra a continuación, no lo es: + +```php +$query->where("status=$status"); // Peligroso! +``` + +En lugar de enlazar los valores de estado inmediatamente, se puede hacer usando `params` o `addParams`: + +```php +$query->where('status=:status'); +$query->addParams([':status' => $status]); +``` + +Se pueden establecer múltiples condiciones en `where` usando el *formato hash*. + +```php +$query->where([ + 'status' => 10, + 'type' => 2, + 'id' => [4, 8, 15, 16, 23, 42], +]); +``` + +El código generará la el siguiente SQL: + +```sql +WHERE (`status` = 10) AND (`type` = 2) AND (`id` IN (4, 8, 15, 16, 23, 42)) +``` + +El valor NULO es un valor especial en las bases de datos, y el Constructor de Consultas lo gestiona inteligentemente. +Este código: + +```php +$query->where(['status' => null]); +``` + +da como resultado la siguiente cláusula WHERE: + +```sql +WHERE (`status` IS NULL) +``` + +También se pueden crear subconsultas con objetos de tipo `Query` como en el siguiente ejemplo: + +```php +$userQuery = (new Query)->select('id')->from('user'); +$query->where(['id' => $userQuery]); +``` + +que generará el siguiente código SQL: + +```sql +WHERE `id` IN (SELECT `id` FROM `user`) +``` + +Otra manera de usar el método es el formato de operando que es `[operator, operand1, operand2, ...]`. + +El operando puede ser uno de los siguientes (ver también [[yii\db\QueryInterface::where()]]): + +- `and`: los operandos deben concatenerase usando `AND`. por ejemplo, `['and', 'id=1', 'id=2']` generará + `id=1 AND id=2`. Si el operando es un array, se convertirá en una cadena de texto usando las reglas aquí descritas. + Por ejemplo, `['and', 'type=1', ['or', 'id=1', 'id=2']]` generará `type=1 AND (id=1 OR id=2)`. El método no + ejecutará ningún filtrado ni entrecomillado. + +- `or`: similar al operando `and` exceptuando que los operando son concatenados usando `OR`. + +- `between`: el operando 1 debe ser el nombre de columna, y los operandos 2 y 3 deben ser los valores iniciales y + finales del rango en el que se encuentra la columna. Por ejemplo, `['between', 'id', 1, 10]` generará + `id BETWEEN 1 AND 10`. + +- `not between`: similar a `between` exceptuando que `BETWEEN` se reemplaza por `NOT BETWEEN` en la condición + generada. + +- `in`: el operando 1 debe ser una columna o una expresión de BD. El operando 2 puede ser un array o un objeto de tipo + `Query`. Generará una condición `IN`. Si el operando 2 es un array, representará el rango de valores que puede + albergar la columna o la expresión de BD; Si el operando 2 es un objeto de tipo `Query`, se generará una subconsulta + y se usará como rango de la columna o de la expresión de BD. Por ejemplo, `['in', 'id', [1, 2, 3]]` generará + `id IN (1, 2, 3)`. El método entrecomillará adecuadamente el nombre de columna y filtrará los valores del rango. El + operando `in` también soporta columnas compuestas. En este caso, el operando 1 debe se un array de columnas, + mientras que el operando 2 debe ser un array de arrays o un objeto de tipo `Query` que represente el rango de las + columnas. + +- `not in`: similar que el operando `in` exceptuando que `IN` se reemplaza por `NOT IN` en la condición generada. + +- `like`: el operando 1 debe ser una columna o una expresión de BD, y el operando 2 debe ser una cadena de texto o un + array que represente los valores a los que tienen que asemejarse la columna o la expresión de BD.Por ejemplo, + `['like', 'name', 'tester']` generará `name LIKE '%tester%'`. Cuando se da el valor rango como un array, se + generarán múltiples predicados `LIKE` y se concatenaran usando `AND`. Por ejemplo, + `['like', 'name', ['test', 'sample']]` generará `name LIKE '%test%' AND name LIKE '%sample%'`. También se puede + proporcionar un tercer operando opcional para especificar como deben filtrarse los caracteres especiales en los + valores. El operando debe se un array que mapeen los caracteres especiales a sus caracteres filtrados asociados. Si + no se proporciona este operando, se aplicará el mapeo de filtrado predeterminado. Se puede usar `false` o un array + vacío para indicar que los valores ya están filtrados y no se necesita aplicar ningún filtro. Hay que tener en + cuenta que cuando se usa un el mapeo de filtrado (o no se especifica el tercer operando), los valores se encerraran + automáticamente entre un par de caracteres de porcentaje. + +> Nota: Cuando se usa PostgreSQL también se puede usar +[`ilike`](http://www.postgresql.org/docs/8.3/static/functions-matching.html#FUNCTIONS-LIKE) en lugar de `like` para +filtrar resultados insensibles a mayúsculas (case-insensitive). + +- `or like`: similar al operando `like` exceptuando que se usa `OR` para concatenar los predicados `LIKE` cuando haya + un segundo operando en un array. + +- `not like`: similar al operando `like` exceptuando que se usa `LIKE` en lugar de `NOT LIKE` en las condiciones + generadas. + +- `or not like`: similar al operando `not like` exceptuando que se usa `OR` para concatenar los predicados `NOT LIKE`. + +- `exists`: requiere un operando que debe ser una instancia de [[yii\db\Query]] que represente la subconsulta. Esto + generará una expresión `EXISTS (sub-query)`. + +- `not exists`: similar al operando `exists` y genera una expresión `NOT EXISTS (sub-query)`. + +Adicionalmente se puede especificar cualquier cosa como operando: + +```php +$query->select('id') + ->from('user') + ->where(['>=', 'id', 10]); +``` + +Cuyo resultado será: + +```sql +SELECT id FROM user WHERE id >= 10; +``` + +Si se construyen partes de una condición dinámicamente, es muy convenientes usar `andWhere()` y `orWhere()`: + +```php +$status = 10; +$search = 'yii'; + +$query->where(['status' => $status]); +if (!empty($search)) { + $query->andWhere(['like', 'title', $search]); +} +``` + +En el caso que `$search` no este vacío, se generará el siguiente código SQL: + +```sql +WHERE (`status` = 10) AND (`title` LIKE '%yii%') +``` + +#### Construcción de Condiciones de Filtro + +Cuando se generan condiciones de filtro basadas en datos recibidos de usuarios (inputs), a menudo se quieren gestionar +de forma especial las "datos vacíos" para ignorarlos en los filtros. Por ejemplo, teniendo un formulario HTML que +obtiene el nombre de usuario y la dirección de correo electrónico. Si el usuario solo rellena el campo de nombre de +usuario, se puede querer generar una consulta para saber si el nombre de usuario recibido es valido. Se puede usar +`filterWhere()` para conseguirlo: + +```php +// $username y $email son campos de formulario rellenados por usuarios +$query->filterWhere([ + 'username' => $username, + 'email' => $email, +]); +``` + +El método `filterWhere()` es muy similar al método `where()`. La principal diferencia es que el `filterWhere()` +eliminará los valores vacíos de las condiciones proporcionadas. Por lo tanto si `$email` es "vació", la consulta +resultante será `...WHERE username=:username`; y si tanto `$username` como `$email` son "vacías", la consulta no +tendrá `WHERE`. + +Decimos que un valor es *vacío* si es nulo, una cadena de texto vacía, una cadena de texto que consista en espacios en +blanco o un array vacío. + +También se pueden usar `andFilterWhere()` y `orFilterWhere()` para añadir más condiciones de filtro. + +### `ORDER BY` + +Se pueden usar `orderBy` y `addOrderBy` para ordenar resultados: + +```php +$query->orderBy([ + 'id' => SORT_ASC, + 'name' => SORT_DESC, +]); +``` + +Aquí estamos ordenando por `id` ascendente y después por `name` descendente. + +### `GROUP BY` and `HAVING` + +Para añadir `GROUP BY` al SQL generado se puede usar el siguiente código: + +```php +$query->groupBy('id, status'); +``` + +Si se quieren añadir otro campo después de usar `groupBy`: + +```php +$query->addGroupBy(['created_at', 'updated_at']); +``` + +Para añadir la condición `HAVING` se pueden usar los métodos `having` y `andHaving` y `orHaving`. Los parámetros para +ellos son similares a los del grupo de métodos `where`: + +```php +$query->having(['status' => $status]); +``` + +### `LIMIT` and `OFFSET` + +Para limitar el resultado a 10 filas se puede usar `limit`: + +```php +$query->limit(10); +``` + +Para saltarse las 100 primeras filas, se puede usar: + +```php +$query->offset(100); +``` + +### `JOIN` + +Las clausulas `JOIN` se generan en el Constructor de Consultas usando el método join aplicable: + +- `innerJoin()` +- `leftJoin()` +- `rightJoin()` + +Este left join selecciona los datos desde dos tablas relacionadas en una consulta: + +```php +$query->select(['user.name AS author', 'post.title as title']) + ->from('user') + ->leftJoin('post', 'post.user_id = user.id'); +``` + +En el código, el primer parámetro del método `leftjoin` especifica la tabla a la que aplicar el join. El segundo +parámetro, define la condición del join. + +Si la aplicación de bases de datos soporta otros tipos de joins, se pueden usar mediante el método `join` genérico: + +```php +$query->join('FULL OUTER JOIN', 'post', 'post.user_id = user.id'); +``` + +El primer argumento es el tipo de join a realizar. El segundo es la tabla a la que aplicar el join, y el tercero es la condición: + +Como en `FROM`, también se pueden efectuar joins con subconsultas. Para hacerlo, se debe especificar la subconsulta +como un array que tiene que contener un elemento. El valor del array tiene que ser un objeto de tipo `Query` que +represente la subconsulta, mientras que la clave del array es el alias de la subconsulta. Por ejemplo: + +```php +$query->leftJoin(['u' => $subQuery], 'u.id=author_id'); +``` + +### `UNION` + +En SQL `UNION` agrega resultados de una consulta a otra consulta. Las columnas devueltas por ambas consultas deben +coincidir. En Yii para construirla, primero se pueden formar dos objetos de tipo query y después usar el método +`union`: + +```php +$query = new Query(); +$query->select("id, category_id as type, name")->from('post')->limit(10); + +$anotherQuery = new Query(); +$anotherQuery->select('id, type, name')->from('user')->limit(10); + +$query->union($anotherQuery); +``` + +Consulta por Lotes +--------------- + +Cuando se trabaja con grandes cantidades de datos, los métodos como [[yii\db\Query::all()]] no son adecuados ya que +requieren la carga de todos los datos en memoria. Para mantener los requerimientos de memoria reducidos, Yii +proporciona soporte a las llamadas consultas por lotes (batch query). Una consulta por lotes usa un cursor de datos y +recupera los datos en bloques. + +Las consultas por lotes se pueden usar del siguiente modo: + +```php +use yii\db\Query; + +$query = (new Query()) + ->from('user') + ->orderBy('id'); + +foreach ($query->batch() as $users) { + // $users is an array of 100 or fewer rows from the user table +} + +// o si se quieren iterar las filas una a una +foreach ($query->each() as $user) { + // $user representa uno fila de datos de la tabla user +} +``` + +Los métodos [[yii\db\Query::batch()]] y [[yii\db\Query::each()]] devuelven un objeto [[yii\db\BatchQueryResult]] que +implementa una interfaz `Iterator` y así se puede usar en el constructor `foreach`. Durante la primera iteración, se +efectúa una consulta SQL a la base de datos. Desde entonces, los datos se recuperan por lotes en las iteraciones. El +tamaño predeterminado de los lotes es 100, que significa que se recuperan 100 filas de datos en cada lote. Se puede +modificar el tamaño de los lotes pasando pasando un primer parámetro a los métodos `batch()` o `each()`. + +En comparación con [[yii\db\Query::all()]], las consultas por lotes sólo cargan 100 filas de datos en memoria cada +vez. Si el procesan los datos y después se descartan inmediatamente, las consultas por lotes, pueden ayudar a mantener +el uso de memora bajo un limite. + +Si se especifica que el resultado de la consulta tiene que ser indexado por alguna columna mediante +[[yii\db\Query::indexBy()]], las consultas por lotes seguirán manteniendo el indice adecuado. Por ejemplo, + +```php +use yii\db\Query; + +$query = (new Query()) + ->from('user') + ->indexBy('username'); + +foreach ($query->batch() as $users) { + // $users esta indexado en la columna "username" +} + +foreach ($query->each() as $username => $user) { +} +``` \ No newline at end of file