Select only one latest message from every distinct sender in php [duplicate] - php

I have a table ("lms_attendance") of users' check-in and out times that looks like this:
id user time io (enum)
1 9 1370931202 out
2 9 1370931664 out
3 6 1370932128 out
4 12 1370932128 out
5 12 1370933037 in
I'm trying to create a view of this table that would output only the most recent record per user id, while giving me the "in" or "out" value, so something like:
id user time io
2 9 1370931664 out
3 6 1370932128 out
5 12 1370933037 in
I'm pretty close so far, but I realized that views won't accept subquerys, which is making it a lot harder. The closest query I got was :
select
`lms_attendance`.`id` AS `id`,
`lms_attendance`.`user` AS `user`,
max(`lms_attendance`.`time`) AS `time`,
`lms_attendance`.`io` AS `io`
from `lms_attendance`
group by
`lms_attendance`.`user`,
`lms_attendance`.`io`
But what I get is :
id user time io
3 6 1370932128 out
1 9 1370931664 out
5 12 1370933037 in
4 12 1370932128 out
Which is close, but not perfect. I know that last group by shouldn't be there, but without it, it returns the most recent time, but not with it's relative IO value.
Any ideas?
Thanks!

Query:
SQLFIDDLEExample
SELECT t1.*
FROM lms_attendance t1
WHERE t1.time = (SELECT MAX(t2.time)
FROM lms_attendance t2
WHERE t2.user = t1.user)
Result:
| ID | USER | TIME | IO |
--------------------------------
| 2 | 9 | 1370931664 | out |
| 3 | 6 | 1370932128 | out |
| 5 | 12 | 1370933037 | in |
Note that if a user has multiple records with the same "maximum" time, the query above will return more than one record. If you only want 1 record per user, use the query below:
SQLFIDDLEExample
SELECT t1.*
FROM lms_attendance t1
WHERE t1.id = (SELECT t2.id
FROM lms_attendance t2
WHERE t2.user = t1.user
ORDER BY t2.id DESC
LIMIT 1)

