简体   繁体   中英

LEFT JOIN using subquery not returning null results as “0”

I'm writing a query that searches a database of parking tickets and counts how many have been issued on each quarter hour.

Two moving parts here. The subquery generates 15-minute increment timestamps using generate_series and stores it as "timestep." The outer query then joins using another timestamp statement that rounds everything to the lowest quarter hour.

Right now, it's not returning null results as "0," which is what I'm trying to accomplish by the join.

Essentially, I'm looking for output like this:

12:00: 0
12:15: 10
12:45: 5
13:00: 0

...and so on.

SELECT count(*), timer.timestep
FROM violations
left join (SELECT (hourstep || ':' || minutestep)::time AS timestep 
    from generate_series(0,23) AS hourstep, 
    generate_series(0,59, 15) AS minutestep) 
 AS timer 

ON timer.timestep = 
(extract(hour from violations."InfractionTime") 
|| ':' || 
((extract(minute FROM violations."InfractionTime")::int / 15)*15))::time

GROUP BY timer.timestep 

I think the problem is that you had the VIOLATIONS table as the left-side table, so it was the basis of joining to your time step result set. If no record in violations, it didn't care if there was an entry in your time step result.

I reversed so your LEFT-SIDE table was that of the time-step so you ALWAYS get all 15 minute intervals... then LEFT JOIN to the violations. If no records in violations, it should keep the time slot, but have zero as you are looking for.

SELECT 
      timer.timestep, 
      count(*)
   FROM 
      ( SELECT 
              (hourstep || ':' || minutestep)::time AS timestep
           from 
              generate_series(0,23) AS hourstep, 
              generate_series(0,59, 15) AS minutestep) AS timer 
        LEFT JOIN violations
           ON timer.timestep = 
              (extract(hour from violations."InfractionTime") 
              || ':' || 
              ((extract(minute FROM violations."InfractionTime")::int / 15)*15))::time
   group by
      timer.timestep 

The switcheroo with your LEFT JOIN has been cleared up already. In addition I would suggest this simper and faster query:

SELECT step::time AS timestep, count(*) AS ct
FROM   generate_series('2000-1-1 00:00'::timestamp
                     , '2000-1-1 23:45'::timestamp
                     , '15 min'::interval ) AS t(step)
LEFT   JOIN violations v
         ON v."InfractionTime"::time >= t.step::time
        AND v."InfractionTime"::time <  t.step::time + interval '15 min'
GROUP  BY 1
ORDER  BY 1
  • This uses the second form of generate_series() that works with timestamps directly. Not with time , though, so I take a staging day and cast the result to time (very cheap!)

  • The rewritten query uses sargable expressions for the join, therefore, a plain index on "InfractionTime" can be utilized to great effect - if you query a smaller sample of the table. As long as you query the whole table, Postgres will use a sequential scan anyways.

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