SQL PIVOT after an INNER JOIN - mysql

I'm trying to create and event page from the data in my wordpress database.
The data are saved in 2 tables ($wp_posts and $wp_postmeta). After the INNER JOIN i need to PIVOT the table $wp_postmeta but after multiple tries i cannot figure out how to do it.
My Query is :
SELECT
PO.ID,
PO.post_title,
PO.post_content,
PM.post_id,
PM.meta_key,
PM.meta_value
FROM $wp_posts PO
INNER JOIN $wp_postmeta PM ON PO.ID = PM.post_id
I need to have the "meta_key" from the table $wp_postmeta to become column names then to be able to select meta_values
Thank you for your help with my query.

MySQL doesn't have the ability to run a pivot.. but you can fake a pivot.
NOTE:
you need to know the number of rows you want to pivot.
QUERY:
SELECT
MAX(CASE meta_key WHEN '_EventOrganizerID' THEN meta_value END )AS _EventOrganizerID,
MAX(CASE meta_key WHEN '_EventURL' THEN meta_value END )AS _EventURL,
MAX(CASE meta_key WHEN '_EventCost' THEN meta_value END )AS _EventCost,
MAX(CASE meta_key WHEN '_EventCurrencyPosition' THEN meta_value END )AS _EventCurrencyPosition,
MAX(CASE meta_key WHEN '_EventCurrencySymbol' THEN meta_value END)AS _EventCurrencySymbol,
MAX(CASE meta_key WHEN '_EventVenueID' THEN meta_value END )AS _EventVenueID,
MAX(CASE meta_key WHEN '_EventDuration' THEN meta_value END )AS _EventDuration,
MAX(CASE meta_key WHEN '_EventEndDate' THEN meta_value END )AS _EventEndDate,
MAX(CASE meta_key WHEN '_EventStartDate' THEN meta_value END )AS _EventStartDate,
MAX(CASE meta_key WHEN '_EventAllDay' THEN meta_value END )AS _EventAllDay
FROM
( SELECT
PO.ID,
PO.post_title,
PO.post_content,
PM.post_id,
PM.meta_key,
PM.meta_value
FROM $wp_posts PO
INNER JOIN $wp_postmeta PM ON PO.ID = PM.post_id
) t
GROUP BY post_id
ANOTHER NOTE:
if you want to select the meta_values from this query.. as it not just pivot the result but actually select specific stuff you can select from this query like I did to pivot it.

Related

Optimize Query - Select COUNT and SUM from the same subquery

