【问题标题】:How to get the position of the first occurrence of a digit in a postgres select如何在postgres选择中获取第一次出现数字的位置
【发布时间】:2019-01-18 11:31:40
【问题描述】:

我有一个包含 SDK 名称和版本的字段,并且它不是标准化的,因此我可以轻松地提取名称和版本。这些是人为的值,但代表了我正在使用的可能值:

  • JavaScript/2.3.4
  • JavaScript/4.3.1
  • Android4.6.5
  • Android3.2.1
  • Swift4.5.3
  • 斯威夫特/3.1.1.5

如您所见,“/”的使用并不一致,我需要一种方法来一致地从数字中解析 alpha,以便最终得到如下两列:

JavaScript     2.3.4
JavaScript     4.3.1
Android        4.6.5
Android        3.2.1
Swift          4.5.3
Swift          3.1.1.5

我已经寻找了不同的方法来做到这一点,但我发现似乎没有什么能满足我的要求。

最终,我需要将其放入 Postgres SELECT 语句中,如下所示:

编辑

我认为 DISTINCT 关键字是不必要且令人困惑的。它最初是 COUNT/GROUP BY 查询的一部分,但为了简单起见,我只想分别列出包含三列的所有行:sdk、sdk_name 和 sdk_version。从那里我将使用答案中最好的解析公式来做我需要的事情。

SELECT sdk, [parse sdk name formula] as "sdk_name", [parse sdk version formula] as "sdk_version"

此外,我没有要在查询中提供的固定 SDK 列表,因此我不确定 with/as/values 策略是否适合我,但我不知道并且看起来很有用。不过,我想 with/values 可以只是另一个 SELECT 查询。

【问题讨论】:

  • 对于所有回答我问题的人,非常感谢您的快速周转。我将尝试所有策略并回发结果并选择最佳答案。干杯!

标签: sql regex postgresql parsing


【解决方案1】:

使用正则表达式函数substring():

with my_data(sdk) as (
values
    ('JavaScript/2.3.4'),
    ('JavaScript/4.3.1'),
    ('Android4.6.5'),
    ('Android3.2.1'),
    ('Swift4.5.3'),
    ('Swift/3.1.1.5')
)

select 
    substring(sdk from '[^\d/]*') as sdk_name, 
    substring(sdk from '\d.*') as sdk_version
from my_data 

  sdk_name  | sdk_version 
------------+-------------
 JavaScript | 2.3.4
 JavaScript | 4.3.1
 Android    | 4.6.5
 Android    | 3.2.1
 Swift      | 4.5.3
 Swift      | 3.1.1.5
(6 rows)

更新。

您可以将select 查询放在with 部分(而不是values):

with my_data(sdk) as (
    <select sdk from ...>
)
select 
    substring(sdk from '[^\d/]*') as sdk_name, 
    substring(sdk from '\d.*') as sdk_version
from my_data 

或在from 子句中:

select 
    substring(sdk from '[^\d/]*') as sdk_name, 
    substring(sdk from '\d.*') as sdk_version
from (
    <select sdk from ...>
) my_data

【讨论】:

  • 如果with/as/values can be a dynamic list like another SELECT statement. We don't have an SDKs table so it would have to be a SELECT on the same table I am trying to query. Not sure this will actually do what I need to do but perhaps the substring` 公​​式就是我所需要的答案,这很有趣。我会放手的。谢谢!
  • 谢谢@klin,我会试试的。在您更新答案之前,我的查询系统出现错误:Mode Analytics using AWS Athena client。我在substring(sdk from '[^\d/]*') as sdk_name 上收到此错误:[Simba][AthenaJDBC](100071) AWS Athena 客户端抛出了一个错误。 SYNTAX_ERROR:第 3:15 行:函数 substr 的意外参数(varchar、varchar(7))。预期: substr(varchar(x), bigint, bigint) , substr(varchar(x), bigint) , substr(char(x), bigint, bigint) , substr(char(x), bigint) -这可能是 postgres 版本问题或模式支持问题,对吧?
  • 我是这么认为的,可惜我从来没有用过这个软件。
【解决方案2】:

正则表达式解析是相当计算密集型的,因此与其使用两个函数调用(如在其他答案中那样),您应该将它们组合成一个调用,然后从结果中提取所需的值:

WITH d(sdk) AS (
  VALUES
    ('JavaScript/2.3.4'),
    ('JavaScript/4.3.1'),
    ('Android4.6.5'),
    ('Android3.2.1'),
    ('Swift4.5.3'),
    ('Swift/3.1.1.5'),
    ('C#/23.1') )
SELECT unq.sdk, re.match[1] AS sdk_name, re.match[2] AS sdk_version
FROM (SELECT DISTINCT sdk FROM d) unq,
     regexp_match(unq.sdk, '([^0-9/]*)/*([0-9.]*)') re (match);

正则表达式执行以下操作:

  1. ([^0-9/]*) 捕获直到第一个数字或正斜杠的所有内容。请注意,这也将匹配包含 A-Za-z 以外字符的 SDK 名称。
  2. /* 跳过正斜杠(如果存在)
  3. ([0-9.]*) 捕获任何以下数字或点。如果您确信后面只有数字和点,那么您也可以使用(*)

还请注意,我将DISTINCT 子句放在单独的子查询中。首先处理每一行然后丢弃任何重复项不是很有效。相反,请先删除重复项。

PG-10 前版本

函数 regexp_match() 是在版本 10 中引入的。如果您有旧版本,则可以使用不带 g 标志的 regexp_matches() 来获得相同的结果 (PG8.3+)。

【讨论】:

  • regexp_match() 中使用的具有两个模式的表达式实际上等同于两个简单的正则表达式匹配。此外,该函数必须创建一个数组,并且查询必须取消嵌套它。这不能比两个substring() 调用快。 See my benchmarks.
  • @klin 您的解决方案不包括 OP 要求的 DISTINCT 子句。这使得基准相当无与伦比。结果将在很大程度上取决于重复的比例,在您的基准数据集的情况下,重复的比例小于 2%(87,684 个中的 1,277 个)。我可以想象在这种情况下(使用某个 SDK 开发的应用程序表?)重复的比例要高得多。
  • 您说得对,我应该从您的查询中删除 DISTINCT 或将其添加到其他查询中。它仍然比我的慢得多。
  • 假设我真正需要的只是将 regexp_match 作为一列。如果是这样,这可能对我有用。我要试一试,然后告诉你。谢谢!
  • @CraigConover regexp_match() 是一个集合返回函数; re 是结果的别名,否则系统会生成一个名称,然后您就不能轻易引用它。与(match) 类似,这是一个(1 项)列名称别名列表。
【解决方案3】:

您可以为此使用translate

SELECT sdk, translate(sdk,'0123456789/.','') AS sdk_name, 
       translate(lower(sdk),'abcdefghijklmnopqrstuvwxyz/','') AS sdk_version 
FROM   table1;

工作fiddle

编辑(戈登):

这是个好主意。我发现使用regexp_replace() 更简单:

select regexp_replace(sdk, '[0-9/.]', '', 'g') as sdk_name,
       regexp_replace(sdk, '[a-zA-Z/]', '', 'g') as sdk_version

【讨论】:

  • 这看起来真的很简单,我会把它和其他答案一起给出。谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-06
  • 2020-08-28
  • 1970-01-01
  • 2020-06-22
  • 2011-11-21
相关资源
最近更新 更多