MySQL: Order by field, placing empty cells at end - mysql

I have a MySQL table which contains a number of products. What I want to do is sort the table by one particular column (most of the values begin with numbers for example: 1st, 2nd), etc. However, since some records do not have a value for this column, when I try to sort, the table automatically puts empty rows FIRST.
I am looking for a way to sort the row ASCENDING, but only insert blank records at the end of the sorted records, if that makes sense?
Any help would be most gratefully received!

select * from table
order by if(field = '' or field is null,1,0),field

This is one of the most effective method
ASC Order
SELECT * FROM user ORDER BY name IS NULL, name ASC
Expected Result:
+----+--------+------------+
| id | name | date_login |
+----+--------+------------+
| 3 | david | 2016-12-24 |
| 2 | john | NULL |
| 4 | zayne | 2017-03-02 |
| 1 | NULL | 2017-03-12 |
DESC Order
SELECT * FROM user ORDER BY name IS NULL, name DESC
Expected Result:
+----+--------+------------+
| id | name | date_login |
+----+--------+------------+
| 4 | zayne | 2017-03-02 |
| 2 | john | NULL |
| 3 | david | 2016-12-24 |
| 1 | NULL | 2017-03-12 |

Related

String begins with a number return different result after query

Suppose I have a table A like this:
+----+---------+--+
| Id | Email | |
+----+---------+--+
| 1 | a#b.com | |
| 2 | c#d.com | |
| 3 | a#b.com | |
| 4 | a#b.com | |
| 5 | a#b.com | |
+----+---------+--+
and my sql query is:
select Email
, case when #record=Email
then #cnt:=#cnt+1
when #record:=Email
then #cnt:=1
end as n
from A
, (select #cnt:=0, #record:='') r
order
by Id;
The purpose of this query is to find the consecutive number with same Email order by Id.
it will return
+---------+------+
| Email | n |
+---------+------+
| a#b.com | NULL |
| c#d.com | NULL |
| a#b.com | NULL |
| a#b.com | 1 |
| a#b.com | 2 |
+---------+------+
Why the n column has the null result, that's not what I expected.
But if I change the value of Email field in table A:
+------+----------+
| Id | Email |
+------+----------+
| 1 | 1a#b.com |
| 2 | 2c#d.com |
| 3 | 1a#b.com |
| 4 | 1a#b.com |
| 5 | 1a#b.com |
+------+----------+
it returns:
+----------+------+
| Email | n |
+----------+------+
| 1a#b.com | 1 |
| 2c#d.com | 1 |
| 1a#b.com | 1 |
| 1a#b.com | 2 |
| 1a#b.com | 3 |
+----------+------+
I think this is the right result, but why?
I am a little confused, can anyone help me?
When #record=Email is false then the next alternative condition is investigated, #record:=Email assignement is performed, and assigned value is a value of an expression.
This expression is used as a condition, i.e. it has numeric context. Implicit datatype convertion is performed.
A string started from a letter gives zero value during this convertion. Which is treated as false.
CASE have no ELSE - so the output is NULL.
This is a gaps and islands problem, and here is one approach using the difference in row numbers method:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (ORDER BY Id) rn1,
ROW_NUMBER() OVER (PARTITION BY Email ORDER BY Id) rn2
FROM yourTable
)
SELECT Id, Email, COUNT(*) OVER (PARTITION BY Email, rn1-rn2 ORDER BY Id) AS cnt
FROM cte
ORDER BY Id;
Demo
The general approach here is to generate a pseudo group for each "island" of like emails (based on the order provided by the Id column) by using the difference in row number as determined two different ways, combined with the email itself. Then, we use COUNT as an analytic function to generate the count sequences for each effective email group.

How to sort with conditions?

