代码之家  ›  专栏  ›  技术社区  ›  Topher Fangio

SQL将行转换为列

  •  37
  • Topher Fangio  · 技术社区  · 16 年前

    我有一个有趣的难题,我相信可以用纯SQL解决。我有如下类似的表格:

    responses:
    
    user_id | question_id | body
    ----------------------------
    1       | 1           | Yes
    2       | 1           | Yes
    1       | 2           | Yes
    2       | 2           | No
    1       | 3           | No
    2       | 3           | No
    
    
    questions:
    
    id | body
    -------------------------
    1 | Do you like apples?
    2 | Do you like oranges?
    3 | Do you like carrots?
    

    我想得到以下输出

    user_id | Do you like apples? | Do you like oranges? | Do you like carrots?
    ---------------------------------------------------------------------------
    1       | Yes                 | Yes                  | No
    2       | Yes                 | No                   | No
    

    我不知道会有多少个问题,它们是动态的,所以我不能只为每个问题编码。我使用的是PostgreSQL,我认为这称为换位,但我似乎找不到任何在SQL中说明这一标准方法的东西。我记得在大学的时候在我的数据库课上做过这个,但是在MySQL中,我真的不记得我们是怎么做的。

    我假设它是连接和 GROUP BY 声明,但我甚至不知道如何开始。

    有人知道怎么做吗?非常感谢!

    编辑1: 我发现了一些关于使用 crosstab 这似乎是我想要的,但我很难理解。链接到更好的文章将非常感谢!

    5 回复  |  直到 10 年前
        1
  •  48
  •   OMG Ponies    15 年前

    用途:

      SELECT r.user_id,
             MAX(CASE WHEN r.question_id = 1 THEN r.body ELSE NULL END) AS "Do you like apples?",
             MAX(CASE WHEN r.question_id = 2 THEN r.body ELSE NULL END) AS "Do you like oranges?",
             MAX(CASE WHEN r.question_id = 3 THEN r.body ELSE NULL END) AS "Do you like carrots?"
        FROM RESPONSES r
        JOIN QUESTIONS q ON q.id = r.question_id
    GROUP BY r.user_id
    

    这是一个标准的透视查询,因为您将数据从行“透视”到列数据。

        2
  •  11
  •   Hannes Landeholm    10 年前

    我实现了一个真正的动态函数来处理这个问题,而不必硬编码任何特定的答案类或使用外部模块/扩展。它还提供对列排序的完全控制,并支持多个键列和类/属性列。

    你可以在这里找到它: https://github.com/jumpstarter-io/colpivot

    解决这个特定问题的示例:

    begin;
    
    create temporary table responses (
        user_id integer,
        question_id integer,
        body text
    ) on commit drop;
    
    create temporary table questions (
        id integer,
        body text
    ) on commit drop;
    
    insert into responses values (1,1,'Yes'), (2,1,'Yes'), (1,2,'Yes'), (2,2,'No'), (1,3,'No'), (2,3,'No');
    insert into questions values (1, 'Do you like apples?'), (2, 'Do you like oranges?'), (3, 'Do you like carrots?');
    
    select colpivot('_output', $$
        select r.user_id, q.body q, r.body a from responses r
            join questions q on q.id = r.question_id
    $$, array['user_id'], array['q'], '#.a', null);
    
    select * from _output;
    
    rollback;
    

    此输出:

     user_id | 'Do you like apples?' | 'Do you like carrots?' | 'Do you like oranges?' 
    ---------+-----------------------+------------------------+------------------------
           1 | Yes                   | No                     | Yes
           2 | Yes                   | No                     | No
    
        3
  •  6
  •   Francisco Puga    13 年前

    您可以使用 crosstab 以这种方式工作

    drop table if exists responses;
    create table responses (
    user_id integer,
    question_id integer,
    body text
    );
    
    drop table if exists questions;
    create table questions (
    id integer,
    body text
    );
    
    insert into responses values (1,1,'Yes'), (2,1,'Yes'), (1,2,'Yes'), (2,2,'No'), (1,3,'No'), (2,3,'No');
    insert into questions values (1, 'Do you like apples?'), (2, 'Do you like oranges?'), (3, 'Do you like carrots?');
    
    select * from crosstab('select responses.user_id, questions.body, responses.body from responses, questions where questions.id = responses.question_id order by user_id') as ct(userid integer, "Do you like apples?" text, "Do you like oranges?" text, "Do you like carrots?" text);
    

    首先,必须安装TableFunc扩展。由于9.1版本,您可以使用创建扩展名执行此操作:

    CREATE EXTENSION tablefunc;
    
        4
  •  2
  •   SunWuKung    11 年前

    我编写了一个函数来生成动态查询。 它为交叉表生成SQL并创建一个视图(如果它存在,则先删除它)。 您可以从视图中选择以获得结果。

    功能如下:

    CREATE OR REPLACE FUNCTION public.c_crosstab (
      eavsql_inarg varchar,
      resview varchar,
      rowid varchar,
      colid varchar,
      val varchar,
      agr varchar
    )
    RETURNS void AS
    $body$
    DECLARE
        casesql varchar;
        dynsql varchar;    
        r record;
    BEGIN   
     dynsql='';
    
     for r in 
          select * from pg_views where lower(viewname) = lower(resview)
      loop
          execute 'DROP VIEW ' || resview;
      end loop;   
    
     casesql='SELECT DISTINCT ' || colid || ' AS v from (' || eavsql_inarg || ') eav ORDER BY ' || colid;
     FOR r IN EXECUTE casesql Loop
        dynsql = dynsql || ', ' || agr || '(CASE WHEN ' || colid || '=''' || r.v || ''' THEN ' || val || ' ELSE NULL END) AS ' || agr || '_' || r.v;
     END LOOP;
     dynsql = 'CREATE VIEW ' || resview || ' AS SELECT ' || rowid || dynsql || ' from (' || eavsql_inarg || ') eav GROUP BY ' || rowid;
     RAISE NOTICE 'dynsql %1', dynsql; 
     EXECUTE dynsql;
    END
    
    $body$
    LANGUAGE 'plpgsql'
    VOLATILE
    CALLED ON NULL INPUT
    SECURITY INVOKER
    COST 100;
    

    以下是我如何使用它:

    SELECT c_crosstab('query_txt', 'view_name', 'entity_column_name', 'attribute_column_name', 'value_column_name', 'first');
    

    例子: 拳头你跑:

    SELECT c_crosstab('Select * from table', 'ct_view', 'usr_id', 'question_id', 'response_value', 'first');
    

    比:

    Select * from ct_view;
    
        5
  •  0
  •   Peter Eisentraut    16 年前

    在中有一个这样的例子 contrib/tablefunc/ .