2017-01-16 4 views
0

Здесь я нашел несколько подобных вопросов, но ни один из них, похоже, не соответствовал моей ситуации.Oracle SQL - при сопоставлении, обновлении и вставке

В таблице я работаю на что-то вроде этого, это таблица, которая записывает студент выступление на свои курсы:

| STUDENT_ID | COURSE_ID | ENROLLMENT_TYPE | MARK | STATUS | VERSION | 
|   |   |     |  |   |   | 
| 1234  | 5678  | Mandatory  | 70 | ACTIVE | 2  | 
| 1234  | 5678  | Optional  | 70 | HISTORY | 1  | 
| 1234  | 5678  | Optional  | null | HISTORY | 0  | 
| 9876  | 4597  | Institutional | 99 | ACTIVE | 1  | 
| 9876  | 4597  | Institutional | null | HISTORY | 0  | 

мне нужно, чтобы объединить это с другой таблицей, которая отслеживает регистрации студенты на основе своей группы, так что я могу вставить или обновить строки по мере необходимости:

| GROUP_ID | STUDENT_ID | COURSE_ID | ENROLLMENT_TYPE | 
| 4976555 | 1234  | 5678  | Mandatory  | 
| 6399875 | 1234  | 9034  | Optional  | 
| 6399875 | 9876  | 4597  | Institutional | 

Короче говоря, мне нужно проверить, если тип регистрации такой же или изменился, так как некоторые студенты получают зачислены в курсы, основанные на группе, в которой они участвуют, но группы могут меняться в одночасье.

Все отлично до сих пор, но если строки совпадают, мне также нужно скопировать строку, которую я собираюсь обновить, и установить ее как «ИСТОРИЯ», чтобы мы могли вести запись обо всех обновлениях, которые были сделанный в определенном ряду.

На данный момент у меня есть типичные

MERGE INTO performance USING group_enrollments 
ON performance.STUDENT_ID = group_enrollments.STUDENT_ID 
AND performance.COURSE_ID = group_enrollments.COURSE_ID 
WHEN MATCHED THEN UPDATE ...... 
WHEN NOT MATCHED THEN INSERT ...... 

людей, которые работали в этой части коды, прежде чем я думал, что это будет отличная идея просто скопировать все строки, как «История» из них до делая это слияние, но это дает нам проблемы, поскольку эта процедура работает каждую ночь и каждый раз записывает более 150 000 строк. Любые идеи?

16/01 12,34: обновленный, более подробную информацию о таблицах и их отношение

+0

Если бы я был вами, я бы написал запрос, который дублировал строки, которые вы собираетесь обновлять, чтобы был один с активным статусом и один с статусом истории - таким образом, вы можете использовать его в своем merge statement, чтобы вставить историческую строку и, при необходимости, обновить активную строку. Если вам нужна помощь в этом, обновите свой вопрос, чтобы включить данные из обеих таблиц вместе с ожидаемым результатом. Таким образом, мы можем проверить все для себя. – Boneist

+0

Обновлено для большей ясности, надеюсь. Вещь, я не буду знать, какие строки я должен обновить, пока не объединю таблицы. Вы имеете в виду, что я должен объединить их, чтобы увидеть, что дублировать и объединить их снова, чтобы обновить активные строки? –

+0

Это помогает - спасибо! Я добавил свой ответ, который показывает, что я первый запрос, чтобы найти строки, которые нуждаются в обновлении, дублировать необходимые строки, а затем использовать этот запрос в операторе слияния. – Boneist

ответ

1

Итак, прежде всего, я хотел бы написать запрос, который произвел строки должны быть обновлены и/или вставки:

