2016-01-25 15 views
6

Mam tabelę bazy danych (Oracle 11g) z opinii na temat kwestionariusza, w tym pytania wielokrotnego wyboru i odpowiedzi wielokrotnej. W kolumnie Opcje znajduje się każda wartość, którą użytkownik może wybrać, a kolumna Odpowiedzi zawiera wartości liczbowe wybranych przez siebie.Regex SQL - Zamień na podciąg z innego pola

ID_NO  OPTIONS        ANSWERS 
1001  Apple Pie|Banana-Split|Cream Tea  1|2 
1002  Apple Pie|Banana-Split|Cream Tea  2|3 
1003  Apple Pie|Banana-Split|Cream Tea  1|2|3 

Potrzebuję kwerendę, która dekoduje odpowiedzi, ze tresc odpowiedzi jako jeden ciąg.

ID_NO  ANSWERS  ANSWER_DECODE 
1001  1|2   Apple Pie|Banana-Split 
1002  2|3   Banana-Split|Cream Tea 
1003  1|2|3  Apple Pie|Banana-Split|Cream Tea 

I eksperymentowali z wyrażeń regularnych, aby zastąpić wartości i dostać podciągi, ale nie mogę wypracować sposób, aby poprawnie połączy dwa.

WITH feedback AS (
    SELECT 1001 id_no, 'Apple Pie|Banana-Split|Cream Tea' options, '1|2' answers FROM DUAL UNION 
    SELECT 1002 id_no, 'Apple Pie|Banana-Split|Cream Tea' options, '2|3' answers FROM DUAL UNION 
    SELECT 1003 id_no, 'Apple Pie|Banana-Split|Cream Tea' options, '1|2|3' answers FROM DUAL) 
SELECT 
    id_no, 
    options, 
    REGEXP_SUBSTR(options||'|', '(.)+?\|', 1, 2) second_option, 
    answers, 
    REGEXP_REPLACE(answers, '(\d)+', ' \1 ') answer_numbers, 
    REGEXP_REPLACE(answers, '(\d)+', REGEXP_SUBSTR(options||'|', '(.)+?\|', 1, To_Number('2'))) "???" 
FROM feedback 

Nie chcę ręcznie definiować ani dekodować odpowiedzi w SQL; istnieje wiele ankiet z różnymi pytaniami (i różnymi liczbami opcji), więc mam nadzieję, że istnieje rozwiązanie, które będzie dynamicznie działać dla nich wszystkich.

Próbowałem podzielić opcje i odpowiedzi na osobne wiersze według POZIOMU, i ponownie dołączyć je tam, gdzie kody pasują, ale to działa bardzo wolno z rzeczywistego zestawu danych (pytanie 5 opcji z 600 rzędów odpowiedzi).

WITH feedback AS (
    SELECT 1001 id_no, 'Apple Pie|Banana-Split|Cream Tea' options, '1|2' answers FROM DUAL UNION 
    SELECT 1002 id_no, 'Apple Pie|Banana-Split|Cream Tea' options, '2|3' answers FROM DUAL UNION 
    SELECT 1003 id_no, 'Apple Pie|Banana-Split|Cream Tea' options, '1|2|3' answers FROM DUAL) 
SELECT 
    answer_rows.id_no, 
    ListAgg(option_rows.answer) WITHIN GROUP(ORDER BY option_rows.lvl) 
FROM 
    (SELECT DISTINCT 
    LEVEL lvl, 
    REGEXP_SUBSTR(options||'|', '(.)+?\|', 1, LEVEL) answer 
    FROM 
    (SELECT DISTINCT 
     options, 
     REGEXP_COUNT(options||'|', '(.)+?\|') num_choices 
    FROM 
     feedback) 
    CONNECT BY LEVEL <= num_choices 
) option_rows 
    LEFT OUTER JOIN 
    (SELECT DISTINCT 
    id_no, 
    to_number(REGEXP_SUBSTR(answers, '(\d)+', 1, LEVEL)) answer 
    FROM 
    (SELECT DISTINCT 
     id_no, 
     answers, 
     To_Number(REGEXP_SUBSTR(answers, '(\d)+$')) max_answer 
    FROM 
     feedback) 
    WHERE 
    to_number(REGEXP_SUBSTR(answers, '(\d)+', 1, LEVEL)) IS NOT NULL 
    CONNECT BY LEVEL <= max_answer 
) answer_rows 
    ON option_rows.lvl = answer_rows.answer 