I tried to find the solution to this on Stackoverflow, maybe my wording is wrong.
I have a query which takes to long to execute. I am sure there are simple ways to improve it. For example, I use the same sub-query twice for displaying two different columns (sum and count) but encountered several errors while trying to solve it on my own.
SELECT u.ID,
u.user_email AS mail,
u.user_login AS userName,
u.user_registered AS signUpDate,
(select meta_value from wp_usermeta where user_id = u.ID and meta_key = 'first_name' limit 1) as firstName,
(select meta_value from wp_usermeta where user_id = u.ID and meta_key = 'last_name' limit 1) as lastName,
(select meta_value from wp_usermeta where user_id = u.ID and meta_key = 'billing_phone' limit 1) as billingPhone,
(select meta_value from wp_usermeta where user_id = u.ID and meta_key = 'shipping_phone' limit 1) as shippingPhone,
(SELECT COUNT(meta_value) from wp_postmeta WHERE meta_key = '_order_total' and post_id IN (select post_id from wp_postmeta where meta_value = u.ID and meta_key = '_customer_user')) as orderCount,
(SELECT SUM(meta_value) from wp_postmeta WHERE meta_key = '_order_total' and post_id IN (select post_id from wp_postmeta where meta_value = u.ID and meta_key = '_customer_user')) as moneySpent
FROM wp_users u;
The problem with your query is that you have nested correlated sub queries. Correlated sub queries are a huge hit to SQL performance. In this case you want to try use joins with aggregation (group by).
I don't understand the intricacies of the schema your database, so I'm not quite sure why you're limiting the sub queries for firstName etc to 1 record. I can edit the answer to accommodate that if you give more detail.
I would suggest you try something like this:
SELECT u.ID,
u.user_email AS mail,
u.user_login AS userName,
u.user_registered AS signUpDate,
mfn.meta_value as firstName,
mln.meta_value as lastName,
mbp.meta_value as billingPhone,
msp.meta_value as shippingPhone,
COUNT(pval.meta_value) as orderCount,
SUM(pval.meta_value) as moneySpent
FROM wp_users u
JOIN wp_usermeta as mfn ON u.ID = mfn.user_id AND mfn.meta_key = 'first_name'
JOIN wp_usermeta as mln ON u.ID = mln.user_id AND mln.meta_key = 'last_name'
JOIN wp_usermeta as mbp ON u.ID = mbp.user_id AND mbp.meta_key = 'billing_phone'
JOIN wp_usermeta as msp ON u.ID = msp.user_id AND msp.meta_key = 'shipping_phone'
JOIN wp_postmeta as p ON p.meta_value = u.ID AND p.meta_key = '_customer_user'
JOIN wp_postmeta as pval ON pval.post_id = p.post_id AND pval.meta_key = '_order_total'
GROUP BY u.ID,
u.user_email,
u.user_login,
u.user_registered,
mfn.meta_value,
mln.meta_value,
mbp.meta_value,
msp.meta_value
As per #O.Jones's comment, consider using LEFT JOINS instead of INNER JOINS so that the query still returns results where a meta_value is missing.
#Timur, as per your question in the comments of the first answer; you'd have to do something like this. You still need joins but you don't need as many for the wp_usermeta table. You will still need to join to wp_postmeta twice, because you can't do a join on the '_customer_user' meta_key and simultaneously retrieve the '_order_total' meta_key in a single join.
Just a note on the MAX(CASE WHEN um.meta_key = '...' THEN um.meta_value END) logic; it's the equivalent to MAX(CASE WHEN um.meta_key = '...' THEN um.meta_value ELSE NULL END).
SELECT u.ID,
u.user_email AS mail,
u.user_login AS userName,
u.user_registered AS signUpDate,
MAX(CASE WHEN um.meta_key = 'first_name' THEN um.meta_value END) AS firstName,
MAX(CASE WHEN um.meta_key = 'last_name' THEN um.meta_value END) AS lastName,
MAX(CASE WHEN um.meta_key = 'billing_phone' THEN um.meta_value END) AS billingPhone,
MAX(CASE WHEN um.meta_key = 'shipping_phone' THEN um.meta_value END) AS shippingPhone,
COUNT(pval.meta_value) AS orderCount,
SUM(pval.meta_value) AS moneySpent
FROM wp_users AS u
LEFT JOIN wp_usermeta AS um ON u.ID = um.user_id
LEFT JOIN wp_postmeta AS pm ON u.ID = pm.meta_value AND pm.meta_key = '_customer_user'
LEFT JOIN wp_postmeta AS pval ON pm.post_id = pval.post_id AND pval.meta_key = '_order_total'
WHERE (um.meta_key IN ('first_name', 'last_name', 'billing_phone', 'shipping_phone')
OR um.meta_key IS NULL)
GROUP BY u.ID,
u.user_email,
u.user_login,
u.user_registered;
I'm not 100% sure on the logic. Let me know if it needs tweaking
Here subquery has been introduced for minimizing multiple times using a single table and removing few column from GROUP BY clause for optimization purpose. Use WHERE clause in every subquery if need a particular user/ few users or any searching condition. If subquery returns no data but needed user info then use LEFT JOIN instead of INNER JOIN.
-- MySQL
SELECT u.ID
, u.user_email AS mail
, u.user_login AS userName
, u.user_registered AS signUpDate
, t.firstName
, t.lastName
, t.billingPhone
, t.shippingPhone
, r.orderCount
, r.moneySpent
FROM wp_users u
INNER JOIN (SELECT user_id
, MAX(CASE WHEN meta_key = 'first_name' THEN meta_value END) firstName
, MAX(CASE WHEN meta_key = 'last_name' THEN meta_value END) lastName
, MAX(CASE WHEN meta_key = 'billing_phone' THEN meta_value END) billingPhone
, MAX(CASE WHEN meta_key = 'shipping_phone' THEN meta_value END) shippingPhone
FROM wp_usermeta
WHERE meta_key IN ('first_name', 'last_name', 'billing_phone', 'shipping_phone')
GROUP BY user_id) t
ON u.ID = t.user_id
INNER JOIN (SELECT p.meta_value
, COUNT(pval.meta_value) as orderCount
, SUM(pval.meta_value) as moneySpent
FROM wp_postmeta as p
INNER JOIN wp_postmeta as pval
ON p.post_id = pval.post_id
AND p.meta_key = '_customer_user'
AND pval.meta_key = '_order_total'
GROUP BY p.meta_value) r
ON u.ID = r.meta_value;

Filter users using by two different user meta

