2013-12-10 3 views
1

Я мигрирующие следующий за ней запрос SQL SERVERМожно ли преобразовать SQL Server запрос CTE в Postgres?

;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 Я изменил имена начало/конец столбца выше просто «старт» и «конец». Оказывается, postgres не нравится имя «end», поэтому я пытался его избежать, но поскольку я также использую функцию 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'); 

ответ

2

выглядит вполне разумным. Трудно быть уверенным без схемы, и некоторые примеры данных (http://sqlfiddle.com/ хороши для этого).

end должно быть указано, потому что это ключевое слово для CASE WHEN .. THEN .. END.

Несколько уборочных работ: Используйте row_number() OVER (...) AS row_number не RowNumber = row_number() OVER (...). Отступьте CTE. Ответить «start» тоже, кто знает, когда это может стать ключевым словом в SQL; это уже синтаксис, выделенный высвечиванием SQL Stack Overflow. Я предпочитаю использовать стандарт 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 types и exclusion 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, округляется до часа, если необходимо.

Вот формулировка с использованием триангов. Я был удивлен, не найдя простой и очевидный способ сделать это с индексируемым самосоединением, но я не очень хорошо знаю типы диапазонов. Это обрабатывает случай, когда следующий доступный app't теперь (округляется до ближайшего часа), или когда это после того, как все текущие назначения закончены:

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

Если вы заинтересованы только в следующий раз, вы можете просто нажать на ORDER BY 1 ASC LIMIT 1.

Если вы хотите скорейший 1-часовой блок, просто используйте upper(booked_time) + INTERVAL '1' HOUR, чтобы сгенерировать верхнюю границу, а не использовать нижнюю границу следующего временного интервала.

+0

@ErwinBrandstetter Пропущенный этот псевдоним, спасибо. –

+0

Также приведены те же данные, что и типы диапазонов.Они не кажутся такими же полезными, как я думал для этого, или я недостаточно умен, чтобы понять, как использовать их для этого. http://sqlfiddle.com/#!15/712cb –

+0

wow -totally ударил по полноте вашего ответа! спасибо, что нашли время, чтобы помочь! один вопрос - ваш второй фрагмент с использованием «свинца» - это предлагаемое решение (существенно улучшающееся по оригиналу)? –

Смежные вопросы