How to define a custom ORDER BY in MySQL query - mysql

I need output in following order(firstly, group by last 3 letters and then arrange in order based on the first 3 digits)
ColumnA
001_eng
004_eng
002_chn
003_usa
But order by ColumnA gives me
ColumnA
001_eng
002_chn
003_usa
004_eng
This is just sample data. I have hundreds of entries in this format and the values keep changing everyday. So, specifying all the entries inside the field is not a feasible option.
I'm not sure of how to use FIELD() in my case.

You can use FIELD:
select *
from tablename
order by
FIELD(ColumnA, '001_eng', '004_eng', '002_chn', '003_usa')
(please be careful if ColumnA is not in the list the field function will return 0 and the rows will be put on top)
or you can use CASE WHEN:
select *
from tablename
order by
case
when ColumnA='001_eng' then 1
when ColumnA='004_eng' then 2
when ColumnA='002_chn' then 3
when ColumnA='003_usa' then 4
else 5
end
or you can use a different languages table where you specify the order:
id | name | sortorder
1 | 001_eng | 1
2 | 002_chn | 3
3 | 003_usa | 4
4 | 004_eng | 2
then you can use a join
select t.*
from
tablename t inner join languages l
on t.lang_id = l.id
order by
l.sortorder
(with proper indexes this would be the better solution with optimal performances)

You can use SUBSTRING_INDEX in case all ColumnA values are formatted like in the sample data:
SELECT *
FROM mytable
ORDER BY FIELD(SUBSTRING_INDEX(ColumnA, '_', -1), 'eng', 'chn', 'usa'),
SUBSTRING_INDEX(ColumnA, '_', 1)
Demo here

you can use substring() and get order by
SELECT *
FROM table_name
ORDER BY SUBSTRING(ColumnA, -7, 3);

Related

SQL Query Select Clause, need a solution to not return few rows

I have a column in a static table like this:
Vehicles
-------------
Bike
Truck
car_2018
car_2019
car_2020
car_2021
Bus
The select query needs to fetch only the car row based on the year of query (for example now its 2018, if I run this next year, it should get back _2019) long with the rest of the rows that's not based on years. Need a solution for this.
So far I have this:
SELECT Vehicles
FROM VehicleMaster
WHERE 'some where clause based on other columns'
select Vehicles
from table_name
where Vehicles like '%2018'
union all
select Vehicles
from table_name
where Vehicles not like '%car%'
You can use substring_index to split that field by underscore _ and query based on that:
CREATE TABLE vehicles(f1 varchar(30));
INSERT INTO vehicles VALUES ('Bike'),
('Truck'),
('car_2018'),
('car_2019'),
('car_2020'),
('car_2021'),
('Bus');
SELECT f1
FROM vehicles
WHERE
f1 NOT LIKE 'car%'
OR (f1 LIKE 'car%' AND substring_index(f1, "_", -1) = YEAR(CURDATE()));
+----------+
| f1 |
+----------+
| Bike |
| Truck |
| car_2018 |
| Bus |
+----------+
SqlFiddle here
You can use regex to exclude all car_#### rows, except for the current year. Assuming that your Vehicles column is called name, this should work for you:
select *
from Vehicles
where
(
-- Exclude all car_####
not trim(name) REGEXP '^car_[0-9]{4}$'
-- Except for the current year
or name = concat('car_', year(now()))
)
I think you want:
select t.*
from t
where t.vehicle = concat('car_', year(curdate())) or
t.vehicle not regexp '[0-9]{4}$'
If you want a general purpose "any current year or any without a year", then:
select t.*
from t
where t.vehicle like concat('%_', year(curdate())) or
t.vehicle not regexp '[0-9]{4}$'

Sort by JSON field values

