Array_agg для 2 столбцов с выводом, не распознанным как список - PullRequest
1 голос
/ 25 октября 2019

Я использую array_agg в одном из моих запросов Postgresql в Python с использованием psycopg2.

Проблема, которую я нахожу, заключается в циклическом прохождении результирующих строк. Сгенерированный столбец из запроса не понимает, что один столбец является списком списков. Вместо этого он думает, что это список стр.

Вот пример базы данных:

+---------+-------------+-----------------+
| student | grade_value |  grade_comment  |
+---------+-------------+-----------------+
| Paul    | 1           | Very good       |
| John    | 1           | Very good       |
| John    | 2           | Average         |
| Mark    | 1           | Very good       |
| Mark    | 3           | Could do better |
| Mark    | 1           | Very good       |
+---------+-------------+-----------------+

Запрос, который я делаю:

    connection = psycopg2.connect(ls.CONNECTION_STRING)

    cursor = connection.cursor(cursor_factory=RealDictCursor)

    cursor.execute(
        "SELECT student, array_agg('(' || grade_value || ',' || grade_comment || ')') as grades"
        "FROM random_table"
        "GROUP BY student"
    )

    students_grades = cursor.fetchall()
    # This returns something like: RealDictRow([('student', John), ('grades', ['(1,Very good)', '(2,Average)'])]), RealDictRow([('student', Paul), ('grades', ['(1,Very good)'])])

    for student in students_grades:
       for grade in student['grades']:
           print(grade)
           print(type(grade))

Значенияof print (grade) в конце имеют такой формат: (1, Very good) Но запрос говорит, что тип является строкой. В результате я не могу получить доступ к комментарию оценки, просто набрав оценку [1]. Он считает, что оценка - это строка.

Не могли бы вы знать, как это исправить?

Ответы [ 3 ]

1 голос
/ 26 октября 2019

Вам не нужно концентрировать строку в array_agg, просто передайте столбцы в массив.
Psycopg2 будет приводить правильные типы между postgres и python, как вы можете видеть в выводе students_grade['grades'] выбирается в виде списка:

cursor.execute("""
select
    student,
    array_agg(array[grade_value, grade_comment]) as grades
from random_table
group by student""")

students_grades = cursor.fetchall()

for students_grade in students_grades:
    print(students_grade['student'])

    for grade in students_grade['grades']:
        print("%s %s" % (type(grade), grade))

Вывод:

Tom
<class 'list'> ['2', 'Good']
<class 'list'> ['3', 'Very good']
John
<class 'list'> ['2', 'Very good']

Редактировать :

В случае, если вам нужно агрегировать различные типы, вы можете агрегировать в JSONобъект:

cursor.execute("""
select
    abc as student,
    json_agg(json_build_object('grade', grade_value, 'comment', array[grade_comment])) as grades
from foo
group by student""")

Вывод:

Tom
<class 'dict'> {'grade': 2, 'comment': ['Good']}
<class 'dict'> {'grade': 3, 'comment': ['Very good']}
John
<class 'dict'> {'grade': 2, 'comment': ['Very good']}
0 голосов
/ 25 октября 2019

psycopg2 не может автоматически конвертировать, если не знает тип. В вашем случае PostgreSQL может путем возврата и массив неизвестного типа. Попробуйте следующее, чтобы исправить это дело. Обратите внимание на ::text[] в конце строки

cursor.execute(
    "SELECT student, array_agg('(' || grade_value || ',' || grade_comment || ')')::text[] as grades"
    "FROM random_table"
    "GROUP BY student"
)

Если это не так, вам может понадобиться зарегистрировать тип массива для пользовательской обработки.

0 голосов
/ 25 октября 2019

Разделить строку, чтобы она стала списком. Примените предварительную обработку по мере необходимости.

for grade in student['grades']:
    # Let's say grade = "(A,Very good)"
    g = grade.replace('(', '').replace(')','') # g --> "A,Very good"
    l = g.split(',') # l --> ["A", "Very good"]
    print(l[1]) # --> "Very good"

Протестировано в IPython:

In [1]: grade = "(A,Very good)"

In [2]: g = grade.replace('(', '').replace(')', '')

In [3]: l = g.split(',')

In [4]: print(l[0])
A

In [5]: print(l[1])
Very good

РЕДАКТИРОВАТЬ:

In [4]: grade = "(A,Very good, but needs some improvement.)"

In [5]: g = grade.replace('(', '').replace(')', '')

In [6]: l = g.split(',', 1)

In [7]: print(l[0])
A

In [8]: print(l[1])
Very good, but needs some improvement.
...