CodeIgniter - Grouping where clause - sql

I have this following query for CodeIgniter:
$q = $this->db->where('(message_from="'.$user_id.'" AND message_to="'.$this->auth_model->userdata['user_id'].'")')
->or_where('(message_from="'.$this->auth_model->userdata['user_id'].'" AND message_to="'.$user_id.'")')
->get('messages');
I want to write this query with completely active record.
I have tried something like this:
$from_where = array('message_from'=>$user_id, 'message_to'=>$this->auth_model->userdata['user_id']);
$to_where = array('message_from'=>$this->auth_model->userdata['user_id'],'message_to'=>$user_id);
$q = $this->db->where($from_where)
->or_where($to_where)
->get('messages');
die($this->db->last_query());
The above code produces this query:
SELECT * FROM (`messages`) WHERE `message_from` = '2' AND `message_to` = '1' OR `message_from` = '1' OR `message_to` = '2'
But this is what I want to produce:
SELECT * FROM (`messages`) WHERE (message_from="2" AND message_to="1") OR (message_from="1" AND message_to="2")
There are similar questions here and here, but thosedid not provide a real solution for me.
How's this possible, If not via core libraries, is there an extension which allows writing such queries?
Thanks,

You can use sub query way of codeigniter to do this for this purpose you will have to hack codeigniter. like this
Go to system/database/DB_active_rec.php Remove public or protected keyword from these functions
public function _compile_select($select_override = FALSE)
public function _reset_select()
Now subquery writing in available And now here is your query with active record
$this->db->where('message_from','2');
$this->db->where('message_to','1');
$subQuery1 = $this->db->_compile_select();
$this->db->_reset_select();
$this->db->where('message_from','1');
$this->db->where('message_to','2');
$subQuery2 = $this->db->_compile_select();
$this->db->_reset_select();
$this->db->select('*');
$this->db->where("$subQuery1");
$this->db->or_where("$subQuery2");
$this->db->get('messages');
Look at this answer of mine. This shows how to use sub queries. This will help
Using Mysql WHERE IN clause in codeigniter
EDITES
Yes i have done it
Rewrite the query this way exactly you want
$this->db->where('message_from','2');
$this->db->where('message_to','1');
$subQuery1 = $this->db->_compile_select(TRUE);
$this->db->_reset_select();
$this->db->where('message_from','1');
$this->db->where('message_to','2');
$subQuery2 = $this->db->_compile_select(TRUE);
$this->db->_reset_select();
$this->db->select('*');
$this->db->where("($subQuery1)");
$this->db->or_where("($subQuery2)");
$this->db->get('messages');
Compile select is with true parameter. Will not produce select clause. This will produce
SELECT * FROM (`messages`) WHERE (`message_from` = '2' AND `message_to` = '1') OR (`message_from` = '1' AND `message_to` = '2')

Related

QueryBuilder - IN expression

I've created a quiz and I record in DB if people answered right to all question and the time they take to finish the quiz.
I'm trying to create a querybuilder to retrieve the guy who answered correct to the maximum of questions with the minimum of time.
My table looks like this :
So, the request (which works) I did in SQL in the DB is :
SELECT
id
FROM
public.user_quizz
WHERE
quizz_id = 4
AND
number_correct_answers IN (SELECT max(number_correct_answers) FROM user_quizz WHERE quizz_id = 4)
AND
answered_in IN (SELECT min(answered_in) FROM user_quizz WHERE quizz_id = 4);
Of course, I don't know if it's the best (and the most optimal) request we could do in this case, but it works.
Now, I'm trying to translate this query into querybuilder.
I'm blocked on the IN expression. I don't know how I could do the SELECT here.
$qb = $this->createQueryBuilder('u');
$query = $qb->select('u')
->andWhere(
$qb->expr()->eq('u.quizz', ':quizzId'),
$qb->expr()->in(
'u.numberCorrectAnswers',
)
)
->setParameter('quizzId', $quizz->getId())
->getQuery()
;
Thanks for your help.
$qbSelectMax = $this->createQueryBuilder('uc') // user copy, to prevent alias collisions
$qbSelectMax
->select($qb->expr()->max('uc.numberCorrectAnswers'))
->where($qb->expr()->eq('uc.quizz', ':quizzId'));
$qb = $this->createQueryBuilder('u')
$query = $qb->select('u')
->andWhere(
$qb->expr()->eq('u.quizz', ':quizzId'),
$qb->expr()->in(
'u.numberCorrectAnswers',
$qbSelectMax->getDQL()
)
)
->setParameter('quizzId', $quizz->getId())
->getQuery();
You can create sub DQL query to select max numberCorrectAnswers first and then pass DQL right into in parameter
ORDER BY could be used to sort by number of answers and less time taken:
$qb = $this->createQueryBuilder('u')
->orderBy('u.numberCorrectAnswers', 'DESC')
->addOrderBy('u.answeredIn', 'ASC');

