On many of our clients’ ecommerce sites, we use the WooCommerce Subscriptions plugin to manage recurring products or memberships. In some cases we need to retrieve a list of all customers who are actively subscribed to a particular product.
Table Of Contents
Common Solution
The generally accepted solution for listing the customers is to retrieve a list of subscriptions and loop through each one to retrieve the customer.
It looks something like this.
This works fine for small sites with a couple hundred subscribers.
The Downside
Using the wcs_get_subscriptions_for_product runs a reasonably performant query to retrieve the subscription ids. To load the customer ids, it must loop through the results and call wcs_get_subscription on each one. Depending on your cache situation, this could mean at most a lot of queries, or at least a lot of cache server lookups.
Turns out that first retrieving the subscriptions to then retrieve the customers is not very efficient and will lead to performance degradation on large sites.
A Direct Approach
Instead of getting a list of subscriptions from the query, we can get a list of customers directly with some creative MySQL
The product id in this example is ‘123’ and must be changed to the actual product id
Let’s break down what is happening here.
- We
SELECTtheuser(customer) id from the users table. - We
JOINtheorderstable using the user id.- We limit the order types to
shop_subscriptiononly. - We limit subscriptions to active subscriptions only.
- We limit the order types to
- We
JOINtheorder_itemstable using the order id.- We limit
order_itemsto onlyline_itemsas we don’t need any other information.
- We limit
- We
JOINtheorder_itemmetatable using the order item id.- We limit
order_itemmetaitems to_variation_idand_product_idonly. - We limit the value of
_variation_idor_product_idto the id of the product we are looking for.
- We limit
The result is we get a list of user ids who are:
- Subscribed to the provided product id.
- Who have an active subscription.
Why the INNER JOIN
When working with a huge database, it is often more performant to use INNER JOIN to limit the resulting set before limiting the results based on criteria. The provided INNER JOIN replaces the typical WHERE statements to limit early and allow MySQL to further optimize the query.
Using INNER JOIN also reduces the resulting set when debugging queries use SELECT *.
If you prefer to use
WHEREit will work perfectly fine.
Reusability
It is not reasonable to send a query directly to MySQL over and over throughout a codebase, so let’s put everything we have into a reusable function.
Notes
- It is assumed your site has HPOS enabled.
- You will probably want to add some caching if you’re going to be querying often.