How to use subquery in the join function of Yii framework 2 ActiveRecord? - sql

Below is my pure SQL query.
SELECT a.*, b.*
FROM a
INNER JOIN b
ON a.id = b.a_id
INNER JOIN (
SELECT a_id, MAX(add_time) AS max_add_time
FROM b
GROUP BY a_id
) m
ON b.a_id = m.a_id AND b.add_time = m.max_add_time
ORDER BY b.add_time DESC
I have the subquery in the second INNER JOIN. Below my active query.
$subQuery = B::find()->select(['a_id', 'MAX(add_time) AS max_add_time'])->groupBy('a_id');
$query = A::find()->innerJoin('b', 'a.id = b.a_id')
->innerJoin('(' .
$subQuery->prepare(Yii::$app->db->queryBuilder)
->createCommand()
->rawSql
. ') m', 'b.a_id = m.a_id AND a.add_time = m.max_add_time ')
->orderBy('b.add_time DESC');
It works fine, but I do not like the way I use the subquery in the second INNER JOIN. What I want to approach with this query is to select the left table inner join with right table, group by a_id and order by the add_time (DESC) of the right table. How should I better use the subquery in the second INNER JOIN?

The snippet below is untested but it should be something like that. If you read the docs (at http://www.yiiframework.com/doc-2.0/yii-db-query.html#innerJoin()-detail) you can see an array with a subquery is also valid input, with the key being the alias.
$subQuery = B::find()
->select(['a_id', 'MAX(add_time) AS max_add_time'])
->groupBy('a_id');
$query = A::find()
->innerJoin('b', 'a.id = b.a_id')
->innerJoin(['m' => $subQuery], 'b.a_id = m.a_id AND a.add_time = m.max_add_time')
->orderBy('b.add_time DESC');

Having created the join, if you need to use any of the columns returned by the subQuery, you need to add properties to the Yii2 model class, e.g.
$subQuery = FinancialTransaction::find()
->select( new \yii\db\Expression( 'SUM(amount) as owing') )
->addSelect('booking_id')
->groupBy('booking_id');
$query = $query
->addSelect(['b.*', 'owing'])
->leftJoin(['ft' => $subQuery], 'b.booking_display_id = ft.booking_id');
To access "owing" the model has to have the property (PHPdoc optional):
/**
* #var float
*/
public $owing=0;

Related

Laravel query builder join using either one of two conditions

I have a complex query that I want to use either Query Builder or Eloquent (preferred) but I'm struggling with an inner join.
The inner join needs to be one of either of 2 conditions so if one fails, the other is used.
This is my original query
SELECT DISTINCT tableA.crmid, tableB.*
FROM tableB
INNER JOIN tableA ON tableA.crmid = tableB.customaccountsid
INNER JOIN tableC ON (tableC.relcrmid = tableA.crmid OR tableC.crmid = tableA.crmid)
WHERE tableA.deleted = 0 AND tableC.relcrmid = 123 AND tableC.relation_id = 186
This is my attempt at using Query Builder and I know where the problem lies. It's where I join tableC. I don't know how to use my condition there
DB::table('tableB')
->join('tableA', 'tableA.crmid', '=', 'tableB.customaccountsid')
->join('tableC', function($join) {
$join->on(DB::raw('(tableC.relcrmid = tableA.crmid OR tableC.crmid = tableA.crmid)'));
})
->where('tableA.deleted', 0)
->where('tableC.relcrmid', 3727)
->where('tableC.relation_id', 186)
->select('tableA.crmid', 'tableB.*')
Ant this is the output of the query when i output as SQL
SELECT `tableA`.`crmid`, `tableB`.*
FROM `tableB`
INNER JOIN `tableA` ON `tableA`.`crmid` = `tableB`.`customaccountsid`
INNER JOIN `tableC` ON `tableC`.`relcrmid` = (tableC.relcrmid = tableA.crmid OR tableC.crmid = tableA.crmid)
WHERE `tableA`.`deleted` = ? AND `tableC`.`relcrmid` = ? AND `tableC`.`relation_id` = ?
Just try this:
->join('tableC', function ($join){
$join->on(function($query){
$query->on('tableC.relcrmid', '=', 'tableA.crmid')
->orOn('tableC.crmid', '=', 'tableA.crmid');
});
})
It returns as in your original query:
INNER JOIN tableC ON (tableC.relcrmid = tableA.crmid OR tableC.crmid = tableA.crmid)

Converting nested SQL statement to LINQ

I need help to figure out how the following sql statement can be converted into a linq statement
SELECT distinct tableA.x, tableA.y, tableA.z
FROM tableA inner join
tableB on tableA.id = tableB.id inner join
tableC on tableA.id = tableC.id
WHERE (tableB.columnOne IN (SELECT tableX.columnOne
FROM tableX INNER JOIN
tableY ON tableX.xId = tableY.xId
WHERE (tableY.xId = tableC.xId) )
AND (tableB.columnTwo IN (SELECT tableXx.columnTwo
FROM tableXx INNER JOIN
tableYy ON tableXx.XxId = tableYy.XxId
WHERE tableYy.XxId =tableC.XxId)))
)
I suppose the first thing to point out is that you can do sub queries in LINQ. Within your main Where clause, you could probably create queries for your subqueries, (which would result in IQueryable types) and then use .Any() to predict if there are any matches e.g.
var tableXquery = {my tableX subquery};
var tableXxquery = {my tableXx subquery};
var result = context.tableB.where(b => tableXquery.any(x => x.columnOne == b.columnOne) && xx => tableXxquery.any(xx => xx.columnTwo == b.columnTwo));
I hope you get the idea - I just tend to put queries where they need to be, and let LINQ sort it out!

