Query to select all rows since last success - mysql

Given my following table structure:
I'm trying to build a query that will return the number of failed login attempts per IP since the last successful login of that same IP within the past hour.
For instance, you'll notice there are several failed attempts (0) from a single IP within the past hour, but since the last successful login (#145) there has only been 1 (#146), which is what I want returned.
This query should also be dynamic and return rows of grouped IPs.
So far this is what I have, but I think #ipa is returning NULL
SELECT COUNT(*) tries, #ipa := login_ip
FROM login_log
WHERE login_id > (
SELECT MAX(login_id)
FROM login_log
WHERE login_success = 1
AND login_ip = #ipa
)
AND login_success = 0
AND login_date > NOW() - 3600
GROUP BY login_ip
ORDER BY tries DESC;
Thanks

Alias the table and try it this way, as your #ipa variable is doing nothing for you:
SELECT COUNT(1) tries, l.login_ip
FROM login_log l
WHERE login_id > (
SELECT MAX(login_id)
FROM login_log l2
WHERE l2.login_success = 1
AND l2.login_ip = l.login_ip
)
AND login_success = 0
AND login_date > NOW() - 3600
GROUP BY login_ip
ORDER BY tries DESC;
Additionally, you can do this with a join:
select
count(1) tries,
log1.login_ip
from
login_log log1
inner join (
select
login_ip,
max(login_date) as max_date
from
login_log
where
login_success = 1
group by
login_ip
) log2 on
log1.login_ip = log2.login_ip
where
log1.login_success = 0
and log1.login_date > NOW() - 3600
and log1.login_date > log2.max_date
group by
login_ip
order by tries desc
You can try both ways and see which one is faster for you.

Related

Subtracting or Adding data based on logtime of another table

