I want to select rows from tags table where tag name includes SOMETHING. The results must be sorted in a smart way: records which start with SOMETHING should go first, and then should go the rest.
It can be easily achieved using 2 queries: select by tag like 'SOMETHING%' first, and then by tag like '%SOMETHING%'. I want to combine this into one query. Is it possible? In general I want some equations which are usually used in WHERE clause to present in FIELDS section. For example,
SELECT
*,
(`name` like 'SOMETHING' ? 1 : 0) as weight0,
(`name` like 'SOMETHING%' ? 1 : 0) as weight1,
(match `name` against 'SOMETHING') as weight2,
length(name) as weight3
FROM `tags`
WHERE `name` like '%SOMETHING%'
ORDER BY weight0, weight1 desc, weight2 desc, weight3, name
LIMIT 0, 10
Obviously, the pseudo code above does not work, but I hope it shows the goal: moving WHERE conditions to SELECT area, assign numeric values, and the sort by these values. Is it somehow possible to achieve in MySQL?
You can use conditional statement like case when
SELECT
*,
(case when `name` like 'SOMETHING' then 1 else 0 end) as weight0,
(case when `name` like 'SOMETHING%' then 1 else 0 end) as weight1,
length(name) as weight3
FROM `tags`
WHERE `name` like '%SOMETHING%'
ORDER BY weight0, weight1 desc, weight3, name
LIMIT 0, 10
MySql evaluates boolean expressions as 1 for true or 0 for false and they can be used in the ORDER BY clause:
SELECT *
FROM tags
WHERE name LIKE '%SOMETHING%'
ORDER BY name = 'SOMETHING' DESC,
name LIKE 'SOMETHING%' DESC,
name LIKE '%SOMETHING%' DESC, -- this is not actually needed
CHAR_LENGTH(name),
name
LIMIT 0, 10;
Related
I want to order the query results by specific values.
Seems like I can use this:
SELECT
column
FROM
table
ORDER BY
IF(
FIELD(
id,
3,1,2
) = 0,
1,
0
) ASC,
FIELD(
id,
3,1,2
)
My problem is that 3,1,2 comes from another table column. Replacing 3,1,2 with (SELECT column from...) is not working properly. Because the SELECT returns the result as "3,1,2" and not as 3,1,2
I can also extract 3,1,2 one by one, but in this case i get error Subquery returns more than 1 row.
What's the solution here ?
Suppose that the statement that returns the values 3, 1, 2 is something like this:
SELECT somecolumn FROM sometable ORDER BY someothercolumn
then you can use GROUP_CONCAT() to create a comma separated string that contains these values:
SELECT GROUP_CONCAT(somecolumn ORDER BY someothercolumn) col FROM sometable
and then use FIND_IN_SET() instead of FIELD():
SELECT t.column
FROM table t
CROSS JOIN (SELECT GROUP_CONCAT(somecolumn col ORDER BY someothercolumn) FROM sometable) s
ORDER BY
SUBSTRING_INDEX(t.id, s.col) = 0,
SUBSTRING_INDEX(t.id, s.col)
I hope that I understood the logic that you want to apply to sort the table with your ORDER BY clause.
I'm trying to make a query to get the results that have null in one of their columns but for for some reason when I use like the part where I exclude the not null fields doesnt work
SELECT * FROM my_column WHERE title LIKE "%hea%" OR department LIKE "%hea%" OR title_pt LIKE "%hea%" AND deleted_at IS NULL LIMIT 10 OFFSET 0
You need parenthesis
SELECT * FROM my_column WHERE (title LIKE "%hea%" OR department LIKE "%hea%" OR
title_pt LIKE "%hea%") AND deleted_at IS NULL LIMIT 10 OFFSET 0
Think of it like math, you have to tell it the order of operation. You were actually asking for title_py LIKE "%hea" AND deleted_at IS NULL while the other ORs were not interacting with the AND at all.
you wrote this :
SELECT * FROM my_column
WHERE (title LIKE "%hea%") OR (department LIKE "%hea%")
OR ((title_pt LIKE "%hea%") AND (deleted_at IS NULL))
LIMIT 10 OFFSET 0
You wanted this:
SELECT * FROM my_column
WHERE ((title LIKE "%hea%") OR (department LIKE "%hea%") OR (title_pt LIKE "%hea%"))
AND (deleted_at IS NULL)
LIMIT 10 OFFSET 0
The parenthesis will give you ONLY the rows with deleted_at NULL AND one of the other conditions TRUE:
SELECT *
FROM my_column
WHERE (
title LIKE "%hea%"
OR department LIKE "%hea%"
OR title_pt LIKE "%hea%"
)
AND
deleted_at IS NULL
LIMIT 10 OFFSET 0
SELECT *
FROM `eBayorders`
WHERE (`OrderIDAmazon` IS NULL
OR `OrderIDAmazon` = "null")
AND `Flag` = "True"
AND `TYPE` = "GROUP"
AND (`Carrier` IS NULL
OR `Carrier` = "null")
AND LEFT(`SKU`, 1) = "B"
AND datediff(now(), `TIME`) < 4
AND (`TrackingInfo` IS NULL
OR `TrackingInfo` = "null")
AND `STATUS` = "PROCESSING"
GROUP BY `Name`,
`SKU`
ORDER BY `TIME` ASC LIMIT 7
I am trying to make sure that none of the names and skus will show up in the same result. I am trying to group by name and then sku, however I ran into the problem where a result showed up that has the same name and different skus, which I dont want to happen. How can I fix this query to make sure that there is always distinct names and skus in the result set?!
For example say I have an Order:
Name: Ben Z, SKU : B000334, oldest
Name: Ben Z, SKU : B000333, second oldest
Name: Will, SKU: B000334, third oldest
Name: John, SKU: B000036, fourth oldest
The query should return only:
Name: Ben Z, SKU : B000334, oldest
Name: John, SKU: B000036, fourth oldest
This is because all of the Names should only have one entry in the set along with SKU.
There are two problems here.
The first is the ANSI standard says that if you have a GROUP BY clause, the only things you can put in the SELECT clause are items listed in GROUP BY or items that use an aggregate function (SUM, COUNT, MAX, etc). The query in your question selects all the columns in the table, even those not in the GROUP BY. If you have multiple records that match a group, the table doesn't know which record to use for those extra columns.
MySql is dumb about this. A sane database server would throw an error and refuse to run that query. Sql Server, Oracle and Postgresql will all do that. MySql will make a guess about which data you want. It's not usually a good idea to let your DB server make guesses about data.
But that doesn't explain the duplicates... just why the bad query runs at all. The reason you have duplicates is that you group on both Name and SKU. So, for example, for Ben Z's record you want to see just the oldest SKU. But when you group on both Name and SKU, you get a seperate group for { Ben Z, B000334 } and { Ben Z, B000333 }... that's two rows for Ben Z, but it's what the query asked for, since SKU is also part of what determines a group.
If you only want to see one record per person, you need to group by just the person fields. This may mean building that part of the query first, to determine the base record set you need, and then JOINing to this original query as part of your full solution.
SELECT T1.*
FROM eBayorders T1
JOIN
( SELECT `Name`,
`SKU`,
max(`TIME`) AS MAX_TIME
FROM eBayorders
WHERE (`OrderIDAmazon` IS NULL OR `OrderIDAmazon` = "null") AND `Flag` = "True" AND `TYPE` = "GROUP" AND (`Carrier` IS NULL OR `Carrier` = "null") AND LEFT(`SKU`, 1) = "B" AND datediff(now(), `TIME`) < 4 AND (`TrackingInfo` IS NULL OR `TrackingInfo` = "null") AND `STATUS` = "PROCESSING"
GROUP BY `Name`,
`SKU`) AS dedupe ON T1.`Name` = dedupe.`Name`
AND T1.`SKU` = dedupe.`SKU`
AND T1.`Time` = dedupe.`MAX_TIME`
ORDER BY `TIME` ASC LIMIT 7
Your database platform should have complained because your original query had items in the select list which were not present in the group by (generally not allowed). The above should resolve it.
An even better option would be the following if your database supported window functions (MySQL doesn't, unfortunately):
SELECT *
FROM
( SELECT *,
row_number() over (partition BY `Name`, `SKU`
ORDER BY `TIME` ASC) AS dedupe_rank
FROM eBayorders
WHERE (`OrderIDAmazon` IS NULL OR `OrderIDAmazon` = "null") AND `Flag` = "True" AND `TYPE` = "GROUP" AND (`Carrier` IS NULL OR `Carrier` = "null") AND LEFT(`SKU`, 1) = "B" AND datediff(now(), `TIME`) < 4 AND (`TrackingInfo` IS NULL OR `TrackingInfo` = "null") AND `STATUS` = "PROCESSING" ) T
WHERE dedupe_rank = 1
ORDER BY T.`TIME` ASC LIMIT 7
You are trying to obtain a result set which doesn't have repeats in either the SKU nor the Name column.
You might have to add a subquery to your query, to accomplish that. The inner query would group by Name, and the Outer query would group by SKU, such that you won't have repeats in either column.
Try this :
SELECT *
FROM
(SELECT *
FROM eBayorders
WHERE (`OrderIDAmazon` IS NULL
OR `OrderIDAmazon` = "null")
AND `Flag` = "True"
AND `TYPE` = "GROUP"
AND (`Carrier` IS NULL
OR `Carrier` = "null")
AND LEFT(`SKU`, 1) = "B"
AND datediff(now(), `TIME`) < 4
AND (`TrackingInfo` IS NULL
OR `TrackingInfo` = "null")
AND `STATUS` = "PROCESSING"
GROUP BY Name)
GROUP BY `SKU`
ORDER BY `TIME` ASC LIMIT 7
With this approach you just filter out rows that do not contain the largest/latest value for TIME.
SELECT SKU, Name
FROM eBayOrders o
WHERE NOT EXISTS (SELECT 0 FROM eBayOrders WHERE Name = o.name and Time > o.Time)
GROUP BY SKU, Name
Note: If two records have exactly the same Name and Time values, you may still end up getting duplicates, because the logic you have specified does not provide any way to break up a tie.
I have three three columns in my MySQL table:
title, featured, sort_order
where title is varchar, featured is enum ("YES" or "NO") and sort_order is int.
I would like return the result set with featured as "YES" first and then sort the featured as "YES" with sort_order ascending (so, 0, 1, 2, 3, etc...) and then then sort the remaining records by title ASC (alphabetical).
I've look around at GROUP BY, etc. but am having issues finding the answer.
Hopefully what I'm trying to accomplish makes sense. Any help would be greatly appreciated :)
You mean this?
select *, IF(featured = 'YES', 0, 1) AS ftsort, CAST(IF(featured = 'YES', sort_oder, title) AS CHAR) AS nosort
from table
order by ftsort ASC, nosort ASC
GROUP BY effects the result set you are getting which is what you don't want: you need to use ORDER BY when sorting returned values in a particular way:
SELECT FEATURED, SORT_ORDER, TITLE FROM your_table
ORDER BY FEATURED, TITLE ASC, SORT_ORDER
Convert the below sql into MySQL (where needed) and this will do the trick.
DECLARE #myTable as Table
(
title varchar(50),
featured varchar(3),
sort_order int
)
Insert INTO #myTable
SELECT title, featured, sort_order
FROM test1
WHERE featured = 'YES'
ORDER BY sort_order
Insert INTO #myTable
SELECT title, featured, sort_order
FROM test1
WHERE featured = 'NO'
ORDER BY title ASC
SELECT * FROM #myTable
I Believe the below MySQL version is correct (please verify).
Create TEMPORARY TABLE myTable
(
title varchar(50),
featured varchar(3),
sort_order int
);
Insert INTO myTable
(SELECT title, featured, sort_order
FROM test1
WHERE featured = 'YES'
ORDER BY sort_order);
Insert INTO myTable
(SELECT title, featured, sort_order
FROM test1
WHERE featured = 'NO'
ORDER BY title ASC);
SELECT * FROM myTable
Of course, my field types maybe different than yours, just modify the create statement to the correct types.
Like DiscipleMichael mentioned, you can not use UNION with a SELECT using an ORDER BY clause. But there is a workaround, you can wrap the ordered queries inside a subselect.
Here is how I would do it :
SELECT * FROM (SELECT title, featured FROM yourTable WHERE featured = 'YES' ORDER BY sort_order) AS featuredYES
UNION
SELECT * FROM (SELECT title, featured FROM yourTable WHERE featured = 'NO' ORDER BY title) AS featuredNO
This is probably better than messing with ifs :
it is more readable;
it allows you to easily add another order sequence if needed;
it allows you to mix ASC and DESC order sequences if needed.
SELECT *
FROM address
WHERE name LIKE 'a%' OR name LIKE '% a%' LIMIT 10
This query retrieves names that start with a either at the beginning 'a%'
or in a word in the middle '% a%'. How can I retrieve results from LIKE 'a%' first
then LIKE '% a%'?.
add ORDER BY clause,
SELECT *
FROM address
WHERE name LIKE 'a%' OR name LIKE '% a%'
ORDER BY CASE WHEN name LIKE 'a%' THEN 0 ELSE 1 END
LIMIT 10
Here it is:
SELECT t1.*
FROM (
SELECT *
FROM address
WHERE name LIKE 'a%'
LIMIT 10
) t1
WHERE t1.name LIKE '% a%'
A union query may be in order here. Per MySQL documentation
To cause rows in a UNION result to consist of the sets of rows retrieved by each SELECT one after the other, select an additional column in each SELECT to use as a sort column and add an ORDER BY following the last SELECT:
(SELECT 1 AS sort_col, col1a, col1b, ... FROM t1)
UNION
(SELECT 2, col2a, col2b, ... FROM t2) ORDER BY sort_col;
So for your case something like
(Select *, 1 as sortcol from addresses where name like 'a%')
Union
(Select *, 2 as sortcol from addresses where name like '% a%')
Order by sortcol
Link: http://dev.mysql.com/doc/refman/5.0/en/union.html
One approach is to add an ORDER BY clause to your query:
ORDER BY IF(name LIKE 'a%',1,2)
Something like this:
SELECT *
FROM address
WHERE name LIKE 'a%' OR name LIKE '% a%'
ORDER BY IF(name LIKE 'a%',1,2)
LIMIT 10
To avoid a "Using filesort" operation on large sets (i.e. LOTS of rows in address), and if you only want to return 10 rows, a more complex looking query will likely perform better, by limiting the number of rows to be sorted:
SELECT c.*
FROM ( SELECT a.*
FROM (
SELECT *
FROM address
WHERE name LIKE 'a%'
LIMIT 10
) a
UNION ALL
SELECT b.*
FROM address b
WHERE b.name LIKE '% a%' AND b.name NOT LIKE 'a%'
LIMIT 10
) c
ORDER BY c.name LIKE 'a%' DESC
LIMIT 10