Sort certain values to the top - mysql

I have a MySQL table with the following data (simplified):
INSERT INTO `stores` (`storeId`, `name`, `country`) VALUES
(1, 'Foo', 'us'),
(2, 'Bar', 'jp'),
(3, 'Baz', 'us'),
(4, 'Foo2', 'se'),
(5, 'Baz2', 'jp'),
(6, 'Bar3', 'jp');
Now, I want to be able to get a paginated list of stores that begins with the customers country.
For example, an american customer would see the following list:
Foo
Baz
Bar
Foo2
Baz2
Bar3
The naive solution I'm using right now (example with an american customer and page size 3):
(SELECT * FROM stores WHERE country = "us") UNION (SELECT * FROM stores WHERE country != "us") LIMIT 0,3
Are there any better ways of doing this? Could ORDER BY be used and told to put a certain value at the top?

Try this:
SELECT * FROM stores ORDER BY country = "us" DESC, storeId

To get the searched-for country first, and the remainder alphabetically:
SELECT *
FROM stores
ORDER BY country = 'us' DESC, country ASC

You have to link each value of a country with a numeric, with a case :
select *
from stores
order by case when country = "us" then 1
else 0
end desc

Create a table of country codes and orders, join to it in your query, and then order by the country code's order.
So you would have a table that looks like
CountryOrder
Code Ord
---- ---
us 1
jp 2
se 3
and then code that looks like:
SELECT s.*
FROM Stores s
INNER JOIN CountryOrder c
ON c.Code = s.Country
ORDER BY c.Ord;