WITH performance AS (SELECT 1234 student_id, 5678 course_id, 'Mandatory' enrollment_type, 70 mark, 'ACTIVE' status, 2 VERSION FROM dual UNION ALL 
         SELECT 1234 student_id, 5678 course_id, 'Optional' enrollment_type, 70 mark, 'HISTORY' status, 1 VERSION FROM dual UNION ALL 
         SELECT 1234 student_id, 5678 course_id, 'Optional' enrollment_type, NULL mark, 'HISTORY' status, 0 VERSION FROM dual UNION ALL 
         SELECT 9876 student_id, 4597 course_id, 'Institutional' enrollment_type, 99 mark, 'ACTIVE' status, 1 VERSION FROM dual UNION ALL 
         SELECT 9876 student_id, 4597 course_id, 'Institutional' enrollment_type, NULL mark, 'HISTORY' status, 0 VERSION FROM dual), 
    group_enrollments AS (SELECT 4976555 group_id, 1234 student_id, 5678 course_id, 'Mandatory2' enrollment_type FROM dual UNION ALL 
         SELECT 6399875 group_id, 1234 student_id, 9034 course_id, 'Optional' enrollment_type FROM dual UNION ALL 
         SELECT 6399875 group_id, 9876 student_id, 4597 course_id, 'Institutional' enrollment_type FROM dual) 
-- end of mimicking your tables with data in them 
SELECT res.student_id, 
     res.course_id, 
     CASE WHEN dummy.id = 1 THEN res.new_enrollment_type 
      WHEN dummy.id = 2 THEN res.old_enrollment_type 
     END enrollment_type, 
     res.mark, 
     CASE WHEN dummy.id = 1 THEN 'ACTIVE' 
      WHEN dummy.id = 2 THEN 'HISTORY' 
     END status, 
     CASE WHEN dummy.id = 1 THEN res.new_version 
      WHEN dummy.id = 2 THEN res.old_version 
     END VERSION 
FROM (SELECT ge.student_id, 
       ge.course_id, 
       ge.enrollment_type new_enrollment_type, 
       p.enrollment_type old_enrollment_type, 
       p.mark, 
       p.status, 
       p.version old_version, 
       nvl(p.version + 1, 0) new_VERSION 
       -- n.b. this may produce duplicates or unique constraint errors in a concurrent environment 
     FROM group_enrollments ge 
       LEFT OUTER JOIN PERFORMANCE p ON ge.student_id = p.student_id 
               AND ge.course_id = p.course_id 
     WHERE (p.status = 'ACTIVE' OR p.status IS NULL) 
     AND (p.enrollment_type != ge.enrollment_type OR p.enrollment_type IS NULL)) res 
     INNER JOIN (SELECT 1 ID FROM dual UNION ALL 
        SELECT 2 ID FROM dual) dummy ON dummy.id = 1 
                OR (dummy.id = 2 
                 AND res.status = 'ACTIVE'); 

STUDENT_ID COURSE_ID ENROLLMENT_TYPE  MARK STATUS  VERSION 
---------- ---------- --------------- ---------- ------- ---------- 
     1234  5678 Mandatory2    70 ACTIVE   3 
     1234  9034 Optional     ACTIVE   0 
     1234  5678 Mandatory    70 HISTORY   2 

Этот запрос в первую очередь находит любые новые строки (т.е. строки в таблице group_enrollment, которые не имеют строки в таблице производительности) или имеют другой тип регистрации. Это строки, которые нужно вставлять или обновлять.

Как только мы узнаем об этом, мы сможем присоединиться к таблице фиктивных таблиц с двумя строками, чтобы мы всегда присоединялись к первой фиктивной строке независимо от того, нужно ли нам вставлять или обновлять, но мы будем присоединяться только к вторая фиктивная строка, если нам нужно обновить. Это означает, что у нас будет только одна строка для вставки, но две строки для обновления.

