Highest entry from group by and compare to another value using having - mysql

This is what I want:
Look if the highest created from the same FK is more than X days old.
This is how the data is structured (this is not the result from the query below):
table_1
id | FK_table_2 | created
-------------------------------------------------------
1 | 20 | 2013-11-12 12:13:14
2 | 20 | 2013-11-12 11:10:12
3 | 21 | 2013-10-02 12:53:20
4 | 21 | 2013-09-02 12:54:20
Note: Doing a subquery will be to slow.
What I come up with is:
SELECT *
FROM table_1
GROUP BY FK_table_2
HAVING MAX(created) < NOW() - INTERVAL 3 DAY
I'm worried that HAVING MAX(created) has not garantuee to use the highest created.
Is there any other ways to do this?

I don't think you need to use a MAX clause, you can surely just select all rows older than 3 days and do your work on them.
SELECT DISTINCT FK_table_2 FROM table_1
WHERE created > NOW() - INTERVAL 3 DAY;
Update this won't work :-(

try this
SELECT FK_table_2,max(created)
FROM table_1
where created < NOW() - INTERVAL 3 DAY
GROUP BY FK_table_2

Related

SQL comparing column changes the last X hours

I have a table containing log entries that looks like this:
id | name | level | timestamp
5 | ironman | 35 | 2019-01-06 11:37:40
6 | ironman | 35 | 2019-01-06 11:38:40
7 | ironman | 36 | 2019-01-06 11:39:40
8 | superman | 25 | 2019-01-06 11:39:49
I need help making a query that returns a list of levels gained the last X hours for each character, preferably sorted by amount gained.
So by this example my wanted result is this
id | name | gained | timestamp
7 | ironman | 1 | 2019-01-06 11:37:40
8 | superman | 0 | 2019-01-06 11:37:40
You need to join the main table with a query that calculates the change in levels:
select levels.id, t.name, t.gained, t.timestamp
from (
select
name,
max(level) - min(level) gained,
max(timestamp) timestamp
from levels
where timestamp > now() - interval 10 hour
group by name
) t inner join levels
on
t.timestamp = levels.timestamp
and
t.name = levels.name
where levels.timestamp > now() - interval 10 hour
order by t.gained desc, t.name
I guess the timestamp in the expected output you posted about superman is wrong and it should be 2019-01-06 11:39:49.
See the demo
Try an aggregate query that compares the MIN and MAX level of each character :
SELECT
name,
MAX(level) - MIN(level) as gained,
MIN(timestamp)
FROM mytable
WHERE timestamp > NOW() - INTERVAL 1 HOUR
GROUP BY
name
ORDER BY
gained desc
NB : this assumes that the level of a character may only increase. If the level of a character decreases, it will still appear as a gain.

How can I return new IDs in accordance with a date?

