Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
163 views
in Technique[技术] by (71.8m points)

sql - Count devices per day in a given date range

I have a table which has devices with 3 statuses, Pass, Fail and Warning.

Device Status Date
Device1 Pass 12/1/2020
Device2 Fail 12/1/2020
Device3 Warning 12/1/2020
Device1 Fail 12/2/2020
Device2 Warning 12/2/2020
Device3 Pass 12/2/2020
question from:https://stackoverflow.com/questions/65929258/count-devices-per-day-in-a-given-date-range

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You can use the aggregate FILTER clause to do it in a single query.

This gets three counts (fail, pass, warn) for every selected device on every day in the selected date range. A count of NULL for days without any appearance. 0 if the device appeared, but not with this status:

SELECT date, device_name
     , fail_count, warning_count, pass_count
FROM  (SELECT DISTINCT device_name FROM status_table) d  -- all devices ①
CROSS  JOIN (
   SELECT generate_series(timestamp '2020-12-01'
                        , timestamp '2020-12-31'
                        , interval  '1 day')::date
   ) t(date)  -- all dates
LEFT   JOIN (
   SELECT date, device_name
        , count(*) FILTER (WHERE overall_status = 'Fail')    AS fail_count
        , count(*) FILTER (WHERE overall_status = 'Warning') AS warning_count
        , count(*) FILTER (WHERE overall_status = 'Pass')    AS pass_count 
   FROM   status_table
   WHERE  date >= '2020-12-01'  -- same date range as above
   AND    date <= '2020-12-31'
   GROUP  BY 1, 2
   ) s USING (date, device_name)
ORDER  BY 1, 2;

Basically, you CROSS JOIN all devices to all dates (Cartesian product), the append data where data can be found with a LEFT JOIN.

① Since you don't seem to have a device table (which you probably should), generate the full list on the fly. The above query with DISTINCT is good for few rows per device. Else, there are (much) faster techniques like:

WITH RECURSIVE cte AS (
   (SELECT device_name FROM status_table ORDER BY 1 LIMIT 1)
   UNION ALL
   SELECT (SELECT device_name FROM status_table
           WHERE  device_name > t.device_name ORDER BY 1 LIMIT 1)
   FROM   cte
   WHERE  device_name IS NOT NULL
   )
SELECT * FROM cte
WHERE  device_name IS NOT NULL;

See:

The subquery s aggregates only rows from the given date range. It's strictly optional. You can also left-join to the underlying table directly, and then aggregate all. But this approach is typically (much) faster.

You can convert NULL to zero or vice versa with COALESCE / NULLIF.

Related:

For more flags, a crosstab() query might be faster. See:

About generating a date range:

Be aware that dates are defined by your current time zone setting if you operate with timestamp with time zone. See:


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...