MySQL: Do a JOIN based on a regular expression? Is it possible? - mysql

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.

Related

Table MYSQL query

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.

Modifying a WP_Query Request join condition

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.

Wordpress MySQL - custom meta key order by key and date

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.

Getting posts from wordpress returns the same result when present in several cathegories

In the home page of a website I need to return the last 3 posts of the blog, created with wordpress.
I have this code:
SELECT p.post_title, p.post_date, p.post_content, wpr.object_id, dt_blog_terms.name, dt_blog_terms.slug
FROM dt_blog_terms
INNER JOIN dt_blog_term_taxonomy ON dt_blog_terms.term_id = dt_blog_term_taxonomy.term_id
INNER JOIN dt_blog_term_relationships wpr ON wpr.term_taxonomy_id = dt_blog_term_taxonomy.term_taxonomy_id
INNER JOIN dt_blog_posts p ON p.ID = wpr.object_id
WHERE taxonomy = 'category'
AND p.post_type = 'post'
AND p.post_status = 'publish'
AND slug != 'notizie-notifiche'
ORDER BY `post_date` DESC
LIMIT 3
As you can see, I have one cathegory (notizie-notifiche) that I want to exclude. This sql string works, but I have a problem when the post has more than one cathegory. In this case, it is returned once for every cathegory, while I want to show it only once in total.
Any idea?
I thought I could use DISTINCT, but it does not seem to work with this kind of SELECT statement.
I can tell you what you need to do, but I can't modify the query to do it. You use the term "category" in your question, but there is no field by that name in your data.
You need to aggregate your data, at the level you want, and then include a having clause. The following group by may solve your problem:
group by p.id
having sum(case when slug = 'notizie-notifiche' then 1 else 0 end) = 0

MySQL Append results from query to current record?

I have a successful query that currently returns all data except for one table. The last table contains meta data where it is a one to many relationship (one product, multiple records with related data).
Is there a way to append a field with a value to the end of an existing query so that I can still access the record set in PHP with a mysql_fetch_array() command?
essentially I want to query for any records with the product ID, and then append multiple additional fields.
After thinking about it i was actually wondering if I can include an array within the records set (i.e. the array of results for the meta data) and access that through PHP?
Working Query:
SELECT offers.*, c.short_name AS sponsorName, c.logo AS sponsorLogo
FROM offers LEFT JOIN sponsors AS c ON c.id=offers.sponsor ORDER BY end_date ASC
Results:
ID: 43875
category: 1
state: CO
city: Denver
zip: 80221
sponsor: 1
title: The coolest thing ever
duration: 2 years
price: 10
frequency:: Month
Second query that gets the right meta data-
SELECT mo.`name` AS meta_value FROM offer_metas
LEFT JOIN meta_options AS mo ON mo.id = offer_metas.meta_option
WHERE offer_id='48735' ORDER BY meta_option ASC
results:
meta_value:
'5-10 tickets'
'General Admission'
I want to add those two fields to the record up top .. but don't know how to append all fields within a query result into a single already existing record.
--SOLVED--
Query has been adjusted to account for GROUP_CONCAT as follows
SELECT
offers.*,
s.short_name AS sponsorName,
s.logo AS sponsorLogo,
GROUP_CONCAT( mn.title) titles,
GROUP_CONCAT( mo.`name`) metas
FROM offers
LEFT JOIN sponsors AS s ON s.id = offers.sponsir
INNER JOIN offer_metas ON offer_metas.offer_id = offers.id
INNER JOIN meta_options as mo ON offer_metas.meta_option = mo.id
INNER JOIN meta_names as mn ON mo.category = mn.category AND mo.cat_seq = mn.seq
ORDER BY end_date ASC
data results looked good including 2 fields called "titles" and "metas" that looked like this
titles: 'Number of Tickets,Purchased Seats'
metas: '5-10,General Administration'
now THOSE .. I can work with in PHP .. and since they have the same number of elements i'll just parse them into an array i can better work with :)
Further to your comments above, it sounds like GROUP_CONCAT() will give you what you're after. It won't return the meta_values as an array, but rather as a string (e.g. with each value separated by <br/> in order that they can be emitted directly as HTML):
SELECT
offers.*,
c.short_name AS sponsorName,
c.logo AS sponsorLogo,
GROUP_CONCAT(mo.name ORDER BY meta_option ASC SEPARATOR '<br/>') AS meta_values
FROM
offers
LEFT JOIN sponsors AS c ON c.id = offers.sponsor
LEFT JOIN offer_metas AS o ON o.offer_id = offers.id
LEFT JOIN meta_options AS mo ON mo.id = offer_metas.meta_option
ORDER BY end_date ASC;
Note that, if you need to escape the meta values for any HTML they might contain, you'll need to choose a different separator (perhaps U+001E, INFORMATION SEPARATOR TWO?) and have PHP replace that separator with suitable HTML after escaping any contained HTML.