Yii - Understanding operators to build SQL via CDBCriteria with LIKE condition - yii

Here's the SQL I want to achieve via Yii CDBCriteria :
WHERE
(
(field1 LIKE '%value1%') AND (field1 LIKE '%value2%')
) OR (
(field2 LIKE '%value1%') AND (field2 LIKE '%value2%')
) OR (
(field3 LIKE '%value1%') AND (field3 LIKE '%value2%')
)
This will return the row if a string contains 'value1' or 'value2' in any order. ex.:
Lorem value1 ipsum value2 sir dolor amet.
OR
Lorem value2 ipsum value1 sir dolor amet.
But will return nothing if there's only one value.
What I've tried (from this thread) :
$c = new CDbCriteria;
$c->addColumnCondition(array('field1 LIKE' => '%value1%', 'field1 LIKE' => '%value2%'), 'AND', 'OR');
$c->addColumnCondition(array('field2 LIKE' => '%value1%', 'field2 LIKE' => '%value2%'), 'AND', 'OR');
$c->addColumnCondition(array('field3 LIKE' => '%value1%', 'field3 LIKE' => '%value2%'), 'AND', 'OR');
But this is not how to use it with LIKE condition and it returns only the second compare because the key is the same for the same field :
(
(field1 LIKE='%value2%') OR (field2 LIKE='%value2%')
) OR (field3 LIKE='%value2%')
So I've tried with compare condition :
$c->compare('field1', 'value1', true);
$c->compare('field1', 'value2', true, 'OR');
$c->compare('field2', 'value1', true, 'AND');
$c->compare('field2', 'value2', true, 'OR');
$c->compare('field3', 'value1', true, 'AND');
$c->compare('field3', 'value2', true, 'OR');
OR
$c->addSearchCondition('field1', 'value1', false);
$c->addSearchCondition('field1', 'value2', false, 'OR');
$c->addSearchCondition('field2', 'value1', false);
$c->addSearchCondition('field2', 'value2', false, 'OR');
$c->addSearchCondition('field3', 'value1', false);
$c->addSearchCondition('field3', 'value2', false, 'OR');
But I could not work it out to return the condition I want. Here's what I get :
(
(
(
(
(field1 LIKE '%value1%') OR (field1 LIKE '%value2%')
)
AND (field2 LIKE '%value1%')
)
OR (field2 LIKE '%value2%')
)
AND (field3 LIKE '%value1%')
) OR (field3 LIKE '%value2%')
So if anybody have an idea how to achieve this, help me :)

Is this what you are looking for?
$c->addCondition("field1 like :value1 and field1 like :value2", "OR");
$c->addCondition("field2 like :value1 and field2 like :value2", "OR");
$c->addCondition("field3 like :value1 and field3 like :value2", "OR");
$c->params = array(':value1' => '%'. $value1 .'%', ':value2' => '%'. $value2 .'%');
Alternative:
$c2 = new CDbCriteria;
$c2->addSearchCondition("field2", ':value1');
$c2->addSearchCondition("field2", ':value2');
$c3 = new CDbCriteria;
$c3->addSearchCondition("field3", ':value1');
$c3->addSearchCondition("field3", ':value2');
$c->addSearchCondition("field1", ':value1');
$c->addSearchCondition("field1", ':value2');
$c->mergeWith($c2, false);
$c->mergeWith($c3, false);

Related

fetching is very slow in yii using CDBcriteria

