3

I want to show the pivot table(crosstab) for the given below table.

Table: Employee

CREATE TABLE Employee
(
Employee_Number varchar(10),
Employee_Role varchar(50),
Group_Name varchar(10)
);

Insertion:

INSERT INTO Employee VALUES('EMP101','C# Developer','Group_1'),
                           ('EMP102','ASP Developer','Group_1'),
                           ('EMP103','SQL Developer','Group_2'),
                           ('EMP104','PLSQL Developer','Group_2'),
                           ('EMP101','Java Developer',''),
                           ('EMP102','Web Developer','');

Now I want to show the pivot table for the above data as shown below:

Expected Result:

Employee_Number     TotalRoles      TotalGroups       Available     Others     Group_1     Group_2
---------------------------------------------------------------------------------------------------
   EMP101               2                2                1           1           1           0
   EMP102               2                2                1           1           1           0
   EMP103               1                2                1           0           0           1 
   EMP104               1                2                1           0           0           1

Explanation: I want to show the Employee_Number, the TotalRoles which each employee has, the TotalGroups which are present to all employees, the Available shows the employee available in how many groups, the Others have to show the employee is available in other's also for which the group_name have not assigned and finally the Group_Names must be shown in the pivot format.

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
MAK
  • 6,824
  • 25
  • 74
  • 131

2 Answers2

1

You can use the crosstab function for this.

First of all you need to add the tablefunc extension if you haven't already:

CREATE EXTENSION tablefunc;

The crosstab functions require you to pass it a query returning the data you need to pivot, then a list of the columns in the output. (In other ways "tell me the input and the output format you want"). The sort order is important!

In your case, the input query is quite complicated - I think you need to do a load of separate queries, then UNION ALL them to get the desired data. I'm not entirely sure how you calculate the values "TotalGroups" and "Available", but you can modify the below in the relevant place to get what you need.

 SELECT * FROM crosstab(

'SELECT employee_number, attribute, value::integer AS value FROM (with allemployees AS (SELECT distinct employee_number FROM employee) -- use a CTE to get distinct employees

SELECT employee_number,''attr_0'' AS attribute,COUNT(distinct employee_role) AS value FROM employee GROUP BY employee_number -- Roles by employee
UNION ALL
SELECT employee_number,''attr_1'' AS attribute,value from allemployees, (select count (distinct group_name) as value from employee where group_name <> '''') a
UNION ALL
SELECT employee_number,''attr_2'' AS attribute, COUNT(distinct group_name) AS value FROM employee where group_name <> '''' GROUP BY employee_number -- Available, do not know how this is calculate
UNION ALL

SELECT a.employee_number, ''attr_3'' AS attribute,coalesce(value,0) AS value FROM allemployees a LEFT JOIN -- other groups. Use a LEFT JOIN to avoid nulls in the output
(SELECT employee_number,COUNT(*) AS value FROM employee WHERE group_name ='''' GROUP BY employee_number) b on a.employee_number = b.employee_number
UNION ALL
SELECT a.employee_number, ''attr_4'' AS attribute,coalesce(value,0) AS value FROM allemployees a LEFT JOIN -- group 1
(SELECT employee_number,COUNT(*) AS value FROM employee WHERE group_name =''Group_1'' GROUP BY employee_number) b on a.employee_number = b.employee_number
UNION ALL
SELECT a.employee_number, ''attr_5'' AS attribute,coalesce(value,0) AS value FROM allemployees a LEFT JOIN -- group 2
(SELECT employee_number,COUNT(*) AS value FROM employee WHERE group_name =''Group_2'' GROUP BY employee_number) b on a.employee_number = b.employee_number) a order by 1,2')

AS ct(employee_number varchar,"TotalRoles" integer,"TotalGroups" integer,"Available" integer, "Others" integer,"Group_1" integer, "Group_2" integer)
mlinth
  • 2,968
  • 6
  • 30
  • 30
1
SELECT * FROM crosstab(
      $$SELECT grp.*, e.group_name
             , CASE WHEN e.employee_number IS NULL THEN 0 ELSE 1 END AS val
        FROM  (
           SELECT employee_number
                , count(employee_role)::int            AS total_roles
                , (SELECT count(DISTINCT group_name)::int
                   FROM   employee
                   WHERE  group_name <> '')            AS total_groups
                , count(group_name <> '' OR NULL)::int AS available
                , count(group_name =  '' OR NULL)::int AS others
           FROM   employee
           GROUP  BY 1
           ) grp
        LEFT   JOIN employee e ON e.employee_number = grp.employee_number
                              AND e.group_name <> ''
        ORDER  BY grp.employee_number, e.group_name$$
     ,$$VALUES ('Group_1'::text), ('Group_2')$$
   ) AS ct (employee_number text
          , total_roles  int
          , total_groups int
          , available    int
          , others       int
          , "Group_1"    int
          , "Group_2"    int);

SQL Fiddle demonstrating the base query, but not the crosstab step, which is not installed on sqlfiddle.com

Basics for crosstab:

Special in this crosstab: all the "extra" columns. Those columns are placed in the middle, after the "row name" but before "category" and "value":

Once again, if you have a dynamic set of groups, you need to build this statement dynamically and execute it in a second call:

Community
  • 1
  • 1
Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
  • What if I want to show only selected number of `group_name` to be display, then how can I calculate the `available` for that? I have tried this : `(SELECT count(available) from ( SELECT CASE WHEN group_name IN ('Group_1') THEN 1 END AS available from employee group by group_name ) a ) as available ` In place of `count(group_name <> '' OR NULL)::int AS available` but did not get proper result. – MAK Mar 19 '15 at 06:22
  • 1
    @MAK: It's a new question. Please start a new question if you are stuck with this. As always, you can link to this one for context. – Erwin Brandstetter Mar 19 '15 at 12:42
  • Have a look at [this](http://stackoverflow.com/questions/29145696/postgresql-9-3-filter-in-pivot-table-query). – MAK Mar 19 '15 at 13:20
  • If I insert `INSERT INTO Employee values('EMP101','C# Developer','Group_1');` same statement multiple times. After this if I execute your script then the `Available` getting changes. Please have a look for this. Thanks :) – MAK Mar 23 '15 at 13:18