Need some help with mysql.
I have a table full of bids, i need to check if there are more than 3 bids from the same user that are one after another.
Here is some code for example:
$all = sql_get('select all bids from bids where auction_id = 1 order by amount asc');
$total = 0;
foreach ($all as $bid) {
if ($bid->user_id == $user->id) {
if (++$total <= 3) continue;
$bid->burned = true;
sql_store($bid);
show_error('you cant have more than 4 bids one after another, the last bid was burned.');
} else {
$total = 0;
}
}
Is there a way to do that in a single query ?
SELECT user_id, CASE WHEN #user_id = user_id
THEN #rownum := #rownum + 1
ELSE ((#user_id := user_id) * 0) + (#rownum := 1) END AS rownum
FROM ( SELECT user_id
FROM bids, (SELECT #rownum := 0, #user_id := NULL) AS vars
WHERE auction_id = 1
ORDER BY created DESC) AS h
HAVING user_id = '{$user->id}' AND rownum >= 3
Simply parse user_id into query and you will know if there are 3 in a row. This asumes that you save the time the rows were created with the created column.
Why not just do a count query and pass the user_id in the WHERE clause?
$query = "SELECT COUNT(bids) AS total_bids FROM bids WHERE auction_id = 1 AND user_id = '$user->id' ORDER BY amount";
Related
i am very new to mysql and tried to rank top records based on two fields
sharing you the current script and output along with the desired output
Current Script :
select u_rank, c_rank,u_name,c_name,
#curRank := #curRank + 1 AS rank
from (
SELECT u_rank, c_rank,u_name,c_name
from abc
) a,
(
select #curRank := 0
) r
order by c_rank,u_rank
Current OutpuT
MY DESIRED OUTPUT IS
Please help
If I am decifering your question correctly, you are wanting to rank the c_ranks ascending or each u_name and then order by that rank then the u_rank. After that you need to get a rank for all records in that order. I'm not sure that the way I just worded that made any sense... but the following should get that result for you:
SET #rank1 = 0;
SET #rank2 = 0;
SET #u_name = '';
SELECT u_rank, c_rank, u_name, c_name, #rank2 := #rank2 + 1 AS rank
FROM (
SELECT u_rank,
c_rank,
c_name,
#rank1 := (#rank1 + 1 - IF(#u_name = u_name, 0, #rank1)) AS rank1,
#u_name := u_name AS u_name
FROM abc
ORDER BY u_name, c_rank
) foo
ORDER BY rank1, u_rank;
I have a small system that accepts votes, on my query I'm selecting the vote(name of user) and the total of votes for this user.
My Idea is to only display the user with more votes, with the query that i have I know that i can add ORDER BY and get the first value which will be the one with the highest umber of votes.
But i'm trying to identifie if two persons have the number of votes. any ideas how can i do that?
I'm using cgi.
my $connection = $MY_CONNECTION->prepare("SELECT voto, COUNT(*) total FROM votos WHERE mes = 12 GROUP BY vote HAVING COUNT(*) > 0 ORDER BY COUNT(*)");
$connection->execute || print "ERROR 1";
my #resultados;
my $info = '';
while ($info = $connection->fetchrow_hashref()) {
my $nombre = $info->{voto};
my $totalVotos = $info->{total};
my $winner = "";
my $temp2 = "";
my $temp = $totalVotos ;
if ($temp2 => $temp) {
$temp2 = $totalVotos ;
$winner = $nombre ;
}
print "<p><strong>Winner: </strong> $winner </p> ";
print "<hr>";
}
You can return everyone who has the highest number of votes (and how many votes) with:
select voto, count(*) as total from votos
where mes = 12
group by voto -- I assume "vote" in your question was typo
having total = (
select max(ct) from (
select voto,count(*) as ct from votos group by voto
)
)
and total > 0 -- from your original but not sure it can fail
;
You may also be able to use a rank() function.
Without modifying your SQL, you could change the perl to something like:
# ...
# initialisation shouldn't be inside while loop
my $winner = "";
my $temp2 = "";
while ($info = $connection->fetchrow_hashref()) {
my $nombre = $info->{voto};
my $totalVotos = $info->{total};
if ($temp2 < $totalVotos) { # your original "=>" seems wrong
$temp2 = $totalVotos;
#winner = $nombre;
elsif ($temp2 == $totalVotos) {
push #winner, $nombre;
}
}
# postprocessing shouldn't be inside while loop
$winner = join(", ", #winner); # or whatever
print "<p><strong>Winner: </strong> $winner </p> ";
print "<hr>";
use subquery then apply order by, to limit the result by 2 add limit 2.
select t1.voto, t1.ct as total from (
SELECT voto, count(1) ct FROM votos
WHERE mes = 12
GROUP BY voto
HAVING COUNT(1) > 1) t1
order by t1.total desc limit 2
I would recommend rank():
SELECT voto, total
FROM (SELECT voto, COUNT(*) as total,
RANK() OVER (ORDER BY COUNT(*) DESC) as seqnum
FROM votos v
WHERE mes = 12
GROUP BY voto
) v
WHERE seqnum = 1;
Note that HAVING COUNT(*) > 0 does nothing. In your query, it is not possible for COUNT(*) to be equal to or less than 0.
I have a table that stores messages sent to users, the layout is as follows
id (auto-incrementing) | message_id | user_id | datetime_sent
I'm trying to find the first N message_id's that each user has received, but am completely stuck. I can do it easily on a per-user basis (when defining the user ID in the query), but not for all users.
Things to note:
Many users can get the same message_id
Message ID's aren't sent sequentially (i.e. we can send message 400 before message 200)
This is a read only mySQL database
EDIT: On second thought I removed this bit but have added it back in since someone was kind enough to work on it
The end goal is to see what % of users opened one of the first N messages they received.
That table of opens looks like this:
user_id | message_id | datetime_opened
This is an untested answer to the original question (with 2 tables and condition on first 5):
SELECT DISTINCT user_id
FROM (
SELECT om.user_id,
om.message_id,
count(DISTINCT sm2.message_id) messages_before
FROM opened_messages om
INNER JOIN sent_messages sm
ON om.user_id = sm.user_id
AND om.message_id = sm.message_id
LEFT JOIN sent_messages sm2
ON om.user_id = sm2.user_id
AND sm2.datetime_sent < sm.datetime_sent
GROUP BY om.user_id,
om.message_id
HAVING messages_before < 5
) AS base
The subquery joins in sm2 to count the number of preceding messages that were sent to the same user, and then the having clause makes sure that there are fewer than 5 earlier messages sent. As for the same user there might be multiple messages (up to 5) with that condition, the outer query only lists the unique users that comply to the condition.
To get the first N (here 2) messages, try
SELECT
user_id
, message_id
FROM (
SELECT
user_id
, message_id
, id
, (CASE WHEN #user_id != user_id THEN #rank := 1 ELSE #rank := #rank + 1 END) AS rank,
(CASE WHEN #user_id != user_id THEN #user_id := user_id ELSE #user_id END) AS _
FROM (SELECT * FROM MessageSent ORDER BY user_id, id) T
JOIN (SELECT #cnt := 0) c
JOIN (SELECT #user_id := 0) u
) R
WHERE rank < 3
ORDER BY user_id, id
;
which uses a RANK substitute, derived from #Seaux response to Does mysql have the equivalent of Oracle's “analytic functions”?
To extend this to your original question, just add the appropriate calculation:
SELECT
COUNT(DISTINCT MO.user_id) * 100 /
(SELECT COUNT(DISTINCT user_id)
FROM (
SELECT
user_id
, message_id
, id
, (CASE WHEN #user_id != user_id THEN #rank := 1 ELSE #rank := #rank + 1 END) AS rank,
(CASE WHEN #user_id != user_id THEN #user_id := user_id ELSE #user_id END) AS _
FROM (SELECT * FROM MessageSent ORDER BY user_id, id) T
JOIN (SELECT #cnt := 0) c
JOIN (SELECT #user_id := 0) u
) R2
WHERE rank < 3
) AS percentage_who_read_one_of_the_first_messages
FROM MessageOpened MO
JOIN
(SELECT
user_id
, message_id
FROM (
SELECT
user_id
, message_id
, id
, (CASE WHEN #user_id != user_id THEN #rank := 1 ELSE #rank := #rank + 1 END) AS rank,
(CASE WHEN #user_id != user_id THEN #user_id := user_id ELSE #user_id END) AS _
FROM (SELECT * FROM MessageSent ORDER BY user_id, id) T
JOIN (SELECT #cnt := 0) c
JOIN (SELECT #user_id := 0) u
) R
WHERE rank < 3) MR
ON MO.user_id = MR.user_id
AND MO.message_id = MR.message_id
;
With no CTEs in MySQL, and being in a read-only database - I see no way around having the above query twice in the statement.
See it in action: SQL Fiddle.
Please comment if and as this requires adjustment / further detail.
I am trying to generate row number for each row selected from my database but it seems that the row number follows the sequence of the table before it's arranged (order by).
Actual table
https://www.dropbox.com/s/otstzak20yxcgt6/test1.PNG?dl=0
After query
https://www.dropbox.com/s/i9jaoy04vq6u2zh/test2.PNG?dl=0
Code
SET #row_num = 0;
SELECT #row_num := #row_num + 1 as Position, Student.Stud_ID, Student.Stud_Name, Student.Stud_Class, SUM(Grade.Percentage) AS Points
FROM Student, Student_Subject, Grade
WHERE Student.Stud_ID = Student_Subject.Stud_ID
AND Student_Subject.Stud_Subj_ID = Grade.Stud_Subj_ID
AND Student.Stud_Form = '1'
AND Grade.Quarter = '1'
GROUP BY Student.Stud_ID
ORDER BY Points DESC
Pls help. Looking forward to receiving replies from yall. Thanks!
Try an inner select, so the row number will be generated after the ORDER BY like so:
SET #row_num = 0;
SELECT #row_num := #row_num + 1 as Position, s.*
FROM
(
SELECT
Student.Stud_ID, Student.Stud_Name, Student.Stud_Class, SUM(Grade.Percentage) AS Points
FROM Student, Student_Subject, Grade
WHERE Student.Stud_ID = Student_Subject.Stud_ID
AND Student_Subject.Stud_Subj_ID = Grade.Stud_Subj_ID
AND Student.Stud_Form = '1'
AND Grade.Quarter = '1'
GROUP BY Student.Stud_ID
ORDER BY Points DESC
) AS s;
I have this query:
SET #current_group = NULL;
SET #current_count = 0;
SELECT user_id, MIN( created_at ) as created_at, CASE WHEN #current_group = user_id THEN #current_count WHEN #current_group := user_id THEN #current_count := #current_count + 1 END AS c
FROM notifies
G ROUP BY user_id, c
ORDER BY id desc LIMIT 0 , 10
If i launch it it works
but if i put it in a find_by_sql method like:
Notify.find_by_sql("SET #current_group = NULL; SET #current_count = 0; SELECT user_id, MIN( created_at ) as created_at, CASE WHEN #current_group = user_id THEN #current_count WHEN #current_group := user_id THEN #current_count := #current_count + 1 END AS c FROM notifies GROUP BY user_id, c ORDER BY id desc LIMIT 0 , 10")
It returns this error:
Mysql2::Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'SET #current_count = 0; SELECT user_id, MIN( created_at ) as created_at, CASE WH' at line 1:
How can i do?
thanks
It's because find_by_sql only works with a single statement.
Setting variables happen in separate statements. set specifically is in the Database Administration section, number 12.4.4, and select is in Data Manipulation section, number 12.2.7. Consoles (usually) allow multiple statements, and keep the variables around, but ActiveRecord queries do not.
To allow multiple statements, I think you have to maintain a persistent connection with the database, which Rails doesn't do (edit: normally). But I'm not certain about that - if anyone else knows, I'd love a more definite reason.
Edit: actually, I have a solution for you. Try this:
items = YourModel.transaction do
YourModel.connection.execute("SET #current_group = NULL;")
YourModel.connection.execute("SET #current_count = 0;")
# this is returned, because it's the last line in the block
YourModel.find_by_sql(%Q|
SELECT user_id, MIN( created_at ) as created_at, CASE WHEN #current_group = user_id THEN #current_count WHEN #current_group := user_id THEN #current_count := #current_count + 1 END AS c
FROM notifies
GROUP BY user_id, c
ORDER BY id desc LIMIT 0 , 10
|)
end
All those run in a single transaction, and any variables / settings inside the transaction block will persist between queries. You're still bound to a single statement per query though. There might be a way to do it without an actual transaction wrapping the whole set, but I haven't looked for it - most likely you want one, or you now have a very specific thing to look for if you know you don't.
Enjoy!
Accordingly to the API it is this syntax:
Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]
So you might want to try putting it into an array instead of using parenthesis.
Notify.find_by_sql ["SET #current_group = NULL; SET #current_count = 0; SELECT user_id, MIN( created_at ) as created_at, CASE WHEN #current_group = user_id THEN #current_count WHEN #current_group := user_id THEN #current_count := #current_count + 1 END AS c FROM notifies GROUP BY user_id, c ORDER BY id desc LIMIT 0 , 10"]