Count number of action and show last date of action - mysql

I am querying an audit database to try and find out how many actions each user has completed and when their last action was.
The query I am using is :
SELECT user_id,
count(id) as actions,
datetime
from auditing
WHERE datetime>='2014-03-01 00:00:00'
GROUP BY user_id
ORDER BY `auditing`.`datetime` DESC
This correctly shows me the total number of items but it does not show the correct last date - the date it does show me it quite random i.e. not at the top or bottom of the list but taken from somewhere in the middle. I checked this for a number of entries produced and they are all wrong and do not reflect the latest action.
How can I get it to show me the last (most recent) event in the above query?
Example:
user_id | actions | datetime
1 | 10 | 2014-07-04 16:10:14
2 | 55 | 2014-07-05 11:15:08
3 | 8 | 2014-07-04 22:19:43
Thanks

You should only SELECT columns that are part of your GROUP BY clause or are a result of an aggregate function. You can and probably should configure your database server so that it would complain about your query. It would say something like:
ERROR 1055 (42000): 'datetime' isn't in GROUP BY
The reason behind it is, that you don't tell the database server which datetime value you want (the earliest, the average, the latest?). So in order to get the last value, try this query:
SELECT user_id, count(id) as actions, max(datetime)
FROM auditing
WHERE datetime>='2014-03-01 00:00:00'
GROUP BY user_id
ORDER BY user_id

You can try with this:
SELECT user_id, COUNT(actions), MAX(datetime)
FROM auditing
WHERE datetime>='2014-03-01 00:00:00'
GROUP BY user_id

Related

SQL select with dynamic count of "BETWEEN" conditions based on joined table

I want to add a messenger to my pet project, but I am having difficulty writing database queries. I use MySQL for this service with Hibernate as ORM. Almost all queries was written in HQL, but in principle I can use native queries.
Messenger can contain group conversations. In addition to writing messages, user can enter the conversation, leave it, clear personal message history. User sees all messages when he has been in a conversation, but he can also clear the history and see only messages after the last clearing.
Below I described the simplified structure of two tables important for this task.
Message table:
ID
text
timestamp
1
first_msg
1609459200
2
second_msg
1609545600
Member_event table:
id
user_id
type
timestamp
1
1
1
1609459100
2
1
3
1609459300
3
1
2
1609459400
4
1
1
1609545500
where type:
1 - user entered the chat,
2 - user leaved the chat,
3 - user cleared his own history of messages in the chat
Is it possible to read all chat messages available to the user with one request?
I have no idea how to check conditions dynamically: WHERE message's timestamps are between all "entered-leaved" cycles and after the last "entered" if not leaved BUT only after the last history clearing. If exists.
I think you could proceed with these steps:
take the union of both tables and consider the records in order of time stamp
Use window functions to determine whether the most recent 1 or 2 type was a 1. We can use a running sum where type 1 adds one and type 2 subtracts one (and 3 does nothing to it). With another window function you could determine whether there is still a type 3 following. The combination of these two informations can be translated to a 1 when the line belongs to an interval that must be collected, and a 0 when not.
Filter the previous result to just get the message records, and only those where the calculation was 1.
Here is the query:
with unified as (
select id, text, timestamp, null as type
from message
union
select id, null, timestamp, type
from member_event
where user_id = 1),
validated as (
select unified.*,
sum(case type when 1 then 1 when 2 then -1 else 0 end)
over (order by timestamp
rows unbounded preceding) *
min(case type when 3 then 0 else 1 end)
over (order by timestamp
rows between current row and unbounded following) valid
from unified
order by timestamp)
select id, text, timestamp
from validated
where type is null and valid = 1
order by timestamp
I do not see, how you could match the Member_event table to the Message_table without an additional FOREIGN_KEY. Are you trying to assign the Messages available to the User via Timestamp?
If so try this:
SELECT * FROM MESSAGE_TABLE m
WHERE m.TIMESTAMP BETWEEN
(SELECT TOP 1 TIMESTAMP FROM MEMBER_EVENT_TABLE WHERE type = 1 ORDER BY TIMESTAMP DESC)
AND (SELECT TOP 1 TIMESTAMP FROM MEMBER_EVENT_TABLE WHERE type != 1 ORDER BY TIMESTAMP DESC)
This at least should show the last Messages between join and clean/leave

