MySQL Query - How to order the results in a certain way - php

Below is a preview of what I have so far:
MySQL Table:
What I want to do is be able to sort this in a certain way. I would like for all rows that are "FREE" to be at the very top, so where special_price is equal to 0.00.
I also would like all rows with a special to be after that, and then all rows with just a normal price would be after that. Is there any way to do that without specifying a sort_id so the user wouldn't have to change the order themselves?
So in the above example, the last field "Testing" would be moved up one, and "Replace Air Filter" would be last.
Right now in my query there is no ORDER BY, so it is ordering by the id.
Any help is appreciated. Thanks!

ORDER BY COALESCE(special_price,99999) ASC, price ASC
should do the trick.
Explaination
COALESCE is a function that takes the first non-null value, so anywhere you have a null special price, it will default to 99999 (just some arbitrary high number to get it at the end of the ordering). From there we order on price, so any ties, as well as the end of the list, will be in ascending order by price.

You can combine two selects with UNION
SELECT * FROM `table` WHERE `special_price` IS NOT NULL ORDER BY `special_price` ASC,`price` ASC;
UNION ALL
SELECT * FROM `table` WHERE `special_price` IS NULL ORDER BY `price` ASC;
I don't know, how this behaves from the performance point of view, but as far as I know this is just like two simple queries, with the small overhead from UNION.
Minor update: Because both queries cannot return the same records the default behaviour UNION DISTINCT is not required and I hope, that UNION ALL is just a little bit more performant.

Using a function return value to sort the results can be very slow if you are sorting a large number of rows because MySQL can't use an index to sort these values. So how to get around this?
If an item MUST be on special to be free (which seems to be the case from the table layout image you provided) you could try:
SELECT *, special_price IS NULL AS not_special
FROM tablename
ORDER BY not_special ASC, special_price ASC, price ASC;
This adds an extra column to the results that equals 0 if the item is on special, 1 if not. We then put all the "special" items at the top of the sort and sort by ascending special price and price.
This will allow you to avoid the overhead of UNION, still use an index for the sort operation and avoid very slow queries when sorting a large number of rows with a function return value ... but only if an item must be on special to be free.

try
SELECT * FROM table_name ORDER BY CASE special_price
WHEN '0.00' THEN 1
WHEN null THEN 3
ELSE 2
END

Related

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

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;

SQL: How to sort query by date (DESC) while having rows with column specified value at top?

My brain is failing me in this. I'm sure this is easily found by searching, but I'm having a hell of a time wording it to find any relevant results.
I'm using Laravel, so if there's an Eloquent solution, that would be preferred, but a RAW query would be fine as well.
I have a table, called threads, which has the two columns status and of course updated_at. I would like to sort by updated_at DESC, but have any rows with a status of a specified value returned on top, but also that group sorted by updated_at.
I hope that makes sense.
Here's what I believe you need.
ORDER BY status=17 DESC, updated_at DESC
Edit. This ORDER BY expression doesn't have any effect on the rows selected, only on their ordering. It works because the expression status=17, as a Boolean value, has either the value 1 or 0. Ordering it DESC puts the true ones first and the false ones second. Then the updated_at DESC puts the rows in descending order by date.
I'm guessing about 17. Put your desired status there instead.
This
it sounds like you want
select status, updated_at from threads order by status, updated_at desc

SQL tricky order by

i'm trying to order a returned set, but i'm having a hard time getting it to order properly.
i'm selecting fields, and each field has a date_updated and a date_added.
when the field is added, the date_updated is null, until there is an update, which it then assigns the current date.
i need to order my list by having the most recent entries be on the BOTTOM, and oldest on the top. so if a item is added, it should go directly to the bottom. is there a way to join all the date fields together, and then just sort by oldest to newest, disregarding update or added?
this may sound kind of confusing, but i've already tried numerous combinations of ORDER BY date_added DESC, date_updated ASC, and things like that, but it's not doing what I need it to do. i get the top portion ordered by date_added and then under that is a separate ordering for date_updated...
any input would be really helpful here...
try
order by coalesce(date_updated, date_added)
COALESCE DOC
You can try a CASE statement
SELECT
firstname,
lastname,
date_added,
date_updated,
CASE date_updated IS NULL
THEN date_added
ELSE date_updated
END CASE AS order_date
FROM mytable
ORDER BY order_date DESC
What this does is it checks if date_updated is null and then puts in the new column the value of date_added. If not, it puts the date_updated. The new column is called order_date and is used for sorting.
You can also add the CASE statement in the ORDER BY clause but that might slow your query down since it has to do the concatenation on the index vs. when constructing the data. Although not conclusive, I have found a slight performance gain if the CASE is used in the SELECT vs. the ORDER BY.
Use CASE in Order By Clause.
ORDER BY CASE WHEN date_updated IS NULL
THEN 0
ELSE 1
END DESC, date_added DESC
Order by IsNull(DateUpdated,DateAdded)
would be one way