I would like to sort a table by "number" only if the type is A
| number | type |
|--------|------|
| 1 | A |
| 2 | B |
| 3 | B |
| 4 | A |
| 5 | A |
the request would give :
| number | type |
|--------|------|
| 1 | A |
| 4 | A |
| 5 | A |
| 2 | B |
| 3 | B |
The rows with "A" type are sorted then the "B" rows are listed (no matter the order)
Is it possible to do it without UNION?
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(number SERIAL PRIMARY KEY
,type CHAR(1) NOT NULL
);
INSERT INTO my_table VALUES
(1,'A'),
(2,'B'),
(3,'B'),
(4,'A'),
(5,'A');
SELECT * FROM my_table ORDER BY type,CASE WHEN type = 'A' THEN number ELSE RAND() END;
+--------+------+
| number | type |
+--------+------+
| 1 | A |
| 4 | A |
| 5 | A |
| 3 | B |
| 2 | B |
+--------+------+
ORDER BY type, number will work:
SELECT number, type
FROM Tablename
ORDER BY type, number
Demo on SQL Fiddle
Note: ORDER BY type, here by default it will ordering the type values by the ascending order. It is equal to ORDER BY type ASC. As per your need, you may use DESC or ASC.
A CASE statement can help.
For "B" rows 'no matter the order'. Does that mean original order has to be retained? In that case following query can help.
select * from TableName
order by type, CASE WHEN type='A' THEN number ELSE 0 END
If original order need not be retained, then ELSE RAND() as answered by Strawberry is good.

MySQL: aggregate changes for multiple objects from a temporary table

I have a table like this, where every status change for every character is saved.
table characters_changes
+----+--------+---------+-------+----------------------+
| id | rank | job | money | datetime |
+----+--------+---------+-------+----------------------+
| 1 | 2 | tailor | 25 | 2018-06-01 12:30:15 |
| 1 | 3 | NULL | 5 | 2018-06-02 10:50:19 |
| 1 | 2 | NULL | -5 | 2018-06-03 18:44:35 |
| 1 | NULL | tinker | 10 | 2018-06-04 04:10:12 |
| 1 | 3 | NULL | NULL | 2018-06-05 17:31:00 |
| 2 | 1 | spy | 7 | 2018-06-01 12:30:15 |
| 2 | 2 | NULL | NULL | 2018-06-02 10:50:19 |
| 2 | NULL | no job | 7 | 2018-06-03 17:31:00 |
| 3 | 3 | soldier | 12 | 2018-06-01 12:30:15 |
| 3 | 1 | NULL | -11 | 2018-06-02 10:50:19 |
+----+--------+---------+-------+----------------------+
NULL means that there was no change at corresponding attribute.
rank and job changes mean replace one with another,
while money change means adding and subtracting the sum (if not NULL). There is guaranteed at least one row per character without any NULL-s.
So I need to get a table where I could show current status of every character at the end.
With their last rank, last job and resulting sum of money. A table like this one.
table characters_status
+----+--------+---------+-------+
| id | rank | job | money |
+----+--------+---------+-------+
| 1 | 3 | tinker | 35 |
| 2 | 2 | no job | 14 |
| 3 | 1 | soldier | 1 |
+----+--------+---------+-------+
What even worse, table characters_changes is a temporary table.
datetime in it comes from another table of events.
So as it is temporary, I can only query it once. But there can be any number of characters and most likely going to be more columns like rank and job.
The whole system is needed to provide the possibility to get statuses for all characters at any given datetime by ignoring all changes after that. But that part is easy for me, so I left it out of scope of my question.
This should work, I think:
SELECT id
, CASE WHEN INSTR(ranks, '|') = 0 THEN ranks ELSE LEFT(ranks, INSTR(ranks, '|')-1) END AS rank
, CASE WHEN INSTR(jobs, '|') = 0 THEN jobs ELSE LEFT(jobs, INSTR(jobs, '|')-1) END AS job
, monies
FROM
(
SELECT id
, GROUP_CONCAT(rank ORDER BY datetime DESC SEPARATOR '|') AS ranks
, GROUP_CONCAT(job ORDER BY datetime DESC SEPARATOR '|') AS jobs
, SUM(money) AS monies
FROM characters_changes
GROUP BY id
) AS lists
;
Technically, you can do it without a subquery, but I broke it down this way for clarity. The alternative would be expressions like this:
, CASE COUNT(rank) WHEN 0 THEN NULL WHEN 1 THEN GROUP_CONCAT(rank ORDER BY datetime DESC SEPARATOR '|') ELSE LEFT(GROUP_CONCAT(rank ORDER BY datetime DESC SEPARATOR '|'), INSTR(GROUP_CONCAT(rank ORDER BY datetime DESC SEPARATOR '|'), '|')-1) END AS rank

mysql order by two unixtime columns combine

