How to order the ORDER BY using the IN() mysql? [duplicate] - php

I am wondering if there is away (possibly a better way) to order by the order of the values in an IN() clause.
The problem is that I have 2 queries, one that gets all of the IDs and the second that retrieves all the information. The first creates the order of the IDs which I want the second to order by. The IDs are put in an IN() clause in the correct order.
So it'd be something like (extremely simplified):
SELECT id FROM table1 WHERE ... ORDER BY display_order, name
SELECT name, description, ... WHERE id IN ([id's from first])
The issue is that the second query does not return the results in the same order that the IDs are put into the IN() clause.
One solution I have found is to put all of the IDs into a temp table with an auto incrementing field which is then joined into the second query.
Is there a better option?
Note: As the first query is run "by the user" and the second is run in a background process, there is no way to combine the 2 into 1 query using sub queries.
I am using MySQL, but I'm thinking it might be useful to have it noted what options there are for other DBs as well.

Use MySQL's FIELD() function:
SELECT name, description, ...
FROM ...
WHERE id IN([ids, any order])
ORDER BY FIELD(id, [ids in order])
FIELD() will return the index of the first parameter that is equal to the first parameter (other than the first parameter itself).
FIELD('a', 'a', 'b', 'c')
will return 1
FIELD('a', 'c', 'b', 'a')
will return 3
This will do exactly what you want if you paste the ids into the IN() clause and the FIELD() function in the same order.

See following how to get sorted data.
SELECT ...
FROM ...
WHERE zip IN (91709,92886,92807,...,91356)
AND user.status=1
ORDER
BY provider.package_id DESC
, FIELD(zip,91709,92886,92807,...,91356)
LIMIT 10

Two solutions that spring to mind:
order by case id when 123 then 1 when 456 then 2 else null end asc
order by instr(','||id||',',',123,456,') asc
(instr() is from Oracle; maybe you have locate() or charindex() or something like that)

If you want to do arbitrary sorting on a query using values inputted by the query in MS SQL Server 2008+, it can be done by creating a table on the fly and doing a join like so (using nomenclature from OP).
SELECT table1.name, table1.description ...
FROM (VALUES (id1,1), (id2,2), (id3,3) ...) AS orderTbl(orderKey, orderIdx)
LEFT JOIN table1 ON orderTbl.orderKey=table1.id
ORDER BY orderTbl.orderIdx
If you replace the VALUES statement with something else that does the same thing, but in ANSI SQL, then this should work on any SQL database.
Note:
The second column in the created table (orderTbl.orderIdx) is necessary when querying record sets larger than 100 or so. I originally didn't have an orderIdx column, but found that with result sets larger than 100 I had to explicitly sort by that column; in SQL Server Express 2014 anyways.

SELECT ORDER_NO, DELIVERY_ADDRESS
from IFSAPP.PURCHASE_ORDER_TAB
where ORDER_NO in ('52000077','52000079','52000167','52000297','52000204','52000409','52000126')
ORDER BY instr('52000077,52000079,52000167,52000297,52000204,52000409,52000126',ORDER_NO)
worked really great

Ans to get sorted data.
SELECT ...
FROM ...
ORDER BY FIELD(user_id,5,3,2,...,50) LIMIT 10

The IN clause describes a set of values, and sets do not have order.
Your solution with a join and then ordering on the display_order column is the most nearly correct solution; anything else is probably a DBMS-specific hack (or is doing some stuff with the OLAP functions in standard SQL). Certainly, the join is the most nearly portable solution (though generating the data with the display_order values may be problematic). Note that you may need to select the ordering columns; that used to be a requirement in standard SQL, though I believe it was relaxed as a rule a while ago (maybe as long ago as SQL-92).

Use MySQL FIND_IN_SET function:
SELECT *
FROM table_name
WHERE id IN (..,..,..,..)
ORDER BY FIND_IN_SET (coloumn_name, .., .., ..);

For Oracle, John's solution using instr() function works. Here's slightly different solution that worked -
SELECT id
FROM table1
WHERE id IN (1, 20, 45, 60)
ORDER BY instr('1, 20, 45, 60', id)

I just tried to do this is MS SQL Server where we do not have FIELD():
SELECT table1.id
...
INNER JOIN
(VALUES (10,1),(3,2),(4,3),(5,4),(7,5),(8,6),(9,7),(2,8),(6,9),(5,10)
) AS X(id,sortorder)
ON X.id = table1.id
ORDER BY X.sortorder
Note that I am allowing duplication too.

Give this a shot:
SELECT name, description, ...
WHERE id IN
(SELECT id FROM table1 WHERE...)
ORDER BY
(SELECT display_order FROM table1 WHERE...),
(SELECT name FROM table1 WHERE...)
The WHEREs will probably take a little tweaking to get the correlated subqueries working properly, but the basic principle should be sound.

My first thought was to write a single query, but you said that was not possible because one is run by the user and the other is run in the background. How are you storing the list of ids to pass from the user to the background process? Why not put them in a temporary table with a column to signify the order.
So how about this:
The user interface bit runs and inserts values into a new table you create. It would insert the id, position and some sort of job number identifier)
The job number is passed to the background process (instead of all the ids)
The background process does a select from the table in step 1 and you join in to get the other information that you require. It uses the job number in the WHERE clause and orders by the position column.
The background process, when finished, deletes from the table based on the job identifier.

I think you should manage to store your data in a way that you will simply do a join and it will be perfect, so no hacks and complicated things going on.
I have for instance a "Recently played" list of track ids, on SQLite i simply do:
SELECT * FROM recently NATURAL JOIN tracks;

Related

INSERT INTO table SELECT not giving correct last_id

I have 2 tables with similar columns in MYSQL. I am copying data from one to another with INSERT INTO table2 SELECT * FROM table1 WHERE column1=smth. I have different columns as autoincrement and KEY in tables. When I use mysqli_insert_id i get the first one rather then last one inserted. Is there any way to get the last one?
Thanks
There is no inherit ordering of data in a relational database. You have to specify which field it is that you wish to order by like:
INSERT INTO table2
SELECT *
FROM table1
WHERE column1=smth
ORDER BY <field to sort by here>
LIMIT 1;
Relying on the order a record is written to a table is a very bad idea. If you have an auto-numbered id on table1 then just use ORDER BY id DESC LIMIT 1 to sort the result set by ID in descending order and pick the last one.
Updated to address OP's question about mysqli_insert_id
According to the Mysql reference the function called here is last_insert_id() where it states:
Important If you insert multiple rows using a single INSERT statement,
LAST_INSERT_ID() returns the value generated for the first inserted
row only. The reason for this is to make it possible to reproduce
easily the same INSERT statement against some other server.
Unfortunately, you'll have to do a second query to get the true "Last inserted id". Your best bet might be to run a SELECT COUNT(*) FROM table1 WHERE column1=smth; and then use that count(*) return to add to the mysqli_insert_id value. That's not great, but if you have high volume where this one function is getting hit a lot, this is probably the safest route.
The less safe route would be SELECT max(id) FROM table2 or SELECT max(id) FROM table2 Where column1=smth. But... again, depending on your keys and the number of times this insert is getting hit, this might be risky.

MYSQL Query: 3 Table UNION, Last 100 Results in ASC Order

So far, I have taken 3 tables and joined them together. What I want to do is display the Last 100 entries (DESC) in ASC ORDER according to the timestamp in the column Posted.
This is as far as I could get: http://sqlfiddle.com/#!9/e2771/1
In addition, if there is a more efficient way to do this in PhP and not MYSQL, I'm all for that. I've tried looking, but haven't been able to find anything that works.
You just need one more level of sort:
select t.*
from (<your query here>) t
order by posted;

Multiple 'Select column where ...' vs Select all the column

I have to select 4 rows randomly from a column.
Is is better to generate randomly 4 id and to perform 4 requests 'select column from database where id = ... '
Or to select all the rows in one request and to choose after?
If you are capable of generating random existing id's, I think the best approach is to use a clause like where id in (id1, id2, id3, id4). This will result in getting 4 records in one query, so no unnecessary query's or records are fetched.
As told before, where id in (id1, id2, id3, id4) is the fastest way from the MySQL perspective. How ever, you will need some logic in the application generating those IDs : All 4 IDs shall exist, be randomly distributed, and you want to avoid duplicates. In worst case you will be retrieving a list of all existend IDs with a huge query, extracting 4 random values, and querying again.
With all that logic to be done, it can be wise to move selection into MySQL:
SELECT * FROM foobar
ORDER BY RAND()
LIMIT 4;
You must understand that this is slow in mysql, but you have a speed gain in the application logic and can be sure to get random values equally seed all over your table.
EDIT:
The comment asks if PHP is fasten in this task then MySQL. Answer is no.
It is not done by "using rand". You need to have an array containing all those IDs in PHP. That is a huge query, lots of TCP traffic, huge array to be buildt in php, huge btree to be buildt by zend engine. Then, with the IDs, you must fire a second query to get the rows for those IDs.
Although the RAND() function may be slow, so far I have not had significant problems with speed. MY strategy is actually to join the database back to a query of itself returning a list of random IDs with a limit.
SELECT *
FROM table AS t1
JOIN (
SELECT rowID
FROM table
ORDER BY RAND()
LIMIT 4
) AS t2
WHERE t1.rowID = t2.rowID
There is also a more robust solution that exist - try checking out this question (asked in 2010).

trick to get the sum of MySQL rows outside of MySQL

so I have the following query:
SELECT DISTINCT d.iID1 as 'id',
SUM(d.sum + d.count*r.lp)/sum(d.count) AS avgrat
FROM abcd i, abce r, abcf d
WHERE r.aID = 1 AND
d.iID1 <> r.rID AND d.iID2 = r.rID GROUP BY d.iID1
ORDER BY avgrat LIMIT 50;
the problem is....with millions of entries in the table, SUM() and GROUP BY would freeze up the query....is there a way to do exactly this that would execute instantaneously using MySQL and/or PHP hacks (perhaps do the summing with PHP....but how would I go about doing that...)
To answer the direct question: no, there is no way to do anything instantaneously.
If you have control over the table updates, or the application which adds the relevant records, then you could add logic which updates another table with the sum, count, and id with each update. Then a revised query targets the "sum table" and trivially calculates the averages.
One solution is to create a rollup table that holds your aggregate values
using a triggers on your source tables to keep it up to date.
You will need to decide if the overhead of the triggers is less then that of the query.
some important factors are:
The frequency of the source table updates
The run frequency of the aggregate query.

Assign places in the rating (MySQL, PHP)

I have a MySQL database with the following columns:
id company rating_score rating_place
I have companies and rating scores for each company. So, my db looks like:
id company rating_score rating_place
75 Intel 356.23
34 Sun 287.49
etc.
How can I assign the places (my rating_place column is empty) based on the score using php and mysql?
Thank you!
While Andrew G. Johnson is correct, you may not need to even store this information in the database.
The answer I have for you is simple: "Why do you want to store this in the database?"
If you have actually have a good reason, then you have a few choices based on how static the data is. If the data is created then inserted all at once, then ORDER BY rating_score DESC at the end of your statement should do it (if rating_place is assigned automatically from 1).
Otherwise, I would do something in a dedicated PHP page that, once your 2 columns are read, assigns the rating_place. If you manually enter data into your database, it shouldn't hurt to have to open the page. If data collection is automated, go ahead and throw a call to the "update_places_page" that updates the rating.
Edit:
Another option is just to create a view for rating_score that takes the top 20 and orders reorders them, then select from the new view and the actual table based on rating_score.
If you are just trying sort by highest rating to lowest add this to the end of your SQL query:
ORDER BY rating_score DESC
Or lowest to highest:
ORDER BY rating_score ASC
If you still want to do this your way [which I'd advise against] try this:
UPDATE mytable SET rating_place=(SELECT COUNT(*)+1 FROM mytable tablecheck WHERE tablecheck.rating_score > mytable.rating_score)
How about this:
update mytable set rating_place =(select count(*)+1 from mytable intb where intb.rating_score>mytable.rating_score)
----edit (after comment)
aww sorry, you can't select from the same table that you're updating in mysql, so try it with a temp table:
create table mytemptable as
select #row := #row +1 as place, mytable.id
from mytable, (SELECT #row := 0) r
order by rating_score desc;
and then just a similar update:
update mytable set rating_place = (
select place
from mytemptable
where mytemptable.id=mytable.id
)
after that you can drop that mytemptable.
although if you want to avoid a separate table and you can use php, you can try
$res=mysql_query("select id from mytable order by rating_score desc");
$ratings=array();
while ($r=mysql_fetch_assoc($res)) {
$ratings[]=$r['id'];
}
foreach ($ratings as $key=>$val) {
mysql_query("update mytable set rating_score=".($key+1)." where id=".$val);
}
Just sort by rating! This approach is just wrong as you would have to shift modify all data above a certain rank if you insert something. Bad data structure.
Well if you only insert something once or twice a year you could argue that integer sorting is faster, but well thats just a very minimal difference as sorting is based on Tree indexes and not on comparision.
So I have seen solutions like Andrew G. Johnson's. You could also tweak this further and only update entries with a higher score.
You could also create a trigger that does it automatically for you.
But let me explain why this is wrong:
Its redundant data. Its not atomic and consistent.
In a good atabase design you should always (if possible) store every information only at one point so it can be modified, deleted in an atomic way.
So you can avoid any inconsistencies and complications in the first place.
If you really wan't to "cache" the ranking, do it in your application.
So what are your alternatives to this if you really want to have database fields called like this?
Create a mysql view based on the sorted query.
You can also do caching there AFAIK if thats your goal.
But the better option for caching would be just to let the mysql query cache do the work for you. That would be the very best option.
I see no reason what so ever to do what you are trying to do, only valid arguments against it.
SELECT #row := 0;
UPDATE
`table`
SET
`table`.`rating_place` = (#row := #row +1)
ORDER BY
`table`.`rating_score` DESC;
PS: If you will be sending the queries from PHP, you will need to split the two, since PHP MySQL extension normally allows only single query per call.
yourQueryFunc('SELECT #row := 0;');
yourQueryFunc('
SELECT #row := 0;
UPDATE
`table`
SET
`table`.`rating_place` = (#row := #row +1)
ORDER BY
`table`.`rating_score` DESC;');
I would do a select using the order by desc clause and then update each row with the rating.
It is probably a lot more convenient to work out the place as you go. To do this you would read the values and order by Rating_Score (ASC). Then they would be in order of place as you read them out. If you like, you could then write this back into the table, but this would mean you have to constantly update the place value. If this database is going to be constantly changing from user input or something, I would recommend working out the places as you go. If the table will remain mostly static, you could have a place column.

Categories