Back to Top

Customized sort order in Elasticsearch

Updated 3 June 2024

Hello friends welcome again. In this blog, we will look at how we can customize sort order in Elasticsearch by providing the sort order in the array format.

Elasticsearch 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.

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

Searching for an experienced
Magento 2 Company ?
Find out More

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

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.

. . .

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