I have a table with the following data (merely an example, actual table has 600,000 rows) (aid = access id [primary key] and id = user id [foreign key]):
aid | id | date
332 | 1 | 2016-12-15
331 | 4 | 2016-12-15
330 | 3 | 2016-12-15
329 | 1 | 2016-12-14
328 | 1 | 2016-12-14
327 | 2 | 2016-12-14
326 | 3 | 2016-12-13
325 | 2 | 2016-12-13
324 | 1 | 2016-12-13
323 | 1 | 2016-12-12
322 | 3 | 2016-12-12
321 | 1 | 2016-12-12
Each id is a users primary key, and every time they access something in my system I log them in this table (with the date in the format as shown, and their id). A user can be logged multiple times a day.
I'm looking to: return the total number of times the thing has been accessed in a day and return the total number of NEW users who have accessed the thing in a day, for the last 8 days (something will always be logged each day, so using "LIMIT 8" is fine for getting only the last 8 days).
My SQL currently looks like:
SELECT COUNT(id), COUNT(distinct id), date
FROM table
GROUP BY date
ORDER BY date DESC
LIMIT 8;
That SQL does the first part correctly, but I can't figure out how to get it to return the number of users who have never accessed the thing until that day.
Desired results would be, the one "newuser" represents the user with id "4" as they have never accessed the thing before:
COUNT(id) | newusers | date
3 | 1 | 2016-12-15
3 | 0 | 2016-12-14
3 | 0 | 2016-12-13
3 | 0 | 2016-12-12
Sorry if I didn't explain this clear enough.
To get new users you want the first day an id appeared:
select id, min(date)
from t
group by id;
The rest is just a join and group by:
select d.date, cnt, count(dd.id) as newusers
from (select date, count(*) as cnt
from t
group by date
) d left join
(select id, min(date) as mindate
from t
group by id
) dd
on d.date = dd.mindate
group by d.date, d.cnt
limit 8;
To get the number of new users you need to compare them to a set of ids over the past 8 days
My MySQL is a bit rusty, so you might have to correct the syntax.
SELECT COUNT(id)
FROM table
WHERE id NOT IN (
SELECT DISTINCT id
FROM table
WHERE date BETWEEN DATE(DATE_SUB(NOW(), INTERVAL 8 DAY)) AND DATE(DATE_SUB(NOW(), INTERVAL 1 DAY))
)
I'll leave it as a task for you to combine it with your other query ;)
Hi if your date column in database is datetime/date or other date representing format you can do something like this:
for getting all users who accessed something in 8 days:
Select id, date from table
where date BETWEEN DATE_ADD(NOW(), INTERVAL -9 DAY) AND NOW()
I think, you can do whatever grouping you want on that.
To get new users, you can either go with self join or with sub select
selfjoin:
select t.id, t.date from table as t
LEFT join table as t2
ON t.id = t2.id
AND t.date BETWEEN DATE_ADD(NOW(), INTERVAL -1 DAY) AND NOW()
AND t2.date NOT BETWEEN DATE_ADD(NOW(), INTERVAL -9 DAY) AND NOW()
WHERE t2.id IS NULL
i used left join to match all access from users and then in where excluded those rows. However self joins are slow, and even slower with LEFT join
subselect:
select id, date from table
where date BETWEEN DATE_ADD(NOW(), INTERVAL -1 DAY) AND NOW()
AND id NOT IN (
SELECT id FROM table
WHERE date BETWEEN DATE_ADD(NOW(), INTERVAL -2 DAY) AND DATE_ADD(NOW(), INTERVAL -1 DAY)
)
I know those betweens with date_adds are not exactly nice looking, but i hope it will help you more than grouping dates
I would suggest using date with time for more information, but its entirely up to meaning of yours data

How can I combine two sql with not exists?