Тогда это легкий вопрос вывода правильных значений на основе dummy.id (новые значения для первых фиктивных строк, старые значения для второго фиктивной строки.

После того, как мы сделали это, мы знаем, какие данные должны быть объединены в таблицу производительности, так что теперь оператор слияния будет выглядеть примерно так:

merge into performance tgt 
    using (SELECT res.student_id, 
       res.course_id, 
       CASE WHEN dummy.id = 1 THEN res.new_enrollment_type 
        WHEN dummy.id = 2 THEN res.old_enrollment_type 
       END enrollment_type, 
       res.mark, 
       CASE WHEN dummy.id = 1 THEN 'ACTIVE' 
        WHEN dummy.id = 2 THEN 'HISTORY' 
       END status, 
       CASE WHEN dummy.id = 1 THEN res.new_version 
        WHEN dummy.id = 2 THEN res.old_version 
       END VERSION 
     FROM (SELECT ge.student_id, 
         ge.course_id, 
         ge.enrollment_type new_enrollment_type, 
         p.enrollment_type old_enrollment_type, 
         p.mark, 
         p.status, 
         p.version old_version, 
         nvl(p.version + 1, 0) new_VERSION 
          -- n.b. this may produce duplicates or unique constraint errors in a concurrent environment 
       FROM group_enrollments ge 
         LEFT OUTER JOIN PERFORMANCE p ON ge.student_id = p.student_id 
                 AND ge.course_id = p.course_id 
       WHERE (p.status = 'ACTIVE' OR p.status IS NULL) 
       AND (p.enrollment_type != ge.enrollment_type OR p.enrollment_type IS NULL)) res 
       INNER JOIN (SELECT 1 ID FROM dual UNION ALL 
          SELECT 2 ID FROM dual) dummy ON dummy.id = 1 
                  OR (dummy.id = 2 
                   AND res.status = 'ACTIVE')) src 
    ON (tgt.student_id = src.student_id AND tgt.course_id = src.course_id AND tgt.status = src.status) 
WHEN MATCHED THEN 
    UPDATE SET tgt.enrollment_type = src.enrollment_type, 
      tgt.version = src.version 
WHEN NOT MATCHED THEN 
    INSERT (tgt.student_id, tgt.course_id, tgt.enrollment_type, tgt.mark, tgt.status, tgt.version) 
    VALUES (src.student_id, src.course_id, src.enrollment_type, src.mark, src.status, src.version); 

в целях разъяснения, здесь очень простой пример условного дублирования строк (мы могли бы также назовите это частичным перекрестным соединением, поскольку все строки в одной таблице, соединены по меньшей мере, один ряд в другой):

WITH sample_data AS (SELECT 100 ID, NULL status FROM dual UNION ALL -- expect only one row 
        SELECT 101 ID, 'A' status FROM dual UNION ALL -- expect two rows 
        SELECT 102 ID, 'B' status FROM dual -- expect only one row 
        ) 
SELECT dummy.id dummy_row_id, 
     sd.id, 
     sd.status 
FROM sample_data sd 
     INNER JOIN (SELECT 1 ID FROM dual UNION ALL 
        SELECT 2 ID FROM dual) dummy ON dummy.id = 1 
                OR (dummy.id = 2 
                 AND sd.status = 'A') 
ORDER BY sd.id, dummy.id; 

DUMMY_ROW_ID   ID STATUS 
------------ ---------- ------ 
      1  100 
      1  101 A 
      2  101 A 
      1  102 B 

Вы можете видеть, что для ид = 101 строки из «таблицы» sample_data, у нас есть две строки, а две другие идентификаторы только имеют по одной строке.

Надеюсь, что это прояснит вам ситуацию?

+0

Вы действительно заработали это преимущество. –

+0

Я все еще новичок в SQL, поэтому не думаю, что у меня есть ваша точка здесь. Как это создает новую строку, когда она находит ту, которая соответствует? –

+0

Вы видите фиктивный подзапрос, что я INNER JOINing в подзапросе res? Это набор результатов из двух строк. Условие соединения 'dummy.id = 1' гарантирует, что мы всегда будем присоединяться к первой строке из фиктивного подзапроса к каждой отдельной строке в подзапросе res. Условие 'или (dummy.id = 2 и res.status = 'ACTIVE')' означает, что мы присоединяем только вторую фиктивную строку, если res.status = 'ACTIVE'. Столбец res.status заполняется только, если у нас уже есть строка для этого student_id и course_id в таблице производительности, поэтому мы знаем, что для этого нужно иметь две строки: обновленную строку и историческую строку. Имеют смысл? – Boneist

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