Table user structure:
id(PK)(int)
name(varchar)
time1(int10) unixtime
time2(int10) unixtime
SELECT * FROM `user` ORDER BY `time1` DESC,`time2` DESC
+------------------------------------------------------+
| id | name | time1 | time2 |
+------------------------------------------------------+
| 12345 | Joe | 1405605785 | 1406733506 |
| 12346 | John | 1406733506 | |
| 12347 | David | | 1405684190 |
+------------------------------------------------------+
I am using this sql:
SELECT * FROM `user` ORDER BY `time1` DESC,`time2` DESC
How to combine two columns time to sort desc?
============
more example reply for sagi
+------------------------------------------------------+
| id | name | time1 | time2 |
+------------------------------------------------------+
| 12345 | Joe | 1 | 2 |
| 12346 | John | 5 | |
| 12347 | David | | 4 |
+------------------------------------------------------+
I want to sort like that (DESC)
John (time = 5)
David (time = 4)
Joe (time max value = 2)
This is called conditional ordering using CASE EXPRESSION :
SELECT * FROM `user` t
ORDER BY CASE WHEN t.time1 is null THEN t.time2 ELSE t.time1 END DESC
Could also be written as:
ORDER BY coalesce(t.time1,t.time2)
You didn't provide any expected results, and you didn't say by which column the result should be ordered in case both time1,time2 are not NULL , so I assumed you want it to be ordered by the first column. If that's not the case, replace time1 with time2 .
Try this:
ORDER BY GREATEST(t.time1,t.time2) DESC

Distinct order-number sequence for every customer

I have table of orders. Each customer (identified by the email field) has his own orders. I need to give a different sequence of order numbers for each customer. Here is example:
----------------------------
| email | number |
----------------------------
| test#com.com | 1 |
----------------------------
| example#com.com | 1 |
----------------------------
| test#com.com | 2 |
----------------------------
| test#com.com | 3 |
----------------------------
| client#aaa.com | 1 |
----------------------------
| example#com.com | 2 |
----------------------------
Is possible to do that in a simple way with mysql?
If you want update data in this table after an insert, first of all you need a primary key, a simple auto-increment column does the job.
After that you can try to elaborate various script to fill the number column, but as you can see from other answer, they are not so "simple way".
I suggest to assign the order number in the insert statement, obtaining the order number with this "simpler" query.
select coalesce(max(`number`), 0)+1
from orders
where email='test1#test.com'
If you want do everything in a single insert (better for performance and to avoid concurrency problems)
insert into orders (email, `number`, other_field)
select email, coalesce(max(`number`), 0) + 1 as number, 'note...' as other_field
from orders where email = 'test1#test.com';
To be more confident about not assign at the same customer two orders with the same number, I strongly suggest to add an unique constraint to the columns (email,number)
create a column order_number
SELECT #i:=1000;
UPDATE yourTable SET order_number = #i:=#i+1;
This will keep incrementing the column value in order_number column and will start right after 1000, you can change the value or even you can even use the primary key as the order number since it is unique all the time
I think one more need column for this type of out put.
Example
+------+------+
| i | j |
+------+------+
| 1 | 11 |
| 1 | 12 |
| 1 | 13 |
| 2 | 21 |
| 2 | 22 |
| 2 | 23 |
| 3 | 31 |
| 3 | 32 |
| 3 | 33 |
| 4 | 14 |
+------+------+
You can get this result:
+------+------+------------+
| i | j | row_number |
+------+------+------------+
| 1 | 11 | 1 |
| 1 | 12 | 2 |
| 1 | 13 | 3 |
| 2 | 21 | 1 |
| 2 | 22 | 2 |
| 2 | 23 | 3 |
| 3 | 31 | 1 |
| 3 | 32 | 2 |
| 3 | 33 | 3 |
| 4 | 14 | 1 |
+------+------+------------+
By running this query, which doesn't need any variable defined:
SELECT a.i, a.j, count(*) as row_number FROM test a
JOIN test b ON a.i = b.i AND a.j >= b.j
GROUP BY a.i, a.j
Hope that helps!
You can add number using SELECT statement without adding any columns in table orders.
try this:
SELECT email,
(CASE email
WHEN #email
THEN #rownumber := #rownumber + 1
ELSE #rownumber := 1 AND #email:= email END) as number
FROM orders
JOIN (SELECT #rownumber:=0, #email:='') AS t