【问题标题】:ORDER BY alphanumeric characters only in SQLite仅在 SQLite 中 ORDER BY 字母数字字符
【发布时间】:2015-12-14 16:56:55
【问题描述】:

我在 SQLite(在 Android 上)中对歌曲进行排序。我想订购它们:

  1. 不区分大小写
  2. 末尾有前导数字,按整数值。
  3. 没有标点符号(例如括号、句号、连字符、撇号)

我有 1 和 2 个工作(见下文)。但是,除了为每个字符调用 replace() 之外,我不知道如何替换每个字符(字母、数字和空格除外)。

除了对 replace() 的约 32 次调用之外,还有其他方法吗?
(ASCII 值 33-47,58-64,91-96,123-126)


这是一个测试表。理想情况下,值“n”应该按顺序排列。 (不,您不能通过n 订购;)

create table songs (n integer, name text);
insert into songs (n,name) values (6,'I''ll Be That Girl');
insert into songs (n,name) values (24,'1969');
insert into songs (n,name) values (9,'La Moldau');
insert into songs (n,name) values (20,'Pule');
insert into songs (n,name) values (7,'I''m a Rainbow Too');
insert into songs (n,name) values (21,'5 Years');
insert into songs (n,name) values (18,'Pressure');
insert into songs (n,name) values (13,'Lagan');
insert into songs (n,name) values (1,'any old wind that blows');
insert into songs (n,name) values (17,'Poles Apart');
insert into songs (n,name) values (8,'Imagine');
insert into songs (n,name) values (14,'Last Stop before Heaven');
insert into songs (n,name) values (3,'I Before E Except After C');
insert into songs (n,name) values (4,'i do, i do, i do');
insert into songs (n,name) values (22,'99 Luftballons');
insert into songs (n,name) values (12,'L''accord parfait');
insert into songs (n,name) values (15,'Pluto');
insert into songs (n,name) values (19,'The Promise');
insert into songs (n,name) values (2,'(Don''t Fear) The Reaper');
insert into songs (n,name) values (10,'L.A. Nights');
insert into songs (n,name) values (23,'911 is a Joke');
insert into songs (n,name) values (5,'Ichthyosaurs Are Awesome');
insert into songs (n,name) values (11,'Labradors are Lovely');
insert into songs (n,name) values (16,'P.O.D.-Boom');

这是上面 1 和 2 的解决方案:

SELECT n
FROM songs
ORDER BY
  CASE WHEN name GLOB '[0-9]*' THEN 1
       ELSE 0
  END,
  CASE WHEN name GLOB '[0-9]*' THEN CAST(name AS INT)
       ELSE name
  END
COLLATE NOCASE

对于这个测试集,它按以下顺序生成结果:2,1,3,4,6,7,5,8,12,10,9,11,13,14,16,15,17,18,20,19,21,22,23,24

我可以通过手动替换每个不需要的字符来修复这个特定的测试集:

