Sort result with empty strings first - mysql

I have this locations table:
+----+-----------+------------+----------+
| id | country | state | city |
+----+-----------+------------+----------+
| 1 | US | Georgia | Atlanta |
| 2 | US | California | |
| 3 | US | | |
| 4 | Canada | Ontario | |
| 5 | Canada | Manitoba | Winnipeg |
| 6 | Canada | | |
I want to create a query but could not build my ORDER BY properly. This is the result that I want:
+----+-----------+------------+----------+
| id | country | state | city |
+----+-----------+------------+----------+
| 6 | Canada | | |
| 3 | US | | |
| 4 | Canada | Ontario | |
| 2 | US | California | |
| 5 | Canada | Manitoba | Winnipeg |
| 1 | US | Georgia | Atlanta |
Basically, this is the priority that I want to follow:
Country listing. Alphabetical order.
State listing. Alphabetical order.
City listing. Alphabetical order.
This query does not seem to account for empty columns (I am not using NULLs in my locations table):
SELECT * FROM locations
ORDER BY
country,state,city

Try this ORDER BY clause:
SELECT *
FROM locations
ORDER BY
CASE WHEN state = '' AND city = '' THEN 0
WHEN city = '' THEN 1
ELSE 2 END,
country,
state,
city;
This sorting logic places first those records which are missing both state and city. Next follows records missing city only, followed last by records having non empty data for all three fields.

