可以在 SQL 的 UDF 中使用列名吗?

Posted

技术标签:

【中文标题】可以在 SQL 的 UDF 中使用列名吗?【英文标题】:Possible to use a column name in a UDF in SQL? 【发布时间】:2019-09-09 12:03:17 【问题描述】:

我有一个查询,其中一系列步骤在不同的列上不断重复,例如:

SELECT DISTINCT
       MAX (
          CASE
             WHEN table_2."GRP1_MINIMUM_DATE" <= cohort."ANCHOR_DATE" THEN 1
             ELSE 0
          END)
       OVER (PARTITION BY cohort."USER_ID")
          AS "GRP1_MINIMUM_DATE",
       MAX (
          CASE
             WHEN table_2."GRP2_MINIMUM_DATE" <= cohort."ANCHOR_DATE" THEN 1
             ELSE 0
          END)
       OVER (PARTITION BY cohort."USER_ID")
          AS "GRP2_MINIMUM_DATE"
  FROM INPUT_COHORT cohort
       LEFT JOIN INVOLVE_EVER table_2 ON cohort."USER_ID" = table_2."USER_ID"

我正在考虑编写一个函数来完成此操作,因为这样做可以节省我的查询空间。我一直在阅读一些关于 SQL 中的 UDF,但还不明白是否可以将列名作为参数传递(即只需将“GRP1_MINIMUM_DATE”切换为“GRP2_MINIMUM_DATE”等)。我想要的是一个看起来像这样的查询

SELECT DISTINCT
FUNCTION(table_2."GRP1_MINIMUM_DATE") AS "GRP1_MINIMUM_DATE",
FUNCTION(table_2."GRP2_MINIMUM_DATE") AS "GRP2_MINIMUM_DATE",
FUNCTION(table_2."GRP3_MINIMUM_DATE") AS "GRP3_MINIMUM_DATE",
FUNCTION(table_2."GRP4_MINIMUM_DATE") AS "GRP4_MINIMUM_DATE" 
FROM INPUT_COHORT cohort
       LEFT JOIN INVOLVE_EVER table_2 ON cohort."USER_ID" = table_2."USER_ID"

谁能告诉我这是否可行/给我指出一些可能对我有帮助的资源?

谢谢!

【问题讨论】:

没有这种直接的方法,但是可以使用 PL/SQL 的动态查询概念来实现。 【参考方案1】:

没有像@Tejash 所说的那样直接,但是看起来您的数据库模型并不理想 - 最好有一个以 USER_IDGRP_ID 为键的表,然后是 MINIMUM_DATE作为单独的字段。

在不改变表结构的情况下,可以使用UNPIVOT查询来模仿这种设计:

WITH INVOLVE_EVER(USER_ID, GRP1_MINIMUM_DATE, GRP2_MINIMUM_DATE, GRP3_MINIMUM_DATE, GRP4_MINIMUM_DATE)
  AS (SELECT 1, SYSDATE, SYSDATE, SYSDATE, SYSDATE FROM dual UNION ALL 
      SELECT 2, SYSDATE-1, SYSDATE-2, SYSDATE-3, SYSDATE-4 FROM dual)
SELECT * 
  FROM INVOLVE_EVER
  unpivot ( minimum_date FOR grp_id IN ( GRP1_MINIMUM_DATE AS 1, GRP2_MINIMUM_DATE AS 2, GRP3_MINIMUM_DATE AS 3, GRP4_MINIMUM_DATE AS 4))

结果:

| USER_ID | GRP_ID | MINIMUM_DATE |
|---------|--------|--------------|
|    1    |    1   | 09/09/19     |
|    1    |    2   | 09/09/19     |
|    1    |    3   | 09/09/19     |
|    1    |    4   | 09/09/19     |
|    2    |    1   | 09/08/19     |
|    2    |    2   | 09/07/19     |
|    2    |    3   | 09/06/19     |
|    2    |    4   | 09/05/19     |

有了这个,您可以编写查询而无需进一步重复代码,如果您需要使用PIVOT-syntax 来获取每个USER_ID 的一行。

最终的查询可能如下所示:

WITH INVOLVE_EVER(USER_ID, GRP1_MINIMUM_DATE, GRP2_MINIMUM_DATE, GRP3_MINIMUM_DATE, GRP4_MINIMUM_DATE)
  AS (SELECT 1, SYSDATE, SYSDATE, SYSDATE, SYSDATE FROM dual UNION ALL 
      SELECT 2, SYSDATE-1, SYSDATE-2, SYSDATE-3, SYSDATE-4 FROM dual)
   , INPUT_COHORT(USER_ID, ANCHOR_DATE) 
  AS (SELECT 1, SYSDATE-1 FROM dual UNION ALL 
      SELECT 2, SYSDATE-2 FROM dual UNION ALL 
      SELECT 3, SYSDATE-3 FROM dual)
-- Above is sampledata query starts from here:
   , unpiv AS (SELECT * 
                 FROM INVOLVE_EVER
               unpivot ( minimum_date FOR grp_id IN ( GRP1_MINIMUM_DATE AS 1, GRP2_MINIMUM_DATE AS 2, GRP3_MINIMUM_DATE AS 3, GRP4_MINIMUM_DATE AS 4)))
SELECT qcsj_c000000001000000 user_id, GRP1_MINIMUM_DATE, GRP2_MINIMUM_DATE, GRP3_MINIMUM_DATE, GRP4_MINIMUM_DATE
  FROM INPUT_COHORT cohort
  LEFT JOIN unpiv table_2
    ON cohort.USER_ID = table_2.USER_ID
pivot (MAX(CASE WHEN minimum_date <= cohort."ANCHOR_DATE" THEN 1 ELSE 0 END) AS MINIMUM_DATE
 FOR grp_id IN (1 AS GRP1,2 AS GRP2,3 AS GRP3,4 AS GRP4))

结果:

| USER_ID | GRP1_MINIMUM_DATE | GRP2_MINIMUM_DATE | GRP3_MINIMUM_DATE | GRP4_MINIMUM_DATE |
|---------|-------------------|-------------------|-------------------|-------------------|
| 3       |                   |                   |                   |                   |
| 1       | 0                 | 0                 | 0                 | 0                 |
| 2       | 0                 | 1                 | 1                 | 1                 |

这样您只需编写一次计算逻辑(参见以pivot 开头的行)。

【讨论】:

以上是关于可以在 SQL 的 UDF 中使用列名吗?的主要内容,如果未能解决你的问题,请参考以下文章

我们可以在 Spark 中编写配置单元查询吗?UDF

使用列名数组中的 UDF 将列合并到单个映射中

UDF 的性能改进 - 在 pyspark 中获取每行最小值的列名

pyspark中列名的字符串操作

如何遍历表的列名并将值传递给 MSSQL while 循环中的 UDF

带参数的 getItem 是列名