You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在PostgreSQL中基于多行数据动态创建透视表实现行转列

Dynamic Pivoting for User Rows in PostgreSQL

Hey there! Let's tackle this problem where you need to pivot multiple rows per user (uId) into a single row with dynamically generated columns (like location1, rate1, etc., based on how many entries each user has). Here's how to get your desired output:

Step 1: Understand the Core Approach

PostgreSQL doesn't have a built-in dynamic pivot function, but we can achieve this using dynamic SQL combined with window functions to number each user's rows. The key steps are:

  • Assign a unique row number to each entry per user (to map rows to columns).
  • Dynamically generate SQL to create pivot columns for the maximum number of rows any user has.
  • Execute the dynamic SQL to get the pivoted result.

Step 2: Full Solution with PL/pgSQL Function

This reusable function will automatically adjust to the number of rows per user:

CREATE OR REPLACE FUNCTION pivot_user_data()
RETURNS TABLE (
    uId INT,
    fnme TEXT,
    mnme TEXT,
    lnme TEXT
) AS $$
DECLARE
    max_rows_per_user INT;
    dynamic_columns TEXT;
    pivot_select_clause TEXT;
BEGIN
    -- First, find the maximum number of rows any single user has
    SELECT MAX(row_count) INTO max_rows_per_user
    FROM (SELECT COUNT(*) AS row_count FROM your_table GROUP BY uId) user_row_counts;

    -- Generate definitions for dynamic columns (locationN, rateN, hoursN)
    dynamic_columns := '';
    FOR i IN 1..max_rows_per_user LOOP
        dynamic_columns := dynamic_columns || format(',
            location%s TEXT,
            rate%s NUMERIC,
            hours%s NUMERIC', i, i, i);
    END LOOP;

    -- Update the function's return type to include dynamic columns
    EXECUTE format('ALTER FUNCTION pivot_user_data() RETURNS TABLE (uId INT, fnme TEXT, mnme TEXT, lnme TEXT %s)', dynamic_columns);

    -- Build the SELECT clause for pivoting using CASE statements
    pivot_select_clause := 'uId, fnme, mnme, lnme';
    FOR i IN 1..max_rows_per_user LOOP
        pivot_select_clause := pivot_select_clause || format(',
            MAX(CASE WHEN row_num = %s THEN location END) AS location%s,
            MAX(CASE WHEN row_num = %s THEN rate END) AS rate%s,
            MAX(CASE WHEN row_num = %s THEN hours END) AS hours%s',
            i, i, i, i, i, i);
    END LOOP;

    -- Execute the dynamic pivot query and return results
    RETURN QUERY EXECUTE format('
        SELECT %s
        FROM (
            SELECT 
                uId, fnme, mnme, lnme,
                location, rate, hours,
                -- Assign row number to each entry per user (ordered by date)
                ROW_NUMBER() OVER (PARTITION BY uId ORDER BY date) AS row_num
            FROM your_table
        ) numbered_rows
        GROUP BY uId, fnme, mnme, lnme
        ORDER BY uId', pivot_select_clause);
END;
$$ LANGUAGE plpgsql;

Step 3: Use the Function

Call the function to get your pivoted table:

SELECT * FROM pivot_user_data();

Key Notes

  • Order of Columns: The ORDER BY date in the ROW_NUMBER() clause determines which entry maps to location1, location2, etc. Adjust this if you want a different order (e.g., ORDER BY hours DESC).
  • Handling NULLs: If a user has fewer entries than the maximum row count, the extra columns will return NULL (matches your example for user 2).
  • Reusability: The function will automatically adjust if you add more rows for existing users or new users in the future.

Alternative: One-Time Dynamic SQL (No Function)

If you don't want to create a function, you can run this one-time dynamic query (works in psql with \gexec or in a PL/pgSQL block):

WITH user_row_counts AS (
    SELECT COUNT(*) AS row_count FROM your_table GROUP BY uId
), max_rows AS (
    SELECT MAX(row_count) AS max_r FROM user_row_counts
), pivot_columns AS (
    SELECT string_agg(
        format(',
            MAX(CASE WHEN row_num = %s THEN location END) AS location%s,
            MAX(CASE WHEN row_num = %s THEN rate END) AS rate%s,
            MAX(CASE WHEN row_num = %s THEN hours END) AS hours%s',
            i, i, i, i, i, i),
        ''
    ) AS cols
    FROM generate_series(1, (SELECT max_r FROM max_rows)) i
)
SELECT format('
    SELECT uId, fnme, mnme, lnme %s
    FROM (
        SELECT 
            uId, fnme, mnme, lnme,
            location, rate, hours,
            ROW_NUMBER() OVER (PARTITION BY uId ORDER BY date) AS row_num
        FROM your_table
    ) numbered_rows
    GROUP BY uId, fnme, mnme, lnme
    ORDER BY uId', (SELECT cols FROM pivot_columns)) AS dynamic_sql \gexec;

内容的提问来源于stack exchange,提问作者Ngoni

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 20:27:35