i have about 15000 rows in my table using cdbcrieria to fetch result , but it take too much time to load the result , can anyone please tell me the fastest way to fetch the result
i want the count of all fetched rows and method to fetch fastest result.Please help !
following is the code that i am currently using:
$criteria = new CDbCriteria;
// $Criteria->$criteria = 50;
$currentAction = Yii::app()->controller->action->id;
if ($currentAction == 'currentstock') {
$storeid = isset(Yii::app()->session['storeId']) ? Yii::app()->session['storeId'] : 0;
$criteria->condition .= ' storexparts.storeId="' . $storeid . '" AND ( partAppStatus="1" OR partAppStatus="15" OR partAppStatus="17" OR partAppStatus="22" ) ';
} else {
$userId = Yii::app()->user->id;
$storeid = isset(Yii::app()->session['storeId']) ? trim(Yii::app()->session['storeId']) : (isset($_REQUEST['storeId']) ? Yii::app()->session['storeId'] = trim($_REQUEST['storeId']) : '');
$criteria->condition = 'storexparts.storeId="' . $storeid . '"';
$criteria->condition .= ' AND userxfavparts.userId="' . $userId . '"';
$criteria->condition .= ' AND userxfavparts.storeId="' . $storeid . '"';
$criteria->condition .= ' AND (t.partAppStatus="1"';
$criteria->condition .= ' OR t.partAppStatus="15"';
$criteria->condition .= ' OR t.partAppStatus="17"';
$criteria->condition .= ' OR t.partAppStatus="22")';
// $criteria->order = 't.partCreateDate DESC';
}
$criteria->with = array('partUnit0', 'partCat0','fleetname', 'partType0', 'partSubCat0', 'storexparts', 'userxfavparts', 'partBrand0', 'partsalias','partPosXstore');
if (!empty($_GET['filterProperties'])) {
$string = implode("#", explode("\\", $_GET['filterProperties']));
$string = implode("#", explode("'", $string));
$code = stripslashes(trim($string));
$criteria->addCondition("partNumber LIKE CONCAT('%', :filterProperties , '%')
OR partsalias.aliasNumber LIKE CONCAT('%', :filterProperties , '%')
OR partType0.typeName LIKE CONCAT('%', :filterProperties , '%')
OR partCat0.catName LIKE CONCAT('%', :filterProperties , '%')
OR partSubCat0.subCatName LIKE CONCAT('%', :filterProperties , '%')
OR partDesc LIKE CONCAT('%', :filterProperties , '%')
OR storexparts.partQty LIKE CONCAT('%', :filterProperties , '%')
OR partNotes LIKE CONCAT('%', :filterProperties , '%')
OR partBrand0.brandCode LIKE CONCAT('%', :filterProperties , '%')
OR partUnit0.unitShort LIKE CONCAT('%', :filterProperties , '%')
OR partPosXstore.partShelf LIKE CONCAT('%', :filterProperties , '%')
OR partPosXstore.partBin LIKE CONCAT('%', :filterProperties , '%')
OR fleetname.fleetcode LIKE CONCAT('%', :filterProperties , '%')
OR fleetname.fleetserialno LIKE CONCAT('%', :filterProperties , '%')
OR fleetname.fleetmodel LIKE CONCAT('%', :filterProperties , '%')
OR fleettype0.typename LIKE CONCAT('%', :filterProperties , '%')
OR fleetbrand0.brandname LIKE CONCAT('%', :filterProperties , '%')
OR engine.engineno LIKE CONCAT('%', :filterProperties , '%')
OR partsalias.aliasBarcode LIKE CONCAT('%', :filterProperties , '%')
");
$criteria->params = array(':filterProperties' => $code);
}
$criteria->together = true;
if (isset($_REQUEST['Parts']) && !empty($_REQUEST['Parts'])) {
if (!empty($_REQUEST['Parts']['partAliasNumber'])) {
$partId = $_REQUEST['Parts']['partAliasNumber'];
$criteria->addCondition("partNumber LIKE CONCAT('%', :filterProperties , '%')
OR partsalias.aliasNumber LIKE CONCAT('%', :filterProperties , '%')");
$criteria->params = array(':filterProperties' => $partId);
}
}
$criteria->compare('partType0.typeName', $this->typeName, true, 'AND', 'LIKE');
$criteria->compare('partCat0.catName', $this->catName, true, 'AND', 'LIKE');
$criteria->compare('partSubCat0.subCatName', $this->subCatName, true, 'AND', 'LIKE');
$criteria->compare('partDesc', $this->partDesc, true, 'AND', 'LIKE');
$criteria->compare('storexparts.partQty', $this->partQty, true, 'AND', 'LIKE');
$criteria->compare('partNotes', $this->partNotes, true, 'AND', 'LIKE');
$criteria->compare('partUnit0.unitShort', $this->unitShort, true, 'AND', 'LIKE');
$criteria->compare('partBrand0.brandCode', $this->partBrand, true, 'AND', 'LIKE');
$criteria->compare('partPosXstore.partShelf', $this->partShelf, true, 'AND', 'LIKE');
$criteria->compare('partPosXstore.partBin', $this->partBin, true, 'AND', 'LIKE');
Very important: Your select must be optimized before any other thing. If you are using MySQL, you can check their official documentation to do it.
With your select properly optimized, I suggest you to create a database view with your RDMBS to avoid overload your query with fat ActiveRecords with a lot of data and metadata. DML for build definition, in MySQL, looks like:
CREATE OR REPLACE VIEW 'mydatabaseview' AS
(
SELECT
..... your select with all joins and minimal
..... WHERE conditions
)
Backing to the code, create a model with this database view. You can generate model with gii code generator. In generated model, into protected/models/Mydatabaseview.php make sure to define a method primaryKey():
class Mydatabaseview extends ActiveRecord
{
.....
// Define this method manually, because your database view
// don't have primary Key
public function primaryKey()
{
return 'field_from_database_view';
}
....
Finally, in same model in protected/models/Mydatabaseview.php, use your criteria as you want:
public function search()
{
$criteria = new CDbCriteria;
.......... all your conditions
}
If your issue continues, your probably need to use Yii Query Builder instead ActiveRecord. It implies to redesign your original concept.

Symfony createquery with union

I need to execute the next query with symfony :
$qb = $this->_em;
$query = $qb->createQuery(
'SELECT f1.friend1
FROM AppBundle:Friend f1
WHERE f1.friend2 = ?1
UNION
SELECT f2.friend2
FROM AppBundle:Friend f2
WHERE f2.friend1 = ?2
'
)->setParameters(array(1 => $user_id, 2 => $user_id));
When i execute this $query, i have this error : Expected end of string, got 'UNION'.
How can i do ?
Edit for my new code :
$rsm = new \Doctrine\ORM\Query\ResultSetMapping();
$rsm->addEntityResult('Project\MyBundle\Entity\Friend', 'f');
$rsm->addFieldResult('f', 'friend1', 'friend1');
$rsm->addFieldResult('f', 'friend2', 'friend2');
$rsm->addFieldResult('f', 'id', 'id');
$rsm->addFieldResult('f', 'state', 'state');
$sql = "SELECT f1.friend1 AS friend
FROM friend f1
WHERE f1.friend2 = ?
UNION
SELECT f2.friend2 AS friend
FROM friend f2
WHERE f2.friend1 = ?";
$result = $this->_em->createNativeQuery($sql, $rsm)
->setParameter(1, $user_id)
->setParameter(2, $user_id)
->getResult();
return $result;
This is my new query. I have test this query directly on phpmyadmin, and i have a return. But with this code, i have empty in $result
UNION is not supported in doctrine instead you can use Doctrine\ORM\Query\ResultSetMapping; this will map your resultset to the entity which you defined to use by the native query like $rsm->addEntityResult('Namespace\yourBundle\Entity\Friend', 'f');
$rsm = new \Doctrine\ORM\Query\ResultSetMapping();
$rsm->addEntityResult('Namespace\yourBundle\Entity\Friend', 'f');
$rsm->addFieldResult('f', 'friend', 'friend1');
$sql = "SELECT f1.friend1 AS friend
FROM friend_table f1
WHERE f1.friend2 = ?
UNION
SELECT f2.friend2 AS friend
FROM friend_table f2
WHERE f2.friend1 = ?";
$result = $DM->createNativeQuery($sql, $rsm)
->setParameter(1, $user_id)
->setParameter(2,$user_id)
->getResult();
Edit from comments
Not advisable solution due to lack of information why using union but the answer for question in comments below you can do so to map fields,but remember this will give you the duplicates
$rsm = new \Doctrine\ORM\Query\ResultSetMapping();
$rsm->addEntityResult('Project\MyBundle\Entity\Friend', 'f');
$rsm->addFieldResult('f', 'friend1', 'friend1');
$rsm->addFieldResult('f', 'friend2', 'friend2');
$rsm->addFieldResult('f', 'id', 'id');
$rsm->addFieldResult('f', 'state', 'state');
$sql = "SELECT f1.id,f1.friend1 AS friend,f1.friend1,f1.friend2 ,f1.state
FROM friend f1
WHERE f1.friend2 = ?
UNION
SELECT f2.id,f2.friend2 AS friend,f2.friend1,f2.friend2 ,f2.state
FROM friend f2
WHERE f2.friend1 = ?";
$result = $this->_em->createNativeQuery($sql, $rsm)
->setParameter(1, $user_id)
->setParameter(2, $user_id)
->getResult();
Edit 2 i just want an array with one field "friend", with friend1 in one case, and friend2 in another
For the above you asked you can run two queries using your entity
$DM = $this->getDoctrine()->getEntityManager();
$result1=$DM->getRepository('Namespace\yourBundle\Entity\Friend')
->findBy(array('friend2'=>$user_id));
$result2=$DM->getRepository('Namespace\yourBundle\Entity\Friend')
->findBy(array('friend1'=>$user_id));

Perl sql query statement

My sql statement is simple as below:
if not exists (select col_a from t_a where co_b = 'v_b')
begin
insert into t_a (col_a ,col_b )
VALUES(v_a,v_b)
end
else
begin
update t_a set col_a = v_a, col_b = v_b where col_b = 'v_b'
end
As I have hundreds of rows to update, how can I do this in Perl for the least time cost?
If I use Prepare + Execute, how to write the statement using the placeholder ? ?
Does the $dbh->prepare($statement); support multiple composite SQL lines like those above? Or do I have to save the lines into an sql file and run it using SQL server?
To make the question more clear, my Perl lines look like those below:
$statement = "if ... VALUES(?,?)...update t_a set col_a = ?, col_b = ?";
# better to use one binding values(v_a, v_b) couplets mapping
# the 2 placeholders of insert and update both?
foreach (#$va_arr) {
my $values_for_one_row = $_;
$dbh->prepare($statement);
$execute->execute($values_for_one_row->{col_a }, $values_for_one_row->{col_b });
}
I forgot one thing: the 'whatever' is also a value in $va_arr to be changed on every iteration: if not exists (select col_a from t_a where co_b = 'v_b'). Also, the update section should be: update t_a set col_a = ?, col_b = ? where col_b = "v_b". Seems no better way then include the prepare into the loop? Sorry I didn't think the example complete. But I think simbabque's answer is good enough.
You can use your SQL without problems. You need to prepare the statement once. I am assuming your $va_arr looks like this:
my $va_arr = [
{
col_a => 1,
col_b => 2,
},
{
col_a => 'foo',
col_b => 'bar',
},
];
Your code to run this could be as follows. Note that you have to pass the col_n params twice as it needs to fill them in two times into each ? with every execute. They get filled in the order of the ? in the query, so we need col_a, col_b for the INSERT and another col_a, col_b for the UPDATE.
my $sql = <<'EOSQL';
if not exists (select col_a from t_a where co_b = 'whatever')
begin
insert into t_a (col_a ,col_b )
VALUES(?, ?)
end
else
begin
update t_a set col_a = ?, col_b = ?
end
EOSQL
my $sth = $dbi->prepare($sql);
foreach ($values = #{ $va_arr }) {
$dbh->execute($values->{col_a }, $values->{col_b },
$values->{col_a }, $values->{col_b });
}
If you have a long list of columns and you know the order, consider this:
my #columns = qw( col_a col_b col_c col_n );
my $va_arr = [
{
col_a => 1,
col_b => 2,
col_n => 99,
},
{
col_a => 'foo',
col_b => 'bar',
col_n => 'baz',
},
];
# build the sql dynamically based on columns
my $sql = q{
if not exists (select col_a from t_a where co_b = 'whatever')
begin
insert into t_a (} . join(',' #columns) . q{)
VALUES(} . join(',', map '?', #columns) . q{)
end
else
begin
update t_a set } . join(',' map { "$_ => ?" } #columns) . q{
end
};
my $sth = $dbi->prepare($sql);
foreach ($values = #{ $va_arr }) {
$dbh->execute(#{$values}{#columns}, #{$values}{#columns});
}
Let's look at what this does. It's helpful if you have a really long list of columns.
You know their names and order, and put that into #columns.
Build the SQL based on these columns. We have to add the column name and a ? to the INSERT and the combination of both to the UPDATE for each of the columns.
Execute it with a hash ref slice
Please note that I have not run this, just hacked it in here.
you should put the prepare statement out of the loop and use a transaction
for example:
my $sql1 = qq(select col_a from t_a where col_b = ?);
my $sql2 = qq(insert into t_a (col_a, col_b) VALUES(?, ?));
my $sql3 = qq(update t_a set col_a = ? where col_b = ?);
my $query = $dbh->prepare($sql1);
$dbh->begin_work();
foreach (#$va_arr) {
my $values_for_one_row = $_;
$query->execute($values_for_one_row->{col_b});
my #out = $query->fetchrow_array();
$query->finish();
if ( not defined $out[0] )
{
$dbh->do($sql2, undef, $values_for_one_row->{col_a}, $values_for_one_row->{col_b});
}
else
{
$dbh->do($sql3, undef, $values_for_one_row->{col_a}, $values_for_one_row->{col_b});
}
}
$dbh->commit();
If upsert is not available, here's how I might do it:
Bulk load the data into a staging table.
Delete all data that joins to the target table.
Insert data from staging to target.
Alternatively you can update from staging to target, delete from the staging data that joins, then insert what's left in staging.
Or, a few hundred rows is not that many, so I might: prepare an insert and an update statement handle outside of the loop. Then in the loop:
my $rows = $upd_sth->execute(...);
$ins_sth->execute(...) if $rows == 0;

Issue with quoting of IS, NULL, NOT, !, and other reserved strings in ON conditions of JOIN clauses in Zend Framework 2

I have an SQL statement, that selets sport classes/courses (courses) with their trainers (trainers) over an association table (courses_trainers). Since some courses have multiple trainers, I use the GROUP_CONCAT(...) function to get the trainer names into one field. Some trainers rows are empty or NULL, so I add a trainers.name IS NOT NULL and a trainers.name != "" condition to the ON clause of the trainers JOIN:
SQL statement
SELECT
courses.id AS id,
GROUP_CONCAT(DISTINCT trainers.name SEPARATOR "|||") AS trainers
...
FROM
courses
...
LEFT JOIN
courses_trainers ON courses.id = courses_trainers.course_id
LEFT JOIN
trainers ON trainer_id = trainers.id
AND trainers.name IS NOT NULL
AND trainers.name != ""
...
...
WHERE `courses`.`id` = '898'
GROUP BY
courses.id
;
OO variant in the CourseTable class
public function findOnceByID($id) {
$concatDelimiter = self::CONCAT_DELIMITER;
$select = new Select();
...
$select->columns(array(
'id', ...
));
$select->from($this->tableGateway->getTable());
$select
...
->join('courses_trainers', 'courses.id = courses_trainers.course_id', array(), Select::JOIN_LEFT)
->join('trainers', 'trainer_id = trainers.id AND trainers.name IS NOT NULL AND trainers.name != ""', array(
'trainers' => new Expression('GROUP_CONCAT(DISTINCT trainers.name SEPARATOR "' . $concatDelimiter . '")')
), Select::JOIN_LEFT)
...
;
$where
->equalTo('courses.id', $id)
;
$select->where($where, Predicate::OP_AND);
$select->group('courses.id');
$resultSet = $this->tableGateway->selectWith($select);
return $resultSet;
}
The generated JOIN code I get looks like this:
LEFT JOIN
`courses_trainers` ON `courses`.`id` = `courses_trainers`.`course_id`
LEFT JOIN
`trainers` ON `trainer_id` = `trainers`.`id`
AND `trainers`.`name` `IS` `NOT` `NULL`
AND `trainers`.`name` `!`= `"``"`
So, here is to much quoted.
How to "explain" to the ZF, that IS, NOT, " etc. should not be quoted?
The join method accepts an expression as its second parameter for the ON clause
->join('trainers', new Expression('trainer_id = trainers.id AND trainers.name IS NOT NULL AND trainers.name != ""'),
Responsible for quoting is Zend\Db\Adapter\Platform\PlatformInterface#quoteIdentifierInFragment(...) (or more precise its implementations, in this case in Zend\Db\Adapter\Platform\Mysql), that gets as second argument an array of "safe words". Zend\Db\Sql\Select#processJoins(...) passes to it the array('=', 'AND', 'OR', '(', ')', 'BETWEEN', '<', '>'). As IS, NOT, ! etc. are not in the list, they are quoted.
The solution is to extend the Zend\Db\Sql\Select and overwrire its processJoins(...), adding the additional "safe words" needed to the list of the second argument in the quoteIdentifierInFragment(...) call:
<?php
namespace MyNamespace\Db\Sql;
use Zend\Db\Adapter\Driver\DriverInterface;
use Zend\Db\Adapter\StatementContainerInterface;
use Zend\Db\Adapter\ParameterContainer;
use Zend\Db\Adapter\Platform\PlatformInterface;
use Zend\Db\Sql\Select as ZendSelect;
class Select extends ZendSelect
{
...
protected function processJoins(PlatformInterface $platform, DriverInterface $driver = null, ParameterContainer $parameterContainer = null)
{
...
// process joins
$joinSpecArgArray = array();
foreach ($this->joins as $j => $join) {
...
$joinSpecArgArray[$j][] = ($join['on'] instanceof ExpressionInterface)
? $this->processExpression($join['on'], $platform, $driver, $this->processInfo['paramPrefix'] . 'join' . ($j+1) . 'part')
: $platform->quoteIdentifierInFragment($join['on'], array('=', 'AND', 'OR', '(', ')', 'BETWEEN', '<', '>', '!', 'IS', 'NULL', 'NOT', '"')); // on
...
}
return array($joinSpecArgArray);
}
...
}

SQL query to cakePHP format (invalid json result)

Hello I have a PostgreSQL query that I would like to write using cakePHP format
SELECT
id,
title,
author,
postdate,
postcontent,
userID
FROM posts
WHERE
userID = 12
AND id IN (SELECT articleID FROM favourites WHERE articlesUserID = 12)
ORDER BY postdate DESC;
this is the format my query has right now in cakePHP :
$favouritearticles = $this->Favourite->query('SELECT id, title, author, postdate, postdatecreation, posteditdate, postcontent, "userID" FROM posts WHERE "userID" = '.$userID.'AND id IN (SELECT lngblogarticleid FROM favourites WHERE lngloginuserid = '.$userID.') ORDER BY postdate DESC');
It's working but if echo json_encode the result like this :
echo json_encode($favouritearticles);
I get an invalid json format like the following :(checked with JSONLint)
[
[
{
"id": 2,
"title": "Prison Or Treatment For the Mentally ill ",
"author": "mike123",
"postdate": "March 12, 2013 at 6:46 pm",
"postdatecreation": "2013-03-12",
"posteditdate": null,
"postcontent": "<p><span>The public revulsion over repeated mass shootings has placed mental health in the spotlight. This is both good and bad.<\/span><\/p>",
"userID": 34
}
]
][
]
So I thought that maybe I should rewrite my query using cakePHP format "using find method" something like :
$favouritearticles = $this->Favourite->find('all',array('conditions'=>array(".........
however the query is quite complex and I don't see how to do so.
Thank you for any help.
Format of JSON is fine except for extra [ ] at the end.
If you still want to rewrite the query in CakePHP format, use following:
private function getFavouritePostsByUserId($userId) {
$db = $this->Post->getDataSource();
$subQuery = $db->buildStatement(
array(
'fields' => array('Favourite.articleID'),
'table' => $db->fullTableName($this->Favourite),
'alias' => 'Favourite',
'conditions' => array(
'Favourite.articlesUserID' => $userId
),
),
$this->Favourite
);
$subQuery = 'Post.id IN (' . $subQuery . ') ';
$subQueryExpression = $db->expression($subQuery);
$conditions = array($subQueryExpression, 'Post.userID' => $userId);
$fields = array('Post.*');
$order = 'Post.postdate DESC';
$this->Post->find('all', compact('conditions', 'fields', 'order'));
}