有条件地使用 set-retuning 函数作为连接子句

Conditionally use set-retuning function as join clause

上下文

我正在实施一个“职位发布”功能,该功能将列出可供用户使用的职位。可以为间隔 start_dateend_date 或数组 service_dates 中的稀疏日期设置作业。可用性检查应使用 jobs 模型中存在的先前描述的日期信息针对 availabilities 模型进行。

它们的结构如下

职位

        Column          |             Type                               
-------------------------+------------------------------
 id                      | bigint                        
 service_dates           | timestamp without time zone[]
 start_date              | timestamp without time zone   
 end_date                | timestamp without time zone  

可用性

        Column          |             Type                               
-------------------------+------------------------------
 id                      | bigint   
 user_id                 | integer                                          
 date                    | date
 spaces_left             | integer   

然后加入时,我想将工作日期与 availabilities table 中存在的匹配日期交叉。问题是工作的日期有两种不同的方式。

对于稀疏日期,如果我执行以下左连接,我可以让它工作:

SELECT j.*
FROM jobs j
LEFT JOIN 
    availabilities a 
        ON a.user_id = 1234
        AND a.date in (
          --this will compose a set of the sparse dates
          SELECT UNNEST(j.service_dates)
        )

对于 start_dateend_date 选项,这将起到作用:

SELECT j.*
FROM jobs j
LEFT JOIN 
    availabilities a 
        ON a.user_id = 1234
        AND a.date in (
          --this will compose a set with the date range between the two dates
          SELECT GENERATE_SERIES(j.start_date::date, j.end_date::date, '1 day'::interval)
        )

我的问题是我需要根据 jobs.service_dates 存在的条件为特定的工作记录申请一个或另一个。本来我以为我可以做到以下几点:

SELECT j.*
FROM jobs j
LEFT JOIN 
    availabilities a 
        ON a.user_id = 1234
        AND a.date in (
          -- use one or the other based on whether or not there's something on j.service_dates
          CASE cardinality(j.service_dates) > 0
          WHEN TRUE THEN
             (SELECT UNNEST(j.services))
          ELSE
            (SELECT GENERATE_SERIES(j.start_date::date, j.end_date::date, '1) day'::interval)
          END
        )

但是我收到以下错误: ERROR: more than one row returned by a subquery used as an expression

如果我尝试从 CASE 语句返回的任何内容中 select 我得到以下结果: ERROR: set-returning functions are not allowed in CASE

我想我已经掌握了如何在可以呈现的场景中逻辑关联 table 的东西。我需要的是一种有条件地应用任一逻辑的方法。


更新 1

正如@Kadet 指出的那样,availabilitites table 中缺少 user_id 所以我添加了它。 他的回答也让我完成了我想要的,我最终没有采用完全相同的解决方案,但我在加入他建议的方式而不是我尝试的方式时应用了 CASE 。他的回答没有任何问题,我只是没有使用它,因为这个查询还有其他特殊性,我没有考虑(为简单起见),这些特殊性会更适合我选择的方式。这是我的做法:

LEFT JOIN
    availabilities a
      ON a.user_id = 1234
      AND case when cardinality(j.service_dates) > 0
        THEN a.date IN ( SELECT unnest(j.service_dates) )
        ELSE a.date IN ( SELECT GENERATE_SERIES(j.start_date::date, j.end_date::date, '1 day'::interval) )
      END

更新 2

值得一提的是@Ely,他的回答在概念上是完全可以接受的table,我相信这是正确的。这对我不起作用,因为我未能在问题的初稿中公开细节,即 service_datesstart_date 以及 end_date 并不相互排斥。前 2 个始终存在,并填充了 service_date 的最小值和最大值。但他的想法可能适用于任何有类似问题但没有特定限制的人。所以,如果你是他的话,请给他投票:)

(A && !B) || (!A && B) 怎么样 - 这基本上就是您要实现的目标。非此即彼。

我会尝试将其翻译成以下内容:

SELECT j.*
FROM jobs j
LEFT JOIN 
    availabilities a 
        ON a.user_id = 1234
        AND
        (
          (
            a.date in (SELECT UNNEST(j.service_dates)
            AND (cardinality(j.service_dates) = 0)
          )
          OR
          (
            a.date in (SELECT GENERATE_SERIES(j.start_date::date, j.end_date::date, '1 day'::interval)
            AND NOT (cardinality(j.service_dates) = 0) 
          )
        )

可能存在语法错误。我不是很精通,也没有从你写的东西中得到启发,但我希望你能理解。

此 table 结构无效或不完整(可用性中没有 user_id 字段 table)但只是为了展示一个想法


SELECT j.*
FROM jobs j
LEFT JOIN 
    availabilities a 
        ON a.user_id = 1234
WHERE 
   case when cardinality(j.service_dates) > 0
     then a.date = any(j.service_dates)
     else a.date between start_date and end_date
   end

或者,如果需要生成系列

SELECT j.*
FROM jobs j
LEFT JOIN 
    availabilities a 
        ON a.user_id = 1234
WHERE 
   case when cardinality(j.service_dates) > 0
     then a.date = any(j.service_dates)
     else a.date = any ( select GENERATE_SERIES(j.start_date::date, j.end_date::date, '1 day'::interval))
   end