I'm having an issue and have tried a lot of methods to solve it -
What I'm trying to do is modify a WordPress WP_Query before it runs: to query based on a post's parent ID rather than it's own ID. To be specific, I have a query that looks something like this:
SELECT SQL_CALC_FOUND_ROWS wp_posts.ID
FROM wp_posts
INNER JOIN wp_term_relationships
ON (wp_posts.ID = wp_term_relationships.object_id)
INNER JOIN wp_postmeta
ON ( wp_posts.ID = wp_postmeta.post_id )
WHERE 1=1 AND wp_posts.ID IN (1,2)
AND ( wp_term_relationships.term_taxonomy_id IN (35)
AND wp_posts.post_type = 'product_variation'
AND ((wp_posts.post_status = 'publish'))
GROUP BY wp_posts.ID
ORDER BY wp_postmeta.meta_value DESC LIMIT 0, 12;
The posts passed in are woocommerce product variations. The issue is that I want to return all product variations within the specific taxonomy, but the term_taxonomy_id of 35 is referenced only by the parent. So in the first join condition above, I believe I need:
ON(wp_posts.post_parent = wp_term_relationship.object_id)
Should be easy enough, but I can't figure out a way to modify this query suitably before it runs. Here are some things I have tried:
The tax_query has a primary_id_column that seems like it would be the right value to modify. I tried modifying the args before creating the query like this:
$args['tax_query']['primary_id_column'] = 'post_parent';
$wp_query = new WP_Query( $args );
In this case, the query vars are not modified whatsoever. I also tried several ways of modifying primary_id_column after the object is created, like these:
$wp_query->tax_query->primary_id_column = 'post_parent';
$wp_query->tax_query->get_sql('wp_posts', 'post_parent');
$wp_query->set('primary_id_column','post_parent');
These do in fact modify the query vars, but no matter what - the $wp_query->request string always contains the join condition on wp_posts.ID rather than wp_posts.post_parent. I wondered if at the point I make these changes the query has already been generated and isn't changed before I get the posts. I tried running the above lines in a hook for this reason, using:
add_action( 'pre_get_posts', 'custom_use_parent' );
But no luck. If anybody has a suggestion for how I could modify the join condition in this query, it would be greatly appreciated! Thanks a ton in advance.
Related
I am working on a WordPress site that uses the Advanced Custom Field plugin and the Advanced Custom Field repeater plugin, so I cannot change the database structure.
The custom fields (and their values) for each post are stored in a table called post_meta with the following fields:
meta_id (autoincrement), post_id, meta_key, meta_value
For regular custom fields, it's easy. Let's say post 200 has "John Doe" for field "full_name", then it would be stored like this:
xxx, 200, full_name, John Doe
The problem is when the field has a subset of repeated fields, the way it is stored is a bit more complicated. For the meta_key, the value is {fieldName}_{rowNumber}_{subfieldName}.
For example, in this Wordpress what I am trying to do is to store the players of a team that participatd in a certain match, and how many minutes they played that match (here is an screenshot):
The general field is called planilla (players sheet), so every value is stored like this:
planilla_1_jugador, planilla_1_minutos (player_id and minutes played for the first row)
So, my problem starts when I want to count all the matches played by a player. I could count all the rows in post_meta where meta_value is my player id, but that would count matches where the player played 0 minutes (and I don't want to). This is the query that does that, by the way:
SELECT COUNT(*) as total from $wpdb->postmeta INNER JOIN $wpdb->posts ON ( $wpdb->posts.ID = $wpdb->postmeta.post_id ) WHERE( $wpdb->postmeta.meta_key LIKE 'planilla_%_jugador' AND $wpdb->postmeta.meta_value = {player_id} ) AND $wpdb->posts.post_type = 'partido' AND (($wpdb->posts.post_status = 'publish'))
If I want to check for the played minutes, I would need to add a join that matches with the same position in the players sheet (planilla). If the table would have an extra column called meta_key_index, that would store the number, it would be as easy as this:
SELECT COUNT(*) as total from wp_postmeta pm1 INNER JOIN wp_posts ON ( wp_posts.ID = pm1.post_id )
INNER JOIN wp_postmeta pm2 ON (pm1.post_id = pm2.post_id)
WHERE( pm1.meta_key LIKE 'planilla_%_jugador' AND pm1.meta_value = 420 )
AND (pm2.meta_key LIKE 'planilla_%_minutos_jugados' and pm2.meta_value > 0)
AND (pm1.meta_key_index = pm.meta_key_index)
AND wp_posts.post_type = 'partido' AND ((wp_posts.post_status = 'publish')
But it hasn't, so I need to do the join using a regular expression or something like that. I thought of several things but nothing seem to make it possible to work.
What I am doing right now, I doing 20 queries (max amount of players per sheet) where the key is hardcoded, so instead o doing planilla_%_jugador and I am querying planilla_1_jugador, and so on, and then summing all the results at the end. But the 20 queries are slow and I'd prefer to solve everything doing just one query.
I hope this post makes sense and thanks in advance.
I have used WPAllImport to import data from a CSV-file to Advanced Custom Fields.
I now want to put them back together with a SQL query, but dont know how to do it.
I've tried WPDataTables, but when I choose 5 or more tables, WPDataTables stops.
If I pick 2, I get this code
SELECT posts_podukter.post_title AS podukter_post_title,
podukter_meta_produkter_0_pris_tbl.meta_value AS podukter_meta_produkter_0_pris
FROM beta_h3L_posts AS posts_podukter
INNER JOIN (SELECT podukter_meta_produkter_0_pris_tbl_posts.ID as id, meta_value, meta_key FROM beta_h3L_postmeta AS podukter_meta_produkter_0_pris_tbl_postmeta INNER JOIN beta_h3L_posts AS podukter_meta_produkter_0_pris_tbl_posts ON podukter_meta_produkter_0_pris_tbl_postmeta.post_id = podukter_meta_produkter_0_pris_tbl_posts.ID AND podukter_meta_produkter_0_pris_tbl_posts.post_type = 'podukter') AS podukter_meta_produkter_0_pris_tbl
ON podukter_meta_produkter_0_pris_tbl.meta_key = 'produkter_0_pris' AND podukter_meta_produkter_0_pris_tbl.id = posts_podukter.ID
WHERE 1=1
AND posts_podukter.post_type = 'podukter'
I think this is too much code.
Can someone help me to get on the right way.... :-)
This is what the table should look like
Here is a capture how the table should look like
I would agree that this is "too much code" which sounds sort of ridiculous, but in this case totally applies. That SQL statement that was produced could be written as:
SELECT
post.post_title as podukter_post_title,
postmeta.meta_value as podukter_meta_produkter_0_pris
FROM beta_h3L_posts AS posts
INNER JOIN beta_h3L_postmeta AS postmeta
ON postmeta.post_id = post.ID
AND postmeta.meta_key = 'produkter_0_pris'
WHERE posts.post_type = 'podukter'
If there is another metavalue that you need you can join again to your meta table:
SELECT
post.post_title as podukter_post_title,
postmeta.meta_value as podukter_meta_produkter_0_pris,
postmeta2.meta_value as tilbudspris
FROM beta_h3L_posts AS posts
INNER JOIN beta_h3L_postmeta AS postmeta
ON postmeta.post_id = post.ID
AND postmeta.meta_key = 'produkter_0_pris'
INNER JOIN beta_h3L_postmeta AS postmeta2
ON postmeta.post_id = post.ID
AND postmeta2.meta_key = 'tilbudspris'
WHERE posts.post_type = 'podukter'
I don't know what any of these words mean (besides post and postmeta) so I'm just going to assume that this is right/helpful.
The only thing is that you may want to switch to using a LEFT OUTER JOIN to your postmeta table just in case the meta_key you are after doesn't exist for the post.id you are querying. In that case, with an INNER JOIN the id/post will be dropped from the result set where a LEFT OUTER JOIN will show the id/post record with a blank for whatever that corresponding meta_value is that you are joining in.
i am writing the following SQL statement using my existing wordpress db as a learning environment...
Select
wp.ID,
wpum.user_id,
wpp.post_name
From
wp_users As wp Left Join
wp_usermeta As wpum
On wp.ID = wpum.user_id Left Join
wp_posts As wpp
On wpum.user_id = wpp.post_author
Where
wpp.post_status = 'publish' and
wpum.meta_key = 'nickname' and
wpp.post_name not in ('sample%', 'hello%')
order by
wpp.ID desc
As you can see I have used alias's for my tables and filtered my results of the word press posts to be only 'published' posts and only selecting the nickname to avoid duplicate results by user_id.
My issue is the wildcards do not appear to do anything.. my results are as returned with the sample & header posts still showing.
However, this would work as:
Select
wp.ID,
wpum.user_id,
wpp.post_name
From
wp_users As wp Left Join
wp_usermeta As wpum
On wp.ID = wpum.user_id Left Join
wp_posts As wpp
On wpum.user_id = wpp.post_author
Where
wpp.post_status = 'publish' and
wpum.meta_key = 'nickname' and
wpp.post_name not like 'sample%' and
wpp.post_name not like 'hello%'
order by
wpp.ID desc
My output is with the desired results removed.. anybody able to help me out? The second example is perfectly usable... I would just like to know my alternatives to learn good practices.
I've tried googling and using a few examples, but not having much luck.
Thanks all
Apol's if my post is wrongly formatted or this has been answered before, again I have searched!
I have a meta key which is set by a select drop down so a user can select an option between 1 and 14 and then save their post. I want the posts to display on the page from 1 to 14 ordered by date but if the user creates a new set of posts the next day I also want this to happen so you have posts 1 to 14 each day displaying in that order.. the SQL i have so far is as follows
SELECT SQL_CALC_FOUND_ROWS
wp_postmeta.meta_key,
wp_postmeta.meta_value,
wp_posts.*
FROM wp_posts
INNER JOIN wp_postmeta ON (wp_posts.ID = wp_postmeta.post_id)
WHERE 1=1
AND wp_posts.post_type = 'projectgallery'
AND ( wp_posts.post_status = 'publish'
OR wp_posts.post_status = 'private')
AND (wp_postmeta.meta_key = 'gallery_area' )
GROUP BY wp_posts.post_date asc
ORDER BY CAST(wp_postmeta.meta_value AS UNSIGNED) DESC,
DATE(wp_posts.post_date) desc;
Which gives me the following output noticte thatthe posts entered at different dates with either 1 or 3 show up in sequence, ideally i want the latest ones to display directly after 14 so it starts over again. the number 14 should not be static either as if someone adds another option to the select then it will increase and decrease if an option is removed.
GROUP BY is confusingly named. It only makes sense when there's a SUM() or COUNT() or some such function in the SELECT clause. It's not useful here.
The canonical way of getting a post_meta.value into a result set of post items is this. You're close but this makes it easier to read.
SELECT SQL_CALC_FOUND_ROWS
ga.meta_value gallery_area,
p.*
FROM wp_posts p
LEFT JOIN wp_postmeta ga ON p.ID = ga.post_id AND ga.meta_key = 'gallery_area'
WHERE 1=1
AND p.post_status IN ('publish', 'private')
AND p.post_type = 'projectgallery'
Notice the two parts of the ON clause in the JOIN. That way of doing the SQL gets you just the meta_key value you want cleanly.
So, that's your result set. You'll get a row for every post. If the metadata is missing, you'll get a NULL value for gallery_area.
Then you have to order the result set the way you want. First order by date, then order by gallery_area, like so:
ORDER BY DATE(p.post_date) DESC,
0+gallery_area ASC
The 0+value trick is sql shorthand for casting the value as an integer.
Edit. Things can get fouled up if the meta_value items contain extraneous characters like leading spaces. Try diagnosing with these changes. Put
DATE(p.post_date) pdate,
0+ga.meta_value numga,
ga.meta_value gallery_area
in your SELECT clause. If some of the numga items come up zero, this is your problem.
Also try
ORDER BY DATE(p.post_date) DESC,
0+TRIM(gallery_area) ASC
in an attempt to get rid of the spaces. But they might not be spaces.
I am not an expert in sql.
My wordpress started to return timeouts and respond really slow.
when I started digging, I noticed that the slow_query log has a lot to tell me.
unfortunately I have a lot of slow queries.
for example:
# Time: 140425 17:03:29
# User#Host: geektime[geektime] # localhost []
# Query_time: 7.024031 Lock_time: 0.000432 Rows_sent: 0 Rows_examined: 0
SET timestamp=1398434609;
SELECT wp_posts.*
FROM wp_posts
INNER JOIN wp_postmeta ON (wp_posts.ID = wp_postmeta.post_id)
INNER JOIN wp_postmeta AS mt1 ON (wp_posts.ID = mt1.post_id)
LEFT JOIN wp_postmeta AS order1 ON order1.post_id = wp_posts.ID
AND order1.meta_key = '_event_start_date'
LEFT JOIN wp_postmeta AS order2 ON order2.post_id = wp_posts.ID
AND order2.meta_key = '_event_start_time'
WHERE 1=1
AND wp_posts.post_type = 'event'
AND (wp_posts.post_status = 'publish'
OR wp_posts.post_status = 'future'
OR wp_posts.post_status = 'draft'
OR wp_posts.post_status = 'pending')
AND ((wp_postmeta.meta_key = '_event_start_date'
AND CAST(wp_postmeta.meta_value AS CHAR) BETWEEN '2014-04-11' AND '2014-04-17')
OR (mt1.meta_key = '_event_end_date'
AND CAST(mt1.meta_value AS CHAR) BETWEEN '2014-04-11' AND '2014-04-17'))
GROUP BY wp_posts.ID
ORDER BY order1.meta_value,
order2.meta_value ASC;
The columns post_id, meta_id and meta_key are indexed in wp_postmeta table.
The columns ID, post_name, post_type, post_status, post_date,post_parent, post_author and guid are indexed in wp_posts table.
however, the columns ID and GUID are indexed twice, is it bad?
and there are 4 indexs with the same key_name: type_status_date, is it bad?
How could it be that I have 60K rows in wp_posts and 3M rows in wp_postmeta?
I know its a lot to ask but I really tried to understand from researching online.
thanks in advance.
however, the columns ID and GUID are indexed twice, is it bad?
There are two different columns, so no, unless you're meaning that both have two indexes on them — in which case yes, it's bad and likely a bug in one of your theme or plugins (or a prior bug in WP itself).
and there are 4 indexs with the same key_name: type_status_date, is it bad?
Same as above: if you mean four identical indexes, it's either a theme or plugin or WP bug and you can safely drop the duplications.
How could it be that I have 60K rows in wp_posts and 3M rows in wp_postmeta?
Because the WP meta API sucks and enforces a database anti-pattern called the Entity Attribute Value (also known as EAV):
http://en.wikipedia.org/wiki/Entity-attribute-value_model
Cursory googling SO will yield plenty of threads that explain why it is a bad idea to store data in an EAV or equivalent (json, hstore, xml, whatever) if the stuff ever needs to appear in e.g. a where, join or order by clause.
You can see the inefficiencies first-hand in form of the slow query you highlighted. The query is joining the meta table four times, does so twice with a cast operator to boot — and it casts the value to char instead of date at that. Adding insult to injury, it then proceeds to order rows using values stored within it. It is a recipe for poor performance.
There is, sadly, little means of escaping the repulsive stench of this sewage, short of writing your own plugins that create proper tables to store, index and query the data you need in lieu of using the WP meta API, its wretched quoting madness, and the putrid SQL that results from using it.
One thing that you can do as temporary duct tape and WD-40 measure while you rewrite the plugins you're using from the ground up, is to toss callbacks on one or more of the filters you'll find in the giant mess of a class method that is WP_Query#get_posts(). For instance the posts_request filter, which holds the full and final SQL query, allows you to rewrite anything to your liking using regex-foo. It's no magic bullet: doing so will allow you to fix bugs such as integer values getting sorted lexicographically and such, as well as toss in very occasional query optimizations; little more.
Edit: Upon re-reading your query, methinks you're mostly in luck with respect to that last point. Your particular query features the following abomination:
INNER JOIN wp_postmeta ON (wp_posts.ID = wp_postmeta.post_id)
INNER JOIN wp_postmeta AS mt1 ON (wp_posts.ID = mt1.post_id)
LEFT JOIN wp_postmeta AS order1 ON order1.post_id = wp_posts.ID
AND order1.meta_key = '_event_start_date'
LEFT JOIN wp_postmeta AS order2 ON order2.post_id = wp_posts.ID
AND order2.meta_key = '_event_start_time'
Two of those have _event_start_date in common, so you can factor it out:
SELECT wp_posts.*
FROM wp_posts
INNER JOIN wp_postmeta ON (wp_posts.ID = wp_postmeta.post_id)
AND wp_postmeta.meta_key = '_event_start_date'
INNER JOIN wp_postmeta AS mt1 ON (wp_posts.ID = mt1.post_id)
AND mt1.meta_key = '_event_end_date'
INNER JOIN wp_postmeta AS order2 ON order2.post_id = wp_posts.ID
AND order2.meta_key = '_event_start_time'
WHERE 1=1
AND wp_posts.post_type = 'event'
AND (wp_posts.post_status = 'publish'
OR wp_posts.post_status = 'future'
OR wp_posts.post_status = 'draft'
OR wp_posts.post_status = 'pending')
AND (CAST(wp_postmeta.meta_value AS CHAR) BETWEEN '2014-04-11' AND '2014-04-17'
OR CAST(mt1.meta_value AS CHAR) BETWEEN '2014-04-11' AND '2014-04-17')
GROUP BY wp_posts.ID
ORDER BY wp_postmeta.meta_value,
order2.meta_value ASC;
Among other things, slow performance is caused by the use of functions like this:
AND CAST(wp_postmeta.meta_value AS CHAR) BETWEEN '2014-04-11' AND '2014-04-17')
Assuming that field is a date field, you will get better performance with something like this:
and wp_postmeta.meta_value >= AStartDateVariable
and wp_postmeta.meta_value < TheDayAfterAnEndDateVariable
That will be even more true if meta_value is indexed. I assume you will be sending these variables as query parmameters.
Holy cow! 3 megarows in postmeta? 60k posts? Something is seriously wrong with your installation.
Is it possible that your events table is open to spammers entering rubbish?
Do you have tons of old expired events that could somehow be purged from your system?
You may be able to get your system back on the air by increasing your timeout value. If you know how to handle php.ini, go find the timeout value and increase it, or ask your hosting company for help.
Are you on one of those $5 per month hosting companies? With sixty thousand events to handle, you may need to upgrade.
The proximate cause of the timeout is obvious. This sequence of code is full-scanning that monster post_meta table TWICE!
Why? It has an OR in it. And it is applying functions to the value of a column.
AND ((wp_postmeta.meta_key = '_event_start_date'
AND CAST(wp_postmeta.meta_value AS CHAR) BETWEEN '2014-04-11' AND '2014-04-17')
OR (mt1.meta_key = '_event_end_date'
AND CAST(mt1.meta_value AS CHAR) BETWEEN '2014-04-11' AND '2014-04-17'))
One of the disadvantages of the WordPress schema when you scale up a site is the generic nature of the postmeta table. This query does date range searches, but it's hard to index a key-value repository like postmeta to optimize those.
Do you know your way around the code of the Events Manager plugin you're using? If so, you may want to investigate optimizing this yourself.
If not, seek support from the Events Manager plugin developer.