MySQL SUM of LIMIT(3) rows with GROUP BY - mysql

Good people, need a bit of a help with MySQL. Tried few solutions online but could get it right.
I have this simple table.
name amount
john | 150
john | 100
john | 100
john | 150
jack | 300
jack | 100
jack | 100
Basically, I have to get the users that have sum of 500 in at least 3 rows(ordered by the highest amount).
The correct answer should only return jack because only he has sum of 500 in 3 records(ordered by highest). Where else john has 500 in total sum, 3 of his highest amounts would only return 400(150+150+100), so the query doesn't return john.
SELECT
*,
SUM(amount) as sums
FROM (SELECT * FROM transfer GROUP BY name ORDER BY amount DESC LIMIT 3) as ttl
GROUP BY name
HAVING sums >= 500
It works fine(no errors at least), but the second select(the one inside the bracket) only returns the first row.
Any help is highly appreciated.

Let me assuming that you have another column that is a unique id. Then you can do this as:
select distinct t1.name
from transfer t1 left join
transfer t2
on t1.name = t2.name and t1.id < t2.id left join
transfer t3
on t1.name = t3.name and t2.id < t3.id
where t1.amount + coalesce(t2.amount, 0) + coalesce(t3.amount, 0) >= 500;
This is not wildly efficient for larger tables. For that, use variables to enumerate the values:
select name
from (select t.*,
(#rn := if(#n = name, #rn + 1,
if(#n := name, 1, 1)
)
) as rn
from transfer cross join
(select #n := '', #rn := 0) params
order by name, amount desc
) t
where rn <= 3
group by name
having sum(amount) >= 500;
This also has the benefit that it does not rely on an id column.

Related

GROUP_BY, MAX() and only_full_group_by

I have a table:
ID ACCOUNT BALANCE TIME
1 Bill 10 1478885000
2 Bill 10 1478885001
3 James 5 1478885002
4 Ann 20 1478885003
5 Ann 15 1478885004
I want to get latest (based on TIME) balance of several accounts. I.e.:
ACCOUNT BALANCE
Bill 10
Ann 15
I try to use this SQL:
SELECT ACCOUNT, BALANCE, max(TIME)
FROM T1
WHERE ACCOUNT IN ( 'Bill', 'Ann')
GROUP BY ACCOUNT
I receive error:
1055 - Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'BALANCE' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by
I understand the error and tried different SQLs but still do not understand how to retrieve needed data without multiple queries.
P.S. I use MySQl 5.7
SELECT T1.ACCOUNT, T1.BALANCE, T1.TIME
FROM T1
JOIN (SELECT ACCOUNT, max(TIME) as m_time
FROM T1
WHERE T1.ACCOUNT IN ( 'Bill', 'Ann')
GROUP BY ACCOUNT ) T2
ON T1.ACCOUNT = T2.ACCOUNT
AND T1.TIME = T2.m_time
WHERE T1.ACCOUNT IN ( 'Bill', 'Ann')
EDIT: for multiple time change better use variables
SQL DEMO: I change the date of Ann to be the same
SELECT ACCOUNT, BALANCE, TIME
FROM (
SELECT ACCOUNT, BALANCE, TIME,
#rn := if(ACCOUNT = #acc,
#rn + 1,
if(#acc := ACCOUNT, 1, 1) as rn
FROM T1, (SELECT #rn := 0, #acc:= '') P
WHERE ACCOUNT IN ( 'Bill', 'Ann')
ORDER BY ACCOUNT, TIME desc, BALANCE desc
) T
WHERE T.rn = 1
OUTPUT
| ACCOUNT | BALANCE | TIME |
|---------|---------|------------|
| Bill | 10 | 1478885001 |
| Ann | 20 | 1478885003 |
The error is quite clear. If you want the latest balance for each account, here is one way:
select t1.*
from t1
where t1.time = (select max(tt1.time) from t1 tt1 where t1.account = tt1.account);
You can add the where in the outer query to filter for particular accounts.
You have column in your select that are not in group by
or you add all the column not in aggregated function
SELECT ACCOUNT, BALANCE, max(TIME)
FROM T1
WHERE ACCOUNT IN ( 'Bill', 'Ann')
GROUP BY ACCOUNT, BALANCE
or you change the sql_mode using
SET sql_mode = ''
or
SELECT ACCOUNT, BALANCE, TIME
FROM T1
where id In (
select id from T1 where (account, time ) in (
select account, max(time)
from t1
WHERE ACCOUNT IN ( 'Bill', 'Ann') group by account))

reverse the serial number of a grouped query

The following query orders votes based on how many times users voted... I would like to know the # in the queue of the specific user.
SELECT #s:=#s+1 serial_number, user_id, COUNT(slug_owner) as cnt
FROM `votes_queue`,(SELECT #s:= 0) AS s
GROUP BY slug_owner
ORDER BY cnt DESC
serial_number | user_id | cnt 
3 | 19 | 8
2 | 14 | 4
1 | 13 | 2
Essentially i need the numbers in the serial_number column to be reversed so I can tell that user 13 is #3 based on votes ..
Assign the serial numbers after generating the ordered count:
SELECT #s:=#s+1 serial_number, temp.*
FROM (
SELECT user_id, COUNT(slug_owner) as cnt
FROM `votes_queue`,
GROUP BY slug_owner
ORDER BY cnt DESC
) temp, (SELECT #s:= 0) AS s
The serial numbers are not reversed because GROUP BY and variables don't mix. An ORDER BY is fine. You can use #hjpotter92's solution, but the following also fixes the problem:
SELECT (#rn := #rn + 1) as serial_number, t.*
FROM (SELECT serial_number, user_id, COUNT(slug_owner) as cnt
FROM `votes_queue`
GROUP BY slug_owner
) t CROSS JOIN
(SELECT #rn := 0) params
ORDER BY cnt DESC;
I think the performance is the same. I am offering this answer just to clarify what the actual problem is.

MySQL COUNT of distinct values from two independent columns of a table

I want to get the COUNT of distinct values from two independent columns of a table.
My table is:
ID CR PB DB CB
-----------------------------
1 1000 1000
2 60000 1000
3 1000 (NULL)
4 1500000 13000
5 60000 12000
6 1000 (NULL)
expected output:
CR PB cnt_crpb DB CB cnt_dbcb
1000 3 1000 2
60000 2 13000 1
1500000 1 12000 1
I have tried to separate both columns CR PB and DB CB in two different tables and joined them using LEFT JOIN but does not give expected output as MySQL does not support FULL OUTER JOIN.
I have also tried using UNION which but gives result in rows.
Any help will be appreciated...
Thanks you.
I think you need to do this using union all:
select max(CRPB) as CRPB, max(CRPB_cnt) as CRPB_cnt, max(DBCB) as DBCB, max(DBCB_cnt) as DBCB_cnt
from ((select (#rn1 := #rn1 + 1) as rn, CRPB, count(CRPB) as CRPB_cnt, NULL as DBCB, NULL as DBCB_cnt
from table t cross join
(select #rn1 := 0) as vars
group by CRPB
) union all
(select (#rn2 := #rn2 + 1) as rn, NULL, NULL, DBCB, count(DBCB) as DBCB_cnt
from table t cross join
(select #rn2 := 0) as vars
group by DBCB
)
) x
group by rn;
This will guarantee results regardless of which list is longest.
Note you need to determine which column will produce more results aka either CR PB or DB CB whichever produces the most results will be the first select you want to do then left join the other. assuming that there is an uneven number of results from the two
SELECT `CR PB`, cnt_crpb, `DB CB`, cnt_dbcb
FROM
( SELECT `CR PB`, COUNT(*) as cnt_crpb, #a := #a + 1 as num_rows_a
FROM test_table
CROSS JOIN (SELECT #a := 0 ) temp
WHERE `CR PB` is not null
GROUP BY `CR PB`
)t
LEFT JOIN
( SELECT `DB CB`, COUNT(*) as cnt_dbcb, #b := #b + 1 as num_rows_b
FROM test_table
CROSS JOIN (SELECT #b := 0)temp1
WHERE `DB CB` is not null
GROUP BY `DB CB`
)t1 ON t1.num_rows_b = t.num_rows_a;
Fiddle Demo

Fetch 2nd Higest value from MySql DB with GROUP BY

I have a table tbl_patient and I want to fetch last 2 visit of each patient in order to compare whether patient condition is improving or degrading.
tbl_patient
id | patient_ID | visit_ID | patient_result
1 | 1 | 1 | 5
2 | 2 | 1 | 6
3 | 2 | 3 | 7
4 | 1 | 2 | 3
5 | 2 | 3 | 2
6 | 1 | 3 | 9
I tried the query below to fetch the last visit of each patient as,
SELECT MAX(id), patient_result FROM `tbl_patient` GROUP BY `patient_ID`
Now i want to fetch the 2nd last visit of each patient with query but it give me error
(#1242 - Subquery returns more than 1 row)
SELECT id, patient_result FROM `tbl_patient` WHERE id <(SELECT MAX(id) FROM `tbl_patient` GROUP BY `patient_ID`) GROUP BY `patient_ID`
Where I'm wrong
select p1.patient_id, p2.maxid id1, max(p1.id) id2
from tbl_patient p1
join (select patient_id, max(id) maxid
from tbl_patient
group by patient_id) p2
on p1.patient_id = p2.patient_id and p1.id < p2.maxid
group by p1.patient_id
id11 is the ID of the last visit, id2 is the ID of the 2nd to last visit.
Your first query doesn't get the last visits, since it gives results 5 and 6 instead of 2 and 9.
You can try this query:
SELECT patient_ID,visit_ID,patient_result
FROM tbl_patient
where id in (
select max(id)
from tbl_patient
GROUP BY patient_ID)
union
SELECT patient_ID,visit_ID,patient_result
FROM tbl_patient
where id in (
select max(id)
from tbl_patient
where id not in (
select max(id)
from tbl_patient
GROUP BY patient_ID)
GROUP BY patient_ID)
order by 1,2
SELECT id, patient_result FROM `tbl_patient` t1
JOIN (SELECT MAX(id) as max, patient_ID FROM `tbl_patient` GROUP BY `patient_ID`) t2
ON t1.patient_ID = t2.patient_ID
WHERE id <max GROUP BY t1.`patient_ID`
There are a couple of approaches to getting the specified resultset returned in a single SQL statement.
Unfortunately, most of those approaches yield rather unwieldy statements.
The more elegant looking statements tend to come with poor (or unbearable) performance when dealing with large sets. And the statements that tend to have better performance are more un-elegant looking.
Three of the most common approaches make use of:
correlated subquery
inequality join (nearly a Cartesian product)
two passes over the data
Here's an approach that uses two passes over the data, using MySQL user variables, which basically emulates the analytic RANK() OVER(PARTITION ...) function available in other DBMS:
SELECT t.id
, t.patient_id
, t.visit_id
, t.patient_result
FROM (
SELECT p.id
, p.patient_id
, p.visit_id
, p.patient_result
, #rn := if(#prev_patient_id = patient_id, #rn + 1, 1) AS rn
, #prev_patient_id := patient_id AS prev_patient_id
FROM tbl_patients p
JOIN (SELECT #rn := 0, #prev_patient_id := NULL) i
ORDER BY p.patient_id DESC, p.id DESC
) t
WHERE t.rn <= 2
Note that this involves an inline view, which means there's going to be a pass over all the data in the table to create a "derived tabled". Then, the outer query will run against the derived table. So, this is essentially two passes over the data.
This query can be tweaked a bit to improve performance, by eliminating the duplicated value of the patient_id column returned by the inline view. But I show it as above, so we can better understand what is happening.
This approach can be rather expensive on large sets, but is generally MUCH more efficient than some of the other approaches.
Note also that this query will return a row for a patient_id if there is only one id value exists for that patient; it does not restrict the return to just those patients that have at least two rows.
It's also possible to get an equivalent resultset with a correlated subquery:
SELECT t.id
, t.patient_id
, t.visit_id
, t.patient_result
FROM tbl_patients t
WHERE ( SELECT COUNT(1) AS cnt
FROM tbl_patients p
WHERE p.patient_id = t.patient_id
AND p.id >= t.id
) <= 2
ORDER BY t.patient_id ASC, t.id ASC
Note that this is making use of a "dependent subquery", which basically means that for each row returned from t, MySQL is effectively running another query against the database. So, this will tend to be very expensive (in terms of elapsed time) on large sets.
As another approach, if there are relatively few id values for each patient, you might be able to get by with an inequality join:
SELECT t.id
, t.patient_id
, t.visit_id
, t.patient_result
FROM tbl_patients t
LEFT
JOIN tbl_patients p
ON p.patient_id = t.patient_id
AND t.id < p.id
GROUP
BY t.id
, t.patient_id
, t.visit_id
, t.patient_result
HAVING COUNT(1) <= 2
Note that this will create a nearly Cartesian product for each patient. For a limited number of id values for each patient, this won't be too bad. But if a patient has hundreds of id values, the intermediate result can be huge, on the order of (O)n**2.
Try this..
SELECT id, patient_result FROM tbl_patient AS tp WHERE id < ((SELECT MAX(id) FROM tbl_patient AS tp_max WHERE tp_max.patient_ID = tp.patient_ID) - 1) GROUP BY patient_ID
Why not use simply...
GROUP BY `patient_ID` DESC LIMIT 2
... and do the rest in the next step?

Mysql query random order (pause and continue) question

Okay i am trying to create a mysql query that does this:
show 3 random records from table then after the 3th record show TEXT
and then show the same 3 items but other field (equaling to the items ofcourse) from same table.
eg table info:
--ids | titles------
10 | one
20 | two
30 | three
and the query results from the given example:
30 10 20 TEXT three one two
if anyone understand what i am asking,post your suggestion/asnwer
thanks for your time all :)
Just for kicks..
select t1.id, t2.id, t3.id, 'TEXT', t1.title, t2.title, t3.title
FROM
(
select #r := #r + 1 rownum, id
from (select #r:=0) initvar, (
select id
from tbl
order by rand()
limit 3
) X
) Y
join tbl t1 on Y.rownum=1 and t1.id = Y.id
join tbl t2 on Y.rownum=2 and t2.id = Y.id
join tbl t3 on Y.rownum=3 and t3.id = Y.id
You should really just do the query below, and do whatever display processing using the 3 rows returned, in whatever programming environment you use (Java/PHP/.Net etc).
select id, title
from tbl
order by rand()
limit 3
EDIT
To get the data in 7 different rows, you can use the below. I stress again that this is front-end display code. I will not use such SQL code in a production system.
select display
from
(
select sorter, rownum,
case when sorter=3 then title else id end display
from
(
select #r := #r + 1 rownum, id, title
from (select #r:=0) initvar,
(
select id, title
from tbl
order by rand()
limit 3
) X
) Y, (select 1 sorter union all select 3) dup
union all
select 2, 0, 'TEXT'
) Z
order by sorter, rownum
Example Output
7
2
1
TEXT
test 7 << title for id=7
test 2
test 1