How about using IF to assign the top value to US rows.
select if(country_cd='us,'aaaUS',country_cd) sort_country_cd, country_cd from stores Order by sort_country_cd
This will give you a pseudo column called sort_country_cd .
Here you can map "US" to "aaaUS".
JP can still be mapped to JP.
That puts US on the top of your sort list.

Related

SQL- preserve order from 'IN' clause and return null for non matching clauses

Let's say I have a table Person with columns id, name, and phone. I want to fetch all records matching a list of pairs of names and phone numbers while preserving the order from the 'IN' clause and returning null or any default value for the mismatching clause.
For instance, if the Person table has the following records:
id
name
phone
1
Name1
1234
2
Name2
2345
3
Name3
4532
I want the query to return the ids of people matching pairs of names and phone numbers.
When queried with
('Name2', 2345), ('NonExistingName', 34543), ('Name1', 1234) should return a list [2, <null or a default value>, 1]
I am aware that I can use IN clause to find the matching rows,
SELECT id
FROM Person
WHERE (name, phone) in (('Name2', 2345),
('NonExistingName', 34543),
('Name1', 1234));
however, this alone doesn't fulfill what I want. The rows returned do not preserve the order and do not allow me to add a default value for nonexisting ids.
Relational databases explicitly disclaim any responsibility to ever preserve order unless you specify an ORDER BY clause. Therefore you will need to include the order information as part of the data in a way where you can reference it in the ORDER BY clause.
For example:
WITH source AS (
SELECT 'Name2' Name, 2345 Phone, 0 Ordinal
UNION
SELECT 'NonExistingName', 34543, 1
UNION
SELECT 'Name1', 1234, 2
)
SELECT p.id
FROM source s
LEFT JOIN Person p ON s.Name = p.Name and s.Phone = p.Phone
ORDER BY s.Ordinal
Or:
SELECT p.id
FROM (VALUES
ROW ('Name2', 2345, 0),
ROW ('NonExistingName', 34543, 1),
ROW ('Name1', 1234, 2)
) s
LEFT JOIN Person p ON s.column_0 = p.Name and s.column_1 = p.Phone
ORDER BY s.column_2

SQL question. Find the two person having same hobbies in one table

TABLE [tbl_hobby]
person_id (int) , hobby_id(int)
has many records. I want to get a SQL query to find all pairs of personid who have the same hobbies( same hobby_id ).
If A has hobby_id 1, B has too, if A doesn't have hobby_id 2, B doesn't have too, we will output A & B 's person_ids.
If A and B and C reach the limits, we output A & B , B & C, A & C.
I've finished in a very very very stupid method, multiple joins the table itself and multiple sub-queries. And of course be laughed by leader.
Is there any high performance method in a SQL for this question?
I have been thinking hard for this since 36 hrs ago......
sample data in mysql dump
CREATE TABLE `tbl_hobby` (
`person_id` int(11) NOT NULL,
`hobby_id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `tbl_hobby` (`person_id`, `hobby_id`) VALUES
(1, 1),(1, 2),(1, 3),(1, 4),(1, 5),(2, 2),
(2, 3),(2, 4),(3, 1),(3, 2),(3, 3),(3, 4),
(4, 1),(4, 3),(4, 4),(5, 1),(5, 5),(5, 9),
(6, 2),(6, 3),(6, 4),(7, 1),(7, 3),(7, 7),
(8, 2),(8, 3),(8, 4),(9, 1),(9, 2),(9, 3),
(9, 4),(10, 1),(10, 5),(10, 9),(10, 11);
COMMIT;
Expert result: (2 and 6 and 8 same, 3 and 9 same)
2,6
2,8
6,8
3,9
Order of result records and order of the two number in one record is not important. Result record in one column or in two columns are all accepted since it can be easily concated or seperated.
Aggregate per person to get strings of their hobbies. Then aggregate per hobby list find out which belong to more than one person.
select hobbies, group_concat(person_id order by person_id) as persons
from
(
select person_id, group_concat(hobby_id order by hobby_id) as hobbies
from tbl_hobby
group by person_id
) persons
group by hobbies
having count(*) > 1
order by hobbies;
This gives a a list of persons per hobby. Which is the easiest way to output a solution as we would otherwise have to build all possible pairs.
UPDATE: If you want pairs, you'll have to query the table twice:
select p1.person_id as person 1, p2.person_id as person2
from
(
select person_id, group_concat(hobby_id order by hobby_id) as hobbies
from tbl_hobby
group by person_id
) p1
join
(
select person_id, group_concat(hobby_id order by hobby_id) as hobbies
from tbl_hobby
group by person_id
) p2 on p2.person_id > p1.person_id and p2.hobbies = p1.hobbies
order by person1, person2;
Alternative version, without using any proprietary string handling:
select distinct t1.person_id, t2.person_id
from tbl_hobby t1
join tbl_hobby t2
on t1.person_id < t2.person_id
where 2 = all (select count(*)
from tbl_hobby
where person_id in (t1.person_id, t2.person_id)
group by hobby_id);
Perhaps less efficient, but portable!

How do I calculate the percentage of a value in a column based on constraints on it , in sql?

I was wondering how to calculate the percentage of a certain value based on constraints on it
For instance, let's say I have a hypothetical table called Cats. I insert the following values into it:
Create table Cats (Cat_ID int, Cat_name varchar(max), Cat_Hometown varchar(max), Gender varchar(max), Birth_Year int);
INSERT INTO Cats (Cat_ID, Cat_Name, Cat_Hometown, Gender, Birth_year)
VALUES (1, 'Blue','Boston','M', 1980),
(2, 'Steamer','Plymouth','F', 1999),
(3, 'Stack','Newton','F', 1980),
(4, 'Overflow','Boston','M', 1978),
(5, 'CatorDog','Allston','F', 1999);
What if I want to determine the percentage of the female cats that were born in the leading hometown of the year 1980? I can't seem to figure it out.
By leading hometown I mean the most common hometown.
Assuming leading hometown means the most number of rows in the table for a given year, you can order by the count and get the most common one. Then use conditional aggregation to get the percentage. AVG(gender='F') uses the fact that MySQL treats conditions as booleans returning 1 for True and 0 for False.
select AVG(gender='F')
from cats
where birth_year=1980 and
cat_hometown in (select cat_hometown
from cats
where birth_year=1980
group by cat_hometown
order by count(*) desc
limit 1)
You need to figure out the most common home town. That is a bit challenging, but a subquery helps. So:
select avg(c.Gender = 'Female' and c.Birth_Year = 1980 and c.Cat_Hometown = ch.Cat_Hometown)
from cats c cross join
(select c2.Cat_Hometown
from cats c2
group by c2.Cat_Hometown
order by count(*) desc
limit 1
) ch;

SELECT WHERE IN - mySQL

let's say I have the following Table:
ID, Name
1, John
2, Jim
3, Steve
4, Tom
I run the following query
SELECT Id FROM Table WHERE NAME IN ('John', 'Jim', 'Bill');
I want to get something like:
ID
1
2
NULL or 0
Is it possible?
How about this?
SELECT Id FROM Table WHERE NAME IN ('John', 'Jim', 'Bill')
UNION
SELECT null;
Start by creating a subquery of names you're looking for, then left join the subquery to your table:
SELECT myTable.ID
FROM (
SELECT 'John' AS Name
UNION SELECT 'Jim'
UNION SELECT 'Bill'
) NameList
LEFT JOIN myTable ON NameList.Name = myTable.Name
This will return null for each name that isn't found. To return a zero instead, just start the query with SELECT COALESCE(myTable.ID, 0) instead of SELECT myTable.ID.
There's a SQL Fiddle here.
The question is a bit confusing. "IN" is a valid operator in SQL and it means a match with any of the values (see here ):
SELECT Id FROM Table WHERE NAME IN ('John', 'Jim', 'Bill');
Is the same as:
SELECT Id FROM Table WHERE NAME = 'John' OR NAME = 'Jim' OR NAME = 'Bill';
In your answer you seem to want the replies for each of the values, in order. This is accomplished by joining the results with UNION ALL (only UNION eliminates duplicates and can change the order):
SELECT max(Id) FROM Table WHERE NAME = 'John' UNION ALL
SELECT max(Id) FROM Table WHERE NAME = 'Jim' UNION ALL
SELECT max(Id) FROM Table WHERE NAME = 'Bill';
The above will return 1 Id (the max) if there are matches and NULL if there are none (e.g. for Bill). Note that in general you can have more than one row matching some of the names in your list, I used "max" to select one, you may be better of in keeping the loop on the values outside the query or in using the (ID, Name) table in a join with other tables in your database, instead of making the list of ID and then using it.

Count on various substrings from same column

I want to count the number of occurences of several substrings from the same column and with the same WHERE condition. For each substring I have a UNION SELECT statement including the repeating WHERE condition. The final result is a query with appox. 2500 lines of sql code, but it gives good result.
Simplified example of query:
SELECT ‘be’ AS country, count(destination) FROM demo
WHERE destination LIKE ‘%be%’ AND field_a=xzx AND field_b=bbb
UNION SELECT ‘de’ AS country, count(destination) FROM demo
WHERE destination LIKE ‘%de%’ AND field_a=xzx AND field_b=bbb
UNION SELECT ‘fr’ AS country, count(destination) FROM demo
WHERE destination LIKE ‘%fr%’ AND field_a=xzx AND field_b=bbb
UNION SELECT ‘nl’ AS country, count(destination) FROM demo
WHERE destination LIKE ‘%nl%’ AND field_a=xzx AND field_b=bbb
Is it possible to modify the query in such a way that the WHERE condition only appears once in the query?
Please find a simplified example of my question via this link:
https://docs.google.com/document/d/1otjZZlBy6au5E2I7T6NdSYmLayhNGWyXGxGo3gBx_-w/edit
DON'T store comma separated values in a single field. Instead, use a many:many relationship table and store one (id,country) combination per row. Your data structure is a SQL-Anti-Pattern and goes against all relational database design principles.
CREATE TABLE map (
demo_id INT,
country VARCHAR(2),
PRIMARY KEY (demo_id, country)
)
INSERT INTO map VALUES
(1, 'nl'), (1, 'de'), (1, 'be'), (1, 'fr'),
(2, 'de'), (2, 'fr'), (2, 'be'),
(3, 'fr'), (3, 'nl'),
(4, 'nl')
Then you will have this single query...
SELECT
map.country,
COUNT(*)
FROM
demo
INNER JOIN
map
ON map.demo_id = demo.id
WHERE
demo.field_a = 'xzx'
AND demo.field_b = 'bbb'
select a.country,count(*) from
(
select 'be' as country
union all
select 'de' as country
union all
select 'fr' as country
union all
select 'nl' as country
)a join demo d
on d.destination like '%'+a.country+'%'
AND d.field_a=xzx AND d.field_b=bbb
group by a.country
I am not sure whether mySQL supports derived table or not . If its not supported the you have to create a temp table with all different country values and replace it with table a in the above query
Use COUNT CASE WHEN ... THEN ... END
SELECT
COUNT(CASE WHEN destination LIKE "%be%" THEN 1 ELSE NULL END) AS "BE",
COUNT(CASE WHEN destination LIKE "%fr%" THEN 1 ELSE NULL END) AS "FR",
COUNT(CASE WHEN destination LIKE "%de%" THEN 1 ELSE NULL END) AS "DE",
COUNT(CASE WHEN destination LIKE "%nl%" THEN 1 ELSE NULL END) AS "NL"
FROM demo
WHERE field_a = "xzx" AND field_b = "bbb"
But you should really think about changing the structure of your table. Having comma-separated values in a single field is not really recommanded.
Instead, you should have for example here an ID for each travel (or whatever it is, I said travel because of the destination field). Then, make an entry for each destination within this travel. With this kind of structure, you'd have a request like this :
SELECT destination, COUNT(id)
FROM demo
WHERE field_a = "xzx" AND field_b = "bbb"
GROUP BY destination