Laravel orderBy在排序数据时忽略数字值

huangapple go评论49阅读模式
英文:

Laravel orderBy ignores numeric values in ordering data

问题

我有一个名为Class ID的列,我试图对这一列进行排序。
它似乎忽略了数字。我猜是因为它们是字符串。

目前看起来是这样的:

NewSyn - 1 OBSSt
NewSyn - 1 OBSSt
NewSyn - 1 OBSSt
NewSyn - 11 SH-B
NewSyn - 11 SH-B
NewSyn - 15 SH-O
NewSyn - 16 SH-On
NewSyn - 18 SH-S
NewSyn - 18 SH-S
NewSyn - 18 SH-S
NewSyn - 19 SHS
NewSyn - 19 SHS
NewSyn - 2 OBSB
NewSyn - 2 OBSB
NewSyn - 2 OBSB
NewSyn - 2 OBSB

应该按字母顺序和数字排序:

NewSyn - 1 OBSSt
NewSyn - 1 OBSSt
NewSyn - 1 OBSSt
NewSyn - 2 OBSB
NewSyn - 2 OBSB
NewSyn - 2 OBSB
NewSyn - 2 OBSB
NewSyn - 11 SH-B
NewSyn - 11 SH-B
NewSyn - 15 SH-O
NewSyn - 16 SH-On
NewSyn - 18 SH-S
NewSyn - 18 SH-S
NewSyn - 18 SH-S
NewSyn - 19 SHS
NewSyn - 19 SHS

我尝试过:

->orderBy('case_id', 'ASC')

我的查询:

$cases = Classrooms::where('created_date', '>=' , $today)
    ->orderBy('case_id', 'ASC')
    ->get();
英文:

I have a column called Class ID and I am trying to sort this column.
It seems to ignore the numbers. I assume because they are strings.

Currently, it looks like this:

NewSyn - 1 OBSSt
NewSyn - 1 OBSSt
NewSyn - 1 OBSSt
NewSyn - 11 SH-B
NewSyn - 11 SH-B
NewSyn - 15 SH-O
NewSyn - 16 SH-On
NewSyn - 18 SH-S
NewSyn - 18 SH-S
NewSyn - 18 SH-S
NewSyn - 19 SHS
NewSyn - 19 SHS
NewSyn - 2 OBSB
NewSyn - 2 OBSB
NewSyn - 2 OBSB
NewSyn - 2 OBSB

It Should be sorted Alphabetical and using the number so :

NewSyn - 1 OBSSt
NewSyn - 1 OBSSt
NewSyn - 1 OBSSt
NewSyn - 2 OBSB
NewSyn - 2 OBSB
NewSyn - 2 OBSB
NewSyn - 2 OBSB
NewSyn - 11 SH-B
NewSyn - 11 SH-B
NewSyn - 15 SH-O
NewSyn - 16 SH-On
NewSyn - 18 SH-S
NewSyn - 18 SH-S
NewSyn - 18 SH-S
NewSyn - 19 SHS
NewSyn - 19 SHS

I have tried:

->orderBy('case_id', 'ASC')

My Query

$cases = Classrooms::where('created_date', '>=' , $today)
    ->orderBy('case_id', 'ASC')
    ->get();

答案1

得分: 1

Laravel正确地按照您的指示进行操作,您希望根据默认的逐字符比较逻辑对文本进行排序。因此,尽管不是您打算的方式,但结果仍然是正确的!

如果您想要设置自定义排序顺序,我认为最好的解决方案是将列分为三个部分。(假设语法是:文本 - 数字 文本。)

1.) 按数字前的文本排序
orderByRaw("SUBSTRING_INDEX(your_column_name, ' ', 1) ASC")
2.) 按数字排序
orderByRaw("CAST(SUBSTRING_INDEX(SUBSTRING_INDEX(your_column_name, ' ', -1), ' ', 1) AS UNSIGNED) ASC")
3.) 按数字后的文本排序
orderByRaw("SUBSTRING_INDEX(your_column_name, ' ', -1) ASC")

总结

YourModel::orderByRaw("SUBSTRING_INDEX(case_id, ' ', 1) ASC")
     ->orderByRaw("CAST(SUBSTRING_INDEX(SUBSTRING_INDEX(case_id, ' ', -1), ' ', 1) AS UNSIGNED) ASC")
     ->orderByRaw("SUBSTRING_INDEX(case_id, ' ', -1) ASC")
     ->get();

