是否可以将SQL Server CTE查询转换为Postgres?

时间:2013-12-10 04:59:30

标签: postgresql

我正在迁移以下SQL Server查询

;WITH CTE AS 
(
   SELECT 
       ID, StartAptDate, EndAptDate, 
       RowNumber = ROW_NUMBER() OVER( ORDER BY StartAptDate ASC)
   FROM 
       Appointments 
   WHERE 
       StylistId = 1 
       AND StartAptDate > CAST(CONVERT(CHAR(8), GetDate(), 112) AS DATETIME)
)
SELECT 
    FirstApptAvail = min(a.EndAptDate)
FROM 
    CTE a
INNER JOIN 
    CTE b ON a.RowNumber = b.RowNumber - 1
WHERE 
    datediff(minute, a.EndAptDate, b.StartAptDate) >= 15 
    AND (CAST(CONVERT(CHAR(8), a.StartAptDate, 108) AS DATETIME) BETWEEN '1900-01-01 07:57:57' AND '1900-01-01 18:59:59' 
    AND CAST(CONVERT(CHAR(8), a.EndAptDate, 108) AS DATETIME) BETWEEN '1900-01-01 07:57:57' AND '1900-01-01 18:59:59') 
    AND ((DATEPART(dw, a.StartAptDate) + @@DATEFIRST) % 7) NOT IN (0, 1)

将数据库迁移到postgres之后,我将上面的开始/结束列名称修改为“start”和“end”。事实证明postgres不喜欢“结束”名称所以我一直试图逃避它,但由于我也在使用DATE_PART函数,我需要尊重单引号。这是我到目前为止所做的,但我希望得到一些帮助来完成最后的转换(假设这可以移植到postgres)

WITH RECURSIVE CTE AS (SELECT id, start, "end", RowNumber =               
  ROW_NUMBER() OVER(order by start asc) 
  FROM api_appointment
  WHERE employee_id = 1 AND start > now())
  SELECT FirstApptAvail = min( a."end" )
  FROM CTE a
  INNER JOIN CTE b ON a.RowNumber = b.RowNumber - 1
  WHERE DATE_PART('minute', a.start - b.start) >= 0
  AND a.start >= '1900-01-01 07:57:57'
  AND a.start <= '1900-01-01 18:59:59'
  AND a."end" >= '1900-01-01 07:57:57'
  AND a."end" <= '1900-01-01 18:59:59');

1 个答案:

答案 0 :(得分:2)

看起来相当合理。没有架构和一些示例数据(http://sqlfiddle.com/对此有好处)很难确定。

必须引用

end,因为它是CASE WHEN .. THEN .. END的关键字。

一些整理:使用row_number() OVER (...) AS row_number而不是RowNumber = row_number() OVER (...)。缩进CTE术语。引用“开始”,谁知道什么时候它可能成为SQL中的关键字;毕竟,Stack Overflow的SQL突出显示已经突出了语法。我更喜欢使用标准current_timestamp而不是非标准now(),但效果相同。

您不需要WITH RECURSIVE(并且它不应该工作),因为CTE没有递归术语;没有UNION ALL ... SELECT ... FROM cte

WITH cte AS (
  SELECT 
      id, "start", "end", row_number() OVER (ORDER BY "start" ASC) AS row_number
  FROM api_appointment
  WHERE employee_id = 1 AND "start" > current_timestamp
)
SELECT min( a."end" ) AS FirstApptAvail
FROM cte a
INNER JOIN cte b ON a.row_number = b.row_number - 1
WHERE DATE_PART('minute', a.start - b.start) >= 0
  AND a."start" >= '1900-01-01 07:57:57'
  AND a."start" <= '1900-01-01 18:59:59'
  AND a."end" >= '1900-01-01 07:57:57'
  AND a."end" <= '1900-01-01 18:59:59';

那就是说,我怀疑有一种更简单的方法可以做你想做的事,但我想看一些样本数据。我也认为你会想要调查PostgreSQL的range typesexclusion constraints,它们非常适合这类工作。范围类型是GiST可索引的,用于重叠测试等操作。

哦,就是这样。如果您不能使用范围类型,请使用lead。类似的东西:

SELECT min("end")
FROM (
   SELECT 
     id, "start", "end", 
     lead(id) OVER w AS next_id, 
     lead("start") OVER w AS next_start, 
     lead("end") OVER w AS next_end
   FROM appointment
   WHERE employee_id = 1 AND "start" > current_timestamp
   WINDOW w AS (ORDER BY "start")
) AS appts
WHERE appts."end" <> appts.next_start;

(参见:http://sqlfiddle.com/#!15/77f63/6

在下次预约时,您需要处理下一个可用约会现在的情况。我可能只是生成一个虚拟约会,其中UNION ALL以current_timestamp结束,如有必要,则四舍五入到小时。

这是使用tzranges的公式。我很惊讶没有找到一个简单而明显的方法来使用可索引的自联接来完成此操作,但我不太了解范围类型。这将处理下一个可用app't现在(四舍五入到最接近的小时),或者在所有当前约会结束后的情况:

http://sqlfiddle.com/#!15/712cb/4

如果您只对下一次感兴趣,可以点击ORDER BY 1 ASC LIMIT 1

如果您想要最快的1小时阻止,只需使用upper(booked_time) + INTERVAL '1' HOUR生成上限,而不是使用下一个时间段的下限。