Advanced (probably not?) MYSQL query select statement - mysql

Please see the below sample data:
'messages' table data:
id, date, user, seen
674, 1399430687, 2, 0
675, 1399430957, 2, 1399431766
676, 1399431065, 1, 1399431766
677, 1399431723, 2, 1399431766
678, 1399434322, 2, 0
679, 1399434330, 2, 0
I want to do a count of only the most recent non seen (seen=0) records to a particular user (user: 2) UP and ONLY UNTIL the next seen record (seen>0). So in the above case there are 3 unseen (seen=0) records to user (user: 2) but only 2 of them I am interested as the next 1 has some seen records in between it. So in the above scenario count of 2 would be returned (not the actual records).
Also if the most recent record to user (user: 2) is seen (seen>0) then count of 0 should be returned. I just can't get my head around how to do this in mysql, or how to really describe it.
This is all I can work out, but in the above scenario this will result in 3, not 2:
SELECT COUNT( * )
FROM `messages`
WHERE seen = 0
AND user = 2
ORDER BY DATE DESC
I hope this makes sense.
Thanks.

You need to find the latest non-zero seen data, and then filter after that.
SELECT Count(id) FROM data WHERE user = 3 AND seen = 0
AND date > (
SELECT coalesce(MAX(date),0) FROM data WHERE seen <> 0 AND user = 3
);
here's the fiddle
EDIT:
The previous code wouldn't return a correct answer if the user hadn't seen any records yet at all, this code fixes it by returning 0 instead of null.

Related

mysql RC multi-thread first search and second delete, but some search only return 1 row

I use mysql to deal some data use multi-thread, first I search the data use id range, like:
select id
from xxx
where id between 1 and 1000
and accountant_time = '2021-05-31 00:00:00'
and enter_accounts_state = 1
and enter_ce_state = 2
and ebs_summary_state = 2
and is_del = 0
result id like '1, 2, 3, 4, 5, ... 1001'.
and second I will delete these match data with addtional condition confirm_state from table like sql below:
DELETE
FROM xxx
WHERE confirm_state = 3
AND id IN ( 1, 2, 3, 4, ..., 1001);
All of the id range no intersection。
I found that some thread need return 1001 rows, but only returned the first row,
I tried several times use same code and same data, but the left data also not same, the common feature is only return first row of that batch count which need return all。
When I add for update for the select sql, it works normal,
How can I understand what happens?

searching for records in mysql using or - and - not in query

I think I am getting turned around when looking at this. I am trying to get all patron records relating to transactions that have a transaction item with one of a number of ids (1 or 2) as well as transaction items with other ids (3 or 4) but not with transaction items with other ids (5 or 6)
The structure is:
=patron=
id
fname
lname
email
phone
=trans=
id
id_org
id_patron
=trans_item=
id
id_trans
id_perf
I was trying the following:
SELECT
patron.email,
patron.fname,
patron.lname,
patron.phone
FROM
trans_item,
trans,
patron
WHERE
trans_item.id_perf IN (1,2)
AND
trans_item.id_perf IN (3,4)
AND
trans_item.id_perf NOT IN (5,6)
AND
trans_item.id_trans = trans.id
AND
trans.id_org = 1
AND
trans.id_patron = patron.id
GROUP BY
patron.id
ORDER BY
patron.email DESC,
patron.phone DESC
I'm aware that saying the id needs to be 2 AND 4 is always going to return nothing but I need to have it as if id is in (1,2) AND (3,4) so it can be 1 or 2 but also needs to be in 3 or 4
For Clarity:
I am trying to get patrons who have gone to performance 1 OR 2 and 3 OR 4 but NOT 5 OR 6
You can do this with group by and having. The basic idea is:
select ti.id_trans
from trans_item ti
group by ti.id_trans
having sum(ti.id_perf in (1, 2)) > 0 and
sum(ti.id_perf in (3, 4)) > 0 and
sum(ti.id_perf in (5, 6)) = 0;
Each condition in the having clause checks a row for the particular ids. The > 0 means they exist for transaction. The = 0 means they do not.
If you want additional columns from other tables, you can join back to this result set.
I think I have a solution. If I combine the ids for all perfs and group all results by the trans_item.id I can get a list that has duplicates. I then convert them into a php multidimensional array and exclude / include based on the ids for each requirement finding the duplicates that way. Any other suggestions are welcome

Mysql query to skip rows and check for status changes

