Duda Consulta Sql Relacional (Criteria) Con Varias Tablas (Más De Dos) - Yii Framework

Buena noche, he trabajado un poco con consultas relacionales entre dos tablas, por medio de las relaciones creadas en el modelo que Yii genera automáticamente, más en este momento requiero hacerlo con tres tablas las cuales son las siguientes (los puntos suspensivos representan campos de poca relevancia):

-Lineas (id_linea, linea…)

-Referencias (id_referencia, id_linea, referencia, imagen (este campo guarda una ruta de la imagen)…)

-Inventario (id_inventario, id_referencia, cantidad, fecha, estado, …)

Los siguientes son los métodos "relations" y "search" del modelo de la tabla "inventario" (cuyo CRUD completo lo genere por gii)




public function relations()

	{

		// NOTE: you may need to adjust the relation name and the related

		// class name for the relations automatically generated below.

		return array(

                        'creadoPor' => array(self::BELONGS_TO, 'Usuarios', 'creado_por'),

			'editadoPor' => array(self::BELONGS_TO, 'Usuarios', 'editado_por'),

			'idReferencia' => array(self::BELONGS_TO, 'Referencias', 'id_referencia'),

                        'referencia' =>array(self::BELONGS_TO, 'Referencia', 'id_referencia'),

                        'creador' =>array(self::BELONGS_TO, 'Usuario', 'creado_por'),

                        'editor' =>array(self::BELONGS_TO, 'Usuario', 'editado_por'),

		);

	}


public function search()

	{

		// Warning: Please modify the following code to remove attributes that

		// should not be searched.


		$criteria=new CDbCriteria;

                

                $criteria->condition = "estado='Ingreso'";

		$criteria->compare('id_inventario',$this->id_inventario);

		$criteria->compare('id_referencia',$this->id_referencia);

		$criteria->compare('cantidad',$this->cantidad);

		$criteria->compare('estado',$this->estado,true);

		$criteria->compare('fecha',$this->fecha,true);

                $criteria->compare('creado_por',$this->creado_por);

		$criteria->compare('creado_en',$this->creado_en,true);

		$criteria->compare('editado_por',$this->editado_por);

		$criteria->compare('editado_en',$this->editado_en,true);


		return new CActiveDataProvider($this, array(

			'criteria'=>$criteria,

		));

	}



La acción "admin" (del controlador) para usar el buscador, está intacta (no lo he modificado).




public function actionAdmin()

	{

		$model=new Inventario('search');

		$model->unsetAttributes();  // clear any default values

                

                $lineas = $model->obtenerLineas();

                $usuarios = $model->obtenerUsuarios();

                

		if(isset($_GET['Inventario']))

			$model->attributes=$_GET['Inventario'];


		$this->render('admin',array(

			'model'=>$model, 'lineas'=>$lineas, 'referencias'=>$referencias, 'usuarios'=>$usuarios,

		));

	}



Y la vista contiene dos listas desplegables la segunda es dependiente de la primera y funciona sin problema, la hice basado en este link http://www.yiiframework.com/wiki/24/creating-a-dependent-dropdown/,




<div class="wide form">


<?php $form=$this->beginWidget('CActiveForm', array(

	'action'=>Yii::app()->createUrl($this->route),

	'method'=>'get',

)); ?>

        

        <div class="row">

                <?php echo CHtml::label('Línea', 'Línea'); ?>

                <?php

                echo CHtml::dropDownList('id_linea','', CHtml::listData(Linea::model()->findAll($lineas),'id_linea','linea'),

                    array('empty'=>'',

                        'ajax' => array(

                            'type'=>'POST', //request type

                            'url'=>CController::createUrl('inventario/obtenerReferencias'),

                            'update'=>'#Inventario_id_referencia', //selector to update

                        )

                    )

                );

                ?>

        </div>

        

        <div class="row">

                <?php echo $form->label($model,'id_referencia'); ?>

                <?php echo CHtml::dropDownList('Inventario[id_referencia]','Inventario_id_referencia', array(),array('empty'=>'')); ?>

        </div>


	<div class="row buttons">

		<?php echo CHtml::submitButton('Buscar'); ?>

	</div>


<?php $this->endWidget(); ?>


</div><!-- search-form -->



solo deje dos campos en el formulario de búsqueda, pues solo me pidieron esos, una vez seleccionó una línea, me despliega (por medio de la petición ajax) en la otra lista las referencias relacionadas a esta línea y una vez seleccionó una referencia, puedo consultar sin problema los inventarios relacionados a la referencia, más requiero que si se selecciona una línea, pero no una referencia, se puedan consultar todos los inventarios relacionados a las líneas "hijas" de la referencia.

Por si les es útil (más no creo que necesario mencionar en relación a mi necesidad) les dejo la acción que se ejecuta en la petición ajax:




