简体   繁体   中英

PostgreSQL Crosstab generate_series of weeks for columns

From a table of "time entries" I'm trying to create a report of weekly totals for each user.

Sample of the table:

+-----+---------+-------------------------+--------------+
| id  | user_id | start_time              | hours_worked |
+-----+---------+-------------------------+--------------+
| 997 | 6       | 2018-01-01 03:05:00 UTC | 1.0          |
| 996 | 6       | 2017-12-01 05:05:00 UTC | 1.0          |
| 998 | 6       | 2017-12-01 05:05:00 UTC | 1.5          |
| 999 | 20      | 2017-11-15 19:00:00 UTC | 1.0          |
| 995 | 6       | 2017-11-11 20:47:42 UTC | 0.04         |
+-----+---------+-------------------------+--------------+

Right now I can run the following and basically get what I need

SELECT COALESCE(SUM(time_entries.hours_worked),0) AS total, 
  time_entries.user_id, 
  week::date

--Using generate_series here to account for weeks with no time entries when
--doing the join

FROM generate_series( (DATE_TRUNC('week', '2017-11-01 00:00:00'::date)),
                      (DATE_TRUNC('week', '2017-12-31 23:59:59.999999'::date)),
                      interval '7 day') as week LEFT JOIN time_entries
ON DATE_TRUNC('week', time_entries.start_time) = week

GROUP BY week, time_entries.user_id
ORDER BY week

This will return

+-------+---------+------------+
| total | user_id | week       |
+-------+---------+------------+
| 14.08 | 5       | 2017-10-30 |
| 21.92 | 6       | 2017-10-30 |
| 10.92 | 7       | 2017-10-30 |
| 14.26 | 8       | 2017-10-30 |
| 14.78 | 10      | 2017-10-30 |
| 14.08 | 13      | 2017-10-30 |
| 15.83 | 15      | 2017-10-30 |
| 8.75  | 5       | 2017-11-06 |
| 10.53 | 6       | 2017-11-06 |
| 13.73 | 7       | 2017-11-06 |
| 14.26 | 8       | 2017-11-06 |
| 19.45 | 10      | 2017-11-06 |
| 15.95 | 13      | 2017-11-06 |
| 14.16 | 15      | 2017-11-06 |
| 1.00  | 20      | 2017-11-13 |
| 0     |         | 2017-11-20 |
| 2.50  | 6       | 2017-11-27 |
| 0     |         | 2017-12-04 |
| 0     |         | 2017-12-11 |
| 0     |         | 2017-12-18 |
| 0     |         | 2017-12-25 |
+-------+---------+------------+

However, this is difficult to parse particularly when there's no data for a week. What I would like is a pivot or crosstab table where the weeks are the columns and the rows are the users. And to include nulls from each (for instance if a user had no entries in that week or week without entries from any user).

Something like this

+---------+---------------+--------------+--------------+
| user_id | 2017-10-30    | 2017-11-06   | 2017-11-13   |
+---------+---------------+--------------+--------------+
| 6       | 4.0           | 1.0          | 0            |
| 7       | 4.0           | 1.0          | 0            |
| 8       | 4.0           | 0            | 0            |
| 9       | 0             | 1.0          | 0            |
| 10      | 4.0           | 0.04         | 0            |
+---------+---------------+--------------+--------------+

I've been looking around online and it seems that "dynamically" generating a list of columns for crosstab is difficult . I'd rather not hard code them, which seems weird to do anyway for dates. Or use something like this case with week number .

Should I look for another solution besides crosstab? If I could get the series of weeks for each user including all nulls I think that would be good enough. It just seems that right now my join strategy isn't returning that.

Personally I would use a Date Dimension table and use that table as the basis for the query. I find it far easier to use tabular data for these types of calculations as it leads to SQL that's easier to read and maintain. There's a great article on creating a Date Dimension table in PostgreSQL at https://medium.com/@duffn/creating-a-date-dimension-table-in-postgresql-af3f8e2941ac , though you could get away with a much simpler version of this table.

Ultimately what you would do is use the Date table as the base for the SELECT cols FROM table section and then join against that, or probably use Common Table Expressions, to create the calculations.

I'll write up a solution to that if you would like demonstrating how you could create such a query.

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