Hello everyone
www\sites\all\modules\views\includes\view.inc uses such code to determine what number of records are there in a recordset, that the $count_query returns:
$count_query = 'SELECT COUNT(*) FROM (' . str_replace(array_keys($replacements), $replacements, $count_query) . ') count_alias';
(you can find it at the line 724)
The problem is it ALWAYS uses a subquery to get the data. Which means, that MySQL has to create a temporary table, copy some data there, and only count what is the total number of records. What I want - is the ability, to set $count_query without subquery. Which would be much faster btw.
So I suggest to reaplace that string with the following code:
if (!preg_match('/select[^,]+count\(\*\)[^,]+from/i',$count_query))
$count_query = 'SELECT COUNT(*) FROM (' . $count_query . ') count_alias';
What it does is checking if there any statement between "select" and "from" keywords, except for "count(*)" or "count(*) AS `some_alias_here`". And if it finds, that $count_query starts with "select count(*) from" than there is no reason to make a subquery for it. But if the SELECT statement includes some other keywords (which are seperated by comma, that's why i'm looking for non-comma statements in regexp), than the codes adds a subquery and it works just like it used to.
How can we use it:
function hook_views_pre_execute(&$view)
{
if($view && $view->name === '')
{
switch($view->current_display){
case '' :
$view->build_info['count_query'] ="SELECT count(*) FROM dru_node node WHERE (node.type in ('family_individual')) AND (node.status <> 0)";
/*
Which takes 0.08 sec
Instead of:
$view->build_info['count_query'] ="SELECT nid FROM dru_node node WHERE (node.type in ('family_individual')) AND (node.status <> 0)";
which will be transformed to:
SLEECT COUNT(*) FROM (SELECT nid FROM dru_node node WHERE (node.type in ('family_individual')) AND (node.status <> 0)) `count_alias`
0.2 sec
or even:
SELECT COUNT( * )
FROM (
SELECT node.nid AS nid
FROM dru_node node
INNER JOIN dru_family_individual family_individual ON node.nid = family_individual.nid
WHERE (node.type IN ('family_individual'))AND (node.status <>0)) count_alias
0.5-0.6 sec
*/
break;
}
}
}
| Comment | File | Size | Author |
|---|---|---|---|
| #2 | total_number_of_records_optimised_count-views-1006810-2.patch | 825 bytes | soul88 |
| #1 | total_number_of_records_optimised_count-views-1006810.patch | 758 bytes | soul88 |
Comments
Comment #1
soul88//Can't modify the issue for some reason, so here is the last code tagged properly
patch here:
Comment #2
soul88Sorry for my distraction, here is the correct patch.
Comment #3
merlinofchaos commentedI don't understand this. In what situation would Views have already generated a count query as the prime query? As near as I can tell, this actually does nothing because Views won't normally generate the kind of query you're looking for.
The main reason that we use the subquery is that there are simply numerous instances (usually when group by or aggregation functions are involved) that the other method of COUNT()ing fails.
One thing that's new, since this method was created, is that we can now actually tell when aggregation is being used. So perhaps we can test on that instead, and maybe use the "old way" of generating a count query. But we have to be careful with that. It has to be bulletproof, which means it has to be tested with a LOT of different views to be sure that it won't provide false results.
In any case, this absolutely will not change for 2.x, so moving this to 3.x
Comment #4
soul88Views itself don't produce such a query. But the idea is to let a programmer to control the queries that are going to be executed. For example: I had a data list of about 80 000+ items that had to be displayed with pagination.
For some reason: 2 of them (queries) took 3 to 9 sec on my laptop.
The solution I found was the following:
Though I didn't hack any module, the solution is still pretty hackish, as for me.
The reason is the following:
1. Views always add db_query_range for the query. Which is not always suitable (when pagination is to be made in subquery).
2. Views don't react on all of the $view->pager array values, and always make a subquery from the count query.
for example: $pager_page_array = isset($_GET['page']) ? explode(',', $_GET['page']) : array();
So, now you can see the use case where the issue was born. And my vision of solving the second problem.
thnx for your reply.
Comment #5
IncrediblyKenzi commentedI ran into this as well.. we had a view that had to parse 400k records.. on InnoDB this will take ages to generate a count even though we were only displaying 15 nodes at a time.
The workaround we had for non-views generated queries was to cache the query count on a per-block basis; right now there's no way to override the query that views generates for the count.
Perhaps a hook to override the count query would solve this?
Comment #6
damien tournoud commentedJust a quick note to mention that the problem is not actually the subquery by itself, but the TEXT/BLOB columns that are pulled by it, because those forces MySQL to materialize the subquery.
Compare:
Comment #7
merlinofchaos commentedSure there is. In hooks_views_pre_execute() change $view->build_info['count_query'] to whatever you want.
In 3.x, you also have pager plugins which can generate whatever count query they want.
Comment #8
rjbrown99 commentedOne note that I found after some trial and error -
$view->build_info['count_query'] (at least in the slightly older 3.x-dev version I am using), passes the query to db_rewrite_sql in the includes/view.inc execute() function. This db_rewrite_sql function also includes $this->base_table and $this->base_field.
Make sure your new query doesn't switch base_table or base_field. In my case I switched field, and the pager query then only works for users with elevated access rights.
Comment #9
maria_zk commentedSoul88 thank you SOOOO much for this!!!
It really saved the day!
Comment #10
mototribe commentedI believe the count query could be further optimized by removing any table joins that don't have a WHERE clause.
For example, take a look at this monster count query - it takes 3500ms to run:
It shouldn't include:
LEFT JOIN votingapi_cache ...
LEFT JOIN history history ...
LEFT JOIN node_counter ....
Those JOINs are just used to get additional fields, not to restrict the view.
However, I see that it could get complicated figuring that out in views ...
Comment #11
mototribe commentedThis is a pretty slick solution:
http://drupal.stackexchange.com/questions/31444/how-do-i-optimize-a-view...
it's still not perfect, for example, I'm able to generate this count query:
SELECT COUNT(*) AS expression FROM (SELECT 1 AS expression FROM users u WHERE (status <> '0') ) subquerywhich still takes 200ms to run (200k user records)
when the ideal query would be something like
SELECT COUNT(*) FROM users u WHERE (status <> '0')which would probably take < 1ms to run.
Would it be possible to inject such an optimized count query into views?
Comment #12
soul88mototribe, it's much improved in the version for D7. In D7 version first you get only the needed IDs which fit the conditions (so we have only joins on the tables we're filtering on). And after that we retrieve all the needed data by these IDs.
Comment #13
mustanggb commented