Yii Generate query with a relation condition (ActiveRecord) - yii

I try to generate this query.
select * from jobs j inner join vacancies v on j.id = v.job_id where v.id = 2943 order by j.created_at desc limit 1
v.id just to test if the query work fine or not.
I have some condition from vacancies tables need to generate it with the jobs tables.
i try more than case to generate it.
this the relation from Vacancies model
'job' => array(self::BELONGS_TO, 'Jobs', 'job_id'),
this the relation from Jobs model
'vacancies' => array(self::HAS_MANY, 'Vacancies', 'job_id'),
and this my query.
$newJobs = Jobs::model()->findAll(
array('with'=>array(
'vacancies'=>array(
'condition'=>'vacancies.id = 2943',
'order'=>'t.created_at desc',
)
),
'order'=>'t.created_at desc'
,'limit'=>1
)
);
but this give me the latest jobs without the any condition from vacancies as id.
and try this with some scopes in the Jobs model.
$newJobs = Jobs::model()->isOffline()->isApproved()->recent()->findAll(array(
'with'=>array(
'vacancies'=>array(
'condition'=>'vacancies.id=2268'
)
)
)
);
the scopes
public function recent($limit=1)
{
$this->getDbCriteria()->mergeWith(array(
'order'=>'t.created_at DESC',
'limit'=>$limit,
));
return $this;
}
public function isOffline($offline = 'False')
{
$this->getDbCriteria()->mergeWith(array(
'condition'=>"t.offline = '$offline' OR t.offline is null",
));
return $this;
}
public function isApproved($approved = 'False')
{
$this->getDbCriteria()->mergeWith(array(
'condition'=>"t.approved= '$approved'",
));
return $this;
}
And not work is still give me the latest jobs without the condition from vacancies.
Can any one help me.

