Select random row per distinct field value while using joins - mysql

I have a Wordpress instance showing some posts. Each post is defined in a specific language and has a property _post_year set. So we can have several posts with the same language and referring to the same year.
MySQL tables:
wp-posts
Contains all posts.
ID | post_author | post_date | ...
==================================
1 | ...
2 | ...
...
wp_term_relationships
Contains information about a language of a post (amongst other things).
object_id | term_taxonomy_id | term_order |
===========================================
1 | ...
1 | ...
2 | ...
...
wp_postmeta
Contains post meta information (like an additional property "_post_year").
meta_id | post_id | meta_key | meta_value |
===========================================
1 | 1 | ...
2 | 1 | ...
...
I once was able to load one random post per year (for all years available) like this:
SELECT DISTINCT
wp_posts.*,
postmeta.meta_value as post_meta_year
FROM (
SELECT * FROM wp_posts
JOIN wp_term_relationships as term_relationships
ON term_relationships.object_id = wp_posts.ID
AND term_relationships.term_taxonomy_id IN ({LANGUAGE_ID})
ORDER BY RAND()
) as wp_posts
JOIN wp_postmeta as postmeta
ON postmeta.post_id = wp_posts.ID
AND postmeta.meta_key = '_post_year'
AND post_status = 'publish'
GROUP BY post_meta_year DESC
ORDER BY post_meta_year DESC
Since i upgraded MySQL to version 5.7 this doesn't work anymore:
Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'wp_posts.ID' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by
How can i achieve to get a random post per year sorted descendingly?