So currently I have 2 tables called listings and logs table. The listings table holds a products reference number and it's current status. So suppose if it's status was Publish currently and it's sold, the status updates to Sold. Here the refno. in this table is unique since the status can change for 1 product.
Now I have another table called Logs table, this table records all the status changes that have happened for a particular product(referenced by refno) in a particular timeframe. Suppose the Product with refno. 5 was Publish on 1st October and Sold on 2nd October, The logs table will display as:
Refno
status_from
status_to
logtime
5
Stock
Publish
2021-10-01
5
Publish
Sold
2021-10-02
This is how my tables currently look like:
Listings table:('D'=>'Draft','N'=>'Action','Y'=>'Publish')
Logs Table which I'm getting using the following statement:
SELECT refno, logtime, status_from, status_to FROM (
SELECT refno, logtime, status_from, status_to, ROW_NUMBER() OVER(PARTITION BY refno ORDER BY logtime DESC)
AS RN FROM crm_logs WHERE logtime < '2021-10-12 00:00:00' ) r
WHERE r.RN = 1 UNION SELECT refno, logtime, status_from, status_to
FROM crm_logs WHERE logtime <= '2021-10-12 00:00:00' AND logtime >= '2015-10-02 00:00:00'
ORDER BY `refno` ASC
The logs table makes a new record every status change made and passes the current timestamp as the logtime, and the listings table changes/updates the status and updates its update_date. Now to get the total listings as of today I'm using the following statement:
SELECT SUM(status_to = 'D') AS draft, SUM(status_to = 'N') AS action, SUM(status_to = 'Y') AS publish FROM `crm_listings`
And this returns all the count data for status as of the current day.
Now this is where it gets confusing for me. So suppose today the count under action is 10 and tomorrow it'll be 15, and I want to retrieve the total that was present yesterday(10). So for this what I would've to do is take todays total(15) and subtract all the places where a product was changed to draft in between yesterday and today(Total count today in listing table - count(*) where status_to='Action' from logs table). Or vice versa, if yesterday it was 10 under action and today it is 5, it should add the values from the status_from column in logs table
Note: Refno isn't unique in my logs table since a product with the same refno can be marked as publish 1 day and unpublish another, but it is unique in my listings table.
Link to dbfiddle: https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=01cb3ccfda09f6ddbbbaf02ec92ca894
I am sure it can be simplifed or better. But its my query and logic :
I found status_changes per refno's and calculated total changes from the desired day to present :
select status_logs, sum(cnt_status) to_add from (
SELECT
status_to as status_logs, -1*count(*) cnt_status
FROM logs lm
where
id = (select max(id) from logs l where l.refno = lm.refno) and
logtime >= '2021-10-01 00:00:00'
group by status_to
union all
SELECT
status_from, count(*) cnt_status_from
FROM logs lm
where
id = (select max(id) from logs l where l.refno = lm.refno) and
logtime >= '2021-10-01 00:00:00'
group by status_from ) total_changes
group by status_logs
I matched the keys between listings table and logs table by converting listings table keys :
select
case status
when 'D' THEN 'Draft'
when 'A' THEN 'Action'
when 'Y' THEN 'Publish'
when 'S' THEN 'Sold'
when 'N' THEN 'Let'
END status_l ,COUNT(*) c
from listings
group by status
I joined them and add the calculations to total sum of current data.
I had to use full outer join , so i have one left and one right join with the same subqueries.
Lastly I used distinct , since it will generate same result for each joined query and used ifnull to bring the other tables status to the other column .
select distinct IFNULL(status_l, status_logs) status, counts_at_2021_10_01
from (select l.*,
logs.*,
l.c + ifnull(logs.to_add, 0) counts_at_2021_10_01
from (select case status
when 'D' THEN
'Draft'
when 'A' THEN
'Action'
when 'Y' THEN
'Publish'
when 'S' THEN
'Sold'
when 'N' THEN
'Let'
END status_l,
COUNT(*) c
from listings
group by status) l
left join (
select status_logs, sum(cnt_status) to_add
from (SELECT status_to as status_logs,
-1 * count(*) cnt_status
FROM logs lm
where id = (select max(id)
from logs l
where l.refno = lm.refno)
and logtime >= '2021-10-01 00:00:00'
group by status_to
union all
SELECT status_from, count(*) cnt_status_from
FROM logs lm
where id = (select max(id)
from logs l
where l.refno = lm.refno)
and logtime >= '2021-10-01 00:00:00'
group by status_from) total_changes
group by status_logs) logs
on logs.status_logs = l.status_l
union all
select l.*,
logs.*,
l.c + ifnull(logs.to_add, 0) counts_at_2021_05_01
from (select case status
when 'D' THEN
'Draft'
when 'A' THEN
'Action'
when 'Y' THEN
'Publish'
when 'S' THEN
'Sold'
when 'N' THEN
'Let'
END status_l,
COUNT(*) c
from listings
group by status) l
right join (
select status_logs, sum(cnt_status) to_add
from (SELECT status_to as status_logs,
-1 * count(*) cnt_status
FROM logs lm
where id = (select max(id)
from logs l
where l.refno = lm.refno)
and logtime >= '2021-10-01 00:00:00'
group by status_to
union all
SELECT status_from, count(*) cnt_status_from
FROM logs lm
where id = (select max(id)
from logs l
where l.refno = lm.refno)
and logtime >= '2021-10-01 00:00:00'
group by status_from) total_changes
group by status_logs) logs
on logs.status_logs = l.status_l) l

Get authorized user now mysql

