SQL query to get historic data from table - mysql

I have a table that stores the history of status changes to a person like this:
id | username | date | status
The date field is when the status was updated and the status field contains the new status that the person has since that date.
So the date in my table could be something like this:
1 | serafeim | 2012-03-03 | "NEW"
2 | john | 2012-03-05 | "NEW"
3 | serafeim | 2012-03-13 | "PENDING"
4 | serafeim | 2012-03-15 | "OLD"
5 | john | 2012-03-05 | "PENDING"
etc etc.
Now, I'd like to have a query that for a specific date in the past will retrieve the status that each user had then. For instance, for 2012-04-14 I'd like to get the following results
serafeim | "PENDING"
john | "NEW"
for 2012-03-04 I should get
serafeim | "NEW"
Can anybody think of an SQL query that will do that ? I don't want to do this programatically ! I'm using mysql but I don't think that that's relative to my problem...
Thanks in advance

The following query identifies the latest record for a given username, before the date specified, and joins the history table with that latest record ID to fetch the rest of the details.
SELECT a.*
FROM
history a
JOIN (SELECT username, MAX(id) 'id' FROM history
WHERE date < #inputDate
GROUP BY username
) as b
ON a.id = b.id

Get the first record of the user having the date less or equal to the input date:
declare #userid nvarchar(128)
declare #date datetime
SELECT userid, status
FROM
(
SELECT limit 1 * FROM mytable
WHERE date <= #date
AND userid = #userid
ORDER by date desc
)
Untested! And sorry if any syntax error.

I cannot test right now on a MySql database, but this query should do the job.
The table2 query retrieve the max date in which you registered an event for every user name before the desired date: this should be the last status event for that person.
The join get the status.
select username, status from table
join
(
select username, max(date) as maxdate from table
where date <= '2012-04-14'
group by username ) table2
on table.username = table2.username and table.date = table.2maxdate
Another way could be without join
select username, status from table
where date = (select max(date) as maxdate from table
where date <= '2012-04-14'
group by username )

Related

MySQL - Count unique users each day considering all previous days

I would like to count how many new unique users the database gets each day for all days recorded.
There will not be any duplicate ids per day, but there will be duplicates over multiple days.
If my table looks like this :
ID | DATE
---------
1 | 2022-05-21
1 | 2022-05-22
2 | 2022-05-22
1 | 2022-05-23
2 | 2022-05-23
1 | 2022-05-24
2 | 2022-05-24
3 | 2022-05-24
I would like the results to look like this :
DATE | NEW UNIQUE IDs
---------------------------
2022-05-21 | 1
2022-05-22 | 1
2022-05-23 | 0
2022-05-24 | 1
A query such as :
SELECT `date` , COUNT( DISTINCT id)
FROM tbl
GROUP BY DATE( `date` )
Will return the count per day and will not take into account previous days.
Any assistance would be appreciated.
Edit : Using MySQL 8
The user is new when the date is the least date for this user.
So you need in something like
SELECT date, COUNT(new_users.id)
FROM calendar
LEFT JOIN ( SELECT id, MIN(date) date
FROM test
GROUP BY id ) new_users USING (date)
GROUP BY date
calendar is either static or dynamically generated table with needed dates list. It can be even SELECT DISTINCT date FROM test subquery.
Start with a subquery showing the earliest date where each id appears.
SELECT MIN(`date`) `firstdate`, id
FROM tbl
GROUP BY id
Then do your count on that subquery. here.
SELECT firstdate, COUNT(*)
FROM (
SELECT MIN(`date`) `firstdate`, id
FROM tbl
GROUP BY id
) m
GROUP BY firstdate
That gives you what you want.
But it doesn't have rows for the dates where no new user ids first appeared.
Only count (and sum) the rows where the left join fails:
SELECT
m1.`DATE` ,
sum(CASE WHEN m2.id is null THEN 1 ELSE 0 END) as C
FROM mytable m1
LEFT JOIN mytable m2 ON m2.`DATE`<m1.`DATE` AND m2.ID=m1.ID
GROUP BY m1.`DATE`
see: DBFIDDLE

MySQL select rows where given date lies between the dates stored in table