I'm building a mysql query but I'm stuck... (I'm logging each minute)
I have 3 tables. Logs, log_field, log_value.
logs -> id, create_time
log_value -> id, log_id,log_field_id,value
log_field -> id, name (one on the entries is status and username)
The values for status can be online,offline and idle...
What I would like to see is from my query is:
When in my logs someone changes from status, I want a row with create_time, username, status.
So for a given user, I want my query to skip rows until a new status appears...
And I need to be able to put a time interval in which status changes are ignored.
Can someone please help ?
Although you have nothing to differentiate an actual "User" (such as by user ID) listed in your post, and what happens if you have two "John Smith" names.
First, an introduction to MySQL #variables. You can think of them as an inline program running while the query is processing rows. You create variables, then change them as each row gets processed, IN THE SAME order as the := assignment in the field selection occurs which is critical. I'll cover that shortly.
Fist an initial premise. You have a field value table of all possible fields that can/do get logged. Of which, two of them exist... one is for the user's name, another for the status you are looking a log changed. I don't know what those internal "ID" numbers are, but they would have to be fixed values per your existing table. In my scenario, I am assuming that field ID = 1 is for the User's Name, and field ID 2 = status column... Otherwise, you would need two more joins to get the field table just to confirm which field was the one you wanted. Obviously my "ID" field values will not match your production tables, so please change those accordingly.
Here's the query...
select FinalAlias.*
from (
select
PQ.*,
if( #lastUser = PQ.LogUser, 1, 0 ) as SameUser,
#lastTime := if( #lastUser = PQ.LogUser, #lastTime, #ignoreTime ) as lastChange,
if( PQ.create_time > #lastTime + interval 20 minute, 1, 0 ) as BeyondInterval,
#lastTime := PQ.create_time as chgTime,
#lastUser := PQ.LogUser as chgUser
from
( select
ByStatus.id,
l.create_time,
ByStatus.Value LogStatus,
ByUser.Value LogUser
from
log_value as ByStatus
join logs l
on ByStatus.log_id = l.id
join log_value as ByUser
on ByStatus.log_id = ByUser.log_id
AND ByUser.log_field_id = 1
where
ByStatus.log_field_id = 2
order by
ByUser.Value,
l.create_time ) PQ,
( select #lastUser := '',
#lastTime := now(),
#ignoreTime := now() ) sqlvars
) FinalAlias
where
SameUser = 1
and BeyondInterval = 1
Now, what's going on. The inner-most query (result alias PQ representing "PreQuery") is just asking for all log values where the field_id = 2 (status column) exists. From that log entry, go to the log table for it's creation time... while we're at it, join AGAIN to the log value table on the same log ID, but this time also look for field_id = 1 so we can get the user name.
Once that is done, get the log ID, Creation time, Status Value and Who it was for all pre-sorted on a per-user basis and sequentially time oriented. This is the critical step. The data must be pre-organized by user/time to compare the "last" time for a given user to the "next" time their log status changed.
Now, the MySQL #variables. Join the prequery to another select of #variables which is given an "sqlvars" query alias. This will pre-initialize the variables fo #lastUser, #lastTime and #ignoreTime. Now, look at what I'm doing in the field list via section
if( #lastUser = PQ.LogUser, 1, 0 ) as SameUser,
#lastTime := if( #lastUser = PQ.LogUser, #lastTime, #ignoreTime ) as lastChange,
if( PQ.create_time > #lastTime + interval 20 minute, 1, 0 ) as BeyondInterval,
#lastTime := PQ.create_time as chgTime,
#lastUser := PQ.LogUser as chgUser
This is like doing the following pseudo code in a loop for every record (which is already sequentially ordered by same person and their respective log time
FOR EACH ROW IN RESULT SET
Set a flag "SameUser" = 1 if the value of the #lastUser is the same
as the current person record we are looking at
if the last user is the same as the previous record
use the #lastTime field as the "lastChange" column
else
use the #ignore field as the last change column
Now, build another flag based on the current record create time
and whatever the #lastTime value is based on a 20 minute interval.
set it to 1 if AT LEAST the 20 minute interval has been meet.
Now the key to the cycling the next record.
force the #lastTime = current record create_time
force the #lastUser = current user
END FOR LOOP
So, if you have the following as a result of the prequery... (leaving date portion off)
create status user sameuser lastchange 20minFlag carry to next row compare
07:34 online Bill 0 09:05 0 07:34 Bill
07:52 idle Bill 1 07:34 0 07:52 Bill
08:16 online Bill 1 07:52 1 08:16 Bill
07:44 online Mark 0 09:05 0 07:44 Mark
07:37 idle Monica 0 09:05 0 07:37 Monica
08:03 online Monica 1 07:37 1 08:03 Monica
Notice first record for Bill. The flag same user = 0 since there was nobody before him. The last change was 9:05 (via the NOW() when creating the sqlvars variables), but then look at the "carry to next row compare". This is setting the #lastTime and #lastUser after the current row was done being compared as needed.
Next row for Bill. It sees he is same as last user previous row, so the SameUser flag is set to 1. We now know that we have a good "Last Time" to compare against the current record "Create Time". So, from 7:34 to 7:52 is 18 minutes and LESS than our 20 minute interval so the 20 minute flag is set to 0. Now, we retain the current 7:52 and Bill for third row.
Third row for Bill. Still Same User (flag=1), last change of 7:52 compared to now 8:16 and we have 24 minutes... So the 20 minute flag = 1. Retain 8:16 and Bill for next row.
First row for Mark. Same User = 0 since last user was Bill. Uses same 9:05 ignore time and don't care about 20 min flag, but now save 7:44 and Mark for next row compare.
On to Monica. Different than Mark, so SameUser = 0, etc to finish similar to Bill.
So, now we have all the pieces and rows considered. Now, take all these and wrap them up as the "FinalAlias" of the query and all we do is apply a WHERE clause for "SameUser = 1" AND "20 Minute Flag" has been reached.
You can strip down the final column list as needed, and remove the where clause to look at results, but be sure to add an outer ORDER BY clause for name/create_time to see similar pattern as I have here.

Preserve from splitting results

Is there any possible way to SELECT from MYSQL database and preserve from splitting results? I'd like to get all the data from previous day, but it'll be too much, but I also cannot split results:
Select all with certain limit, but do not split (by certain value, i.e. user_id) onto separate results.
EXAMPLE
SELECT
ti.id, ti.date, ti.duedate, ti.datepaid,
tii.invoiceid, tii.userid,
tc.postcode, tc.country,
(SELECT GROUP_CONCAT(value) FROM custom WHERE relid=tc.id) AS vatid
FROM invoices ti
LEFT JOIN invoiceitems tii
ON tii.invoiceid=ti.id
LEFT JOIN clients tc
ON tc.id=tii.userid
WHERE ti.status='Paid'
AND ti.nullmo_no IS NULL
ORDER BY tii.userid AND tii.id
Now I get all the results, but I need to split them without breaking userid. For example one SELECT returns 20 results, because there were 15 invoices for user 1, and 5 invoices for user 2, then the next call returns the rest, also with a limit, but not breaking user related group of results:
SELECT
part 1 (all from user 1, all from user 2)
part 2 (all from user 3, all from user 4)
Can this be done in one select statement?
id = 1,2,3,4,5,6,7,8,9,10
name = n1, n2, n3, n4, n5, n6, n7, n8, n9, n10
user_id = 1, 1, 1, 2, 2, 2, 3, 3, 4, 5 // split but not divide
content = c1,c2,c3,c4,c5,c6,c7,c8,c9,c10
date = yesterday, yesterday, yesterday, yesterday, yesterday, yesterday, yesterday, yesterday, yesterday, yesterday
The deal is to select all of them, with a limit, but not to split user_id, so: 1. All from yesterday 2. LIMIT if per one or more user_id's there are more results than LIMIT So the limit would be determined by the number of results.

How do I create a query that caters for two different groups of data and produces a result that is dependent on the first set in MySQL?

I'm trying to count all instances where a group of data has one or more fail.
I'm also Finding it very difficult to build this question so I'm hoping that showing an example will do the trick in explaining what I'm trying to achieve.
Sample data:
INSERT INTO test.answers (id, result_id, fail_all, fail_group) VALUES
(1,1,0,1), (2,1,0,1), (3,1,0,1), (4,1,0,0),
(5,2,1,0), (6,2,0,0), (7,2,1,0), (8,2,1,0), (9,2,1,0),
(10,3,0,1), (11,3,1,1), (12,3,0,1), (13,3,0,1), (14,3,0,1),
(15,4,0,0), (16,4,0,0), (17,4,0,1), (18,4,0,1), (19,4,0,0), (20,4,0,1),
(21,5,1,0), (22,5,0,1), (23,5,1,1), (24,5,0,1), (25,5,1,0), (26,5,0,1);
INSERT INTO test.results (id,team_id) VALUES
(1,1), (2,1), (3,1), (4,2), (5,2);
I then run the following query:
SELECT
COUNT(IF(a.fail_all = 1,1,NULL)) AS count_fail_all,
COUNT(IF(a.fail_group = 1,1,NULL)) AS count_fail_group,
a.result_id
FROM test.answers AS a
GROUP BY a.result_id
Result:
count_fail_all, count_fail_group, result_id
0, 3, 1
4, 0, 2
1, 5, 3
0, 3, 4
3, 4, 5
I need to create a query that groups by team_id and counts how many fails there are per result. If a result has more than one fail, then that overall result is a fail. thereby if the count of results in the above query is 3 (example: first result) then it should only be counted as one. The fail_group can be ignored for now as I believe that the same solution for fail_all will work for fail_group.
The result I hope for is:
team_id, amount_of_fails, amount_of_fails_per_group
1, 2, 2
2, 1, 2
I hope someone might be able to help me create the query that I need? I'm not even sure how to start.
Let me know if there is anything that I can do to adjust the query as I know it's not very well asked?
Thanks!
If you want that output. Maybe something like this:
SELECT
r.team_id,
SUM(IF(a.fail_all = 1,1,0)) AS amount_fail_all,
SUM(IF(a.fail_group = 1,1,0)) AS amount_fail_group
FROM answers AS a
JOIN results AS r on r.id=a.result_id
GROUP BY r.team_id
This will get you this output:
team_id amount_fail_all amount_fail_group
1 2 2
2 1 2