mysqli COUNT with WHERE and GROUP BY

I am trying to get the amount of records on specific dates. The problem is that when a date has no records it doesnt show the date.
$asksome= mysqli_query("
SELECT COUNT(worker_id) AS amount, problem.datter AS datum
FROM problem
WHERE worker_id = $idmed
GROUP BY datter"
);
The structure looks like this:
worker_id | datter | note
1 |2018-02-25 |
1 |2018-02-26 |
3 |2018-02-25 |
This is the query I am using. I want the query to show the dates even if the COUNT is 0. what i mean by this is i want the result to be like this:
count result | date
0 | 2018-02-24
2 | 2018-02-25
1 | 2018-02-26
0 | 2018-02-27
- - For people finding this post not knowing what to do: i fixed my issue by making another table that simply contained the specific dates that i want to check.
I used the following query:
SELECT weeks.week_id AS datum, COUNT(problem.datter) AS amount
FROM weeks
LEFT JOIN problem ON problem.datter = weeks.week_id
GROUP BY weeks.week_id
You should make clear for the sql of how to handle the count when it is 0. In general, when using SQL's SUM() and COUNT() along with the GROUP BY statement, 0 is never output. And that is because, the GROUP BY clause has nothing to group with when no records for your count of 0 exist.
For that you can use the COALESCE expression.
You can see its documentation here: https://learn.microsoft.com/en-us/sql/t-sql/language-elements/coalesce-transact-sql
So in your example, the sql query should be:
SELECT COALESCE (SELECT COUNT(worker_id) AS amount, problem.datter AS datum
FROM problem
WHERE worker_id = $idmed
GROUP BY datter"), 0);

mysql highly selective query

I have a data set like this:
User Date Status
Eric 1/1/2015 4
Eric 2/1/2015 2
Eric 3/1/2015 4
Mike 1/1/2015 4
Mike 2/1/2015 4
Mike 3/1/2015 2
I'm trying to write a query in which I will retrieve users whose MOST RECENT transaction status is a 4. If it's not a 4 I don't want to see that user in the results. This dataset could have 2 potential results, one for Eric and one for Mike. However, Mike's most recent transaction was not a 4, therefore:
The return result would be:
User Date Status
Eric 3/1/2015 4
As this record is the only record for Eric that has a 4 as his latest transaction date.
Here's what I've tried so far:
SELECT
user, MAX(date) as dates, status
FROM
orders
GROUP BY
status,
user
This would get me to a unqiue record for every user for every status type. This would be a subquery, and the parent query would look like:
SELECT
user, dates, status
WHERE
status = 4
GROUP BY
user
However, this is clearly flawed as I don't want status = 4 records IF their most recent record is not a 4. I only want status = 4 when the latest date is a 4. Any thoughts?
SELECT user, date
, actualOrders.status
FROM (
SELECT user, MAX(date) as date
FROM orders
GROUP BY user) AS lastOrderDates
INNER JOIN orders AS actualOrders USING (user, date)
WHERE actualOrders.status = 4
;
-- Since USING is being used, there is not a need to specify source of the
-- user and date fields in the SELECT clause; however, if an ON clause was
-- used instead, either table could be used as the source of those fields.
Also, you may want to rethink the field names used if it is not too late and user and date are both found here.
SELECT user, date, status FROM
(
SELECT user, MAX(date) as date, status FROM orders GROUP BY user
)
WHERE status = 4
The easiest way is to include your order table a second time in a subquery in your from clause in order to retrieve the last date for each user. Then you can add a where clause to match the most recent date per user, and finally filter on the status.
select orders.*
from orders,
(
select ord_user, max(ord_date) ord_date
from orders
group by ord_user
) latestdate
where orders.ord_status = 4
and orders.ord_user = latestdate.ord_user
and orders.ord_date = latestdate.ord_date
Another option is to use the over partition clause:
Oracle SQL query: Retrieve latest values per group based on time
Regards,

Multiple distinct counts with where

I am having an issue creating most efficient query for multiple distinct counts of a column with different where clauses. My MYSQL table looks like this:
id client_id result timestamp
---------------------------------------------------
1 1234566 escalated 2014-01-02 00:00:00
2 1233344 approved 2014-02-03 00:00:00
3 1234566 escalated 2014-01-02 01:00:00
What I am trying to achieve is to build the following data in the return:
Total number of unique client IDs processed from the beginning of time.
Total number of unique client IDs processed escalated from the beginning of time.
Total number of unique client IDs processed approved from the beginning of time.
Count of unique client IDs approved within specified timeframe using between statement on timestamp.
Count of unique client IDs escalated within specified timeframe using between statement on timestamp.
I have thought about running multiple selects, but I think it would be a waste of resources, and possibly if this could be done with a single query it would the best way to handle it, unfortunately my experience is lacking in this area. What I would like would the return to simple contain an alias and the count.
Any help would be appreciated.
You want conditional aggregation, something like:
select count(distinct ClientId) as NumClients,
count(distinct case when result = 'Approved' then ClientId end) as NumApproved,
count(distinct case when result = 'Escalated' then ClientId end) as NumEscalated,
count(distinct case when result = 'Approved' and timestamp between #Time1 and #Time2
then ClientId end) as NumApproved,
count(distinct case when result = 'Escalated' and timestamp between #Time1 and #Time2
then ClientId end) as NumEscalated,
from table t;

How can I repeat this query over several months in MySQL?

I have a membership list with over 1.2M members. People commonly subscribe, unsubscribe, and re-subscribe to the list. Often, I find myself needing to know which users were subscribed at a particular moment in time. I have a table called subscription_history, with this structure:
-----------------------------------------------------------------------
| id | native key |
-----------------------------------------------------------------------
| user_id | foreign key that joins the user table |
-----------------------------------------------------------------------
| change_code | 1 or 2 for subscriptions, 4-7 for unsubscriptions |
-----------------------------------------------------------------------
| created_at | date-time stamp when the change was made |
-----------------------------------------------------------------------
Right now, if I want to know who was subscribed at a particular date in the past (March 31, 2012 in this example), I run this query:
SELECT user_id
FROM
(SELECT
user_id
, MAX(created_at) AS last_change_date
, change_code
FROM subscriptionhistory
WHERE DATE(created_at) <= '2012-03-31'
GROUP BY user_id
) AS last
WHERE change_code IN (1,2)
This finds each user's last subscription action before or on the target date, then returns the user if that action was a subscription. We then use that list of users to run various other queries, such as the average lifetime sales. This system works well, but only for one date at a time. If I wanted to know the average subscriber's lifetime sales for every month of the year, I would have to run this query 12 times, manually incrementing the date in the WHERE statement each time.
Now I want to create a version of this that can I can use for more than a single date... so that it could give me all users subscribed in January, then February, etc., and I could run average lifetime sales for subscribers in each month. I can't just do a GROUP BY for this, since someone who was a subscriber in March might have unsubscribed in April and re-subscribed in June. I suppose I could 12 UNION queries ... but was hoping for something a little more elegant!
A few limiting parameters: I only have read-only access to the database; I cannot change anything about the table structure or make temporary tables. I have to do this only in MySQL - because of the way our CRM works, I can't use Python or PHP to manipulate results. Any help would be greatly appreciated! Please let me know if I am not explaining this well. Thanks!
SELECT user_id, group_concat(date_format(created_at, '%Y-%m')) as ActiveMonth from
(SELECT user_id, created_at, change_code from Subscriptions WHERE
change_code in (1,2) order by 1,2,3) b
group by user_id
order by user_id, ActiveMonth desc
You can take the group_concat out and the group by and it should give you a row and active month for every user_id.
I created a SQLFiddle and changed the table name to subscriptions for ease of use.
http://sqlfiddle.com/#!2/6b2f2/14