SELECT n
FROM songs
ORDER BY
  CASE WHEN name GLOB '[0-9]*' THEN 1
       ELSE 0
  END,
  CASE WHEN name GLOB '[0-9]*' THEN CAST(name AS INT)
       ELSE
         replace(
           replace(
             replace(
               replace(name,'.',''),
               '(',''
             ),
             '''',''
           ),
           '  ',' '
         )
  END
COLLATE NOCASE

【问题讨论】:

  • 如果有帮助,我可以依赖 SQLite 3.8.6 或更高版本。目前我只针对 Android L,很快就会针对 Android M。
  • @Phrogz ..为什么不尝试将原始字符串长度和用''(空字符串) 和order 替换的标点符号的字符串长度之间的差异问题中的第 3 位?
  • 我不认为你明白我的意思。这就是我想要传达的。对于字符串 'ab,c' 它将是 len(orig_string) 为 4 并且 len(orig_string_with_punctuation 替换为 '') 为 3。所以这样你就可以得到 1 的差异。对于没有标点符号的字符串,这个差异将为 0。因此您可以在 order by 子句中使用这些差异。希望你能得到我。
  • 当您存储数据时,请创建一个干净的排序键。然后按提取数据排序。这将使您的生活变得更加简单。
  • @AllanS.Hansen 这是对媒体播放器从 mp3 id3 标签中实时提取的数据的 sqlite 查询。无法修改数据库。

标签: sql sqlite


【解决方案1】:

我会在表中添加一个额外的列,称为“SortingName”或其他名称。插入时计算这个值,最好不要在 SQL 中,而是在具有所有这些不错的字符串操作的高级语言中。

我真的不明白这个数字。我想您可以做的最简单的事情是在插入之前提取数字并将其放入另一列,例如“SortingNumber”。

然后像这样简单地排序:

Order By
  SortingName,
  SortingNumber

(或者反过来。)

另一个优势是性能。您通常比写入数据更频繁地读取数据。您甚至可以在这两个排序列上创建索引,这在查询中计算通常是不可能的。

【讨论】:

  • 对不起,数据库在我看来是只读的。该表是从找到的歌曲的元数据动态生成的。有可能我可以添加包含此信息的第二个表,但我需要担心随着新歌曲的出现或消失,这些表会不同步。
【解决方案2】:

在我看来,性能最高的方法是创建一个触发器来填充一个名为 sort_key 的新字段。您将需要一个主键。

CREATE TABLE songs (n INTEGER, name TEXT, 
                    sort_key TEXT, 
                    ID INTEGER PRIMARY KEY AUTOINCREMENT);

CREATE TRIGGER songs_key_trigger
    AFTER INSERT ON songs FOR EACH ROW
    BEGIN n
        Declare @sort_key as varchar(255)
        -- calculate and call here your slugify function
        -- to fill sort_key from 'new.n' and 'new.name'
        UPDATE songs 
          SET sort_key = @sort_key
          WHERE ID = new.ID;
    END

意识到这种方法索引友好,您可以在新列上创建索引以避免表全扫描操作。

【讨论】:

    【解决方案3】:

    第一种方案(DB和应用可以修改时):

    将单列添加到您的表格中,例如solumntForSorting。 然后在插入之前在您的应用程序上,将您的第二个条件(“末尾有前导数字,按整数值。”)作为 0 或 1 连接到首先从不需要的符号中“清除”的歌曲名称。 所以在 solumntForSorting 你会得到这样的结果:0Im a Rainbow Too1911 is a Joke

    第二种方案(只修改应用时):

    如果您必须对不包括某些符号的数据进行排序并且不允许更改数据库,则由于过滤了不需要的值,您将获得较慢的选择。大部分开销将在 CPU 时间和内存上。

    从我的角度来看,使用替换功能很乏味,这就是为什么我建议将 CTE 与要删除的值列表一起使用,例如 ('.', '.', ';', '(', ' )'、''''、'-')。 CTE 会像多次替换一样笨重,但更容易修改和维护。

    试试这个解决方案:

     WITH RECURSIVE 
     ordering_name_substr(len, name, subsstr, hex_subsstr, number) 
     AS (SELECT  length(name), name, substr(name, 1, 1), hex(substr(name, 1, 1)), 1  
           FROM songs
          UNION ALL 
         SELECT len, name, substr(name, number + 1, 1),
                hex(substr(name, number + 1, 1)), number + 1
           FROM ordering_name_substr WHERE number < len),
     last_order_cretaria(value, old_name)
      AS (select GROUP_CONCAT(subsstr, ''), name 
               from ordering_name_substr 
            where hex_subsstr not in
           ('28', '29', '2C', '2E', '27') group by name )
    
    SELECT S.n, S.name
    FROM songs AS S LEFT JOIN last_order_cretaria AS OC
    ON S.name = OC.old_name
    ORDER BY
      CASE WHEN name GLOB '[0-9]*' THEN 1
           ELSE 0
      END,
      CASE WHEN name GLOB '[0-9]*' THEN CAST(name AS INT)
           ELSE
             OC.value
      END
    COLLATE NOCASE
    

    I have tested on sqlfiddle.

    ('28', '29', '2C', '2E', '27') 列表中,您有一些 ASCII 码(十六进制)值,您希望在排序时考虑转义这些值。

    您也可以尝试使用值本身,例如:('.', '.', ';', '(', ')', '''', '-')

    WITH RECURSIVE 
     ordering_name_substr(len, name, subsstr, number) 
     AS (SELECT length(name), name, substr(name, 1, 1), 1  
           FROM songs
          UNION ALL 
         SELECT len, name, substr(name, number + 1, 1),
                number + 1
           FROM ordering_name_substr WHERE number < len),
     last_order_cretaria(value, old_name)
      AS (select GROUP_CONCAT(subsstr, ''), name 
               from ordering_name_substr 
            where subsstr not in
           ('.', '.', ';', '(', ')', '''', '-') group by name )
    
    SELECT S.n, S.name
    FROM songs AS S LEFT JOIN last_order_cretaria AS OC
    ON S.name = OC.old_name
    ORDER BY
      CASE WHEN name GLOB '[0-9]*' THEN 1
           ELSE 0
      END,
      CASE WHEN name GLOB '[0-9]*' THEN CAST(name AS INT)
           ELSE
             OC.value
      END
    COLLATE NOCASE
    

    要使这种排序工作快速简单,您必须能够更改您的数据库和应用程序。

    【讨论】:

      【解决方案4】:

      如果你被允许创建函数,这就是我要创建的(取自 How to strip all non-alphabetic characters from string in SQL Server? 并稍作修改):

      Create Function [dbo].[RemoveNonAlphaNumericCharacters](@Temp VarChar(1000))
      Returns VarChar(1000)
      AS
      Begin
      
          Declare @KeepValues as varchar(50)
          Set @KeepValues = '%[^a-zA-Z0-9\s]%'
          While PatIndex(@KeepValues, @Temp) > 0
              Set @Temp = Stuff(@Temp, PatIndex(@KeepValues, @Temp), 1, '')
      
          Return @Temp
      End
      

      这将满足您的 #3 要求并从字符串中去除所有垃圾,然后您的查询将如下所示:

      SELECT n
      FROM songs
      ORDER BY
        CASE WHEN [dbo].[RemoveNonAlphaNumericCharacters](name) GLOB '[0-9]*' THEN 1
             ELSE 0
        END,
        CASE WHEN [dbo].[RemoveNonAlphaNumericCharacters](name) GLOB '[0-9]*' THEN CAST(name AS INT)
             ELSE [dbo].[RemoveNonAlphaNumericCharacters](name)
        END
      COLLATE NOCASE
      

      它看起来不漂亮,可能没有最佳性能。我可能会按照 Stefan 的建议去做。解析您的歌曲名称并将修剪后的名称插入单独的列中,仅用于排序(当然该列上有索引)。这应该是最好的解决方案。

      【讨论】:

      • 感谢您的想法。我认为这是不可能的,因为这不是我的数据库,而是由媒体管理器提供的。 stackoverflow.com/a/8283265
      • @Phrogz 我在查看标记产品时应该更加小心。我想没有其他真正的方法,而不是用 30 多个替换手动删除所有垃圾。
      【解决方案5】:

      您可以使用 sqlite3 Android NDK Bindings 通过 JNI 调用访问完整的 sqlite3 c API。

      然后你可以使用sqlite3_create_collation_v2()和相关函数Define New Collating Sequences

      此方法不会更改数据库,因为排序规则仅在当前数据库连接上被覆盖。所以它满足了这个要求,如果数据库是只读的,它就可以工作。

      请注意,我说你可以。我不是说你应该!权衡这种方法的优缺点,因为在大多数情况下,这可能不值得付出额外的努力。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-10-12
        • 2012-01-23
        • 2012-07-14
        • 2022-01-07
        • 2015-11-30
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多