具有动态列和联接计数的PostgreSQL查询

具有动态列和联接计数的PostgreSQL查询,postgresql,pivot,crosstab,Postgresql,Pivot,Crosstab,我可能想得太多了,因为我真的不知道从哪里开始。。。但这里有: 我有以下几点 桌子 学生 评估 学生评估 预期产量 “尝试”的第一个查询 “已通过”的第二个查询 让我大吃一惊的部分不是为每个评估进行联接,甚至不是手动定义列,而是为存在的评估“生成”每个列 我觉得这很简单,但我现在实在是太累了,无法弄清楚:)提前感谢您的帮助,下面是一组数据作为示例SELECT*FROM crosstab( '选择studentid、assessmentname、count(studentassessmentid)作

我可能想得太多了,因为我真的不知道从哪里开始。。。但这里有:

我有以下几点

桌子 学生

评估

学生评估

预期产量 “尝试”的第一个查询 “已通过”的第二个查询 让我大吃一惊的部分不是为每个评估进行联接,甚至不是手动定义列,而是为存在的评估“生成”每个列

我觉得这很简单,但我现在实在是太累了,无法弄清楚:)提前感谢您的帮助,下面是一组数据作为示例

SELECT*FROM crosstab( '选择studentid、assessmentname、count(studentassessmentid)作为ct(studentid、Assessment1、assessment2、Assessent3、assessment4);

简单查询“尝试”

在crosstab()函数中,还需要明确定义列名称,如“Assessment 1”、“Assessment 2”等

或者编写自定义函数来创建动态查询,并使用execute语句执行

DROP FUNCTION get_Attempts() ;

CREATE OR REPLACE FUNCTION get_Attempts() RETURNS text AS
$BODY$
DECLARE
        r1 record;
        str_query text := '';
 BEGIN
    str_query :='select student_id,';
    FOR r1 IN SELECT "_id" , "name" FROM Assessments
    LOOP
      str_query:= str_query || 
                  'sum(case when assessment_id=' || r1."_id" || ' then 1 else 0 end) as  "' || r1.name ||'",' ;
    END LOOP;
    str_query:=trim( trailing  ',' from str_query); -- remove last semicolon
    str_query:= str_query || ' from assessments_students group by student_id order by student_id';
    return str_query;
END
$BODY$
LANGUAGE 'plpgsql' ;

SELECT * FROM get_Attempts();

“已通过”的第二个查询

它的功能看起来像

DROP FUNCTION get_passed() ;

CREATE OR REPLACE FUNCTION get_passed() RETURNS text AS
$BODY$
DECLARE
        r1 record;
        str_query text := '';
 BEGIN
    str_query :='select student_id,';
    FOR r1 IN SELECT "_id" , "name" FROM Assessments
    LOOP
      str_query:= str_query || 
                  'max(case when assessment_id=' || r1."_id" || ' and passed=''t'' then ''t'' else ''f'' end) as  "' || r1.name ||'",' ;
    END LOOP;
    str_query:=trim( trailing  ',' from str_query); -- remove last semicolon
    str_query:= str_query || ' from assessments_students group by student_id order by student_id';

    return str_query;
END
$BODY$
LANGUAGE 'plpgsql' ;

SELECT * FROM get_passed();

我意识到它不再工作了。我正在使用PostgreSQL 12。 所以在这里,您不能返回非预定义的表类型(我指的是变量列),也不能从变量字符中选择

使用匿名代码块、交叉表和临时表的示例。 光标是多余的,我正在解决一个任务,这意味着使用它们

CREATE EXTENSION IF NOT EXISTS tablefunc;

DO
$$
    DECLARE
        movie_probe CURSOR FOR
            SELECT m.name movie_name, count(c.id) cinema_count
            FROM movies m
                     JOIN sessions s ON m.id = s.movie_id
                     JOIN cinema_halls ch ON s.cinema_hall_id = ch.id
                     JOIN cinemas c ON ch.cinema_id = c.id
            GROUP BY m.name
            HAVING count(c.name) > 1
            ORDER BY count(c.name) DESC;
        movie_rec          RECORD;
        movie_columns      TEXT DEFAULT '';

        cinemas_probe CURSOR (cond_movie_name TEXT) FOR
            SELECT m.name movie_name, c.name cinema_name
            FROM movies m
                     JOIN sessions s ON m.id = s.movie_id
                     JOIN cinema_halls ch ON s.cinema_hall_id = ch.id
                     JOIN cinemas c ON ch.cinema_id = c.id
            WHERE cond_movie_name = m.name
            ORDER BY c.name;

        cinema_rec         RECORD;
        cinema_row_counter INT DEFAULT 0;
    BEGIN
        DROP TABLE IF EXISTS cinema_multiples_aev;
        CREATE TEMP TABLE cinema_multiples_aev (
            row_id      INT,
            movie_name  TEXT,
            cinema_name TEXT
        );

        OPEN movie_probe;
        LOOP
            FETCH movie_probe INTO movie_rec;
            EXIT WHEN NOT FOUND;

            OPEN cinemas_probe(movie_rec.movie_name);

            LOOP
                FETCH cinemas_probe INTO cinema_rec;
                EXIT WHEN NOT FOUND;
                cinema_row_counter := cinema_row_counter + 1;

                INSERT INTO cinema_multiples_aev (row_id, movie_name, cinema_name)
                VALUES (cinema_row_counter, cinema_rec.movie_name, cinema_rec.cinema_name);
            END LOOP;

            CLOSE cinemas_probe;
            cinema_row_counter := 0;

            movie_columns := movie_columns || ', "' || movie_rec.movie_name || '" TEXT';
        END LOOP;
        CLOSE movie_probe;

        movie_columns := substring(movie_columns FROM 2);

        DROP TABLE IF EXISTS movie_multiples;
        EXECUTE format('CREATE TEMP TABLE movie_multiples(row_id INT, %s)', movie_columns);

        EXECUTE format(E'
        INSERT INTO movie_multiples
        SELECT *
        FROM crosstab(\'select row_id, movie_name, cinema_name
                        from cinema_multiples_aev

                        order by 1,2\')
            AS cinema_multiples_aev(row_id INT, %s);
        ', movie_columns, movie_columns);

        ALTER TABLE movie_multiples DROP COLUMN  row_id;
    END
$$ LANGUAGE plpgsql;

SELECT *
FROM movie_multiples;

DROP TABLE IF EXISTS movie_multiples;
DROP TABLE IF EXISTS cinema_multiples_aev;

如果因为缺乏结构而让人困惑,那么这里的所有内容都可以找到

这并不是特别简单,因为PostgreSQL不擅长数据透视。请查看tablefunc模块中的交叉表函数。您的结果是否必须是您定义的数据透视形状,或者您可以使用3列studentid、评估、尝试/通过?其作用是什么技术上正确-它将我需要的查询生成一个字符串-我想有一种方法可以在一个查询步骤(生成查询/使用查询)中完成,但这肯定让我走上了正确的轨道。
DROP FUNCTION get_Attempts() ;

CREATE OR REPLACE FUNCTION get_Attempts() RETURNS text AS
$BODY$
DECLARE
        r1 record;
        str_query text := '';
 BEGIN
    str_query :='select student_id,';
    FOR r1 IN SELECT "_id" , "name" FROM Assessments
    LOOP
      str_query:= str_query || 
                  'sum(case when assessment_id=' || r1."_id" || ' then 1 else 0 end) as  "' || r1.name ||'",' ;
    END LOOP;
    str_query:=trim( trailing  ',' from str_query); -- remove last semicolon
    str_query:= str_query || ' from assessments_students group by student_id order by student_id';
    return str_query;
END
$BODY$
LANGUAGE 'plpgsql' ;

SELECT * FROM get_Attempts();
select student_id,
max(case when assessment_id=1 and passed='t' then 't' else 'f' end) as  "Assessment 1",
max(case when assessment_id=2 and passed='t' then 't' else 'f' end) as  "Assessment 2",
max(case when assessment_id=3 and passed='t' then 't' else 'f' end) as  "Assessment 3",
max(case when assessment_id=4 and passed='t' then 't' else 'f' end) as  "Assessment 4",
max(case when assessment_id=5 and passed='t' then 't' else 'f' end) as  "Assessment 5",
max(case when assessment_id=6 and passed='t' then 't' else 'f' end) as  "Assessment 6" 
from assessments_students 
group by student_id 
order by student_id
DROP FUNCTION get_passed() ;

CREATE OR REPLACE FUNCTION get_passed() RETURNS text AS
$BODY$
DECLARE
        r1 record;
        str_query text := '';
 BEGIN
    str_query :='select student_id,';
    FOR r1 IN SELECT "_id" , "name" FROM Assessments
    LOOP
      str_query:= str_query || 
                  'max(case when assessment_id=' || r1."_id" || ' and passed=''t'' then ''t'' else ''f'' end) as  "' || r1.name ||'",' ;
    END LOOP;
    str_query:=trim( trailing  ',' from str_query); -- remove last semicolon
    str_query:= str_query || ' from assessments_students group by student_id order by student_id';

    return str_query;
END
$BODY$
LANGUAGE 'plpgsql' ;

SELECT * FROM get_passed();
CREATE EXTENSION IF NOT EXISTS tablefunc;

DO
$$
    DECLARE
        movie_probe CURSOR FOR
            SELECT m.name movie_name, count(c.id) cinema_count
            FROM movies m
                     JOIN sessions s ON m.id = s.movie_id
                     JOIN cinema_halls ch ON s.cinema_hall_id = ch.id
                     JOIN cinemas c ON ch.cinema_id = c.id
            GROUP BY m.name
            HAVING count(c.name) > 1
            ORDER BY count(c.name) DESC;
        movie_rec          RECORD;
        movie_columns      TEXT DEFAULT '';

        cinemas_probe CURSOR (cond_movie_name TEXT) FOR
            SELECT m.name movie_name, c.name cinema_name
            FROM movies m
                     JOIN sessions s ON m.id = s.movie_id
                     JOIN cinema_halls ch ON s.cinema_hall_id = ch.id
                     JOIN cinemas c ON ch.cinema_id = c.id
            WHERE cond_movie_name = m.name
            ORDER BY c.name;

        cinema_rec         RECORD;
        cinema_row_counter INT DEFAULT 0;
    BEGIN
        DROP TABLE IF EXISTS cinema_multiples_aev;
        CREATE TEMP TABLE cinema_multiples_aev (
            row_id      INT,
            movie_name  TEXT,
            cinema_name TEXT
        );

        OPEN movie_probe;
        LOOP
            FETCH movie_probe INTO movie_rec;
            EXIT WHEN NOT FOUND;

            OPEN cinemas_probe(movie_rec.movie_name);

            LOOP
                FETCH cinemas_probe INTO cinema_rec;
                EXIT WHEN NOT FOUND;
                cinema_row_counter := cinema_row_counter + 1;

                INSERT INTO cinema_multiples_aev (row_id, movie_name, cinema_name)
                VALUES (cinema_row_counter, cinema_rec.movie_name, cinema_rec.cinema_name);
            END LOOP;

            CLOSE cinemas_probe;
            cinema_row_counter := 0;

            movie_columns := movie_columns || ', "' || movie_rec.movie_name || '" TEXT';
        END LOOP;
        CLOSE movie_probe;

        movie_columns := substring(movie_columns FROM 2);

        DROP TABLE IF EXISTS movie_multiples;
        EXECUTE format('CREATE TEMP TABLE movie_multiples(row_id INT, %s)', movie_columns);

        EXECUTE format(E'
        INSERT INTO movie_multiples
        SELECT *
        FROM crosstab(\'select row_id, movie_name, cinema_name
                        from cinema_multiples_aev

                        order by 1,2\')
            AS cinema_multiples_aev(row_id INT, %s);
        ', movie_columns, movie_columns);

        ALTER TABLE movie_multiples DROP COLUMN  row_id;
    END
$$ LANGUAGE plpgsql;

SELECT *
FROM movie_multiples;

DROP TABLE IF EXISTS movie_multiples;
DROP TABLE IF EXISTS cinema_multiples_aev;