我有一个带有title
字段的 PostgreSQL 表,但这些标题通常在前面包含“The”或“An”,我需要一种方法来按字母顺序对这些记录进行排序,就像在进行排序时忽略这些文章一样。
两个问题
在 SQL 中编写这个 ORDER BY 表达式的最佳方法是什么?
如何在标题字段上构建和使用适当的索引,而无需将标题字段值的子字符串复制到“alphabetical_title”字段之类的内容中并对其进行索引?
我正在寻找为 PostgreSQL 量身定制的解决方案。谢谢。
我有一个带有title
字段的 PostgreSQL 表,但这些标题通常在前面包含“The”或“An”,我需要一种方法来按字母顺序对这些记录进行排序,就像在进行排序时忽略这些文章一样。
两个问题
在 SQL 中编写这个 ORDER BY 表达式的最佳方法是什么?
如何在标题字段上构建和使用适当的索引,而无需将标题字段值的子字符串复制到“alphabetical_title”字段之类的内容中并对其进行索引?
我正在寻找为 PostgreSQL 量身定制的解决方案。谢谢。
您可以在表达式上添加索引:
create index on yourtable (natural_sort(title));
Postgres 将在适当的时候使用该索引,并且不会实际计算natural_sort(title)
它何时使用——除非你也选择了它。
话虽如此(很像 tsvector 字段),如果您出于性能原因实际存储预先计算的结果,您将获得改进的性能。如果在上述情况下,Postgres 出于任何原因决定不使用该索引,则需要为所考虑的每一行实际计算它将对您的查询造成很大的拖累。
无论哪种情况,都不要忘记数字:
http://www.codinghorror.com/blog/2007/12/sorting-for-humans-natural-sort-order.html
这里有两个函数可以帮助你开始自然排序:
/**
* @param text _str The input string.
* @return text The output string for consumption in natural sorting.
*/
CREATE OR REPLACE FUNCTION natsort(text)
RETURNS text
AS $$
DECLARE
_str text := $1;
_pad int := 15; -- Maximum precision for PostgreSQL floats
BEGIN
-- Bail if the string is empty
IF trim(_str) = ''
THEN
RETURN '';
END IF;
-- Strip accents and lower the case
_str := lower(unaccent(_str));
-- Replace nonsensical characters
_str := regexp_replace(_str, E'[^a-z0-9$¢£¥₤€@&%\\(\\)\\[\\]\\{\\}_:;,\\.\\?!\\+\\-]+', ' ', 'g');
-- Trim the result
_str := trim(_str);
-- @todo we'd ideally want to strip leading articles/prepositions ('a', 'the') at this stage,
-- but to_tsvector()'s default dictionary also strips stop words (e.g. 'all').
-- We're done if the string contains no numbers
IF _str !~ '[0-9]'
THEN
RETURN _str;
END IF;
-- Force spaces between numbers, so we can use regexp_split_to_table()
_str := regexp_replace(_str, E'((?:[0-9]+|[0-9]*\\.[0-9]+)(?:e[+-]?[0-9]+\\M)?)', E' \\1 ', 'g');
-- Pad zeros to obtain a reasonably natural looking sort order
RETURN array_to_string(ARRAY(
SELECT CASE
WHEN val !~ E'^\\.?[0-9]'
-- Not a number; return as is
THEN val
-- Do our best after expanding the number...
ELSE COALESCE(lpad(substring(val::numeric::text from '^[0-9]+'), _pad, '0'), '') ||
COALESCE(rpad(substring(val::numeric::text from E'\\.[0-9]+'), _pad, '0'), '')
END
FROM regexp_split_to_table(_str, E'\\s+') as val
WHERE val <> ''
), ' ');
END;
$$ IMMUTABLE STRICT LANGUAGE plpgsql COST 1;
COMMENT ON FUNCTION natsort(text) IS
'Rewrites a string so it can be used in natural sorting.
It''s by no means bullet proof, but it works properly for positive integers,
reasonably well for positive floats, and it''s fast enough to be used in a
trigger that populates an indexed column, or in an index directly.';
/**
* @param text[] _values The potential values to use.
* @return text The output string for consumption in natural sorting.
*/
CREATE OR REPLACE FUNCTION sort(text[])
RETURNS text
AS $$
DECLARE
_values alias for $1;
_sort text;
BEGIN
SELECT natsort(value)
INTO _sort
FROM unnest(_values) as value
WHERE value IS NOT NULL
AND value <> ''
AND natsort(value) <> ''
LIMIT 1;
RETURN COALESCE(_sort, '');
END;
$$ IMMUTABLE STRICT LANGUAGE plpgsql COST 1;
COMMENT ON FUNCTION sort(text[]) IS
'Returns natsort() of the first significant input argument.';
第一个函数的单元测试的示例输出:
public function testNatsort()
{
$this->checkInOut('natsort', array(
'<NULL>' => null,
'' => '',
'ABCde' => 'abcde',
'12345 12345' => '000000000012345 000000000012345',
'12345.12345' => '000000000012345.123450000000000',
'12345e5' => '000001234500000',
'.12345e5' => '000000000012345',
'1e10' => '000010000000000',
'1.2e20' => '120000000000000',
'-12345e5' => '- 000001234500000',
'-.12345e5' => '- 000000000012345',
'-1e10' => '- 000010000000000',
'-1.2e20' => '- 120000000000000',
'+-$¢£¥₤€@&%' => '+-$¢£¥₤€@&%',
'ÀÁÂÃÄÅĀĄĂÆ' => 'aaaaaeaaaaaae',
'ÈÉÊËĒĘĚĔĖÐ' => 'eeeeeeeeee',
'ÌÍÎÏĪĨĬĮİIJ' => 'iiiiiiiiiij',
'ÒÓÔÕÖØŌŐŎŒ' => 'oooooeoooooe',
'ÙÚÛÜŪŮŰŬŨŲ' => 'uuuueuuuuuu',
'ÝŶŸ' => 'yyy',
'àáâãäåāąăæ' => 'aaaaaeaaaaaae',
'èéêëēęěĕėð' => 'eeeeeeeeee',
'ìíîïīĩĭįıij' => 'iiiiiiiiiij',
'òóôõöøōőŏœ' => 'oooooeoooooe',
'ùúûüūůűŭũų' => 'uuuueuuuuuu',
'ýÿŷ' => 'yyy',
'ÇĆČĈĊ' => 'ccccc',
'ĎĐ' => 'dd',
'Ƒ' => 'f',
'ĜĞĠĢ' => 'gggg',
'ĤĦ' => 'hh',
'Ĵ' => 'j',
'Ķ' => 'k',
'ŁĽĹĻĿ' => 'lllll',
'ÑŃŇŅŊ' => 'nnnnn',
'ŔŘŖ' => 'rrr',
'ŚŠŞŜȘſ' => 'sssssss',
'ŤŢŦȚÞ' => 'ttttt',
'Ŵ' => 'w',
'ŹŽŻ' => 'zzz',
'çćčĉċ' => 'ccccc',
'ďđ' => 'dd',
'ƒ' => 'f',
'ĝğġģ' => 'gggg',
'ĥħ' => 'hh',
'ĵ' => 'j',
'ĸķ' => 'kk',
'łľĺļŀ' => 'lllll',
'ñńňņʼnŋ' => 'nnnnnn',
'ŕřŗ' => 'rrr',
'śšşŝșß' => 'sssssss',
'ťţŧțþ' => 'ttttt',
'ŵ' => 'w',
'žżź' => 'zzz',
'-_aaa--zzz--' => '-_aaa--zzz--',
'-:àáâ;-žżź--' => '-:aaa;-zzz--',
'-.à$â,-ž%ź--' => '-.a$a,-z%z--',
'--à$â--ž%ź--' => '--a$a--z%z--',
'-$à(â--ž)ź%-' => '-$a(a--z)z%-',
'#-à$â--ž?!ź-' => '-a$a--z?!z-',
));
您可以在 PostgreSQL 中使用各种字符串函数,但也许您最好使用文本索引,请参阅http://www.postgresql.org/docs/9.2/static/textsearch.html
正如 Denis 所提到的,您可以在 PostgreSQL 中为表达式编制索引,因此您可以索引您正在搜索的同一表达式。