How to group results according to range of datetimes on another table records
I have two tables:
BusinessViews
:
id | createdAt | businessId | userId |
---|---|---|---|
1 | 2022-01-19 18:00:00 | 1 | 1 |
2 | 2022-01-19 18:02:00 | 1 | 1 |
3 | 2022-01-19 18:05:00 | 1 | 1 |
4 | 2022-01-19 18:05:50 | 1 | 1 |
5 | 2022-01-19 18:07:00 | 1 | 1 |
6 | 2022-01-19 18:08:00 | 1 | 1 |
7 | 2022-01-19 18:10:00 | 1 | 1 |
BusinessClients
:
id | status | createdAt | userId | createdBy | businessId |
---|---|---|---|---|---|
1 | 1 | 2022-01-19 18:01:00 | 1 | 1 | 1 |
2 | 2 | 2022-01-19 18:03:00 | 1 | 2 | 1 |
3 | 0 | 2022-01-19 18:06:00 | 1 | 1 | 1 |
4 | 1 | 2022-01-19 18:07:30 | 1 | 1 | 1 |
5 | 0 | 2022-01-19 18:09:00 | 1 | 2 | 1 |
The BusinessViews
is for register the business page views by users and BusinessClients
for the clients history. To get the count of total page views for businessId = 1
for example I simply did this:
SELECT COUNT(*)
FROM BusinessViews
WHERE businessId = 1
GROUP BY businessId
Result: 7
But now I want to segment this result to get the count of views made by clients and the count of views made by non-clients users at the moment of view. For that, let me explain the dynamics of BusinessClients
: with this table users can request to be clients of any business and the business employees can refuse/accept (in case of request) and remove (in case of the user is already a client). If the user has no records in the table it's because he was never a client of the business and if the user has records in the table they can have three types of status:
-
status = 1
means user sent the request to business (If the user does not have records yet, this is the only possibility) -
status = 2
means user request was accepted by a business employee -
status = 0
ifuserId = createdBy
means user leaved the business and ifuserId != createdBy
means employee refused the request (if previous status = 1) or removed user of the business (if previous status = 2)
Taking that into account, the result I would like to get is:
Client views: 2
Non-client views: 5
I know I have to work with BusinessClients.status
and BusinessClients.createdAt
ranges according to BusinessViews.createdAt
value. But I'm completely out of ideas on how to do this query. I don't know if I should use left join or subqueries and I can't think of any way to get these results.
What is the correct way to do this?
UPDATE:
Explaining the desired result: I have 7 views and I want to know how many of these views came from clients and non-clients users at the moment of the view (I'm using only one userId
to simplify understanding but there may be multiple users registering views in BusinessViews
table and joining and leaving BusinessClients
table), so:
-
BusinessView with
id = 1
was created at 2022-01-19 18:00:00 and at this moment the corresponding user is not a client yet (because he was never a client), so:
Client Views: 0
andNon-client views: 1
.
-
BusinessView with
id = 2
was created at 2022-01-19 18:02:00 and at this moment the corresponding user is not a client yet again (because thecurrent status still = 1
at this time and only changed at 2022-01-19 18:03:00 (BusinessClient withid = 2
), so:
Client Views: 0
andNon-client views: 2
.
-
BusinessView with
id = 3
was created at 2022-01-19 18:05:00 and at this moment the corresponding user is already a client (becausestatus = 2
was created at 2022-01-19 18:03:00 (BusinessClient withid = 2
) and only changed at 2022-01-19 18:06:00 (BusinessClient withid = 3
), so:
Client Views: 1
andNon-client views: 2
.
-
BusinessView with
id = 4
same as previous item, so:
Client Views: 2
andNon-client views: 2
.
-
BusinessView with
id = 5
was created at 2022-01-19 18:07:00 and at this moment the corresponding user is not a client anymore (becausestatus = 0
was created at 2022-01-19 18:06:00 (BusinessClient withid = 3
) and only changed at 2022-01-19 18:07:30 (BusinessClient withid = 4
), so:
Client Views: 2
andNon-client views: 3
-
BusinessView with
id = 6
was created at 2022-01-19 18:08:00 and at this moment the corresponding user still not a client (becausestatus = 1
was created at 2022-01-19 18:07:30 (BusinessClient withid = 4
) and only changed at 2022-01-19 18:09:00 (BusinessClient withid = 5
), so:
Client Views: 2
andNon-client views: 4
.
-
BusinessView with
id = 7
same as previous item (the only difference is that this is the last record so far), so
Client Views: 2
andNon-client views: 5
.
The question is: where is the BusinessViews.createdAt
between the BusinessClients.createdAt
records and what is the corresponding BusinessClients.status
for the user at BusinessViews.createdAt
?
Solution 1:
UPDATED
Thank you for adding the explanation of the join.
Client views: 2
Non-client views: 5
WITH BusinessViews (id, createdAt, businessId, userId) AS (
SELECT 1, '2022-01-19 18:00:00', 1, 1 UNION ALL
SELECT 2, '2022-01-19 18:02:00', 1, 1 UNION ALL
SELECT 3, '2022-01-19 18:05:00', 1, 1 UNION ALL
SELECT 4, '2022-01-19 18:05:50', 1, 1 UNION ALL
SELECT 5, '2022-01-19 18:07:00', 1, 1 UNION ALL
SELECT 6, '2022-01-19 18:08:00', 1, 1 UNION ALL
SELECT 7, '2022-01-19 18:10:00', 1, 1
), BusinessClients (id, status, createdAt, userId, createdBy, businessId) AS (
SELECT 1, 1, '2022-01-19 18:01:00', 1, 1, 1 UNION ALL
SELECT 2, 2, '2022-01-19 18:03:00', 1, 2, 1 UNION ALL
SELECT 3, 0, '2022-01-19 18:06:00', 1, 1, 1 UNION ALL
SELECT 4, 1, '2022-01-19 18:07:30', 1, 1, 1 UNION ALL
SELECT 5, 0, '2022-01-19 18:09:00', 1, 2, 1
)
SELECT
COUNT(CASE WHEN bcs.status IS NULL OR bcs.status <> 2 THEN 1 END) 'non-client-views',
COUNT(CASE WHEN bcs.status = 2 THEN 1 END) 'client-views'
FROM BusinessViews bv
LEFT JOIN LATERAL (
SELECT status
FROM BusinessClients bc
WHERE bc.businessID = bv.businessID
AND bc.userId = bv.userId
AND bc.createdAt < bv.createdAt
ORDER BY bc.id DESC
LIMIT 1
) bcs ON true
WHERE bv.businessId = 1;
I have included the CTEs to save others the effort, if they fancy trying this.
Solution 2:
If i understand correctly :
SELECT
Count(case when bc.id is null end) as client
, count(case when bc.id is not null end) as non-client
FROM BusinessViews bv
Left join BusinessClients bc
On bv.businessid = bc.businessid
WHERE bv.businessId = 1
GROUP BY bv.businessId