GROUP BY 
    answer_rows.id_no 
ORDER BY 
    answer_rows.id_no 

Jeśli nie jest to rozwiązanie tylko przy użyciu regex, czy jest bardziej efektywny sposób niż poziom podziału wartości? Czy istnieje inne podejście, które mogłoby zadziałać?

+0

Stosowna: http://stackoverflow.com/questions/26407538/split-string-into-rows-oracle-sql – QuestionC

+0

Dlaczego nie funkcja. Powinien być prostszy. –

Odpowiedz

1

Jest powolny, ponieważ jesteś rozszerza każdy rząd zbyt wiele razy; klauzule connect-by, których używasz, sprawdzają się we wszystkich wierszach, więc dostajesz ogromną ilość danych do sortowania - prawdopodobnie jest to spowodowane tym, że trafiłeś tam pod numer DISTINCT.

Można dodać dwa PRIOR klauzul connect-by, po pierwsze więc ID_NO jest zachowana, a drugi, aby uniknąć pętli - każda funkcja niedeterministyczne zrobi tego, wybrałem dbms_random.value ale można użyć sys_guid jeśli wolisz, lub coś innego. Nie potrzebujesz też wielu podzapytań, możesz to zrobić za pomocą dwóch; albo jako współczynniki CTE, który myślę, że jest trochę jaśniej:

WITH feedback AS (
    SELECT 1001 id_no, 'Apple Pie|Banana-Split|Cream Tea' options, '1|2' answers FROM DUAL UNION 
    SELECT 1002 id_no, 'Apple Pie|Banana-Split|Cream Tea' options, '2|3' answers FROM DUAL UNION 
    SELECT 1003 id_no, 'Apple Pie|Banana-Split|Cream Tea' options, '1|2|3' answers FROM DUAL 
), 
option_rows AS (
    SELECT 
    id_no, 
    LEVEL answer, 
    REGEXP_SUBSTR(options, '[^|]+', 1, LEVEL) answer_text 
    FROM feedback 
    CONNECT BY LEVEL <= REGEXP_COUNT(options, '[^|]+') 
    AND id_no = PRIOR id_no 
    AND PRIOR dbms_random.value IS NOT NULL 
), 
answer_rows AS (
    SELECT 
    id_no, 
    REGEXP_SUBSTR(answers, '[^|]+', 1, LEVEL) answer 
    FROM feedback 
    CONNECT BY LEVEL <= REGEXP_COUNT(answers, '[^|]+') 
    AND PRIOR id_no = id_no 
    AND PRIOR dbms_random.value IS NOT NULL 
) 
SELECT 
    option_rows.id_no, 
    LISTAGG(option_rows.answer, '|') WITHIN GROUP (ORDER BY option_rows.answer) AS answers, 
    LISTAGG(option_rows.answer_text, '|') WITHIN GROUP (ORDER BY option_rows.answer) AS answer_decode 
FROM option_rows 
JOIN answer_rows 
ON option_rows.id_no = answer_rows.id_no 
AND option_rows.answer = answer_rows.answer 
GROUP BY option_rows.id_no 
ORDER BY option_rows.id_no; 

Który dostaje:

 ID_NO ANSWERS ANSWER_DECODE       
---------- ---------- ---------------------------------------- 
     1001 1|2  Apple Pie|Banana-Split     
     1002 2|3  Banana-Split|Cream Tea     
     1003 1|2|3  Apple Pie|Banana-Split|Cream Tea 

Ja również zmieniły swój wzorzec regex, więc nie trzeba dołączać lub pozbawić |.

0

Napisałem bliskie rozwiązanie w MySQL (nie mam teraz zainstalowanej Oracle) - ale napisałem, co należy zmienić, aby zapytanie zadziałało w Oracle.

Również najbrzydsza część mojego kodu będzie wyglądała lepiej w Oracle, ponieważ ma znacznie lepszą funkcję INSTR.