For versions pre 8.0...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id SERIAL PRIMARY KEY
,country VARCHAR(20) NOT NULL
,state VARCHAR(20) NULL
,city VARCHAR(20) NULL
);
INSERT INTO my_table VALUES
(1,'US','Georgia','Atlanta'),
(2,'US','California',NULL),
(3,'US',NULL,NULL),
(4,'Canada','Ontario',NULL),
(5,'Canada','Manitoba','Winnipeg'),
(6,'Canada',NULL,NULL);
SELECT id
, country
, state
, city
FROM
( SELECT x.*
, CASE WHEN #prev=country THEN #i:=#i+1 ELSE #i:=1 END i
, #prev:=country
FROM my_table x
, (SELECT #prev:=null,#i:=0) vars
ORDER
BY country
, city
, state
) a
ORDER
BY i
, country;
+----+---------+------------+----------+
| id | country | state | city |
+----+---------+------------+----------+
| 6 | Canada | NULL | NULL |
| 3 | US | NULL | NULL |
| 4 | Canada | Ontario | NULL |
| 2 | US | California | NULL |
| 5 | Canada | Manitoba | Winnipeg |
| 1 | US | Georgia | Atlanta |
+----+---------+------------+----------+

select *
from locations
order by
state <> '',
city <> '',
country,
state,
city
db-fiddle
Note that in MySQL a boolean expression returns 0 (for FALSE) or 1 (for TRUE). That means for an empty state string state <> '' will return 0 and thus ordered first.

Related

SQL : query for multiple conditions with multiple columns

How to query multiple conditions with multiple columns, like AND clause in AND clause.
Example data
| Name | Country | ZipCode |
| -------- | -------------- | -------------- |
| A | Italy | 2020 |
| B | Japan | 1010 |
| C | Canada | 3030 |
| D | Japan | 1011 |
| E | Japan | 1012 |
The result that I need is without..
Country = Japan and ZipCode = 1010
Country = Canada and ZipCode = 3030
| Name | Country | ZipCode |
| -------- | -------------- | -------------- |
| A | Italy | 2020 |
| D | Japan | 1011 |
| E | Japan | 1012 |
I try to write SQL like :
SELECT * FROM table
WHERE ((Country <> 'Japan' AND ZipCode <> '1010') AND
(Country <> 'Canada' AND ZipCode <> '3030'))
but it's not correct. Any help please ?
Just use not:
where not (country = 'Japan' and ZipCode = '1010' or
Country = 'Canada' and ZipCode = '3030'
)
You can expand this out of course:
where (country <> 'Japan' or ZipCode <> '1010') and
(country <> 'Canada' or Zipcode <> '3030')
Note: This constructs will also filter out NULL values. There are none in your sample data and they can both be tweaked to handle them if necessary.

finding distinct pairs in sql

I was trying to learn non-equi joins when I encountered this problem. I have a table pops:
+------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+-------+
| country | varchar(100) | YES | | NULL | |
| continent | varchar(100) | YES | | NULL | |
| population | bigint(20) | YES | | NULL | |
I was trying to find countries with their population in the vicinity of say, 100.
select distinct
p1.country,
p2.country,
p1.population,
p2.population
from pops p1
inner join pops p2
on p1.population between p2.population - 100 and p2.population + 100
and p1.country <> p2.country
where p2.country <> p1.country
output I got was:
+------------+------------+------------+------------+
| country | country | population | population |
+------------+------------+------------+------------+
| pakistan | india | 99988 | 99999 |
| china | india | 99990 | 99999 |
| bangladesh | japan | 999 | 999 |
| india | pakistan | 99999 | 99988 |
| china | pakistan | 99990 | 99988 |
| japan | bangladesh | 999 | 999 |
| india | china | 99999 | 99990 |
| pakistan | china | 99988 | 99990 |
+------------+------------+------------+------------+
as we can see, I am getting pairs of (india, pakistan) as well as (pakistan, india), which is data-wise the same thing. Is it possible to eliminate one of the records from the pair?
You could decide to always have the lexographically first (or last, for argument's sake) country on the p1 side - use < (or >) instead of <>.
Also, note that your where clause is redundant, since you already have this condition in the on clause of the join:
select p1.country,
p2.country,
p1.population,
p2.population
from pops p1
inner join pops p2
on p1.population between p2.population - 100 and p2.population + 100 and
p1.country < p2.country
-- Here ------------------^
just change the join condition of p1.country <> p2.country to p1.country < p2.country

Get information from tree parent as column

I've got data with geographical locations. That may be cities, provinces, states, countries, continents, or whatever kind of location. A location can be part of an other location, like a State is part of a Country (like the US), but The Netherlands has provinces. Cities can be in a Province but they aren't necessarily part of a province. Partly because some cities are city-state(-like) (i.e. Luxembourg), but in my case it's not even relevant in what province a city is, if there's only one city from a country in my locations list.
This this very simple example:
| types |
|-----------|
| Continent |
| Country |
| Province |
| City |
Locations
| id | name | type | parent |
|----|---------------|-----------|--------|
| 1 | Europe | Continent | NULL |
| 2 | Netherlands | Country | 1 |
| 3 | Noord-Holland | Province | 2 |
| 4 | Amsterdam | City | 3 |
| 5 | Haarlem | City | 3 |
| 6 | Luxembourg | City | 1 |
For every location I want to know their 'geographical parent' (if exist). So the expected outcome is this:
| id | name | type | Continent | Country | Province |
|----|---------------|-----------|-----------|-------------|---------------|
| 1 | Europe | Continent | | | |
| 2 | Netherlands | Country | Europe | | |
| 3 | Noord-Holland | Province | Europe | Netherlands | |
| 4 | Amsterdam | City | Europe | Netherlands | Noord-Holland |
| 5 | Haarlem | City | Europe | Netherlands | Noord-Holland |
| 6 | Luxembourg | City | Europe | | |
How can I get all types as a column for my Locations table? I've tried to use subqueries, but I'm completely stuck because of the recursiveness: Amsterdam is not part of a country (but it's Province is), while Luxembourg is part of a country (without a Province).
How can I get the expected output?
Surely there is a better solution but at the moment I wrote this:
SELECT Locations.id,
Locations.name,
Locations.type,
IF(l3.name IS NULL, IF(l2.name IS NULL, IFNULL(l1.name, ''), l2.name), l3.name) as Continent,
IF(l3.name IS NULL, IF(l2.name IS NULL, '', l1.name), l2.name) as Country,
IF(l3.name IS NULL, '', l1.name) as Province
FROM Locations
LEFT join Locations l1 on Locations.parent = l1.id
LEFT join Locations l2 on l1.parent = l2.id
LEFT join Locations l3 on l2.parent = l3.id
ORDER BY Locations.id
DEMO

Select and count where result is elsewhere

I have a Table like following:
ID | Name | Type | State | County
-------------------------------------------------------------------
1 | Rheinland-Pfalz | State | Rheinland-Pfalz | NULL
2 | Trier | City | Rheinland-Pfalz | NULL
3 | Alzey-Worms | County | Rheinland-Pfalz | Alzey-Worms
4 | Alzey | City | Rheinland-Pfalz | Alzey-Worms
5 | Worms | City | Rheinland-Pfalz | Alzey-Worms
6 | Lorem | County | Rheinland-Pfalz | Lorem
7 | Ipsum | City | Rheinland-Pfalz | Lorem
Now I want to get all Counties within the state "Rheinland-Pfalz" and their included cities and all countie free cities.
Wished Result:
ID | Name | Type | Included
-------------------------------------------------------------------
2 | Trier | City | NULL
3 | Alzey-Worms | County | 2
6 | Lorem | County | 1
My Query:
select a.id, a.name, a.type,
(select count(*) from data where a.type="city" AND b.county=a.county) as included
from data as a, data as b
WHERE a.location_type='county' AND a.state = 'Rheinland-Pfalz' OR a.type='city' AND a.gmap_area1 = 'Rheinland-Pfalz' AND a.county IS NULL
order by a.name asc
My Result:
ID | Name | Type | Included
-------------------------------------------------------------------
3 | Alzey-Worms | County | 0
3 | Alzey-Worms | County | 0
3 | Alzey-Worms | County | 0
3 | Alzey-Worms | County | 0
3 | Alzey-Worms | County | 0
.... AND SO ON
Don't be shy to use parenthesis
SELECT
a.id,
a.name,
a.type,
(SELECT COUNT(*) FROM data
WHERE type='city' AND county=a.county) AS included
FROM data AS a
WHERE
a.state='Rheinland-Pfalz' AND
(a.type='county' OR (a.type='city' AND a.county IS NULL));
one way you could try group clause - not tested:
select count(*) from data where type in ('state','County') where
state = 'Rheinland-Pfalz' group by type,state;
Also to consider is the ID or detailed field level values will not appear in counts

MySQL - No result when joining tables using a NULL value

Similar question here but this is slightly different...
I have two tables that I want to join:
location
---------------------------
| id | city | state_id |
---------------------------
| 1 | Denver | 6 |
| 2 | Phoenix | 2 |
| 3 | Seattle | NULL |
---------------------------
state
-------------------
| id | name |
-------------------
| 1 | Alabama |
| 2 | Alaska |
| 3 | Arizona |
| 4 | Arkansas |
| 5 | California |
| 6 | Colorado |
-------------------
SELECT
location.id,
location.city,
state.name
FROM
location
JOIN
state ON state.id = location.state_id;
However, in the case where location.state_id happens to be NULL (perhaps the person inputting the data forgot to select a state), the query would not return a result, but that doesn't mean the location doesn't exist.
How do I get around this problem and somehow display all the locations, even though the state_id might be NULL ?
Use a LEFT OUTER JOIN
SELECT
location.id,
location.city,
state.name
FROM
location
LEFT OUTER JOIN
state ON state.id = location.state_id;