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 t开发者_运维百科hat 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
精彩评论