Rails Activerecord Relation: using subquery as a table for a SQL select statement

Can somebody help me figure out how to write the following SQL using Rails (I'm using Rails 4) Activerecord methods? I know you can do this with find_by_sql but I'd like to preserve the active record relation. Here's the sql for a postGreSQL db that I'm trying to create:
SELECT * FROM
(SELECT DISTINCT ON(table_a.id) table_a.name as alias_a, table_b.id, table_b.time
FROM table_1
LEFT OUTER JOIN table_b ON table_a.id = table_b.id
ORDER BY table_a.id, table_b.time asc) AS subquery
ORDER BY alias_a asc
For my subquery, I have the following (which generates the sql of the subquery above):
#subquery = table_a.select("DISTINCT ON(table_a.id) table_a.name as alias_a, table_b.time")
#subquery = #subquery.joins("LEFT OUTER JOIN table_b ON table_a.id = table_b.id")
#subquery = #subquery.order("table_a.id, table_b.time asc")
But, I can't figure out how to write a select statement that uses #subquery as the table for the outer select statement.
Use the from() method from the Active Record interface.
For example:
#subquery = table_a.select("DISTINCT ON(table_a.id) table_a.name as alias_a, table_b.time")
#subquery = #subquery.joins("LEFT OUTER JOIN table_b ON table_a.id = table_b.id")
#subquery = #subquery.order("table_a.id, table_b.time asc")
Then use it like this in the outer query:
#query = OtherModel.from("(#{#subquery.to_sql}) table_name, other_model_table, etc ...").where(:field => table_name.alias_a) ...etc.
This one is more elegant:
#subquery = Model.balalalala
#query = OtherModel.from(#subquery, :sub_query).where(sub_query: {column_b: balalala}).order('sub_query.column_a')

Dynamically setting the IDMultilingual value by using select

In my INNER JOIN clause, I want to set the column IDMultilingual value dynamically by using a select statement. Must I use the IN clause, and may I use EXISTS clause?
INNER JOIN MLTA ON MLTA.IDObject = MSR.idobject
AND MLTA.IDObjectType = 15
AND MLTA.IDMultilingual = SELECT ID
FROM MLA
WHERE Columnname = 'SHOW'
AND Tablename = 'Category'
Add an extra JOIN to the same table MLTA with a new alias. Something like:
...
INNER JOIN MLTA m ON m.IDObject = MSR.idobject
AND m.IDObjectType = 15
INNER JOIN MLTA m2 ON m.IDMultilingual = m2.ID ---<<<<<<<<<<<<<<<<<<<<<<<
....
However, you can use the IN predicate like this:
....
INNER JOIN MLTA ON MLTA.IDObject = MSR.idobject
AND MLTA.IDObjectType = 15
WHERE MLTA.IDMultilingual IN(SELECT ID
from MLA
WHERE Columnname = 'SHOW'
AND Tablename = 'Category');
or the EXISTS predicate the same way, and it might be safer than the IN predicate in case of the NULL values of the column ID.

Join with Zend_Db, without having the columns of the joined tables

I use Zend_Db_Select to perform a query with a Join. I end up with the following SQL query :
SELECT `Utilisateur`.*, `Ressource`.*, `Acl_Cache`.*, `Role`.*, `UtilisateurRole`.* FROM `Utilisateur`
INNER JOIN `Ressource` ON Ressource.idJointure = Utilisateur.id
INNER JOIN `Acl_Cache` ON Acl_Cache.idRessource = Ressource.id
INNER JOIN `Role` ON Role.id = Acl_Cache.idRole
INNER JOIN `UtilisateurRole` ON UtilisateurRole.idRole = Role.id
WHERE (Ressource.tableJointure = 'Utilisateur') AND (UtilisateurRole.idUtilisateur = '2')
Why is Zend_Db adding this part is the SELECT clause :
, `Ressource`.*, `Acl_Cache`.*, `Role`.*, `UtilisateurRole`.*
I never asked this, and I don't want that. How to prevent this behavior ?
$db->select()
->from(array('alias1'=>'table_i_want_all_cols_on'))
->joinLeft(array('alias2'=>'table_i_want_no_cols_on'),
'alias1.id = alias2.id',
array()
)
->joinLeft(array('alias3'=>'table_i_want_some_cols_on'),
'alias3.id = alias1.id',
array('col1', 'col2')
);