Group data from two tables, multiple columns and rows, into one row - mysql

I'm trying to group key = value pairs that are each on its row, into one row groupped by ID.
table 1. wp_posts
keys: ID - post_title
table 2. wp_postmeta
keys: post_id, wpcf-owner, wpcf- tel, wpcf-mob, wpcf-email...
Using this query, I'm getting all info, but for each key I get separate row. What I need is an array where all is in one row groupped by wp_posts.ID
SELECT wp_posts.ID, wp_posts.post_title, wp_postmeta.post_id, wp_postmeta.meta_key, wp_postmeta.meta_value
FROM wp_posts
JOIN wp_postmeta
ON wp_posts.ID = wp_postmeta.post_id
WHERE wp_posts.post_type = 'accommodation' AND wp_postmeta.meta_key LIKE 'wpcf%'
ORDER BY hic13_posts.post_title
Result is:
ID | post_title | key value
------------------------------
1 | TITLE | wpcf-owner
1 | TITLE | wpcf-tel
1 | TITLE | wpcf-mob
1 | TITLE | wpcf-email
2 | TITLE | wpcf-owner
2 | TITLE | wpcf-tel
2 | TITLE | wpcf-mob
2 | TITLE | wpcf-email
...
What I need is:
ID | post_title | key value | key value | key value | key value
-----------------------------------------------------------------
1 | TITLE | wpcf-owner | wpcf-tel | wpcf-mob | wpcf-email
2 | TITLE | wpcf-owner | wpcf-tel | wpcf-mob | wpcf-email
If I add command to group by ID, then I get only one key from second table...
SELECT wp_posts.ID, wp_posts.post_title, wp_postmeta.post_id, wp_postmeta.meta_key, wp_postmeta.meta_value
FROM wp_posts
JOIN wp_postmeta
ON wp_posts.ID = wp_postmeta.post_id
WHERE wp_posts.post_type = 'accommodation' AND wp_postmeta.meta_key LIKE 'wpcf%'
GROUP BY hic13_posts.ID
ORDER BY hic13_posts.post_title
I get only this
ID | post_title | key value
------------------------------
1 | TITLE | wpcf-owner
2 | TITLE | wpcf-tel
Hope all this makes sense :-)
Thank you!

