WooCommerce query order totals grouped by users with specific role - mysql

I need to list all users with a specific role (custom role 'forhandler') and for each show their totals (spent) on published orders that have status 'completed', 'processing' and 'on-hold', along with a count of the number of orders per user (also published and status as above). The result must be sorted with the highest totals first.
I managed to write a SQL query that works (and that's the limit of my SQL knowledge), but it takes 20 seconds+ when running on production data (1200 orders and increasing). Can this query be rewritten to something more efficient (or simply better)?
SELECT users.ID, users.display_name,
(SELECT COUNT(posts.ID)
FROM $wpdb->posts AS posts
LEFT JOIN $wpdb->postmeta ON $wpdb->postmeta.post_id = posts.ID
LEFT JOIN $wpdb->term_relationships AS rel ON posts.ID=rel.object_ID
LEFT JOIN $wpdb->term_taxonomy AS tax USING( term_taxonomy_id )
LEFT JOIN $wpdb->terms AS term USING( term_id )
WHERE $wpdb->postmeta.meta_key = '_customer_user'
AND $wpdb->postmeta.meta_value = users.ID
AND posts.post_type = 'shop_order'
AND posts.post_status = 'publish'
AND tax.taxonomy = 'shop_order_status'
AND term.slug IN ('completed', 'processing', 'on-hold')
) AS numOrders,
(
SELECT SUM(postmeta.meta_value)
FROM $wpdb->postmeta AS postmeta
LEFT JOIN $wpdb->posts AS pp ON pp.ID = postmeta.post_id
LEFT JOIN $wpdb->postmeta AS pm2 ON pm2.post_id = postmeta.post_id
LEFT JOIN $wpdb->term_relationships AS rel ON pp.ID=rel.object_ID
LEFT JOIN $wpdb->term_taxonomy AS tax USING( term_taxonomy_id )
LEFT JOIN $wpdb->terms AS term USING( term_id )
WHERE postmeta.meta_key = '_order_total'
AND pm2.meta_key = '_customer_user'
AND pm2.meta_value = users.ID
AND tax.taxonomy = 'shop_order_status'
AND term.slug IN ('completed', 'processing', 'on-hold')
AND pp.post_type = 'shop_order'
AND pp.post_status = 'publish'
) AS totalsOfOrders
FROM wp_users AS users
INNER JOIN wp_usermeta ON users.ID = wp_usermeta.user_id
WHERE wp_usermeta.meta_key = 'wp_capabilities'
AND wp_usermeta.meta_value LIKE '%forhandler%'
ORDER BY totalsOfOrders DESC

Related

Getting number of purchased items using SQL - Woocommerce

I'm stuck in this query for while and any type of help would be appreciated. This is for a wordpress website which uses woocommerce.
Trying to get purchase details of customers and I need to get a result set which should contain following fields.
Email | First Name | Orders | Items Purchased | Order Total
I've written the following SQL for this purpose. achg8 is the table prefix. An order may have one or more items.
SELECT
pm1.meta_value as email ,
pm2.meta_value as first_name,
sum(pm3.meta_value) as total,
count(posts.ID) as orders ,
count(items.order_item_id) as items
from achg8_posts as posts
left join achg8_postmeta as pm1 on posts.ID = pm1.post_id
left join achg8_postmeta as pm2 on posts.ID = pm2.post_id
left join achg8_postmeta as pm3 on posts.ID = pm3.post_id
left join achg8_woocommerce_order_items as items on items.order_id = posts.ID
WHERE
posts.post_type = 'shop_order' and
posts.post_status = 'wc-processing' and
pm1.meta_key='_billing_email' and
pm2.meta_key='_billing_first_name' and
pm3.meta_key='_order_total'
GROUP by email
The problem is order number and item numbers are always equal. but that's not the case actually.
Need and insight on what i'am doing wrong.
UPDATE
edited the sql to the following as per #matigo's comment. The problem now is the order total gets added up multiple times. For an example if an order has two items (i.e two rows in the joined resultset) order-total gets added up twice.
SELECT
pm1.meta_value as email ,
pm2.meta_value as first_name,
sum(pm3.meta_value) as total,
count(DISTINCT posts.ID) as orders ,
count(items.order_item_id) as items
from achg8_posts as posts
left join achg8_postmeta as pm1 on posts.ID = pm1.post_id
left join achg8_postmeta as pm2 on posts.ID = pm2.post_id
left join achg8_postmeta as pm3 on posts.ID = pm3.post_id
left join achg8_woocommerce_order_items as items on items.order_id = posts.ID
WHERE
posts.post_type = 'shop_order' and
posts.post_status = 'wc-processing' and
pm1.meta_key='_billing_email' and
pm2.meta_key='_billing_first_name' and
pm3.meta_key='_order_total'
GROUP by email
Assuming that the same order number can appear in items multiple times, it looks like you need a DISTINCT in your COUNT.
Try this:
SELECT pm1.`meta_value` as `email`,
pm2.`meta_value` as `first_name`,
SUM(pm3.`meta_value`) as `total`,
COUNT(posts.`ID`) as `orders`,
(SELECT COUNT(z.`order_item_id`)
FROM `achg8_woocommerce_order_items` z
WHERE posts.`ID` = z.`order_id`) as `items`
FROM `achg8_posts` posts INNER JOIN `achg8_postmeta` pm1 ON posts.`ID` = pm1.`post_id`
INNER JOIN `achg8_postmeta` pm2 ON posts.`ID` = pm2.`post_id`
INNER JOIN `achg8_postmeta` pm3 ON posts.`ID` = pm3.`post_id`
WHERE posts.`post_type` = 'shop_order'
and posts.`post_status` = 'wc-processing'
and pm1.`meta_key` = '_billing_email'
and pm2.`meta_key` = '_billing_first_name'
and pm3.`meta_key` = '_order_total'
GROUP by `email`, `first_name`
This should give you exactly what you're looking for 👍🏻
Can you try this and see if it works.
Sorry, I removed that joins without considering the relation with meta_key, can you try this.
SELECT
pm1.`meta_value` as `email`,
pm2.`meta_value` as `first_name`,
SUM(pm3.`meta_value`) as `total`,
COUNT(distinct posts.`ID`) as `orders`,
COUNT(distinct items.order_item_id) as `items`
FROM `achg8_posts` posts
INNER JOIN `achg8_postmeta` pm1 ON posts.`ID` = pm1.`post_id`
INNER JOIN `achg8_postmeta` pm2 ON posts.`ID` = pm2.`post_id`
INNER JOIN `achg8_postmeta` pm3 ON posts.`ID` = pm3.`post_id`
INNER JOIN achg8_woocommerce_order_items as items on items.order_id = posts.ID
WHERE
posts.`post_type` = 'shop_order' and
posts.`post_status` = 'wc-processing' and
pm1.`meta_key` = '_billing_email' and
pm2.`meta_key` = '_billing_first_name' and
pm3.`meta_key` = '_order_total'
GROUP by `email`

Querying WordPress DB from normal sql

Disclaimer: I am not going to use any other ways I have a specific requirement.
All I want is to Join from posts table to the posts meta so that I can get the featured image per post. I am able to get the post via where clause post type is post and published but I don't know how to write join for MySql to get the featured image of each post
You may try something like this:
$sql = "
SELECT $wpdb->posts.*
FROM $wpdb->posts, $wpdb->postmeta
WHERE $wpdb->posts.ID = $wpdb->postmeta.post_id
AND $wpdb->posts.post_status = 'publish'
AND $wpdb->posts.post_type = 'post'
ORDER BY $wpdb->posts.post_date DESC
";
// Get an array of objects
$posts = $wpdb->get_results($sql, OBJECT);
You may check more here.
If someone else is still looking for the sql needed here you go
SET #term = "someterm"
SELECT DISTINCT
posts.id,
posts.post_author,
posts.post_title,
posts.post_date,
LEFT(posts.post_content,300) post_content,
wpmeta.meta_value thumbnail
FROM wp_posts posts
INNER JOIN wp_postmeta wpm ON posts.id = wpm.post_id
INNER JOIN wp_postmeta wpmeta ON wpm.meta_value = wpmeta.post_id
LEFT JOIN wp_term_relationships rel ON rel.object_id = posts.ID
LEFT JOIN wp_term_taxonomy tax ON tax.term_taxonomy_id = rel.term_taxonomy_id
LEFT JOIN wp_terms t ON t.term_id = tax.term_id
WHERE
post_status = 'publish'
AND posts.post_type = 'post'
AND wpm.meta_key = '_thumbnail_id'
AND wpmeta.meta_key = '_wp_attached_file'
AND 1 =
CASE
WHEN #term IS NULL THEN 1
WHEN #term IS NOT NULL AND t.term_id = #term THEN 1
ELSE 0
END
ORDER BY post_date DESC

My SQL query pulls inaccurate information about variable woocommerce products, but works fine for regular products. Why?

I have a rather long and complicated sql query that I use to pull product reports from Woocommerce (because the browser reports do not contain enough information for my superiors' liking) For the most part, it works wonderfully. But it gives me incorrect/unreliable data for variable products, and I am not sure why this is.
Query follows:
SELECT p.ID, b.meta_value, p.post_title, pv.meta_value, SUM( qty.meta_value ), var.meta_value, "CR [4/3]", SUM( tot.meta_value ), "Avg Prc [6/4] 3?", pr.meta_value, AVG(aic.meta_value) , "Grs Prft [(8-9)/8]", "GrAvPrft [(7-9)/7]", "Grs Markup [(8-9)/8]"
FROM wp_posts p INNER JOIN wp_woocommerce_order_itemmeta pid ON p.ID = pid.meta_value AND pid.meta_key = '_product_id'
INNER JOIN wp_woocommerce_order_itemmeta qty ON pid.order_item_id = qty.order_item_id AND qty.meta_key = '_qty'
INNER JOIN wp_woocommerce_order_itemmeta tot ON tot.order_item_id = qty.order_item_id AND tot.meta_key = '_line_total'
LEFT JOIN wp_woocommerce_order_itemmeta var ON var.order_item_id = qty.order_item_id AND var.meta_key = '_variation_id'
LEFT JOIN wp_woocommerce_order_itemmeta shp ON shp.order_item_id = qty.order_item_id AND shp.meta_key = '_per_product_shipping_cost'
LEFT JOIN wp_postmeta pr ON pr.post_id = p.ID AND pr.meta_key = '_price'
LEFT JOIN wp_postmeta b ON b.post_id = p.ID and b.meta_key = '_bucket'
LEFT JOIN wp_postmeta pv ON p.ID = pv.post_id and pv.meta_key = '_page_views'
LEFT JOIN wp_woocommerce_order_itemmeta aic ON aic.order_item_id = pid.order_item_id and aic.meta_key='_actual_item_cost'
INNER JOIN wp_woocommerce_order_items oim ON oim.order_item_id = pid.order_item_id
INNER JOIN wp_posts ord ON oim.order_id = ord.ID
WHERE p.post_type = 'product'
AND oim.order_item_type = 'line_item'
AND tot.meta_value > 0
AND ord.post_status IN ('wc-completed', 'wc-processing')
GROUP BY p.ID;
Some of the stuff in the select statement is just placeholders for post-query calculations, just ignore those. My main issue is that SUM( qty.meta_value ) and SUM( tot.meta_value ) give incorrect data for variable products.

mySql Distinct - group by issue

I'm trying to find out the most purchased products but to only count distinct users ids. Basically my client wants to stop duplicate purchases from the same user, so that they can't affect the chart/best sellers.
I need to count all order_items for that product, using only Distinct users ids. Currently the results are counting all order_items so the Distinct isn't working.
Any help and I would be grateful.
Thanks in advance
SELECT *
FROM
( SELECT DISTINCT
order_item_meta_3.meta_value as distinct_user_order_items_id,
order_item_meta_2.meta_value as product_id,
SUM( order_item_meta.meta_value ) as item_quantity
FROM
wp_woocommerce_order_items as order_items
LEFT JOIN wp_woocommerce_order_itemmeta as order_item_meta
ON order_items.order_item_id = order_item_meta.order_item_id
LEFT JOIN wp_woocommerce_order_itemmeta as order_item_meta_2
ON order_items.order_item_id = order_item_meta_2.order_item_id
LEFT JOIN wp_woocommerce_order_itemmeta as order_item_meta_3
ON order_items.order_item_id = order_item_meta_3.order_item_id
LEFT JOIN wp_posts AS posts
ON order_items.order_id = posts.ID
LEFT JOIN wp_term_relationships AS rel
ON posts.ID = rel.object_ID
LEFT JOIN wp_term_taxonomy AS tax
USING( term_taxonomy_id )
LEFT JOIN wp_terms AS term
USING( term_id )
WHERE
posts.post_type = 'shop_order'
AND posts.post_status = 'publish'
AND tax.taxonomy = 'shop_order_status'
AND term.slug IN ('completed','processing','on-hold')
AND order_items.order_item_type = 'line_item'
AND order_item_meta.meta_key = '_qty'
AND order_item_meta_2.meta_key = '_product_id'
AND order_item_meta_3.meta_key = '_user_id'
GROUP BY
order_item_meta_2.meta_value
ORDER BY
item_quantity DESC ) as order_table,
wp_posts
LEFT JOIN wp_postmeta as mk1
ON wp_posts.ID = mk1.post_id
LEFT JOIN wp_postmeta as mk2
ON wp_posts.ID = mk2.post_id
WHERE
order_table.product_id = wp_posts.ID
AND wp_posts.ID = mk1.post_id
AND mk1.meta_key = 'is_album'
AND mk1.meta_value = 0
AND mk2.meta_key = '_price'
AND mk2.meta_value = 0
Wouldn't you need to do the sum outside the group by. you have to materialize the distinct set first then aggregate it...
so change first few lines to...
SELECT distinct_user_order_items_Id, product_Id, sum(item_Quantity) as item_Quantity
FROM (
SELECT
order_item_meta_3.meta_value as distinct_user_order_items_id,
order_item_meta_2.meta_value as product_id,
order_item_meta.meta_value as item_quantity

MySQL query for wordpress meta data

I need some help with a query that should return posts based on their wp-postratings score (http://wordpress.org/extend/plugins/wp-postratings/).
The user chooses a minimum rating (0 to 5 stars) and a maximum rating (0 to 5 stars) and the query should return the posts that match. I have it working where the user input for both values is above 0 but I can't seem to get my head around the 0 value. Since 0 represents unrated posts - and hence onces that have no ratings meta data - I need to select not only the posts where the rating is no more than the specified max value, but also every post that has no rating meta data.
How can I do this?? Any help will be very much appreciated!
Here's my current query:
SELECT DISTINCT p.*, (t1.meta_value+0.00) AS ratings_average, (t2.meta_value+0.00) AS ratings_users, (t3.meta_value+0.00) AS ratings_score
FROM wp_posts p
INNER JOIN wp_term_relationships tr ON (p.ID = tr.object_id)
INNER JOIN wp_term_taxonomy tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id)
INNER JOIN wp_terms t ON t.term_id = tt.term_id
LEFT JOIN wp_postmeta AS t1 ON t1.post_id = p.ID
LEFT JOIN wp_postmeta AS t2 ON t1.post_id = t2.post_id
LEFT JOIN wp_postmeta AS t3 ON t3.post_id = p.ID
WHERE t1.meta_key = 'ratings_average'
AND t2.meta_key = 'ratings_users'
AND t3.meta_key = 'ratings_score'
AND p.post_date < NOW()
AND p.post_status = 'publish'
AND (tt.taxonomy = 'post_tag' AND tt.term_id = t.term_id AND t.slug = 'liverpool')
AND ( (t1.meta_value+0.00) IS NULL OR (t1.meta_value+0.00) <= $max_stars )
ORDER BY p.post_date DESC
LIMIT 20
I had to do something similar a while back where I was running a cron job to send posts to another application that weren't already registered. The best method I found was to write a query that checked that the ID was NOT IN a query of posts with the meta key.
SELECT $wpdb->posts.ID
FROM $wpdb->posts
WHERE $wpdb->posts.post_status = 'publish'
AND $wpdb->posts.post_type = 'post'
AND $wpdb->posts.ID NOT IN (
SELECT $wpdb->posts.ID
FROM $wpdb->posts
left join $wpdb->postmeta ON ($wpdb->posts.ID = $wpdb->postmeta.post_id)
WHERE $wpdb->posts.post_status = 'publish'
AND $wpdb->postmeta.meta_key = 'meta_key')
I believe this should work, though I obviously haven't tested it.
SELECT DISTINCT p.*, (t1.meta_value+0.00) AS ratings_average, (t2.meta_value+0.00) AS ratings_users, (t3.meta_value+0.00) AS ratings_score
FROM wp_posts p
INNER JOIN wp_term_relationships tr ON (p.ID = tr.object_id)
INNER JOIN wp_term_taxonomy tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id)
INNER JOIN wp_terms t ON t.term_id = tt.term_id
LEFT JOIN wp_postmeta AS t1 ON t1.post_id = p.ID
LEFT JOIN wp_postmeta AS t2 ON t1.post_id = t2.post_id
LEFT JOIN wp_postmeta AS t3 ON t3.post_id = p.ID
WHERE t1.meta_key = 'ratings_average'
AND t2.meta_key = 'ratings_users'
AND t3.meta_key = 'ratings_score'
AND p.post_date < NOW()
AND p.post_status = 'publish'
AND (tt.taxonomy = 'post_tag'
AND tt.term_id = t.term_id
AND t.slug = 'liverpool')
AND (
p.ID NOT IN (
SELECT p.ID
FROM wp_posts AS p
LEFT JOIN wp_postmeta AS pm ON (pm.post_id = p.ID)
WHERE pm.meta_key = 'ratings_score'
)
OR
(t1.meta_value+0.00) <= $max_stars )
ORDER BY p.post_date DESC
LIMIT 20
Okay, this query seems to work for me. Its a bit ugly though and not too quick so if anyone has a better one feel free to improve upon it!
It selects all of the rated posts that are below the $max_stars value, then combines the table with a separate select which gets all of the non-rated posts:
(SELECT DISTINCT p.*, (t1.meta_value+0.00) AS ratings_average, (t2.meta_value+0.00) AS ratings_users, (t3.meta_value+0.00) AS ratings_score
FROM wp_posts p
INNER JOIN wp_term_relationships tr ON (p.ID = tr.object_id)
INNER JOIN wp_term_taxonomy tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id)
INNER JOIN wp_terms t ON t.term_id = tt.term_id
LEFT JOIN wp_postmeta AS t1 ON t1.post_id = p.ID
LEFT JOIN wp_postmeta AS t2 ON t2.post_id = p.ID
LEFT JOIN wp_postmeta AS t3 ON t3.post_id = p.ID
WHERE t1.meta_key = 'ratings_average'
AND t2.meta_key = 'ratings_users'
AND t3.meta_key = 'ratings_score'
AND p.post_date < NOW()
AND p.post_status = 'publish'
AND (tt.taxonomy = 'post_tag' AND tt.term_id = t.term_id AND t.slug = 'liverpool')
AND (t1.meta_value+0.00) <= $max_stars )
UNION
(SELECT DISTINCT p.*, NULL AS ratings_average, NULL AS ratings_users, NULL AS ratings_score
FROM wp_posts p
INNER JOIN wp_term_relationships tr ON (p.ID = tr.object_id)
INNER JOIN wp_term_taxonomy tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id)
INNER JOIN wp_terms t ON t.term_id = tt.term_id
LEFT JOIN wp_postmeta AS t1 ON (t1.post_id = p.ID AND t1.meta_key = 'ratings_score')
WHERE t1.post_id is null
AND p.post_date < NOW()
AND p.post_status = 'publish'
AND (tt.taxonomy = 'post_tag' AND tt.term_id = t.term_id AND t.slug = 'liverpool') )
ORDER BY post_date DESC