Elastic sort by in stock


I need some assistance with sorting elastic search results by in stock for all available sorting methods. The case would be something like:

“Sort by desired sorting method, then put all out of stock products at the end of the list of hits”

Is this possible?

Litium version: 7.6

If you’ve added the stock to the ProductDocument you can just add a sorting for in BuildSortings(...). By having it first in the additional sortings it would push unavailable items to the end.

s = s
	.Descending(x => x.InStock)
	.Field(f => searchQuery.SortDirection == SortDirection.Ascending
	? f.Field(x => x.Name.Suffix("keyword")).Ascending()
	: f.Field(x => x.Name.Suffix("keyword")).Descending())
	.Ascending(x => x.ArticleNumber);

(InStock is a boolean in this example)

Things to think of during implementation

  1. Don’t store de actual quantity in search index, instead have a calculated property (of field on the product) that contains if they are in stock as in the example above.
  2. Sorting on “In stock” may break other sorting operations, example hits by score for free text search that in the most cases need to be the first sort field.
  3. Instead of sorting it may work to boost the relevance of the search result depend on a field, se examples on elasticsearch - How can we use exists query in tandem with the search query? - Stack Overflow, that may give similar result but not break the sorting.

I was thinking I’d add a boolean to the productdocument depending on whether or not there is a instockquantity > 0 on any of the variants and then use Nils’ example sort. That would work, right?

I’m almost done with the above. I’ll try it and see how free text search behaves.

Thank you both!

Hi, I picked up this issue from @victaxel.

Could you give an example in code for option 3? I can’t get it to work, all items that are not in stock are excluded from the result, using the following code:

var countryId = _requestModelAccessor.RequestModel.CountryModel.Country.Id;
allQueries.Add(qc.Bool(b => b.Should(bs => bs.Exists(e => e.Field(f => f.InStock[countryId]).Boost(1)))));

InStock is a Dictionary<string, bool> where key is country id and value is stock status boolean (true = in stock, false = out of stock).

I also tried with the following code:

var countryId = _requestModelAccessor.RequestModel.CountryModel.Country.Id;
allQueries.Add(qc.Bool(b => b.Should(bs => bs.Term(t => t.InStock[countryId], true, 1))));

This also results in products that are not in stock to be excluded from the result.
I’m doing this in the BuildQuery-method in ProductSearchServiceDecorator.

I haven’t tested this my self but I think you need to boost more than with 1 because that is the default-value.

If using exists you probably need to set the value to true for the records that exists, all the other should not have any value there at all.

Also dependent on how the field are indexed (object or nested) the result can be different.

I’ve tried with both exists and just match the value to “true”, both filters out all out of stock products. When I used exists I only set the value if the product had stock.
I also tried boosting more than 1, such as 100 or 20, without any change. I’m lost here…

This is the property in ProductDocument, no attributes:
public Dictionary<string, bool> InStock { get; set; }

The documentation on Exists suggests that it will only retrieve documents that has a value for that field in the index, so if you leave it out when it’s not in stock it makes sense that you’re not seeing it.

Could you maybe add a weight to documents where the InStock property is true?

var countryId = _requestModelAccessor.RequestModel.CountryModel.Country.Id;
allQueries.Add(qc.FunctionScore(fs => fs
	.Functions(fu => fu
		.Weight(w => w
			.Filter(wf => wf
				.Term(t => t.InStock[countryId], true))))));
1 Like

This topic was automatically closed 28 days after the last reply. New replies are no longer allowed.