Chodzi o wykonanie CROSS JOIN z listą liczb (od 1 do 10, aby obsłużyć do 10 opcji dla każdej ankiety) i podzielenie pola OPCJE na różne wiersze ... (robisz to korzystając zarówno z listy numerów, jak i z funkcji Oracle, zobacz komentarze).

Stamtąd odfiltrować wiersze, które nie zostały wybrane, i zgrupować je ponownie.

-- I've used GROUP_CONCAT in MySQL, but in Oracle you'll have to use WM_CONCAT 
select ID_NO, ANSWERS, group_concat(broken_down_options,'|') `OPTIONS` 
from (
    select your_table.ID_NO, your_table.ANSWERS, 
      -- Luckily, you're using ORACLE so you can use an INSTR function that has the "occurrence" parameter 
      -- INSTR(string, substring, [position, [occurrence]]) 
      -- use the nums.num field as input for the occurrence parameter 
      -- and just put '1' under "position" 
      case when nums.num = 1 
       then substr(your_table.`OPTIONS`, 1, instr(your_table.`OPTIONS`, '|') - 1) 
       when nums.num = 2 
       then substr(substr(your_table.`OPTIONS`, instr(your_table.`OPTIONS`, '|') + 1), 1, instr(substr(your_table.`OPTIONS`, instr(your_table.`OPTIONS`, '|') + 1), '|') - 1) 
       else substr(your_table.`OPTIONS`, length(your_table.`OPTIONS`) - instr(reverse(your_table.`OPTIONS`), '|') + 2) end broken_down_options 
    from (select 1 num union all 
     select 2 num union all 
     select 3 num union all 
     select 4 num union all 
     select 5 num union all 
     select 6 num union all 
     select 7 num union all 
     select 8 num union all 
     select 9 num union all 
     select 10 num 
     ) nums 
     CROSS JOIN 
     (select 1001 ID_NO, 'Apple Pie|Banana-Split|Cream Tea' `OPTIONS`, '1|2' ANSWERS union 
     select 1002 ID_NO, 'Apple Pie|Banana-Split|Cream Tea' `OPTIONS`, '2|3' ANSWERS union 
     select 1003 ID_NO, 'Apple Pie|Banana-Split|Cream Tea' `OPTIONS`, '1|2|3' ANSWERS 
     ) your_table 
    -- for example: 2|3 matches 2 and 3 but not 1 
    where your_table.ANSWERS like concat(concat('%',nums.num),'%') 
) some_query 
group by ID_NO, ANSWERS 
0

Tworzenie zapisanego predure i wykonać poniższe kroki

  • Zadeklaruj tablicę swojej wielkości.
  • Uzyskaj dane z pierwszego rzędu z option. Użyj wyrażeń regularnych lub level, aby wyodrębnić wartości między rurami, a następnie zapisać je w tablicy. Uwaga: będzie to tylko jednorazowe pismo. Więc nie musisz go powtarzać dla każdego rzędu.
  • Teraz w pętli dla każdego wiersza, wybierz answers i użyć wartości tablicy do przypisania wartości answers
1

Sprawdź to kompaktowe rozwiązanie:

with sample_data as 
(
    select 'ala|ma|kota' options, '1|2' answers from dual 
    union all 
    select 'apples|oranges|bacon', '1|2|3' from dual 
    union all 
    select 'a|b|c|d|e|f|h|i','1|3|4|5|8' from dual 
) 
select answers, options, 
regexp_replace(regexp_replace(options,'([^|]+)\|([^|]+)\|([^|]+)','\' || replace(answers,'|','|\')),'[|]+','|') answer_decode 
from sample_data; 

wyjściowa:

ANSWERS OPTIONS    ANSWER_DECODE 
--------- -------------------- --------------------------- 
1|2  ala|ma|kota   ala|ma 
1|2|3  apples|oranges|bacon apples|oranges|bacon 
1|3|4|5|8 a|b|c|d|e|f|h|i  a|c|d|f|h|i 
+0

Co się dzieje, jeśli w ankiecie jest więcej niż 3 możliwe odpowiedzi? – DougieHauser

+0

Stil pracuje z niewielkim dodatkowym czyszczeniem - usuwanie niepotrzebnych separatorów –