mongodb將數字字符串按照數字大小排序


一、問題概述

最近在使用mongodb時遇見了一個問題,因為精度問題,在保存價格的時候使用了字符串!這樣做也一直沒遇見什么問題,只是有一天,突然有個需求,在展示商品的時候需要按照價格排序,結果悲劇了,因為價格是字符串類型的,排序的時候是按照字符串的規則進行排序的,最終導致查詢出來的結果雜亂無章!

二、問題模擬

下面,我們就模擬一下自己遇見的問題。

我們先向數據庫中插入如下數據:


> db.product.insertMany([
... {
... "name":"手機",
... "price":"3000"
... },
...
... {
... "name":"充電器",
... "price":"50"
... },
...
... {
... "name":"耳機",
... "price":"900"
... }
... ]);
{
        "acknowledged" : true,
        "insertedIds" : [
                ObjectId("6219c823ca5ebc1273fd6421"),
                ObjectId("6219c823ca5ebc1273fd6422"),
                ObjectId("6219c823ca5ebc1273fd6423")
        ]
}

在這里,我們的插入的數據價格都是字符串類型的,我們來驗證一下:

> db.product.find({
... price:{$type:'string'}
... }).pretty();                  # 查詢價格類型為string的記錄
{
        "_id" : ObjectId("6219c823ca5ebc1273fd6421"),
        "name" : "手機",
        "price" : "3000"
}
{
        "_id" : ObjectId("6219c823ca5ebc1273fd6422"),
        "name" : "充電器",
        "price" : "50"
}
{
        "_id" : ObjectId("6219c823ca5ebc1273fd6423"),
        "name" : "耳機",
        "price" : "900"
}

下面,我們來按照價格進行正序排序:

> db.product.find().sort({"price":1}).pretty();      # 按照價格進行正序排序
{
        "_id" : ObjectId("6219c823ca5ebc1273fd6421"),
        "name" : "手機",
        "price" : "3000"
}
{
        "_id" : ObjectId("6219c823ca5ebc1273fd6422"),
        "name" : "充電器",
        "price" : "50"
}
{
        "_id" : ObjectId("6219c823ca5ebc1273fd6423"),
        "name" : "耳機",
        "price" : "900"
}

從上面運行的結果中,我們看出直接對價格進行排序是按照字符串的排序規則進行排序的(即從左到右,依次按照每位進行排序)。可是我們如果想要按照數字字符串表示的數字大小進行排序,該怎么辦呢?下面,我將介紹兩種解決方案!

三、解決方案

方案一:使用mongodb的collation功能

collation是mongodb3.4的新特性。這就意味着如果你想要使用collation,mongodb的版本必須要大於等於3.4。

> db.product.find().collation({"locale":"zh", "numericOrdering":true}).sort({"price":1}).pretty();
{
        "_id" : ObjectId("6219c823ca5ebc1273fd6422"),
        "name" : "充電器",
        "price" : "50"
}
{
        "_id" : ObjectId("6219c823ca5ebc1273fd6423"),
        "name" : "耳機",
        "price" : "900"
}
{
        "_id" : ObjectId("6219c823ca5ebc1273fd6421"),
        "name" : "手機",
        "price" : "3000"
}

在使用了collation之后,我們發現現在的輸出結果是按照價格大小進行排序了
說明:

  • "locale":"zh"表示漢字按照拼音進行排序
  • "numericOrdering":true表示將字符串類型數字按照數字大小進行排序,設置該屬性時,必須設置locale屬性,否則會報錯。

方案二: 使用聚合查詢

該方案的原理是將查詢出來的結果先轉換為數字,再按照轉換之后的結果進行排序!

> db.product.aggregate([
...   {
...     $addFields: {
...       priceNum: {
...         $toDecimal: "$price"
...       }
...     }
...   },
...   { $sort: { priceNum: 1 } }
... ]).pretty();
{
        "_id" : ObjectId("6219c823ca5ebc1273fd6422"),
        "name" : "充電器",
        "price" : "50",
        "priceNum" : NumberDecimal("50")
}
{
        "_id" : ObjectId("6219c823ca5ebc1273fd6423"),
        "name" : "耳機",
        "price" : "900",
        "priceNum" : NumberDecimal("900")
}
{
        "_id" : ObjectId("6219c823ca5ebc1273fd6421"),
        "name" : "手機",
        "price" : "3000",
        "priceNum" : NumberDecimal("3000")
}

我們發現,使用該種方法也能實現數字字符串的排序!

說明:

  • $addFields: {priceNum: {$toDecimal: "$price"}}表示將查詢出來的價格轉換為Decimal,並將這個轉換之后的字段命名為priceNum
  • sort排序一定要按照轉換之后的字段排序

四、解決方案代碼實現

1.項目創建

因為SpringBoot對各種數據源都進行了封裝,如果需要操作mongodb,只需要引入對應的啟動器,添加相關配置即可:

在pom.xml文件中引入相關依賴:

<!-- mongodb啟動器 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

在配置文件application.yaml中添加如下配置:

spring:
  data:
    mongodb:
      host: 127.0.0.1
      port: 27017
      database: test_db
logging:
  level:
    org.springframework.data.mongodb.core: DEBUG

2.創建相關pojo類

package com.xdw.mongodbtest.pojo;

import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Field;

@Data
public class Product {

    @Id
    private String id;

    @Field("name")
    private String name;

    @Field("price")
    private String price;

    @Field("chainId")
    private String chainId;

    @Field("content")
    private String content;

}

3.兩種方式的java代碼實現

方式一:使用collation

1.編寫查詢方法

public List<Product> getProductList() {
    Query query = new Query();
    // 設置collation,將字符串數字按照數值處理
    Collation collation = Collation.of(Locale.CHINESE).numericOrdering(true);
    query.collation(collation);
    // 按照價格降序進行排序
    query.with(Sort.by(Sort.Order.desc("price")));
    return mongoTemplate.find(query, Product.class);
}

2.編寫測試方法

@Test
void testProductQuery() {
    List<Product> productList = productService.getProductList();
    if(!CollectionUtils.isEmpty(productList)) {
        for (Product product : productList) {
            System.out.println(product);
        }
    }
}

3.運行測試
運行結果

最終的輸出結果果然是按照價格降序排列的。

方式二:使用聚合查詢

1.編寫查詢方法

public List<Product> getProdutByAggregation() {
        ProjectionOperation project = Aggregation.project().and("_id").as("id")
                .and("name").as("name")
                .and(ConvertOperators.ToDecimal.toDecimal("$price")).as("price")  // 將查詢出來的價格轉換為decimal
                .and("chainId").as("chainId")
                .and("content").as("content");

        // 按照價格降序進行排序
        SortOperation sort = Aggregation.sort(Sort.by(Sort.Order.desc("price")));

        return mongoTemplate.aggregate(Aggregation.newAggregation(project, sort), "product", Product.class).getMappedResults();
}

2.編寫測試方法

@Test
void testGetProdutByAggregation() {
    List<Product> productList = productService.getProdutByAggregation();
    for (Product product : productList) {
        System.out.println(product);
    }
}

3.運行測試
運行結果

五、小結

目前我只知道這兩種方式,如果還有其他方法,歡迎大家積極留言!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM