sql query order by seems not working - php

I have this table structure,
| id | name | level |
---------------------
| 1 | a | 1 |
| 2 | b | 2 |
| 3 | c | 3 |
| 5 | d | 4 |
| 6 | e | 1 |
| 7 | f | 2 |
| 8 | g | 1 |
| 9 | g | 4 |
I want to order my fetch result to level, so I execute this query:
$sql = "SELECT * FROM section_tb WHERE id = ? ORDER BY level";
$stmt = $db->prepare($sql);
$stmt->execute(array($id));
$result = $stmt->fetch(PDO::FETCH_ASSOC);
However when I print_r($result) the order seems like it was sorted by id. I am confused why.
My db details:
id - PRIMARY, AUTO INCREMENT
name - UNIQUE
INNODB

Your query is ordering correctly.
This is irrelevant since it's only returning one row each time its called anyway.
Your foreach is calling into it multiple times, and the ordering only affects the actual database call. Therefore the overall order of the results is the order of that foreach.
If the foreach had passed a parameter that identified more than one row, then within each of those calls the order would be by level (e.g. if you'd done queries to match on name, then the two that match "g" would be in the order requested).
You want to change the query to something like SELECT * FROM section_tb WHERE id in (1,2,3,4,5,6,7,8,9) ORDER BY level (or perhaps just SELECT * FROM section_tb ORDER BY level), call it once, and loop through the results.

Your WHERE clause is seeking an id which you've identified as the primary key, so your query should only return one row.

You can't do that. You can either use a place holder or bind the parameter.

Related

MySQL query for a single row and get the value from it

I have a php file that will update a table in MySQL. It will update all the done flags from 0 to 1 after a job has completed. I need to query the done=0 starting from the lowest primary key (ID). After the job is done, I update the done=1 and move on to the next row. I have the following table :
--------------------
| ID | test | done |
--------------------
| 1 | test1| 0 |
--------------------
| 2 | test2| 1 |
--------------------
| 3 | test3| 0 |
--------------------
| 4 | test4| 0 |
--------------------
| 5 | test5| 1 |
--------------------
When I do the following query SELECT test FROM mytable WHERE done=0 ORDER BY id ASC it gives me all the test that have done flags that are 0, however, I want to start with the first and handle that first, then move on to the next one and so on. So I need a query that will show me just the first row. How can I do this?
Your query is on the right track, since it already is sorting by ascending id. All you need to do is limit it to only returning the first result, if one exists. Just add LIMIT 1 to the end of the query:
SELECT test FROM mytable WHERE done=0 ORDER BY id ASC LIMIT 1

MySQL - Do queries *always* begin searching at the start of the table?

I can't find a clear answer for this anywhere in MySQL documentation.
When I run a query, something like:
Code Block 1
$stmt = $db->prepare('SELECT id, name FROM table WHERE status=1');
does the search start at the beginning of the table, at row 0 (or the lowest available row)?
What I'm trying to do is go through a table one row at a time, and then exit when I get to the end:
Code Block 2
$curRow = 0;
while(true){
$stmt = $db->prepare('SELECT id, name FROM table WHERE status=? AND id>? LIMIT 1');
$stmt->execute(array(0, $curRow));
$result = $stmt->fetchAll();
if(count($result)){
$curRow = $result[0]['id'];
$stmt2 = $db->prepare('UPDATE table SET status=? WHERE id=?');
$stmt2->execute(array(1, $curRow));
... do some other stuff ...
}else{
exit();
}
}
And so far, in testing, this has worked exactly as intended. But will it always be so?
Possible erroneous case:
Start out with the following table:
table
id | name | status
-- | ---- | ------
1 | ... | 0
2 | ... | 0
3 | ... | 0
4 | ... | 0
5 | ... | 0
6 | ... | 0
And run the query in Code Block 2. Say it starts at the first row, so now we have $curRow=1, and the table looks as follows:
table
id | name | status
-- | ---- | ------
1 | ... | 1
2 | ... | 0
3 | ... | 0
4 | ... | 0
5 | ... | 0
6 | ... | 0
All is well. The code does whatever it needs to, and then continues with the loop. Any of the remaining rows will satisfy the conditions in $stmt (i.e. status=0 and id>$curRow).
Will the statement always look at consecutive rows when checking the conditions? If not, it could end up at any arbitrary row, say the third:
table
id | name | status
-- | ---- | ------
1 | ... | 1
2 | ... | 0
3 | ... | 1
4 | ... | 0
5 | ... | 0
6 | ... | 0
And now we have $curRow=3, which means the query will never go back and look at the second row.
I know it's tricky business speaking in absolutes (always, never, every time, ...), but is there a way to ensure that the query begins at the lowest available row? Or does MySQL handle this automatically?
There is no guarantee for any reliable order when you do not explicitly set it to a key. It might seem to be ordered for now, but over time, with more data, maybe with more servers, partitioned data, union'ed data, it will change quickly to something unexpected.
Better use ORDER BY:
$stmt = $db->prepare('SELECT id, name FROM table WHERE status=1 ORDER BY ID ASC');
Make sure you have an index on the column you want to order by, it will speed up things!
You should not write such a code which has such assumptions of your database. Your code might get less maintainable, harder to debug when some change comes to your database and that'll be a total headache for you. You should think of other mechanisms / workarounds to get the job done which is also more professional.
You might want to add a column which will provide that they can be ordered. For example date, ID, whatsoever.
Look up ORDER BY clause.

Omit / ignore any records that have been purchased by a user

I'm currently in the process of developing a site that amongst other things allows a user to filter a marketplace by showing or hiding items they have already purchased. This works on a basic AJAX call that passes through the current conditions of those filters available, and then using CodeIgniter's active record, it builds the appropriate query.
My issue is wrapping my head around the query so that if a user selects to hide purchased items the query omits / ignores any relevant records (i.e. if user_id = 5 and hide purchased is true, any scenes that user_id = 5 owns are not returned in the query).
Tbl: scenes
-------------------------------------------------------------------------
| design_id | scene_id | scene_name | ... [irrelevant columns to the Q] |
|-----------|----------|------------|-----------------------------------|
| 1 | 1 | welcome | |
| 1 | 2 | hello | |
| 2 | 3 | asd | |
-------------------------------------------------------------------------
The designs table is very similar to this and includes references to the game, game type, design name and so forth.
Tbl: user_scenes
----------------------------------------------------------------------
| design_id | scene_id | user_id | ... [irrelevant columns to the Q] |
|-----------|----------|---------|-----------------------------------|
| 1 | 1 | 5 | |
| 1 | 2 | 5 | |
| 1 | 1 | 9 | |
----------------------------------------------------------------------
Query
SELECT `designs`.`design_id`, `designs`.`design_name`, `scenes`.`scene_id`, `scenes`.`scene_name`, `scenes`.`scene_description`, `scenes`.`scene_unique_code`, `scenes`.`date_created`, `scenes`.`scene_cost`, `scenes`.`type`, `games`.`game_title`, `games`.`game_title_short`, `games_genres`.`genre`
FROM (`scenes`)
JOIN `designs` ON `designs`.`design_id` = `scenes`.`design_id`
JOIN `games` ON `designs`.`game_id` = `games`.`game_id`
JOIN `games_genres` ON `games`.`genre_id` = `games_genres`.`genre_id`
WHERE `scenes`.`private` = 0
ORDER BY `designs`.`design_name` asc, `scenes`.`scene_name` asc
LIMIT 6
The query uses CodeIgniter's active record ($this->db->select() / $this->db->where()) but that is somewhat irrelevant.
--
I've tried things like an INNER JOIN with user_scenes and then grouping by scene_id, but that presents an issue with only returning scenes that are present in user_scenes. I then made an attempt at a subquery but then questioned whether that was the correct route.
I understand there are other ways - looping through the returned data and querying whether that record exists for a specific user, but that I suspect would be highly inefficient. As such, I'm at a loss as to what to try and would appreciate any help.
I don't know if your setup permits it, but I would do a subselect:
Either via a NOT IN:
SELECT * FROM `scenes`
WHERE `scenes`.`scene_id` NOT IN (SELECT `scene_id` FROM `user_scenes` WHERE `user_id` = 5)
Or maybe via a LEFT JOIN:
SELECT * FROM `scenes`
LEFT JOIN (SELECT `scene_id`, `user_id` FROM `user_scenes` WHERE `user_id` = 5) AS `user_scenes`
ON `scenes`.`scene_id` = `user_scenes`.`scene_id`
WHERE `user_scenes`.`user_id` IS NULL
Bit I guess the first way is faster.

MYSQL: Get next 'n' results

Right now I have a PHP script that is fetching the first three results from a MYSQL database using:
SELECT * FROM table Order by DATE DESC LIMIT 3;
After that command I wanted PHP to fetch the next three results, initially I was going to use:
SELECT * FROM table Order by DATE DESC LIMIT 3,3;
However there will be a delay between the two commands which means that it is very possible that a new row will be inserted into the table during the delay. My first thought was to store the DATE value of the last result and then include a WHERE DATE > $stored_date but if entry 3 and 4 have the same date it will skip entry 4 and return results from 5 onward. This could be avoided using the primary key field which is an integer which increments automatically.
I am not sure which the best approach is, but I feel like there should be a more elegant and robust solution to this problem, however I am struggling to think of it.
Example table:
-------------------------------------------
| PrimaryKey | Data | Date |
-------------------------------------------
| 0 | abc | 2014-06-17 11:43:00 |
| 1 | def | 2014-06-17 12:43:00 |
| 2 | ghi | 2014-06-17 13:43:00 |
| 3 | jkl | 2014-06-17 13:56:00 |
| 4 | mno | 2014-06-17 14:23:00 |
| 5 | pqr | 2014-06-17 14:43:00 |
| 6 | stu | 2014-06-17 15:43:00 |
-------------------------------------------
Where Data is the column that I want.
Best will be using primary key and select like
SELECT * FROM table WHERE pk < $stored_pk Order by DATE DESC LIMIT 3;
And if you have automatically generated PK you should use ORDER BY pk it will be faster
Two options I can think of depending on what your script does:
You could either use transactions: performing these queries inside a transaction will give you a consistent view of the data.
Alternatively you could just use:
SELECT * FROM table Order by DATE DESC;
And only fetch the results as you need them.

Does ORDER BY apply before or after DISTINCT?

In a MySQL query, when using the DISTINCT option, does ORDER BY apply after the duplicates are removed? If not, is there any way to make it do so? I think it's causing some issues with my code.
EDIT:
Here's some more information about what's causing my problem. I understand that, at first glance, this order would not be important, since I am dealing with duplicate rows. However, this is not entirely the case, since I am using an INNER JOIN to sort the rows.
Say I have a table of forum threads, containing this data:
+----+--------+-------------+
| id | userid | title |
+----+--------+-------------+
| 1 | 1 | Information |
| 2 | 1 | FAQ |
| 3 | 2 | Support |
+----+--------+-------------+
I also have a set of posts in another table like this:
+----+----------+--------+---------+
| id | threadid | userid | content |
+----+----------+--------+---------+
| 1 | 1 | 1 | Lorem |
| 2 | 1 | 2 | Ipsum |
| 3 | 2 | 2 | Test |
| 4 | 3 | 1 | Foo |
| 5 | 2 | 3 | Bar |
| 6 | 3 | 5 | Bob |
| 7 | 1 | 2 | Joe |
+----+----------+--------+---------+
I am using the following MySQL query to get all threads, then sort them based on the latest post (assuming that posts with higher ids are more recent:
SELECT t.*
FROM Threads t
INNER JOIN Posts p ON t.id = p.threadid
ORDER BY p.id DESC
This works, and generates something like this:
+----+--------+-------------+
| id | userid | title |
+----+--------+-------------+
| 1 | 1 | Information |
| 3 | 2 | Support |
| 2 | 1 | FAQ |
| 3 | 2 | Support |
| 2 | 1 | FAQ |
| 1 | 1 | Information |
| 1 | 1 | Information |
+----+--------+-------------+
However, as you can see, the information is correct, but there are duplicate rows. I'd like to remove such duplicates, so I used SELECT DISTINCT instead. However, this yielded the following:
+----+--------+-------------+
| id | userid | title |
+----+--------+-------------+
| 3 | 2 | Support |
| 2 | 1 | FAQ |
| 1 | 1 | Information |
+----+--------+-------------+
This is obviously wrong, since the "Information" thread should be on top. It would seem that using DISTINCT causes the duplicates to be removed from the top to the bottom, so only the final rows are left. This causes some issues in the sorting.
Is this the case, or am I analyzing things incorrectly?
Two things to understand:
Generally speaking, resultsets are unordered unless you specify an ORDER BY clause; to the extent that you specify a non-strict order (i.e. ORDER BY over non-unique columns), the order in which records that are equal under that ordering appear within the resultset is undefined.
I suspect you may be specifying such a non-strict order, which is the root of your problems: ensure that your ordering is strict by specifying ORDER BY over a set of columns that is sufficient to uniquely identify each record for which you care about its final position in the resultset.
DISTINCT may use GROUP BY, which causes the results to be ordered by the grouped columns; that is, SELECT DISTINCT a, b, c FROM t will produce a resultset that appears as though ORDER BY a, b, c has been applied. Again, specifying a sufficiently strict order to meet your needs will override this effect.
Following your update, bearing in mind my point #2 above, it is clear that the effect of grouping the results to achieve DISTINCT makes it impossible to then order by the non-grouped column p.id; instead, you want:
SELECT t.*
FROM Threads t INNER JOIN Posts p ON t.id = p.threadid
GROUP BY t.id
ORDER BY MAX(p.id) DESC
DISTINCT informs MySQL how to build a rowset for you, ORDER BY gives a hint how this rowset should by presented. So the answer is: DISTINCT first, ORDER BY last.
The order in which DISTINCT and ORDER BY are applied, in most cases, will not affect the final output.
However, if you also use GROUP BY, this will affect the final output. In this case, the ORDER BY is performed after the GROUP BY, which will return unexpected results (assuming you expect the sort to be performed before the grouping).

Categories