Is it possible to do this sort of query in zend frameowrk?
SELECT *
FROM `relations`
WHERE (root_type, root_id) IN ( ("PRJ", 12), ("PRJ", 13), ("GRP", 42))
I only found a way to make a query with a IN clause on one column but not on two columns.
You can JOIN them instead, like this:
SELECT r.*
FROM relations r
INNER JOIN
(
SELECT 'PRJ' AS root_typ, 12 AS root_id
UNION ALL
SELECT 'PRJ', 13
UNION ALL
SELECT 'GRP', 42
) AS t ON r.root_type = t.root_type
AND r.root_id = t.root_id;
This is a ZF1 query using Zend_Db_Statement:
//common way to aquire currently selected db adapter
$db = Zend_Db_Table::getDefaultAdapter();
//$db is the currently selected database adapter.
$stmt = $db->query(
SELECT * FROM `relations`
WHERE (root_type, root_id)
IN ( ("PRJ", 12), ("PRJ", 13), ("GRP", 42))
);
This answer is not intended to be snide. For complex database queries, Zend_Db_Statement is often the easiest/simplest/best way to perform the query.
If you would like an explanation more tailored to your needs, please provide more info on your structure. In Zend Framework (1 or 2) there are often many ways to accomplish any given task.
Related
I am starting to get headaches over this so I thought I just post it here.
I have two tables that are related through a pivot table (as it is a many-to-many relationship). I use Laravel and Eloquent (but general help on how to achieve this with normal SQL queries is also highly appreciated).
I want to order the first table based a column of the second one but the column needs to be "aggregated" for this.
Example with Cars that are shared by many drivers and can have different colors:
Car-Table: [id, color]
Driver-Table: [id, name]
Car.Driver-Table: [car_id, driver_id]
I need a query that gets all drivers that only drive red cars and then all that don't drive red cars.
I have to use a query because I'll maybe do other things (like filtering) on this query afterwards and want to paginate in the end.
I already use queries that get either one of the two groups. They look like this:
In the Driver model:
public function redCars() {
return $this->cars()->where('color', 'red');
}
public function otherColoredCars() {
return $this->cars()->where('color', '<>', 'red');
}
And then in somewhere in a controller:
$driversWithOnlyRedCars = Driver::whereDoesntHave('otherColoredCars')->get();
$driversWithoutRedCars = Driver::whereDoesntHave('redCars')->get();
Is there a way to combine these two?
Maybe I am just thinking completely wrong here.
Update for clarification:
Basically I would need something like this (ot any other way that would lead to the same outcome)
$driversWithOnlyRedCars->addTemporaryColumn('order_column', 0); // Create temporary column with value 0
$driversWithoutRedCars->addTemporaryColumn('order_column', 1);
$combinedQuery = $driversWithOnlyRedCars->combineWith($driversWithoutRedCars); // Somehow combine both queries
$orderedQuery = $combinedQuery->orderBy('order_colum');
$results = $combinedQuery->get();
Update 2
I think, I found out how to get near my goal with raw queries.
Would be something like this:
$a = DB::table(DB::raw("(
SELECT id, 0 as ordering
FROM drivers
WHERE EXISTS (
SELECT * FROM cars
LEFT JOIN driver_car ON car.id = driver_car.car_id
WHERE driver.id = driver_car.driver_id
AND cars.color = 'red'
)
) as only_red_cars"));
$b = DB::table(DB::raw("(
SELECT id, 1 as ordering
FROM drivers
WHERE EXISTS (
SELECT * FROM cars
LEFT JOIN driver_car ON car.id = driver_car.car_id
WHERE driver.id = driver_car.driver_id
AND cars.color <> 'red'
)
) as no_red_cars"));
$orderedQuery = $a->union($b)->orderBy('ordering');
Now the problem is that I need the models ordered like this and paginated in the end so this is not really an answer to my question. I tried to convert this back to models but I didn't succeed yet. What I tried:
$queriedIds = array_column($orderedQuery->get()->toArray(), 'id');
$orderedModels = Driver::orderByRaw('(FIND_IN_SET(drivers.id, "' . implode(',', $queriedIds) . '"))');
But looks like FIND_IN_SET only allows for a column of the table as second parameter. Is there another way to get the Models in the right order out of the ordered union query?
You can use a UNION query:
$driversWithOnlyRedCars = Driver::select('*', DB::raw('0 as ordering'))
->whereDoesntHave('otherColoredCars');
$driversWithoutRedCars = Driver::select('*', DB::raw('1 as ordering'))
->whereDoesntHave('redCars');
$drivers = $driversWithOnlyRedCars->union($driversWithoutRedCars)
->orderBy('ordering')
->orderBy('') // TODO
->paginate();
How do you want drivers with the same ordering to be ordered? You should add a second ORDER BY clause to get a consistent order every time you execute the query.
This is the best I got:
$driversWithOnlyRedCars = Driver::whereHas('cars',function($q){
$q->where('color', 'red');
})->withCount('cars')->get()->where('cars_count',1);
How to convert this query to laravel db query.
SELECT * FROM {
Select * from organizers
Order by organizers.rank
} Group by t.department
This is simplified version of query. In real the inner query has more where clause and built using laravel db query.
Edit: I am aware of raw query. But that's not what I am looking for. Inner query is complex and has lots of conditional where clause. I would like to retain the db query object I used there.
You can have 2 different query builders and merge their binding like below :
$innerQuery = DB::table('organizers')->orderBy('organizers.rank');
$mainQuery = DB::table(DB::raw('(' . $innerQuery->toSql() . ') as t'))
->mergeBindings($innerQuery->getQuery())
->groupBy('t.department')
->get();
This will also help you retail the $innerQuery builder instance for your later use as you have mentioned in the question.
I think you will have to execute a raw query.
$result = DB::select("SELECT * FROM (
Select * from organizers
Order by organizers.rank
) Group by t.department");
reference: https://laravel.com/docs/5.7/queries#raw-expressions
I need to create native SQL query with couple of unions and subqueries. It'll look approximately like this:
SELECT res.id, COUNT(*) as count_ids
FROM (
SELECT a.id FROM ... a WHERE ... LIKE ('%:param%')
UNION ALL
SELECT b.id FROM ... b WHERE ... LIKE ('%:param%')
UNION ALL
...
) res
GROUP BY res.id
ORDER BY count_ids asc
Result won't match any Entity I use in my application. Is it possible to create ResultSetMapping with "anonymous" object? Or is it, at least, possible to create an Entity that wouldn't create table next time I update schema, so I can map results to it?
Or is there any other Doctrine-friendly way to deal with such query? Making changes to database isn't possible though, as I'm dealing with legacy stuff that cannot be touched. I'd also strongly prefer if I did everything on database side, not involving much of PHP in it.
Do you have a particular need to map results to a domain object? If not, you could use the DBAL to make a plain old query, which will return an array, as detailed in the Symfony2 cookbook and the Doctrine DBAL documentation:
$conn = $this->container->get('database_connection');
$sql = 'SELECT res.id, COUNT(*)...';
$rows = $conn->query($sql);
Use addScalarResult method of ResultSetMapping
$rsm = new ResultSetMapping();
$rsm->addScalarResult('cnt', 'cnt');
$rsm->addScalarResult('id', 'id');
$query = $this->em->createNativeQuery('SELECT count(*) AS cnt, id_column as id FROM your_table group by id', $rsm);
$result = $query->getResult();
var_dump($result);
Result array:
array (size=1)
0 =>
array (size=2)
'cnt' => int 1
'id' => int 15
I have query like this:
SELECT * FROM activity
WHERE (((userId = 1 OR userId IN(SELECT userId FROM follower WHERE followerId = 1))
AND activityType IN(1, 2, 3))
OR (targetId = 24 AND aType IN(1, 2, 3, 4, 5)))
ORDER BY id DESC;
I have try to use model()->findAllBySql($sql) and it works. But I want to make it using CDbCriteria, if you have another solutions let me know it :D
You could still build this statement with a CDbCriteria I think... something like:
$criteria=new CDbCriteria;
$criteria->condition = '
(
(
userId = 1 OR
userId IN (SELECT userId FROM follower WHERE followerId = 1)
)
AND activityType IN(1, 2, 3)
)
OR (
targetId = 24
AND aType IN(1, 2, 3, 4, 5)
)
';
$criteria->order = 'id DESC';
$results=Activity::model()->findAll($criteria);
As this point you might as well just write a regular SQL statement, but there might be some benefits to doing it this way: binding params, merging criteria, adding additional criteria, etc.
As long as your plain SQL works, you're safe. There are many times when I have to throw Active Record away and just get the job done in the ol' saner way.
I tried to translate this query into a readable CDbCriteria construction. Bad idea. Yii sucks when it comes to query complex data.
The answer can be found here:
http://www.yiiframework.com/doc/guide/1.1/en/database.dao#executing-sql-statements
In your case:
$sql = 'SELECT * FROM activity';
$sql .= 'WHERE (((userId = 1 OR userId IN(SELECT userId FROM follower WHERE followerId = 1))';
$sql .= 'AND activityType IN(1, 2, 3))';
$sql .= 'OR (targetId = 24 AND aType IN(1, 2, 3, 4, 5)))';
$sql .= 'ORDER BY id DESC';
$connection = Yii::app()->db;
$command = $connection->createCommand($sql);
$results = $command->queryAll();
#pestaa is right that sometimes you have to throw active record out the window. This is especially true if you're doing mass updates where looping through numerous models is horribly inefficient.
Just use CSqlDataProvider
http://www.yiiframework.com/doc/api/1.1/CSqlDataProvider
Disclaimer: I know it's not precise answer to this particural question but it might help work around the problem that was given. I suspect the main purpose of this question is getting way to use CGridView, CListView etc. with arbitrary SQL.
I use CDbCriteria for complex queries in which I use the with feature.
You can build complex criteria like this:
$dbCriteria->with=array(
'<relation1>'=>array( 'condition'=>'<condition for given relation>',
'with'=>array('<relation2:relation of relation1>'
=>array(<conditions for relation2>)
)
'scopes'=><scopes for relation1>
)
);
I have not checked how OR can get into play here.
By using scopes, you can also insert some more complex criteria and still keep your search condition readable.
This is pretty powerful. I didn't see a complete 'tutorial' about this yet; I kind of concluded this from the source code.
Is there any way to enumerate tables used in mysql query?
Lets say I have query :
SELECT * FROM db_people.people_facts pf
INNER JOIN db_system.connections sm ON sm.source_id = pf.object_id
INNER JOIN db_people.people p ON sm.target_id = p.object_id
ORDER BY pf.object_id DESC
And I want in return array:
$tables = array(
[0] => 'db_people.people_facts',
[1] => 'db_system.connections',
[2] => 'db_people.people',
);
Yes, you can get information about tables and columns that are part of a query result. This is called result set metadata.
The only PHP solution for MySQL result set metadata is to use the MySQLi extension and the mysqli_stmt::result_metadata() function.
$stmt = $mysqli->prepare("SELECT * FROM db_people.people_facts pf
INNER JOIN db_system.connections sm ON sm.source_id = pf.object_id
INNER JOIN db_people.people p ON sm.target_id = p.object_id
ORDER BY pf.object_id DESC");
$meta = $stmt->result_metadata();
$field1 = $meta->fetch_field();
echo "Table for field " . $field1->name . " is " . $field1->table . "\n";
You'll have to build the array of distinct tables used in the query yourself, by looping over the fields.
Depending on what you're using it for, MySQL's EXPLAIN could do the trick for you:
http://dev.mysql.com/doc/refman/5.0/en/explain.html
The solution marked as good will return only the result tables. But if you do the next query it will fail:
SELECT users.* FROM users, cats, dogs WHERE users.id = cats.user_id
Will return only users and not cats and dogs tables.
The best solution is find a good parser, another solution is using REGEX and EXPLAIN query (more info in the next link):
Get mysql tables in a query
But I think that another good solution is list all tables and search them inside the query, you can cache the list of tables.
EDIT: When searching for tables, better use a preg like:
// (`|'|"| )table_name(\1|$)
if(preg_match('/(`|\'|"| )table_name(\1|$)/i', $query))
// found
If not, it can return false positives with for example "table_name2", "table_name3"... table_name will return FOUND two times.