Laravel按照您的请求顺序执行排序,所以如果您只需要关注数字和其后的文本,您可以通过使用第2个和第3个代码片段(省略第1个)来完成任务。这取决于您项目的要求和您的需求。

关键点在于,我们可以使用SQL代码来操作复杂的列。我们可以将带有SQL代码的列操作传递给称为“RAW”函数的函数,例如selectRaw、whereRaw、orderByRaw等。

处理用户输入的安全性非常重要,以防止直接传递SQL代码。这就是为什么有一种分开的方法来处理这个问题。简单的select、where、orderBy和其他函数不会以任何方式接受SQL代码,但RAW函数会。在我的解决方案中,我使用了3个连续的RAW函数,将文本分成3个部分,允许我分别对每个部分进行排序,就好像它们来自3个不同的列一样。

英文:

Laravel correctly follows your instructions, as you want to sort a text based on its default logic of character-by-character comparison. Therefore, the result, although not what you intended, is still correct!

If you want to set a custom sort order, the best solution in my opinion is to divide the column into three parts. (Assuming the syntax is: text - number text.)


// Example
/*
N... - 1 O...
N... - 11 O...
A... - 11 O...
N... - 2 S...
N... - 11 A...
*/
1.) Order By Text before the number
orderByRaw("SUBSTRING_INDEX(your_column_name, ' ', 1) ASC")

// Result from Example
/*
A... - 11 O... // changed (move to here)
N... - 1 O...
N... - 11 O...
               // changed (moved from here)
N... - 2 S...
N... - 11 A...
*/
SELECT SUBSTRING_INDEX('NewSyn - 2 SH-On', ' ', 1);

# Result: "NewSyn"
2.) Order By Number
orderByRaw("CAST(SUBSTRING_INDEX(SUBSTRING_INDEX(your_column_name, ' ', -1), ' ', 1) AS UNSIGNED) ASC")

// Result from 1st orderBy
/*
A... - 11 O...
N... - 1 O...
N... - 2 S... // changed
N... - 11 O... // changed
N... - 11 A...
*/
SELECT CAST(SUBSTRING_INDEX(SUBSTRING_INDEX('NewSyn - 2 SH-On', ' - ', -1), ' ', 1) AS UNSIGNED);

# Result: 2
3.) Order By Text after the number
orderByRaw("SUBSTRING_INDEX(your_column_name, ' ', -1) ASC")

// Result from 2nd orderBy
/*
A... - 11 O...
N... - 1 O...
N... - 2 S...
N... - 11 A... // changed
N... - 11 O... // changed
*/
SELECT SUBSTRING_INDEX('NewSyn - 2 SH-On', ' ', -1);

# Result: "SH-On"

Summary

YourModel::orderByRaw("SUBSTRING_INDEX(case_id, ' ', 1) ASC")
     ->orderByRaw("CAST(SUBSTRING_INDEX(SUBSTRING_INDEX(case_id, ' ', -1), ' ', 1) AS UNSIGNED) ASC")
     ->orderByRaw("SUBSTRING_INDEX(case_id, ' ', -1) ASC")
     ->get();

Laravel performs the sorting in the order you request, so if you only need to pay attention to the number and the text after it, you can achieve the task by using the 2nd and 3rd code snippets (omitting the 1st one). It depends on the requirements of your project and what you need.

The key point is that we have the ability to manipulate complex columns using SQL code. We can pass column manipulations with SQL code to functions known as "RAW" functions, such as selectRaw, whereRaw, orderByRaw, etc.

It is of utmost importance to handle user input in a secure manner to prevent direct passing of SQL code. That's why there is a separated approach to handle this. The simple select, where, orderBy, and other functions will not accept SQL code in any way, but the RAW functions will. In my solution, I used 3 consecutive RAW functions, splitting the text into 3 parts, allowing me to sort each part separately as if they were from 3 different columns.

What is "SUBSTRING_INDEX" function?

What is "CAST" function?

How to can use SQL functions in your Laravel query?

答案2

得分: 1

以下是您要翻译的内容:

"如您已经了解,无法像对文本一样对数字进行排序。在词法排序中,2将始终在1之后出现,即使1是104的一部分。

因此,只需获取一个Collection实例并将sortBy()传递给一个函数来实现您想要的效果。这个示例使用正则表达式来分离数字和文本,然后使用太空船操作符来比较这些值。

