Mysql query to get latest data using date - mysql

This is my table. Now i have to find out the latest company_name of each user_id. The fact is that, if exp_to is null then it will be considered latest, or if both exp_from & exp_to are not null then i have to find the latest company name using exp_to date. Note that, user_id is appearing multiple times in the given table.
Here is what i want:
id | company_name
-----------------
14 | Deltalife Insurance Company Ltd.
1 | Orbital Software
25 | MMTV
18 | Noakhali Science & Technology University
and so on.....
How can i do it with mysql? Thanks in advance.

This is pretty straightforward aggregation problem. As you have likely figured out, nulls cause some interesting behavior in SQL. You can use CASE WHEN to check for null and replace with a different value.
In your case:
SELECT
data.*
FROM (
SELECT
*,
CASE WHEN exp_to is null THEN 1 ELSE exp_to END max_date
FROM your_table
) data
INNER JOIN (
SELECT
user_id,
CASE WHEN MAX(exp_to is null) = 1 THEN 1 ELSE MAX(exp_to) END max_date
FROM
your_table
GROUP BY user_id
) j
ON data.user_id = j.user_id AND data.max_date = j.max_date
Hope this helps, and good luck!

With whatever information you have provided, I can figure-out this query
Select * from Table_Name where exp_to = null || exp_to = (select max(exp_to) from Table_Name)

