Alex
Alex

Reputation: 2775

Get all Orders IDs from a product ID in Woocommerce (+ HPOS)

How can I get an array with Order IDs by Product ID?

I mean receive all orders where specific product is presented.

I know how to do this by MySQL, but is there a way to do this by WP_Query function?

Upvotes: 4

Views: 17760

Answers (5)

ilie
ilie

Reputation: 1

Here is an improved version compatible with HPOS and in theory does not rely on the wc_order_lookup table (as it might not always contain the newest information).

function get_order_ids_by_productId($product_id, $statuses = ['wc-completed']){
    global $wpdb;

    $orders_statuses = "'" . implode("', '", $statuses) . "'";

    // High-Performance Order Storage (HPOS) table names
    $orders_table         = $wpdb->prefix . 'wc_orders';
    $order_items_table    = $wpdb->prefix . 'woocommerce_order_items';
    $order_itemmeta_table = $wpdb->prefix . 'woocommerce_order_itemmeta';

    // SQL query to get order IDs by product ID
    $order_ids = $wpdb->get_col($wpdb->prepare("
        SELECT DISTINCT o.ID
        FROM {$orders_table} AS o
        INNER JOIN {$order_items_table} AS oi ON o.ID = oi.order_id
        INNER JOIN {$order_itemmeta_table} AS oim ON oi.order_item_id = oim.order_item_id
        WHERE o.type = 'shop_order'
        AND o.status  IN ( {$orders_statuses} )
        AND oim.meta_key = '_product_id'
        AND oim.meta_value = %d
    ", $product_id));

    return $order_ids;
}

    

Upvotes: 0

LoicTheAztec
LoicTheAztec

Reputation: 254378

Updates:
  • 2024 update:

    • added more secured WPDB prepare() method, double forcing function argument as an integer in the query.
    • added another function with lightweight SQL query compatible with High-Performance Order Storage (HPOS).
    • SQL Query optimization using JOIN (thanks to MultiSuperFreek)
  • 2019 - Enabled product variation type support in the SQL Query

  • 2017 - SQL query changed to "SELECT DISTINCT" instead of "SELECT" to avoid duplicated Order IDs in the array (then no need of array_unique() to filter duplicates…).

You can embed an SQL query in a custom function with $product_id as argument.
You will have to set inside it, the order statuses that you are targeting.

1). New lightweight SQL query (compatible with High-Performance Order Storage HPOS)

WooCommerce is progressively migrating to custom tables, for better performances.

From WooCommerce 8.2, released on October 2023, High-Performance Order Storage (HPOS) is officially flagged as stable and will be enabled by default for new installations.

The fast and lightweight SQL query embedded in a function (compatible with HPOS):

// Get All defined statuses Orders IDs for a defined product ID (or variation ID)
function get_orders_ids_by_product_id( $product_id ) {
    global $wpdb;

    // HERE Define the orders status to include IN (each order status always starts with "wc-")
    $orders_statuses = array('wc-completed', 'wc-processing', 'wc-on-hold');

    // Convert order statuses array to a string for the query
    $orders_statuses = "'" . implode("', '", $orders_statuses) . "'";

    // The query
    return $wpdb->get_col( $wpdb->prepare("
        SELECT DISTINCT opl.order_id
        FROM {$wpdb->prefix}wc_order_product_lookup opl
        JOIN {$wpdb->prefix}wc_orders o ON opl.order_id = o.id
        WHERE o.type = 'shop_order'
        AND o.status IN ( {$orders_statuses} )
        AND ( opl.product_id = %d OR opl.variation_id = %d )
        ORDER BY opl.order_item_id DESC;", intval($product_id), intval($product_id) ) 
    );
}

Code goes in functions.php file of your child theme (or in a plugin).

Tested and works for WooCommerce version 8+


2). Old classic SQL query using WordPress and WooCommerce legacy tables (heavier, less efficient and not compatible with HPOS):

// Get All defined statuses Orders IDs for a defined product ID (or variation ID)
function get_orders_ids_by_product_id( $product_id ) {
    global $wpdb;

    // HERE Define the orders status to include IN (each order status always starts with "wc-")
    $orders_statuses = array('wc-completed', 'wc-processing', 'wc-on-hold');

    // Convert order statuses array to a string for the query
    $orders_statuses = "'" . implode("', '", $orders_statuses) . "'";

    // The query
    return $wpdb->get_col( $wpdb->prepare("
        SELECT DISTINCT woi.order_id
        FROM {$wpdb->prefix}woocommerce_order_itemmeta woim
        JOIN {$wpdb->prefix}woocommerce_order_items woi
            ON woi.order_item_id = woim.order_item_id
        JOIN {$wpdb->prefix}posts p
            ON woi.order_id = p.ID
        WHERE p.post_status IN ( {$orders_statuses} )
        AND woim.meta_key IN ( '_product_id', '_variation_id' )
        AND woim.meta_value = %d
        ORDER BY woi.order_item_id DESC;", intval($product_id) ) 
    );
}

Code goes in functions.php file of your child theme (or in a plugin).

Tested and works for WooCommerce version 2.5+, 2.6+ and 3+


USAGE EXAMPLES:

## This will display all orders containing this product ID in a coma separated string ##

// A defined product ID: 40
$product_id = 40;

// We get all the Orders for the given product ID in an arrray
$orders_ids = (array) get_orders_ids_by_product_id( $product_id );

// We display the orders count and the orders IDs in a coma separated string
printf( '<p>%d orders for IDs: %s</p>', count($orders_ids), implode( ', ', $orders_ids ) );

Upvotes: 26

Nadav
Nadav

Reputation: 1819

If you want your code to work in future WC updates, it is better to use functions provided by WC to get details from the DB, since WC often change the DB structure. I'd try something like:

function get_orders_id_from_product_id($product_id, $args = array() ) {
  //first get all the order ids
  $query = new WC_Order_Query( $args );
  $order_ids = $query->get_orders();
  //iterate through order
  $filtered_order_ids = array();
  foreach ($order_ids as $order_id) {
    $order = wc_get_order($order_id);
    $order_items = $order->get_items();
    //iterate through an order's items
    foreach ($order_items as $item) {
      //if one item has the product id, add it to the array and exit the loop
      if ($item->get_product_id() == $product_id) {
        array_push($filtered_order_ids, $order_id);
        break;
      }
    }
  }
  return $filtered_order_ids;
}

Usage example:

$product_id = '2094';
// NOTE: With 'limit' => 10 you only search in the last 10 orders
$args = array(
    'limit' => 10,
    'orderby' => 'date',
    'order' => 'DESC',
    'return' => 'ids',
);

$filtered_order_ids = get_orders_id_from_product_id($product_id, $args);

print_r($filtered_order_ids);

Upvotes: 3

Nadeem0035
Nadeem0035

Reputation: 3957

Modified function to get specific user product ids

function retrieve_orders_ids_from_a_product_id( $product_id,$user_id )
{
    global $wpdb;

    $table_posts = $wpdb->prefix . "posts";
    $table_postmeta = $wpdb->prefix . "postmeta";
    $table_items = $wpdb->prefix . "woocommerce_order_items";
    $table_itemmeta = $wpdb->prefix . "woocommerce_order_itemmeta";

    // Define HERE the orders status to include in  <==  <==  <==  <==  <==  <==  <==
    $orders_statuses = "'wc-completed', 'wc-processing', 'wc-on-hold'";

    # Requesting All defined statuses Orders IDs for a defined product ID
    $orders_ids = $wpdb->get_col( "
        SELECT DISTINCT $table_items.order_id
        FROM $table_itemmeta, $table_items, $table_posts , $table_postmeta
        WHERE  $table_items.order_item_id = $table_itemmeta.order_item_id
        AND $table_items.order_id = $table_posts.ID
        AND $table_posts.post_status IN ( $orders_statuses )
        AND $table_postmeta.meta_key LIKE '_customer_user'
        AND $table_postmeta.meta_value LIKE  '$user_id '
        AND $table_itemmeta.meta_key LIKE '_product_id'
        AND $table_itemmeta.meta_value LIKE '$product_id'
        ORDER BY $table_items.order_item_id DESC"
    );
    // return an array of Orders IDs for the given product ID
    return $orders_ids;
}

Usage Example

## This will display all orders containing this product ID in a coma separated string ##

// A defined product ID: 40
$product_id = 40;

// Current User
$current_user = wp_get_current_user();

// We get all the Orders for the given product ID of current user in an arrray
$orders_ids_array = retrieve_orders_ids_from_a_product_id( $product_id, $current_user->ID );

// We display the orders in a coma separated list
echo '<p>' . implode( ', ', $orders_ids_array ) . '</p>';

Upvotes: -1

Jamie Chang
Jamie Chang

Reputation: 19

I'd like to note that the above answer will return a duplicate of the order_id if the order has multiple items in it.

E.g.

If there was a product called "apples" with product_id=>1036

Customer puts "apples" 3 times in their cart and purchases it, creating order_id=>555

If I query product_id->1036, I will get array(555,555,555).

There's probably an SQL way of doing this which may be faster, (would appreciate anyone that could add to this), otherwise I used: array_unqiue() to merge the duplicates.

function retrieve_orders_ids_from_a_product_id( $product_id )
{
    global $wpdb;

    $table_posts = $wpdb->prefix . "posts";
    $table_items = $wpdb->prefix . "woocommerce_order_items";
    $table_itemmeta = $wpdb->prefix . "woocommerce_order_itemmeta";

    // Define HERE the orders status to include in  <==  <==  <==  <==  <==  <==  <==
    $orders_statuses = "'wc-completed', 'wc-processing', 'wc-on-hold'";

    # Requesting All defined statuses Orders IDs for a defined product ID
    $orders_ids = $wpdb->get_col( "
        SELECT $table_items.order_id
        FROM $table_itemmeta, $table_items, $table_posts
        WHERE  $table_items.order_item_id = $table_itemmeta.order_item_id
        AND $table_items.order_id = $table_posts.ID
        AND $table_posts.post_status IN ( $orders_statuses )
        AND $table_itemmeta.meta_key LIKE '_product_id'
        AND $table_itemmeta.meta_value LIKE '$product_id'
        ORDER BY $table_items.order_item_id DESC"
    );
    // return an array of Orders IDs for the given product ID
    $orders_ids = array_unique($orders_ids);
    return $orders_ids;
}

Upvotes: 1

Related Questions