I doubt it can be right to solve.
test_table:
id | name | value | date
---------|---------|---------|------------
1 | john | 32 | 2016-01-08
2 | tom | 590 | 2016-01-03
3 | king | 1903 | 2016-01-01
4 | john | 490 | 2016-01-02
5 | gary | 58 | 2016-01-18
6 | cat | 5 | 2016-01-10
sql1:
select name,sum(value) as val from test_table where val > 500 group by name;
sql2:
select name from test_table where date >= DATE_SUB(CURDATE(),INTERVAL 1 WEEK) group by name;
I want combine two sql in one. The sql result name is not exists in sql2 name result collection.
update:
sorry for my confused describe.
The sql1 is scan whole the table, it aim to find out who's total value is greater than 500.
The sql2 is scan last week's data to find out who exists last week.
So, I Want combine two sql , to find out those people who's total value is greater than 500 but not exists last week.
sorry again for my poor English.
update - add example:
just like the table content,the result should be :
john,tom,king
because their total value is > 500,but not update last week
But how can I do it.
Thanks all.
SELECT name, sum(value)
FROM test_table
WHERE value > 500
AND name NOT IN (
SELECT name
FROM test_table
WHERE `date` >= DATE_SUB(CURDATE(), INTERVAL 1 WEEK)
)
GROUP BY 1
OR
SELECT name, sum(value)
FROM test_table
WHERE value > 500
GROUP BY 1
HAVING max(`date') < DATE_SUB(CURDATE(), INTERVAL 1 WEEK)
If you are trying to having sum(value) > 500, delete above lines
WHERE value > 500
And change AND to WHERE for the first SQL
And add this line at end of each of above:
having sum(value) > 500
It is really helpful to describe what you want, as well as providing SQL samples. You seem to want the sum of values greater than 500 for names that have no row with date in the past week.
If so:
select name, sum(case when value > 500 then value else 0 end)
from test_table
where value > 500
group by name
having max(date) < DATE_SUB(CURDATE(), INTERVAL 1 WEEK) ;

MySQL: Calculate 4 Week Average with 4 Week Offset

This question is related to a post I made earlier: MySQL: Calculating Data from Table with One Month Offset
But now I need to build a procedure that queries a table of contact data stored by week. Here's a simplified example of the table I am working with:
+-----------------+------------+
| week_start_date | contacts |
+-----------------+------------+
| 2015-03-08 | 12 |
| 2015-03-01 | 20 |
| 2015-02-22 | 5 |
| 2015-02-15 | 17 |
| 2015-02-08 | 8 |
| 2015-02-01 | 2 |
| 2015-01-25 | 16 |
| 2015-01-18 | 10 |
| 2015-01-11 | 4 |
| ... | ... |
+-----------------+------------+
What I need to figure out is how to calculate a 4 week moving average that also has a 4 week offset on top of that. For instance, if I wanted to get the average contacts for the week of March 8, 2015, it would be the average of January 18 through February 8. In the example above, my average would be: (10 + 16 + 2 + 8 ) / 4 = 9. And if I wanted to find the average for the week of March 1, 2015, then it would be the average of January 11 through February 1 which comes out to be 8 using the sample table above.
From my last post, I know that I can handle the 4 week offset by joining the table with itself on the week_start_date similar to this:
SELECT s1.week_start_date, s2.Total_Contacts
FROM sample_table s1
LEFT JOIN (SELECT week_start_date, sum(contacts) AS Total_Contacts
FROM sample_table
GROUP BY week_start_date) s2
ON s1.week_start_date =
date_add(s2.week_start_date, INTERVAL 4 WEEK)
WHERE s1.week_start_date = '2015-03-08'
GROUP BY s1.week_start_date;
But getting it to compute the four week average as well is where I am stuck. I thought joining it on a range of dates would work, but I keep getting averages that are a lot larger than expected. I'm guessing it is due to how the week_start_date's are being grouped. (Note that there can be multiple records for each week. I only show one record for each week on the sample table to make it less cluttered.)
Is joining on a date range the correct approach? Or do I need to add another join somewhere?
Thanks for your help.
I would suggest using a correlated subquery:
select st.*,
(select avg(contacts)
from sample_table st2
where st2.week_start_date >= st.week_start_date - interval 7 * 7 days and
st2.week_start_date <= st.week_start_date - interval 4 * 7 days
) as avg_4week_delayed
from sample_table st;
I would use the DATE_SUB() function and just subtract the necessary weeks you need. So, for March 8 in your example, try something like this:
SELECT AVG(contacts)
FROM myTable
WHERE week_start_date <= DATE_SUB('2015-03-08', INTERVAL 4 WEEK) AND week_start_date >= DATE_SUB('2015-03-08', INTERVAL 7 WEEK);
It worked in SQL Fiddle.

Mysql query to get count for last week

I'm trying to get a count from last week visits.
Up to now, i have this working:
SELECT FROM_UNIXTIME(login),COUNT(*)
FROM users
WHERE FROM_UNIXTIME(login) >= (CURDATE() - INTERVAL DAYOFWEEK(CURDATE())+6 DAY)
GROUP BY DAYOFWEEK(FROM_UNIXTIME(login));
Results:
+----------------------+----------+
| FROM_UNIXTIME(login) | COUNT(*) |
+----------------------+----------+
| 2013-04-08 12:49:04 | 1 |
| 2013-04-10 17:29:21 | 2 |
| 2013-04-05 21:27:00 | 1 |
+----------------------+----------+
Problems:
-Table is not ordered by date;
-I'd like to show all 7 rows, even if count value='0'.
How can i fix this? Thanks in advance!
[UPDATE]
Order by date solved with:
ORDER BY FROM_UNIXTIME(login);
Just need to show all 7 rows of week!
You can use the query from this answer which can generate a list of dates, and then left join your query against it:
generate days from date range