$result = collect([
    "NewSyn - 1 OBSSt", "NewSyn - 1 OBSSt", "NewSyn - 1 OBSSt", "NewSyn - 11 SH-B",
    "NewSyn - 11 SH-B", "NewSyn - 15 SH-O", "NewSyn - 16 SH-On", "NewSyn - 18 SH-S",
    "NewSyn - 18 SH-S", "NewSyn - 18 SH-S", "NewSyn - 19 SHS", "NewSyn - 19 SHS", 
    "NewSyn - 2 OBSB", "NewSyn - 2 OBSB", "NewSyn - 2 OBSB", "NewSyn - 2 OBSB",
]);

$sorted = $result->sortBy([function ($a, $b) {
    // number will be in first match, text in second
    $pattern = "/NewSyn - ([0-9]+) (.*)/";
    if (preg_match($pattern, $a, $a_matches) && preg_match($pattern, $b, $b_matches)) {
        // if numbers are the same (comparison returns 0) sort by text instead
        return $a_matches[1] <=> $b_matches[1] ?: $a_matches[2] <=> $b_matches[2];
    }
    return $a <=> $b;
}]);
dump($sorted->values()->toArray());

您并未说明所有数字/文本对是否相同,因此在此示例中,当数字相同时,它将回退到比较文本。它还假定这些值也将以'NewSyn - '开头。

输出:

array:16 [
0 => "NewSyn - 1 OBSSt"
1 => "NewSyn - 1 OBSSt"
2 => "NewSyn - 1 OBSSt"
3 => "NewSyn - 2 OBSB"
4 => "NewSyn - 2 OBSB"
5 => "NewSyn - 2 OBSB"
6 => "NewSyn - 2 OBSB"
7 => "NewSyn - 11 SH-B"
8 => "NewSyn - 11 SH-B"
9 => "NewSyn - 15 SH-O"
10 => "NewSyn - 16 SH-On"
11 => "NewSyn - 18 SH-S"
12 => "NewSyn - 18 SH-S"
13 => "NewSyn - 18 SH-S"
14 => "NewSyn - 19 SHS"
15 => "NewSyn - 19 SHS"
]
英文:

As you already figured out, you can't sort numbers the same way you sort text. 2 will always come after 1 in a lexical sort, even if that 1 is part of 104.

So just get() a Collection instance and pass sortBy() a function to do what you want. This one uses a regular expression to separate numbers and text, then uses the spaceship operator to compare the values.

$result = collect([
    "NewSyn - 1 OBSSt", "NewSyn - 1 OBSSt", "NewSyn - 1 OBSSt", "NewSyn - 11 SH-B",
    "NewSyn - 11 SH-B", "NewSyn - 15 SH-O", "NewSyn - 16 SH-On", "NewSyn - 18 SH-S",
    "NewSyn - 18 SH-S", "NewSyn - 18 SH-S", "NewSyn - 19 SHS", "NewSyn - 19 SHS", 
    "NewSyn - 2 OBSB", "NewSyn - 2 OBSB", "NewSyn - 2 OBSB", "NewSyn - 2 OBSB",
]);

$sorted = $result->sortBy([function ($a, $b) {
    // number will be in first match, text in second
    $pattern = "/NewSyn - ([0-9]+) (.*)/";
    if (preg_match($pattern, $a, $a_matches) && preg_match($pattern, $b, $b_matches)) {
        // if numbers are the same (comparison returns 0) sort by text instead
        return $a_matches[1] <=> $b_matches[1] ?: $a_matches[2] <=> $b_matches[2];
    }
    return $a <=> $b;
}]);
dump($sorted->values()->toArray());

You didn't say if all the number/text pairs would be the same, so in this example it will fall back to comparing the text when the numbers are the same. It also assumes the values will also start with "NewSyn - ".

Output:

array:16 [
0 => "NewSyn - 1 OBSSt"
1 => "NewSyn - 1 OBSSt"
2 => "NewSyn - 1 OBSSt"
3 => "NewSyn - 2 OBSB"
4 => "NewSyn - 2 OBSB"
5 => "NewSyn - 2 OBSB"
6 => "NewSyn - 2 OBSB"
7 => "NewSyn - 11 SH-B"
8 => "NewSyn - 11 SH-B"
9 => "NewSyn - 15 SH-O"
10 => "NewSyn - 16 SH-On"
11 => "NewSyn - 18 SH-S"
12 => "NewSyn - 18 SH-S"
13 => "NewSyn - 18 SH-S"
14 => "NewSyn - 19 SHS"
15 => "NewSyn - 19 SHS"
]

huangapple
  • 本文由 发表于 2023年5月31日 23:08:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/76374947.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定