Searching with CDbCriteria - yii

Is there any way to make a CDbCriteria search (as in compare()) in the fields I'm selecting, but using the model's search() method instead of having to manually add the compare() conditions?
Note that I'm aiming at a solution that will let me write some fewer lines, nothing more and nothing less. So, if the solution is something really hacky and/or mesy, I'll just go for the "add-a-few-compares()" method.
My current code:
$criteria = new CDbCriteria;
$criteria->with = array('A', 'B', 'C', 'D', 'E');
$criteria->compare("A.field1", "test", false, 'OR');
$criteria->compare("A.field2", "test", false, 'OR');
$criteria->compare("B.field1", "test", false, 'OR');
$criteria->compare("B.field2", "test", false, 'OR');
$dataProvider = new CActiveDataProvider('Z', array(
'criteria'=>$criteria,
//pagination...
//more options...
));

Update: It seems that you are actually looking(from comments below this answer) for partial matches, and for that you will have to pass true to your compare calls:
$criteria->compare("A.field1", "test", true, 'OR');
Even that can be passed to addCondition:
$criteria->addCondition('A.field1 LIKE "%test"','OR');
// or with params as below
$criteria->addCondition('A.field2 LIKE :test','OR');
$criteria->params=array(
':test'=>'%test%',
);
As i have already mentioned in the comments, i don't think it'll be possible to use each model's default search() method. There are other alternatives though, for instance you can use addCondition instead:
$criteria = new CDbCriteria;
$criteria->with = array('A', 'B', 'C', 'D', 'E');
$criteria->together = true; // you'll need together so that the other tables are joined in the same query
$criteria->addCondition('A.field1 = "test"','OR');
$criteria->addCondition('A.field2 = "test"','OR');
// and so on
I would suggest going with the above, because compare (doc-link) should actually be used in cases when you want to "intelligently" determine the operator for comparision, for example: if you are taking the test values from user input and the user is allowed to use operators (<,>,<= etc). After determining the operator to be used in the condition, compare calls other functions accordingly, including addCondition. So using addCondition will atleast avoid those unnecessary checks.
Further if all you have to do is check equality only, i.e if your sql's WHERE is supposed to be:
WHERE A.field1 = "test" OR A.field2 = "test"
then you don't even need addCondition, and you can simply use a more complex condition (doc) :
$criteria->condition='A.field1 = "test" OR A.field2 = "test"';
// or even better if you use params
$criteria->condition='A.field1 =:test1 OR A.field2 =:test2 OR B.field1 =:test3 OR B.field2 =:test3';
$criteria->params=array(
':test1'=>'test',
':test2'=>'anothertest',
'test3'=>'tests' // omitting ':' here for params also works
);

Related

Multiple "OR" strings in sSearch

This searches my WPDataTable and filters it to only display rows containing 'apple':
function filterGlobalSearch( $tableDescriptionObj, $tableId , $wpDataTablesObj) {
if( $tableId == 1 ){
$tableDescriptionObj->dataTableParams->oSearch = array(
'bSmart' => false,
'bRegex' => false,
'sSearch' => 'apple'
);
}
return $tableDescriptionObj;
}
add_filter('wpdatatables_filter_table_description','filterGlobalSearch', 10, 3);
Please can you tell me how to search for 'apple' OR 'banana'? (to show rows that contain either word).
I've found similar answers on StackExchange and other forums about multiple OR strings (for example here) but can't seem to get them working.
Have already tried:
'apple|banana'
'apple||banana'
'apple'|'banana'
'apple'||'banana'
'apple' OR 'banana'
array('apple', 'banana')
Grateful for any assistance.
I've been provided a working solution that I'll share here:
'bRegex' => false should be changed to true.
'apple|banana' is the search string.

Linq2DB can't translate a mapped column in Where clause

I'm working with a legacy Oracle database that has a column on a table which stores boolean values as 'Y' or 'N' characters.
I have mapped/converted this column out like so:
MappingSchema.Default.SetConverter<char, bool>(ConvertToBoolean);
MappingSchema.Default.SetConverter<bool, char>(ConvertToChar);
ConvertToBoolean & ConvertToChar are simply functions that map between the types.
Here's the field:
private char hasDog;
[Column("HAS_DOG")]
public bool HasDog
{
get => ConvertToBoolean(hasDog);
set => hasDog = ConvertToChar(value);
}
This has worked well for simply retrieving data, however, it seems the translation of the following:
var humanQuery = (from human in database.Humans
join vetVisit in database.VetVisits on human.Identifier equals vetVisit.Identifier
select new HumanModel(
human.Identifier
human.Name,
human.HasDog,
vetVisit.Date,
vetVisit.Year,
vetVisit.PaymentDue
));
// humanQuery is filtered by year here
var query = from vetVisits in database.VetVisits
select new VetPaymentModel(
(humanQuery).First().Year,
(humanQuery).Where(q => q.HasDog).Sum(q => q.PaymentDue), -- These 2 lines aren't correctly translated to Y/N
(humanQuery).Where(q => !q.HasDog).Sum(q => q.PaymentDue)
);
As pointed out above, the .Where clause here doesn't translate the boolean comparison of HasDog being true/false to the relevant Y/N values, but instead a 0/1 and results in the error
ORA-01722: invalid number
Is there any way to handle this case? I'd like the generated SQL to check that HAS_DOG = 'Y' for instance with the specified Where clause :)
Notes
I'm not using EntityFramework here, the application module that this query exists in doesn't use EF/EFCore
You can define new mapping schema for your particular DataConnection:
var ms = new MappingSchema();
builder = ms.GetFluentMappingBuilder();
builder.Entity<Human>()
.Property(e => e.HasDog)
.HasConversion(v => v ? 'Y' : 'N', p => p == 'Y');
Create this schema ONCE and use when creating DataConnection