This type of transformation is a pivot but MySQL does not have a pivot function so you will need to use an aggregate function with a CASE expression.:
SELECT p.ID,
p.post_title,
m.post_id,
max(case when m.meta_key = 'wpcf-owner' then m.meta_value end) as owner,
max(case when m.meta_key = 'wpcf-tel' then m.meta_value end) as tel,
max(case when m.meta_key = 'wpcf-mob' then m.meta_value end) as mob,
max(case when m.meta_key = 'wpcf-email' then m.meta_value end) as email
FROM wp_posts p
JOIN wp_postmeta m
ON p.ID = m.post_id
WHERE p.post_type = 'accommodation' AND m.meta_key LIKE 'wpcf%'
GROUP BY p.ID, p.post_title, m.post_id
ORDER BY p.post_title
Edit, if you have an unknown number of meta_key items to transform, then you can use a prepared statement to generate dynamic sql:
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'max(CASE WHEN m.meta_key = ''',
meta_key,
''' THEN m.meta_value else null END) AS `',
meta_key, '`'
)
) INTO #sql
FROM wp_postmeta;
SET #sql
= CONCAT('SELECT p.ID,
p.post_title,
m.post_id, ', #sql, '
FROM wp_posts p
JOIN wp_postmeta m
ON p.ID = m.post_id
WHERE p.post_type = ''accommodation''
AND m.meta_key LIKE ''wpcf%''
GROUP BY p.ID, p.post_title, m.post_id
ORDER BY p.post_title');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

Related

Wordpress Multiple DB Rows Into Columns

I want to retrieve the data of multiple product meta values for the same product but instead of displaying it in multiple rows I want the meta values as their own columns.
I start with:
SELECT a.post_title, b.meta_key, b.meta_value
FROM wp_posts a
INNER JOIN wp_postmeta b
ON a.ID = b.post_id
WHERE a.post_type = 'product'
AND b.meta_key = '_custom_animal_id'
OR b.meta_key = '_custom_breed';
This returns a table:
Name
meta_key
meta_value
Cow 1
_custom_animal_id
FR4380
Cow 1
_custom_breed
HO
Cow 2
_custom_animal_id
FR0001
Cow 2
_custom_breed
HO
Cow 3
_custom_animal_id
FR9999
Cow 3
_custom_breed
ZZ
What I want to display is:
Name
Animal ID
Breed
Cow 1
FR4380
HO
Cow 2
FR0001
HO
Cow 3
FR9999
ZZ
I know I will need to group by post_id (as a the title may not be unique) but returning the rows to columns I have searched a lot and not finding the answer to do it.
You can try to use the condition aggregate function.
MAX with CASE WHEN to make the pivot
SELECT a.post_title 'Name',
MAX(CASE WHEN b.meta_key = '_custom_animal_id' THEN b.meta_value END) 'Animal ID' ,
MAX(CASE WHEN b.meta_key = '_custom_breed' THEN b.meta_value END) 'Breed'
FROM wp_posts a
INNER JOIN wp_postmeta b
ON a.ID = b.post_id
WHERE a.post_type = 'product'
AND b.meta_key = '_custom_animal_id'
OR b.meta_key = '_custom_breed'
GROUP BY a.post_title
Results:
| Name | Animal ID | Breed |
|-------|-----------|-------|
| Cow 1 | FR4380 | HO |
| Cow 2 | FR0001 | HO |
| Cow 3 | FR9999 | ZZ |
Need to make use of a subquery here to get what we need. Make sure you have an appropriate index on wp_postmeta.meta_key if you haven't already.
SELECT a.post_title, animalid.meta_key, breed.meta_key
FROM wp_posts a
INNER JOIN (SELECT post_id, meta_key
FROM wp_postmeta
WHERE meta_key = '_custom_animal_id') animalid ON a.ID = animalid.post_id
INNER JOIN (SELECT post_id, meta_key
FROM wp_postmeta
WHERE meta_key = '_custom_breed') breed ON a.ID = breed.post_id
WHERE a.post_type = 'product';

How to filter post by join condition?

I have a table called wpps_posts which have this structure:
ID | post_title | post_type
1 foo zoacres-property
2 foo2 zoacres-property
3 foo3 post
I would like to return all the posts with type zoacres-property and also I want filter them by price. Each price is stored inside the table wp_postmeta:
meta_id | post_id | meta_key | meta_value
100 2 price 5000
100 1 price 0
How can I order all the posts by price ASC?
I'm stuck with the following query:
SELECT * FROM wpps_posts p
INNER JOIN wpps_posts wp ON wp.ID = p.ID
WHERE p.post_type = 'zoacres-property'
ORDER BY wp.meta??
EXPECTED RESULT:
ID | post_title | post_type
1 foo zoacres-property
2 foo2 zoacres-propertY
SELECT * FROM wpps_posts p
INNER JOIN wp_postmeta wp ON wp.post_ID = p.ID
AND wp.meta_key='price'
WHERE p.post_type = 'zoacres-property'
ORDER BY wp.meta_value asc
You could do something like this, depends what other type of meta type records you have.
SELECT * FROM wpps_posts
LEFT JOIN wp_postmeta ON wp_postmeta.post_id = wpps_posts.ID AND wp_postmeta.meta_key = 'price'
WHERE wpps_posts.post_type = 'zoacres-property'
ORDER BY wp_postmeta.meta_value

Subquery using result from parent query

UPDATE The pay query is now solved with the help provided in the replies. The answer Strawberry provided is very close. It just needed to be edited a bit as the WHERE clause has a redundancy and incorrect statement that I provided in my original query. Here is the correct, slightly revised query:
SELECT ux.user_id
, ux.meta_value pay
FROM wp_um_groups_members m
Join wp_usermeta ux
ON m.user_id1 = ux.user_id
WHERE ux.meta_key = CONCAT('_um_groups_', m.group_id,'_price')
This matches the following data for my 'Pay' field:
From up_usermeta:
umeta_id | user_id | meta_key | meta_value
===========================================================
622680 | 5989 | _um_groups_47652_price | 500
From wp_um_groups_members:
id | group_id | user_id1 | user_id2 | status | role | invites | time_stamp | date_joined
===============================================================================================================
187 | 47682 | 5989 | 3 | approved | member | 1 | 2020-02-15 10:59:08 | 2020-02-15 10:59:08
I through that in with my original query below, and everything is working.
Original Post
I'm sure I'm making this much more difficult than it needs to be. Below is the flow chart and query. When I add the 'Pay'subquery, I receive results that are not matched correctly. If I remove the "Pay" subquery, it works fine (with exception that there is no pay).
In the database, I have many entries in wp_usermeta.meta_value such as:
meta_value = _um_groups_47859_price
In that example, the number 47859 equals the value of wp_um_groups_members.group_id
So I was hoping to be able to query that exact match using something similar to: LIKE CONCAT('%', wp_um_groups_members.group_id ,'%')
That did not seem to work either.
Here is my current query:
SELECT
wp_um_groups_members.group_id AS ID, wp_posts.post_title AS Gig, Location.Location, Type.`Event Type`, wp_users.display_name AS Player, Date.Date, Pay.Pay
FROM
wp_um_groups_members
Inner Join wp_posts ON
wp_um_groups_members.group_id = wp_posts.ID
Inner Join (SELECT
wp_postmeta.post_id, wp_postmeta.meta_value AS Location
FROM
wp_postmeta
WHERE
wp_postmeta.meta_key = '_um_groups_event_location'
) Location ON
Location.post_id = wp_posts.ID
Inner Join (SELECT
wp_postmeta.post_id, wp_postmeta.meta_value AS `Event Type`
FROM
wp_postmeta
WHERE
wp_postmeta.meta_key = '_um_groups_event_type'
) Type ON
Type.post_id = wp_posts.ID
Inner Join wp_users ON
wp_users.ID = wp_um_groups_members.user_id1
Inner Join (SELECT
wp_postmeta.post_id, wp_postmeta.meta_value AS Date
FROM
wp_postmeta
WHERE
wp_postmeta.meta_key = '_um_groups_event_start'
) Date ON
Date.post_id = wp_posts.ID
Inner Join (SELECT
wp_usermeta.user_id, wp_usermeta.meta_value AS Pay
FROM
wp_um_groups_members
Inner Join wp_usermeta ON
wp_um_groups_members.user_id1 = wp_usermeta.user_id
WHERE
wp_usermeta.meta_key like '%price'
) Pay ON
Pay.user_id = wp_users.ID
WHERE
wp_um_groups_members.status = 'approved'
Is there a way to to modify that last Inner Join in order to contain something like: WHERE
wp_usermeta.meta_value = '_um_groups_47859_price' in which 47859 would is wp_um_groups_members.group_id?
SELECT ux.user_id
, ux.meta_value pay
FROM wp_um_groups_members m
Join wp_usermeta ux
ON m.user_id1 = ux.user_id
WHERE ux.meta_key LIKE '%price'
AND ux.meta_value = CONCAT('_um_groups_', m.group_id,'_price')
Incidentally, while there's no performance benefit, when working with an EAV (like wp_postmeta), I prefer this syntax...
SELECT post_id
, MAX(CASE WHEN meta_key = '_um_groups_event_location' THEN meta_value END) location
, MAX(CASE WHEN meta_key = '_um_groups_event_type' THEN meta_value END) event_type
, MAX(CASE WHEN meta_key = '_um_groups_event_start' THEN meta_value END) date
, MAX(CASE WHEN meta_key = '_um_groups_event_type' THEN meta_value END) event_type
FROM wp_postmeta
GROUP
BY post_id
(Strictly speaking, this is more akin to using LEFT JOIN than INNER JOIN, as per your example)

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: 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'