Yii: Executing the where condition before joining - sql

I'm currently having a problem with my query
TableA::find()
->select('*')
->joinWith(['TableB'])
->joinWith(['TableC'])
->joinWith(['TableD'])
->where([TableA.attribute1=1 or TableA.attribute2=1])
->andWhere([(further possible conditions on the other Tables)])
->all()
The usual execution order for SQL Queries is From (with joins) and then where.
Is there a way to execute the first where condition before the joins, to reduce the amount of joined lines? Something like
TableA::find()
->where([TableA.attribute1=1 or TableA.attribute2=1])
->select('*')
->joinWith(['TableB'])
->joinWith(['TableC'])
->joinWith(['TableD'])
->andWhere([(further possible conditions on the other Tables)])
->all()

You can modify the condition that is used for joining the tables.
In SQL it would look like this:
SELECT
*
FROM
`tableA`
JOIN `tableB` ON (
`tableA`.`FK` = `tableB`.`PK`
AND `tableA`.`attr1` = 'someValue'
)
JOIN tableC ON (`tableB`.`FK` = `tableC`.`PK`)
To do that in Yii you can use ActiveQuery::onCondition() method. If you want to apply this condition only for this one query you can use callback in joinWith() method to modify the query used for join.
$query = TableA::find()
->joinWith([
'tableB' => function(\yii\db\ActiveQuery $query) {
$query->onCondition(['tableA.attr1' => 'someValue']);
}
])
//... the rest of query
Other option would be using a subquery in FROM part of sql query like this:
SELECT
*
FROM
(
SELECT
*
FROM
`tableA`
WHERE
`attr1` = 'someValue'
) AS `tableA`
JOIN `tableB` ON (`tableA`.`FK` = `tableB`.`PK`)
In yii:
$subQuery = TableA::find()
->select('*')
->where(['attr1' => 'someValue']);
$query = TableA::find()
->select('*')
->from(['tableA' => $subQuery])
->joinWith('tableB')
// ... the rest of query
The main weakness of this approach is that temporary table from your subquery won't have any indexes so the join and other conditions will be slower. It might still be worth to use this approach if the tableA has a lot of rows and the condition you want to apply before join will reduce the number of rows significantly.

Well, I think that's the best way at all. But if you don't print data from relation TableB or TabelC (you just get them only about where condition), you can set the relation like that:
TableA::find()
->joinWith('TableB', false) //dont load data from this relational table
->joinWith('TableC', false) //dont load data from this relational table
->joinWith(['TableD']) //load data from this relational table
->where([TableA.attribute1=1 or TableA.attribute2=1])
->andWhere([(further possible conditions on the other Tables)])
->all()

Related

BigQuery : WITH clause behavior in multiple JOIN conditions

For readability, I have defined "org_location_ext" clause in the query as follows.
This "org_location_ext" is first used to join with the main fact-table "LOCATION_SALES".
It is used in other JOIN conditions as well.
According to the BigQuery documentation : https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#with_clause
The WITH clause contains one or more named subqueries which execute
every time a subsequent SELECT statement references them
I want to know the behavior for this case.
Does this query executes the "org_location_ext" WITH clause multiple times ?
Or when the SELECT query gets executed, a temporary table is created for "org_location_ext" and use this temporary table for all the JOINs.
Basically, after the first JOIN with the fact-table , later joins use that "filtered" result for their joins , or do they rerun the WITH clause ?
WITH org_location_ext AS (
SELECT *
FROM ORG_LOC_MASTER AS loc_master
JOIN LOC_REGN1 as regn1 ON loc_master.id = regn1.id
JOIN ...
JOIN ...
)
SELECT
..
org_location_ext.store_class,
org_location_ext.country,
org_location_ext.
..
..
FROM LOCATION_SALES AS sales
JOIN org_location_ext ON org_location_ext.area_id = sales.area_id AND org_location_ext.date = sales.date
JOIN ....
JOIN ....
JOIN COUNTRY_VAT AS vat ON vat.key1 =TBL_Y.key1 AND vat.country_code = org_location_ext.country_code
It depends on the query plan. Consider checking a query plan. You'll see how many times any specific table is accessed.

SQL Query to Cdbcriteria

I have a SQL query like this
SELECT *
FROM nu.tb_class t
WHERE NOT EXISTS (SELECT st_id FROM student_class s WHERE s.st_id = t.id)
I need this to put it Cdbcriteria can any tell me how to do this ?
(somehow I need to put this data to CGridView if there is any other way I like to know it)
You can use the NotInCondition in your Criteria
http://www.yiiframework.com/doc/api/1.1/CDbCriteria#addNotInCondition-detail
Note: that it takes an array, that means you have to select only the st_id column, and pass it to the addNotInCondition function ... the easy way to do this is some thing like:
$st_ids = CHtml::listData(StudentClass::model()->findAll($criteria)), 'column1', 'column2');
And for sure you can run it as sql query, then use the CArrayDataProvider
http://www.yiiframework.com/doc/guide/1.1/en/database.dao#executing-sql-statements
http://www.yiiframework.com/doc/api/1.1/CArrayDataProvider
You can convert that query to an equivalent LEFT JOIN query:
$classes = TbClass::model()->findAll(array(
'condition' => 's.st_id IS NULL',
'join' => 'LEFT JOIN student_class s ON t.id=s.st_id',
));
Notice that you can pass the properties of a CDbCriteria to findAll().