This is an application for the structured part of Structured query language.
First, you need to know which date is the latest for each user_id. This subquery yields that information:
SELECT user_id,
MAX(IFNULL(NOW(), exp_to) exp_to
FROM mytable
GROUP BY user_id
Then, you need to use that result to find the company, by joining it to your table.
SELECT a.user_id, a.company_name
FROM mytable a
JOIN (
SELECT user_id,
MAX(IFNULL(NOW(), exp_to) exp_to
FROM mytable
GROUP BY user_id
) b ON a.user_id = b.user_id AND IFNULL(NOW(), a.exp_to) = b.exp_to
That should locate the company name associated with the most recent exp_to for each user_id. IFNULL(NOW(),exp_to) implements your requirement that a NULL date be considered as if it were the present time.
This query is an example of a general pattern of queries: Find the rows with extreme (max,min) values of a particular values.

Related

MySQL - Group By Latest and Join First Instance

I've tried a few things but I've ended up confusing myself.
What I am trying to do is find the most recent records from a table and left join the first after a certain date.
An example might be
id | acct_no | created_at | some_other_column
1 | A0001 | 2017-05-21 00:00:00 | x
2 | A0001 | 2017-05-22 00:00:00 | y
3 | A0001 | 2017-05-22 00:00:00 | z
So ideally what I'd like is to find the latest record of each acct_no sorted by created_at DESC so that the results are grouped by unique account numbers, so from the above record it would be 3, but obviously there would be multiple different account numbers with records for different days.
Then, what I am trying to achieve is to join on the same table and find the first record with the same account number after a certain date.
For example, record 1 would be returned for a query joining on acct_no A0001 after or equal to 2017-05-21 00:00:00 because it is the first result after/equal to that date, so these are sorted by created_at ASC AND created_at >= "2017-05-21 00:00:00" (and possibly AND id != latest.id.
It seems quite straight forward but I just can't get it to work.
I only have my most recent attempt after discarding multiple different queries.
Here I am trying to solve the first part which is to select the most recent of each account number:
SELECT latest.* FROM my_table latest
JOIN (SELECT acct_no, MAX(created_at) FROM my_table GROUP
BY acct_no) latest2
ON latest.acct_no = latest2.acct_no
but that still returns all rows rather than the most recent of each.
I did have something using a join on a subquery but it took so long to run I quite it before it finished, but I have indexes on acct_no and created_at but I've also ran into other problems where columns in the select are not in the group by. I know this can be turned off but I'm trying to find a way to perform the query that doesn't require that.
Just try a little edit to your initial query:
SELECT latest.* FROM my_table latest
join (SELECT acct_no, MAX(created_at) as max_time FROM my_table GROUP
BY acct_no) latest2
ON latest.acct_no = latest2.acct_no AND latest.created_at = latest2.max_time
Trying a different approach. Not sure about the performance impact. But hoping that avoiding self join and group by would be better in terms of performance.
SELECT * FROM (
SELECT mytable1.*, IF(#temp <> acct_no, 1, 0) selector, #temp := acct_no FROM `mytable1`
JOIN (SELECT #temp := '') a
ORDER BY acct_no, created_at DESC , id DESC
) b WHERE selector = 1
Sql Fiddle
you need to get the id where max date is created.
SELECT latest.* FROM my_table latest
join (SELECT max(id) as id FROM my_table GROUP
BY acct_no where created_at = MAX(created_at)) latest2
ON latest.id = latest2.id

MySQL Query to count values

I got a table on my database that contains Book ISBNs and Suppliers:
ISBN | Supplier
12345 | iSupply
12345 | disal
13333 | disal
14444 | iSupply
15555 | disal
16666 | emporio
I need to make a query that shows the count of ISBN that appears only in one supplier. In this example the query should show:
iSupply (1)
disal (2)
emporio (1)
since ISBN 12345 appears both on iSupply and disal it should not count.
Is it possible to do that ?
EDIT: I'm trying to use some of the solutions you guys posted but it keeps loading for 5-10 with no results. The database has 100-150k records, is this a problem ? Thank you for answers
SELECT x.supplier
, COUNT(*)
FROM my_table x
LEFT
JOIN my_table y
ON y.isbn = x.isbn
AND y.supplier <> x.supplier
WHERE y.isbn IS NULL
GROUP
BY x.supplier;
Group by the SUPPLIER and get the count of each group. The inner query filters out ISBN that are in multiple SUPPLIERs
select supplier, count(*)
from your_table
where isbn not in
(
select isbn
from your_table
group by isbn
having count(*) > 1
)
group by supplier
OK, I am obviously a bit late with my answer, but it works:
SELECT suppl,count(*) cnt FROM tmp
WHERE isbn IN (SELECT isbn FROM tmp GROUP BY isbn HAVING COUNT(*)=1)
GROUP BY suppl
The subquery in the where clause returns only isbns that appear uniquely, then the outer select groups together the counts for each supplier.
Yeah, I just looked up and discovered that it is almost the same anser as "juergen d"'s but it was written independently.
Or, "a variation on the theme":
SELECT suppl,count(*) cnt FROM tmp t
WHERE not exists (SELECT 1 FROM tmp WHERE isbn=t.isbn AND suppl!=t.suppl )
GROUP BY suppl
In the end it is a matter of taste. ;-)

Grouping users by group ids in mysql, exclude specified userid from the results?

There is a small application that I've been tasked on, that deals with getting latest posts in a group. In this sample below, I have there is a MySQL table formatted as such:
groupid userid date_updated
1 1 [date]
1 2 [date]
2 1 [date]
2 2 [date]
2 3 [date]
...
How do I do an SQL statement as such as the results go out in this manner (assuming I give a userid with a value of 1 for example):
groupid userid date
1 2 [date]
2 2 [date]
2 3 [date]
These are all ordered by date. As you may have noticed, the results do not include the provided userid (as the requirement is only to get users other than the supplied user ID). In other words, show only users other than the specified user in groups where the specified user is part of.
Is it possible to do this in a single SQL statement?
Search select query with where
select * from table where userid != '1'
Try the following solution.
select
tbl.*
from
tbl INNER JOIN
(select groupid, userid, max(date_updated)
from tbl
group by groupid, userid) tbl2
USING(groupid, userid)
ORDER BY tbl.date_updated;
You can use this
SELECT tbl.* FROM (SELECT * FROM tablename ORDER BY date DESC) as tbl GROUP BY tbl.groupid
I managed to find a possible answer to my question here with this SQL statement:
SELECT a.groupid, a.userid, a.date_updated
FROM group_participants a
WHERE a.groupid IN (
SELECT DISTINCT b.groupid FROM group_participants b WHERE b.userid = 1
)
AND a.user_id <> 1
GROUP BY a.userid
ORDER by a.date_updated DESC
Thank you guys those SQL statements you posted, gave me an idea. I don't know if the SQL statement above can still be optimized, but this one above gave me the correct answer.

How can I write a query that aggregate a single row with latest date among multiple set of rows?

I have a MySQL table where there are many rows for each person, and I want to write a query which aggregates rows with special constraint. (one per person)
For example, lets say the table is consist of following data.
name date reason
---------------------------------------
John 2013-04-01 14:00:00 Vacation
John 2013-03-31 18:00:00 Sick
Ted 2012-05-06 20:00:00 Sick
Ted 2012-02-20 01:00:00 Vacation
John 2011-12-21 00:00:00 Sick
Bob 2011-04-02 20:00:00 Sick
I want to see the distribution of 'reason' column. If I just write a query like below
select reason, count(*) as count from table group by reason
then I will be able to see number of reasons for this table overall.
reason count
------------------
Sick 4
Vacation 2
However, I am only interested in single reason from each person. The reason that should be counted should be from a row with latest date from the person's records. For example, John's latest reason would be Vacation while Ted's latest reason would be Sick. And Bob's latest reason (and the only reason) is Sick.
The expected result for that query should be like below. (Sum of count will be 3 because there are only 3 people)
reason count
-----------------
Sick 2
Vacation 1
Is it possible to write a query such that single latest reason will be counted when I want to see distribution(count) of reasons?
Here are some facts about the table.
The table has tens of millions of rows
For most of times, each person has one reason.
Some people have multiple reasons, but 99.99% of people have fewer than 5 reasons.
There are about 30 different reasons while there are millions of distinct names.
The table is partitioned based on date range.
SELECT T.REASON, COUNT(*)
FROM
(
SELECT PERSON, MAX(DATE) AS MAX_DATE
FROM TABLE-NAME
GROUP BY PERSON
) A, TABLE-NAME T
WHERE T.PERSON = A.PERSON AND T.DATE = A.MAX_DATE
GROUP BY T.REASON
Try this
select reason, count(*) from
(select reason from table where date in
(select max(date) from table group by name)) t
group by reason
In MySQL, it's not very efficient to do this kind of query since you don't have access to tools like partitionning query in SQL Server or Oracle.
You can still emulate it by doing a subquery and retrieve the rows based on the condition you need, here the maximum date :
SELECT t.reason, COUNT(1)
FROM
(
SELECT name, MAX(adate) AS maxDate
FROM #aTable
GROUP BY name
) maxDateRows
INNER JOIN #aTable t ON maxDateRows.name = t.name
AND maxDateRows.maxDate = t.adate
GROUP BY t.reason
You can see a sample here.
Test this query on your samples, but I'm afraid that it will be slow as hell.
For your information, you can do the same thing in a more elegant and much much faster way in SQL Server :
SELECT reason, COUNT(1)
FROM
(
SELECT name
, reason
, RANK() OVER(PARTITION BY name ORDER BY adate DESC) as Rank
FROM #aTable
) AS rankTable
WHERE Rank = 1
GROUP BY reason
The sample is here
If you are really stuck to MySql, and the first query is too slow, then you can split the problem.
Do a first query creating a table:
CREATE TABLE maxDateRows AS
SELECT name, MAX(adate) AS maxDate
FROM #aTable
GROUP BY name
Then create index on both name and maxDate.
Finally, get the results :
SELECT t.reason, COUNT(1)
FROM maxDateRows m
INNER JOIN #aTable t ON m.name = t.name
AND m.maxDate = t.adate
GROUP BY t.reason
The solution you are looking for seems to be solved by this query :
select
reason,
count(*)
from (select * from tablename group by name) abc
group by
reason
It is quite fast and simple. You can view the SQL Fiddle
Apologies if this answer duplicates an existing. Maybe I'm suffering from some form aphasia but I cannot see it...
SELECT x.reason
, COUNT(*)
FROM absentism x
JOIN
( SELECT name,MAX(date) max_date FROM absentism GROUP BY name) y
ON y.name = x.name
AND y.max_date = x.date
GROUP
BY reason;

How do I select only the most recent record based on a userid that can be in either of two columns?

I have a messages table that is structured somewhat like this:
from | to | date
-----------------------------------
1 | 3 | 2011-09-23 11:51:44
3 | 1 | 2011-09-23 11:56:29
3 | 2 | 2011-10-04 10:20:01
2 | 3 | 2011-10-05 07:48:00
I want to display a messages page on my website very similar to Facebook's messages page, which shows a list of the people with which the user has a conversation. Regardless of the depth of the conversation, the page only shows each person once, along with the most recent message in that conversation with that particular person, whether it was sent or received.
The part that stumps me is that the most recent message can be either sent or received, which means that the user's ID number can be in either the from or to column. I'm not sure how to test against both columns the way I need to.
I'm still learning how to write more complex MySQL queries, and while I feel like this is a simple case of using OR with subqueries, I can't seem to get it right.
SOLUTION
Turns out it wasn't really a very simple case at all. Widor took some time to help me out with this, and the following query finally seems to do the job. It hasn't been tested thoroughly, but so far it seems to work fine:
SELECT m.*
FROM messages m
JOIN (SELECT Max(x.id) AS `id`,
x.userid,
x.partnerid,
Max(x.mostrecent) AS `mostrecent`
FROM (SELECT Max(id) `id`,
`from` AS `userid`,
`to` AS `partnerid`,
Max(`created`) AS `mostrecent`
FROM messages
GROUP BY `from`,
`to`
UNION
SELECT Max(id) `id`,
`to` AS `userid`,
`from` AS `partnerid`,
Max(`created`) AS `mostrecent`
FROM messages
GROUP BY `to`,
`from`) AS `x`
GROUP BY x.`userid`,
x.`partnerid`) AS `y`
ON y.id = m.id
WHERE y.userid = $userid
RE-EDITED
My previous answer (as some other ones here) didn't take account of the case where you have more than 2 conversation 'partners', which isn't the case in your example data but I'm sure will be in real life.
So consider the case where you now have an extra record in the data:
1 | 4 | 2011-10-04 08:34:12
My revised query is as follows:
SELECT userid, partnerid, max(mostRecent) from (
SELECT [from] as [userid], [to] as [partnerid], max([date]) as [mostrecent] FROM messages GROUP BY [from], [to]
UNION
SELECT [to] as [userid], [from] as [partnerid],max([date]) as [mostrecent] FROM messages GROUP BY [to], [from]
) [x]
WHERE userid = ?
GROUP BY userid, partnerid
So, our inner UNION gives us a dataset containing the userid along with a partnerid, i.e. who they are chatting to. This could be created separately as a view if you wanted, to simplify the query.
The outer SELECT then retrieves one record for every 'partner' that the specified userid has had a chat with, along with the most recent date.
The Max() function achieves the most recent date, and the GROUP BY takes care of ensuring we bring back more then one record for each partner.
Sounds like you want something like:
SELECT MAX(date), * FROM messages WHERE to IN (
SELECT DISTINCT to FROM messages WHERE from = :id
) GROUP BY to
UNION
SELECT MAX(date), * FROM messages WHERE from IN (
SELECT DISTINCT from FROM messages WHERE to = :id
) GROUP BY from
ORDER BY 1
Where id is the parameter you're using for the user ID.
you can create a view and query it:
CREATE VIEW dataview AS
(SELECT t1.from AS userid, t1.date FROM messages AS t1)
UNION
(SELECT t2.to AS userid, t2.date FROM messages AS t2)
look at the result table, it should now be very easy to query what you want
I think you probably want something like this:
SELECT MAX(date)
FROM messages
WHERE from = $userid
OR to = $userid
You would replace $userid with the actual value, hopefully using prepared statements. :-)