Suppose I have some data like:
id status activity_date
--- ------ -------------
101 R 2014-01-12
101 Mt 2014-04-27
101 R 2014-05-18
102 R 2014-02-19
Note that for rows with id = 101 we have activity between 2014-01-12 to 2014-04-26 and 2014-05-18 to current date.
Now I need to select that data where status = 'R' and the date is the most current date as of a given date, e.g. if I search for 2014-02-02, I would find the status row created on 2014-01-12, because that was the status that was still valid at the time for entity ID 101.
If I understand correctly:
Step 1: Convert the start and end date rows into columns. For this, you must join the table with itself based on this criteria:
SELECT
dates_fr.id,
dates_fr.activity_date AS date_fr,
MIN(dates_to.activity_date) AS date_to
FROM test AS dates_fr
LEFT JOIN test AS dates_to ON
dates_to.id = dates_fr.id AND
dates_to.status = 'Mt' AND
dates_to.activity_date > dates_fr.activity_date
WHERE dates_fr.status = 'R'
GROUP BY dates_fr.id, dates_fr.activity_date
+------+------------+------------+
| id | date_fr | date_to |
+------+------------+------------+
| 101 | 2014-01-12 | 2014-04-27 |
| 101 | 2014-05-18 | NULL |
| 102 | 2014-02-19 | NULL |
+------+------------+------------+
Step 2: The rest is simple. Wrap the query inside another query and use appropriate where clause:
SELECT * FROM (
SELECT
dates_fr.id,
dates_fr.activity_date AS date_fr,
MIN(dates_to.activity_date) AS date_to
FROM test AS dates_fr
LEFT JOIN test AS dates_to ON
dates_to.id = dates_fr.id AND
dates_to.status = 'Mt' AND
dates_to.activity_date > dates_fr.activity_date
WHERE dates_fr.status = 'R'
GROUP BY dates_fr.id, dates_fr.activity_date
) AS temp WHERE '2014-02-02' >= temp.date_fr and ('2014-02-02' < temp.date_to OR temp.date_to IS NULL)
+------+------------+------------+
| id | date_fr | date_to |
+------+------------+------------+
| 101 | 2014-01-12 | 2014-04-27 |
+------+------------+------------+
SQL Fiddle
You can try
select id, status, activity_date
from TABLE
where status = "R" and activity_date = "2014-02-02"
where TABLE is name of your table
I think you need following ans
SELECT id,MAX(CAST(ACTIVITY_DATE AS date),MIN(CAST (ACTIVITY_DATE AS date)
FROM Table_Name WHERE CAST('2014-02-02' AS date)
BETWEEN MIN(CAST (ACTIVITY_DATE AS date) AND MAX(CAST(ACTIVITY_DATE AS date)
AND Status='R'
GROUP BY id
Try this:
select * from yourtable
where status='R' and activity_date= '2014-02-02'
You can make a query to effectively give you the most status as of a date, e.g.
SELECT
id,
substr(max(concat(activity_date, status)),11) as status,
max(activity_date) as activity_date
FROM table
WHERE activity_date <= '2014-02-02'
GROUP by id;
Then, similar to Salman's answer, you can use this result inside another query and look for all those results with a status of 'R'
SELECT * from (
SELECT
id,
substr(max(concat(activity_date, status)),11) as status,
max(activity_date) as activity_date
FROM table
WHERE activity_date <= '2014-02-02'
GROUP by id
) AS temp WHERE temp.status = 'R';
Edit: Rather than use the questionable method of sorting the statuses, you could identify the relevant maximum record with a sub-query, so the original query would become
SELECT join1.* FROM table AS join1
INNER JOIN (
SELECT id, max(activity_date) as max_activity_date
FROM table
WHERE activity_date < '2014-02-02'
GROUP BY id
) AS join2
ON join1.id = join2.id AND join1.activity_date = join2.max_activity_date;
and the full query
SELECT * from (
SELECT join1.* FROM table AS join1
INNER JOIN (
SELECT id, max(activity_date) as max_activity_date
FROM table
WHERE activity_date < '2014-02-02'
GROUP BY id
) AS join2
ON join1.id = join2.id AND join1.activity_date = join2.max_activity_date
) AS temp WHERE temp.status = 'R';
try the following
SELECT *
FROM your_relation
WHERE status='R'
AND activity_data="2014-02-02"
I completely agree with Salman's response, the table could be designed in a fashion that allows for greater query accuracy and extensibility. However, the question asked, with regards to a query selecting information based on status and date range can be expressed as.
SELECT * FROM Table_1
WHERE ((status = 'R')
AND ((activity_date BETWEEN '2014-01-12' AND '2014-04-26')
OR activity_date > CONVERT(DATETIME, '2014-05-17')))
This will select all data with a status of 'R' and will use the BETWEEN operator for the range desired; moreover, the conversion of the final operator is because the expression is evaluated as a mathematical expression and requires explicit conversion.

Get the MAX date between a range

I was searching for querys but i cant find an answer that helps me or if exit a similar question.
i need to get the info of the customers that made their last purchase between two dates
+--------+------------+------------+
| client | amt | date |
+--------+------------+------------+
| 1 | 2440.9100 | 2014-02-05 |
| 1 | 21640.4600 | 2014-03-11 |
| 2 | 6782.5000 | 2014-03-12 |
| 2 | 1324.6600 | 2014-05-28 |
+--------+------------+------------+
for example if i want to know all the cust who make the last purchase between
2014-02-11 and 2014-03-16, in that case the result must be
+--------+------------+------------+
| client | amt | date |
+--------+------------+------------+
| 1 | 21640.4600 | 2014-03-11 |
+--------+------------+------------+
cant be the client number 2 cause have a purchease on 2014-05-28,
i try to make a
SELECT MAX(date)
FROM table
GROUP BY client
but that only get the max of all dates,
i dont know if exist a function or something that can help, thanks.
well i dont know how to mark this question as resolved but this work for me
to complete the original query
SELECT client, MAX(date)
FROM table
GROUP BY client
HAVING MAX(date) BETWEEN date1 AND date2
thanks to all that took a minute to help me with my problem,
special thanks to Ollie Jones and Peter Pei Guo
Something in this format, replace date1 and date 2 with the real values.
SELECT client, max(date)
from table
group by client
having max(date) between date1 AND date2
There is more than one way to do this. Here is one of them.
select * from
(
select client, max(date) maxdate
from table
group by client ) temp
where maxdate between '2014-02-11' and '2014-03-06'
This will allow you to grab the amount column of the applicable rows as well:
select t.*
from tbl t
join (select client, max(date) as last_date
from tbl
group by client
having max(date) between date1 and date2) v
on t.client = v.client
and t.date = v.last_date
I had to change the field "Date" to "TheDate" since date is a reserved word. I assume you are using SQL? My table name is Table1. You need to group records:
SELECT Table1.Client, Sum(Table1.Amt) AS SumOfAmt, Table1.TheDate
FROM Table1
GROUP BY Table1.Client, Table1.TheDate
HAVING (((Table1.TheDate) Between #2/11/2014# And #3/16/2014#));
Query Results:
Client SumOfAmt TheDate
1 21640 03/11/14
2 6792 03/12/14
You may want to get yourself a copy of MS Access. You can generate SQL statements using their query builder which I used to generate this SQL. When I make a post here I will always test it first to make sure it works! I have never written even 1 line of SQL code, but have executed thousands of them from within MS Access.
Good luck,
Dan

How to group by rows but with the most recent date?

In my MySQL database I have a table like this used for storing conversation messages from any people
id int(11) id of the message
from member_id int(11) id of the person the message was sent from
to member_id int(11) id of the person the message was sent to
date sent datetime date of when it was sent
active tinyint(1) if the message is deleted
text longtext the text of the message
from_read tinyint(1) boolean to know if the person who sent it read it
to_read tinyint(1) boolean to know if the person who it got sent to read it
So for example, it could have like:
from_member_id to_member_id date sent
1 2 june 12
1 3 june 13
2 3 june 14
3 1 june 9
So we have a conversation between person 1 and 2, 1 and 3, 2 and 3.
I am trying to get a select statement which will give me the most recent message that the current user is involved with from every conversation that user is in. So if 1 is logged in then I would expect to get 2 rows. The first row in the result set would be the second row above (july 13) because its the most recent, then then the second row in the result set would be the first row above (june 12), which are the most recent from 1's two conversations. The result set also needs to be sorted by date sent, so newer conversations are listed on top.
What I am trying to do is like the texting in android phones, where you see the list of conversations, and the most recent message in each listing.
This is my sql query
SELECT *
FROM (
SELECT *
FROM message
WHERE `from member_id`=1 OR `to member_id`=1
ORDER BY IF(`from member_id`=1, `to member_id`, `from member_id`)
) as t
GROUP BY IF(`from member_id`=1, `to member_id`, `from member_id`)
I just hardcoded 1 for now to be the current user. What I am doing is, sorting them by the id of the other person which I can check using the if statement, then grouping that result so I try to get the recent one from each conversation.
The problem is that when grouping, each group can have more than 1 rows, and it just seems to pick some random row. How can I get it to pick the row that has the most recent date sent value?
Are you looking for something like this?
SELECT m.*
FROM message m JOIN
(
SELECT from_member_id, to_member_id, MAX(date_sent) date_sent
FROM message
WHERE from_member_id = 1
GROUP BY from_member_id, to_member_id
) q
ON m.from_member_id = q.from_member_id
AND m.to_member_id = q.to_member_id
AND m.date_sent = q.date_sent
ORDER BY date_sent DESC
Sample output:
| FROM_MEMBER_ID | TO_MEMBER_ID | DATE_SENT |
----------------------------------------------
| 1 | 3 | 2013-06-13 |
| 1 | 2 | 2013-06-12 |
Here is SQLFiddle demo
UPDATE
SELECT m.*
FROM message m JOIN
(
SELECT LEAST(from_member_id, to_member_id) least_id,
GREATEST(from_member_id, to_member_id) greatest_id,
MAX(date_sent) date_sent
FROM message
WHERE from_member_id = 1
OR to_member_id = 1
GROUP BY LEAST(from_member_id, to_member_id),
GREATEST(from_member_id, to_member_id)
) q
ON LEAST(m.from_member_id, m.to_member_id) = q.least_id
AND GREATEST(m.from_member_id, m.to_member_id) = q.greatest_id
AND m.date_sent = q.date_sent
ORDER BY date_sent DESC
Sample output:
| FROM_MEMBER_ID | TO_MEMBER_ID | DATE_SENT |
----------------------------------------------
| 3 | 1 | 2013-06-14 |
| 1 | 2 | 2013-06-12 |
Here is SQLFiddle demo
SELECT
*
FROM message m INNER JOIN
(
SELECT
from_menber_id,
MAX(date_sent) AS sentdate
FROM message s
GROUP BY from_menber_id
) AS a
ON m.date_sent = a.sentdate AND a.from_menber_id = m.from_menber_id

MySQL - Exclude rows from Select based on duplication of two columns

I am attempting to narrow results of an existing complex query based on conditional matches on multiple columns within the returned data set. I'll attempt to simplify the data as much as possible here.
Assume that the following table structure represents the data that my existing complex query has already selected (here ordered by date):
+----+-----------+------+------------+
| id | remote_id | type | date |
+----+-----------+------+------------+
| 1 | 1 | A | 2011-01-01 |
| 3 | 1 | A | 2011-01-07 |
| 5 | 1 | B | 2011-01-07 |
| 4 | 1 | A | 2011-05-01 |
+----+-----------+------+------------+
I need to select from that data set based on the following criteria:
If the pairing of remote_id and type is unique to the set, return the row always
If the pairing of remote_id and type is not unique to the set, take the following action:
Of the sets of rows for which the pairing of remote_id and type are not unique, return only the single row for which date is greatest and still less than or equal to now.
So, if today is 2011-01-10, I'd like the data set returned to be:
+----+-----------+------+------------+
| id | remote_id | type | date |
+----+-----------+------+------------+
| 3 | 1 | A | 2011-01-07 |
| 5 | 1 | B | 2011-01-07 |
+----+-----------+------+------------+
For some reason I'm having no luck wrapping my head around this one. I suspect the answer lies in good application of group by, but I just can't grasp it. Any help is greatly appreciated!
/* Rows with exactly one date - always return regardless of when date occurs */
SELECT id, remote_id, type, date
FROM YourTable
GROUP BY remote_id, type
HAVING COUNT(*) = 1
UNION
/* Rows with more than one date - Return Max date <= NOW */
SELECT yt.id, yt.remote_id, yt.type, yt.date
FROM YourTable yt
INNER JOIN (SELECT remote_id, type, max(date) as maxdate
FROM YourTable
WHERE date <= DATE(NOW())
GROUP BY remote_id, type
HAVING COUNT(*) > 1) sq
ON yt.remote_id = sq.remote_id
AND yt.type = sq.type
AND yt.date = sq.maxdate
The group by clause groups all rows that have identical values of one or more columns together and returns one row in the result set for them. If you use aggregate functions (min, max, sum, avg etc.) that will be applied for each "group".
SELECT id, remote_id, type, max(date)
FROM blah
GROUP BY remote_id, date;
I'm not whore where today's date comes in, but assumed that was part of the complex query that you didn't describe and I assume isn't directly relevant to your question here.
Try this:
SELECT a.*
FROM table a INNER JOIN
(
select remote_id, type, MAX(date) date, COUNT(1) cnt from table
group by remote_id, type
) b
WHERE a.remote_id = b.remote_id,
AND a.type = b.type
AND a.date = b.date
AND ( (b.cnt = 1) OR (b.cnt>1 AND b.date <= DATE(NOW())))
Try this
select id, remote_id, type, MAX(date) from table
group by remote_id, type
Hey Carson! You could try using the "distinct" keyword on those two fields, and in a union you can use Count() along with group by and some operators to pull non-unique (greatest and less-than today) records!