Grails: "where" query with optional associations

I'm trying to run a "where" query to find a domain model object that has no association with another domain model object or if it does, that domain model object has a specific property value. Here's my code:
query = Model.where({
other == null || other.something == value
})
def list = query.list()
However, the resulting list only contains objects that match the second part of the OR statement. It contains no results that match the "other == null" part. My guess is that since it's checking a value in the associated object its forcing it to only check entries that actually have this associated object. If that is the case, how do I go about creating this query and actually having it work correctly?
You have to use a LEFT JOIN in order to look for null associations. By default Grails uses inner join which will not be joined for null results. Using withCriteria as below you should get the expected results:
import org.hibernate.criterion.CriteriaSpecification
def results = Model.withCriteria {
other(CriteriaSpecification.LEFT_JOIN){
or{
isNull 'id'
eq 'something', value
}
}
}
UPDATE
I know aliasing is not possible in DetachedCritieria where one would try to specify the join as in createCriteria/withCriteria. There is an existing defect regarding adding the functionality to DetachedCriteria. Just adding the work around for where query as mentioned in defect.
Model.where {
other {
id == null || something == value
}
}.withPopulatedQuery(null, null){ query ->
query.#criteria.subcriteriaList[0].joinType = CriteriaSpecification.LEFT_JOIN
query.list()
}
I would rather use withCriteria instead of the above hack.
this might work:
query = Model.where({
isNull( other ) || other.something == value
})
If that wouldn't work, try something like:
other.id == null || other.something == value
UPDATE:
or with good'ol criteria query:
list = Pack.withCriteria{
or{
isNull 'other'
other{ eq 'something', value }
}
}

ZF2: Is there a more efficient way to do this Zend\Db Update query?

Here's some ZF1 code for an update query:
$this->getAdapter()->update(
'users', $data, $this->getAdapter()->quoteInto('node_id = ?', $user->nodeId)
);
Here's the same query with ZF2:
$param = $this->getAdapter()->platform->quoteValue($user->nodeId);
$sqlOj = new Sql($this->getAdapter());
$update = $sqlOj->update('users')->set($data)->where('node_id = ' . $param);
$updateString = $sqlOj->getSqlStringForSqlObject($update);
$this->getAdapter()->query($updateString, Adapter::QUERY_MODE_EXECUTE);
As you can see, one line of ZF1 code has become 5 lines of ZF2 code, (actually without the fluent interface it would be 7 lines...)
Am I missing something? Or is ZF2's DB component just more verbose that ZF1?
BTW, I have found the same scenario with select and insert queries too...
I managed to limit it to 3 lines.
use \Zend\Db\Sql\Sql;
$sql = new Sql ($adapter);
$update = $sql->update ('users')->set ($data)->where (['id = ?' => 1]);
$adapter->query ($sql->getSqlStringForSqlObject ($update), $db::QUERY_MODE_EXECUTE);
The problem is they didn't expect you to run your updates like that. Instead, you are expected to use a table gateway.
This way it becomes one line again:
$this->tableGateway->update($data, array('id' => $id));

Preserve Order of IN in ORM Order

I'm trying to do a query where I preserve the order of the ids in a IN statement. I can't seem to do it with either the Model Manage Query Builder or the standard ORM 'order' array parameter. Am I missing something? I keep getting:
UNEXPECTED TOKEN IDENTIFIER(, NEAR TO 'id`enter code here`,17743,16688,16650
Here's my model manager:
$query = $this->modelsManager->createQuery('SELECT * FROM Projects WHERE id IN ('.implode(',', array_keys($finalIterations)).')
ORDER BY FIELD(id,'.implode(',', array_keys($finalIterations)).'');
It's pretty obvious PhQL doesn't like the FIELD key word. Is there a way for me to do what I'm trying to do with PhQL? It seems I will not be able to do what I need to.
Unfortunately as previously said, this is missing a feature in Phalcon.
Have a look at this function, I've put it into my ModelBase abstract class which is parent class of all my models. It uses PhQL variable binding, so it's safe for handling direct user input.
You could have reimplemented custom \Phalcon\Mvc\Model\Criteria but this solution seems to be easier to work with, at least for me.
ModelBase abstract
public function appendCustomOrder( \Phalcon\Mvc\Model\CriteriaInterface &$criteria, $orderField, array &$orderValues = [] ) {
if(!empty($orderValues)) {
$queryKeys = $bindParams = [];
foreach($orderValues as $key => $id) {
$queryKey = 'pho'.$key;
$queryKeys[] = ':'.$queryKey.':';
$bindParams[$queryKey] = $id;
}
// TODO: add support for multiple orderBy fields
$criteria->orderBy('FIELD('.$orderField.','.implode(',',$queryKeys).')');
// there's no 'addBind' function, need to merge old parameters with new ones
$criteria->bind( array_merge( (array) #$criteria->getParams()['bind'], $bindParams ) );
}
}
Controller usage
$projectIDs = [17743, 16688, 16650];
$projectsModel = new Projects();
$criteria = $projectsModel->query->inWhere( 'id', $projectIDs );
$projectsModel->appendCustomOrder( $criteria, 'id', $projectIDs );
$projectsData = $criteria->execute();
This will generate valid PhQL syntax similar to this one:
SELECT `projects`.`id` AS `id`, `projects`.`title` AS `title`
FROM `projects`
WHERE `projects`.`id` IN (:phi0, :phi1, :phi2)
ORDER BY FIELD(`projects`.`id`, :pho0, :pho1, :pho2)