2016-01-25 16 views
6

Ich habe eine Datenbanktabelle (Oracle 11g) von Fragebogen-Feedback, einschließlich Multiple-Choice-, Multiple-Antwort-Fragen. Die Spalte Optionen enthält alle Werte, die der Benutzer auswählen kann, und die Spalte Antworten enthält die numerischen Werte ihrer Auswahl.SQL Regex - Ersetzen durch Teilstring aus einem anderen Feld

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 

Ich brauche eine Abfrage, die die Antworten, mit den Textversionen der Antworten als einzelne Zeichenfolge dekodiert.

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 

Ich habe mit regulären Ausdrücken experimentierte Werte zu ersetzen und Teil bekommen, aber ich kann nicht einen Weg erarbeiten, um richtig die beiden zu verschmelzen.

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 

Ich möchte die Antworten in SQL nicht manuell definieren oder dekodieren müssen; Es gibt viele Umfragen mit verschiedenen Fragen (und einer unterschiedlichen Anzahl von Optionen), also hoffe ich, dass es eine Lösung gibt, die für alle dynamisch funktioniert.

Ich habe versucht, die Optionen und Antworten in separate Zeilen nach LEVEL aufzuteilen und sie wieder zu verbinden, wo die Codes übereinstimmen, aber das läuft extrem langsam mit dem tatsächlichen Dataset (eine 5-Option-Frage mit 600 Zeilen von Antworten)).

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 

Wenn es keine Lösung nur mit Regex ist, ist es eine effizientere Art und Weise als LEVEL die Werte zu teilen? Oder gibt es einen anderen Ansatz, der funktionieren würde?

+0

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

+0

Warum keine Funktion? Sollte einfacher sein. –

Antwort

1

Es ist langsam zuweisen, weil Sie jede Zeile zu oft sind erweitert; Die connect-by-Klauseln, die Sie verwenden, schauen über alle Zeilen hinweg, so dass Sie mit einer riesigen Menge an Daten zum Sortieren enden - was vermutlich der Grund dafür ist, dass Sie am Ende mit DISTINCT da drin waren.

Sie können zwei PRIOR Klauseln fügen dem connect-by zunächst so der ID_NO erhalten ist, und eine zweite um eine Schleife zu vermeiden - jede nicht-deterministische Funktion dafür tun wird, habe ich gepflückt dbms_random.value aber Sie verwenden können, sys_guid wenn Sie bevorzugen, oder etwas anderes. Sie brauchen auch nicht viele Unterabfragen, Sie können es mit zwei tun; oder als CTEs denen ich denke, es ist etwas klarer:

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; 

Welche bekommt:

 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 

Ich habe auch Ihre RegexMuster geändert, so dass Sie müssen nicht anhängen oder die | abzustreifen.

0

Ich habe eine enge Lösung in MySQL geschrieben (Oracle ist gerade nicht installiert) - aber ich habe geschrieben, was geändert werden muss, damit die Abfrage in Oracle funktioniert.

Auch der hässlichste Teil meines Codes wird in Oracle viel besser aussehen, da es eine viel bessere INSTR-Funktion hat.

Die Idee ist, eine CROSS JOIN mit einer Liste von Zahlen (1 bis 10, um bis zu 10 Optionen pro Umfrage zu unterstützen), und zerlegen Sie das Feld OPTIONEN in verschiedene Zeilen ... (Sie tun dies Verwenden Sie sowohl die Liste der Zahlen als auch die INSTR-Funktion von Oracle (siehe Kommentare).

Von dort aus filtern Sie die Zeilen aus, die nicht ausgewählt wurden, und gruppieren Sie alles wieder zusammen.

-- 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

Erstellen Sie eine gespeicherte predure und unten tun

  • eine Reihe von Ihrer Größe deklarieren Schritte.
  • Get option Daten aus der ersten Zeile. Verwenden Sie eine Regex oder level, um Werte zwischen Pipes zu extrahieren und sie dann in einem Array zu speichern. Hinweis: Dies ist nur ein einmaliger Vorgang. Sie müssen es also nicht für jede Zeile wiederholen.
  • nun in einer Schleife für jede Zeile, wählen Sie answers und Array-Werte verwenden, um die Werte von answers
1

Sehen Sie sich diese kompakte Lösung aus:

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; 

Ausgang:

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

Was passiert, wenn die Umfrage mehr als 3 mögliche Antworten enthält? – DougieHauser

+0

Stil arbeitet mit wenig zusätzlicher Reinigung - Entfernen von unnötigen Separatoren –

Verwandte Themen