I have table structure:
I need get last authorized user with status 'Signed in' without last status 'Signed off'. How I can do it?
I have a query:
SELECT * FROM vtiger_loginhistory WHERE status = 'Signed in' ORDER BY login_time DESC LIMIT 1
But this get me last authorized user admin, why? If he is Signed off. I need get Igor, because he is Signed in last.
You can try that: first select more recent row for each username and use JOIN or another SELECT to see if it's a "in" or "off"
EDIT: the concept is the same but corrected thanks to this answer
Version i don't fully understand: https://sqltest.net/#464377
SELECT *
FROM vtiger_loginhistory v_l
LEFT JOIN vtiger_loginhistory tmp
ON (v_l.user_name = tmp.user_name AND v_l.login_time < tmp.login_time)
WHERE tmp.login_time IS NULL AND v_l.status = 'Signed in'
ORDER BY v_l.login_time DESC LIMIT 1
Similar result with a subquery as I described (seems slower as explained in linked answer): https://sqltest.net/#464385
SELECT *
FROM vtiger_loginhistory v_l
INNER JOIN (SELECT user_name, MAX(tmp.login_time) AS maxlogintime
FROM vtiger_loginhistory tmp GROUP BY user_name
ORDER BY login_time DESC) tmp2
ON (v_l.login_time = tmp2.maxlogintime)
WHERE status = 'Signed in'
ORDER BY login_time DESC LIMIT 1
EDITED ORIGINAL NOT WORKING VERSION: (because GROUP BY keeps the first row met, so the oldest dates)
SELECT *
FROM (
SELECT *
FROM vtiger_loginhistory
GROUP BY user_name
ORDER BY login_time DESC
) as temp
WHERE status = 'Signed in'
ORDER BY login_time DESC LIMIT 1
You could check for username that is not IN the list for signed off too
SELECT *
FROM vtiger_loginhistory
WHERE status = 'Signed in'
AND username NOT IN (
select username
from vtiger_loginhistory
where status = 'Signed OFF'
)
ORDER BY login_time DESC LIMIT 1
you can try below way by using not exists
select t1.* from vtiger_loginhistory t1
where
status = 'Signed in'
and not exists ( select 1 from vtiger_loginhistory t2
where t2.user_name=t1.user_name
and status = 'Signed off')
order by STR_TO_DATE(login_time,'%Y%m%d %h%i') DESC LIMIT 1
i think your login_time column is not datetime
There are many Signed in and Signed off user, the admin has signed off but the last signed in is more recent than Igor.
There are 2 solutions for you :
Renew the flow, when user Signed off just update the status instead of create new record
Create top1signedoff temporary table first and select the other not in top1signedoff
CREATE TEMPORARY TABLE top1signedoff SELECT username FROM vtiger_loginhistory WHERE status = 'Signed off' ORDER BY login_time DESC LIMIT 1;
SELECT * FROM vtiger_loginhistory
WHERE status = 'Signed in'
and username Not IN (
SELECT username FROM top1signedoff
)
ORDER BY login_time DESC LIMIT 1
Following query should give you the desired result
SELECT t.login_id,t.user_name, t.logout_time,t.login_time,t.status
FROM vtiger_loginhistory t
WHERE t.login_id = (
SELECT MAX(t2.login_id)
FROM vtiger_loginhistory t2
WHERE t2.user_name = t.user_name
GROUP BY t2.user_name
)
AND t.status = 'Signed in';
What we are doing here..
in sub-query we are fetching last login_id for user
and in main query we are checking if the status for that login_id is 'Signed in'

How to select last and last but one records

I have a table with 3 columns id, type, value like in image below.
What I'm trying to do is to make a query to get the data in this format:
type previous current
month-1 666 999
month-2 200 15
month-3 0 12
I made this query but it gets just the last value
select *
from statistics
where id in (select max(id) from statistics group by type)
order
by type
EDIT: Live example http://sqlfiddle.com/#!9/af81da/1
Thanks!
I would write this as:
select s.*,
(select s2.value
from statistics s2
where s2.type = s.type
order by id desc
limit 1, 1
) value_prev
from statistics s
where id in (select max(id) from statistics s group by type) order by type;
This should be relatively efficient with an index on statistics(type, id).
select
type,
ifnull(max(case when seq = 2 then value end),0 ) previous,
max( case when seq = 1 then value end ) current
from
(
select *, (select count(*)
from statistics s
where s.type = statistics.type
and s.id >= statistics.id) seq
from statistics ) t
where seq <= 2
group by type

Group mysql results by type - but only in a certain order

