问题 如何对SQLite查询进行排序而忽略文章(“the”,“a”等)?


我正在使用C#来显示我从SQLite数据库调用的电影标题列表。目前,我正在使用一个自定义ListBox类,它具有从每个项目的开头剥离单词“The”的文本排序功能。但是,它似乎并不是最简单的方法,因为它从SQLite数据库调用 然后 排序。我更愿意将其缩减到一步,希望在我的“SELECT”查询中直接从数据库中进行排序。

我已经对此进行了一些搜索,并找到了一些建议,包括在数据库中创建一个额外的排序列。虽然这肯定是可能的,但我想知道是否有任何更简单的选项不需要插入几乎相同的重复信息(特别是如果数据库变大)。我是SQLite的新手,但我已经阅读了一些关于创建可用于创建自定义排序的collat​​e函数的内容。但是,我不确定这是否适合使用它似乎无法找到任何帮助在C#中实现它。

希望有人能够分享一些指导。如果额外的排序列是最好的方法,那么这就是我要做的。


972
2017-09-12 18:33


起源



答案:


为避免插入重复数据,有两列:TITLE_PREFIX(通常为空,但有时包含“The”或“A”;此列没有索引)和TITLE(包含没有“The”或“A”的标题;这是您创建索引的列)。要显示数据,您必须组合TITLE_PREFIX和TITLE。但你只是在TITLE上搜索。


6
2017-09-12 18:38





这是解决方案:

ORDER BY (CASE 
    WHEN sortTitle LIKE 'the %' THEN substr(sortTitle,5) 
    WHEN sortTitle LIKE 'a %' THEN substr(sortTitle,3) 
    WHEN sortTitle LIKE 'an %' THEN substr(sortTitle,4) 
    ELSE sortTitle END)

4
2018-03-02 01:51





您可以将每个标题分为两部分: title 和 prefix

使用SQLite,您可以通过组合2个字符串值 || operator 也被称为 concatenate operator

这是一个例子:

SELECT prefix || ' ' || title FROM movies ORDER BY title

你也可以使用 ltrim 如果前缀为空,那么前面没有空格:

SELECT ltrim(prefix || ' ' || title) FROM movies ORDER BY title

另一种方法是将前缀存储在标题的末尾。例如,在很多电影商店,你会看到类似的东西:

三个火枪手,


1
2017-09-12 18:38





在C#代码中

如果您想在C#中执行此操作,请使用LINQ为您执行排序。我发布了一个 PasteBin上的完整示例。这将允许您:

  • 避免重复数据库中的数据
  • 像往常一样利用数据库索引,无论哪个RDBMS
  • 在配置文件中添加干扰词,从而减少修改列表时的停机时间/重建/重新部署
  • 确保您的客户端代码中的解决方案更具可读性
DropDownList1.DataSource = myBooks.OrderBy(n => ReplaceNoise(n.Title))

public string ReplaceNoise(string input)
{
     string[] noise = new string[] { "the", "an", "a" };

     //surely this could be LINQ'd 
     foreach (string n in noise)
     {
         if (input.ToLower().StartsWith(n))
         {
             return input.Substring(n.Length).Trim();
         }
     }
     return input;
}

在您的SQLite语句中

如何简单地用空格中的空白替换噪音词?这是一个丑陋的第一步,但强烈考虑使用新列来存储此值以进行排序。

ORDER BY REPLACE(REPLACE([title],'the',''), 'a', '')

不可否认,当你最终得到这个时,这会变得很难看:

REPLACE(REPLACE(REPLACE(REPLACE([title],'The ',''),'a',''),'of',''),'by','')

0
2017-09-12 18:43



这将不允许数据库使用索引 title 如果存在的话。而且看起来OP期待大量数据。 - Daniel Vassallo
@Daniel:的确,你在索引上是正确的。对于高性能,OP可能希望找到除SQLite之外的另一个数据库。 C#解决方案更具吸引力。无论RDBMS如何,排序都属于客户端。 - p.campbell
@downvoter:小心解释为什么使用LINQ排序没有帮助?如何建议新列存储“排序”值?这些建议中的任何一个如何获得一个downvote? - p.campbell
使用replace()函数不起作用,尤其是'a'。你将用任何东西替换所有字母a。意思是“苹果”变成“pple”,可能不是你想要的。 - satur9nine


答案:


为避免插入重复数据,有两列:TITLE_PREFIX(通常为空,但有时包含“The”或“A”;此列没有索引)和TITLE(包含没有“The”或“A”的标题;这是您创建索引的列)。要显示数据,您必须组合TITLE_PREFIX和TITLE。但你只是在TITLE上搜索。


6
2017-09-12 18:38





这是解决方案:

ORDER BY (CASE 
    WHEN sortTitle LIKE 'the %' THEN substr(sortTitle,5) 
    WHEN sortTitle LIKE 'a %' THEN substr(sortTitle,3) 
    WHEN sortTitle LIKE 'an %' THEN substr(sortTitle,4) 
    ELSE sortTitle END)

4
2018-03-02 01:51





您可以将每个标题分为两部分: title 和 prefix

使用SQLite,您可以通过组合2个字符串值 || operator 也被称为 concatenate operator

这是一个例子:

SELECT prefix || ' ' || title FROM movies ORDER BY title

你也可以使用 ltrim 如果前缀为空,那么前面没有空格:

SELECT ltrim(prefix || ' ' || title) FROM movies ORDER BY title

另一种方法是将前缀存储在标题的末尾。例如,在很多电影商店,你会看到类似的东西:

三个火枪手,


1
2017-09-12 18:38





在C#代码中

如果您想在C#中执行此操作,请使用LINQ为您执行排序。我发布了一个 PasteBin上的完整示例。这将允许您:

  • 避免重复数据库中的数据
  • 像往常一样利用数据库索引,无论哪个RDBMS
  • 在配置文件中添加干扰词,从而减少修改列表时的停机时间/重建/重新部署
  • 确保您的客户端代码中的解决方案更具可读性
DropDownList1.DataSource = myBooks.OrderBy(n => ReplaceNoise(n.Title))

public string ReplaceNoise(string input)
{
     string[] noise = new string[] { "the", "an", "a" };

     //surely this could be LINQ'd 
     foreach (string n in noise)
     {
         if (input.ToLower().StartsWith(n))
         {
             return input.Substring(n.Length).Trim();
         }
     }
     return input;
}

在您的SQLite语句中

如何简单地用空格中的空白替换噪音词?这是一个丑陋的第一步,但强烈考虑使用新列来存储此值以进行排序。

ORDER BY REPLACE(REPLACE([title],'the',''), 'a', '')

不可否认,当你最终得到这个时,这会变得很难看:

REPLACE(REPLACE(REPLACE(REPLACE([title],'The ',''),'a',''),'of',''),'by','')

0
2017-09-12 18:43



这将不允许数据库使用索引 title 如果存在的话。而且看起来OP期待大量数据。 - Daniel Vassallo
@Daniel:的确,你在索引上是正确的。对于高性能,OP可能希望找到除SQLite之外的另一个数据库。 C#解决方案更具吸引力。无论RDBMS如何,排序都属于客户端。 - p.campbell
@downvoter:小心解释为什么使用LINQ排序没有帮助?如何建议新列存储“排序”值?这些建议中的任何一个如何获得一个downvote? - p.campbell
使用replace()函数不起作用,尤其是'a'。你将用任何东西替换所有字母a。意思是“苹果”变成“pple”,可能不是你想要的。 - satur9nine


您可以尝试构建一个支持全文搜索的表(使用 FTS标题上的模块)。然后,您将能够快速搜索标题中的任何单词,而无需您进行大量额外的工作。例如,用户查询 好难看 可能会产生“好,坏,丑”作为其首批成果之一。所有这些的额外成本大约是文本本身长度的四分之一,但对于您的数据集可能更多,因为标题不是完整的英文文本。您还需要花时间构建这些额外的索引 - 您不希望在实时系统上的主数据集上构建它们(显然) - 但这不应该是一个太大的问题。


0
2017-09-12 21:35





创建一个虚拟列(可以在C#中实现的函数的结果)并对此虚拟列进行排序。无论你想要它做什么,这个功能都可以像“三个火枪手”一样将“The”移动到最后,或者丢弃“The”。


0
2017-09-14 18:02



有关如何在System.Data.SQLite站点上的某个位置执行此操作的文档。排序将在SQLite中的SELECT语句中完成,但您将放弃对排序索引的访问权限。我假设如果你现在将它们放在一个列表框中,你的结果集中没有成千上万的标题,所以这应该没问题。 - Tim
这是我的意思的一个例子: - Tim
ivankristianto.com/software-development/visual-studio-net/... - Tim