【问题标题】:How to extract the table name from a CREATE/UPDATE/INSERT statement in an SQL query?如何从 SQL 查询中的 CREATE/UPDATE/INSERT 语句中提取表名?
【发布时间】:2020-06-10 18:51:40
【问题描述】:

我正在尝试解析存储在表列中的以下 sql 查询创建、插入或更新的表。

我们将表格列称为query。以下是一些示例数据,用于展示数据的外观变化。

with sample_data as (
  select 1 as id, 'CREATE TABLE tbl1 ...' as query union all
  select 2 as id, 'CREATE OR REPLACE TABLE tbl1 ...' as query union all
  select 3 as id, 'DROP TABLE IF EXISTS tbl1; CREATE TABLE tbl1 ...' as query union all
  select 4 as id, 'INSERT /*some comment*/ INTO tbl2 ...' as query union all
  select 5 as id, 'INSERT /*some comment*/ INTO tbl2 ...' as query union all
  select 6 as id, 'UPDATE tbl3 SET col1 = ...' as query union all
  select 7 as id, '/*some garbage comments*/ UPDATE tbl3 SET col1 = ...' as query union all  
  select 8 as id, 'DELETE tbl4 ...' as query
),

以下是查询的格式(我们正在尝试提取 table_name):

#1 some optional statements like drop table 创建some comments or optional statement like OR REPLACEtable_name everything else

#2 some optional statements like drop table 插入some comments 插入some comments table_name

#3 some optional statements like drop table 更新some comments table_name everything else

【问题讨论】:

  • “我是”,“我们是”?到目前为止,您/您尝试了什么?你在哪里失败了?
  • 我的正则表达式经验仅限于 LIKE '%%' 语句。你能帮忙吗?
  • SQL 和它的 LIKE 运算符都不是正则表达式。 Stack Overflow 用于征求建议,而不是委派要完成的工作。你甚至没有解决问题。
  • 那么根据显示的数据,预期的输出是什么?

标签: sql regex parsing google-bigquery data-extraction


【解决方案1】:

正则表达式

要构造一个合适的正则表达式,让我们从以下相对简单/可读的版本开始:

((CREATE( OR REPLACE)?|DROP) TABLE( IF EXISTS)?|UPDATE|DELETE|INSERT INTO) ([^\s\/*]+)

以上所有空格都可以替换为“至少一个空格字符”,即\s+。但是我们还需要允许 cmets。对于看起来像/*anything*/ 的评论,正则表达式看起来像\/\*.*\*\/ (其中评论字符用\ 转义,“anything”是中间的.*。鉴于可能有多个这样的 cmets,可选用空格分隔,我们最终得到 (\s*\/\*.*\*\/\s*?)*\s+。将其插入任何有空间的地方:

((CREATE((\s*\/\*.*\*\/\s*?)*\s+OR(\s*\/\*.*\*\/\s*?)*\s+REPLACE)?|DROP)(\s*\/\*.*\*\/\s*?)*\s+TABLE((\s*\/\*.*\*\/\s*?)*\s+IF(\s*\/\*.*\*\/\s*?)*\s+EXISTS)?|UPDATE|DELETE|INSERT(\s*\/\*.*\*\/\s*?)*\s+INTO)(\s*\/\*.*\*\/\s*?)*\s+([^\s\/*]+)

需要进一步改进:括号中的表达式已用于选择,例如(CHOICE1|CHOICE2)。但是这种语法将它们包括为捕获组。实际上,我们只需要一个捕获组作为表名,因此我们可以通过?: 排除所有其他捕获组,例如(?:CHOICE1|CHOICE2)。这给出了:

(?:(?:CREATE(?:(?:\s*\/\*.*\*\/\s*?)*\s+OR(?:\s*\/\*.*\*\/\s*?)*\s+REPLACE)?|DROP)(?:\s*\/\*.*\*\/\s*?)*\s+TABLE(?:(?:\s*\/\*.*\*\/\s*?)*\s+IF(?:\s*\/\*.*\*\/\s*?)*\s+EXISTS)?|UPDATE|DELETE|INSERT(?:\s*\/\*.*\*\/\s*?)*\s+INTO)(?:\s*\/\*.*\*\/\s*?)*\s+([^\s\/*]+)

在线正则表达式演示

这是一个与您的示例一起使用的演示:Regex101 demo

SQL

REGEXP_EXTRACT 的 Google BigQuery 文档说它将返回与捕获组匹配的子字符串。所以我希望这样的事情能够奏效:

with sample_data as (
  select 1 as id, 'CREATE TABLE tbl1 ...' as query union all
  select 2 as id, 'CREATE OR REPLACE TABLE tbl1 ...' as query union all
  select 3 as id, 'DROP TABLE IF EXISTS tbl1; CREATE TABLE tbl1 ...' as query union all
  select 4 as id, 'INSERT /*some comment*/ INTO tbl2 ...' as query union all
  select 5 as id, 'INSERT /*some comment*/ INTO tbl2 ...' as query union all
  select 6 as id, 'UPDATE tbl3 SET col1 = ...' as query union all
  select 7 as id, '/*some garbage comments*/ UPDATE tbl3 SET col1 = ...' as query union all  
  select 8 as id, 'DELETE tbl4 ...' as query
)

SELECT
  *, REGEXP_EXTRACT(query, r"(?:(?:CREATE(?:(?:\s*\/\*.*\*\/\s*?)*\s+OR(?:\s*\/\*.*\*\/\s*?)*\s+REPLACE)?|DROP)(?:\s*\/\*.*\*\/\s*?)*\s+TABLE(?:(?:\s*\/\*.*\*\/\s*?)*\s+IF(?:\s*\/\*.*\*\/\s*?)*\s+EXISTS)?|UPDATE|DELETE|INSERT(?:\s*\/\*.*\*\/\s*?)*\s+INTO)(?:\s*\/\*.*\*\/\s*?)*\s+([^\s\/*]+)") AS table_name
FROM sample_data;

(以上内容未经测试,如有任何问题,请在 cmets 中告诉我。)

【讨论】:

    【解决方案2】:

    我认为这确实取决于您的数据,但使用这样的方法您可能会发现一些成功:

    with data as (
      select 1 as id, 'CREATE TABLE tbl1 ...' as query union all
      select 2 as id, 'INSERT INTO tbl2 ...' as query union all
      select 3 as id, 'UPDATE tbl3 ...' as query union all
      select 4 as id, 'DELETE tbl4 ...' as query
    ),
    splitted as (
      select id, split(query, ' ') as query_parts from data
    )
    select
      id,
      case 
        when query_parts[safe_offset(0)] in('CREATE', 'INSERT') then query_parts[safe_offset(2)]
        when query_parts[safe_offset(0)] in('UPDATE', 'DELETE') then query_parts[safe_offset(1)]
        else 'Error'
      end as table_name
    from splitted
    

    当然,这取决于您的query 列中的清洁度和语法。此外,如果您的 table_name 符合 project.table.dataset 条件,则需要进一步拆分。

    【讨论】:

    • 谢谢!非常感谢您的回答。这是一个很好的开始。这让我意识到我应该先添加一个示例数据集。当前的解决方案涵盖了一些情况,但我用示例数据(以您的格式)更新了问题以反映一些边缘情况。如果您能提出一个包含这些变化的解决方案,我们将不胜感激。
    猜你喜欢
    • 2022-07-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-13
    • 2016-06-08
    • 2012-02-03
    • 2023-02-25
    • 2015-09-08
    相关资源
    最近更新 更多