2014-01-06 31 views
0

不幸的是,我有类似下面的表格:法从非标准标记字符串值的PostgreSQL

DROP TABLE IF EXISTS my_list; 
CREATE TABLE my_list (index int PRIMARY KEY, mystring text, status text); 

INSERT INTO my_list  
(index, mystring,           status) VALUES 
    (12, '',             'D'), 
    (14, '[id] 5',            'A'), 
    (15, '[id] 12[num] 03952145815',       'C'), 
    (16, '[id] 314[num] 03952145815[name] Sweet',    'E'), 
    (19, '[id] 01211[num] 03952145815[name] Home[oth] Alabama', 'B'); 

有什么诀窍从上面显示的文本mystring走出[id]数量integer?仿佛我跑下面的查询:

SELECT index, extract_id_function(mystring), status FROM my_list; 

,并得到了类似的结果:

12 0  D 
14 5  A 
15 12 C 
16 314 E 
19 1211 B 

优选仅简单的字符串函数,如果没有正则表达式将被罚款。

+1

**总是在问题**中指定PostgreSQL版本。 (请在完成后进行编辑和评论)。然后请找出设计该架构的人并向他们说明意思;-)。无论如何,我会给出答案。 –

+0

另外,*为什么*你想避免正则表达式?有时他们是工作的正确工具。尤其是考虑到SQL中的字符串操作有多痛苦,因为无法在同一查询级别的其他位置轻松引用值。 –

+0

我在Windows 7上的实际版本是9.1。我使用正则表达式对查询进行了一些测试,然后发现该正则表达式在unicode字母方面存在问题,而这些字母常常是我的语言,所以我不能使用它可靠。很明显,我做出了这个shema,我准备好为自己说:)但是今天我绝对不会那么做。用于我使用的编程。NET这样的表达式不是问题,但我对PostgreSQL的想法不够。 –

回答

2

如果我理解正确,你有一个非常规的标记格式,其中[id]后跟一个空格,然后是一系列代表数字标识符的数字。没有结束标记,下一个非数字字段结束标识。

如果是这样,你将能够用non-regexp字符串操作来做到这一点,但只能非常糟糕。你真正需要的是相当于strtol的SQL,它消耗了直到第一个非数字的输入,并且只返回它。转换为integer不会这样做,如果在数字后面看到非数字垃圾,它会报告错误。 (正如我刚刚写了一个C扩展,揭露strtol解码十六进制值,但我猜你不想使用C扩展,如果你甚至不想正则表达式......)

它可以与字符串OPS做,如果你做了简化假设,即[id] nnnn标签总是用绳子或另一个标签年底结束,所以它总是[在号码的末尾。如果多个字符串出现,我们还假定您只对第一个[id]感兴趣。这样,你可以写类似下面的恐怖怪物:

select 
    "index", 
    case 
    when next_tag_idx > 0 then substring(cut_id from 0 for next_tag_idx) 
    else cut_id 
    end AS "my_id", 
    "status" 
from (
    select 
    position('[' in cut_id) AS next_tag_idx, 
    * 
    from (
    select 
     case 
     when id_offset = 0 then null 
     else substring(mystring from id_offset + 4) 
     end AS cut_id, 
     * 
    from (
     select 
     position('[id] ' in mystring) AS id_offset, 
     * 
     from my_list 
    ) x 
) y 
) z; 

如果有人曾经实际使用该查询什么,小猫会从天上掉下来,并图示在人行道上,惊恐地哀号一路下跌)。

或者你也可以是明智的,只是使用正则表达式对这种字符串处理的,在这种情况下,您的查询(假设你只想要第一个[id])是:

regress=> SELECT 
      "index", 
      coalesce((SELECT (regexp_matches(mystring, '\[id\]\s?(\d+)'))[1])::integer, 0) AS my_id, 
      status 
      FROM my_list; 
index | my_id   | status 
-------+----------------+-------- 
    12 | 0    | D 
    14 | 5    | A 
    15 | 12    | C 
    16 | 314   | E 
    19 | 01211   | B 
(5 rows) 

更新:如果你在使用正则表达式处理unicode问题时,升级到Pg 9.2。请参阅https://stackoverflow.com/a/14293924/398670

+0

你好Craig并且感谢你对范例的广泛解释。这真的是我应该考虑创建一个脚本来改变过去数据的地方!由于这两种解决方案都很复杂且耗时。无论如何,正则表达式看起来更容易接受,也更不容易出错。您的表情是否可以升级到索引12(第一行)上的数字0? –

+0

@ user973238当然。这是一个简单的'coalesce'。是的,我建议将数据分解到您的模式中,这样您就不必一直进行这种处理。如果你试图存储键/值数据(标签等),如果没有固定的属性名称列表,你可以用作列,查看'hstore',或者考虑存储'json'字段。或者如果卡住了,你可以回退EAV。 –

+0

hstore非常有趣的事情,不知道这一点。我虽然关于存储XML这样的东西,但我看到有提供的方法。谢谢克雷格。 –