Back to Top

Customized sort order in Elasticsearch

Updated 24 September 2024

Here will look how we can customize sort order in Elasticsearch by providing the sort order in the array format.

You can also check the Magento 2 Elasticsearch extension that provides near real-time search and analytics for all types of data.

Whether you have structured or unstructured text, numerical data, or geospatial data, Elasticsearch can efficiently store and index it to support fast searches.

One of the fundamental strengths of Elasticsearch lies in its scalability and speed. Its distributed nature allows for horizontal scaling, accommodating growing data needs seamlessly.

Additionally, its near real-time indexing capability ensures that data is available for search immediately after ingestion.

Searching for an experienced
Magento 2 Company ?
Find out More

Elasticsearch also provides several other sorting techniques by default. However, the customized sort technique is one of my favorites and has several use cases.

To get started, we first need to create a module in which we write our functionality.

You can also connect with a certified Magento 2 development company that can assist you with the development of Adobe Commerce extensions.

After creating the module we need to create a plugin to modify the Elasticsearch query. For that, we first define our plugin in the di.xml file at path app/code/VendorName/ModuleName/etc/di.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Framework\Search\AdapterInterface">
        <plugin name="customize_elasticsearch_search_results" type="Webkul\CustomizeElasticsearch\Plugin\Elasticsearch7\SearchAdapter\Adapter" sortOrder="1" />
    </type>
</config>

After defining the plugin we need to create our plugin file at the location defined in the definition of the plugin.

<?php
namespace Webkul\CustomizeElasticsearch\Plugin\Elasticsearch7\SearchAdapter;

use Magento\Framework\Search\RequestInterface;
use Magento\Elasticsearch7\SearchAdapter\Mapper;
use Magento\Elasticsearch\SearchAdapter\ResponseFactory;
use Magento\Elasticsearch\SearchAdapter\ConnectionManager;
use Magento\Elasticsearch\SearchAdapter\QueryContainerFactory;
use Magento\Elasticsearch\SearchAdapter\Aggregation\Builder as AggregationBuilder;

class Adapter
{
    /**
     * @var array
     */
    private static $emptyRawResponse = [
        "hits" =>
        [
            "hits" => []
        ],
        "aggregations" =>
        [
            "price_bucket" => [],
            "category_bucket" =>
            [
                "buckets" => []

            ]
        ]
    ];

    /**
     * Dependency Initilization
     *
     * @param Mapper $mapper
     * @param \Psr\Log\LoggerInterface $logger
     * @param ResponseFactory $responseFactory
     * @param ConnectionManager $connectionManager
     * @param AggregationBuilder $aggregationBuilder
     * @param QueryContainerFactory $queryContainerFactory
     * @param \Magento\Framework\Message\ManagerInterface $messageManager
     * @param \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $collectionFactory
     */
    public function __construct(
        private Mapper $mapper,
        private \Psr\Log\LoggerInterface $logger,
        private ResponseFactory $responseFactory,
        private ConnectionManager $connectionManager,
        private AggregationBuilder $aggregationBuilder,
        private QueryContainerFactory $queryContainerFactory,
        private \Magento\Framework\Message\ManagerInterface $messageManager,
        private \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $collectionFactory
    ) {
    }

