Finding the higest voted Question in last week. (sql query) - php

I have a 3 sql tables:
qotwQuestion1a(QuestionId [primarykey], Question, MemberId, PostDate);
qotwVote1a (QuestionId [primarykey], MemberId [primarykey], Vote1a);
qotwMember (MemberId [primarykey], Name, Password, emailId);
I want to write a sql query to find the QuetionId and MemberId of the Question that has the highest vote in the last week. I have written this query in php, but it gives me a wrong result:
$result6 = mysql_query("SELECT MAX(Vote1a) AS highestVote, * FROM qotwMember, qotwQuestion1a , qotwVote1a
WHERE qotwMember.MemberId=qotwQuestion1a.MemberId
AND qotwQuestion1a.QuestionId=qotwVote1a.QuestionId
AND qotwQuestion1a.MemberId=qotwVote1a.MemberId
AND PostDate>='".$startofweek."' AND PostDate<='".$endofweek."'
ORDER BY qotwQuestion1a.QuestionId DESC ");
while($row6 = mysql_fetch_array($result6))
{
echo "The highest voted question of the last week is: "; echo $row6['highestVote']; echo $row6['MemberId'] . " " . $row6['Name'] . " " . $row6['Password'] . " " . $row6['PostDate'] . " " . $row6['Question']." ".$row6['QuestionId']." ".$row6['Vote1a'];
echo "<br />";
}
The $startofweek and $endofweek give the date of the beginning of the last week and end of the last week.
Can someone help me with this, please.
Best
Zeeshan

SELECT *
FROM (
SELECT q.QuestionId, q.MemberID
FROM qotwQuestion1a q
JOIN qotwVote1a v
ON v.QuestionID = q.QuestionID
WHERE PostDate BETWEEN $startdate AND $enddate
GROUP BY
q.questionID
ORDER BY
COUNT(*) DESC
LIMIT 1
) qo
JOIN qotwMember m
ON m.MemberID = q.MemberID

Well, first of all, using MAX() without groupping is .. useless, you don't need it in this case. Second, if you want your results ordered from highest voted down to lowest voted, why don't you order by Vote1a and just take the first result with a LIMIT clause.

Hope you're dealing with ties somewhere ;)
And questions with no answers, for that matter.
Other than that...it looks like you're matching the member to the question, which might not make sense if your tables are set up the way they appear to be.

Try this:
SELECT Vote1a AS highestVote, *
FROM qotwMember, qotwQuestion1a , qotwVote1a
WHERE qotwMember.MemberId=qotwQuestion1a.MemberId
AND qotwQuestion1a.QuestionId=qotwVote1a.QuestionId
AND qotwQuestion1a.MemberId=qotwVote1a.MemberId
AND PostDate>='".$startofweek."'
AND PostDate<='".$endofweek."'
GROUP BY Votela, *
HAVING MAX(Votela)
ORDER BY qotwQuestion1a.QuestionId DESC");
Not too sure on the syntax of this one but the basic idea is to have a query that can get all records, and then at the last minute, isolate it to just those records having the MAX(Votela).
I would also recommend not using * unless you absolutely need it. I know it can be a pain if you want most of the columns in the tables, but it is always best practice to only select the columns you need.

Related

Querying efficiently

I have two tables: Exam (ExamID, Date, Modality) and CT(ctdivol, ExamID(FK)) with the attributes in brackets.
Note: CT table has about 100 000 entries.
I want to calculate the average of ctdivol in a specific interval of dates.
I have this code that works but is too slow:
function get_CTDIvolAVG($min, $max) {
$values = 0;
$number = 0;
$query = "SELECT (unix_timestamp(date)*1000), examID
from exam use index(dates)
where modality = 'CT'
AND (unix_timestamp(date)*1000) between '" . $min . "' AND '" . $max . "';";
$result = mysql_query($query) or die('Query failed: ' . mysql_error());
while($line = mysql_fetch_array($result, MYSQL_ASSOC)) {
$avg = "SELECT SUM(ctdivol_mGy), count(ctdivol_mGy)
from ct use index(ctd)
where examID ='" . $line["examID"] ."'
AND ctdivol_mGy>0;";
$result1 = mysql_query($avg) or die('Query failed: ' . mysql_error());
while ($ct = mysql_fetch_array($result1, MYSQL_ASSOC)) {
$values = $values + floatval($ct["SUM(ctdivol_mGy)"]);
$number = $number + floatval($ct["count(ctdivol_mGy)"]);
}
}
if ($number!=0) {
echo $values/$number;
}
}
How can I make it faster?
Use EXPLAIN to see the query execution plan.
For that first query, MySQL can't make effective use of a index range scan operation. That expression in the WHERE clause has to be evaluated for every row in the table. We get better performance when we do the comparison to a bare column. Do the manipulation on the literal side... get those values converted to the datatype of the column you're comparing to.
WHERE e.date BETWEEN expr1 AND expr2
For expr1, you need an expression that converts your $min value into a datetime. Just be careful of timezone conversions. I think this might do what you need for expr1:
FROM_UNIXTIME( $min /1000)
Something like:
WHERE e.date BETWEEN FROM_UNIXTIME( $min /1000) AND FROM_UNIXTIME( $max /1000)
Then we should see MySQL able to make effective use of an index with leading column of date. The EXPLAIN output should show range for the access type.
If the number of columns being returned is a small subset, consider a covering index. Then the EXPLAIN will show "Using index", which means the query can be satisfied entirely from the index, with no lookups to pages in the underlying table.
Secondly, avoid running queries multiple times in a loop. It is usually more efficient to run a single query that returns a single resultset, because of the overhead of sending the SQL to the database, that database parsing the SQL text, for valid syntax (keywords in the right places), valid semantics (identifiers reference valid objects), considering possible access paths and determining which is lowest cost, then executing the query plan, obtaining metadata locks, generating the resultset, returning that to the client, and then cleaning up. It's not noticeable for a single statement, but when you start running a lot of statements in a tight loop, it starts to add up. Couple that with an inefficient query, and it starts to get really noticeable.
IF examID column in exam is unique and not null (or its the PRIMARY KEY of exam, then it looks like you could use a single query, like this:
SELECT UNIX_TIMESTAMP(e.date)*1000 AS `date_ts`
, e.examID AS `examID`
, SUM(ct.ctdivol_mGy) AS `SUM(ctdivol_mGy)`
, COUNT(ct.ctdivol_mGy) AS `count(ctdivol_mGy)`
FROM exam e
LEFT
JOIN ct
ON ct.examid = e.examID
AND ct.ctdivol_mGy > 0
WHERE e.modality = 'CT'
AND e.date >= FROM_UNIXTIME( $min /1000)
AND e.date <= FROM_UNIXTIME( $max /1000)
GROUP
BY e.modality
, e.date
, e.examID
ORDER
BY e.modality
, e.date
, e.examID
For best performance of that, you'd want covering indexes:
... ON exam (modality, date, examID)
... ON ct (examID, ctdivol_mGy)
We'd want to see the EXPLAIN output; we'd expect that MySQL could make use of the index on exam to do the GROUP BY (and avoiding a "Using filesort" operation), and also make use of a ref operation on the index to ct.
To reiterate... that query requires that examID be the PRIMARY KEY of the exam table (or at least be guaranteed to be unique and non-null). Otherwise, the result from that can be different than the original code. Absent that gurantee, we could use either an inline view, or subqueries in the SELECT list. But in terms of performance, we don't want to go there without good reason to.
That's just some general ideas, not a hard and fast "this will be faster".
You can write a join on the first table to a subquery table by exam_id:
$query = "SELECT (unix_timestamp(date)*1000) as time_calculation, ed.examID, inner_ct.inner_sum, inner_ct.inner_count "
" FROM exam ed,"
. " ( SELECT SUM(ctdivol_mGy) as inner_sum, count(ctdivol_mGy) as inner_count, examID"
. " FROM ct"
. " WHERE ctdivol_mGy>0 ) inner_ct"
. " WHERE ed.modality = 'CT' AND time_calculation between"
. " '$min' and '$max'"
. " AND ed.examId = inner_ct.examID";
The ( SELECT . . .) inner_ct creates an in memory table you can join from. Useful if you're selecting composed data (sums in your case) across a join.
Conversely, you can use the following syntax:
$query = "SELECT (unix_timestamp(date)*1000) as time_calculation, ed.examID, inner_ct.inner_sum, inner_ct.inner_count "
" FROM exam ed,"
. " LEFT JOIN ( SELECT SUM(ctdivol_mGy) as inner_sum, count(ctdivol_mGy) as inner_count, examID"
. " FROM ct"
. " WHERE ctdivol_mGy>0 ) inner_ct"
. " ON ed.examID = inner_ct.examID"
. " WHERE ed.modality = 'CT' AND time_calculation between"
. " '$min' and '$max'";
You have not provided sample data in the question so we resort to assumptions in an attempt to answer. If there is only one exam row for many rows in ct - but an exam row can exist that has no ct rows at all - then this single query should provide the results required.
SELECT
exam.examID
, (unix_timestamp(exam.date) * 1000
, SUM(ct.ctdivol_mGy)
, COUNT(ct.ctdivol_mGy)
FROM exam
LEFT OUTER JOIN ct on exam.examID = ct.examID AND ct.ctdivol_mGy > 0
WHERE exam.modality = 'CT'
AND exam.date >= #min AND exam.date < #max
GROUP BY
exam.examID
, (unix_timestamp(exam.date) * 1000)
;
Note I am not attempting the PHP code, just concentrating on the SQL. I have used #min and #max to indicate the 2 dates required in the where clause. These should be of the same data type as the column exam.date so do those calculations in PHP before adding into the query string.
I want to calculate the average of ctdivol in a specific interval of
dates.
If you are trying to return a single figure, then this should help:
SELECT
AVG(ct.ctdivol_mGy)
FROM exam
INNER JOIN ct on exam.examID = ct.examID AND ct.ctdivol_mGy > 0
WHERE exam.modality = 'CT'
AND exam.date >= #min AND exam.date < #max
;
Note for this variant we probably don't need a left join (but again due to a lack of sample data and expected result that is an assumption).

how to solve this mysql query

I have 3 mysql tables
user(u_id(p),name),
team(t_id(p),u_id(f),t_name,t_money,days_money) and
history(t_id(f),day,d_money).
Now I have to display leaderboard using php.
I tried this.
SELECT t_id FROM team;
got result.
then,
in for loop
foreach($tid_all as $tid)
{
$que = $db_con->prepare("SELECT d_money, t_name FROM team, history WHERE t_id= '".$tid['t_id']."' && day='1'");
$que->execute();
while($info = $que->fetch(PDO::FETCH_NUM))
{
echo "<tr>";
echo "<td>".$info[0]."</td>";
echo "<td>".$info[1]."</td>";
echo "</tr>";
}
}
but it didnt work. any solution?
Solution 1:
i tried this and it worked.
`SELECT d_money, t_name FROM team, history WHERE history.t_id=$tid['t_id'] AND team.t_id=history.t_id`
is it correct way or not?
thanks everyone for help.
Question : is it possible to order the result table by d_money? i want it in descending order.
Replace && with AND.Try like this :
"SELECT d_money, t_name FROM team, history WHERE t_id= '".$tid['t_id']."' AND day='1' order by d_money DESC "
There is no && in MySQL Query. Replace that with AND Operator on your query.
Since you want to get the data from the two tables, then JOIN the two tables instead of doing that with a loop:
SELECT
h.d_money,
t.t_name
FROM team AS t
INNER JOIN history AS h ON t.t_id = h.t_id;
Run this single query once and you will get what you want. You can also add a WHERE clause at the end of it the way you did in your query.
try this
SELECT d_money, t_name FROM team, history WHERE team.t_id= '".$tid['t_id']."' AND history.t_id= '".$tid['t_id']."' && day='1'
Can you replace
WHERE t_id= '".$tid."' AND day='1'
instead of
WHERE t_id= '".$tid['t_id']."' && day='1'

While statement help (PHP)

My database contains both questions and answers. The questions have an ID (intQAID) and the responses have an ID (intResponseID). The intRespondID is the same as the intQAID ID that it is responding to. Each entry into the database has its own ID, which is intPostID.
What's i'm trying to do is write a query that will grab all this information and post it to a website using a while statement. However, the structure needs to be Question and underneath be the answer, until the while loop ends.
I can get the questions to post:
$question = mysql_query("SELECT *,
(SELECT cUsername FROM tblUsers tblU WHERE Q2.intPosterID = tblU.intUserID) AS username,
(SELECT DATE_FORMAT(dPostDateTime, '%b %e %Y %H:%i')) AS post_time
FROM tblQA Q2 WHERE intResponseID = 0
ORDER BY Q2.dSortDateTime DESC, Q2.intQAID DESC LIMIT 40");
while($row = mysql_fetch_array($question))
{
echo "<tr class='forum'>";
echo "<td class='forum'>" . substr($row['cBody'], 0, 150) . "</td>";
echo "<td class='forum'>" . $row['cCategory'] . "</td>";
echo "<td class='forum'>" . $row['username'] . "</td>";
echo "<td class='forum'>" . $row['post_time'] . "</td>";
echo "</tr>";
}
But how can I get it to post the answer in the same while statement?
Should output like so:
Question 1:
Answer 1:
Question 2:
Answer 2:
Question 3:
Answer 3:
etc....
Join the table on itself:
SELECT
*,
(SELECT cUsername FROM tblUsers tblU WHERE Q2.intPosterID = tblU.intUserID) AS username,
(SELECT DATE_FORMAT(dPostDateTime, '%b %e %Y %H:%i')) AS post_time
FROM tblQA Q2
JOIN tblQA AS tblQAjoin ON tblQAjoin.intRespondID = Q2.intPostID
WHERE Q2.intResponseID = 0
ORDER BY Q2.dSortDateTime DESC, Q2.intQAID DESC
LIMIT 40
I'm not sure why you would want to design your table this way, though. If there can just be one answer per question, why not simply have two columns in a row, one containing the question and the other one the answer?
Try using a JOIN statement. As long as your questions and answers are one-to-one, you should be able to pull everything in one fell swoop.
http://dev.mysql.com/doc/refman/5.0/en/join.html
If every question has only one answer, then you should get the data for both in the one query by joining the questions table with the answers table using intRespondID and intQAI.
Then each trip through the while loop has a , whatever you want on the first line, a , a , whatever you want on the second line, and finally a . So one trip through the loop doesn't have to just show one line of output.

MySQL Counting Results Within a Range and First Occurance In Any Range

Looking for some advice on the best way to accomplish this. I've tried Unions, Joins, and Alias examples from a few Stack Overflow questions - none seems to get me where I want to go to no fault of theirs. I think I've just been looking to solve this the wrong way.
I've got one table that logs all activity from our users. Each log contains a column with an ID and another with a TIMESTAMP. There is no column that states what the event type is.
What I'm looking to do is grab counts within a range and append a virtual column with the activation date (first access) regardless if it is in the range or not. The business case for this is that I'd like to have reports that show users active within a range, their activation date, and the amount of events in the range.
The HTML output of this would look like this:
User / Total Visits in the Range / First Visit (in the range or not) / Most Recent Visit (in the range)
How I've gotten this far is by doing this:
$result = mysql_query("
SELECT user, MIN(timestamp), MAX(timestamp), count(user)
AS tagCount FROM " . $table . "
WHERE date(timestamp) BETWEEN '" . $startdate . "' AND '" . $enddate . "'
GROUP BY user
ORDER BY " . $orderby . " " . $order) or die(mysql_error());
I then loop:
$i = 1;
while($row = mysql_fetch_array($result)){
$user_name = str_replace("www.", "", $row['user']); // removing www from usernames
if( $i % 2 != 0) // used for alternating row colors
$iclass = "row";
else
$iclass = "row-bg";
echo "<div class=\"" . $iclass . "\"><div class=\"number\">" . $i . "</div><div class=\"number\">" . $row['tagCount'] . "</div><div class=\"name\">" . "" . $server_name . "" . "</div>" . "<div class=\"first\">" . $row['MIN(timestamp)'] . "</div><div class=\"recent\">" . $row['MAX(timestamp)'] . "</div></div>";
$i++;
}
The MIN(timestamp) in the above grabs the first timestamp in the range - I want to grab the first timestamp regardless of range.
How can I do this?
The key is to create a virtual derived table that calculates their first access separately and then join to it from your query that returns records for the time period you specify.
The below is SQL Server code, but I think it's fine in mysql too. If not, let me know and i'll edit the syntax. The concept is sound either way though.
Just setup code for the sample
if object_id('tempdb..#eventlog') is not null
drop table #eventlog
create table #eventlog
(
userid int ,
eventtimestamp datetime
)
insert #eventlog
select 1,'2011-02-15'
UNION
select 1,'2011-02-16'
UNION
select 1,'2011-02-17'
UNION
select 2,'2011-04-18'
UNION
select 2,'2011-04-20'
UNION
select 2,'2011-04-21'
declare #StartDate datetime
declare #EndDate datetime
set #StartDate = '02-16-2011'
set #EndDate = '05-16-2011'
Here's the code that would solve your problem, you can replace #eventlog with your tablename
select e.userid,
min(eventtimestamp)as FirstVisitInRange,
max(eventtimestamp) as MostRecentVisitInRange,
min(e2.FirstAccess) as FirstAccessEver,
count(e.userid) as EventCountInRange
from #eventlog e
inner join
(select userid,min(eventtimestamp) as FirstAccess
from #eventlog
group by userid
) e2 on e.userid = e2.userid
where
e.eventtimestamp between #StartDate and #EndDate
group by e.userid

MySQL data - Count number of phone calls at the same time

I have a MySQL table with phone calls. Every row means one phone call.
Columns are:
start_time
start_date
duration
I need to get a maximum phone calls called at the same time. It's because of telephone exchange dimensioning.
My solution is to create two timestamp columns timestamp_start and timestamp_end. Then I run a loop second by second, day by day and ask MySQL something like:
SELECT Count(*) FROM tbl WHERE start_date IN (thisday, secondday) AND "this_second_checking" BETWEEN timestamp_start AND timestamp_end;
It's quite slow.
Is there a better solution? Thank you!
EDIT - I use this solution and it gives me proper results. There is used SQL layer dibi - http://dibiphp.com/cs/quick-start .
$starts = dibi::query("SELECT ts_start, ts_end FROM " . $tblname . " GROUP BY ts_start");
if(count($starts) > 0):
foreach ($starts as $row) {
if(isset($result)) unset($result);
$result = dibi::query('SELECT Count(*) FROM ' . $tblname . ' WHERE "'.$row->ts_start.'" BETWEEN ts_start AND ts_end');
$num = $result->fetchSingle();
if($total_max < $num):
$total_max = $num;
endif;
}
endif;
echo "Total MAX: " . $total_max;
Instead of running it second by second, you should for each row (phonecall) see what other phone calls were active at that time. After that you group all of the results by the row's ID, and check which has the maximum count. So basically something like this:
SELECT MAX(calls.count)
FROM (
SELECT a.id, COUNT(*) AS count
FROM tbl AS a
INNER JOIN tbl AS b ON (
(b.timestamp_start BETWEEN a.timestamp_start AND a.timestamp_end)
OR
(b.timestamp_end BETWEEN a.timestamp_start AND a.timestamp_end)
)
GROUP BY a.id
) AS calls
Creating an index on the timestamp columns will help as well.
I'm going to add something to #reko_t answer. I think there is a use case to consider.
Calls that start before and ended after - Calls completely overlapped
So, how about:
SELECT MAX(calls.count)
FROM (
SELECT a.id, COUNT(*) AS count
FROM tbl AS a
INNER JOIN tbl AS b ON (
(b.timestamp_start BETWEEN a.timestamp_start AND a.timestamp_end)
OR
(b.timestamp_end BETWEEN a.timestamp_start AND a.timestamp_end)
OR
(b.timestamp_start <= a.timestamp_start AND b.timestamp_end >= a.timestamp_end)
)
GROUP BY a.id
) AS calls
How about:
SELECT MAX(callCount) FROM (SELECT COUNT(duration) AS callCount, CONCAT(start_date,start_time) AS callTime FROM tbl GROUP BY callTime)
That would give you the max number of calls in a single "time". Assuming start_date and start_time are strings. If they're integer times, you could probably optimise it somewhat.

Categories