Try this:
...
array('with'=>array(
'vacancies'=>array(
'alias'=>'v',
'joinType'=>'INNER JOIN',
'condition'=>'v.id = 2943',
'order'=>'t.created_at desc',
)
),
...
http://www.yiiframework.com/doc/guide/1.1/en/database.arr#performing-relational-query-without-getting-related-models

Related

Filter on my module for Prestashop 1.6

I'm trying to create a custom list for my module on Prestashop 1.6 and I need to get data from three different tables. My problem is the $this->_filter variable, how can I do that?
I need to do this:
$query = '
SELECT s.*, pl.*
FROM '._DB_PREFIX_.'`scd_gift` s
INNER JOIN '._DB_PREFIX_.'product_lang pl
ON s.id_product = pl.id_product
WHERE s.`id_gift_type` = '.(int)$id_gift_type.' and id_lang='.$id_lang;
Here is my function:
public function getCustomListHostessGifts() {
$this->table = 'scd_gift';
$this->list_id = 'hostess_gift';
$this->lang = true;
$this->identifier = 'id_scd_gift';
$this->_orderBy = 'id_product';
$this->_orderWay = 'DESC';
$this->addRowAction('delete');
$this->fields_list = (array(
'id_product' => array('title' => $this->l('ID'), 'class' => 'fixed-width-xs',
'align' => 'center'),
'name' => array('title' => $this->l('Name'), 'filter_key' => 'b!name'),
));
$this->clearFilters();
$hostessType = MlmGiftsModule::getGiftTypeIdByGiftTypeName('_HOSTESSGIFT_');
$this->_join = Shop::addSqlAssociation('scd_gift', 'a');
$this->_filter = '
INNER JOIN '._DB_PREFIX_.'product_lang pl
ON s.id_product = pl.id_product
AND a.`id_gift_type` ='.$hostessType ;
$this->toolbar_title = $this->l('Hostess gifts:');
return $this->renderList();
}
_filter is use to set filter parameters passed in backoffice list search fields.
Any JOIN sentence must be set in _join var:
$this->_join = Shop::addSqlAssociation('scd_gift', 'a');
. ' INNER JOIN '._DB_PREFIX_.'product_lang pl
ON s.id_product = pl.id_product
AND a.`id_gift_type` ='.$hostessType ;
Good luck.
PixelWeb's answer is correct, but of course, there is no semicolon after
$this->_join = Shop::addSqlAssociation('scd_gift', 'a')
Additional. You may skip this line anyway, because Prestashop adds the default SQL association "a" for the table in the controller itsself.

CActiveDataProvider Querying on join

I have a simple SQL query, which I need to convert to use in Yii 1.1.
SELECT *
FROM User
INNER JOIN Role ON Role.UserId = User.Id
WHERE Role.Name = 'admin'
How is this written into the CActiveDataProvider?
I have came up with an answer. Hopefully it helps someone in the future.
$dataProvider = new ActiveDataProvider('User', array
(
'criteria' => array
(
'with' =>'roles',
'join' => 'INNER JOIN Role r ON r.UserId = User.Id',
'condition' => 'r.Name=:term',
'params' => array(':term'=>'admin')
)
));

Yii: A HAS_MANY B, B HAS_MANY C, find count of C belonging to A

Basically, I have 3 tables.
house_table
===========
house_id
floor_table
===========
floor_id
house_id
room_table
===========
room_id
floor_id
is_occupied
A house has many floor, a floor has many rooms. A room belongs to one floor, a floor belongs to one house. The corresponding models created automatically by gii are HouseTable, FloorTable, RoomTable.
What I need is to findAll() houses that have rooms that are not occupied.
How do I do that? Something like this?
class HouseRecord extends CActiveRecord {
public function relations() {
return array(
'FREE_ROOM_COUNT' => array(self::STAT ...???...),
);
}
}
Sure, I could do it with SQL, but it needs to be done this way, because the result of findAll() is used as a data provider in a grid.
UPDATE
Following tinybyte's advice, here's what finally worked.
public function relations() {
return array(
'FREE_ROOM_COUNT' => array(
self::STAT ,
'FloorTable',
'house_id',
'select' => 'COUNT(rt.floor_id)',
'join' => 'INNER JOIN room_table rt ON t.floor_id = rt.floor_id',
'condition' => 'rt.is_occupied = 0',
),
);
}
And to be used as so:
$criteria = new CDbCriteria;
$criteria->with = array('FREE_ROOM_COUNT');
$criteria->together = true;
$provider = new CActiveDataProvider(HouseTable::model(), array(
'criteria'=>$criteria,
'pagination' => array(
'pageSize' => 1,
),
));
$this->widget('zii.widgets.CListView', array(
'dataProvider'=>$provider,
'itemView'=>'house',
));
Unfortunately it turns out one can not use these STAT relations as conditions! (Confirmed here: Using STAT relation in CActiveDataProvider criteria).
I think this should do the trick
'FREE_ROOM_COUNT' => array(
self::STAT ,
'Floor' ,
'house_id'
'select' => 'count(rt.floor_id)' , // or count(rt.room_id)
'join' => 'Inner join room_table rt ON Floor.floor_id = rt.floor_id' ,
),

LINQ to EF Using a Collection in a Where Clause

I have a main VendorProfile table and a 1-many VendorHistory table that contains status codes and date stamps. The query below works at retrieving only the latest status (status code and date) for each vendor. However, the view allows the user to select checkboxes of any of the status codes to filter the view. So I need to add a where clause that matches ANY of the checkbox StatusSelections.
Model Diagram
public IEnumerable<BrowseStatusModel> BrowseByStatus(int[] StatusSelections)
{
IQueryable<BrowseStatusModel> query = _db.VendorProfiles
.Include("VendorStatusHistory")
.Include("StatusCodes")
.Select(s => new BrowseStatusModel
{
ProfileID = s.ProfileID,
Name = s.Name,
CompanyName = s.CompanyName,
CompanyDBA = s.CompanyDBA,
DateCreated = s.DateCreated,
Status = s.VendorStatusHistories.OrderByDescending(o => o.DateCreated).FirstOrDefault().Id,
StatusDate = s.VendorStatusHistories.OrderByDescending(o => o.DateCreated).FirstOrDefault().DateCreated
})
.OrderBy(x => x.ProfileID);
foreach (int status in StatusSelections)
{
query = query.Where(x => x.Status == status);
}
return query;
}
The above foreach loop works but, unfortunately creates AND condition where ALL selections must be true instead of ANY. I figured I would have to use a where clause with the following in some way but have been unsuccessful at the correct syntax.
.AsQueryable().Any();
Use contains in the place of that foreach loop
query = query.Where(x => StatusSelections.Contains(x.Status))

Duplicated and unnecessary joins when using Linq in NHibernate

Basically I crossed the same problem of Linq provider in this linq-to-nhibernate-produces-unnecessary-joins
List<Competitions> dtoCompetitions;
dtoCompetitions = (from compset in session.Query<FWBCompetitionSet>()
where compset.HeadLine == true
&& compset.A.B.CurrentSeason == true
select (new Competitions
{
CompetitionSetID = compset.CompetitionSetID,
Name = compset.Name,
Description = compset.Description,
Area = compset.Area,
Type = compset.Type,
CurrentSeason = compset.A.B.CurrentSeason,
StartDate = compset.StartDate
}
)).ToList();
Which leads to duplicated join in its generated SQL
SELECT fwbcompeti0_.competitionsetid AS col_0_0_,
fwbcompeti0_.name AS col_1_0_,
fwbcompeti0_.DESCRIPTION AS col_2_0_,
fwbcompeti0_.area AS col_3_0_,
fwbcompeti0_.TYPE AS col_4_0_,
fwbseason3_.currentseason AS col_5_0_,
fwbcompeti0_.startdate AS col_6_0_
FROM fwbcompetitionset fwbcompeti0_
INNER JOIN A fwbcompeti1_
ON fwbcompeti0_.competitionseasonid = fwbcompeti1_.competitionseasonid
INNER JOIN A fwbcompeti2_
ON fwbcompeti0_.competitionseasonid = fwbcompeti2_.competitionseasonid
INNER JOIN B fwbseason3_
ON fwbcompeti2_.seasonid = fwbseason3_.seasonid
WHERE fwbcompeti0_.headline = #p0
AND fwbseason3_.currentseason = #p1
Notice these joins, which are totally duplicated and also affect my SQL Server's performence.
INNER JOIN A fwbcompeti1_
ON fwbcompeti0_.competitionseasonid = fwbcompeti1_.competitionseasonid
INNER JOIN A fwbcompeti2_
ON fwbcompeti0_.competitionseasonid = fwbcompeti2_.competitionseasonid
Update1
In the NHibernate 3.2, this LiNQ bug is still valid, and I could not find a simple and reasonable Linq solution.
So I used QueryOver + JoinAlias + TransformUsing finishing the job, workds perfect to me.
FWBCompetitionSet compset = null;
FWBCompetitionSeason compseason = null;
FWBSeason season = null;
IList<Competitions> dtoCompetitions;
dtoCompetitions = session.QueryOver<FWBCompetitionSet>(() => compset)
.JoinAlias(() => compset.FWBCompetitionSeason, () => compseason)
.JoinAlias(() => compseason.FWBSeason, () => season)
.Where(() => compset.HeadLine == true)
.And(() => season.CurrentSeason == true)
.SelectList(
list => list
.Select(c => c.CompetitionSetID).WithAlias(() => compset.CompetitionSetID)
.Select(c => c.Name).WithAlias(() => compset.Name)
.Select(c => c.Description).WithAlias(() => compset.Description)
.Select(c => c.Area).WithAlias(() => compset.Area)
.Select(c => c.Type).WithAlias(() => compset.Type)
.Select(c => season.CurrentSeason).WithAlias(() => season.CurrentSeason)
.Select(c => c.StartDate).WithAlias(() => compset.StartDate)
)
.TransformUsing(Transformers.AliasToBean<Competitions>())
.List<Competitions>();
Yet Another Edit:
I think I finally found out what's going on. It seems that the LINQ to NHibernate provider has trouble navigating associations from the target to the source table and generates a separate join each time it encounters such an association.
Since you don't provide your mapping, I used the mapping from linq-to-nhibernate-produces-unnecessary-joins. This model has a Document with one Job and many TranslationUnits. Each TranslationUnit has many Translation entities.
When you try to find a Translation based on a Job, you are traversing the associations in the reverse order and the LINQ provider generates multiple joins: one for Translation -> TranslationUnit and one for TranslationUnit to Document.
This query will generate redundant joins:
session.Query<TmTranslation>()
.Where(x => x.TranslationUnit.Document.Job == job)
.OrderBy(x => x.Id)
.ToList();
If you reverse the navigation order to Document -> TranslationUnit -> Translation, you get a query that doesn't produce any redundant joins:
var items=(from doc in session.Query<Document>()
from tu in doc.TranslationUnits
from translation in tu.Translations
where doc.Job ==job
orderby translation.Id
select translation).ToList();
Given this quirkiness, QueryOver seems like a better option.
Previous Edit:
I suspect the culprit is compset.A.B.CurrentSeason. The first joined table (fwbcompeti1_) returns A.B while the next two (fwbcompeti2_ and fwbseason3_) are used to return A.B. The LINQ to NHibernate provider doesn't seem to guess that A is not used anywhere else and fails to remove it from the generated statement.
Try to help the optimizer a little by replacing CurrentSeason = compset.A.B.CurrentSeason with CurrentSeason = true from the select, since your where statement returns only items with CurrentSeason == true.
EDIT: What I mean is to change the query like this:
List<Competitions> dtoCompetitions;
dtoCompetitions = (from compset in session.Query<FWBCompetitionSet>()
where compset.HeadLine == true
&& compset.A.B.CurrentSeason == true
select (new Competitions
{
CompetitionSetID = compset.CompetitionSetID,
Name = compset.Name,
Description = compset.Description,
Area = compset.Area,
Type = compset.Type,
CurrentSeason = true,
StartDate = compset.StartDate
}
)).ToList();
I simply replace the value compset.A.B.CurrentSeason with true