I have a table with json values like this:
-Table 1
id | name | data
------+----------+---------------
1 | Test | {"city_id": 3, "email":"test#test.com", "city_name":"something"}
2 | Test 2 | {"city_id": 1, "email":"test2#test2.com", "city_name":"another"}
3 | Test 3 | {"city_id": 6, "email":"test3#test3.com", "city_name":"blahblah"}
Now I want SELECT records with order by data.city_name, so I use this code:
SELECT id, name, JSON_EXTRACT(data, 'city_name') AS cityName
FROM table1
ORDER BY cityName ASC
but this query cannot sort my records correctly !
P.S: city_name have UTF-8 characters.
you do not seem to be using JSON_EXTRACT() properly, try with:
SELECT id, name, JSON_EXTRACT(data, '$.city_name') AS cityName
FROM demo ORDER BY cityName ASC
Demo Fiddle
I usually cast the JSON value (->>) to the correct type in order to sort properly:
SELECT id, name, data->>'$.city_name' AS cityName
FROM table1
ORDER BY CAST(cityName AS CHAR) ASC
Otherwise, you end up sorting as a blob (binary), which are treated as binary strings (byte strings) and thus they have the binary character set and collation, and comparison and sorting are based on the numeric values of the bytes in column values (ref).
the easiest way in my opinion
SELECT * FROM YourTable order by data->"$.city_name" desc
Check This.
SELECT Id ,name,SUBSTRING_INDEX(SUBSTRING_INDEX(data,'city_name":"',-1),'"',1) as CITY
FROM tempjson
order by SUBSTRING_INDEX(SUBSTRING_INDEX(data,'city_name":"',-1),'"',1)
OutPut :

select one row multiple time when using IN()