Background information: I am working at a little, private pingdom.com clone.
I have a table with status checks if a website is available or not and the date of the check of course. Some of them have the status "ok", some "not ok". Now I want to group the rows by status, but only the one in the same "timeframe".
Example:
id status timestamp
1 ok 1234
2 ok 1235
3 not ok 1236
4 ok 1237
The query should make three groups with the ID's 1-2, 3 and 4. In the end I want do display that the tested site was 2 hours online, 1 hour offline and then 1 hour online again. Of course i could filter the results afterwards but I thought that this would be very inefficient with a large data set.
I have absolutely no idea where to start, because you can't group only by status. A short help for the search term would be sufficient, english is not my first language.
Try this query:
SELECT min( timestamp ) from_timestamp,
max( timestamp ) to_timestamp,
max( timestamp) - min( timestamp ) + 1 how_long,
min( id ) from_id,
max( id ) to_id,
status
FROM (
SELECT t.id,
t.timestamp,
if(#last_status = status, #group, #group:=#group+1) group_number,
#last_status := status as status
FROM table1 t
CROSS JOIN (
SELECT #last_status := null, #group:=0
) as init_vars
ORDER BY t.timestamp
) q
GROUP BY group_number
ORDER BY from_timestamp
demo: --> http://www.sqlfiddle.com/#!2/2aa1e/10

Checking for maximum length of consecutive days which satisfy specific condition

I have a MySQL table with the structure:
beverages_log(id, users_id, beverages_id, timestamp)
I'm trying to compute the maximum streak of consecutive days during which a user (with id 1) logs a beverage (with id 1) at least 5 times each day. I'm pretty sure that this can be done using views as follows:
CREATE or REPLACE VIEW daycounts AS
SELECT count(*) AS n, DATE(timestamp) AS d FROM beverages_log
WHERE users_id = '1' AND beverages_id = 1 GROUP BY d;
CREATE or REPLACE VIEW t AS SELECT * FROM daycounts WHERE n >= 5;
SELECT MAX(streak) AS current FROM ( SELECT DATEDIFF(MIN(c.d), a.d)+1 AS streak
FROM t AS a LEFT JOIN t AS b ON a.d = ADDDATE(b.d,1)
LEFT JOIN t AS c ON a.d <= c.d
LEFT JOIN t AS d ON c.d = ADDDATE(d.d,-1)
WHERE b.d IS NULL AND c.d IS NOT NULL AND d.d IS NULL GROUP BY a.d) allstreaks;
However, repeatedly creating views for different users every time I run this check seems pretty inefficient. Is there a way in MySQL to perform this computation in a single query, without creating views or repeatedly calling the same subqueries a bunch of times?
This solution seems to perform quite well as long as there is a composite index on users_id and beverages_id -
SELECT *
FROM (
SELECT t.*, IF(#prev + INTERVAL 1 DAY = t.d, #c := #c + 1, #c := 1) AS streak, #prev := t.d
FROM (
SELECT DATE(timestamp) AS d, COUNT(*) AS n
FROM beverages_log
WHERE users_id = 1
AND beverages_id = 1
GROUP BY DATE(timestamp)
HAVING COUNT(*) >= 5
) AS t
INNER JOIN (SELECT #prev := NULL, #c := 1) AS vars
) AS t
ORDER BY streak DESC LIMIT 1;
Why not include user_id in they daycounts view and group by user_id and date.
Also include user_id in view t.
Then when you are queering against t add the user_id to the where clause.
Then you don't have to recreate your views for every single user you just need to remember to include in your where clause.
That's a little tricky. I'd start with a view to summarize events by day:
CREATE VIEW BView AS
SELECT UserID, BevID, CAST(EventDateTime AS DATE) AS EventDate, COUNT(*) AS NumEvents
FROM beverages_log
GROUP BY UserID, BevID, CAST(EventDateTime AS DATE)
I'd then use a Dates table (just a table with one row per day; very handy to have) to examine all possible date ranges and throw out any with a gap. This will probably be slow as hell, but it's a start:
SELECT
UserID, BevID, MAX(StreakLength) AS StreakLength
FROM
(
SELECT
B1.UserID, B1.BevID, B1.EventDate AS StreakStart, DATEDIFF(DD, StartDate.Date, EndDate.Date) AS StreakLength
FROM
BView AS B1
INNER JOIN Dates AS StartDate ON B1.EventDate = StartDate.Date
INNER JOIN Dates AS EndDate ON EndDate.Date > StartDate.Date
WHERE
B1.NumEvents >= 5
-- Exclude this potential streak if there's a day with no activity
AND NOT EXISTS (SELECT * FROM Dates AS MissedDay WHERE MissedDay.Date > StartDate.Date AND MissedDay.Date <= EndDate.Date AND NOT EXISTS (SELECT * FROM BView AS B2 WHERE B1.UserID = B2.UserID AND B1.BevID = B2.BevID AND MissedDay.Date = B2.EventDate))
-- Exclude this potential streak if there's a day with less than five events
AND NOT EXISTS (SELECT * FROM BView AS B2 WHERE B1.UserID = B2.UserID AND B1.BevID = B2.BevID AND B2.EventDate > StartDate.Date AND B2.EventDate <= EndDate.Date AND B2.NumEvents < 5)
) AS X
GROUP BY
UserID, BevID