I have a user meta field called country (string) and another one called is_blocked (binary). What I need is returns the number of non-blocked users per country selecting the user registered date too.
With this query, I managed to get the values without considering the is_blocked field:
SELECT meta_value AS 'country', COUNT(meta_value) AS 'total'
FROM wp_usermeta INNER JOIN
wp_users
ON wp_usermeta.user_id = wp_users.ID
WHERE meta_key = 'user_country' AND
meta_value <> '' AND
DATE_FORMAT(wp_users.user_registered, '%Y-%m') = '2021-01'
GROUP BY meta_value
ORDER BY total DESC
But I can't just select only non-bloked users. What I try:
SELECT meta_value AS 'country', COUNT(meta_value) AS 'total'
FROM wp_usermeta INNER JOIN
wp_users
ON wp_usermeta.user_id = wp_users.ID
WHERE meta_key = 'user_country' AND
meta_value <> '' AND
DATE_FORMAT(wp_users.user_registered, '%Y-%m') = '2021-01' AND
(wp_usermeta.meta_key = 'user_blocked' AND
CAST(wp_usermeta.meta_value AS BINARY) != '1'
)
GROUP BY meta_value
ORDER BY total DESC
You need another join or exists:
SELECT umc.meta_value AS country, COUNT(umb.user_id) AS total
FROM wp_users u INNER JOIN
wp_usermeta umc
ON umc.user_id = u.ID AND
umc.meta_key = 'user_country' AND
umc.meta_value <> '' LEFT JOIN
wp_usermeta umb
ON umb.user_id = u.ID AND
umb.meta_key = 'user_blocked' AND
CAST(umb.meta_value AS BINARY) <> '1'
WHERE DATE_FORMAT(u.user_registered, '%Y-%m') = '2021-01'
GROUP BY umc.meta_value
ORDER BY total DESC;
This should give you 0 values, if you have countries with no unblocked users. This also assumes that the two meta keys are not repeated for a single user.
You can use conditional aggregation in the table wp_usermeta to get each user and country which meet your conditions:
SELECT user_id,
MAX(CASE WHEN meta_key = 'user_country' THEN meta_value END) country
FROM wp_usermeta
GROUP BY user_id
HAVING country <> ''
AND MAX(CASE WHEN meta_key = 'user_blocked' THEN meta_value END) + 0 <> 1
and then join it to the table wp_users:
SELECT m.country, COUNT(*) total
FROM wp_users u
INNER JOIN (
SELECT user_id,
MAX(CASE WHEN meta_key = 'user_country' THEN meta_value END) country
FROM wp_usermeta
GROUP BY user_id
HAVING country <> ''
AND MAX(CASE WHEN meta_key = 'user_blocked' THEN meta_value END) + 0 <> 1
) m ON m.user_id = u.ID
WHERE DATE_FORMAT(u.user_registered, '%Y-%m') = '2021-01'
GROUP BY m.country
ORDER BY total DESC

MySql - Pivot Table

I'm trying to pull some data out of the wp_postmeta table which is basically a series of key/value pairs tied to a numeric post_id. As such, when I try to extract various values for a post, this is what I get:
This is the current query I'm using to get that output:
select post_id,meta_key,meta_value from wp_postmeta
where meta_key in ('_sku','_length','_width','_height')
and post_id in (
select post_id from wp_postmeta
where meta_value in ('28-005080','28-005287')
)
order by post_id DESC
What I'm trying to do is format the information like this:
I've tried to look at the MySql pivot table examples, but I'm not sure if they quite fit this specific scenario. Frankly, I don't know where to start with accomplishing this task.
You can use conditional aggregation:
select post_id,
max(case when meta_key = '_sku' then meta_value end) as sku,
max(case when meta_key = '_length' then meta_value end) as length,
max(case when meta_key = '_width' then meta_value end) as width,
max(case when meta_key = '_height' then meta_value end) as height
from wp_postmeta
where
meta_key in ('_sku','_length','_width','_height')
and post_id in (select post_id from wp_postmeta where meta_value in ('28-005080','28-005287'))
group by post_id
order by post_id desc
Actually, we might be able to replace the subquery in the where clause with a having clause:
select post_id,
max(case when meta_key = '_sku' then meta_value end) as sku,
max(case when meta_key = '_length' then meta_value end) as length,
max(case when meta_key = '_width' then meta_value end) as width,
max(case when meta_key = '_height' then meta_value end) as height
from wp_postmeta
group by post_id
having max(meta_value in ('28-005080','28-005287')) = 1
order by post_id desc

Mysql returning multiple column from same row in same in same table