public function actionObtenerReferencias()

        {

            $data=Referencia::model()->findAll('id_linea=:id_linea', 

                          array(':id_linea'=>(int) $_POST['id_linea']));


            $data=CHtml::listData($data,'id_referencia','referencia');

            echo CHtml::tag('option',

                           array('value'=>''),CHtml::encode(''),true);

            foreach($data as $value=>$name)

            {

                echo CHtml::tag('option',

                           array('value'=>$value),CHtml::encode($name),true);

            }

        }



y la función del modelo con la cual consultó las referencias que se cargan por defecto en la primera lista desplegable:




public function obtenerReferencias()

        {

                $criteria = new CDbCriteria;

                $criteria->select = "id_referencia, referencia";

                $criteria->order = "referencia";

                return $criteria;

        }



Supongo que hay que modificar el método "search" del modelo, más no tengo claro cómo hacerlo, he leído sobre las consultas con criteria, más aún me parecen un poco confusas, si alguno de uds me puede ayudar con esto, se lo agradecería mucho.

De igual manera les agradecería si alguno sabe cómo imprimir de forma "100% limpia" (no un array, ni nada ambiguo, como ya he visto en muchos posts) las consultas ejecutadas, por criteria.

Gracias por su atención.

Antes que nada le recomiendo usar un generador un poco más potente que el generador por defecto (personalmete uso AweCrud) que ayudan a generar un código más "completo" en el sentido de consultas campos relacionales.

En cuanto a la función search() y los criteria, son un concepto bastante usado (no solamente en Yii sino en buena cantidad de frameworks), así que conviene conocerlo.

Un criteria es una especie de "extensión" o "restricción" a una consulta "SELECT * FROM MiTabla", que es bastante genérica.

Los criteria permiten agregarle a esa consulta las relaciones (sentencia join o with), condiciones del where (sentencia condition), agrupamiento (group), parámetros (param), entre muchos otros…

OJO: esas condiciones por sí solas no hacen nada, se tienen que aplicar a un modelo o a una búsqueda o a un provider… Entonces podríamos llegar a hacer algo como:


$this->getDbCriteria()->mergeWith($criteria);

return $this;

En el caso de las condiciones tipo LIKE, se hacen con la sentencia "compare" del criteria, por eso que en el search encuentra varias de estas sentencias.

Entonces, lo que sucede es que el CGridView utiliza la función "search()" para hacer sus búsquedas, de ahí la importancia de esta función. Pero no hay mayor ciencia detrás de esa función.

Espero haber resuelto algunas dudas.

Por cierto, qué quiere decir o a qué se refiere cuando dice:

Saludos.

Hola robregonm, gracias por la respuesta, más debo admitir que me siento "en las mismas" pues más que teoría, buscaba es código, sé que la teoría es importante, más también el código respecto a la consulta que necesito realizar, de igual aclaro, con lo de imprimir SQL, me refiero es a qué de forma "típica" se puede crear consultas SQL a manera de cadena y "meter" esa cadena en una variable, para luego llamar un método de tipo "execute" al cual se le pasa como parámetro la variable (que contiene la cadena) para ejecutar el query en la BD, más esa variable es posible imprimirla retornando la cadena SQL por completo, para depurar la consulta que finalmente se ejecuta. soy claro?

Ah ya entendí… jajaja… no había comprendido, pero bueno… a lo q se refiere es pasar por alto el ActiveRecord y utilizar el DAO… mejor dicho, hacer una consulta de forma directa a la BD en lenguaje SQL, correcto?

Bueno, se hace algo así:




Yii::app()->db->createCommand()->select('campos')->from('tablas')->where('condiciones')->group(...)->execute()

Lo anterior equivale al típico SELECT campos FROM tablas WHERE condiciones GROUP… (Era de esperarse, no?)

Un poco extenso pero espero haber cubierto una buena cantidad de opciones… porque igual se pueden hacer consultas sencillas del tipo:


Yii::app()->createCommand('SELECT * FROM tablas')

Pero por escalabilidad se prefiere la primera opción.

Depronto cabría agregar que el DAO (o consultas por SQL directo) no deberían hacerse a menos que haya una buena razón para eso, pues estoy seguro que despues de que haya aprenddo a usar ActiveRecord no querrá usar DAO a menos que exista esa buena razón, jajaja

Con ActiveRecord es mucho más sencillo hacer varias cosas.

Por ejemplo una consulta relacional podría ser algo como:


$CreadorInventario = Inventario::model()->findByPk(1)->creadoPor->nombre;

Lo anterior se traduciría como dos consultas (también se le puede indicar que lo haga en una sola consulta y no dos separadas):

Una sería: SELECT * FROM Inventario WHERE id = 1;

y la otra: SELECT * FROM Usuarios WHERE id = {Valor devuelto en la consulta anterior}

Y la versión para una sola consulta, es decir, que genere algo como: "SELECT * FROM Inventario i LEFT JOIN Usuarios u ON (i.creado_por = u.id) WHERE i.id=1" sería entonces algo como:


$creadorInventario = Inventario::model()->with('creadoPor')->together()->findByPk(1)->nombre;

Saludos