This is my problem: MySQL "Or" Condition
The solution is to group the OR statements, but i'm using CodeIgniters Active Record. Is there a way to group OR statements using Active Record? Or do I have to write the query myself?
I'm using $this->db->where() with $this->db->or_where()
It writes the query like this:
WHERE title = 'foobar' AND category = 2 OR category = 3
but what I need is:
WHERE title = 'foobar' AND (category = 2 OR category = 3)
I can't do this:
$this->db->where("title = 'foobar' AND (category = 2 OR category = 3)");
because i'm adding ORs using a foreach loop
You can do it manually as stated here:
Custom string: You can write your own clauses manually:
$where = "name='Joe' AND status='boss' OR status='active'";
$this->db->where($where);
As for your question:
$this->db->where("category = 1 AND (category = 2 OR category = 3)");
In 3.0-dev:
$this->db->select()
->group_start()
->or_like([ 'category' => 2, 'category' => 3 ])
->group_end()
->where([ 'category' => 1 ]);
update
See the answers on this question if you're using CI 2.2. Choose an answer other than the accepted.
Or simply try this:
$categories = array(2, 3);
array_walk($categories, function(&$cat) { $cat = 'category = ' . $cat; });
$catstring = implode(" OR ", $categories);
$where = "category = 1 AND ($catstring)";
// => category = 1 AND (category = 2 OR category = 3)
$this->db->where($where);
as OP mentioned you are generating OR using foreach (array)
you can simply use
$cat_array=array(2,3,4,5,6,7);
$this->db->select('col1, col2')->from('table')->where("col1",1)->where_in('col2', $cat_array);
will generate
SELECT `col1`, `col2` FROM (`table`) WHERE `col1` = 1 AND `col2` IN (2, 3, 4, 5, 6, 7)
you can't manipulate ground condition query using DB active record methods (where, or_where) , i would recommond to pass as sting in where() method
$this->db->where("category = 1 AND (category = 2 OR category = 3)");
In the Code Igniter Version 3.0-dev you can add groups in any query using group_start and group_end :
$this->db->select('col1, col2')
->where("category = 1")
->group_start()
->where("category = 2")
->or_where("category = 3")
->group_end();
Produces:
SELECT `col1`, `col2`
FROM `table_name`
WHERE category = 1 AND (category = 2 OR category = 3);
Related
This question already has answers here:
How to create a MySQL hierarchical recursive query?
(16 answers)
Closed 4 years ago.
I have a MYSQL table called collections when viewed and implemented as a table could be something like this:
I needed to know whether one mysql query will be able to get all the products under a collection type entry (a given) which could have collections under it. For example, if I select 10, it should return 14, 12, 13, and 15.
I implemented a solution that involves a do..while loop...
$concatted = 10;
$products = [];
do {
$sql = "SELECT id, type FROM collections WHERE parent IN ($id_concatted)";
$result = $mysqli->query($sql);
if($result) {
while($row = $result->fetch_object()){
if($row->type == 'product') {
apply_changes_to_product($row->id);
} elseif ($row->type=='collection'){
$collections[] = $row->id;
}
}
}
if(count($collections) > 0){
$id_concatted = implode($collections, ",");
$continue = true;
$collections = [];
} else {
$continue = false;
}
} while ($continue);
I think that the above code is not efficient. I think it is doable with one query but I don't know how.
UPDATE: I mark this as a duplicate of How to create a MySQL hierarchical recursive query although in that post there is NO accepted solution. I got myself this solution based on one reply there (Mysql 5.6):
SELECT id, `type` FROM (
select id, `type`
from (select * from collections
order by parent, id) products_sorted,
(select #pv := '10') initialisation
where find_in_set(parent, #pv)
and length(#pv := concat(#pv, ',', id))
) products
WHERE
products.`type` = 'product'
The fiddle is http://sqlfiddle.com/#!9/ea214f/2.
yes, you may need to use subquery and first fetch id where parent = selectedId and type = 'collection' and then select id where parent in the subquery id and type = 'product'
Like below:
SELECT id, type FROM collections WHERE parent IN (select id from collections where
parent = $id_concatted and type = 'collection') and type = 'product'
For Multiple level, Use Recursive feature of MySql. Like below:
WITH RECURSIVE COLLECTIONS_PRODUCTS (ID, TYPE, PATH)
AS
(
SELECT ID, TYPE, CAST(ID AS CHAR(200))
FROM COLLECTIONS
WHERE PARENT IN ($id_concatted)
UNION ALL
SELECT S.ID, S.TYPE, CONCAT(M.PATH, ",", S.ID)
FROM COLLECTIONS_PRODUCTS M JOIN COLLECTIONS S ON M.ID=S.PARENT
)
SELECT * FROM COLLECTIONS_PRODUCTS WHERE TYPE = 'product' ORDER BY PATH;
I am trying to refer the parent query fetched array in sub query of same statement. I have a news table and I want to get a specific news by its title and 10 more news which have id lower than that specific news. I want in one statement of Sql and i am php to fetch array.
<?php
// $_GET['q'] is title
include('db.php');
$result = array();
$sel = "SELECT * FROM news WHERE title = '".$_GET['q']."' "; // AND 10 MORE NEWS WHICH HAVE ID LOWER THAN THIS $_GET['q'] ID .
$qry = #mysqli_query($conn , $sel);
$num = mysqli_num_rows($qry);
while($row = #mysqli_fetch_array($qry)) {
array_push($result, array('id' => $row['id'] , 'title' => $row['title'] , 'desc' => $row['about'] , 'image' => $row['image'] , 'time' => $time , 'htitle' => $row['Htitle'] , 'habout' => $row['Habout']));
}
echo json_encode(array('result' => $result));
?>
Your original query is
SELECT * FROM news WHERE title = :title.
If you really want to use a subquery use something along the lines of
SELECT
*
FROM news
WHERE id <
(SELECT
id
FROM news
WHERE title = :title
LIMIT 1)
ORDER BY id DESC
LIMIT 10
A final note: PLEASE use parameters in your query, because you are WIDE OPEN to SQL injection (think about when $_GET['q'] has a value of ; DROP TABLE news;--).
$this->db->select('*');
$where = "(rank = '2')";
$this->db->where($where);
$this->db->from('deceased');
$this->db->join('causes', 'causes.id = deceased.id');
i have a query from codeigniter, and i should get the rank 2, but the problem is, if there is no rank 2 i should get the rank 1.
db_deceased (id primary key)
id date_died
1 2014-01-01
2 2014-03-19
db_causes (id foreign key)
id cause rank
1 J96.0 1
1 J44.1 2
2 I21 1
Probably you should try with $this->db->or_where(); either $this->db->or_where_in(); but also consider documentation's approach writing arguments there. Your $where variable should be like $where = array('name =' => '2');
$this->db->select('*');
$this->db->where('name =', '2');
$this->db->or_where('name =', '1');
$this->db->from('deceased');
$this->db->join('causes', 'causes.id = deceased.id');
Active records are pretty well explained in documentation.
I have an array with ids that I get from client. And I want use those ids in my sql query with IN clause. But this query goes on a table that has no model. So there is no active record (criteria) query possible.
** Table userTasks **
--------------------
| idUser | idTasks |
---------+----------
| 1 | 1 |
---------+----------
| 1 | 2 |
---------+----------
| 1 | 3 |
---------+----------
First approach does not work because params are always considered as strings. So :tasks is a string '1,2,3' instead of a comma separated list of ids:
$sql = 'SELECT COUNT(*) AS matches
FROM userTasks
WHERE idUser = :idUser
AND idTask IN (:tasks)';
$result = Yii::app()->db->createCommand($sql)
->queryRow(true,[
':idUser' => $idUser,
':tasks' => implode(',', $tasks)]); //$tasks is a simple array of ids [1,2,3]
So my workaround:
foreach($tasks as $task) //$tasks is a simple array of ids [1,2,3]
{
$inTasks[] = (int) $task;
}
$sql = 'SELECT COUNT(*) AS matches
FROM userTasks
WHERE idUser = :idUser
AND idTask IN (' . implode(',', $inTasks . ')';
$result = Yii::app()->db->createCommand($sql)
->queryRow(true,[':idUser' => $idUser]);
Having come across this problem a few times in my projects I have come-up with the following Yii work-around using CDbCriteria which is a little hacky, but gives the security of param count matching.
I would also use queryScalar() in this instance to get the result directly.
When applied to your example my code would be:
$idUser = 1;
$tasks = array(1,2,3);
$criteria = new CDbCriteria();
$criteria->addInCondition('idTask',$tasks);
$sql = '
SELECT COUNT(*) matches
FROM userTasks
WHERE idUser = :idUser
AND '.$criteria->condition;
$command = Yii::app()->db->createCommand($sql);
$command->bindValue('idUser',$idUser);
$command->bindValues($criteria->params);
$result = $command->queryScalar();
For preventing SQL injection in Yii IN clause we need to bind parameters in IN clause, Yii CDB criteria queries don't have this functionality in built. so you can use below code.
$products_ids = array(234,100,405,506);
map the array for binding
$in_query = implode(',', array_fill(0, count($products_ids), '?'));
Prepare the commadn object for select
$command = Yii::app()->db->createCommand()
->select('product_id, product_name, product_image, product_price')
->from('products')
->where('product_id IN(' . $in_query . ')');
bind the parameters
foreach ($products_ids as $k => $product_id){
$command->bindValue(($k+1),$product_id,PDO::PARAM_INT);
}
get the result
$products = $command->queryAll();
So there is this database table:
and this array with selected options:
$options[1] = 1;
$options[2] = 5;
$options[3] = 3;
$options[4] = 2;
$options[5] = 1;
...
$options[x] = y;
Now, the aim is to fetch all item_ids, where if there is an option_id in its row from one of the options array's keys, the value must be the same as the value in the options array.
For example:
option 1 has selected value 1
option 2 has selected value 5
option 3 has selected value 4
option 4 has selected value 2
so we should select item_id 1 and other item_ids, where if option 1 -> option 1 = 1 AND if option 2 -> option 2 = 5 AND if option 3 -> option 3 = 4 AND ...
The item_ids will be used in IN() to select the items data from the items table.
The main point is that the user selects some options on a page, then the options are put into array, then I must find all items that comply with the selected options. In the table above we have the relation between the items and the options, and the option values per item which are predefined.
$where = array();
foreach($options as $key => $value) $where[] = 'option_id = ' . $key . ' AND option_value = ' . $value;
$sql = 'SELECT DISTINCT item_id
FROM table
WHERE (' . explode(') OR (', $where) . ')';
I might have completely misunderstood your question however.
I think this might do it for you. Haven't tested it at all and it'll probably blow up, but...
SELECT *
FROM items
WHERE (item_id in (
SELECT item_id
FROM optiontable
WHERE ((option_id = 1) and (option_value = 1)) or
((option_id = 2) and (option_value=5)) or
((option_id = 3) and (option_value=3)) or
((option_id = 4) and (option_value=2)) or
((option_id = 5) and (option_value=1))
GROUP BY CONCAT(option_id, ',', option_value)
HAVING COUNT(CONCAT(option_id, ',', option_value)) = 5
));
Basically, the inner query pulls out all the rows that match one of the member rows in your required options row. It does an artificial grouping/count on the paired option_id/option_value pairs and returns the item_ids of the rows where the number of opt_id/opt_val pairs add up to 5 rows.
You'd have to build such a query dynamically in the client, so that the number of 'where' clause entries matches the number in the having clause.
<?php
$options = array(
1 => 1,
2 => 5,
3 => 3,
4 => 2,
5 => 1
);
$cases = array();
foreach($options as $id => $value){
$cases[] = "WHEN $id THEN $value";
}
$query =
'SELECT item_id '.
'FROM your_table '.
'WHERE option_value = CASE option_id '.implode(' ', $cases).' '.
'GROUP BY item_id';
echo $query;
?>
Output (formatted by me):
SELECT item_id
FROM your_table
WHERE option_value = CASE option_id
WHEN 1 THEN 1
WHEN 2 THEN 5
WHEN 3 THEN 3
WHEN 4 THEN 2
WHEN 5 THEN 1
GROUP BY item_id
Test this query and let me know if it works the way you expect it to. :)
Update: Suggestion for your final query
$query =
'SELECT * FROM items '.
'WHERE id IN('.
'SELECT item_id '.
'FROM your_table '.
'WHERE option_value = CASE option_id '.implode(' ', $cases).' '.
'GROUP BY item_id)';