    /**
     * Add seller product to seller collection
     *
     * @param \Magento\Framework\Search\AdapterInterface $subject
     * @param callable $proceed
     * @param RequestInterface $request
     */
    public function aroundQuery(
        \Magento\Framework\Search\AdapterInterface $subject,
        callable $proceed,
        RequestInterface $request
    ) {
        try {
            $productIds = $this->getProductCustomSortOrder();
            $client = $this->connectionManager->getConnection();
            $aggregationBuilder = $this->aggregationBuilder;
            $updatedQuery = $this->mapper->buildQuery($request);
            unset($updatedQuery['body']['query']['bool']['should']);
            unset($updatedQuery['body']['query']['bool']['minimum_should_match']);
            unset($updatedQuery['body']['sort']);
            $updatedQuery['body']['sort'][0]['_script']['type'] = 'number';
            $setSortOrderAttribute = 'params.sortOrder.indexOf(doc["_id"].value)';
            $updatedQuery['body']['sort'][0]['_script']['script']['inline'] = $setSortOrderAttribute;
            $updatedQuery['body']['sort'][0]['_script']['script']['params']['sortOrder'] = $productIds;
            unset($updatedQuery['body']['sort'][0]['_score']);
            $updatedQuery['body']['query']['bool']['filter'] = ['ids' => ['values' => $productIds]];
            $aggregationBuilder->setQuery($this->queryContainerFactory->create(['query' => $updatedQuery]));
            try {
                $rawResponse = $client->query($updatedQuery);
            } catch (\Exception $e) {
                $this->logger->debug(
                    "Elasticsearch7_SearchAdapter_Adapter aroundQuery " . $e->getMessage()
                );
                $rawResponse = self::$emptyRawResponse;
            }
            $rawDocuments = $rawResponse['hits']['hits'] ?? [];
            $queryResponse = $this->responseFactory->create(
                [
                    'documents' => $rawDocuments,
                    'aggregations' => $aggregationBuilder->build($request, $rawResponse),
                    'total' => $rawResponse['hits']['total']['value'] ?? 0
                ]
            );
            return $queryResponse;
        } catch (\Exception $e) {
            $this->logger->debug(
                "Elasticsearch7_SearchAdapter_Adapter aroundQuery " . $e->getMessage()
            );
            $this->messageManager->addError(__($e->getMessage()));
        }
        return $proceed($request);
    }

    /**
     * Get Product Custom SortOrder
     *
     * @return array
     */
    private function getProductCustomSortOrder()
    {
        $products = $this->collectionFactory->create()
            ->addFieldToFilter('visibility', ['in' => [3, 4]]);
        $evenProductIds = [];
        $oddProductIds = [];
        foreach ($products as $product) {
            if ($product->getEntityId() % 2 == 0) {
                $evenProductIds[] = $product->getEntityId();
            } else {
                $oddProductIds[] = $product->getEntityId();
            }
        }
        return array_merge($oddProductIds, $evenProductIds);
    }
}

The code that actually helps in sorting is.

$updatedQuery['body']['sort'][0]['_script']['type'] = 'number';
$setSortOrderAttribute = 'params.sortOrder.indexOf(doc["_id"].value)';
$updatedQuery['body']['sort'][0]['_script']['script']['inline'] = $setSortOrderAttribute;
$updatedQuery['body']['sort'][0]['_script']['script']['params']['sortOrder'] = $productIds;

Our actual query format looks something like this.

Array
(
    [body] => Array
    (
        [sort] => Array
        (
            [0] => Array
            (
                [_script] => Array
                (
                    [type] => number
                    [script] => Array
                    (
                        [inline] => params.sortOrder.indexOf(doc["_id"].value)
                        [params] => Array
                        (
                            [sortOrder] => Array
                            (
                                [0] => 1
                                [1] => 3
                                [2] => 5
                                [3] => 7
                                [4] => 9
                                [5] => 2
                                [6] => 4
                                [7] => 6
                                [8] => 8
                                [9] => 10
                            )
                        )
                    )
                )
            )
        )
    )
)

Here our sortOrder array contains all the odd product IDs first and all the even product IDs after them which causes our product listing to sort products that way.

According to the above code, our product listing will look like this.

Product Listing

That’s all guys we have successfully customized the sort order for our product listing.

You can also find other sorting techniques Elasticsearch provides to understand the Elasticsearch better.

Take the opportunity to browse through our range of Magento 2 extensions, meticulously created by certified Adobe Commerce Developers.

For a customized experience, bring on board and hire Magento developers who can exclusively focus on your personalized e-commerce projects.

Conclusion

This blog has guided you through the process of customizing the sort order in Elasticsearch for Magento 2, utilizing an array format to achieve a tailored product listing.

By leveraging the scalability and near real-time indexing capabilities of Elasticsearch, you can enhance the search experience for both structured and unstructured data.

We explored the steps required to create a module and a plugin that modifies the Elasticsearch query to implement a customized sorting technique.

With the provided code snippet, you can effectively organize your product listings according to specific criteria.

For further enhancements and additional sorting techniques, exploring other resources and Magento 2 extensions can be beneficial.

If you seek specialized support, consider hiring Magento developers to bring your unique e-commerce vision to life. Thank you for reading, and happy customizing!

. . .

Leave a Comment

Your email address will not be published. Required fields are marked*


Be the first to comment.

Back to Top

Message Sent!

If you have more details or questions, you can reply to the received confirmation email.

Back to Home