简体   繁体   中英

Sql: Transposing rows into columns

Consider the example below where I have a Person table containing person records and a PersonAttribute table which contains optional attributes linked to a person:

Table: Person

ID    Name
1     Joe Bloggs
2     Jane Doe

Table PersonAttribute

PersonId  Key         Value
1         Age         27            
2         HairColor   Brown

How would I write a query that returns all people with the attributes as if they were columns? The resultset I require is:

ID    Name        Age    HairColor
1     Joe Bloggs  27     
2     Jane Doe           Brown

So essentially I need to write a query that gets all Person Records with all unique Attribute Keys transposed as columns with the value for each person record.

Note that the primary key on the PersonAttribute table is PersonID and Key combined so we wont have duplicate entries for a specific key and person.

Obviously I could add the Age and HairColor as fields in the Person table and not use the PersonAttribute table at all, but this is just an example to illustrate the problem. In reality I have a huge number of custom attributes that vary wildly for different person records so it is not practical to do it that way.

I can't speak about MySQL, but in PostgreSQL you could use crosstab function from tablefunc module:

CREATE OR REPLACE VIEW PersonAttributePivot AS
    SELECT PersonId AS ID, Age, HairColor
    FROM crosstab
    (
       'SELECT PersonId, Key, Value FROM PersonAttribute',
       'SELECT DISTINCT Key FROM PersonAttribute ORDER BY Key'
    )
    AS
    (
        PersonId integer,
        Age text,
        HairColor text
    );

Join query:

SELECT id, name, age, haircolor
FROM Person JOIN PersonAttributePivot USING(id)
ORDER BY id;

Wanted result:

 id |    name    | age | haircolor 
----+------------+-----+-----------
  1 | Joe Bloggs | 27  | 
  2 | Jane Doe   |     | Brown
(2 rows)

As you see I put explicit list of columns in PersonAttributePivot view. I don't know any "automatic-pivot" creation way with implicit column list.

EDIT:

For huge column list (assuming always text type) as a workaround I see such little modified approach:

Dynamic type creation (here trivially Java based):

Class.forName("org.postgresql.Driver");
Connection c =
        DriverManager.getConnection("jdbc:postgresql://localhost/postgres", "postgres", "12345");
Statement s = c.createStatement();
ResultSet rs = s.executeQuery("SELECT DISTINCT Key FROM PersonAttribute ORDER BY Key");
List<String> columns = new ArrayList<String>();

while (rs.next())
    columns.add(rs.getString(1));

System.out.println("CREATE TYPE PersonAttributePivotType AS (");
System.out.println("\tPersonId integer,");
for (int i = 0; i < columns.size(); ++i)
{
    System.out.print("\t" + columns.get(i) + " text");
    if (i != columns.size() - 1)
        System.out.print(",");
    System.out.println();
}
System.out.println(");");

Result:

CREATE TYPE PersonAttributePivotType AS (
    PersonId integer,
    Age text,
    HairColor text
);

Function wrapper:

CREATE OR REPLACE FUNCTION crosstabPersonAttribute(text, text)
    RETURNS setof PersonAttributePivotType
    AS '$libdir/tablefunc','crosstab_hash' LANGUAGE C STABLE STRICT;

Automatic view creation:

CREATE OR REPLACE VIEW PersonAttributePivot AS
    SELECT * FROM crosstabPersonAttribute
    (
       'SELECT PersonId, Key, Value FROM PersonAttribute',
       'SELECT DISTINCT Key FROM PersonAttribute ORDER BY Key'
    );

Result:

TABLE PersonAttributePivot;
 personid | age | haircolor
----------+-----+-----------
        1 | 27  |
        2 |     | Brown
(2 rows)

Process of transforming row-based dataset in column-based dataset is called pivoting. You might get some insight in how to do it at this link: How to pivot a MySQL entity-attribute-value schema

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM