Как получить значение столбца (для определенной строки), когда имя столбца совпадает с переменной, передаваемой динамически?

У меня есть следующая структура таблицы в базе данных Oracle. Я хочу узнать, сколько баллов набрала ABC по математике? Имя субъекта является динамическим. Он может меняться в зависимости от ввода пользователя.

Ученический стол:

Student_Id  Student_Name    Maths   English History Physics
1                ABC         93      89      90      70
2                XYZ         88      98      88      80
3                DEF         79      78      87      90

Есть ли способ сопоставить имя столбца с определенным значением, полученным динамически через пользовательский ввод?


person user12409704    schedule 22.11.2019    source источник
comment
Не уверен, что это действительно нужно решать в запросе. Я предполагаю, что пользователь зайдет на веб-сайт, чтобы получить эти оценки. Затем просто запрограммируйте внешнее программное обеспечение так, чтобы отображались только те результаты, которые он/она хочет видеть.   -  person LukStorms    schedule 22.11.2019
comment
Ваша проблема в базе данных. Было бы лучше, если бы вы изменили свою модель данных на три таблицы: студенты (student_id, имя), предметы (subject_id, имя), student_subject (student_id, subject_id, оценка). В вашем приложении вы предложите предметы, считанные из таблицы предметов, и запрос будет использовать идентификатор выбранного предмета для получения оценки. Очень просто и легко расширяется для большего количества предметов (просто больше записей в таблицах) или, возможно, оценок за год (добавьте столбец года в таблицу student_subject).   -  person Thorsten Kettner    schedule 22.11.2019


Ответы (4)


Вы должны знать, какие столбцы находятся в таблице. Следовательно, вы можете написать запрос, используя выражение case:

select (case when :input = 'Maths' then Maths
             when :input = 'English' then English
             when :input = 'History' then History
             when :input = 'Physics' then Physics
        end)  
from students
were Student_Name = 'ABC';

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

Ученики:

Student_Id    Student_Name
    1            ABC 
    . . .

StudentMarks

Student_Id    Subject    Marks   
    1         Maths        93
    1         English      89
    1         History      90
    1         Physics      70
    . . .

(Обратите внимание, что вам также может понадобиться отдельная справочная таблица для предметов.)

Запрос будет тривиальным с этой структурой данных:

select sm.marks
from studentmarks sm join
     students s
     on sm.student_id = s.student_id
where subject = :input;

Такая структура позволит также легко добавлять новые темы.

person Gordon Linoff    schedule 22.11.2019

Динамический SQL — это то, что вам нужно. Вот пример:

Образец данных:

SQL> select * from student;

        ID NAM      MATHS    ENGLISH
---------- --- ---------- ----------
         1 ABC         93         89
         2 XYZ         88         98
         3 DEF         79         78

Функция:

SQL> CREATE OR REPLACE FUNCTION f_sum (par_subject IN VARCHAR2)
  2     RETURN NUMBER
  3  IS
  4     l_str   VARCHAR2 (200);
  5     retval  NUMBER;
  6  BEGIN
  7     l_str :=
  8           'select sum('
  9        || DBMS_ASSERT.simple_sql_name (par_subject)
 10        || ') from student';
 11
 12     EXECUTE IMMEDIATE l_str INTO retval;
 13
 14     RETURN retval;
 15  END;
 16  /

Function created.

Тестирование:

SQL> SELECT f_sum ('maths') FROM DUAL;

F_SUM('MATHS')
--------------
           260

SQL>
person Littlefoot    schedule 22.11.2019
comment
ОП спросил, хочу ли я узнать, сколько баллов ABC набрала по математике? Это находит общий балл по предмету, а не индивидуальный балл. - person MT0; 22.11.2019
comment
О да. Итак, вы показали, как это сделать, вот как это можно было бы сделать, если бы вопрос был другим. - person Littlefoot; 22.11.2019

Вам не нужен динамический SQL или даже PL/SQL. Вы можете сделать это в SQL-запросе, используя UNPIVOT:

Настройка Oracle:

CREATE TABLE test_data ( Student_Id, Student_Name, Maths, English, History, Physics ) AS
SELECT 1, 'ABC', 93, 89, 90, 70 FROM DUAL UNION ALL
SELECT 2, 'XYZ', 88, 98, 88, 80 FROM DUAL UNION ALL
SELECT 3, 'DEF', 79, 78, 87, 90 FROM DUAL;

Запрос:

SELECT score
FROM   test_data
UNPIVOT ( score FOR subject IN ( Maths, English, History, Physics ) )
WHERE subject      = 'MATHS'
AND   student_name = 'ABC'

Вывод:

| SCORE |
| ----: |
|    93 |

db‹>скрипка здесь

Если вы хотите использовать функцию PL/SQL, вам все равно не нужен динамический SQL, и вы можете просто обернуть приведенный выше запрос или использовать оператор CASE, как показано ниже:

Функция PL/SQL:

CREATE FUNCTION getScore(
  i_student_name IN TEST_DATA.STUDENT_NAME%TYPE,
  i_subject      IN VARCHAR2
) RETURN NUMBER
IS
  p_score NUMBER(3,0);
BEGIN
  SELECT CASE i_subject
         WHEN 'MATHS' THEN Maths
         WHEN 'ENGLISH' THEN English
         WHEN 'HISTORY' THEN History
         WHEN 'PHYSICS' THEN Physics
         END
  INTO p_score
  FROM   test_data
  WHERE  student_name = i_student_name;

  RETURN p_score;
EXCEPTION
  WHEN NO_DATA_FOUND THEN
    RETURN NULL;
END;
/

Затем:

SELECT getScore( 'ABC', 'MATHS' ) FROM DUAL;

Выходы:

| GETSCORE('ABC','MATHS') |
| ----------------------: |
|                      93 |

db‹>скрипка здесь

person MT0    schedule 22.11.2019

Кроме того, есть еще один подход с использованием xmlquery следующим образом:

SELECT      
      to_number(xmlquery('/ROWSET/ROW/C/text()'
        passing xmltype(dbms_xmlgen.getxml(
          'select '|| <subject_name> || ' as c '
          || 'from test_data WHERE student_name = ''' 
          || <student_name>  || ''''))
      returning content)) as marks
    FROM dual;

db‹>демонстрация скрипки

Ваше здоровье!!

person Popeye    schedule 22.11.2019