I'm trying to get the sql to return the memberid 2 because I want to get the member with the most consecutive value of 1 even if memberid 1 as four times the value 1.
memberid position createdat
======== ======== =========
1 1 9/1/2001
1 1 8/1/2001
2 1 7/1/2001
2 1 6/1/2001
2 1 5/1/2001
1 1 4/1/2001
1 1 3/1/2001
Thanks a million times for any help.
You need to use parameters as counters. In the below #r is incremented for each row as long as the member is the same as the previous row (as defined by the order by), if it is not the same member #r resets to 1:
SELECT MemberID
FROM ( SELECT MemberID,
Position,
CreateDat,
#r:=IF(#m = MemberID, #r + 1, 1) AS Consec,
#m:= MemberID
FROM T,
(SELECT #r:= 0) r,
(SELECT #m:= 0) m
ORDER BY CreateDat DESC
) t
ORDER BY Consec DESC
LIMIT 1;
Example on SQL Fiddle
EDIT
With a slight tweek you can get a bit more information out to, such as when the consecutive period started and ended:
SELECT MemberID, FirstCreateDat, CreateDat, Consec
FROM ( SELECT MemberID,
Position,
CreateDat,
#r:=IF(#m = MemberID, #r + 1, 1) AS Consec,
#d:=IF(#m = MemberID, #d, CreateDat) AS FirstCreateDat,
#m:= MemberID
FROM T,
(SELECT #r:= 0) r,
(SELECT #m:= 0) m,
(SELECT #d:= CAST(NULL AS DATETIME)) d
ORDER BY CreateDat DESC
) t
ORDER BY Consec DESC
LIMIT 1;
Example on SQL Fiddle
Related
I have a transaction table with columns as sender, receiver, date and amount I want to find all the name of receivers whose records sum is atleast 1000 in no more than 3 transactions.
Example:
sender, receiver, date, amount
A, B, 2020-01-01, 500
C, B, 2020-01-01, 500
D, B, 2020-21-01, 200
A, C, 2021-01-01, 400
D, C, 2021-01-01, 60
A, D, 2021-01-01, 4000
C, A, 2019-01-01, 50
D, A, 2019-02-01, 50
B, A, 2019-03-01, 50
E, A, 2019-04-01, 50
Ans:
B, D. Answer in sorted name order
Explanation:
B receives 1000 in just 2 transactions. D received > 1000 in just 1 transaction.
I know how to group the records based on name but dont know how to find the sum of 3 records and check if more than 1000.
WITH cte AS (
SELECT receiver, amount,
ROW_NUMBER() OVER (PARTITION BY receiver ORDER BY amount DESC) rn
FROM transaction
)
SELECT receiver
FROM cte
WHERE rn <= 3
GROUP BY receiver
HAVING SUM(amount) >= 1000
You can group by receiver and use GROUP_CONCAT() to collect all amounts for each receiver in descending order in a comma separated list.
Then use SUBSTRING_INDEX to extract the top 3 amounts and add them to check if their sum is equal or greater than 1000:
SELECT receiver
FROM (
SELECT receiver,
COUNT(*) counter,
GROUP_CONCAT(amount ORDER BY amount DESC) amounts
FROM tablename
GROUP BY receiver
) t
WHERE SUBSTRING_INDEX(amounts, ',', 1) +
CASE WHEN counter > 1 THEN SUBSTRING_INDEX(SUBSTRING_INDEX(amounts, ',', 2), ',', -1) ELSE 0 END +
CASE WHEN counter > 2 THEN SUBSTRING_INDEX(SUBSTRING_INDEX(amounts, ',', 3), ',', -1) ELSE 0 END
>= 1000;
See the demo.
Of course this does not scale well for more than 3 amounts.
You can use mysql variables to count row number then filter based on row number like that
set #current_client := '';
set #row_count = 0;
select receiver, sum(amount) as total from (select receiver,amount,
case
when #current_client = receiver then #row_count := #row_count+1
when #current_client != receiver THEN #row_count := 1
end as tmp1,
case when #current_client != receiver THEN #current_client := receiver end as tmp2,
#row_count as row_number
FROM transaction order by transaction.receiver asc, transaction.amount desc
) as tmp
where row_number <= 3
group by receiver
having total >= 1000
demo here
The following applies to MySQL 8+ (the 5.7 was not in t he original question or the original tag).
You can use row_number() to order the transactions in descending order by amount. Then aggregate:
select receiver
from (select t.*,
row_number() over (partition by receiver order by amount desc) as seqnum
from t
) t
where seqnum <= 3
group by receiver
having sum(amount) >= 1000;
In older versions, you will probably need to enumerate the rows using variables, because you could have ties in the data:
select receiver
from (select t.*,
(#rn := if(#r = receiver, #rn + 1,
if(#r := receiver, 1, 1)
)
) as seqnum
from (select t.* from t order by receiver, amount desc) t cross join
(select #rn := 0, #r := '') params
) t
where seqnum <= 3
group by receiver
having sum(amount) >= 1000;
WITH A AS (
SELECT receiver, amount, ROW_NUMBER() OVER(PARTITION BY receiver ORDER BY receiver) AS ranking
FROM transactions
)
SELECT receiver, sum(amount) as total_received
FROM A
WHERE ranking IN (1,2,3)
group by receiver
HAVING sum(amount) >= 1000
How would I go about creating group_ids in the following example based on the area(s) the users are active in?
group_id rep_id area datebegin dateend
1 1000 A 1/1/15 1/1/16
1 1000 B 1/1/15 1/1/16
2 1000 C 1/2/16 12/31/99
In the table you can see that rep 1000 was active in both A and B between 1/15 and 1/16. How would I go about coding the group_id field to group by datebegin & dateend?
Thanks for any help.
You can use variables in order to enumerate groups of records having identical rep_id, datebegin, dateend values:
SELECT rep_id, datebegin, dateend,
#rn := IF(#rep_id <> rep_id,
IF(#rep_id := rep_id, 1, 1),
#rn + 1) AS rn
FROM (
SELECT rep_id, datebegin, dateend
FROM mytable
GROUP BY rep_id, datebegin, dateend) AS t
CROSS JOIN (SELECT #rep_id := 0, #rn := 0) AS v
ORDER BY rep_id, datebegin
Output:
rep_id, datebegin, dateend, rn
-----------------------------------
1000, 2015-01-01, 2016-01-01, 1
1000, 2016-02-01, 2099-12-03, 2
You can use the above query as a derived table and join back to the original table. rn field is the group_id field you are looking for.
You can use variables to assign groups. As you said, only if the date_begin and date_end exactly match for 2 rows, they would be in the same group. Else a new group starts.
select rep_id,area,date_begin,date_end,
,case when #repid <> rep_id then #rn:=1 --reset the group to 1 when rep_id changes
when #repid=rep_id and #begin=date_begin and #end=date_end then #rn:=#rn --if rep_id,date_begin and date_end match use the same #rn previously assigned
else #rn:=#rn+1 --else increment #rn by 1
end as group_id
,#begin:=date_begin
,#end:=date_end
,#repid:=rep_id
from t
cross join (select #rn:=0,#begin:='',#end:='',#repid:=-1) r
order by rep_id,date_begin,date_end
The above query includes variables in the output. To only get the group_id use
select rep_id,area,date_begin,date_end,group_id
from (
select rep_id,area,date_begin,date_end
,case when #repid <> rep_id then #rn:=1
when #repid=rep_id and #begin=date_begin and #end=date_end then #rn:=#rn
else #rn:=#rn+1
end as group_id
,#begin:=date_begin
,#end:=date_end
,#repid:=rep_id
from t
cross join (select #rn:=0,#begin:='',#end:='',#repid:=-1) r
order by rep_id,date_begin,date_end
) x
I have a below table and wants to select only last 2 entries of all users.
Source table:
-------------------------------------
UserId | QuizId(AID)|quizendtime(AID)|
--------------------------------------
1 10 2016-5-12
2 10 2016-5-12
1 11 2016-6-12
2 12 2016-8-12
3 12 2016-8-12
2 13 2016-8-12
1 14 2016-9-12
3 14 2016-9-12
3 11 2016-6-12
Expected output is like, (should list only recent 2 quizid entries for all users)
-------------------------------------
UserId | QuizId(AID)|quizendtime(AID)|
--------------------------------------
1 14 2016-9-12
1 11 2016-6-12
2 13 2016-8-12
2 12 2016-8-12
3 14 2016-9-12
3 12 2016-8-12
Any idea's to produce this output.
Using MySQL user defined variables you can accomplish this:
SELECT
t.UserId,
t.`QuizId(AID)`,
t.`quizendtime(AID)`
FROM
(
SELECT
*,
IF(#sameUser = UserId, #a := #a + 1 , #a := 1) row_number,
#sameUser := UserId
FROM your_table
CROSS JOIN (SELECT #a := 1, #sameUser := 0) var
ORDER BY UserId , `quizendtime(AID)` DESC
) AS t
WHERE t.row_number <= 2
Working Demo
Note: If you want at most x number of entries for each user then change the condition in where clause like below:
WHERE t.row_number <= x
Explanation:
SELECT
*,
IF(#sameUser = UserId, #a := #a + 1 , #a := 1) row_number,
#sameUser := UserId
FROM your_table
CROSS JOIN (SELECT #a := 1, #sameUser := 0) var
ORDER BY UserId , `quizendtime(AID)` DESC;
This query sorts all the data in ascending order of userId and descending order of quizendtime(AID).
Now take a walk on this (multi) sorted data.
Every time you see a new userId assign a row_number (1). If you see the same user again then just increase the row_number.
Finally filtering only those records which are having row_number <= 2 ensures the at most two latest entries for each user.
EDIT: As Gordon pointed out that the evaluation of expressions using user defined variables in mysql is not guaranteed to follow the same order always so based on that the above query is slightly modified:
SELECT
t.UserId,
t.`QuizId(AID)`,
t.`quizendtime(AID)`
FROM
(
SELECT
*,
IF (
#sameUser = UserId,
#a := #a + 1,
IF(#sameUser := UserId, #a := 1, #a:= 1)
)AS row_number
FROM your_table
CROSS JOIN (SELECT #a := 1, #sameUser := 0) var
ORDER BY UserId , `quizendtime(AID)` DESC
) AS t
WHERE t.row_number <= 2;
WORKING DEMO V2
User-defined variables are the key to the solution. But, it is very important to have all the variable assignments in a single expression. MySQL does not guarantee the order of evaluation of expressions in a select -- and, in fact, sometimes processes them in different orders.
select t.*
from (select t.*,
(#rn := if(#u = UserId, #rn + 1,
if(#u := UserId, 1, 1)
)
) as rn
from t cross join
(select #u := -1, #rn := 0) params
order by UserId, quizendtime desc
) t
where rn <= 2;
I need to display the top4 and lease 4 rows based Amount and group by agentId but here rank is showing wrong
And how to show least(last 4 rows?)
schema:
AgentID amount
1 3000
1 3200
2 9000
SELECT Agentid,SUM(AmountRecevied) as Amount,#rownum := #rownum + 1 AS Rank
FROM collection ,(SELECT #rownum := 0) r
GROUP BY AgentID
ORDER BY Amount DESC
limit 4;
Try this way:
SELECT T.Agentid,T.Amount, #rownum := #rownum - 1 AS Rank
FROM
(SELECT Agentid,SUM(AmountRecevied) as Amount
FROM collection
GROUP BY AgentID
ORDER BY Amount
LIMIT 4) T,(SELECT #rownum := 11) r
Try this :
SELECT
C.*,
#rownum := #rownum + 1 AS Rank
FROM (
SELECT
Agentid,
SUM(AmountRecevied) as Amount
FROM collection
GROUP BY AgentID
ORDER BY Amount DESC
LIMIT 4
) AS C, (SELECT #rownum := 0) r
In case of amount matching for different agentids, then, I believe, ranks should be assigned same.
This solution should help you:
select
/*case when rank>6 then '' else rank end as */
rank, agentid, amount
from (
select agentid, #ca:=amount amount
, case when #pa=#ca then #rn:=#rn
else #rn:=( #rn + 1 )
end as rank
, #pa:=#ca as temp_currAmount
from ( select agentid, sum(amount) as amount
from agents
group by agentid
order by amount
) amounts_summary,
(select #pa:=0, #c0:=0,
#rn:=0) row_nums
order by rank desc
) results
where rank > 6
order by rank
;
Demo # MySQL 5.6.6 Fiddle
And if you want no display ranks greater than '6' but empty, then
just uncomment the case line and comment the where condition line
select
case when rank>6 then '' else rank end as
rank, agentid, amount
from (
select agentid, #ca:=amount amount
, case when #pa=#ca then #rn:=#rn
else #rn:=( #rn + 1 )
end as rank
, #pa:=#ca as temp_currAmount
from ( select agentid, sum(amount) as amount
from agents
group by agentid
order by amount
) amounts_summary,
(select #pa:=0, #ca:=0,
#rn:=0) row_nums
order by rank
) results
-- where rank > 6
order by rank
;
You can modify asc or desc as required.
Demo # MySQL 5.6.6 Fiddle
Have a table photos
photos.id
photos.user_id
photos.order
A) Is it possible via a single query to group all photos by user and then update the order 1,2,3..N ?
B) added twist, what if some of the photos already have an order value associated? Make sure that the new photos.order never gets repeated and fills in ant orders lower or higher than those existing (as best as possible)
My only thought is just to run a script on this and loop through it and re'order' everything?
photos.id int(10)
photos.created_at datetime
photos.order int(10)
photos.user_id int(10)
Right now data may look like this
user_id = 1
photo_id = 1
order = NULL
user_id = 2
photo_id = 2
order = NULL
user_id = 1
photo_id = 3
order = NULL
the desired result would be
user_id = 1
photo_id = 1
order = 1
user_id = 2
photo_id = 2
order = 1
user_id = 1
photo_id = 3
order = 2
A)
You can use a variable that increments with each row and resets with each user_ID to get the row count.
SELECT ID,
User_ID,
`Order`
FROM ( SELECT #r:= IF(#u = User_ID, #r + 1,1) AS `Order`,
ID,
User_ID,
#u:= User_ID
FROM Photos,
(SELECT #r:= 1) AS r,
(SELECT #u:= 0) AS u
ORDER BY User_ID, ID
) AS Photos
Example on SQL Fiddle
B)
My First solution was to just add Order to the sorting that adds the row number, therefore anything with an Order Gets sorted by its order first, but this only works if your ordering system has no gaps and starts at 1:
SELECT ID,
User_ID,
RowNumber AS `Order`
FROM ( SELECT #r:= IF(#u = User_ID, #r + 1,1) AS `RowNumber`,
ID,
User_ID,
#u:= User_ID
FROM Photos,
(SELECT #i:= 1) AS r,
(SELECT #u:= 0) AS u
ORDER BY User_ID, `Order`, ID
) AS Photos
ORDER BY `User_ID`, `Order`
Example using Order Field
ORDERING WITH GAPS
I have eventually found a way of maintaining the sort order even when there are gaps in the sequence.
SELECT ID, User_ID, `Order`
FROM Photos
WHERE `Order` IS NOT NULL
UNION ALL
SELECT Photos.ID,
Photos.user_ID,
Numbers.RowNum
FROM ( SELECT ID,
User_ID,
#r1:= IF(#u1 = User_ID,#r1 + 1,1) AS RowNum,
#u1:= User_ID
FROM Photos,
(SELECT #r1:= 0) AS r,
(SELECT #u1:= 0) AS u
WHERE `Order` IS NULL
ORDER BY User_ID, ID
) AS Photos
INNER JOIN
( SELECT User_ID,
RowNum,
#r2:= IF(#u2 = User_ID,#r2 + 1,1) AS RowNum2,
#u2:= User_ID
FROM ( SELECT DISTINCT p.User_ID, o.RowNum
FROM Photos AS p,
( SELECT #i:= #i + 1 AS RowNum
FROM INFORMATION_SCHEMA.COLLATION_CHARACTER_SET_APPLICABILITY,
( SELECT #i:= 0) AS i
) AS o
WHERE RowNum <= (SELECT COUNT(*) FROM Photos P1 WHERE p.User_ID = p1.User_ID)
AND NOT EXISTS
( SELECT 1
FROM Photos p2
WHERE p.User_ID = p2.User_ID
AND o.RowNum = p2.`Order`
)
AND p.`Order` IS NULL
ORDER BY User_ID, RowNum
) AS p,
(SELECT #r2:= 0) AS r,
(SELECT #u2:= 0) AS u
ORDER BY user_ID, RowNum
) AS numbers
ON Photos.User_ID = numbers.User_ID
AND photos.RowNum = numbers.RowNum2
ORDER BY User_ID, `Order`
However as you can see this is pretty complicated. This works by treating those with an order value separately to those without. The top query just ranks all photos with no order value in order of ID for each user. The bottom query uses a cross join to generates a sequential list from 1 to n for each user ID (up to the number of entries for each User_ID). So with a data set like this:
ID User_ID Order
1 1 NULL
2 2 NULL
3 1 NULL
4 1 1
5 1 3
6 2 2
7 2 3
It would generate
UserID RowNum
1 1
1 2
1 3
1 4
2 1
2 2
2 3
It then uses NOT EXISTS to elimiate all combinations already used by Photos with a non null order, and ranked in order of RowNum partitioned by User_ID giving
UserID RowNum Rownum2
1 2 1
1 4 2
2 1 1
The RowNum2 value can then be matched with the rownum value achieved in the from subquery, giving the correct order value. Long winded, but it works.
Example on SQL Fiddle
Worked for me. I needed to increment version grouping by 4 fields (host, folder, fileName, status) and sort by 1 (downloadedAtTicks).
This is is my SELECT
SET #status := NULL;
SET #version := NULL;
SELECT
id,
host,
folder,
fileName,
status,
downloadedAtTicks,
version,
IF(IF(status IS NULL, 0, status) = #status, #version := #version + 1, #version := 0) AS varVersion,
#status := IF(status IS NULL, 0, status) AS varStatus
FROM csvsource
ORDER BY host, folder, fileName, status, downloadedAtTicks;
And this is my UPDATE
SET #status := NULL;
SET #version := NULL;
UPDATE
csvsource csv,
(SELECT
id,
IF(IF(status IS NULL, 0, status) = #status, #version := #version + 1, #version := 0) AS varVersion,
#status := IF(status IS NULL, 0, status) AS varStatus
FROM csvsource
ORDER BY host, folder, fileName, status, downloadedAtTicks) AS sub
SET
csv.version = sub.varVersion
WHERE csv.id = sub.id;