One method you can try: From a derived table with the distinct years select the year and, in a correlated subquery, a random post ID with that year using ORDER BY rand() and LIMIT 1. Join the result of that second derived table with the posts.
SELECT po1.*,
ppmo1.meta_value
FROM (SELECT pmo1.meta_value,
(SELECT pi1.id
FROM wp_posts pi1
INNER JOIN wp_postmeta pmi2
ON pmi2.post_id = pi1.id
INNER JOIN wp_term_relationships tri1
ON tri1.object_id = pi1.id
WHERE tri1.term_taxonomy_id = {LANGUAGE_ID}
AND pmi2.meta_key = '_post_year'
AND pmi2.meta_value = pmo1.meta_value
ORDER BY rand()
LIMIT 1) id
FROM (SELECT DISTINCT
pmi1.meta_value
FROM wp_postmeta pmi1
WHERE pmi1.meta_key = '_post_year') pmo1) ppmo1
INNER JOIN wp_posts po1
ON po1.id = ppmo1.id
ORDER BY ppmo1.meta_value DESC;
(Untested because schema and sample data weren't given by consumable DDL and DML.)

In MySQL 5.7, where mode ONLY_FULL_GROUP_BY is (happily) enabled by default, I would recommend a correlated subquery for filtering:
select * -- better enumerate the actual column names here
from wp_posts p
inner join wp_postmeta pm on pm.post_id = p.id
where pm.meta_key = '_post_year' and p.id = (
select pm1.post_id
from wp_post p1
inner join wp_postmeta pm1 on pm1.post_id = p1.id
where p1.status = 'publish' and pm1.meta_key = '_post_year' and pm1.meta_value = pm.meta_value
order by rand() limit 1
)
Basically the subquery selects one random post id per group of records having the same '_post_year', which is used to filter the query.
Note that with this technique there is no need to filter again in the outer query on the post status, since the subquery does it already and returns a primary key column.

Related

Get 2 meta values from meta key column

I need to use some library to export product names, SKU and prices to one CSV file. This library connects using PDO and needs an SQL query.
I want to select 'name', 'SKU' and 'price' from 2 WordPress tables, namely wp_posts and wp_postmeta.
I don't know how to get data from 'meta_value' column twice for 'meta_key'='_price' and 'meta_key'='_sku', ex.
My current query:
"SELECT a.post_title, m1.meta_value, m2.meta_value FROM wp_posts a, wp_postmeta m1, wp_postmeta m2
WHERE a.post_type='product' AND m1.post_id = a.ID
AND m1.meta_key='_sku'
AND m2.meta_key='_price'"
It sounds like you could do with a join so you're relating the meta information to the right posts.
SELECT
post.post_title,
meta.meta_value
FROM wp_posts AS post
LEFT JOIN wp_postmeta AS meta
ON post.post_id = meta.post_id
WHERE post.post_type = 'product'
AND meta.meta_key IN ('_sku', '_price')
Example results:
post_title | meta_value
--------------|-----------
Cheddar | CHE001
Cheddar | 2.45
Red Leicester | CHE002
...
This assumes that the id column in wp_posts is post_id.
It's important to note that this will return up to two rows for each post, depending on whether it has a meta row for _sku and _price). If you need the data all on the same row (as you might for your export) you might need something like this instead:
SELECT
post.post_title,
metaSku.meta_value AS sku,
metaPrice.meta_value AS price
FROM wp_posts AS post
LEFT JOIN (
SELECT
*
FROM wp_postmeta
WHERE meta_key = '_sku'
) AS metaSku
ON post.post_id = metaSku.post_id
LEFT JOIN (
SELECT
*
FROM wp_postmeta
WHERE meta_key = '_price'
) AS metaPrice
ON post.post_id = metaPrice.post_id
WHERE post.post_type = 'product'
Example results:
post_title | sku | price
--------------|--------|------
Cheddar | CHE001 | 2.45
Red Leicester | CHE002 |
...
I hope this helps.
This will works for me
SELECT post.post_title, metaSku.meta_value AS sku, metaPrice.meta_value AS price FROM wp_posts AS post LEFT JOIN ( SELECT * FROM wp_postmeta WHERE meta_key = '_sku' ) AS metaSku ON post.ID = metaSku.post_id LEFT JOIN ( SELECT * FROM wp_postmeta WHERE meta_key = '_price' ) AS metaPrice ON post.ID = metaPrice.post_id WHERE post.post_type = 'product';

MySQL query stalling - is there a more efficient solution for this MySQL Query?

SELECT post_title,
count(*) AS c
FROM wp_posts
WHERE post_type = "product"
GROUP BY post_title
HAVING c > 1
ORDER BY c DESC
runs no problem, returns result in < 1 sec. Yet
select * from wp_posts where post_title in (
select post_title from wp_posts WHERE post_type = "product"
group by post_title having count(*) > 1
)
hangs up.
Yet they are fundamentally the same query except for the fact that in the second query I'm trying to pull out the entire record rather than just the post_title.
Have I erred? Is there a more efficient way to achieve the equivalent?
Edit: EXPLAIN query and SHOW CREATE TABLE wp_posts has been appended for your information.
you could avoid the IN clause on the subquery and use an inner join
select a.*
from wp_posts a
INNER JOIN (
select post_title
from wp_posts
WHERE post_type = "product"
group by post_title
having count(*) > 1
) t ON t.post_title = a.post_title
this should be more performant
The most efficient way to write this query is probably using exists . . . assuming wp_posts has a primary key:
select p.*
from wp_posts p
where p.post_type = 'product' and
exists (select 1
from wp_posts p2
where p2.post_title = p.post_title and
p2.post_type = p.post_type and
p2.id <> p.id
);
For performance, yu want an index on wp_posts(post_type, title, id).

SQL - ordering results from joined table

Note: This question is related to a WordPress specific question but I wanted to have an "outside look" at this from a pure SQL point of view: https://wordpress.stackexchange.com/questions/55263/order-posts-by-custom-field-and-if-custom-field-is-empty-return-remaining-posts
Let's say we have to tables with the following strucure:
Tabe posts: ID (key), Title
Table post_metadata: post_ID(FKEY), meta_key, meta_value
And I want to retrieve ID and Title of posts that have:
an entry in post_metadata with key = 'meta_1' and meta_value = 'value_1'
AND an entry in post_metadata with key = 'meta_2' and meta_value = 'value_2'
I want to order the results by the value of a third metadata with meta_key = "meta_3".
Now here is the tricky part:
Not all posts have an entry in post_metadata table with 'meta_3' as meta_key. Since Im not filtering posts by meta_3, only ordering, I wanted to keep these posts in my results, as if they had an empty value for this meta.
How can we achieve that?
Thanks
Edit:
There is SQL fiddle now: https://www.db-fiddle.com/f/kBNaaRFB5xfna5MniuTpaG/1
Perhaps:
Use an Left join once to get meta 3 value if it exists ensuring you keep all posts that have meta1 and meta2 with desired values.
and then use an exist and having to ensure you only get records having both meta1 and 2 with desired values.
UNTESTED...
SELECT P.ID, P.Title, PM.Meta_value
FROM Posts P
LEFT JOIN Post_MetaData PM
on P.ID = PM.Post_ID
and PM.key = 'meta_3'
WHERE exists (SELECT 1
FROM post_meta
WHERE ((Key 'meta_1' and meta_value = 'Value_1') OR
(Key 'meta_2' and meta_value = 'Value_2'))
and P.ID = Post_ID --Either here or an AND in the HAVING clause...need to test to know
GROUP BY Post_ID
HAVING count(*) = 2 )
ORDER BY -PM.meta_value desc, P.ID
This does assume that post_metaData has a unique constraint on the key per Post_ID. otherwise we could get meta_1 with value a and meta1 with value a and the count(*) would be 2; and incorrectly return it in the results.
To ensure nulls are last follow this approach;
MySQL Orderby a number, Nulls last
Doing this as an IN.... but would be slower I would think.
SELECT P.ID, P.Title, PM.Meta_value
FROM Posts P
LEFT JOIN Post_MetaData PM
on P.ID = PM.Post_ID
and PM.key = 'meta_3'
WHERE P.ID in (SELECT Post_meta.Post_ID
FROM post_meta
WHERE ((Key 'meta_1' and meta_value = 'Value_1') OR
(Key 'meta_2' and meta_value = 'Value_2'))
GROUP BY Post_ID
HAVING count(*) = 2 )
ORDER BY -PM.meta_value desc, P.ID

MySQL: order by value from second table, use default if value not set

This takes place inside WordPress, but it's a general MySQL question.
There are two tables, one of which contains posts, the other metadata, linked by ID.
post_title | ID post_id | meta_key | meta_value
-----------+--- --------+----------+-----------
title | 1 1 | key_1 | aaa
-----------+--- --------+----------+-----------
title | 2 1 | key_2 | bbb
--------+----------+-----------
1 | mykey | 1
--------+----------+-----------
2 | key_n | ccc ddd
I'm trying to order results on some column value, which might not be set for all rows. Basically, I want to see rows with this column/value pair set first, followed by all the others. Each post might have some metadata associated with it, based on meta_key and meta_value pairs. There may be more keys for a single post and they need not include the one I want to sort by.
The problem is that using a MySQL query with a WHERE meta_key = mykey will exclude all the posts where this key doesn't exist. So what I need is a way to display a default value for all those posts, where this meta key doesn't exist.
First step: It's easy to select all rows with a certain meta_key:
SELECT
p.ID, p.post_title, p.post_type, p.post_date, m.meta_value
FROM wp_posts AS p
LEFT JOIN wp_postmeta AS m ON p.ID = m.post_id
WHERE
m.meta_key = 'mykey'
Second step: how do I select all the rows where this meta_key doesn't exist?
Here's what I mean, but this is probably a bad solution:
SELECT
p.ID, p.post_title, p.post_type, p.post_date, "some_default"
FROM wp_posts AS p
WHERE
p.ID NOT IN (
SELECT
p.ID
FROM wp_posts AS p
LEFT JOIN wp_postmeta AS m ON p.ID = m.post_id
WHERE
m.meta_key = 'mykey'
)
Third step: show combined results. This could be a UNION of both queries above.
I'm sure there must be a better sulution. What's more important, I don't know how to specify additional paramaters – e. g., first find all posts with some given meta key, or title, or category etc. and then order by said mykey as layed out above.
FINAL EDIT
If anyone's interested, here's the final solution in context. RedFilter's answer made it possible, thanks again.
SELECT p1.ID, p1.post_title, p1.post_type, p1.post_date, m1.meta_value AS meta1, meta2
FROM wp_posts AS p1
LEFT JOIN wp_postmeta AS m1 ON m1.post_id = p1.ID
LEFT JOIN wp_term_relationships AS tr0 ON tr0.object_id = p1.ID
LEFT JOIN wp_term_taxonomy AS tt0 ON tr0.term_taxonomy_id = tt0.term_taxonomy_id
LEFT JOIN wp_terms AS t0 ON tt0.term_id = t0.term_id
LEFT JOIN
(
SELECT
p.ID, IF (m.meta_value = 'on', 1, 0) AS meta2
FROM wp_posts AS p
LEFT JOIN wp_postmeta AS m
ON p.ID = m.post_id
and m.meta_key = 'mykey'
) as extra
ON extra.ID = p1.ID
WHERE 1 = 1
AND m1.meta_key = 'some-other-meta-key'
AND p1.post_type IN ('post', 'some-custom-post-type')
AND tt0.taxonomy = 'some-taxonomy'
AND t0.term_id = 'some-id'
ORDER BY meta2 DESC, meta1 ASC, p1.post_date DESC
SELECT p.ID, p.post_title, p.post_type, p.post_date,
ifnull(m.meta_value, 'default val') as meta_value
FROM wp_posts AS p
LEFT JOIN wp_postmeta AS m ON p.ID = m.post_id
and m.meta_key = 'mykey'

Wordpress: mysql query to return posts from specific categry - and keywords?

Wordpress / Custom MySQL queries
I've got a query to return all posts from a specific category:
SELECT ID, post_title, post_name, guid, post_date, post_content, guid FROM wp_posts as p
INNER JOIN wp_term_relationships AS tr ON
(p.ID = tr.object_id AND
tr.term_taxonomy_id IN (4) )
INNER JOIN wp_term_taxonomy AS tt ON
(tr.term_taxonomy_id = tt.term_taxonomy_id AND
taxonomy = 'category')
ORDER BY id DESC LIMIT 10
But I also need this query to return the keywords associated with each post. I started to create a "keywords by post ID" query, but realized that would be a very costly call when repeated.
Any idea on how to request the keywords/terms for each post that comes back? Comma-separated would work, I guess?
SOLUTION:
No pretty way to do it in MySQL -- joining all those tables can be nasty. So, the solution is three steps:
1) get the posts:
SELECT DISTINCT ID, post_title, post_name, guid, post_date, post_content, guid , GROUP_CONCAT(k.term_taxonomy_id) as keywords FROM wp_posts as p
INNER JOIN wp_term_relationships AS tr ON
(p.ID = tr.object_id AND
tr.term_taxonomy_id IN (4) )
INNER JOIN wp_term_relationships as k ON
(p.ID = k.object_id)
INNER JOIN wp_term_taxonomy AS tt ON
(tr.term_taxonomy_id = tt.term_taxonomy_id AND
taxonomy = 'category')
GROUP BY p.ID
ORDER BY id DESC LIMIT 10
The "group_concat" will return "keywords" back as comma-separated ID's
2) get the keywords from wp_terms
3) use PHP to look up keywords from that array (#2) as needed
You can select all keywords and posts ids with another query and combine both results in higher level (PHP - I guess you use it). You will call database only twice, however you will have to write more logic in PHP level.
2nd result would look like this:
post_id | keyword
-----------------
2 | keyword1
2 | keyword2
3 | keyword3
4 | keyword2
4 | keyword10