I didn't write this code, it's from the Racetracker (http://sourceforge.net/projects/racetracker/), but I've been trying to make a couple modifications.
I'm trying to make a brief teams standings list, and thus limit it to 5 teams. The teams standings page has "ORDER BY SUM(driverpoints)" in it, but it doesn't do anything (the page uses Javascript to manually sort the HTML table). Furthermore, when I try to add a LIMIT 5, it similarly does nothing, it simply prints all the different teams. Here's the relevant code (I removed a lot of repetitive ORs here, as there were 43 of the OR drivername = '$thenames[1]' qualifiers, they don't affect whether or not the ORDER BY and LIMIT parts work):
$total= mysql_query("SELECT SUM(driverpoints), SUM(events), SUM(firstplace),
SUM(qual1stplace), SUM(lapsseason), SUM(lapsledseason), SUM(top5season),
SUM(top10season), SUM(provisionals)
FROM `$whatseriestable`
WHERE `year` = $siteyear AND `drivername` = '$thenames[0]'
OR `drivername` = '$thenames[1]'
ORDER BY `SUM(driverpoints)` DESC LIMIT 5");
What is also puzzling is that ORDER BY and LIMIT work fine in the drivers standings summary:
$stn= mysql_query("SELECT * FROM `$whatseriestable`
WHERE `year` = $siteyear
ORDER BY `driverpoints` DESC LIMIT 10");
Why doesn't this work with the teams standings?
Its probably all just your WHERE clause... AND's take a precidence over the or so
WHERE
`year` = $siteyear
AND `drivername` = '$thenames[0]'
OR `drivername` = '$thenames[1]'
Is being interpreted as
WHERE
( year condition and first driver condition )
OR ( second driver )
I think what you mean is
WHERE
( year condition )
and ( first driver condition OR second driver )
Wrap your driver conditions in ( ) parens.
Related
I have a series of queries that find out what is the best position every user had over a series of events (note: all users have the same $points_pos, so I am looking at a discriminating value). The query is looped for every user.
$max=$amount+1;
$i=1;
$highestsofar="99";
$numpointsall = "SELECT driver, position FROM standings_drivers WHERE season='42' AND points='$points_pos'";
$numall = mysql_query($numpointsall);
while ($i<$max) {
while ($row = mysql_fetch_row($numall)) {
$driver_id = $row[0];
$posvar = "SELECT position FROM results WHERE compId='$compId' AND driverId='$driver_id' AND eventID>='$firstevent' AND eventID<='$lastevent' AND (eventSession!='T' AND eventSession!='P' AND eventSession!='Q' AND eventSession!='Q1' AND eventSession!='Q2' AND eventSession!='QA') ORDER BY position ASC LIMIT 1";
$posres = mysql_query($posvar);
while ($row = mysql_fetch_row($posres)) {
$highestpos = $row[0];
}
$i++;
}
}
Having established the $highestpos for each of the $driver_id, how can I arrange them in order of best position (that is, the ones with the lowest $highestpos)?
Ideally, I want to achieve something that tells me:
$driver_id = 1
$driver_id = 2 etc
so that I can amend a table by putting them in the correct order of position.
EDIT: additional info
The results table look like this:
ID compId eventId eventSession driverId position
1 2 739 R 563 7
2 2 739 R 903 1
3 2 562 R 874 16
...
In the case above, assuming that 739 and 562 are the IDs of events in the range, I would like to order the three users in driverId as follows:
903 = 1st
563 = 2nd
874 = 3rd
Thank you for your help!
One thing about SQL is that it's typically much better than you (or I) are at getting data. The developers and architects for Microsoft, Oracle, etc. have spent a great deal of time and effort working on the best ways to select data, so trying to duplicate their efforts (like looping over results and getting values, etc.) is usually a mistake.
All of your code can likely be replaced with a single SQL query:
SELECT
SD.driver,
MIN(R.position) AS highest_position
FROM
Standings_Drivers SD
INNER JOIN Results R ON
R.driver = SD.driver AND
R.compId = ? AND
eventID >= ? AND
eventID <= ? AND
eventSession NOT IN ('T', 'P', 'Q', 'Q1', 'Q2', 'QA')
WHERE
SD.season = '42' AND -- Should this really be a string and not an integer?
SD.points = ?
GROUP BY
SD.driver
ORDER BY
MIN(R.position)
The question marks are where you would pass in your various parameters. Make sure that you're are executing this as a parameterized query and not just building up a complete string. I don't program in PHP, so I don't know the syntax for that. Googling for "sql injection php" should help you out there though.
I started using only 1 query but then I wanted some to show up more than others so I ended up doing more queries but it, really slows down the load time, is there a way I can do them all in a single query but have them all with their separate variable name?
$sql23 = "SELECT * FROM monsters WHERE rare='0' AND level<='".$row['level']."' ORDER BY RAND() LIMIT 1;";
$result23 = mysqli_query($link,$sql23) or die(mysqli_error());
$battle_get23 = mysqli_fetch_array($result23);
$boss = "SELECT * FROM monsters WHERE rare='1' AND level<='".$row['level']."' ORDER BY RAND() LIMIT 1;";
$boss = mysqli_query($link,$boss) or die(mysqli_error());
$boss = mysqli_fetch_array($boss);
$rare1 = "SELECT * FROM monsters WHERE rare='2' AND level<='".$row['level']."' ORDER BY RAND() LIMIT 1;";
$rare1 = mysqli_query($link,$rare1) or die(mysqli_error());
$rare1 = mysqli_fetch_array($rare1);
$rare2 = "SELECT * FROM monsters WHERE rare='3' AND level<='".$row['level']."' ORDER BY RAND() LIMIT 1;";
$rare2 = mysqli_query($link,$rare2) or die(mysqli_error());
$rare2 = mysqli_fetch_array($rare2);
$rare3 = "SELECT * FROM monsters WHERE rare='4' AND level<='".$row['level']."' ORDER BY RAND() LIMIT 1;";
$rare3 = mysqli_query($link,$rare3) or die(mysqli_error());
$rare3 = mysqli_fetch_array($rare3);
$rare4 = "SELECT * FROM monsters WHERE rare='5' AND level<='".$row['level']."' ORDER BY RAND() LIMIT 1;";
$rare4 = mysqli_query($link,$rare4) or die(mysqli_error());
$rare4 = mysqli_fetch_array($rare4);
$rare5 = "SELECT * FROM monsters WHERE rare='6' AND level<='".$row['level']."' ORDER BY RAND() LIMIT 1;";
$rare5 = mysqli_query($link,$rare5) or die(mysqli_error());
$rare5 = mysqli_fetch_array($rare5);
$rare6 = "SELECT * FROM monsters WHERE rare='7' AND level<='".$row['level']."' ORDER BY RAND() LIMIT 1;";
$rare6 = mysqli_query($link,$rare6) or die(mysqli_error());
$rare6 = mysqli_fetch_array($rare6);
$rare7 = "SELECT * FROM monsters WHERE rare='8' AND level<='".$row['level']."' ORDER BY RAND() LIMIT 1;";
$rare7 = mysqli_query($link,$rare7) or die(mysqli_error());
$rare7 = mysqli_fetch_array($rare7);
$rare8 = "SELECT * FROM monsters WHERE rare='9' AND level<='".$row['level']."' ORDER BY RAND() LIMIT 1;";
$rare8 = mysqli_query($link,$rare8) or die(mysqli_error());
$rare8 = mysqli_fetch_array($rare8);
$rare9 = "SELECT * FROM monsters WHERE rare='10' AND level<='".$row['level']."' ORDER BY RAND() LIMIT 1;";
$rare9 = mysqli_query($link,$rare9) or die(mysqli_error());
$rare9 = mysqli_fetch_array($rare9);
First. You are barking the wrong tree.
Combining a number of slow queries in one won't make them fast. Overhead for running a query is NOT that important as most php users think. If query itself is fast, no matter how many times it is called (within sane numbers of course). If query is slow, no combining would help.
You have to take care for the queries themselves instead of trying to combine them. Thus, to solve your problem you have to provide a lot more info:
Define "really slows" in certain numbers. For all queries in one and for the every single query.
Provide database schema and data amounts.
Provide result of one of the queries run perpended with EXPLAIN keyword
You could give your monsters a random number once a day or once an hour, so
update monsters set sortorder=rand();
It might help to have an index on that key sortorder.
Then you could calculate for every read a random number in php:
$rand = mt_rand(0,1); // or however mt_rand() is called
Then you could select:
$sql23 = "SELECT * FROM monsters WHERE rare='0' AND level<='".intval($row['level'])."' ORDER BY (sortorder-$rand) DESC LIMIT 1;";
$result23 = mysqli_query($link,$sql23) or die(mysqli_error());
$battle_get23 = mysqli_fetch_array($result23);
From SQL it is easy to create a solution with only one select, but that needs a temporary table to support undisturbed random.
Use a category-table
CREATE TABLE raretype (id int(11), name varchar(255), sortkey float default 0, primary key(id));
Put your rares in there:
INSERT INTO raretype (id, name) values (0, 'battle_get23'),(1,'boss'),(2,'rare1'),...
Create a random sortorder for your read:
DROP TEMPORARY TABLE IF EXISTS tmp_rare_sort;
CREATE TEMPORARY TABLE tmp_rare_sort (raretype int(11) not null primary key, sortorder float);
INSERT INTO tmp_rare_sort SELECT id, rand() from raretype;
Read them out:
SELECT rt.name as type, m.*
FROM raretype rt
INNER JOIN tmp_rare_sort rts on rts.raretype = rt.id
LEFT JOIN monsters m on m.id =
( select id from monsters imo where imo.rare = rt.id
and imo.level <= {intval($level)}
order by abs(sortorder - rts.sortorder) desc
limit 1
)
You should read out an array as a result:
$allMonsters = array();
while($line = $rs->next()){
$allMonsters[$line['type']] = $line;
}
So that $allMonsters['boss'] gives the boss (with an additional field 'type', that should not hurt).
If you really want it as single variables, you could extract() this array.
Hope that helps!
If some syntax errors, please create a SQL-fiddle and I'll check. Just no time to create the tables myself. ;-)
1) take out all of your order by RAND() and limit 1
2) do randomization after query
$bossSql = "SELECT * FROM monsters WHERE rare='1' AND level<='".$row['level']."' ";
$bossRs = mysqli_query($link,$bossSql) or die(mysqli_error());
$bossArray = mysqli_fetch_array($bossRs);
//just saw you had limit in your sql, edited
//shuffle($bossArray);
$randomBosskey = array_rand($bossArray);
$randomBoss = $bossArray[$randomBossKey];
edit: thank you #flaschenpost for pointing out the potential problem, which inspired me to come up with a possibly quicker solution (under certain assumption which might not be true)
Solution 2) cache the table
From the looks of the code I think there is a high chance that you would need to do the query for monster action many times throughout the whole program lifetime.
Instead of getting a new random monster from DB everytime, will it be better to do full query:
SELECT * FROM monsters
save it in a variable and then pick the a random monster according to rarity and level from the same variable everytime you need a monster? But this method takes up unknown amount of memory depends on your table size and might be faster/slower than your original depending on how many "random monster" query you are actually using in your program. It might also depends on the "power" of your machine, assuming your DB and server are not in the same machine and the 2 machine have significant difference in processing power.
I don't know if these are "complex queries" by defn, but they look very complex to a noob like me.
So I have a query here that will get the latest chart of customer_id=5:
$query = "SELECT c.Chart_ID, c.Chart_Notes
FROM tblchart AS c WHERE c.Customer_ID=5
ORDER BY c.Last_Edited ASC LIMIT 1";
But I have to relate it to another table that uses the Chart_ID as foreign key. How can I get the data from the tblcontent using tblchart.Chart_ID=tblcontent.Chart_ID? I couldn't just add that as:
$query = "SELECT c.Chart_ID, c.Chart_Notes, d.Content_Desc, d.Content_Title
FROM tblchart AS c, tblcontent AS d
WHERE c.Customer_ID=5 AND c.Chart_ID=d.Chart_ID
ORDER BY c.Last_Edited DESC LIMIT 1";
can I? As that would limit the search to just one...the use of LIMIT 1 is just to get the latest, but for the subsequent query (extended query), I am expecting multiple results extracted from tblcontent in addition to the first query I posted. A join, maybe, or union, or a complex query, but how? Please, can anyone help me? Thanks.
SELECT a.Chart_ID, a.Chart_Notes, c.Content_Desc, c.Content_Title
FROM tblChart a
INNER JOIN
(
SELECT Chart_ID, MAX(Last_edited) maxEdited
FROM tblChart
GROUP BY Chart_ID
) b ON a.Chart_ID = b.Chart_ID AND
a.Last_Edited = b.maxEdited
INNER JOIN tblcontent c
ON a.Chart_ID = c.Chart_ID
WHERE a.Customer_ID=5
i have this code:
while ($sum<16 || $sum>18){
$totala = 0;
$totalb = 0;
$totalc = 0;
$ranka = mysql_query("SELECT duration FROM table WHERE rank=1 ORDER BY rand() LIMIT 1");
$rankb = mysql_query("SELECT duration FROM table WHERE rank=2 ORDER BY rand() LIMIT 1");
$rankc = mysql_query("SELECT duration FROM table WHERE rank=3 ORDER BY rand() LIMIT 1");
while ($rowa = mysql_fetch_array($ranka)) {
echo $rowa['duration'] . "<br/>";
$totala = $totala + $rowa['duration'];
}
while ($rowb = mysql_fetch_array($rankb)) {
$totalb = $totalb + $rowb['duration'];
}
while ($rowc = mysql_fetch_array($rankc)) {
$totalc = $totalc + $rowc['duration'];
}
$sum=$totala+$totalb+$totalc;
}
echo $sum;
It works fine, But the problem is until "$sum=16" the "echo $rowa['duration']" executes, the question is, is there a away to "echo" only the latest executed code in the "while ($rowa = mysql_fetch_array($ranka))" i this while loop?
Because most of the times returns all the numbers until the "$sum=16"
You are explicitly echoing the $rowa['duration'] in the first inner while loop. If you only want to print the last duration from the $ranka set, simple change the echo to $rowa_duration = $rowa['duration'] then echo it outside the loop.
while ($rowa = mysql_fetch_array($ranka)) {
$rowa_duration = $rowa['duration'];
$totala = $totala + $rowa['duration'];
}
echo $rowa_duration . '<br/>';
What you are doing there is bad on multiple levels. And your english horrid. Well .. practice makes perfect. You could try joining ##php chat room on FreeNode server. That would improve both your english and php skills .. it sure helped me a lot. Anyway ..
The SQL
First of all, to use ORDER BY RAND() is extremely ignorant (at best). As your tables begin the get larger, this operation will make your queries slower. It has n * log2(n) complexity, which means that selecting querying table with 1000 entries will take ~3000 times longer then querying table with 10 entries.
To learn more about it , you should read this blog post, but as for your current queries , the solution would look like:
SELECT duration
FROM table
JOIN (SELECT CEIL(RAND()*(SELECT MAX(id) FROM table)) AS id) as choice
WHERE
table.id >= choice.id
rank = 1
LIMIT 1
This would select random duration from the table.
But since you you are actually selecting data with 3 different ranks ( 1, 2 and 3 ), it would make sense to create a UNION of three queries :
SELECT duration
FROM table
JOIN (SELECT CEIL(RAND()*(SELECT MAX(id) FROM table)) AS id) as choice
WHERE
table.id >= choice.id
rank = 1
LIMIT 1
UNION ALL
SELECT duration
FROM table
JOIN (SELECT CEIL(RAND()*(SELECT MAX(id) FROM table)) AS id) as choice
WHERE
table.id >= choice.id
rank = 2
LIMIT 1
UNION ALL
SELECT duration
FROM table
JOIN (SELECT CEIL(RAND()*(SELECT MAX(id) FROM table)) AS id) as choice
WHERE
table.id >= choice.id
rank = 3
LIMIT 1
Look scary, but it actually will be faster then what you are currently using, and the result will be three entries from duration column.
PHP with SQL
You are still using the old mysql_* functions to access database. This form of API is more then 10 years old and should not be used, when writing new code. The old functions are not maintained (fixed and/or improved ) anymore and even community has begun the process of deprecating said functions.
Instead you should be using either PDO or MySQLi. Which one to use depends on your personal preferences and what is actually available to you. I prefer PDO (because of named parameters and support for other RDBMS), but that's somewhat subjective choice.
Other issue with you php/mysql code is that you seem to pointlessly loop thought items. Your queries have LIMIT 1, which means that there will be only one row. No point in making a loop.
There is potential for endless loop if maximum value for duration is 1. At the start of loop you will have $sum === 15 which fits the first while condition. And at the end that loop you can have $sum === 18 , which satisfies the second loop condition ... and then it is off to the infinity and your SQL server chokes.
And if you are using fractions for duration, then the total value of 3 new results needs to be even smaller. Just over 2. Start with 15.99 , ends with 18.01 (that's additional 2.02 in duration or less the 0.7 per each). Again .. endless loop.
Suggestion
Here is how i would do it:
$pdo = new PDO('mysql:dbname=my_db;host=localhost', 'username', 'password');
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$sum = 0;
while ( $sum < 16 )
{
$query = 'that LARGE query above';
$statement = $pdo->prepare( $query );
if ( $statement->execute() )
{
$data = $statement->fetchAll( PDO::FETCH_ASSOC );
$sum += $data[0]['duration']+$data[1]['duration']+$data[2]['duration'];
}
}
echo $data[0]['duration'];
This should do what your code did .. or at least, what i assume, was your intentions.
Will this query work? Is it most efficient?
SELECT * FROM `posts`
WHERE MATCH (`title`, `body`)
AGAINST ('search terms' IN BOOLEAN MODE)
AND `price` BETWEEN '100' AND '1000'
AND (`postinto` = 'cat1' OR `postinto` = 'cat2')
AND (`location` = 'loc1' OR `location` = 'loc2')
ORDER BY `id` DESC
LIMIT 0, 100;
Note: values for postinto and location will be contained in a PHP arrays, so if this will work I plan on looping their the arrays to generate the query terms. Is there a way to pass the entire array to MySQL? Also, these two conditions have a possibility of being quite long (a dozen values). Is there a better way?
specifically my question is about this:
AND (`postinto` = 'cat1' OR `postinto` = 'cat2')
AND (`location` = 'loc1' OR `location` = 'loc2')
an example of possible values would be "community|groups", "buy-sell-trade|electronics" where before the | is a category and after | is a sub category. If I am searching an entire category I would want to change that part of the query to:
AND (`postinto` LIKE 'category|%' OR `postinto` = 'this'
I have the proper indexes for the fulltext search, my question is about the OR clause. Is there a maximum number of times you can use OR in one query? Is this syntax even correct?
Thanks
Actually there is. The IN statement can help you here.
Your query would then become like this:
SELECT * FROM `posts`
WHERE MATCH (`title`, `body`)
AGAINST ('search terms' IN BOOLEAN MODE)
AND `price` BETWEEN '100' AND '1000'
AND `postinto` IN ('cat1', 'cat2')
AND `location` IN ('loc1', 'loc2')
ORDER BY `id` DESC
LIMIT 0, 100;
You can use AND and OR in a WHERE as often as you want, but to keep your sanity you need to obey a few simple rules.
1) Do not mix them, constructing a query with WHERE AND OR AND OR will probably work but the results will be unpredictable and the result set may contain multiple instances of the same record! (You are effectively turning the OR construct into an EITHER!
2) I would recommend always putting all of the OR statements before any AND statements. It will be easier for you to understand and for anybody else to maintain.
3) It is probably not a necessity but I always if possible enclose the OR statements in brackets.
I Maintain a Sports database and run some queries that can have multiple options in the select.
Hopefully the following examples will simplify the explanation.
// Make the query to retrieve the set of Singles Games.
$query = "SELECT * from Games WHERE Season = '$season'
AND GameNo < 8
OR Player1 = '$playerName' ORDER by MatchNo ASC";
$result = #mysql_query($query); // Run the query.
$num = mysql_num_rows($result);
This will run, it is syntactically correct but the results will be unpredictable!
If in these circumstances you wish to put the OR after the AND then you need to 'AND' the OR.
// Make the query to retrieve the set of Singles Games.(This is correct)
$query = "SELECT * from Games WHERE (Player1 = '$playerName'
OR Player3 = '$playerName') AND Season = '$season'
AND GameNo < 8 ORDER by MatchNo ASC";
$result = #mysql_query($query); // Run the query.
$num = mysql_num_rows($result);
This will run and produce a list of Singles games in which a player has participated.
The following is the same statement with the OR after the AND. Notice you need to AND
the OR to the end of the statement.
// Make the query to retrieve the set of Singles Games.(This is also correct)
$query = "SELECT * from Games WHERE Season = '$season'
AND GameNo < 8 AND (Player1 = '$playerName'
OR Player3 = '$playerName') ORDER by MatchNo ASC";
$result = #mysql_query($query); // Run the query.
$num = mysql_num_rows($result);
This will run and produce a list of Singles games in which a player has participated.
Finally a couple of examples, why I prefer the OR first
$query = "SELECT * from Games WHERE (Player1 = '$playerName'
OR Player2 = '$playerName' OR Player3 = '$playerName'
OR Player4 = '$playerName') AND Season = '$season'
AND GameNo > 7 ORDER by MatchNo ASC";
$result = #mysql_query($query); // Run the query.
$num = mysql_num_rows($result);
This will produce a result set containing all the League doubles that a player has participated in. Either as home player1 or 2, or away player 3 or 4.
To produce a query with multiple separate OR statements. Then place an AND between the OR
statements.
$query = "SELECT * from CupGames WHERE (Player1 = '$playerName'
OR Player2 = '$playerName' OR Player3 = '$playerName'
OR Player4 = '$playerName' OR Player5 = '$playerName'
OR Player6 = '$playerName') AND (CupID = 'CS' OR CupID = 'ND')
AND Season = '$season' AND GameNo < 4 ORDER by CupRound ASC";
$result04 = #mysql_query($query); // Run the query.
$num = mysql_num_rows($result04);
This will produce a result set containing all the Cup Triples that a player has participated in. Either as home player1,2 or 3, or away player 4, 5 or 6. In either of
the two selected cups. CS(Coronation Shield) or ND(Norman Day Cup) for the selected season.
If you wish to keep your hair I suggest following these rules.
(I have retired from IT and have a full head of hair!)
Best of Luck!