No need to trying reinvent the wheel, as this is common greatest-n-per-group problem. Very nice solution is presented.
I prefer the most simplistic solution (see SQLFiddle, updated Justin's) without subqueries (thus easy to use in views):
SELECT t1.*
FROM lms_attendance AS t1
LEFT OUTER JOIN lms_attendance AS t2
ON t1.user = t2.user
AND (t1.time < t2.time
OR (t1.time = t2.time AND t1.Id < t2.Id))
WHERE t2.user IS NULL
This also works in a case where there are two different records with the same greatest value within the same group - thanks to the trick with (t1.time = t2.time AND t1.Id < t2.Id). All I am doing here is to assure that in case when two records of the same user have same time only one is chosen. Doesn't actually matter if the criteria is Id or something else - basically any criteria that is guaranteed to be unique would make the job here.

Based in #TMS answer, I like it because there's no need for subqueries but I think ommiting the 'OR' part will be sufficient and much simpler to understand and read.
SELECT t1.*
FROM lms_attendance AS t1
LEFT JOIN lms_attendance AS t2
ON t1.user = t2.user
AND t1.time < t2.time
WHERE t2.user IS NULL
if you are not interested in rows with null times you can filter them in the WHERE clause:
SELECT t1.*
FROM lms_attendance AS t1
LEFT JOIN lms_attendance AS t2
ON t1.user = t2.user
AND t1.time < t2.time
WHERE t2.user IS NULL and t1.time IS NOT NULL

Already solved, but just for the record, another approach would be to create two views...
CREATE TABLE lms_attendance
(id int, user int, time int, io varchar(3));
CREATE VIEW latest_all AS
SELECT la.user, max(la.time) time
FROM lms_attendance la
GROUP BY la.user;
CREATE VIEW latest_io AS
SELECT la.*
FROM lms_attendance la
JOIN latest_all lall
ON lall.user = la.user
AND lall.time = la.time;
INSERT INTO lms_attendance
VALUES
(1, 9, 1370931202, 'out'),
(2, 9, 1370931664, 'out'),
(3, 6, 1370932128, 'out'),
(4, 12, 1370932128, 'out'),
(5, 12, 1370933037, 'in');
SELECT * FROM latest_io;
Click here to see it in action at SQL Fiddle

If your on MySQL 8.0 or higher you can use Window functions:
Query:
DBFiddleExample
SELECT DISTINCT
FIRST_VALUE(ID) OVER (PARTITION BY lms_attendance.USER ORDER BY lms_attendance.TIME DESC) AS ID,
FIRST_VALUE(USER) OVER (PARTITION BY lms_attendance.USER ORDER BY lms_attendance.TIME DESC) AS USER,
FIRST_VALUE(TIME) OVER (PARTITION BY lms_attendance.USER ORDER BY lms_attendance.TIME DESC) AS TIME,
FIRST_VALUE(IO) OVER (PARTITION BY lms_attendance.USER ORDER BY lms_attendance.TIME DESC) AS IO
FROM lms_attendance;
Result:
| ID | USER | TIME | IO |
--------------------------------
| 2 | 9 | 1370931664 | out |
| 3 | 6 | 1370932128 | out |
| 5 | 12 | 1370933037 | in |
The advantage I see over using the solution proposed by Justin is that it enables you to select the row with the most recent data per user (or per id, or per whatever) even from subqueries without the need for an intermediate view or table.
And in case your running a HANA it is also ~7 times faster :D

Ok, this might be either a hack or error-prone, but somehow this is working as well-
SELECT id, MAX(user) as user, MAX(time) as time, MAX(io) as io FROM lms_attendance GROUP BY id;

select b.* from
(select
`lms_attendance`.`user` AS `user`,
max(`lms_attendance`.`time`) AS `time`
from `lms_attendance`
group by
`lms_attendance`.`user`) a
join
(select *
from `lms_attendance` ) b
on a.user = b.user
and a.time = b.time

I have tried one solution which works for me
SELECT user, MAX(TIME) as time
FROM lms_attendance
GROUP by user
HAVING MAX(time)

I have a very large table and all of the other suggestions here were taking a very long time to execute. I came up with this hacky method that was much faster. The downside is, if the max(date) row has a duplicate date for that user, it will return both of them.
SELECT * FROM mb_web.devices_log WHERE CONCAT(dtime, '-', user_id) in (
SELECT concat(max(dtime), '-', user_id) FROM mb_web.devices_log GROUP BY user_id
)

select result from (
select vorsteuerid as result, count(*) as anzahl from kreditorenrechnung where kundeid = 7148
group by vorsteuerid
) a order by anzahl desc limit 0,1

I have done same thing like below
SELECT t1.*
FROM lms_attendance t1
WHERE t1.id in (SELECT max(t2.id) as id
FROM lms_attendance t2
group BY t2.user)
This will also reduce memory utilization.
Thanks.

Possibly you can do group by user and then order by time desc. Something like as below
SELECT * FROM lms_attendance group by user order by time desc;

Try this query:
select id,user, max(time), io
FROM lms_attendance group by user;

This worked for me:
SELECT user, time FROM
(
SELECT user, time FROM lms_attendance --where clause
) AS T
WHERE (SELECT COUNT(0) FROM table WHERE user = T.user AND time > T.time) = 0
ORDER BY user ASC, time DESC

Related

Get data if insert are in the same second

I have this table :
id idm date_play
1 5 2017-08-23 12:12:12
2 5 2017-08-23 12:12:12
3 6 2017-08-23 12:14:13
I want to identify if user has more then one insert in the same second. In the case describe I want to get the user id that is 5.
I tried like this :
SELECT `idm`, MAX(`s`) `conseq` FROM
(
SELECT
#s := IF(#u = `idm` AND (UNIX_TIMESTAMP(`date_play`) - #pt) BETWEEN 1 AND 100000, #s + 1, 0) s,
#u := `idm` `idm`,
#pt := UNIX_TIMESTAMP(`date_play`) pt
FROM table
WHERE date_play >= '2017-08-23 00:00:00'
AND date_play <= '2017-08-23 23:59:59'
ORDER BY `date_play`
) AS t
GROUP BY `idm`
Can you help me please ? Thx in advance and sorry for my english.
Assuming your dates are accurate down to the second level, you can do this with a single aggregation:
select idm
from t
group by idm
having count(*) > count(distinct date_play);
If date_play has fractional seconds, then you would need to remove those (say by converting to a string).
If you want the play dates where there are duplicates:
select idm, date_play
from t
group by idm, date_play
having count(*) >= 2;
Or, for just the idms, you could use select distinct with group by:
select distinct idm
from t
group by idm, date_play
having count(*) >= 2;
(I only mention this because this is the only type of problem that I know of where using select distinct with group by makes sense.)
If you want all the rows that are duplicated, I would go for exists instead:
select t.*
from t
where exists (select 1
from t t2
where t2.idm = t.idm and t2.date_play = t.date_play and
t2.id <> t.id
);
This should have reasonable performance with an index on (idm, date_play, id).
If your table is called mytable, the following should work:
SELECT t.`idm`
FROM mytable t INNER JOIN mytable t2
ON t.`idm`=t2.`idm` AND t.`date_play`=t2.`date_play` AND t.`id`!=t2.`id`
GROUP BY t.`idm`
Basically we join the table with itself, pairing records that have the same idm and date_play, but not the same id. This will have the effect of matching up any two records with the same user and datetime. We then group results by user so you don't get the same user id listed multiple times.
Edit:
Gordon Linoff and tadman's suggestions led me to this probably much more efficient query (credit to them)
SELECT t.`idm`
FROM mytable t
GROUP BY t.`date_play`
HAVING COUNT(t.`id`)>1

Split record from one column to two column in MysQL or PHP

I have a problem to split data from attendance machine. The data source after export from it like this:
id name att
1 John 01/04/2015 7:59:00
1 John 01/04/2015 17:44:00
1 John 02/04/2015 7:50:00
1 John 02/04/2015 18:14
Where record (in and out) from fingerprint time save in one column. And I want to split the data to be like this:
id name in out
1 John 01/04/2015 7:59:00 01/04/2015 17:44:00
1 John 02/04/2015 7:50:00 02/04/2015 18:14:00
How to split those record into 2 column in MySQL or PHP (maybe)? Thank you.
Assuming there is only one in/out per day, it's as simple as self-joining on the date and greater time.
select t1.id, t1.name, t1.att as `in`, t2.att as `out`
from table1 t1
inner join table1 t2
on date(t1.att) = date(t2.att) and t1.id = t2.id
and t2.att > t1.att
sql fiddle demo
If you want to create a brand new table with this data, so you can get rid of the import, you just need to use this query as the input to create table, like so:
create table new_table
as
select t1.id, t1.name, t1.att as `in`, t2.att as `out`
from table1 t1
inner join table1 t2
on date(t1.att) = date(t2.att) and t1.id = t2.id
and t2.att > t1.att
You could try this one.
SELECT a.userID,a.name,min(a.att)as `in`,max(a.att) as `out`
FROM
(
SELECT userID,name,str_to_date(att,'%m/%d/%Y%T') as att,str_to_date(att,'%m/%d/%Y') as attd
FROM attendance
) as a
GROUP BY a.userID,a.attd

mySQL custom sort order ORDER BY FIELD() issues

I'm trying to sort the resutls of a SELECT statement using a custom order like so:
SELECT * FROM table ORDER BY FIELD(id,4,5,6) LIMIT 6
I was expecting to have returned rows with ids: 4,5,6,1,2,3 but instead I'm getting 1,2,3,7,8,9. What am I doing wrong?
As a side note: Prior to running this query, I'm pulling this sort order from the database using a different SELECT with a GROUP_CONCAT function like so:
SELECT group_concat(clickID ORDER BY count DESC separator ',') from table2 WHERE searchphrase='$searchphrase'
This results in the 4,5,6 which is then used in the main query. Is there a faster way to write this all in one statement?
Try it this way
SELECT *
FROM table1
ORDER BY FIELD(id, 4,5,6) > 0 DESC, id
LIMIT 6
Output:
| ID |
|----|
| 4 |
| 5 |
| 6 |
| 1 |
| 2 |
| 3 |
Here is SQLFiddle demo
There is no need of the FIELD function. That will only make things slow.
You just need to properly use the ORDER BY:
SELECT * FROM table
ORDER BY id IN (4,5,6) DESC, id
LIMIT 6
here's how to do it all in one query
SELECT DISTINCT t1.*
FROM table t1
LEFT JOIN table2 ON t1.id = t2.clickID AND t2.searchphrase='$searchphrase'
ORDER BY t2.clickID IS NULL ASC, t1.id ASC
When the LEFT JOIN finds no match, it sets the fields in t2 to NULL in the returned row. This orders by this nullness.

Count/Check first instances of entries in mysql table

id | user_id | date_tracked
---------------------------------------------
1001 | 1 | 10-10-2013
1002 | 2 | 10-10-2013
1003 | 3 | 10-10-2013
1004 | 1 | 10-11-2013
1005 | 2 | 10-12-2013
I have a table similar to this, which tracks a user and a date. I need to find how many first-time entries for users occurred on a selected date. This table is going to hold a significant amount of data, which is why I'd like to rely on a query to process this instead of a bunch of PHP loops.
i.e. on 10-11-2013, user 1 visited but not their first time, so return 0
on 10-10-2013, user 1 and 2 visited for their first time, so return 2.
Obviously, using a simple query can count the number of entries on a specified date, but what methodology will allow me to only count if the user_id is not present on a row prior.
The table is ordered by date, meaning that a more recent date should never have a smaller id than an older date.
Any ideas?? Thanks!
Finding all "first time"-date, by user:
SELECT user_id, MIN(date_tracked) AS first_date
FROM table
GROUP BY user_id
Counting "first time", for each date:
SELECT t.first_date, COUNT(*) AS nb
FROM (SELECT user_id, MIN(date_tracked) AS first_date
FROM table
GROUP BY user_id) t
GROUP BY t.first_date
In response to Filipe,
Maybe this would be more suitable?
SELECT COUNT(*)
FROM table t1
WHERE t1.date ='2013-8-27'
AND NOT EXISTS (
SELECT 1
FROM table t2
WHERE t2.user_id = t1.user_id
AND t2.id < t1.id
);
SELECT user_id, MIN(date_tracked)
FROM table_name
GROUP BY user_id
By your tags I assume you want your answer in php as well.
Just one query: shouldn't date 10-10-2013 return 3?
Anyway you can try this:
<?php
$dbConnect = #mysqli_connect($host, $user, $pass)or die();
#mysqli_select_db($dbConnect, $dbname) or die();
$query = "SELECT id FROM tablename WHERE date_tracked = '$searchDate' AND (SELECT COUNT(id) FROM tablename WHERE date_tracked < '$searchDate')=0";
$queryResult = #mysqli_query($dbConnect, $query) or die();
$rowCount = mysqli_num_rows($queryResult);
echo $rowCount . "<br/>";
mysqli_close($dbConnect);
?>
For counting as one, if the user visits multiple times in the first day, you can do it in two ways:
SELECT COUNT(distinct user_id)
FROM user t1
WHERE t1.date_tracked = '2013-10-12'
AND NOT EXISTS (
SELECT 1
FROM user t2
WHERE t2.user_id = t1.user_id
AND t2.date_tracked < t1.date_tracked
);
SELECT COUNT(user_id)
FROM user t1
WHERE t1.date_tracked ='2013-10-12'
AND NOT EXISTS (
SELECT 1
FROM user t2
WHERE t2.user_id = t1.user_id
AND t2.id < t1.id
);
I think i prefer the second, as it is much more cleaner by comparing ID's and not having to do the distinct.
sqlfiddle demo

Select Query with MAX

i've got a big Problem and i was trying the whole day and did not find any Solution. Hope you can help me?
I have two tables:
The first one named "orders":
orders_id | orders_date | .....
1 xxxxxxxxxx
2 xxxxxxxxxx
3 xxxxxxxxxx
The second is "orders_history":
orders_id | order_status_id | date_added
1 1 2009-10-01
1 2 2010-01-01
2 1 2010-02-01
3 1 2010-02-01
So now i want to have all orders where order_status_id = '1'
I have tried with MAX, HAVING, GROUP BY, ... Subselects also, but i haven't found any solution. I know it's not very hard, but i'm finished...
Is it something like:
SELECT orders.*, orders_history.* FROM orders, orders_history WHERE orders_history.order_status_id <= '1'
But then i also get Order with order_id 1
Hope you can help. Thank you!
Sascha
To further clarify, the poster's 'orders_history' table keeps track of the state of all orders over time. The goal is a query that will find all orders that currently have an order status of 1. Order ID# 1 currently has a status of 2, so it should not be included in the results.
Assumably, order status goes up over time and never goes down, so that the order status and date_added will constantly increase.
This should do it for you:
SELECT *
FROM orders
, orders_history
WHERE orders.orders_id = orders_history.orders_id
AND orders.orders_id IN (
SELECT orders_id
FROM orders_history
GROUP BY orders_id
HAVING MAX(order_status_id) = 1
)
I'm not surprised you had trouble getting this to work - it's a very tricky type of query where you must 'GROUP BY' and find the MAX and also all the other corresponding values in the same row. This is a common request, and it often surprises people that it's actually quite difficult to express this in SQL. Here's one way to do it in MySQL:
SELECT T2.orders_id FROM (
SELECT orders_id, MAX(date_added) AS date_added
FROM orders_history
GROUP BY orders_id
) AS T1
JOIN orders_history T2
ON T1.orders_id = T2.orders_id AND T1.date_added = T2.date_added
GROUP BY T2.orders_id, T2.date_added
HAVING MAX(order_status_id) = 1
Here I am assuming that:
orders_id, date_added is not unique.
orders_id, date_added, order_status_id is unique.
If not the second assumption is not true, add DISTINCT after the first SELECT.
Here are the results I get for your test data:
2
3
You can join this to your orders table if you want to fetch extra information about each order.
Edited after discussion in comments (changed the where clause):
SELECT orders.*, orders_history.*
FROM orders INNER JOIN orders_history
ON orders.orders_id = orders_history.orders_id
WHERE orders.orders_id IN
(SELECT orders_id FROM orders_history
GROUP BY orders_id
HAVING MAX(order_status_id) = 1)
select o.*, oh.*
from orders o
inner join orders_history oh on oh.orders_id = o.orders_id
where oh_orders_status = 1
should do the trick. It's a while since I touched mysql though, so I don't know if your orders_status should be in quotes - I'd guess not if it is an int...

Categories