Ignore the database duplicates when setting LIMIT with PDO

I want to ignore the duplicates in my database when I will set my "LIMIT 0, 50", then "LIMIT 50, 50" then LIMIT..... I will need to scan the duplicates on only 1 column of my table, not all the columns at once. I can't merge the duplicates because they are different in a way : these duplicates have different prices.
more precisely, I will need to show a list of these items, but to show their different prices at their right.
I need a precise number (50) per pages, so I cant load less then go to the next page. I could therefore load more from the beginning (changing the max and previous offsets if i'm on a far page) in a way that if i ignore the duplicates, I will got exactly 50 per pages and I will get the good number of pages shown at the end.
I'm a bit beginner with PHP and I have no idea about how to do that. Maybe pre-scan all the table and then start writing my code, by being flexible with my scan's variables of LIMIT and everything ? what functions I need ? how ?
Else, do something pre-programmed or a function of php that I don't know it exists can solve this problem ? Or I really need to get an headhache xD
I am not entirely certain of what you are asking, but I think you might want to do a aggregate statement along these lines:
select
itemID,
group_concat(itemPrice)
from
yourTable
group by
itemID
limit 50
This will bring back a list of 50 items and a second column where all the prices are grouped together. Then in your PHP code, you can either explode() that second column keep it as is.
Edit: If you select every field, you can't then use an aggregate function. If you want to select other columns that won't be different, add them to both the select and the group by sections like this:
select
itemID,
itemName,
itemSomething,
itemSomethingElse,
group_concat(itemPrice)
from
yourTable
group by
itemID,
itemName,
itemSomething,
itemSomethingElse
limit 50
Probably you can group by item, and use GROUP_CONCAT to show different prices list? In this way you can still use LIMIT 50. If the price column is numeric, cast it to VCHAR.
I admit I borrowed the group_concat() function from the other answers :)
After reading this paragraph from the docs:
The default behavior for UNION is that duplicate rows are removed from the result.
The optional DISTINCT keyword has no effect other than the default because it also
specifies duplicate-row removal. With the optional ALL keyword, duplicate-row removal
does not occur and the result includes all matching rows from all the SELECT statements.
Assume the following table (testdb.test):
ID Name Price
1 Item-A 10
2 Item-A 15
3 Item-A 9.5
4 Item-B 5
5 Item-B 4
6 Item-B 4.5
7 Item-C 50
8 Item-C 55
9 Item-C 40
You can page this table rows (9 rows) or groups (3 groups, based on the item's name).
If you would like to page your items based on the item groups, this should help:
SELECT
name, group_concat(price)
FROM
testdb.test
GROUP BY name
LIMIT 1 , 3
UNION SELECT
name, group_concat(price)
FROM
testdb.test
GROUP BY name
LIMIT 0 , 3; -- Constant 0, range is the same as the first limit's
If you would like to page your items based on all the items (I don't think that's what you were asking for, but just in case it helps someone else), this should help:
SELECT
name, price
FROM
testdb.test
LIMIT 1 , 5
UNION SELECT
name, price
FROM
testdb.test
LIMIT 0 , 5; -- Constant 0, range is the same as the first limit's
A very important thing to note is how you'll have to modify the limits. The first limit is your key, you can start from any limit you'd like as long as it's <= count(*) but you will have to have the same range as the second limit (i.e 3 in the first example and 5 in the second example). And the second limit will always start from 0 as shown.
I enjoyed working on this, hope this helps.

How to sort rows by ON, OFF, SOLD

I have followings 3 values that could be in a row in database - ON, OFF, SOLD (column sort_it). When I set the sort clausule on ORDER BY sort_it ASC, so I will get the items with a value in the sort_in column OF, then ON and SOLD.
But I need the sequence ON, OFF, SOLD.
Exist any way to do it somehow? I mean... edit a way saving data into database will be demanding, so I would do this in the last moment.
Yes you can use custom data sortings like this:
SELECT * FROM table ORDER BY FIELD(`sort_it`,'ON','OFF','SOLD'),`sort_it`
You can use a CASE statement to generate your custom ordering sequence from the underlying values, something like this:
ORDER BY
CASE sort_it
WHEN 'ON' then 1
WHEN 'OFF' then 2
WHEN 'SOLD' then 3
END
Sample Demo: http://sqlize.com/MGz6lLb0Qk
Using Strings is generally a bad idea. it would be better to use a numeric type. Then you can say ON=1, OFF=2 and SOLD=3 and then sort.
SELECT t.*, IF(sort_it='ON',1,IF(sort_it='OFF',2,3)) as new_sort FROM Table AS t ORDER by new_sort ASC
Another solution from comments on MySql manual:
http://dev.mysql.com/doc/refman/5.0/en/sorting-rows.html
select * from tablename order by priority='High' DESC, priority='Medium' DESC, priority='Low" DESC;

Categories