Consolidate many queries into one - mysql

I'm looking to achieve my goals as described below using one single query, as opposed to multiple queries as I currently have to use.
The problem I am having is that data from the wp_usermeta table is stored as a meta_key/meta_value in 2x columns, as opposed to each type of data having it's own column. While the reasoning for this makes sense, it does mean I'm stumped at this point.
The database in question is for a WordPerss site, should anyone wish to replicate this.
My goals
For each user who has at least one image/video post I need to grab the following details -
+-----------------------+---------------+------------------------------------------------+
| Description | Table | Column |
+-----------------------+---------------+------------------------------------------------+
| User ID | `wp_users` | `ID` |
| Display Name | `wp_users` | `display_name` |
| First Name | `wp_usermeta` | `meta_value` (WHERE `meta_key` = 'first_name' |
| Description | `wp_usermeta` | `meta_value` (WHERE `meta_key` = 'description' |
| Facebook profile link | `wp_usermeta` | `meta_value` (WHERE `meta_key` = 'facebook' |
| Google+ profile link | `wp_usermeta` | `meta_value` (WHERE `meta_key` = 'google_plus' |
| Twitter profile link | `wp_usermeta` | `meta_value` (WHERE `meta_key` = 'twitter' |
+-----------------------+---------------+------------------------------------------------+
My current solution
First I select the ID and display name of all users who have at least 1 image/video post (this is one single query) -
SELECT DISTINCT `wp_users`.`ID`, `wp_users`.`display_name`
FROM `wp_posts`
INNER JOIN `wp_users`
WHERE `wp_posts`.`post_type` = "attachment"
AND `wp_posts`.`post_status` = "inherit"
AND `wp_posts`.`post_author` = `wp_users`.`ID`
AND (
`wp_posts`.`post_mime_type` LIKE "image%"
OR `wp_posts`.`post_mime_type` LIKE "video%"
)
Next, I have to loop through each result from the first query and select the first name, description and social media links for each (this is one example, for user_id = 2) -
SELECT `wp_usermeta`.`meta_key`, `wp_usermeta`.`meta_value`
FROM `wp_usermeta`
WHERE `wp_usermeta`.`user_id` = 2
AND (
`wp_usermeta`.`meta_key` = 'first_name'
OR `wp_usermeta`.`meta_key` = 'description'
OR `wp_usermeta`.`meta_key` = 'facebook'
OR `wp_usermeta`.`meta_key` = 'google_plus'
OR `wp_usermeta`.`meta_key` = 'twitter'
);
As part of the loop where the second query is run I also have to insert those results into the results from the first. All of this not only means extra code, but leads to a longer execution time.
My full code can be found here - http://pastebin.com/P2jv3WTt
Is this goal achievable with MySQL, or is it simply not something it is able to do? Thanks.
What I've tried
I have tried to join to the wp_usermeta table as follows, outputting the results as a named column, but there is an issue - the only results are for users who have an entry for every single meta_key in the wp_usermeta table, but some don't (no Twitter profile link, for example).
SELECT DISTINCT
u.`ID`,
u.`display_name`,
m1.`meta_value` AS first_name,
m2.`meta_value` AS description,
m3.`meta_value` AS facebook,
m4.`meta_value` AS google_plus,
m5.`meta_value` AS twitter
FROM `wp_users` u
JOIN `wp_posts` p
JOIN `wp_usermeta` m1 ON (m1.user_id = u.id AND m1.meta_key = 'first_name')
JOIN `wp_usermeta` m2 ON (m2.user_id = u.id AND m2.meta_key = 'description')
JOIN `wp_usermeta` m3 ON (m3.user_id = u.id AND m3.meta_key = 'facebook')
JOIN `wp_usermeta` m4 ON (m4.user_id = u.id AND m4.meta_key = 'google_plus')
JOIN `wp_usermeta` m5 ON (m5.user_id = u.id AND m5.meta_key = 'twitter')
WHERE p.`post_type` = "attachment"
AND p.`post_status` = "inherit"
AND p.`post_author` = u.`ID`
AND (
p.`post_mime_type` LIKE "image%"
OR p.`post_mime_type` LIKE "video%"
)
ORDER BY RAND()

Most straightforward solution would be like this. Simply do a join for each meta-value you need.
By using LEFT OUTER JOIN instead of INNER JOIN to join the meta-data, you will still keep users for which some of these meta-data don't exist.
SELECT DISTINCT u.ID, u.DisplayName,
fn.meta_value AS firstname, fb.meta_value AS facebook, (etc.)
FROM wp_Users u
INNER JOIN wp_posts p ON p.post_author = u.ID
LEFT OUTER JOIN wp_usermeta fn ON fn.UserId = u.ID AND fn.meta_key = 'first_name'
LEFT OUTER JOIN wp_usermeta fb ON fb.UserId = u.ID AND fb.meta_key = 'facebook'
--- (etc. for each meta column you need to join)
WHERE p.post_type = 'attachment'
AND p.post_status = 'inherit'
AND (p.post_mime_type LIKE 'image%'
OR p.post_mime_type LIKE 'video%')

I would go with something like this:
SELECT DISTINCT
`wp_users`.`ID`,
`wp_users`.`display_name`,
`wp_usermeta`.`meta_key`,
`wp_usermeta`.`meta_value`
FROM
`wp_posts`
INNER JOIN
`wp_users` ON (`wp_posts`.`post_author` = `wp_users`.`ID`)
LEFT JOIN
`wp_usermeta` ON (`wp_usermeta`.`user_id` = `wp_users`.`ID`)
WHERE
`wp_posts`.`post_type` = 'attachment'
AND `wp_posts`.`post_status` = 'inherit'
AND (`wp_posts`.`post_mime_type` LIKE 'image%'
OR `wp_posts`.`post_mime_type` LIKE 'video%')
AND `wp_usermeta`.`meta_key`
IN ('first_name', 'description', 'facebook', 'google_plus', 'twitter');
LEFT JOIN must return you posts and users even if there are no meta associated with users.

are you looking for something like this?
SELECT meta.user_id, meta.meta_key, meta.meta_value
FROM wp_usermeta meta JOIN wp_posts posts
ON posts.post_author = meta.user_id
WHERE posts.post_type = "attachment"
AND posts.post_status = "inherit"
AND posts.post_mime_type RLIKE "^(image|video)"
AND meta_key IN ('first_name', 'description','facebook','google_plus','twitter');
Keep in mind that wp_posts.post_author and wp_usermeta.user_id should be indexes, otherwise this query will be slow.
Better yet if you have a joined index on wp_usermeta: user_key (user_id, meta_key). You can add this with the following command:
ALTER TABLE wp_usermeta ADD KEY user_key(user_id, meta_key);
PS If you also need display_name from the wp_users table, then you have to join it too:
SELECT meta.user_id, users.display_name, meta.meta_key, meta.meta_value
FROM wp_usermeta meta JOIN wp_posts posts
ON posts.post_author = meta.user_id
JOIN wp_users users
ON users.id = meta.user_id
WHERE posts.post_type = "attachment"
AND posts.post_status = "inherit"
AND posts.post_mime_type RLIKE "^(image|video)"
AND meta_key IN ('first_name', 'description','facebook','google_plus','twitter');

Related

Multiple Meta_Key Select on on Wordpress database

I've developed a query which selects events from Wordpress. I am using the where clause to select where the meta_value of the meta_key eventstartdate is after today.
The issue I'm having now is, I also want filter on a second meta_value that being from the meta_key '_VenueCity'.
I have tried aliasing the wp_postmeta table and doing a where on the meta_key but I think I'm missing a join.
This is the code that works without my additional code to get it to work. Can any one advise on how I get this to work?
SELECT
`wp_posts`.`ID` AS `EventID`,
`wp_posts`.`post_parent` AS `SeriesID`,
`wp_posts`.`post_title` AS `EventTitle`,
`wp_posts`.`post_content` AS `EventDescription`,
`wp_posts`.`post_excerpt` AS `EventSummary`,
`wp_posts`.`post_name` AS `EventSlug`,
min(`wp_postmeta`.`meta_value`) AS `EventStartDate`,
max(`tribe_event_end_date`.`meta_value`) AS `EventEndDate`,
`wp_posts`.`guid` AS `GUID`
FROM ((`wp_posts`
JOIN `wp_postmeta` ON
(
(`wp_posts`.`ID` = `wp_postmeta`.`post_id`)
))
LEFT JOIN `wp_postmeta` `tribe_event_end_date` ON
(
(
(`wp_posts`.`ID` = `tribe_event_end_date`.`post_id`) AND
(`tribe_event_end_date`.`meta_key` = '_EventEndDate')
)
))
WHERE
(
(`wp_postmeta`.`meta_key` = '_EventStartDate') AND
(`wp_posts`.`post_type` = 'tribe_events') AND
(`wp_posts`.`post_status` = 'publish') AND
(`tribe_event_end_date`.`meta_value` >= CURDATE())
)
GROUP BY
`wp_posts`.`ID`
ORDER BY
`EventStartDate`,
`wp_posts`.`post_date`;
I am not going to write your query for you but I will give an example of how to get multiple postmeta values. The power will be in the where clause to get the right values.
You should also consider what joins you want to use.
SELECT
p.post_title,
pm1.meta_value,
pm2.meta_value
FROM wp_posts as p
INNER JOIN wp_postmeta as pm1
ON p.ID = pm1.post_id
INNER JOIN wp_postmeta as pm2
ON p.ID = pm2.post_id
WHERE
pm1.meta_key = '_my_postmeta_field1'
AND
pm2.meta_key <> '_not_this_field'

MySQL - How to combine 2 tables in a query when one table may not have a corresponding row

I am writing a basic CMS system with NodeJS and MySQL. I am using the tables structure for posts that WordPress also uses, that being 'posts' and 'post_meta'.
The 'posts' table has the following columns...
id
post_title
post_date
...etc
The 'post_meta' table has the following columns...
id
post_id - ID of associated post
meta_key
meta_value
I like this style as it keeps my rows easy to read in a SQL editor without having to scroll as long lengths, as well as add additional meta data as needed.
Using JOINs (Query below) I am able to make a result set that contains the values from 'posts', as well as additional columns named after 'post_meta.meta_key' that contain the value of 'post_meta.meta_value'.
The problem I am currently running into is how I would go about returning results where a 'meta_key' is not present in the 'posts_meta' table, and instead filling that column of the result with NULL.
Example usage. 2 Posts are in the 'posts' table, the first has 2 meta_keys associated with it, the other has only 1.
'posts' table contents
id : 1
title : 'Post 1'
post_date : 'timestamp of posted date/time'
id : 2
title : 'Post 2'
post_date : 'timestamp of posted date/time'
'posts_meta' table contents
id : 1
post_id : 1
meta_key : 'key1'
meta_value : 'key1 value'
id : 2
post_id : 1
meta_key : 'key2'
meta_value : 'key2 value'
id : 3
post_id : 2
meta_key : 'key1'
posts_meta.meta_value : 'key1 value'
the results I would like to get from this are
post_id : 1
title : 'Post 1'
post_date : 'timestamp of posted date/time'
key1 : 'key1 value'
key2 : 'key2 value'
post_id : 2
title : 'Post 2'
post_date : 'timestamp of posted date/time'
key1 : 'key1 value'
key2 : NULL
However, with the code I am currently working with, the second result (Post 2) does not return as part of the results, only 'Post 1', since all of the meta_keys that were being tested for exist in the 'post_meta' table.
Here is my SQL code as it currently stands (edited to keep to the example above)
SELECT
posts.id,
posts.title,
posts.post_date,
m1.meta_value as `key1`,
m2.meta_value as `key2`
FROM posts
LEFT JOIN post_meta as m1 ON ( posts.id = m1.post_id )
LEFT JOIN post_meta as m2 ON ( posts.id = m2.post_id )
WHERE
( m1.meta_key = 'key1' )
AND ( m2.meta_key = 'key2' )
ORDER BY posts.post_date DESC
as you can see, i am selecting the meta_values from the 2 JOINs, and naming them after the specific key each of those joins is being used to retrieve (as defined in the WHERE clause).
I know it has got to be something simple, since I am almost there. It returns rows that have both key1 and key2, but not rows that are completely missing either or all of them. I want it to return all rows, and if a key is missing, fill that field in the row with NULL
You want FULL OUTER JOIN rather than LEFT JOIN.
Left Join will not include rows that do not exist in the other table. In order to have the data (even if it is NULL). You need to do a FULL OUTER JOIN, that will give you cells with NULL rather than leaving them off entirely.
Since you are using MySQL, you will have to emulate the behavior of a FULL OUTER JOIN using UNION. Something like this:
SELECT
posts.id,
posts.title,
posts.post_date,
m1.meta_value as `key1`,
m2.meta_value as `key2`
FROM posts
LEFT JOIN post_meta as m1 ON ( posts.id = m1.post_id )
LEFT JOIN post_meta as m2 ON ( posts.id = m2.post_id )
WHERE
( m1.meta_key = 'key1' )
AND ( m2.meta_key = 'key2' )
ORDER BY posts.post_date DESC
UNION
SELECT
posts.id,
posts.title,
posts.post_date,
m1.meta_value as `key1`,
m2.meta_value as `key2`
FROM posts
RIGHT JOIN post_meta as m1 ON ( posts.id = m1.post_id )
LEFT JOIN post_meta as m2 ON ( posts.id = m2.post_id )
WHERE
( m1.meta_key = 'key1' )
AND ( m2.meta_key = 'key2' )
ORDER BY posts.post_date DESC
UNION
SELECT
posts.id,
posts.title,
posts.post_date,
m1.meta_value as `key1`,
m2.meta_value as `key2`
FROM posts
RIGHT JOIN post_meta as m1 ON ( posts.id = m1.post_id )
RIGHT JOIN post_meta as m2 ON ( posts.id = m2.post_id )
WHERE
( m1.meta_key = 'key1' )
AND ( m2.meta_key = 'key2' )
ORDER BY posts.post_date DESC

Joining/summing custom fields in WordPress database query

Despite Googling this for 2 hours and trying a variety of JOINS and UNIONS and ALIASES and whatnot, I still can't retrieve in one table the result of two subqueries in the wp_postmeta table from the WordPress database (I don't even care about wp_posts here, all the data I'm interested in is in postmeta).
I have custom fields (managed with the ACF plugin) that look like the following in the database, with one key for regions and another for a numeric value.
| ID | meta_key | meta_value |
| 1 | group_name | Japan |
| 1 | facility_index | 0.83 |
| 2 | group_name | USA |
| 2 | facility_index | 4.51 |
I need a query that returns the following so that I can calculate aggregates by region (for instance, the sum of facility_index for all facilities, by country/region, i.e. by group_name):
| ID | group_name | facility_index |
| 1 | Japan | 0.83 |
| 2 | USA | 4.51 |
This query lists group_name twice instead of listing facility_index in the 3rd column:
SELECT post_id, meta_value, result2.meta_value FROM
(
(
SELECT post_id, meta_value
FROM wp_postmeta
WHERE meta_key = "group_name"
)
UNION
(
SELECT post_id, meta_value
FROM wp_postmeta
WHERE meta_key = "facility_index"
)
) AS result2
GROUP BY post_id
Edit to display the final query that does exactly what I wanted, thanks to the accepted answer below:
SELECT
meta1.meta_value AS group_name,
meta2.meta_value AS facility_index,
SUM(meta2.meta_value) AS 'fac_index_geo'
FROM wp_posts post
LEFT JOIN wp_postmeta meta1 ON meta1.post_id = post.ID AND meta1.meta_key = 'group_name'
LEFT JOIN wp_postmeta meta2 ON meta2.post_id = post.ID AND meta2.meta_key = 'facility_index'
WHERE
post.post_type = 'facility'
GROUP BY group_name
As I understand you are looking for a custom SQL query - i.e. you don't want to use a WP_Query object.
For this I'd use some JOINS instead of subqueries.
Also I encourage you to query for the post_type, as otherwise you might get data from autosaves/revisions or similar wrong results.
/*
INNER JOIN
This query will only return posts that contain all meta values
i.e. all 'post' items with group_name AND facility_index
*/
$sql = "
SELECT
post.ID,
meta1.meta_value AS group_name,
meta2.meta_value AS facility_index
FROM {$wpdb->posts} post
INNER JOIN {$wpdb->postmeta} meta1 ON meta1.post_id = post.ID AND meta1.meta_key = 'group_name'
INNER JOIN {$wpdb->postmeta} meta2 ON meta2.post_id = post.ID AND meta2.meta_key = 'facility_index'
WHERE
post.post_type = 'post'
";
$data = $wpdb->get_results( $sql );
Just make sure to update the post_type condition to match your required post type.
Alternatively you can use this query if you want to also get posts that have a missing meta value
/*
LEFT JOIN
This query will return all posts even if they have missing meta values
i.e. if group_name or facility_index is empty, then NULL is returned in the column
*/
$sql = "
SELECT
post.ID,
meta1.meta_value AS group_name,
meta2.meta_value AS facility_index
FROM {$wpdb->posts} post
LEFT JOIN {$wpdb->postmeta} meta1 ON meta1.post_id = post.ID AND meta1.meta_key = 'group_name'
LEFT JOIN {$wpdb->postmeta} meta2 ON meta2.post_id = post.ID AND meta2.meta_key = 'facility_index'
WHERE
post.post_type = 'post'
";

Wordpress Custom Meta Query without wp functions (wo. wp_query())

I have to fetch title, description and attachment out of the wordpress database without wp_query. I can't get how to fetch both, attachments and meta_descriptions at the same time...
This is how my query looks like so far
$query="SELECT post_title, meta_value
FROM wp_posts p JOIN wp_postmeta pm ON p.ID=pm.post_id
WHERE post_status='publish'
AND post_type='page'
AND post_parent=2330
AND ( meta_key='_wpseo_edit_description' OR meta_key='_wp_attached_file' )
ORDER BY post_date DESC";
This query returns "doubled" results i have to split within a loop
______________________________
| post_title | meta_value |
|------------|---------------|
| title 1 | 1349 |
|------------|---------------|
| title 1 | description 1 |
|------------|---------------|
| title 2 | 1348 |
|------------|---------------|
| title 2 | description 2 |
|____________|_______________|
You wlil need to join onto the wp_postmeta table in order to get any of the attached information, such as file attachments or custom fields.
An example of how to do this:
SELECT p.*, pm2.meta_value AS featured_image, pm3.meta_value AS wpseo_edit_description, pm4.meta_value AS wp_attached_file
FROM `wp_posts` p
LEFT JOIN `wp_postmeta` pm2 ON p.ID = pm2.post_id AND pm2.meta_key = 'wp_attached_file'
LEFT JOIN `wp_postmeta` pm3 ON p.ID = pm3.post_id AND pm3.meta_key = '_wpseo_edit_description'
LEFT JOIN `wp_postmeta` pm4 ON p.ID = pm4.post_id AND pm4.meta_key = '_wp_attached_file'
WHERE p.post_status = 'publish'
AND p.post_type = 'page'
AND p.post_parent = 2330
The normal post fields (post title, description, ID, etc) will then be available, along with featured_image, wp_attached_file and wpseo_edit_description.

Wordpress custom QUERY

I need to do select from Post table, each post is related to one category and on tag.
I need to select specific tag/s and/or category/s.
Terms table have custom field "menu_order", it is used to set category order.
I want to sort posts by that field.
If i do plain select:
SELECT * from wp_posts
LEFT
JOIN wp_term_relationships
ON ( wp_term_relationships.object_id = wp_posts.ID )
LEFT
JOIN wp_terms
ON ( wp_terms.term_id = wp_term_relationships.term_taxonomy_id )
WHERE 1=1
AND wp_posts.post_type = 'post'
AND (
wp_posts.post_status = 'publish'
OR wp_posts.post_status = 'private'
)
LIMIT 0, 20
I get result:
+----------------+----------------+----------+---------------------+
| POST ID | Term name | order | type |
+----------------+----------------+----------+---------------------+
| 2 | Fire | 1 | category |
+----------------+----------------+----------+---------------------+
| 2 | blue | 0 | tag |
+----------------+----------------+----------+---------------------+
That is just the intro.
If I do WHERE $wpdb->terms.term_id = 170 what is a TAG I have only one return.
+----------------+----------------+----------+---------------------+
| POST ID | Term name | order | type |
+----------------+----------------+----------+---------------------+
| 2 | blue | 0 | tag |
+----------------+----------------+----------+---------------------+
I need to select POSTS, filter out by category or/and tag, then get TERM_ORDER from category attached to POST.
So, no matter what results I have, order is always pulled from related category.
As I am not so strong in MYSQL, this gets me baffled. Any input would be helpful :)
UPDATE --------------------------------------------------
So, after crazy coding I managed to get what I wanted.
Do not hold your breath as this probably is ugly and slow, but works.
First, I create view where all products are collected.
$qqquery = "
CREATE VIEW samsam AS
SELECT
ID, post_content, post_title, post_excerpt, post_status, post_name, guid, post_type, $wpdb->term_relationships.term_taxonomy_id as cat_term_taxonomy_id, tt1.term_taxonomy_id as tag_term_taxonomy_id
FROM $wpdb->posts
LEFT
JOIN $wpdb->term_relationships
ON ( $wpdb->term_relationships.object_id = $wpdb->posts.ID )
LEFT
JOIN $wpdb->term_relationships AS tt1
ON ( tt1.object_id = $wpdb->posts.ID )
WHERE 1=1
AND $wpdb->posts.post_type = 'post'
AND (
$wpdb->posts.post_status = 'publish'
OR $wpdb->posts.post_status = 'private'
)
AND ( wp_term_relationships.term_taxonomy_id IN ( $query_cat ) // ID's of categories, seperated by coma
AND tt1.term_taxonomy_id IN (' . $add_query_tag_id . ') //ID's of POSTS seperated by coma
)
ORDER BY $wpdb->term_relationships.term_taxonomy_id DESC
";
$wpdb->query($qqquery);
After that I do query where I select view and join relationships and terms to get category order by filtering out order > 0, then group BY id to filter out duplicates.
$querystr = "
SELECT * from samsam
LEFT
JOIN $wpdb->term_relationships
ON (samsam.ID = $wpdb->term_relationships.object_id)
LEFT
JOIN $wpdb->terms
ON ($wpdb->terms.term_id = $wpdb->term_relationships.term_taxonomy_id)
AND ($wpdb->terms.menu_order > 0)
WHERE menu_order > 0
GROUP BY ID
ORDER BY menu_order ASC
LIMIT $item_limit_offset, $item_limit_pp"; // first is calculation of products to skip, second is how many posts per page
$pageposts = $wpdb->get_results($querystr, OBJECT);
What it does:
Category 1 (have order 1)
post
post
post
Category 3 (have order 2)
post
post
post
Category 2 (have order 3)
post
post
post
Can be limited, offseted, paginated, filtered out by tags and/or categories.
I used plugin "Term Menu Order" that adds extra field to order categories.
Have a nice day.
Why are you writing your own query here? Wordpress has built in functions to retrieve data.
http://codex.wordpress.org/Template_Tags/get_posts
http://codex.wordpress.org/Function_Reference/query_posts