Update oldest rows based on sum of column - mysql

We are having a following MySQL table to maintain user credits.
id user credits expiry status
-----------------------------------------
1 A 1.2 somedatetime 0
2 A 4.4 somedatetime 0
3 A 5.0 somedatetime 0
4 B 1.0 somedatetime 0
5 B 2.4 somedatetime 0
6 C 7.8 somedatetime 0
Whenever user makes a purchase, we deduct the amount from the available credits. To be fair to user, the credits with nearest expiry will be consumed first and so on. We will also update the status to mark row as consumed.
For example, if user A makes a purchase of $2, $1.2 will be debited from id 1 and remaining $0.8 from id 2 and so on. So Table will now look like
id user credits expiry status
-----------------------------------------
1 A 0.0 somedatetime 1
2 A 3.6 somedatetime 1
3 A 5.0 somedatetime 0
4 B 1.0 somedatetime 0
5 B 2.4 somedatetime 0
6 C 7.8 somedatetime 0
So far we have been doing it with brute-force approach. Any idea suggestion how to do it more efficiently in minimum or single query.
Update: since someone asked about our current brute-force approach, it's iterating each row from the oldest and updating till the purchase amount is covered, which is very inefficient.
Thanks

Using variables you calculate the totals credits. Run the inner query so you learn what is beign calculate first.
Fiddle Demo
UPDATE customer c
JOIN (
SELECT cu.`id`,
cu.`user`,
`credits`, `expiry`, `status`,
#total := IF(#customer = cu.`user`, #total := #total + `credits`, `credits`) as cumulative_total,
#customer := cu.`user` as user_current,
`credit_used`
FROM customer cu
CROSS JOIN (SELECT #customer := '', #total := 0 ) t
JOIN credits
ON cu.`user` = credits.`user`
ORDER BY cu.`id`
) t
ON c.`id` = t.`id`
SET c.credits = CASE WHEN c.credits <= t.credit_used THEN 0
ELSE t.cumulative_total - credit_used
END,
c.status = CASE WHEN c.credits <= t.credit_used THEN 1
ELSE 0
END;
My test Setup:
CREATE TABLE customer
(`id` int, `user` varchar(1), `credits` double, `expiry` int, `status` int)
;
INSERT INTO customer
(`id`, `user`, `credits`, `expiry`, `status`)
VALUES
(1, 'A', 1.2, 1, 0),
(2, 'A', 4.4, 2, 0),
(3, 'A', 5.0, 3, 0),
(4, 'B', 1.0, 4, 0),
(5, 'B', 2.4, 5, 0),
(6, 'C', 7.8, 6, 0)
;
CREATE TABLE credits
(`id` int, `user` varchar(1), `credit_used` double)
;
INSERT INTO credits
(`id`, `user`, `credit_used`)
VALUES
(1, 'A', 2.0),
(2, 'B', 3.4)
;

http://sqlfiddle.com/#!9/485673/1
SET #amount = 2;
UPDATE t1
JOIN (
SELECT t2.id,
IF(#amount=0,t2.credits, IF(#amount>t2.credits,0,t2.credits-#amount)) credits,
IF(#amount>=t2.credits,#amount := #amount-t2.credits, 0)
FROM (
SELECT id, credits
FROM t1
WHERE credits>0 AND `user`='A'
ORDER BY expiry ASC
) t2
) t
ON t1.id = t.id
SET t1.credits=t.credits
WHERE t1.user = 'A';

Related

Displayed values are not what they should be

There are 2 tables ost_ticket and ost_ticket_action_history.
create table ost_ticket(
ticket_id int not null PRIMARY KEY,
created timestamp,
staff bool,
status varchar(50),
city_id int
);
create table ost_ticket_action_history(
ticket_id int not null,
action_id int not null PRIMARY KEY,
action_name varchar(50),
started timestamp,
FOREIGN KEY(ticket_id) REFERENCES ost_ticket(ticket_id)
);
In the ost_ticket_action_history table the data is:
INSERT INTO newdb.ost_ticket_action_history (ticket_id, action_id, action_name, started) VALUES (1, 1, 'Consultation', '2022-01-06 18:30:29');
INSERT INTO newdb.ost_ticket_action_history (ticket_id, action_id, action_name, started) VALUES (2, 2, 'Bank Application', '2022-02-06 18:30:45');
INSERT INTO newdb.ost_ticket_action_history (ticket_id, action_id, action_name, started) VALUES (3, 3, 'Consultation', '2022-05-06 18:42:48');
In the ost_ticket table the data is:
INSERT INTO newdb.ost_ticket (ticket_id, created, staff, status, city_id) VALUES (1, '2022-04-04 18:26:41', 1, 'open', 2);
INSERT INTO newdb.ost_ticket (ticket_id, created, staff, status, city_id) VALUES (2, '2022-05-05 18:30:48', 0, 'open', 3);
INSERT INTO newdb.ost_ticket (ticket_id, created, staff, status, city_id) VALUES (3, '2022-04-06 18:42:53', 1, 'open', 4);
My task is to get the conversion from the “Consultation” stage to the “Bank Application” stage broken down by months (based on the start date of the “Bank Application” stage).Conversion is calculated according to the following formula: (number of applications with the “Bank Application” stage / number of applications with the “Consultation” stage) * 100%.
My request is like this:
select SUM(action_name='Bank Application')/SUM(action_name='Consultation') * 2 as 'Conversion' from ost_ticket_action_history JOIN ost_ticket ot on ot.ticket_id = ost_ticket_action_history.ticket_id where status = 'open' and created > '2020 -01-01 00:00:00' group by action_name,started having action_name = 'Bank Application';
As a result I get:
Another query:
SELECT
SUM(CASE
WHEN b.ticket_id IS NOT NULL THEN 1
ELSE 0
END) / COUNT(*) conversion,
YEAR(a.started) AS 'year',
MONTH(a.started) AS 'month'
FROM
ost_ticket_action_history a
LEFT JOIN
ost_ticket_action_history b ON a.ticket_id = b.ticket_id
AND b.action_name = 'Bank Application'
WHERE
a.action_name = 'Consultation'
AND a.status = 'open'
AND a.created > '2020-01-01 00:00:00'
GROUP BY YEAR(a.started) , MONTH(a.started)
I apologize if I didn't write very clearly. Please explain what to do.
Like I explained in my comment, you exclude rows with your having clause.
I will show you in the next how to debug.
First check what the raw result of the select query is.
As you see, when you remove the GROUP BY and see what you actually get is only 1 row with bank application, because the having clause excludes all other rows
SELECT
*
FROM
ost_ticket_action_history
JOIN
ost_ticket ot ON ot.ticket_id = ost_ticket_action_history.ticket_id
WHERE
status = 'open'
AND created > '2020-01-01 00:00:00'
GROUP BY
action_name, started
HAVING
action_name = 'Bank Application';
Output:
ticket_id
action_id
action_name
started
ticket_id
created
staff
status
city_id
2
2
Bank Application
2022-02-06 18:30:45
2
2022-05-05 18:30:48
0
open
3
Second step, see what the result set is without calculating anything.
As you can see you make a division with 0, what you have learned in school, is forbidden, hat is why you have as result set NULL
SELECT
SUM(action_name = 'Bank Application')
#/
,SUM(action_name = 'Consultation') * 2 AS 'Conversion'
FROM
ost_ticket_action_history
JOIN
ost_ticket ot ON ot.ticket_id = ost_ticket_action_history.ticket_id
WHERE
status = 'open'
AND created > '2020-01-01 00:00:00'
GROUP BY action_name , started
HAVING action_name = 'Bank Application';
SUM(action_name = 'Bank Application') | Conversion
------------------------------------: | ---------:
1 | 0
db<>fiddle here
#Third what you can do exclude a division with 0, here i didn't remove all othe rows as this is only for emphasis
SELECT
SUM(action_name = 'Bank Application')
/
SUM(action_name = 'Consultation') * 2 AS 'Conversion'
FROM
ost_ticket_action_history
JOIN
ost_ticket ot ON ot.ticket_id = ost_ticket_action_history.ticket_id
WHERE
status = 'open'
AND created > '2020-01-01 00:00:00'
GROUP BY action_name , started
HAVING SUM(action_name = 'Consultation') > 0;
| Conversion |
| ---------: |
| 0.0000 |
| 0.0000 |
db<>fiddle here
Final words,
If you get a strange result, simply go back remove everything that doesn't matter and try to get all values, so hat you can check your math

Make table with two new columns that categorize another column by even or odd and that sums all even and odd numbers in a category

I have the following structure of table:
category
user_id
value
A
1
0.01
A
2
0.05
A
3
0.09
A
4
0.12
B
1
0.34
B
2
0.27
B
3
0.08
B
4
0.12
There are many more rows in the actual table. This is just an example.
I want to make a table that keeps 'category', makes another column 'user_id_type' that labels even and odd, and another new column (value_sum) that sums all of the 'value' based on 'category' and 'user_id_type'.
So, it will have only four rows, with 'A' 'odd' and the sum, 'A' 'even' and the sum, 'B' 'odd' and the sum, 'B' 'even' and the sum.
I want it to look like this:
category
user_id_type
value_sum
A
odd
0.10
A
even
0.17
B
odd
0.42
B
even
0.39
Schema:
CREATE TABLE table_1 (
`category` VARCHAR(2),
`user_id` INT(2),
`value` DECIMAL(3,2)
);
INSERT INTO table_1
(`category`, `user_id`, `value`)
VALUES
('A', 1, 0.01),
('A', 2, 0.05),
('A', 3, 0.09),
('A', 4, 0.12),
('B', 1, 0.34),
('B', 2, 0.27),
('B', 3, 0.08),
('B', 4, 0.12)
;
You can create a table according to your requirements and fill this table using a select like this:
INSERT INTO table_2
SELECT category,
CASE WHEN user_id % 2 = 0 THEN 'even' ELSE 'odd' END user_id_type,
SUM(value) FROM table_1 GROUP BY user_id_type, category ORDER BY category;
Please have a look here to see it's working: fiddle
You can use MOD with a case expression to determine odd or even, and then use the results as a derived table to aggregate from:
select a.category
, a.user_id_type
, sum(a.value)
from
(
SELECT t.category
, case
when MOD(t.user_id, 2) = 0 then 'Even'
else 'Odd'
end user_id_type
, t.value
FROM tbl t
) a
group by a.category, a.user_id_type
;
Edit: Adding Lemon's recommendation for compactness
Here is the example with Lemon's compacted version:
select category
, user_id_type
, sum(value)
from
(
SELECT category
, IF(MOD(t.user_id, 2)=0, 'even', 'odd') user_id_type
, value
FROM tbl t
) a
group by a.category, a.user_id_type
;
You can use MODulo to dete4rmin the odds
CREATE TABLE table_1 (
`category` VARCHAR(2),
`user_id` INT(2),
`value` DECIMAL(3,2)
);
INSERT INTO table_1
(`category`, `user_id`, `value`)
VALUES
('A', 1, 0.01),
('A', 2, 0.05),
('A', 3, 0.09),
('A', 4, 0.12),
('B', 1, 0.34),
('B', 2, 0.27),
('B', 3, 0.08),
('B', 4, 0.12)
;
SELECT
`category`
, MAX(CASE WHEN MOD( `user_id`,2) = 1 then 'Even' ELSE 'Odd' ENd ) odds, SUM(`value`)
FROM table_1
GROUP BY category, MOD( `user_id`,2)
ORDER BY category, MOD( `user_id`,2) ASC
category | odds | SUM(`value`)
:------- | :--- | -----------:
A | Odd | 0.17
A | Even | 0.10
B | Odd | 0.39
B | Even | 0.42
db<>fiddle here

Tricky sql query required, finding a sum of a subquery

A relevant part of my db looks as follows (MS Visio, I know I'm pathetic :D):
I need to extract a list consisting of all items in a category as well as bundles. So I have to use UNION. First part of a UNION for your reference (as it sets the data format for the SELECT in the second part of UNION; note that ? signifies where an argument goes in node-mysql):
SELECT `ID`, `Name`, `Description`,
`PictureID`, `SellingPrice`,
`Cost`, 0 AS `Bundle`
FROM `Item`
WHERE `CategoryID`=? AND
`ID` IN (
SELECT `ItemID`
FROM `Stock`
WHERE `CityID`=?
AND `IsLimitless`=1 OR `Quantity`>0
)
So I want to present my Bundles as if they are also items, with all same fields etc.
My attempt:
SELECT `ID`, `Name`, `Description`, `PictureID`,
(
SELECT SUM( // Here SQL indicates a syntax problem
SELECT `ItemAmount`*`PriceModifier`*(
SELECT `SellingPrice`
FROM `Item`
WHERE `ID`=`BundleItem`.`ItemID`
)
FROM `BundleItem` WHERE `BundleID`=`Bundle`.`ID`
)
) AS `SellingPrice`,
(
SELECT SUM(
SELECT `ItemAmount`*(
SELECT `Cost`
FROM `Item`
WHERE `ID`=`BundleItem`.`ItemID`
)
FROM `BundleItem` WHERE `BundleID`=`Bundle`.`ID`
)
) AS `Cost`,
1 AS `Bundle`
FROM `Bundle`
WHERE `ID` IN (
SELECT `BundleID`
FROM `BundleCategory`
WHERE `CategoryID`=?
)
//No need to check bundles for stock due to business logic
I have a faint idea that I'm overcomplicating this, but I can't put my finger on it, unfortunately.
Any advise will be very welcome and thanks in advance for taking your time. <3
Sample data:
Fields of no interest like "Description"/"PictureID"/"SupplierID" will be omitted
for the relevant parts to fit on screen
**Bundle**
ID Name Description PictureID
1 Valentine Pack Blah-blah tasty buy me imgur link in text
**Item**
ID Name SellingPrice Cost CategoryID
1 Movie Ticket 10 2 24
2 Box of Chocolates 5 1 4
3 Teddy Bear 15 3 2
4 Roses 10 4 8
**Stock**
ItemID CityID Quantity IsLimitLess
1 1 25 false
1 2 11 false
2 1 84 false
3 1 33 false
4 1 1 true
4 3 1 true
**BundleItem**
BundleID ItemID ItemAmount PriceModifier
1 1 2 1.25
1 2 1 1
1 3 1 1
1 4 5 0.75
**BundleCategory** (bundle for marketing reasons can appear in different
categories depending on its contents)
BundleID CategoryID
1 4 //Sweets
1 2 //Toys
1 8 //Flowers
Desired output: (For searching CityID 1, CategoryID 8, Flowers)
ID Name (Descr/PicID) SellingPrice Cost Bundle
4 Roses 10 4 false
1 Valentine Pack 82.5 28 true
/*2*10*1.25+ 2*2+ <movie
1*1*5+ 1*1+ <chocolate
1*1*15+ 3*1+ <teddy bear
5*0.75*10 5*4 <roses */
User suggested solutions
As per #drakin8564 's suggestion I tried doing
SELECT `ID`, `Name`, `Description`, `PictureID`,
(
SELECT SUM((
SELECT `ItemAmount`*`PriceModifier`*(
SELECT `SellingPrice`
FROM `Item`
WHERE `ID`=`BundleItem`.`ItemID`
)
FROM `BundleItem` WHERE `BundleID`=`Bundle`.`ID`
))
) AS `SellingPrice`,
(
SELECT SUM((
SELECT `ItemAmount`*(
SELECT `Cost`
FROM `Item`
WHERE `ID`=`BundleItem`.`ItemID`
)
FROM `BundleItem` WHERE `BundleID`=`Bundle`.`ID`
))
) AS `Cost`,
1 AS `Bundle`
FROM `Bundle`
WHERE `ID` IN (
SELECT `BundleID`
FROM `BundleCategory`
WHERE `CategoryID`=8
)
Returns
(1242): Subquery returns more than 1 row.
This happens even when I try SELECT SUM((SELECT ID FROM Item)). Weird.
I commented on other solutions about how good they work. I appreciate all you guys taking part in this. <3
It looks like you had a few syntax issues. Your code worked with a few changes. See comments in query for details.
http://sqlfiddle.com/#!9/ee0725/16
SELECT `ID`, `Name`, `Description`, `PictureID`,
(SELECT SUM(`ItemAmount`*`PriceModifier`*( -- changed order of SELECT and SUM; removed extra SELECT; fixed Parens
SELECT `SellingPrice`
FROM `Item`
WHERE `ID`=`BundleItem`.`ItemID`
))
FROM `BundleItem` WHERE `BundleID`=`Bundle`.`ID`)
AS `SellingPrice`,
(SELECT SUM(`ItemAmount`*( -- changed order of SELECT and SUM; removed extra SELECT; fixed Parens
SELECT `Cost`
FROM `Item`
WHERE `ID`=`BundleItem`.`ItemID`
))
FROM `BundleItem` WHERE `BundleID`=`Bundle`.`ID`)
AS `Cost`,
1 AS `Bundle`
FROM `Bundle`
WHERE `ID` IN (
SELECT `BundleID`
FROM `BundleCategory`
WHERE `CategoryID`=8
);
Something like this should work
SELECT tb.`ID`, MAX(tb.`Name`), MAX(tb.`Description`), MAX(tb.`PictureID`),
SUM(`ItemAmount`*`PriceModifier`*`SellingPrice`) AS `SellingPrice`,
SUM(`ItemAmount`*`Cost`) AS `Cost`,
1 AS `Bundle`
FROM `Bundle` tb
JOIN `BundleItem` tbi on tb.ID=tbi.BundleID
JOIN `Item` ti on tbi.ItemID=ti.ID
WHERE tb.`ID` IN (
SELECT `BundleID`
FROM `BundleCategory`
WHERE `CategoryID`=?
)
GROUP BY tb.ID
//No need to check bundles for stock due to business logic
Your syntax error is because your subquery is not wrapped in (). Examples below.
This will fail:
SELECT SUM(SELECT 1);
This will work:
SELECT SUM((SELECT 1));
Assumption #1: All items must have enough stock in a city for a bundle to be available in that city. (See query comments for how to remove this business rule)
In the sample data, there are no bundles that are fully in stock in any cities - to remedy this, I changed the Quanity for ItemID=4 in CityID=1 from "1" to "5". This created your desired output.
Assumption #2: Stock.Quantity=0 is allowed.
This solution produces query results that contain all Items and Bundles for every City and Category where the Item or Bundle is in stock. The where clause at the bottom filters it to CityID=1 and Category=8 per the original request.
Note: You can paste the Solution and Schema below into www.sqlfiddle.com and see the results.
UPDATE
Fixed BundleCategory join.
Solution
select * from (
select
Stock.CityID,
Item.CategoryID,
Item.ID,
Item.Name,
Item.Description,
Item.SellingPrice,
Item.Cost,
'false' as Bundle
from Item
inner join Stock on Stock.ItemID = Item.ID
where IFNULL(Stock.Quantity,0) > 0 -- remove this to show out of stock items
union
select
BundleSummary.CityID,
BundleCategory.CategoryID,
Bundle.ID,
Bundle.Name,
Bundle.Description,
BundleSummary.SellingPrice as SellingPrice,
BundleSummary.Cost as Cost,
'true' as Bundle
from Bundle
inner join (
select
BundleItem.BundleID,
City.CityID,
MIN(IF(IFNULL(Stock.Quantity, 0) < BundleItem.ItemAmount, 0, 1)) as InStock,
SUM(Item.SellingPrice * BundleItem.ItemAmount * BundleItem.PriceModifier) as SellingPrice,
SUM(Item.Cost * BundleItem.ItemAmount) as Cost
from BundleItem
inner join Item on Item.ID = BundleItem.ItemID
inner join (select distinct CityID from Stock where CityID IS NOT NULL) as City on 1=1
left join Stock on Stock.ItemID = Item.ID and Stock.CityID = City.CityID
group by BundleItem.BundleID, City.CityID
) as BundleSummary on BundleSummary.BundleID = Bundle.ID
inner join BundleCategory on BundleCategory.BundleID = Bundle.ID
where BundleSummary.InStock = 1 -- remove this to show out of stock bundles
) as qry1
where CityID=1 and CategoryID=8;
I also generated a script to create the database schema and populate it with the sample data. Thought this might be helpful to anyone who is using this solution to investigate their own issues.
Schema
create table Item (
ID int,
Name varchar(255),
Description varchar(255),
PictureID int,
SellingPrice DECIMAL(12,4),
Cost DECIMAL(12,4),
SupplierID int,
CategoryID int
);
insert into Item values (1, 'Movie Ticket', '', NULL, 10, 2, NULL, 24);
insert into Item values (2, 'Box of Chocolates', '', NULL, 5, 1, NULL, 4);
insert into Item values (3, 'Teddy Bear', '', NULL, 15, 3, NULL, 2);
insert into Item values (4, 'Roses', '', NULL, 10, 4, NULL, 8);
create table Bundle (
ID int,
Name varchar(255),
Description varchar(255),
PictureID int
);
insert into Bundle values (1, 'Valentine Pack', 'Blah-blah tasty buy me', NULL);
create table Stock (
ItemID int,
CityID int,
Quantity int,
IsLimitless bit
);
insert into Stock values (1, 1, 25, false);
insert into Stock values (1, 2, 11, false);
insert into Stock values (2, 1, 84, false);
insert into Stock values (3, 1, 33, false);
insert into Stock values (4, 1, 5, true);
insert into Stock values (4, 3, 1, true);
create table BundleItem (
BundleID int,
ItemID int,
ItemAmount int,
PriceModifier DECIMAL(12,4)
);
insert into BundleItem values (1, 1, 2, 1.25);
insert into BundleItem values (1, 2, 1, 1);
insert into BundleItem values (1, 3, 1, 1);
insert into BundleItem values (1, 4, 5, 0.75);
create table BundleCategory (
BundleID int,
CategoryID int
);
insert into BundleCategory values (1, 4); -- Sweets
insert into BundleCategory values (1, 2); -- Toys
insert into BundleCategory values (1, 8); -- Flowers

Sql update where left join count equal to 3

Hi I'm trying to build a sql query where I update the value of a table where the left join of another table is equal to 3.
Example when a vehicle has 3 photos.
The query I've written thus far, it seems to fail with group by though.
UPDATE domain.vehicle_listing AS t0 LEFT OUTER JOIN photo AS t1 ON t0.id = t1.vehicle_listing_id
SET t0.active = 0
WHERE `create_date` >= '2015-5-2'
AND user_profile_id is not null
AND t0.active = 1
GROUP BY t1.vehicle_listing_id
HAVING COUNT(DISTINCT t1.id) = 3
ORDER BY create_date desc;
Vehicle_Listing
id
Photo
id, vehicle_listing_id, photo_url
OneToMany relationship with photo.
You can also use exists
UPDATE vehicle_listing AS t0
SET t0.active = 0
WHERE t0.`create_date` >= '2015-05-02'
AND t0.user_profile_id is not null
AND t0.active = 1
AND EXISTS (
SELECT 1
FROM photo
WHERE vehicle_listing_id=t0.id
GROUP BY vehicle_listing_id
HAVING COUNT(DISTINCT id) = 3
)
Sample data for vehicle_listing
INSERT INTO vehicle_listing
(`id`, `title`, `create_date`, `active`,user_profile_id)
VALUES
(1, 'test', '2015-05-02 00:00:00', 1,1),
(2, 'test1', '2015-05-02 00:00:00', 1,1)
;
Sample data for photo
INSERT INTO photo
(`id`, `vehicle_listing_id`, `photo_url`)
VALUES
(1, 1, 'image.jpg'),
(2, 1, 'image.jpg'),
(3, 1, 'image.jpg'),
(4, 2, 'image.jpg'),
(5, 2, 'image.jpg')
;
Sample Output
id title create_date active user_profile_id
1 test May, 02 2015 00:00:00 0 1
2 test1 May, 02 2015 00:00:00 1 1
DEMO
It is silly to use a left join for this. You want inner join:
UPDATE cardaddy.vehicle_listing vl INNER JOIN
(SELECT p.vehicle_listing_id, count(*) as cnt
FROM photo p
GROUP BY p.vehicle_listing_id
) p
ON vl.id = p.vehicle_listing_id AND
p.cnt = 3
SET vl.active = 0
WHERE vl.create_date >= '2015-05-02' AND
vl.user_profile_id IS NOT NULL AND
vl.active = 1;
(Assuming that user_profile_id is in vehicle_listing.)
UPDATE cardaddy.vehicle_listing AS t0
LEFT OUTER JOIN (
SELECT vehicle_listing_id, count(1) AS counter
FROM photo
GROUP BY vehicle_listing_id
) AS t1
ON t0.id = t1.vehicle_listing_id
AND t1.counter = 3
SET t0.active = 0
WHERE `create_date` >= '2015-5-2'
AND user_profile_id IS NOT NULL
AND t0.active = 1
AND t1.vehicle_list_is IS NOT NULL

Count consecutive rows with a particular status

I need to count whether there are three consecutive failed login attempts of the user in last one hour.
For example
id userid status logindate
1 1 0 2014-08-28 10:00:00
2 1 1 2014-08-28 10:10:35
3 1 0 2014-08-28 10:30:00
4 1 0 2014-08-28 10:40:00
In the above example, status 0 means failed attempt and 1 means successful attempt.
I need a query that will count three consecutive records of a user with status 0 occurred in last one hour.
I tried below query
SELECT COUNT( * ) AS total, Temp.status
FROM (
SELECT a.status, MAX( a.id ) AS idlimit
FROM loginAttempts a
GROUP BY a.status
ORDER BY MAX( a.id ) DESC
) AS Temp
JOIN loginAttempts t ON Temp.idlimit < t.id
HAVING total >1
Result:
total status
2 1
I don't know why it display status as 1. I also need to add a where condition on logindate and status field but don't know how would it work
For consecutive count you can use user defined variables to note the series values ,like in below query i have use #g and #r variable, in inner query i am storing the current status value that could be 1/0 and in case expression i am comparing the value stored in #g with the status column if they both are equal like #g is holding previous row value and previous row's status is equal to the current row's status then do not change the value stored in #r,if these values don't match like #g <> a.status then increment #r with 1, one thing to note i am using order by with id column and assuming it is set to auto_increment so for consecutive 1s #r value will be same like #r was 3 for first status 1 and the again status is 1 so #r will 3 until the status changes to 0 same for status 0 vice versa
SELECT t.userid,t.consecutive,t.status,COUNT(1) consecutive_count
FROM (
SELECT a.* ,
#r:= CASE WHEN #g = a.status THEN #r ELSE #r + 1 END consecutive,
#g:= a.status g
FROM attempts a
CROSS JOIN (SELECT #g:=2, #r:=0) t1
WHERE a.`logindate` BETWEEN '2014-08-28 10:00:00' AND '2014-08-28 11:00:00'
ORDER BY id
) t
GROUP BY t.userid,t.consecutive,t.status
HAVING consecutive_count >= 3 AND t.status = 0
Now in parent query i am grouping results by userid the resultant value of case expression i have name is it as consecutive and status to get the count for each user's consecutive status
One thing to note for above query that its necessary to provide the
hour range like i have used between without this it will be more
difficult to find exactly 3 consecutive statuses with in an hour
Sample data
INSERT INTO attempts
(`id`, `userid`, `status`, `logindate`)
VALUES
(1, 1, 0, '2014-08-28 10:00:00'),
(2, 1, 1, '2014-08-28 10:10:35'),
(3, 1, 0, '2014-08-28 10:30:00'),
(4, 1, 0, '2014-08-28 10:40:00'),
(5, 1, 0, '2014-08-28 10:50:00'),
(6, 2, 0, '2014-08-28 10:00:00'),
(7, 2, 0, '2014-08-28 10:10:35'),
(8, 2, 0, '2014-08-28 10:30:00'),
(9, 2, 1, '2014-08-28 10:40:00'),
(10, 2, 1, '2014-08-28 10:50:00')
;
As you can see from id 3 to 5 you can see consecutive 0s for userid 1 and similarly id 6 to 8 userid 2 has consecutive 0s and they are in an hour range using above query you can have results as below
userid consecutive status consecutive_count
------ ----------- ------ -------------------
1 2 0 3
2 2 0 3
Fiddle Demo
M Khalid Junaid's answer is great, but his Fiddle Demo didn't work for me when I clicked it.
Here is a Fiddle Demo which works as of this writing.
In case it doesn't work later, I used the following in the schema:
CREATE TABLE attempts
(`id` int, `userid` int, `status` int, `logindate` datetime);
INSERT INTO attempts
(`id`, `userid`, `status`, `logindate`)
VALUES
(1, 1, 0, '2014-08-28 10:00:00'),
(2, 1, 1, '2014-08-28 10:10:35'),
(3, 1, 0, '2014-08-28 10:30:00'),
(4, 1, 0, '2014-08-28 10:40:00'),
(5, 1, 0, '2014-08-28 10:50:00'),
(6, 2, 0, '2014-08-28 10:00:00'),
(7, 2, 0, '2014-08-28 10:10:35'),
(8, 2, 0, '2014-08-28 10:30:00'),
(9, 2, 1, '2014-08-28 10:40:00'),
(10, 2, 1, '2014-08-28 10:50:00')
;
And this as the query:
SELECT t.userid,t.consecutive,t.status,COUNT(1) consecutive_count
FROM (
SELECT a.* ,
#r:= CASE WHEN #g = a.status THEN #r ELSE #r + 1 END consecutive,
#g:= a.status g
FROM attempts a
CROSS JOIN (SELECT #g:=2, #r:=0) t1
WHERE a.`logindate` BETWEEN '2014-08-28 10:00:00' AND '2014-08-28 11:00:00'
ORDER BY id
) t
GROUP BY t.userid,t.consecutive,t.status
HAVING consecutive_count >= 3 AND t.status = 0;