I created a php function to fetch records from a sql table subscriptions, and I want to add a condition to mysql_query to ignore the records in table subscriptions that exists in table removed_items, here is my code;
function subscriptions_func($user_id, $limit){
$subs = array();
$sub_query = mysql_query("
SELECT `subscriptions`.`fo_id`, `subscriptions`.`for_id`, `picture`.`since`, `picture`.`user_id`, `picture`.`pic_id`
FROM `subscriptions`
LEFT JOIN `picture`
ON `subscriptions`.`fo_id` = `picture`.`user_id`
WHERE `subscriptions`.`for_id` = $user_id
AND `picture`.`since` > `subscriptions`.`timmp`
GROUP BY `subscriptions`.`fo_id`
ORDER BY MAX(`picture`.`since_id`) DESC
$limit
");
while ($sub_row = mysql_fetch_assoc($sub_query)) {
$subs [] = array(
'fo_id' => $sub_row['fo_id'],
'for_id' => $sub_row['for_id'],
'user_id' => $sub_row['user_id'],
'pic_id' => $sub_row['pic_id'],
'since' => $sub_row['since']
);
}
return $subs ;
}
My solution is to create another function to fetch the records from table removed_items and set a php condition where I call subscriptions_func() to skip/unset the records that resemble the records in subscriptions_func(), as the following
$sub = subscriptions_func($user_id);
foreach($sub as $sub){
$rmv_sub = rmv_items_func($sub[‘pic_id’]);
If($rmv_sub[‘pic_id’] != $sub[‘pic_id’]){
echo $sub[‘pic_id’];
}
}
This solution succeeded to skip the items in the table removed_items however this solution makes gaps in the array stored in the variable $sub which makes plank spots in the echoed items.
Is there a condition I can add to the function subscriptions_func() to cut all the additional conditions and checks?
Assuming id is the primary key of subscriptions and subs_id is the foreign key in removed_items, then you just have to add a condition to the WHERE clause. Something like this should work :
...
AND `subscriptions`.id NOT IN (SELECT `removed_items`.subs_id FROM `removed_items`)
...
Not related to your problem :
Your code seems vulnerable to SQL injection : use prepared statement to prevent this.
The original Mysql API is deprecated, it is highly recommended to switch to Mysqli instead.
I have the following criteria for a findAll statement
$with=array(
'tumor'=>array(
'select'=>false,
'joinType'=>'INNER JOIN',
),
'tumorLibraryType'=>array(
'select'=>false,
'joinType'=>'INNER JOIN',
'condition'=>'tumorLibraryType.id = 1 OR tumorLibraryType.id = 6',
),
'tumorPatient'=>array(
'select'=>false,
'joinType'=>'INNER JOIN',
)
);
$libPairs=LibraryPairs::model()->with($with)->findAll();
These are the relevant model relations:
'tumor' => array(self::BELONGS_TO, 'Libraries', array('tumor_library'=>'id')),
'normal' => array(self::BELONGS_TO, 'Libraries', array('normal_library'=>'id')),
// making separate AR routes for tumor and normal. only tumor used currently
'tumorLibraryType'=>array(self::HAS_ONE,'LibraryTypes','','on'=>'tumor.library_type_id = tumorLibraryType.id'),
'tumorLibrariesIsolates'=>array(self::HAS_MANY,'LibrariesIsolates',array('id'=>'library_id'),'through'=>'tumor'),
'tumorSamplesIsolates'=>array(self::HAS_MANY,'SamplesIsolates',array('isolate_id'=>'isolate_id'),'through'=>'tumorLibrariesIsolates'),
'tumorSamples'=>array(self::HAS_MANY,'Samples',array('sample_id'=>'id'),'through'=>'tumorSamplesIsolates'),
'tumorPatient'=>array(self::HAS_ONE,'Patients',array('patient_id'=>'id'),'through'=>'tumorSamples'),
This code generates the following sql:
SELECT `t`.`tumor_library` AS `t0_c0`, `t`.`normal_library` AS `t0_c1`, `t`.`created` AS `t0_c2`, `t`.`created_by` AS `t0_c3`, `t`.`last_modified` AS `t0_c4`, `t`.`last_modified_by` AS `t0_c5`, `tumor`.`library_type_id` AS `t1_c2`, `tumor`.`id` AS `t1_c0`
FROM `library_tumor_normal_pairs` `t`
INNER JOIN `library_types` `tumorLibraryType` ON (tumor.library_type_id = tumorLibraryType.id)
INNER JOIN `libraries` `tumor` ON (`t`.`tumor_library`=`tumor`.`id`)
LEFT OUTER JOIN `libraries_isolates` `tumorLibrariesIsolates` ON (`tumor`.`id`=`tumorLibrariesIsolates`.`library_id`)
LEFT OUTER JOIN `samples_isolates` `tumorSamplesIsolates` ON (`tumorLibrariesIsolates`.`isolate_id`=`tumorSamplesIsolates`.`isolate_id`)
LEFT OUTER JOIN `samples` `tumorSamples` ON (`tumorSamplesIsolates`.`sample_id`=`tumorSamples`.`id`)
INNER JOIN `patients` `tumorPatient` ON (`tumorSamples`.`patient_id`=`tumorPatient`.`id`)
WHERE (tumorLibraryType.id = 1 OR tumorLibraryType.id = 6)
But that sql throws an error:
"Column not found: 1054 Unknown column 'tumor.library_type_id' in 'on clause'. "
However if I simply move the tumor line in the sql query up to be the first join statement, and run the query manually, then the query works.
SELECT `t`.`tumor_library` AS `t0_c0`, `t`.`normal_library` AS `t0_c1`, `t`.`created` AS `t0_c2`, `t`.`created_by` AS `t0_c3`, `t`.`last_modified` AS `t0_c4`, `t`.`last_modified_by` AS `t0_c5`, `tumor`.`library_type_id` AS `t1_c2`, `tumor`.`id` AS `t1_c0`
FROM `library_tumor_normal_pairs` `t`
INNER JOIN `libraries` `tumor` ON (`t`.`tumor_library`=`tumor`.`id`)
INNER JOIN `library_types` `tumorLibraryType` ON (tumor.library_type_id = tumorLibraryType.id)
LEFT OUTER JOIN `libraries_isolates` `tumorLibrariesIsolates` ON (`tumor`.`id`=`tumorLibrariesIsolates`.`library_id`)
LEFT OUTER JOIN `samples_isolates` `tumorSamplesIsolates` ON (`tumorLibrariesIsolates`.`isolate_id`=`tumorSamplesIsolates`.`isolate_id`)
LEFT OUTER JOIN `samples` `tumorSamples` ON (`tumorSamplesIsolates`.`sample_id`=`tumorSamples`.`id`)
INNER JOIN `patients` `tumorPatient` ON (`tumorSamples`.`patient_id`=`tumorPatient`.`id`)
WHERE (tumorLibraryType.id = 1 OR tumorLibraryType.id = 6)
So my question is, how can I control the sql join order of "with" criteria in Yii? Is it possible? As you can see my 'with' array and relations have 'tumor' before the others, but the join order is not preserved.
I encountered a similar problem: Yii generates joins in such order that makes SQL statement invalid. This situation occurs, for example, when you try to write custom $CDBCriteria->join which relies on tables specified in relations passed by $CDBCriteria->with. This happens because join is processed in CJoinQuery::__constructor whereas all "standard" joins (from with) are generated by Yii in CJoinQuery::join, that is after the constructor.
Unfortunately there is no solution other than a patch. Here is an example of how I did it my copy of Yii (the code is provided "as is" - please, check if it's applicable for your case).
First, we need to add a field into CDbCriteria, which will switch on/off a new option.
CDbCriteria.php
class CDbCriteria extends CComponent
{
...
/**
* #var string how to join with other tables. This refers to the JOIN clause in an SQL statement.
* For example, <code>'LEFT JOIN users ON users.id=authorID'</code>.
*/
public $join='';
/**
* Patch begins:
*/
public $joinreorder = false; // new option
...
Second, we need to extend CJoinQuery (please, note, it's in CActiveFinder.php):
CActiveFinder.php
class CJoinQuery
{
...
/**
* #var array list of join element IDs (id=>true)
*/
public $elements=array();
/**
* Patch begins:
*/
private $joinreorder = false; // the same new option
private $postjoins; // the variable to store our custom joins
...
Now we can alter the CJoinQuery constructor:
CActiveFinder.php (continuation)
public function __construct($joinElement,$criteria=null)
{
if($criteria!==null)
{
$this->joinreorder = $criteria->joinreorder;
$this->selects[]=$joinElement->getColumnSelect($criteria->select);
$this->joins[]=$joinElement->getTableNameWithAlias();
if($this->joinreorder) //
{ //
$this->postjoins=$criteria->join; // new lines
} //
else //
{ //
$this->joins[]=$criteria->join;
} //
$this->conditions[]=$criteria->condition;
$this->orders[]=$criteria->order;
If joinreorder is true we store custom joins in our new member variable postjoins. Otherwise, all should work as before.
And now the actual fix in CJoinQuery::createCommand:
CActiveFinder.php (continuation)
public function createCommand($builder)
{
$sql=($this->distinct ? 'SELECT DISTINCT ':'SELECT ') . implode(', ',$this->selects);
$sql.=' FROM ' . implode(' ',$this->joins);
if($this->joinreorder) //
{ //
$sql .= $this->postjoins; // new lines
} //
...
Finally we add the custom joins into SQL statement, and they are appended (not prepended as in standard implementation) to other joins generated from Yii's relations.
Now we can use it like so:
$criteria = new CDbCriteria;
$criteria->joinreorder = true;
$criteria->with = array('product', 'shop');
$criteria->join = 'LEFT OUTER JOIN `shop2address` `s2a` ON (`shop`.`id` = `s2a`.`shop_id`)';
Without joinreorder = true this generates the error stating that shop.id is unknown column in ON clause, bacause the 'shop' table is not yet added into SQL-statement. With joinreorder = true it works as expected.
As for the cases when only with is used, and incorrect sequence of joins is generated, one should apply more complicated patch. It involves CJoinQuery::join method. It should, possibly, have an optional parameter $priority, which can be again populated via corresponding member added into CDbCriteria. Then in CJoinQuery::join change these lines:
$this->joins[$element->priority]=$element->getJoinCondition();
$this->joins[$element->priority]=$element->relation->join;
This allows for re-ordering joins in arbitrary manner according to specified priorities.
This line doesn't look correct:
'tumorLibraryType'=>array(self::HAS_ONE,'LibraryTypes','','on'=>'tumor.library_type_id = tumorLibraryType.id'),
Maybe it should be
'tumorLibraryType'=>array(self::HAS_ONE,'LibraryTypes',array('id'=>'library_type_id'),'through'=>'tumor'),
guys, I believe I'm late to the party
I had similar problem
I've criteria with merges:
$criteria = new CDbCriteria();
$criteria->with = [
'codebaseName' => [
'alias' => 'cn'
],
'codebaseProducer' => [
'alias' => 'cp'
],
'registrationDocumentLast' => [
'alias' =>'rdl'
]
];
It ended up in such order by statement:
ORDER BY COALESCE(cn.name_our,cn.name_supplier), id DESC LIMIT 50
I didn't specify ordering by id DESC explicitly!
After playing for around, I discovered that it came from relation registrationDocumentLast , which was defined as
'registrationDocumentLast' => [
self::HAS_ONE, RegistrationDocument::class, 'codebase_product_pharm_id',
'joinType' => 'LEFT JOIN',
'order' => 'id DESC'
],
Look at order key. Changing it from
'order' => 'id DESC'
to
'order' => 't.id DESC'
solved the problem
I've been trying to look for a solution where you can fetch database with one prepared statement and execute it with an array value
Typically I do this with my statement:
$search = $db->prepare("SELECT * FROM table WHERE name = ?");
$search->execute(array($name));
But what if i have an array like so:
Array (
[0] => Array
(
[name] => Burger Joint
)
[1] => Array
(
[name] => Burger Joint
)
[2] => Array
(
[name] => Burgers
)
[3] => Array
(
[name] => Meats
)
)
I'd like to somehow go through my database with either of the values in the array WHERE name=? in the statement. However, sometimes there's going to be multiple similar names, is there a way to condense the array before hand or what would be the best practice in a situation like this?
Thanks!
You can do this in a number of ways, but since you mentioned OR, let's use that:
First, your array of possible values. Let's take your array and mold it into an array of unique values:
$values_array = array_unique(
array_map(
function($element) {
return $element['name'];
},
$original_array
)
);
// $values_array now contains array('Burger Joint', 'Burgers', 'Meats')
Now, we build the prepared query by introducing as many placeholders as you have possible values:
$query = sprintf('SELECT * FROM table WHERE %s',
implode(
' OR ',
array_fill(
'name = ?',
count($values_array)
)
)
);
// $query now contains 'SELECT * FROM table WHERE name = ? OR name = ? OR name = ?'
and execute it:
$search = $db->prepare($query);
$search->execute($values_array);
Alternatively, you could use IN instead, building your query like so:
$query = sprintf('SELECT * FROM table WHERE name in (%s)',
implode(
', ',
array_fill(
'?',
count($values_array)
)
)
);
// $query now contains 'SELECT * FROM table WHERE name in (?, ?, ?)'
$search = $db->prepare($query);
$search->execute($values_array);
This will have the same effect, and it's slightly more clear what's going on by looking at the code.
Try name IN instead of name = .
First, you need IN. field IN (1,2) is equal to field=1 OR field=2.
Next, you need some sort of helper function, to put all that mess of technical details of creating correct SQL statements away from application business code. To make it in ONE line, not 50.
$data = $db->getAll("SELECT * FROM table WHERE name IN (?a)",$names);
Finally, it seems you're getting your names from another query.
In this case you have to run only single query using JOIN. You may ask another question under [mysql] tag providing both your queries.
To get only names into array you have to use another helper function (though you have to create it yourself or get somewhere first):
$names = $db->getCol("here goes your query to get names");
$conditions = Array
(
[table] => products_pages
[alias] => ProductsPage
[type] => inner
[foreignKey] =>
[conditions] => Array
(
[0] => ProductsPage.product_id = Product.id
)
)
I'm trying to set up NOT EXISTS conditions, like the following SQL statement:
SELECT * FROM products_pages,products
WHERE NOT EXISTS (SELECT id
from products_pages
where products_pages.product_id = products.id)
So basically select any product that doesn't exist in the products_pages table.
What is the proper way to format that SQL statement for CakePHP and replace it here:
[conditions] => Array
(
[0] => (What's the proper way to insert above SQL here?
)
Would really appreciate your help guys, I've been trying to figure this out for about 5 hours with no luck. Thanks!
You can always use query if you don't find the way to do it with CakePHP:
http://book.cakephp.org/2.0/en/models/retrieving-your-data.html#model-query
In this case security wouldn't be compromised as you are not using any input.
Anyway, something simple would be just to do it in more than one step:
//selecting the products in the productcs_pages table
$productsWithPages = /* query to get them*/
//getting an array of IDs
$productsWidthPagesIds = Hash::extract($productsWithPages, '{n}.Product.id');
//doing the NOT IN to select products without pages
$productsWithoutPages= $this->Product->find('all',
array('conditions' =>
array( 'NOT' => array('Product.id' => $productsWidthPagesIds )
)
);
I have 2 tables in my db...
Entita
id int(11)
descrizione varchar(50)
.....
Publicobjects
....
model varchar(50) the model I need (in this case 'Entita')
model_id int(11)
I would like to make a query like this:
select entita.*
from entita
where NOT EXISTS (select * from publicobjects where publicobjects.model = 'Entita' and publicobjects.model_id = entita.id)
How can I do this with the model functions of Cakephp without use custom query?
Thanks
I believe you're trying to find rows from the Entita table that are not in the Publicobjects table. Assuming that is correct, here is the SQL query for MySQL to find it:
SELECT `entita`.*
FROM `entita`
LEFT JOIN `publicobjects` ON (`publicobjects`.`model` = 'entita'
AND `publicobjects`.`model_id` = `entita`.`id`)
WHERE `publicobjects`.`model_id` IS NULL
To make this work with CakePHP's models takes a couple of steps. I've made some assumptions about your model names, but I could be wrong and those are easy to fix.
First add this to the Entita model:
<?php
var $hasOne = array('Publicobject' => array(
'foreignKey' => 'model_id',
'conditions' => 'Publicobject.model = "Entita"'));
Now, you can check for entries that are missing in the Publicobjects table like this:
<?php
$this->Entita->find('all', array('conditions' => array('Publicobject.model_id IS NULL')));