How to select from subquery using Laravel Query Builder?

I'd like to get value by the following SQL using Eloquent ORM.
- SQL
SELECT COUNT(*) FROM
(SELECT * FROM abc GROUP BY col1) AS a;
Then I considered the following.
- Code
$sql = Abc::from('abc AS a')->groupBy('col1')->toSql();
$num = Abc::from(\DB::raw($sql))->count();
print $num;
I'm looking for a better solution.
Please tell me simplest solution.
In addition to #delmadord's answer and your comments:
Currently there is no method to create subquery in FROM clause, so you need to manually use raw statement, then, if necessary, you will merge all the bindings:
$sub = Abc::where(..)->groupBy(..); // Eloquent Builder instance
$count = DB::table( DB::raw("({$sub->toSql()}) as sub") )
->mergeBindings($sub->getQuery()) // you need to get underlying Query Builder
->count();
Mind that you need to merge bindings in correct order. If you have other bound clauses, you must put them after mergeBindings:
$count = DB::table( DB::raw("({$sub->toSql()}) as sub") )
// ->where(..) wrong
->mergeBindings($sub->getQuery()) // you need to get underlying Query Builder
// ->where(..) correct
->count();
Laravel v5.6.12 (2018-03-14) added fromSub() and fromRaw() methods to query builder (#23476).
The accepted answer is correct but can be simplified into:
DB::query()->fromSub(function ($query) {
$query->from('abc')->groupBy('col1');
}, 'a')->count();
The above snippet produces the following SQL:
select count(*) as aggregate from (select * from `abc` group by `col1`) as `a`
The solution of #JarekTkaczyk it is exactly what I was looking for. The only thing I miss is how to do it when you are using
DB::table() queries. In this case, this is how I do it:
$other = DB::table( DB::raw("({$sub->toSql()}) as sub") )->select(
'something',
DB::raw('sum( qty ) as qty'),
'foo',
'bar'
);
$other->mergeBindings( $sub );
$other->groupBy('something');
$other->groupBy('foo');
$other->groupBy('bar');
print $other->toSql();
$other->get();
Special atention how to make the mergeBindings without using the getQuery() method
From laravel 5.5 there is a dedicated method for subqueries and you can use it like this:
Abc::selectSub(function($q) {
$q->select('*')->groupBy('col1');
}, 'a')->count('a.*');
or
Abc::selectSub(Abc::select('*')->groupBy('col1'), 'a')->count('a.*');
There are many readable ways to do these kinds of queries at the moment (Laravel 8).
// option 1: DB::table(Closure, alias) for subquery
$count = DB::table(function ($sub) {
$sub->from('abc')
->groupBy('col1');
}, 'a')
->count();
// option 2: DB::table(Builder, alias) for subquery
$sub = DB::table('abc')->groupBy('col1');
$count = DB::table($sub, 'a')->count();
// option 3: DB::query()->from(Closure, alias)
$count = DB::query()
->from(function ($sub) {
$sub->from('abc')
->groupBy('col1')
}, 'a')
->count();
// option 4: DB::query()->from(Builder, alias)
$sub = DB::table('abc')->groupBy('col1');
$count = DB::query()->from($sub, 'a')->count();
For such small subqueries, you could even try fitting them in a single line with PHP 7.4's short closures but this approach can be harder to mantain.
$count = DB::table(fn($sub) => $sub->from('abc')->groupBy('col1'), 'a')->count();
Note that I'm using count() instead of explicitly writing the count(*) statement and using get() or first() for the results (which you can easily do by replacing count() with selectRaw(count(*))->first()).
The reason for this is simple: It returns the number instead of an object with an awkwardly named property (count(*) unless you used an alias in the query)
Which looks better?
// using count() in the builder
echo $count;
// using selectRaw('count(*)')->first() in the builder
echo $count->{'count(*)'};
Correct way described in this answer: https://stackoverflow.com/a/52772444/2519714
Most popular answer at current moment is not totally correct.
This way https://stackoverflow.com/a/24838367/2519714 is not correct in some cases like: sub select has where bindings, then joining table to sub select, then other wheres added to all query. For example query:
select * from (select * from t1 where col1 = ?) join t2 on col1 = col2 and col3 = ? where t2.col4 = ?
To make this query you will write code like:
$subQuery = DB::query()->from('t1')->where('t1.col1', 'val1');
$query = DB::query()->from(DB::raw('('. $subQuery->toSql() . ') AS subquery'))
->mergeBindings($subQuery->getBindings());
$query->join('t2', function(JoinClause $join) {
$join->on('subquery.col1', 't2.col2');
$join->where('t2.col3', 'val3');
})->where('t2.col4', 'val4');
During executing this query, his method $query->getBindings() will return bindings in incorrect order like ['val3', 'val1', 'val4'] in this case instead correct ['val1', 'val3', 'val4'] for raw sql described above.
One more time correct way to do this:
$subQuery = DB::query()->from('t1')->where('t1.col1', 'val1');
$query = DB::query()->fromSub($subQuery, 'subquery');
$query->join('t2', function(JoinClause $join) {
$join->on('subquery.col1', 't2.col2');
$join->where('t2.col3', 'val3');
})->where('t2.col4', 'val4');
Also bindings will be automatically and correctly merged to new query.
I like doing something like this:
Message::select('*')
->from(DB::raw("( SELECT * FROM `messages`
WHERE `to_id` = ".Auth::id()." AND `isseen` = 0
GROUP BY `from_id` asc) as `sub`"))
->count();
It's not very elegant, but it's simple.
This works fine
$q1 = DB::table('tableA')->groupBy('col');
$data = DB::table(DB::raw("({$q1->toSql()}) as sub"))->mergeBindings($q1)->get();
I could not made your code to do the desired query, the AS is an alias only for the table abc, not for the derived table.
Laravel Query Builder does not implicitly support derived table aliases, DB::raw is most likely needed for this.
The most straight solution I could came up with is almost identical to yours, however produces the query as you asked for:
$sql = Abc::groupBy('col1')->toSql();
$count = DB::table(DB::raw("($sql) AS a"))->count();
The produced query is
select count(*) as aggregate from (select * from `abc` group by `col1`) AS a;
->selectRaw('your subquery as somefield')
Deriving off mpskovvang's answer, here is what it would look like using eloquent model. (I tried updating mpskovvang answer to include this, but there's too many edit requests for it.)
$qry = Abc::where('col2', 'value')->groupBy('col1')->selectRaw('1');
$num = Abc::from($qry, 'q1')->count();
print $num;
Produces...
SELECT COUNT(*) as aggregate FROM (SELECT 1 FROM Abc WHERE col2='value' GROUP BY col1) as q1

Zend-framework Query TOP

Hello i try generate a query in Zend 2 just like this
select top 10 * from mensaje where idUsuario = 11 order by fechaAltaMensaje DESC
i try use this
$select = $sql->select();
$select->from('mensaje');
$select->where('idUsuario = '.$idUser.' order by fechaAltaMensaje DESC');
$select->limit(5);
but don't work
You are missing some details in your code in order for it to work,
please see below.
$adapter = $this->tableGateway->getAdapter();//use Zend\Db\TableGateway\TableGateway;
$sql = new Sql($adapter);//use Zend\Db\Sql\Sql;
$select = $sql->select();
$select->from('mensaje');
$select->where('idUsuario = '.$idUser.'');
$select->order('fechaAltaMensaje DESC');
$select->limit(5);
$selectString = $sql->getSqlStringForSqlObject($select);//print_r($selectString);die; //gives you the query in string
$results = $adapter->query($selectString, $adapter::QUERY_MODE_EXECUTE);
$resultSet = new ResultSet();//use Zend\Db\ResultSet\ResultSet;
$resultSet->initialize($results);
return $resultSet->toArray();//the result to array
Please read the tutorials below and you will get the full picture
Examples
Examples 2
The limit function only applies to platforms that support it. To achieve what you're after in SQL you need to use the quantifier function.
Also - where accepts an array of column => value pairs.
And there is an order function that accepts a column name and direction:
$select = $sql->select();
$select->from('mensaje')
->where(['idUsuario' => $idUser])
->order('fechaAltaMensaje DESC')
->quantifier('TOP(5)')
I am not pleased with Zends implementation of the sql abstraction layer, when you need to use two different functions to write SQL that is not cross platform to do simple things like limit or top. That's just my two pence.

sql select with one value of two where

This is the only place that I get all answer ;)
I want to select :
SELECT
RTRIM(LTRIM(il.Num_bloc)) AS Bloc,
RTRIM(LTRIM(il.num_colis)) AS Colis,
cd.transporteur AS Coursier,
cd.origine AS Origine,
cd.destination AS Destinataire,
cd.adresse AS [Adresse Destinataire],
cd.poids AS Poids,
il.Signataire, il.num_cin AS CIN, il.date_livraison AS [Date Livraison]
FROM
dbo.cd
INNER JOIN
dbo.il ON cd.bloc = il.Num_bloc AND dbo.cd.colis = dbo.il.num_colis
WHERE
(il.Num_bloc = RTRIM(LTRIM(#ParamBloc)))
AND (il.num_colis = RTRIM(LTRIM(#ParamColis)))
In the way of getting result if the user put ether #ParamBloc or #ParamColis
Try using IsNull() function.
A simple query would go like this
Select * from yourTable
Where
Num_bloc = ISNULL(#ParamBloc, Num_block) AND
num_colis = ISNULL(#ParamColis, num_colis)
The second parameter would make the expression to true if the #parameter Bloc or Colis is null. This query would be useful for all 4 possible combination of these two parameter.

SELECT MAX query returns only 1 variable + codeigniter

I use codeigniter and have an issue about SELECT MAX ... I couldnot find any solution at google search...
it looks like it returns only id :/ it's giving error for other columns of table :/
Appreciate helps, thanks!
Model:
function get_default()
{
$this->db->select_max('id');
$query = $this->db->getwhere('gallery', array('cat' => "1"));
if($query->num_rows() > 0) {
return $query->row_array(); //return the row as an associative array
}
}
Controller:
$default_img = $this->blabla_model->get_default();
$data['default_id'] = $default_img['id']; // it returns this
$data['default_name'] = $default_img['gname']; // it gives error for gname although it is at table
To achieve your goal, your desire SQL can look something like:
SELECT *
FROM gallery
WHERE cat = '1'
ORDER BY id
LIMIT 1
And to utilise CodeIgniter database class:
$this->db->select('*');
$this->db->where('cat', '1');
$this->db->order_by('id', 'DESC');
$this->db->limit(1);
$query = $this->db->get('gallery');
That is correct: select_max returns only the value, and no other column. From the specs:
$this->db->select_max('age');
$query = $this->db->get('members');
// Produces: SELECT MAX(age) as age FROM members
You may want to read the value first, and run another query.
For an id, you can also use $id = $this->db->insert_id();
See also: http://www.hostfree.com/user_guide/database/active_record.html#select
CodeIgniter will select * if nothing else is selected. By setting select_max() you are populating the select property and therefore saying you ONLY want that value.
To solve this, just combine select_max() and select():
$this->db->select('somefield, another_field');
$this->db->select_max('age');
or even:
$this->db->select('sometable.*', FALSE);
$this->db->select_max('age');
Should do the trick.
It should be noted that you may of course also utilize your own "custom" sql statements in CodeIgniter, you're not limited to the active record sql functions you've outlined thus far. Another active record function that CodeIgniter provides is $this->db->query(); Which allows you to submit your own SQL queries (including variables) like so:
function foo_bar()
{
$cat = 1;
$limit = 1;
$sql = "
SELECT *
FROM gallery
WHERE cat = $cat
ORDER BY id
LIMIT $limit
";
$data['query'] = $this->db->query($sql);
return $data['query'];
}
Recently I have been utilizing this quite a bit as I've been doing some queries that are difficult (if not annoying or impossible) to pull off with CI's explicit active record functions.
I realize you may know this already, just thought it would help to include for posterity.
2 helpful links are:
http://codeigniter.com/user_guide/database/results.html
http://codeigniter.com/user_guide/database/examples.html