Suppose I have table name "POST" which only contain "POST_ID" , I have a another table name post_meta , which also contain "POST_ID"(as foreign key) and two other column "meta_key" and "meta_value"
The structure of my tables are
Post Table :
post_ID
1
2
Meta Table :
meta_id post_id meta_key meta_value
1 1 suite_size 1000
2 1 suite_number 10
3 2 suite_size 2000
4 2 suite_number 20
I want to create a such table like
post_id suite_size suite_number
1 1000 10
2 2000 20
How can I do this ?
Try this:
SELECT pm.post_ID,
SUM(CASE WHEN pm.meta_key = 'suite_size' THEN pm.meta_value ELSE 0 END) as suite_size,
SUM(CASE WHEN pm.meta_key = 'suite_number' THEN pm.meta_value ELSE 0 END) as suite_number
FROM post_meta pm
GROUP BY pm.post_ID;
OR
If you want all posts whether they are not exists in post_meta table then use below query
SELECT p.post_ID,
SUM(CASE WHEN pm.meta_key = 'suite_size' THEN pm.meta_value ELSE 0 END) as suite_size,
SUM(CASE WHEN pm.meta_key = 'suite_number' THEN pm.meta_value ELSE 0 END) as suite_number
FROM post p
LEFT JOIN post_meta pm ON p.post_id = pm.post_ID
GROUP BY p.post_ID;
You need to use the pivot technique to show row as column to achieve this
select
p.post_ID,
max(case when pm.meta_key = 'suite_size' then pm.meta_value end) as `suite_size`,
max(case when pm.meta_key = 'suite_number' then pm.meta_value end) as `suite_number`
from post p
join post_meta pm on pm.post_id = p.post_ID
group by p.post_ID

Wordpress - fetch data from multiple tables

I have these two different tables (wp_usermeta & my cutom table-sia_licence):
wp_usermeta table:-
sia_licence table(custom):-
and I want final data table to be like this:-
We tried using
$data = $wpdb->get_results("SELECT * FROM wp_usermeta, sia_licence WHERE wp_usermeta.user_id = sia_user_id
AND wp_usermeta.meta_key = 'first_name'
AND wp_usermeta.meta_key = 'last_name'
AND wp_usermeta.meta_key = 'phone_number'
AND wp_usermeta.meta_key = 'email'
AND wp_usermeta.meta_key = 'address'");
but getting blank array..
You need to perform conditional aggregation on wp_usermeta in order to pivot all attributes (first name, last name, ...) per user_id and then JOIN with sia_licence to produce desired output
SELECT q.user_id, q.first_name, q.last_name, q.email, q.phone_number, q.address,
l.select_licence, l.visa_expiry, l.licence_number, l.sia_licence, l.driving_licence
FROM
(
SELECT user_id,
MAX(CASE WHEN meta_key = 'first_name' THEN meta_value END) first_name,
MAX(CASE WHEN meta_key = 'last_name' THEN meta_value END) last_name,
MAX(CASE WHEN meta_key = 'phone_number' THEN meta_value END) phone_number,
MAX(CASE WHEN meta_key = 'email' THEN meta_value END) email,
MAX(CASE WHEN meta_key = 'address' THEN meta_value END) address
FROM wp_usermeta m
WHERE EXISTS
(
SELECT *
FROM sia_licence
WHERE sia_user_id = m.user_id
)
GROUP BY user_id
) q JOIN sia_licence l
ON q.user_id = l.sia_user_id
or join first and then aggregate
SELECT q.user_id,
MAX(CASE WHEN meta_key = 'first_name' THEN meta_value END) first_name,
MAX(CASE WHEN meta_key = 'last_name' THEN meta_value END) last_name,
MAX(CASE WHEN meta_key = 'phone_number' THEN meta_value END) phone_number,
MAX(CASE WHEN meta_key = 'email' THEN meta_value END) email,
MAX(CASE WHEN meta_key = 'address' THEN meta_value END) address,
MAX(select_licence) select_licence,
MAX(visa_expiry) visa_expiry,
MAX(licence_number) licence_number,
MAX(sia_licence) sia_licence,
MAX(driving_licence) driving_licence
FROM wp_usermeta q JOIN sia_licence l
ON q.user_id = l.sia_user_id
GROUP BY q.user_id
try this:
SELECT * FROM wp_usermeta wu
INNER JOIN sia_licence sl ON wu.user_id = sl.sia_user_id
WHERE <you can specify your filter here>;
Does sla_user_id reference user_id?
if so you want to use a JOIN like this...
SELECT um.*,sl.select_licence,sl.visa_expiry,sl.licence_number,sl.sia_licence,sl.driving_licence FROM `wp_usermeta`um JOIN `sia_licence`sl ON um.`user_id`=sl.`sia_user_id`;
You can also add a where clause like this
SELECT um.*,sl.select_licence,sl.visa_expiry,sl.licence_number,sl.sia_licence,sl.driving_licence FROM `wp_usermeta`um JOIN `sia_licence`sl ON um.`user_id`=sl.`sia_user_id` WHERE um.`user_id`=101;
if neeeded. IN the above example it would just return user ID = 101.
You can also just use SELECT *, in this case, but generally it's a bad idea because if there are any ambigious columns (columns with the same names) between the two tables it'll give you an error, but in your case it looks like you should be fine with:
SELECT * FROM `wp_usermeta`um JOIN `sia_licence`sl ON um.`user_id`=sl.`sia_user_id`;