Rails ActiveRecord where or clause

I'm looking to write an ActiveRecord query and this is what I have below. Unfortunately you can't use OR like this. What's the best way to execute? category_ids is an array of integers.
.where(:"categories.id" => category_ids).or.where(:"category_relationships.category_id" => category_ids)
One way is to revert to raw sql...
YourModel.where("categories.id IN ? OR category_relationships.category_id IN ?", category_ids, category_ids)
Keep the SQL out of it and use ARel, like this:
.where(Category.arel_table[:id].in(category_ids).
or(CategoryRelationship.arel_table[:category_id].in(category_ids))
Assuming you want to return Categories, you need to OUTER JOIN category_relationships and put a OR condition on the combined table.
Category.includes(:category_relationships).where("categories.id IN (?) OR category_relationships.category_id IN (?)",category_ids,category_ids )
This query is creating an outer join table by combining columns of categories and category_relationships. Unlike an inner join (e.g. Category.joins(:category_relationships)), outer join table would also have categories with no associated category_relationship. It would then apply the conditions in whereclause on the outer join table to return the matching records.
includes statement without conditions on the association usually makes two separate sql queries to retrieve the records and their association. However when used with conditions on the associated table, it would make a single query to create an outer join table and run conditions on the outer join table. This allows you to retrieve records with no association as well.
See this for a detailed explanation.
What you want to do is manually write the OR part of the query like this:
.where("category.id in (#{category_ids.join(',')}) OR category_relationships.category_id in (#{category_ids.join(',')})")

Nhibernate and a large list of ids in a WHERE in restriction

I am perfomring a QueryOver query, and have a restriction cluase that effectively does a WHERE IN cluase.
If I will have thousands of results coming back from that inner query, this will obviously be a slow query right?
public List<SomeEntity> GetByIds(List<Guid> listOfIds)
{
return NHibernateHelper.Session.QueryOver<SomeEntity>()
.WhereRestrictionOn(x => x.id).IsIn(listOfIds)
.List();
}
Is it possible to convert this into a INNER JOIN somehow?
You can convert it to a sub-query or inner join. Your restriction is on the number of parameters you can add to a query in sql server, which is about 2.2 k parameters. The way your current code works you should split the parameters in chunks from about 2k and add them to a result list.
if listOfIds comes from the same database then only when there is a realtionship, otherwise you have to use sql.
if listOfIds is large and doesnt come from the same source then this might be faster
Session.CreateSqlQuery("CREATE TEMP TABLE temp (Id)").ExecuteUpdate();
foreach (var id in listofids)
{
Session.CreateSqlQuery("INSERT INTO temp (?)").SetParam(id).ExecuteUpdate();
}
Session.CreateSqlQuery("CREATE INDEX for temp (Id)").ExecuteUpdate();
return NHibernateHelper.Session.QueryOver<SomeEntity>()
.UnderlyingCriteria.Add(Expression.Sql("0 < (SELECT Count(*) FROM temp t WHERE t.id = Id)"))
.List<SomeEntity>();

How to implement paging in NHibernate with a left join query

I have an NHibernate query that looks like this:
var query = Session.CreateQuery(#"
select o
from Order o
left join o.Products p
where
(o.CompanyId = :companyId) AND
(p.Status = :processing)
order by o.UpdatedOn desc")
.SetParameter("companyId", companyId)
.SetParameter("processing", Status.Processing)
.SetResultTransformer(Transformers.DistinctRootEntity);
var data = query.List<Order>();
I want to implement paging for this query, so I only return x rows instead of the entire result set.
I know about SetMaxResults() and SetFirstResult(), but because of the left join and DistinctRootEntity, that could return less than x Orders.
I tried "select distinct o" as well, but the sql that is generated for that (using the sqlserver 2008 dialect) seems to ignore the distinct for pages after the first one (I think this is the problem).
What is the best way to accomplish this?
In these cases, it's best to do it in two queries instead of one:
Load a page of orders, without joins
Load those orders with their products, using the in operator
There's a slightly more complex example at http://ayende.com/Blog/archive/2010/01/16/eagerly-loading-entity-associations-efficiently-with-nhibernate.aspx
Use SetResultTransformer(Transformers.AliasToBean()) and get the data that is not the entity.
The other solution is that you change the query.
As I see you're returning Orders that have products which are processing.
So you could use exists statement. Check nhibernate manual at 13.11. Subqueries.