How to sort with conditions? - mysql

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.

Related

how can i select key that contains 2 value in talbe

My table:
|id |value|
| 1 | a |
| 1 | b |
| 1 | c |
| 2 | b |
| 3 | a |
| 3 | d |
| 3 | c |
I want to get result like this:
|id |
| 1 |
| 3 |
whose id contain a and c values. Please help me.
Aggregation is one simple approach:
SELECT id
FROM yourTable
WHERE value IN ('a', 'c')
GROUP BY id
HAVING MIN(value) <> MAX(value);
The HAVING clause asserts that there are two different values present in each matching group after the WHERE clause has filtered off all values other than a and c. If the HAVING clause fails, then it means that both a and c are not present.
Other approach:
select id
from yourtable
group by id
having sum(case when value in('a','c') then 1 else 0 end ) = 2;

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

MySQL complex nth row selection

I have 2 tables:
Types Data
+----+----------+ +-------+-------+
| id | name | | id | type |
+----+----------+ +-------+-------+
| 1 | name1 | | 1 | 1 |
| 2 | name2 | | 2 | 5 |
| 3 | name3 | | 3 | 7 |
| 4 | name4 | | 4 | 4 |
| 5 | name5 | | 5 | 2 |
| 6 | name6 | | 6 | 6 |
| 7 | name7 | | 7 | 3 |
| .. | .. | | 8 | 5 |
+----+----------+ | 9 | 5 |
| 10 | 4 |
| 11 | 1 |
| 12 | 2 |
| 13 | 6 |
| 14 | 5 |
| 15 | 2 |
| ... | ... |
| 1...? | 1...? |
+-------+-------+
Data table is very large, it contains millions of rows I need to select 1000 rows, but the result has to be from whole table, so every nth row select. I'v done this using answer from How to select every nth row in mySQL starting at n but, I need add some more logic to it, I need a select query that would select every nth row of all the types. I guess this sound complicated so I'll try to describe what I would like to achieve:
Lets say there are 7 Types and Data table has 7M rows 0.5M rows for types 1,2,3, 1.5M rows for types 4,5,6,7 (just be clear intervals may now be equal for all the types).
I need 1000 records that contains equal amounts of types so if I 7 types each type can occur in result set ROUND(1000/7) which would be equal to 142 records per type so I need to select 142 per type from Data table;
For types 1,2,3 which contains 0.5M rows that would be ROUND(0.5M / 142) which equals every nth 3521 row;
For types 4,5,6,7 which contains 1.5M rows that would be ROUND(1.5M / 142) which equals every nth 10563 row;
So result would look something like this:
Result
+-------+------+
| id | type |
+-------+------+
| 1 | 1 |
| 3522 | 1 |
| 7043 | 1 |
| .. | .. |
| .. | 2 |
| .. | 2 |
| .. | .. |
| .. | 3 |
| .. | 3 |
| .. | .. |
| .. | 4 |
| .. | 4 |
| .. | .. |
| .. | 5 |
| .. | 5 |
| .. | .. |
| .. | 6 |
| .. | 6 |
| .. | .. |
| .. | 7 |
| .. | 7 |
| .. | .. |
+-------+------+
I could do this simply in any programming language with multiple queries that return each type's count from Data table, then after doing the maths selecting only single type at the time.
But I would like to do this purely in MySQL, using as less queries as possible.
EDIT
I'll try to explain in more detail what I wan't to achieve with real example.
I have table with 1437823 rows. Table schema looks like this:
+---------+----------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------+----------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| type | int(11) | NO | | NULL | |
| counter | int(11) | NO | | NULL | |
| time | datetime | NO | | NULL | |
+---------+----------+------+-----+---------+----------------+
That table type statistics is:
+------+-----------+
| Type | Row Count |
+------+-----------+
| 1 | 135160 |
| 2 | 291416 |
| 3 | 149863 |
| 4 | 296293 |
| 5 | 273459 |
| 6 | 275929 |
| 7 | 15703 |
+------+-----------+
(P.S. Types count can change in time.)
Let's say I need to select sample data from time interval, In first version of question I omitted time because I thought of it as insignificant but now I think it might have some significance when ordering to improve performance.
So anyway I need to select approximately 1000 rows sample in which there's equal chunk of data for each type, so the statistic of end result would look like this:
I am selecting 1000 rows with 7 types so ROUND(1000 / 7) = 143 rows per type;
+------+-----------+
| Type | Row Count |
+------+-----------+
| 1 | 143 |
| 2 | 143 |
| 3 | 143 |
| 4 | 143 |
| 5 | 143 |
| 6 | 143 |
| 7 | 143 |
+------+-----------+
So now I need to select 143 rows for each type in equal gaps in time interval. So for single type it would look something like this:
SET #start_date := '2014-04-06 22:20:21';
SET #end_date := '2015-02-20 16:20:58';
SET #nth := ROUND(
(SELECT COUNT(*) FROM data WHERE type = 1 AND time BETWEEN #start_date AND #end_date) / ROUND(1000 / (SELECT COUNT(*) FROM types))
);
SELECT r.*
FROM (SELECT * FROM data WHERE type = 1 AND time BETWEEN #start_date AND #end_date) r
CROSS
JOIN ( SELECT #i := 0 ) s
HAVING ( #i := #i + 1) MOD #nth = 1
Statistics:
+------+-----------+
| Type | Row Count |
+------+-----------+
| 1 | 144 |
+------+-----------+
This query would give me needed results with tolerable performance, but I would need a query for each type which would decrease performance and would require later to concatenate results into single data set since that's what I need for further processing, so I would like to do it in single query or at least get single result set.
P.S. I can tolerate row count deviation in result set as long as type chunks are equal.
This should do what you want (tested on a table with 100 rows with TYPE=1, 200 rows with TYPE=2, 300 rows with TYPE=3, 400 rows with TYPE=4; with the value 10 in _c / 10, I get 40 rows, 10 of each type). Please check the performance, since I'm obviously using a smaller sample table than what you really have.
select * from
(select
#n := #n + 1 _n,
_c,
data.*
from
(select
type _t,
count(*) _c
from data
group by type) _1
inner join data on(_t = data.type)
inner join (select #n := 0) _2 order by data.type) _2
where mod(_n, floor(_c / 10)) = 0
order by type, id;
Although this gets the same number from each group, it isn't guaranteed to get the exact same number from each group, since there are obviously rounding inaccuracies introduced by the floor(_c / 10).
What you want is a stratified sample. A good way to get a stratified sample is to order the rows by the type and assign a sequential number -- the numbering does not have to start over for each type.
You can then get 1000 rows by taking each nth value:
select d.*
from (select d.*, (#rn := #rn + 1) as rn
from data d cross join
(select #rn := 0) vars
order by type
) d
where mod(rn, floor( #rn / 1000 )) = 1;
Note: The final comparison is getting 1 out of n rows to approximate 1000. It might be off by one or two depending on the number of values.
EDIT:
Oops, the above does a stratified sample that matches the original distribution of the types in the data. To get equal counts for each group, enumerate them randomly and choose the first "n" for each group:
select d.*
from (select d.*,
(#rn := if(#t = type, #rn + 1,
if(#t := type, 1, 1)
)
) as rn
from data d cross join
(select #rn := 0, #t := -1) vars
order by type, rand()
) d cross join
(select count(*) as numtypes from types) as t
where rn <= 1000 / numtypes;

Fast SQL search and sort without temporary tables

I have two tables.
Tab1:
+------------+
| id | title |
+------------+
| 1 | B |
| 2 | C |
| 3 | A |
| 4 | A |
| 5 | A |
| 6 | A |
| ... |
+------------+
PK: ID
Index: title
Tab2:
+-------------------------------------------+
| id | item_id | item_key | item_value |
+-------------------------------------------+
| 1 | 1 | value | $4 |
| 2 | 1 | url | http://h.com/ |
| 3 | 2 | value | $5 |
| 4 | 3 | url | http://i.com/ |
| 5 | 3 | value | $1 |
| 6 | 3 | url | http://y.com/ |
| 7 | 4 | value | $2 |
| 8 | 4 | url | http://z.com/ |
| 9 | 5 | value | $1 |
| 10 | 5 | url | http://123.com/ |
| ... |
+-------------------------------------------+
PK: id
Index: item_id, item_key
item_id is a foreign key from tab1.
How do I make it so I get a table of ids from Tab1 in order according to criteria from both tables. The criteria are the following:
Order ASC by title. If title is the same,
Order DESC by value. If both title and value is the same,
Prioritize items who's 'url' key contains '123.com'.
The resulting table with the ordered results would be:
+------------+
| id | title |
+------------+
| 4 | A |
| 5 | A |
| 3 | A |
| 6 | A |
| 1 | B |
| 2 | C |
| ... |
+------------+
I know I can do it with:
SELECT Tab1.id, Tab1.title
FROM Tab1
JOIN Tab2 t2_val ON t2_val.item_id = Tab1.id AND t2_val.item_key='value'
JOIN Tab2 t2_url ON t2_url.item_id = Tab1.id AND t2_url.item_key='url'
ORDER BY title,
t2_val.item_value DESC,
t2_url.item_value LIKE '%123.com%' DESC
but for large data sets, it's too slow. Is there a way to do it faster? I've set index on id and title in Tab 1, and on item key in Tab 2. Now I'd like to drop temporary tables if I could, so that means no joins, right?
How else could this be done?
First, for larger data sets, your result set is going to be larger. What are you doing with the data afterwards? The decrease in performance could be primarily related to the data coming out of the database and not to the processing in the database.
Next, what indexes do you have? The query seems to be begging for an index on tab2(item_key, item_id) to resolve the joins.
And, finally, I don't see how you can get around the final sorting for order by, because it is using values from both tables.
You are using an "entity-attribute-value" (EAV) model. This can be inherently slow when choosing lots of columns for lots of records. If you know that you have these two fields, think about changing the data model so url and value are columns in tab1.
Try this:
SELECT t1.id, t1.title
FROM Tab1 t1
INNER JOIN (SELECT item_id, MAX(item_key='value', item_value, '') AS 'value',
MAX(item_key='url', item_value, '') AS 'url'
FROM Tab2 GROUP BY item_id
) t2 ON t2.item_id = t1.id
ORDER BY t1.title, t2.value DESC, IF(t2.url LIKE '%123.com%', 0, 1);

MySQL: Order by field, placing empty cells at end

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 |