I have this query :
select
name
from
provinces
WHERE
province_id IN(1,3,2,1)
ORDER BY FIELD(province_id, 1,3,2,1)
the Number of values in IN() are dynamic
How can I get all rows even duplicates ( in this example -> 1 ) with given ORDER BY ?
the result should be like this :
name1
name3
name2
name1
plus I shouldn't use UNION ALL :
select * from provinces WHERE province_id=1
UNION ALL
select * from provinces WHERE province_id=3
UNION ALL
select * from provinces WHERE province_id=2
UNION ALL
select * from provinces WHERE province_id=1
You need a helper table here. On SQL Server that can be something like:
SELECT name
FROM (Values (1),(3),(2),(1)) As list (id) --< List of values to join to as a table
INNER JOIN provinces ON province_id = list.id
Update: In MySQL Split Comma Separated String Into Temp Table can be used to split string parameter into a helper table.
To get the same row more than once you need to join in another table. I suggest to create, only once(!), a helper table. This table will just contain a series of natural numbers (1, 2, 3, 4, ... etc). Such a table can be useful for many other purposes.
Here is the script to create it:
create table seq (num int);
insert into seq values (1),(2),(3),(4),(5),(6),(7),(8);
insert into seq select num+8 from seq;
insert into seq select num+16 from seq;
insert into seq select num+32 from seq;
insert into seq select num+64 from seq;
/* continue doubling the number of records until you feel you have enough */
For the task at hand it is not necessary to add many records, as you only need to make sure you never have more repetitions in your in condition than in the above seq table. I guess 128 will be good enough, but feel free to double the number of records a few times more.
Once you have the above, you can write queries like this:
select province_id,
name,
#pos := instr(#in2 := insert(#in2, #pos+1, 1, '#'),
concat(',',province_id,',')) ord
from (select #in := '0,1,2,3,1,0', #in2 := #in, #pos := 10000) init
inner join provinces
on find_in_set(province_id, #in)
inner join seq
on num <= length(replace(#in, concat(',',province_id,','),
concat(',+',province_id,',')))-length(#in)
order by ord asc
Output for the sample data and sample in list:
| province_id | name | ord |
|-------------|--------|-----|
| 1 | name 1 | 2 |
| 2 | name 2 | 4 |
| 3 | name 3 | 6 |
| 1 | name 1 | 8 |
SQL Fiddle
How it works
You need to put the list of values in the assignment to the variable #in. For it to work, every valid id must be wrapped between commas, so that is why there is a dummy zero at the start and the end.
By joining in the seq table the result set can grow. The number of records joined in from seq for a particular provinces record is equal to the number of occurrences of the corresponding province_id in the list #in.
There is no out-of-the-box function to count the number of such occurrences, so the expression at the right of num <= may look a bit complex. But it just adds a character for every match in #in and checks how much the length grows by that action. That growth is the number of occurrences.
In the select clause the position of the province_id in the #in list is returned and used to order the result set, so it corresponds to the order in the #in list. In fact, the position is taken with reference to #in2, which is a copy of #in, but is allowed to change:
While this #pos is being calculated, the number at the previous found #pos in #in2 is destroyed with a # character, so the same province_id cannot be found again at the same position.
Its unclear exactly what you are wanting, but here's why its not working the way you want. The IN keyword is shorthand for creating a statement like ....Where province_id = 1 OR province_id = 2 OR province_id = 3 OR province_id = 1. Since province_id = 1 is evaluated as true at the beginning of that statement, it doesn't matter that it is included again later, it is already true. This has no bearing on whether the result returns a duplicate.

count comma-separated values from a column - sql

I want count the length of a comma separated column
I have use these
(LENGTH(Col2) - LENGTH(REPLACE(Col2,",","")) + 1)
in my select query.
Demo:
id | mycolumn
1 2,5,8,60
2 4,5,1
3 5,Null,Null
query result for first two row is coming correctly.for 1 = 4 ,2 = 3 but for 3rd row it is calculating null value also.
Here is what I believe the actual state of your data is:
id | mycolumn
1 2,5,8,60
2 4,5,1
3 NULL
In other words, the entire value for mycolumn in your third record is NULL, likely from doing an operation involving a NULL value. If you actually had the text NULL your current query should still work.
The way to get around this would be to use COALESCE(val, "") when handling the NULL values in your strings.
Crude way of doing it is to replace the occurances of ',Null' with nothing first:-
SELECT a.id, (LENGTH(REPLACE(mycolumn, ',Null', '')) - LENGTH(REPLACE(REPLACE(mycolumn, ',Null', ''),",","")) + 1)
FROM some_table a
If the values refer to the id of rows in another table then you can join against that table using FIND_IN_SET and then count the matches (assuming that the string 'Null' is not an id on that other table)
SELECT a.id, COUNT(b.id)
FROM some_table a
INNER JOIN id_list_table b
ON FIND_IN_SET(b.id, a.mycolumn)
GROUP BY a.id

Simple MySQL Query - Change table format around

I'm fairly sure this is a fairly easy answer but the answer is completely slipping my mind.
I have a database table that is currently formatted like:
event_id | elem_id | value
1 1 Value 1
1 2 Value 2
2 1 Value 3
2 2 Value 4
Both event_id and elem_id are undetermined numbers and have infinite possibilities.
How would I query it for example based on event_id 1 to get the data to be formatted as such:
event_id | 1 | 2
1 Value 1 Value 2
Knowing that elem_id is a number >= n so potentially there could be 50 elem_id yet I still need the data in that format.
Like I said I can't for the life of me figure out the query to assemble it that way. Any help would be GREATLY appreciated.
Try following:
SELECT
`event_id`,
(SELECT t2.`value` FROM table t2 WHERE t2.`event_id` = t1.`event_id` AND t2.`elem_id` = 1),
(SELECT t3.`value` FROM table t3 WHERE t3.`event_id` = t1.`event_id` AND t3.`elem_id` = 2)
FROM `table` t1 GROUP BY `event_id`;
Also you can use different way, and get elem_ids and values in comma-separated format in two cells
SELECT `event_id`, GROUP_CONCAT(`elem_id`), GROUP_CONCAT(`value`) FROM `table` GROUP BY `event_id`;
and you can change separator with following syntax: GROUP_CONCAT(field SEPARATOR '::')