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;
I have a table like;
tablea
4c4fedf7 OMoy3Hoa
4c4fedf7 yiWDGB4D
broe4AMb A9rLRawV
broe4AMb mi9rLmZW
nhrtK9ce yEsBoYLj
rEEtK9gt A9rLRawV
rEEtK9gt mi9rLmZW
rEEtK9Hh A9rLRawV
rEEtK9Hh msBWz8CQ
If I give the input like A9rLRawV,mi9rLmZW. I want an output like;
broe4AMb
rEEtK9gt
The output is generated as a result of A9rLRawV,mi9rLmZW combination. Here, broe4AMb and rEEtK9gt both have A9rLRawV and mi9rLmZW associated in tablea. I made a query, but I get output like;
broe4AMb
rEEtK9gt
rEEtK9Hh
My query is like;
SELECT DISTINCT prodid
FROM tablea
WHERE tagid IN ('A9rLRawV','mi9rLmZW');
The output is like this because I think, reetK9Hh has A9rLRawV associated with it in tablea. But i don't want that entry to appear because it doesn't have mi9rLmZW associated with it.
Here is the SQL fiddle http://sqlfiddle.com/#!9/12223/2/0
Does it require a SELF JOIN. What will be the most 'efficient' method? Is it possible to achieve this with MySQL alone or with support of PHP? How can I do this / fix this?
I firmly believe this can be resolved by SQL alone. Get the data you need from the database. Have PHP do the presentation. Why? Typically the Database can parse data much faster than you can through code on a webserver/middleware server.
As to how... Read up on having clauses and group by:
What I've done here is group by prodID to ensure all prodIDs are returned. mySQL extends the group by clause and while it may allow a having clause to exist without a group by, you will not get the desired results without grouping by prodid. The having a gets a distinct count of both tags requested in the where. Note: I added distinct to the count on tagID as I was unsure if your overall data could have duplicate tagIds for each prodid. we could eliminate it if we know values are distinct.
The # can be dynamically set based on the number of TagIDs provided if needed.
SELECT prodid
FROM tablea
WHERE tagID in ('A9rLRawV','mi9rLmZW')
GROUP BY prodid
HAVING count(distinct tagID)=2
SQL FIddle
I prefer this approach as it scales better than a self join as you indicated might be needed. Pass in two parameters: one for the tags one for the number of tags. (I do so hope you're using paramaterized queries with your PHP) With self joins you have to write dynamic SQL to handle each additional tag so if you have 3, 4,5 more joins. This way you just pass in the 5 values and the number 5; and get a list back of all those that match.
Like you assumed, a self join helps. Use this query
SELECT a.prodid
FROM tablea AS a
INNER JOIN tablea AS b
ON a.prodid = b.prodid
WHERE a.tagid = 'A9rLRawV' and b.tagid = 'mi9rLmZW'
This here you join the table with itself and the matching pairs have both A9rLRawV (in the main table a) and mi9rLmZW (in the joined table b).
Here's your updated SQL fiddle: http://sqlfiddle.com/#!9/12223/13
The results are as wished:
broe4AMb
rEEtK9gt
Yes, there's a thousand questions about this on SO, but I've been searching for half an hour and I've yet to find a solution.
So, I've a table like this:
And this is my query:
SELECT DISTINCT rengasID,leveys FROM renkaat ORDER BY leveys ASC
And this is the result I get:
If you get the idea, I'm populating a select field with it, but it still has duplicates.
What am I doing wrong?
If you want distinct leveys, just choose that field:
SELECT DISTINCT leveys
FROM renkaat
ORDER BY leveys ASC
The rengasid has a different value on each row.
The distinct clause applies to all the columns being returned, regardless of parentheses.
EDIT:
If you need the regasid in the result, then use group by:
select leveys, min(regasid) as regasid
from renkaat
group by leveys
order by leveys asc;
This gives the first id. If you need all of them, you can get them in a list using group_concat(). If you need a separate id on each row, well, then you have duplicates.
Your rengasID is still different in each shown line. The distinct will check a mix of every selected field, so in this case it will search a distinct combination of rengasID and leveys.
You cannot ask for your ID here, since MySQL has no way of knowing which one you want.
Depending on what you want to do it can be more correct to save your "leveys" (I'm not sure what they are) in a separate table with a unique ID and join it. For filling up your list with all possible leveys, you can just query that new table.
This can be important because using group by, you can get random results for id's later on.
This is because you are selecting combination of rengasID and leveys. And what you are getting as a result is a distinct combination of the two.
To achieve what you are trying, see the answer of #GordonLinoff.
I am developing a car rental site. I have two tables test_tbl_cars and test_reservations.
I am using the search query (cribbed from Jon Kloske in "How do I approach this PHP/MYSQL query?"):
$sql = mysql_query("SELECT
test_tbl_cars.*,
SUM(rental_start_date <= '$ddate' AND rental_end_date >= '$adate') AS ExistingReservations
FROM test_tbl_cars
LEFT JOIN test_reservations USING (car_id)
GROUP BY car_id
HAVING ExistingReservations = 0");
This gives me excellent search results but the test_tbl_cars table contains many cars which in any given search returns several of the same car model as being available.
How can I filter the query return such that I get one of each model available?
Use Distict clause
$sql = mysql_query("SELECT
DISTINCT test_tbl_cars.model, test_tbl_cars.*,
SUM(rental_start_date <= '$ddate' AND rental_end_date >= '$adate') AS ExistingReservations
FROM test_tbl_cars
LEFT JOIN test_reservations USING (car_id)
GROUP BY car_id
HAVING ExistingReservations = 0");
Awww, should have tagged me, I only saw this now over a year later! You've probably already figured out how to work this by now, but I'll take a crack at it anyway for completeness sake and because most of the answers here I don't think are doing what you want.
So the problem you are having is that in the other question each room had a unique ID and it was unique rooms people were interested in booking. Here, you're extending the concept of a bookable item to a pool of items of a particular class (in this case, model of car).
There's may be a way to do this without subqueries but by far the easiest way to do it is to simply take the original idea from my other answer and extend it by wrapping it up in another query that does the grouping into models (and as you'll see shortly, we get a bunch of other useful stuff for free out of doing this).
So, firstly lets start by getting the list of cars with counts of conflicting reservations (as per the update to my other answer):
(I'll use your query for these examples as a starting point, but note you really should use prepared statements or at the very least escaping functions supplied by your DB driver for the two parameters you're passing)
SELECT car_id, model_id, SUM(IF(rental_id IS NULL, 0, rental_start_date <= '$ddate' AND rental_end_date >= '$adate')) AS ConflictingReservations
FROM test_tbl_cars
LEFT JOIN test_reservations USING (car_id)
GROUP BY car_id
This will return one row per car_id giving you the model number, and the number of reservations that conflict with the date range you've specified (0 or more).
Now at this stage if we were asking about individual cars (rather than just models of cars available) we could restrict and order the results with "HAVING ConflictingReservations = 0 ORDER BY model_id" or something.
But, if we want to get a list of the availability of ~models~, we need to perform a further grouping of these results to get the final answer:
SELECT model_id, COUNT(*) AS TotalCars, SUM(ConflictingReservations = 0) AS FreeCars, CAST(IFNULL(GROUP_CONCAT(IF(ConflictingReservations = 0, car_id, NULL) ORDER BY car_id ASC), '') AS CHAR) AS FreeCarsList
FROM (
SELECT car_id, model_id, SUM(IF(rental_id IS NULL, 0, rental_start_date <= '$ddate' AND rental_end_date >= '$adate')) AS ConflictingReservations
FROM test_tbl_cars
LEFT JOIN test_reservations USING (car_id)
GROUP BY car_id
) AS CarReservations
GROUP BY model_id
You'll notice all we're doing is grouping the original query by model_id, and then using aggregate functions to get us the model_id, a count of total cars we have of this model, a count of free cars of this model we have which we achieve by counting all the times a car has zero ConflictingReservations, and finally a cute little bit of SQL that returns a comma separated list of the car_ids of the free cars (in case that was also needed!)
A quick word on performance: all the left joins, group bys, and subqueries could make this query very slow indeed. The good news is the outer group by should only have to process as many rows as you have cars for, so it shouldn't be slow until you end up with a very large number of cars. The inner query however joins two tables (which can be done quite quickly with indexes) and then groups by the entire set, performing functions on each row. This could get quite slow, particularly as the number of reservations and cars increases. To alleviate this you could use where clauses on the inner query and combine that with appropriate indexes to reduce the number of items you are inspecting. There's also other tricks you can use to move the comparison of the start and end dates into the join condition, but that's a topic for another day :)
And finally, as always, if there's incorrect edge cases, mistakes, wrong syntax, whatever - let me know and I'll edit to correct!
I have to run this Mysql query on my website to fetch huge amount of data: (3 tables , each with 100,000 + records)
SELECT on_resume.*, on_users.subscribed, on_users.user_avatar, on_resume_page.*
FROM on_resume
LEFT JOIN on_users ON (on_resume.resume_userid = on_users.user_id )
LEFT JOIN on_resume_page ON ( on_resume.resume_userid = on_resume_page.resume_userid)
WHERE on_resume.active= '1'
GROUP BY on_resume.rid
ORDER BY on_resume.rid DESC
LIMIT 0,18
The time I run this at Phpmyadmin sql section, the whole mysqld service will be down and needs to be restarted.
Now I was testing this query and I found out if I don't use Group by and Order by conditions the query will be fine.
SELECT on_resume.*, on_users.subscribed, on_users.user_avatar, on_resume_page.*
FROM on_resume
LEFT JOIN on_users ON (on_resume.resume_userid = on_users.user_id )
LEFT JOIN on_resume_page ON ( on_resume.resume_userid = on_resume_page.resume_userid)
WHERE on_resume.active= '1'
LIMIT 0,18
Showing rows 0 - 17 ( 18 total, Query took 0.4248 sec)
Why is it like this and how can I fix it?...
NOTE : I have tested the SQL query with group by or Order by alone in either case , even with one of them still the query fails and hangs the server.
EDIT : This problem is solved by making column on_resume_page.resume_userid indexed.
This is what i was told, took a while to figure it out:
Look at #jer in Chicago comment
Remember, when there is a GROUP BY clause, there are certain rules that apply for grouping columns. One of those rules is "The Single-Value Rule" -- every column named in the SELECT list must also be a grouping column unless it is an argument for one of the set functions. MySQL extends standard SQL by allowing you to use columns or calculations in a SELECT list that don't appear in a GROUP BY clause. However, we are warned not to use this feature unless the columns you omit from the GROUP BY clause are not unique in the group because you will get unpredictable results.