如何在 BigQuery 中处理语义版本数据类型

Mic*_*oru 3 sql google-bigquery google-cloud-platform

我知道 BigQuery 中没有数据类型。您希望在 BigQuery 中如何处理语义版本?

我有以下架构:

software:string,
software_version:string
Run Code Online (Sandbox Code Playgroud)

software_version 列是string但我存储的数据采用 semver 格式:`MAJOR.MINOR.PATCH-prerelease

我特别想演算子< > =

select '4.0.0' < '4.0.0-beta' 
Run Code Online (Sandbox Code Playgroud)

这会返回true,但根据 semver定义,它是 false。因为该字符-用于预发布。

Mik*_*ant 6

以下是 BigQuery 标准 SQL

您可以使用compareSemanticVersionUDF 比较两个语义版本
和/或使用normaizedSemanticVersionUDF 通过 ORDER BY 子句对输出进行排序。
请参阅下面的示例,其中两个用例(比较和排序)合二为一

#standardSQL
CREATE TEMP FUNCTION normaizedSemanticVersion(semanticVersion STRING) 
AS ((
  SELECT STRING_AGG(
      IF(isDigit, REPEAT('0', 8 - LENGTH(chars)) || chars, chars), '' ORDER BY grp 
    ) || 'zzzzzzzzzzzzzz' 
  FROM (
    SELECT grp, isDigit, STRING_AGG(char, '' ORDER BY OFFSET) chars,
    FROM (
      SELECT OFFSET, char, isDigit,
        COUNTIF(NOT isDigit) OVER(ORDER BY OFFSET) AS grp
      FROM UNNEST(SPLIT(semanticVersion, '')) AS char WITH OFFSET, 
      UNNEST([char IN ('1','2','3','4','5','6','7','8','9','0')]) isDigit
    )
    GROUP BY grp, isDigit
)));
CREATE TEMP FUNCTION compareSemanticVersions(
  normSemanticVersion1 STRING, 
  normSemanticVersion2 STRING) 
AS ((
  SELECT CASE 
      WHEN v1 < v2 THEN 'v2 newer than v1'
      WHEN v1 > v2 THEN 'v1 newer than v2'
      ELSE 'same versions'
    END
  FROM UNNEST([STRUCT(
    normaizedSemanticVersion(normSemanticVersion1) AS v1, 
    normaizedSemanticVersion(normSemanticVersion2) AS v2
  )])
));
WITH test AS (
  SELECT '1.10.0-alpha' AS v1 , '1.0.0-alpha.1' AS v2 UNION ALL
  SELECT '4.0.0', '4.0.0-beta' UNION ALL
  SELECT '1.0.0-alpha.1'     , '1.0.0-alpha.beta' UNION ALL
  SELECT '1.0.0-alpha.beta'  , '1.0.0-beta' UNION ALL
  SELECT '1.0.0-beta'        , '1.0.0-beta.2' UNION ALL
  SELECT '1.0.0-beta.2'      , '1.0.0-beta.11' UNION ALL
  SELECT '1.0.0-beta.11'     , '1.0.0-rc.1' UNION ALL
  SELECT '1.0.0-rc.1'        , '1.0.0' UNION ALL
  SELECT '1.0.0-alpha-1.1+build1234-a', '1.0.0-alpha-1.1+build1234-a'
)
SELECT v1, v2, compareSemanticVersions(v1, v2) result
FROM test 
ORDER BY normaizedSemanticVersion(v1)   
Run Code Online (Sandbox Code Playgroud)

带输出

Row v1                              v2                              result   
1   1.0.0-alpha-1.1+build1234-a     1.0.0-alpha-1.1+build1234-a     same versions    
2   1.0.0-alpha.1                   1.0.0-alpha.beta                v2 newer than v1     
3   1.0.0-alpha.beta                1.0.0-beta                      v2 newer than v1     
4   1.0.0-beta.2                    1.0.0-beta.11                   v2 newer than v1     
5   1.0.0-beta.11                   1.0.0-rc.1                      v2 newer than v1     
6   1.0.0-beta                      1.0.0-beta.2                    v1 newer than v2     
7   1.0.0-rc.1                      1.0.0                           v2 newer than v1     
8   1.10.0-alpha                    1.0.0-alpha.1                   v1 newer than v2     
9   4.0.0                           4.0.0-beta                      v1 newer than v2    
Run Code Online (Sandbox Code Playgroud)

注意:我在阅读您提供的参考资料后根据我对语义版本控制的理解编写了上面的 UDF。可能还有一些边缘情况需要解决。但绝对应该适用于简单的情况,我希望您能够简单地采用这些 UDF 并根据您的特定需求调整输出,甚至可能优化我最终在这里使用的

仅供参考:在normaizedSemanticVersionUDF 中,我仅使用它zzzzzzzzzz来解决一些边缘情况。我尝试的另一个选择是..zzzzzzzzzz(注意两个额外的点) - 我认为这对于更复杂的情况给出了更好的结果 - 但我真的没有时间完成测试。请尝试

例如,在语义版本控制页面中有一个示例:1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0。 0-beta.11 < 1.0.0-rc.1 < 1.0.0。

要使该顺序与该示例中的顺序相同 -..zzzzzzzzzz应该使用 - 见下文

#standardSQL
CREATE TEMP FUNCTION normaizedSemanticVersion(semanticVersion STRING) 
AS ((
  SELECT STRING_AGG(
      IF(isDigit, REPEAT('0', 8 - LENGTH(chars)) || chars, chars), '' ORDER BY grp 
    ) || '..zzzzzzzzzzzzzz' 
  FROM (
    SELECT grp, isDigit, STRING_AGG(char, '' ORDER BY OFFSET) chars,
    FROM (
      SELECT OFFSET, char, isDigit,
        COUNTIF(NOT isDigit) OVER(ORDER BY OFFSET) AS grp
      FROM UNNEST(SPLIT(semanticVersion, '')) AS char WITH OFFSET, 
      UNNEST([char IN ('1','2','3','4','5','6','7','8','9','0')]) isDigit
    )
    GROUP BY grp, isDigit
)));
CREATE TEMP FUNCTION compareSemanticVersions(
  normSemanticVersion1 STRING, 
  normSemanticVersion2 STRING) 
AS ((
  SELECT
    CASE 
      WHEN v1 < v2 THEN 'v2 newer than v1'
      WHEN v1 > v2 THEN 'v1 newer than v2'
      ELSE 'same versions'
    END
  FROM UNNEST([STRUCT(
    normaizedSemanticVersion(normSemanticVersion1) AS v1, 
    normaizedSemanticVersion(normSemanticVersion2) AS v2
  )])
));
WITH test AS (
  SELECT 1 `order`, '1.0.0-alpha' version UNION ALL
  SELECT 2, '1.0.0-alpha.1' UNION ALL
  SELECT 3, '1.0.0-alpha.beta' UNION ALL
  SELECT 4, '1.0.0-beta' UNION ALL
  SELECT 5, '1.0.0-beta.2' UNION ALL
  SELECT 6, '1.0.0-beta.11' UNION ALL
  SELECT 7, '1.0.0-rc.1' UNION ALL
  SELECT 8, '1.0.0.' 
)
SELECT *
FROM test
ORDER BY normaizedSemanticVersion(version)   
Run Code Online (Sandbox Code Playgroud)

输出符合语义版本控制规范

Row order   version  
1   1   1.0.0-alpha  
2   2   1.0.0-alpha.1    
3   3   1.0.0-alpha.beta     
4   4   1.0.0-beta   
5   5   1.0.0-beta.2     
6   6   1.0.0-beta.11    
7   7   1.0.0-rc.1   
8   8   1.0.0.       
Run Code Online (Sandbox Code Playgroud)