Group data irrespective of its presence in the separate columns - sql

I have a table with different airline routes and I want to group them without taking into account which is the origin city and which is the destination.
e.g. the values of the routes Amsterdam-London and London-Amsterdam should be grouped together by the route Amsterdam-London.
Has anybody faced a similar problem?

I think you are looking for
CREATE TABLE Routes(
ID INT,
OriginalLocation VARCHAR(45),
Destination VARCHAR(45)
);
INSERT INTO Routes VALUES
(1, 'Amsterdam', 'London'),
(2, 'London', 'Amsterdam'),
(3, 'London', 'Algeria'),
(4, 'Algeria', 'France');
WITH C AS
(
SELECT *,
CASE WHEN EXISTS(
SELECT 1
FROM Routes
WHERE OriginalLocation = R.Destination --You can use UPPER()/LOWER() here
AND
Destination = R.OriginalLocation --and here too
AND
ID != R.ID
)
THEN ID
ELSE 0
END G
FROM Routes R
)
SELECT ID,
OriginalLocation,
Destination
FROM C
WHERE NOT (G > 1);
Returns:
+----+------------------+-------------+
| ID | OriginalLocation | Destination |
+----+------------------+-------------+
| 1 | Amsterdam | London |
| 3 | London | Algeria |
| 4 | Algeria | France |
+----+------------------+-------------+
Demo

In case you have table like
[flight routes]
originlocation| destinationlocation
Amsterdam | London
London | Amsterdam
You can try a set of sql queries like below
; with orderedroutes as
(
select distinct
originlocation= Case when originlocation < destinationlocation then originlocation else destinationlocation end ,
destinationlocation = Case when originlocation < destinationlocation then destinationlocation else originlocation end
from [flight routes]
)
select * from orderedroutes

Related

SQL - Multiple records into one line with conditions

I have two tables that look like this
Account Table
| Account Number | Account Name |
| -------------- | -------------- |
| 12345 | Bob Jones |
| 12346 | Jenny Smith |
| 12347 | Mary Frederick |
Email Table
| Account Number | Email Type |Email | IsPrimary |
| ---------------------- | --------------|-----------------------|-----------------|
| 12345 | WORK | BOBSMITH#STACK.COM | 0 |
| 12345 | HOME | BOBSMITH#GMAIL.COM | 0 |
| 12345 | WORK | BOBSMITH#APPLE.COM | 1 |
| 12345 | HOME | BOBSMITH#HOTMAIL.COM | 0 |
| 12346 | HOME | JMSMITH#GMAIL.COM | 1 |
| 12346 | WORK | JMSMITH#DISNEY.COM | 0 |
| 12346 | HOME | JMSMITH#HOTMAIL.COM | 0 |
| 12347 | HOME | MFRED#HOTMAIL.COM | 0 |
I need to end up with results like this:
| Account Number | Account Name | Home Email | Work Email | Alternate Email |
| ------------------------ | ----------------- |--------------------|--------------------|----------------------|
| 12345 | Bob Jones | BOBSMITH#GMAIL.COM | BOBSMITH#APPLE.COM | BOBSMITH#HOTMAIL.COM |
| 12346 | Jenny Smith | JMSMITH#GMAIL.COM | JMSMITH#DISNEY.COM | JMSMITH#HOTMAIL.COM |
| 12347 | Mary Frederick | MFRED#HOTMAIL.COM | | |
I have all possible combinations of emails such as multiple work and no home, no primaries at all, multiple primaries... etc.
I need the logic to be:
Home Email: When type is Home. If there is a primary, choose the first one. If there is no primary, choose the first non-primary home email. Otherwise make it blank
Work Email: Same as above but for work
Alternate Email: Any third email on the list. If there are more than 3, choose home over work.
I have tried a ton of things and nothing has worked well. Here is my top attempt -two things don't work about it. The 'top 1' returns a lot of nulls and the code for alternates isn't always mutually exclusive to the home and work email, especially for people that have no primary checked:
select distinct
A01.AccountNumber,
A01.FirstName,
A01.LastName,
(
select top 1
case
when (A07.Active = 1 and A07.EmailType = 'HOME' and A07.UseAsPrimary = 1)
then A07.EmailAddress
when (A07.Active = 1 and A07.EmailType = 'HOME')
then A07.EmailAddress
end
from A07_AccountEmails AS A07
where A07.AccountNumber = A01.AccountNumber
) as HomeEmail
,(
select top 1
case
when (A07.Active = 1 and A07.EmailType = 'WORK' and A07.EmailType is not null)--and A07.UseAsPrimary = 1)
then A07.EmailAddress
when (A07.Active = 1 and A07.EmailType = 'WORK')
then A07.EmailAddress
end
from A07_AccountEmails AS A07
where A07.AccountNumber = A01.AccountNumber
) as WorkEmail
,(
select top 1 (A07.EmailAddress)
from A07_AccountEmails A07
where A07.AccountNumber = A01.AccountNumber
and A07.Active = 1 and A07.UseAsPrimary = 0
) as AlternateEmailMulti
from A01_AccountMaster A01
left outer join A07_AccountEmails A07 on A07.AccountNumber = A01.AccountNumber
where
A01.[Status] = 'A'
It seems a simple sub-query per email address should do the trick? I have added them in an OUTER APPLY to allow the values to be used twice, once in the select, and once to be excluded from the alternate email column.
declare #Account table (Number int, [Name] varchar(64));
declare #Email table (AccountNumber int, [Type] varchar(4), Email varchar(256), IsPrimary bit);
insert into #Account (Number, [Name])
values
(12345, 'Bob Jones'),
(12346, 'Jenny Smith'),
(12347, 'Mary Frederick');
insert into #Email (AccountNumber, [Type], Email, IsPrimary)
values
(12345, 'WORK', 'BOBSMITH#STACK.COM', 0),
(12345, 'HOME', 'BOBSMITH#GMAIL.COM', 0),
(12345, 'WORK', 'BOBSMITH#APPLE.COM', 1),
(12345, 'HOME', 'BOBSMITH#HOTMAIL.COM', 0),
(12346, 'HOME', 'JMSMITH#GMAIL.COM', 1),
(12346, 'WORK', 'JMSMITH#DISNEY.COM', 0),
(12346, 'HOME', 'JMSMITH#HOTMAIL.COM', 0),
(12347, 'HOME', 'MFRED#HOTMAIL.COM', 0);
select A.Number, A.[Name]
, E.Home [Home Email]
, E.Work [Work Email]
, (
select top 1 Email
from #Email
where AccountNumber = A.Number
and Email not in (E.Home, E.Work)
order by case when [Type] = 'HOME' then 1 else 0 end desc
)
from #Account A
outer apply (
select
(select top 1 Email from #Email where AccountNumber = A.Number and [Type] = 'HOME' order by IsPrimary desc),
(select top 1 Email from #Email where AccountNumber = A.Number and [Type] = 'WORK' order by IsPrimary desc)
) E (Home, Work);
Returns your desired results:
Account Number
Account Name
Home Email
Work Email
Alternate Email
12345
Bob Jones
BOBSMITH#HOTMAIL.COM
BOBSMITH#APPLE.COM
BOBSMITH#GMAIL.COM
12346
Jenny Smith
JMSMITH#GMAIL.COM
JMSMITH#DISNEY.COM
JMSMITH#HOTMAIL.COM
12347
Mary Frederick
MFRED#HOTMAIL.COM
NULL
NULL
Note: If you provide your sample data as DDL+DML (as I have here) it makes it much easier for people to assist.
Here is an alternative approach which list all e-mails, primaries first.
We use group_,concat to show all matching entries, order by primary prim inversed by desc and finally a newline separator \n to make a vertical list.
NB : It doesn't print correctly in the terminal because of the newlines.
USE test;
CREATE TABLE user (
acc INT,
nam CHAR(10)
);
INSERT INTO user VALUES ( 1, 'Andrew');
INSERT INTO user VALUES ( 2, 'Bernard');
INSERT INTO user VALUES ( 3, 'Charles');
CREATE TABLE telMail (
acc INT,
email CHAR(25),
type CHAR(5),
prim INT
);
INSERT INTO telMail VALUES(1,'a#w0','Work',0);
INSERT INTO telMail VALUES(1,'a#w1','Work',0);
INSERT INTO telMail VALUES(1,'ap#w2','Work',1);
INSERT INTO telMail VALUES(1,'a#w3','Work',0);
INSERT INTO telMail VALUES(1,'a#h1','Home',0);
INSERT INTO telMail VALUES(1,'a#h2','Home',0);
INSERT INTO telMail VALUES(2,'b#h1','Home',0);
INSERT INTO telMail VALUES(2,'b#w1','Work',0);
INSERT INTO telMail VALUES(2,'b#w2','Work',0);
INSERT INTO telMail VALUES(3,'c#h1','Home',0);
INSERT INTO telMail VALUES(3,'cp#h1','Home',1);
INSERT INTO telMail VALUES(3,'c#w1','Work',0);
SELECT
u.acc 'Account',
u.nam 'Name',
GROUP_CONCAT(DISTINCT w.email ORDER BY w.prim DESC SEPARATOR '\n' ) work_mail,
GROUP_CONCAT(DISTINCT h.email ORDER BY h.prim DESC SEPARATOR '\n' ) home_mail
FROM
(SELECT acc,email,prim FROM telMail WHERE type ='Home') h
JOIN
user u on u.acc=h.acc
JOIN
(SELECT acc,email,prim FROM telMail WHERE type ='Work') w ON u.acc = w.acc
GROUP BY u.acc, u.nam
ORDER BY u.acc;
DROP TABLE user;
DROP TABLE telMail;

Select all records of one table that contain two records in another with certain id

I have two tables of 1:m relation. Need to select which People records have both records in Actions table whit id 1 and 2
People
+----+------+--------------+
| id | name | phone_number |
+----+------+--------------+
| 1 | John | 111111111111 |
+----+------+--------------+
| 3 | Jane | 222222222222 |
+----+------+--------------+
| 4 | Jack | 333333333333 |
+----+------+--------------+
Action
+----+------+------------+
| id | PplId| ActionId |
+----+------+------------+
| 1 | 1 | 1 |
+----+------+------------+
| 2 | 1 | 2 |
+----+------+------------+
| 3 | 2 | 1 |
+----+------+------------+
| 4 | 4 | 2 |
+----+------+------------+
Output
+----+------+--------------+----------
|PplId| name | Phone |ActionId |
+-----+------+-------------+----+-----
| 1 | John | 111111111111| 1 |
+-----+------+-------------+----+-----
| 1 | John | 111111111111| 2 |
+-----+------+-------------+----+-----
Return records of People that have both Have Actionid 1 and Action id 2(Have records in Actions).
Window functions are one method. Assuming actions are not duplicated for a person:
select pa.*
from (select p.*, a.action, count(*) over (partition by p.id) as num_actions
from people p join
action a
on p.id = a.pplid
where a.action in (1, 2)
) pa
where num_actions = 2;
In my opinion, getting two rows with the action detail seems superfluous -- you already know the actions. If you only want the people, then exists comes to mind:
select p.*
from people p
where exists (select 1 from actions where a.pplid = p.id and a.action = 1) and
exists (select 1 from actions where a.pplid = p.id and a.action = 2);
With the right index (actions(pplid, action)), I would expect two exists to be faster than group by.
Try this below query using subquery and join
select a.Pplid, name, phone, actionid from (
select a.pplid as Pplid, name, phone_number as phone
from People P
join Action A on a.pplid= p.id
group by a.pplid, name, phone_number
having count(*)>1 )P
join Action A on a.Pplid= p.Pplid
Try something like this
IF OBJECT_ID('tempdb..#People') IS NOT NULL DROP TABLE #People
CREATE TABLE #People (id INT, name VARCHAR(255), phone_number VARCHAR(50))
INSERT #People
SELECT 1, 'John', '111111111111' UNION ALL
SELECT 3, 'Jane', '222222222222' UNION ALL
SELECT 4, 'Jack', '333333333333'
IF OBJECT_ID('tempdb..#Action') IS NOT NULL DROP TABLE #Action
CREATE TABLE #Action (id INT, PplId INT, ActionId INT)
INSERT #Action
SELECT 1, 1, 1 UNION ALL
SELECT 2, 1, 2 UNION ALL
SELECT 3, 2, 1 UNION ALL
SELECT 4, 4, 2
GO
SELECT p.ID AS PplId
, p.name
, p.phone_number AS Phone
, a.ActionId
FROM #People p
JOIN #Action a
ON p.ID = a.PplId
WHERE p.ID IN ( SELECT PplId
FROM #Action
WHERE ActionId IN (1, 2)
GROUP BY PplId
HAVING COUNT(*) = 2 )
AND a.ActionId IN (1, 2)
GO

Select rows into columns and show a flag in the column

Trying to get an output like the below:
| UserFullName | JAVA | DOTNET | C | HTML5 |
|--------------|--------|--------|--------|--------|
| Anne San | | | | |
| John Khruf | 1 | 1 | | 1 |
| Mary Jane | 1 | | | 1 |
| George Mich | | | | |
This shows the roles of a person. A person could have 0 or N roles. When a person has a role, I am showing a flag, like '1'.
Actually I have 2 blocks of code:
Block #1: The tables and a simple output which generates more than 1 rows per person.
SQL Fiddle
MS SQL Server 2008 Schema Setup:
CREATE TABLE AvailableRoles
(
id int identity primary key,
CodeID varchar(5),
Description varchar(500),
);
INSERT INTO AvailableRoles
(CodeID, Description)
VALUES
('1', 'JAVA'),
('2', 'DOTNET'),
('3', 'C'),
('4', 'HTML5');
CREATE TABLE PersonalRoles
(
id int identity primary key,
UserID varchar(100),
RoleID varchar(5),
);
INSERT INTO PersonalRoles
(UserID, RoleID)
VALUES
('John.Khruf', '1'),
('John.Khruf', '2'),
('Mary.Jane', '1'),
('Mary.Jane', '4'),
('John.Khruf', '4');
CREATE TABLE Users
(
UserID varchar(20),
EmployeeType varchar(1),
EmployeeStatus varchar(1),
UserFullName varchar(500),
);
INSERT INTO Users
(UserID, EmployeeType, EmployeeStatus, UserFullName)
VALUES
('John.Khruf', 'E', 'A', 'John Khruf'),
('Mary.Jane', 'E', 'A', 'Mary Jane'),
('Anne.San', 'E', 'A', 'Anne San'),
('George.Mich', 'T', 'A', 'George Mich');
Query 1:
SELECT
A.UserFullName,
B.RoleID
FROM
Users A
LEFT JOIN PersonalRoles B ON B.UserID = A.UserID
WHERE
A.EmployeeStatus = 'A'
ORDER BY
A.EmployeeType ASC,
A.UserFullName ASC
Results:
| UserFullName | RoleID |
|--------------|--------|
| Anne San | (null) |
| John Khruf | 1 |
| John Khruf | 2 |
| John Khruf | 4 |
| Mary Jane | 1 |
| Mary Jane | 4 |
| George Mich | (null) |
Block #2: An attempt to convert the rows into columns to be used in the final result
SQL Fiddle
MS SQL Server 2008 Schema Setup:
CREATE TABLE AvailableRoles
(
id int identity primary key,
CodeID varchar(5),
Description varchar(500),
);
INSERT INTO AvailableRoles
(CodeID, Description)
VALUES
('1', 'JAVA'),
('2', 'DOTNET'),
('3', 'C'),
('4', 'HTML5');
Query 1:
SELECT
*
FROM
(
SELECT CodeID, Description
FROM AvailableRoles
) d
PIVOT
(
MAX(CodeID)
FOR Description IN (Java, DOTNET, C, HTML5)
) piv
Results:
| Java | DOTNET | C | HTML5 |
|--------|--------|-------|--------|
| 1 | 2 | 3 | 4 |
Any help in mixing both blocks to show the top output will be welcome. Thanks.
Another option without PIVOT operator is:
select u.UserFullName,
max(case when a.CodeID='1' then '1' else '' end) JAVA,
max(case when a.CodeID='2' then '1' else '' end) DOTNET,
max(case when a.CodeID='3' then '1' else '' end) C,
max(case when a.CodeID='4' then '1' else '' end) HTML5
from
Users u
LEFT JOIN PersonalRoles p on (u.UserID = p.UserID)
LEFT JOIN AvailableRoles a on (p.RoleID = a.CodeID)
group by u.UserFullName
order by u.UserFullName
SQLFiddle: http://sqlfiddle.com/#!3/630c3/19
You can try this.
SELECT *
FROM
(
select u.userfullname,
case when p.roleid is not null then 1 end as roleid,
a.description
from users u
left join personalroles p
on p.userid = u.userid
left join availableroles a
on a.codeid = p.roleid
) d
PIVOT
(
MAX(roleID)
FOR Description IN (Java, DOTNET, C, HTML5)
) piv
Fiddle

Create view based on three tables with columns as rows in one of tables

I'm starting to learn SQL, learning about views.
I was wondering if is possible to join three tables in one view, where some columns will be based on rows from of these tables.
I have three tables:
Roles:
Id | Name | PermissionId
1 | Admin | 1
2 | Staff | 2
RolePermissions:
Id | RoleId | PermissionId
1 | 1 | 1
2 | 1 | 2
3 | 1 | 3
4 | 2 | 1
5 | 2 | 2 <- staff doesn't have permission 3
Permissions:
Id | Name
1 | Perm1
2 | Perm2
3 | Perm3
.
. (not fixed number of permissions)
I would like to create view like this:
Id (of role) | Name | Perm1 | Perm2 | Perm3 ... (not fixed number of columns)
1 | Admin | True | True | True
2 | Staff | True | True | False
Is it possible?
You cannot use a view if you don't know how many columns to output.
Code below should be you in the right direction. Use it in a Procedure with dynamic SQL if you need to dynamically build the list of columns
; With roles(Id, Name, PermissionID) as (
Select * From (values(1, 'Admin', '1'), (2, 'Staff', '2')) as r(Id, Name, PermissionId)
), RolePermissions(Id, RoleId, PermissionId) as (
Select * From (Values(1, 1, 1), (2, 1, 2), (3, 1, 3), (4, 2, 1), (5, 2, 2)) as p(Id, RoleId, PermissionId)
), Permissionss(Id, Name) as (
Select * From (Values(1, 'Perm1'), (2, 'Perm2'), (3, 'Perm3')) as p(Id, Name)
), data as(
Select r.Id, rp.PermissionId, p.Name From Roles as r
Inner Join RolePermissions as rp on rp.RoleId = r.Id
Inner Join Permissionss as p on rp.PermissionId = p.Id
)
Select piv.Id as [Id of Role], r.Name
, [Perm1] = case when [Perm1] is not null then 'true' else 'false' end
, [Perm2] = case when [Perm2] is not null then 'true' else 'false' end
, [Perm3] = case when [Perm3] is not null then 'true' else 'false' end
From data
Pivot( max(PermissionId)
For Name in ( [Perm1], [Perm2], [Perm3])
) as piv
Inner Join roles as r on r.Id = Piv.Id

Nested query to find details in table B for maximum value in table A

I've got a huge bunch of flights travelling between airports.
Each airport has an ID and (x,y) coordinates.
For a given list of flights belonging to a user, I want to find the northernmost (highest y) airport visited.
Here's the query I'm currently using:
SELECT name,iata,icao,apid,x,y
FROM airports
WHERE y=(SELECT MAX(y)
FROM airports AS a
, flights AS f
WHERE (f.src_apid=a.apid OR f.dst_apid=a.apid) AND f.uid=[user_id]
)
This works beautifully and reasonably fast as long as y is unique (= there's only one airport at that latitude), but fails once it isn't. Unfortunately this happens quite often, as eg. military and civilian airports have separate entries even though they occupy the same coordinates.
What I'd really want to do is find the airport with MAX(y) in the subquery and return the actual matching airport (a.apid), instead of returning the value of y and then matching it again. Any suggestions?
Assume the user has only this one flight, from apid '3728':
mysql> select * from flights where uid=35 and src_apid=3728 limit 1;
+------+----------+----------+----------+----------+------+------+-----------+-------+--------+------+------+------+--------+----------+--------------+--------------+---------------------+------+------------+------+
| uid | src_apid | src_time | dst_apid | distance | code | seat | seat_type | class | reason | plid | alid | trid | fid | duration | registration | note | upd_time | opp | src_date | mode |
+------+----------+----------+----------+----------+------+------+-----------+-------+--------+------+------+------+--------+----------+--------------+--------------+---------------------+------+------------+------+
| 35 | 3728 | NULL | 3992 | 4116 | NW16 | 23C | A | Y | L | 167 | 3731 | NULL | 107493 | 08:00:00 | | del. typhoon | 2008-10-04 10:40:58 | Y | 2001-08-22 | F |
+------+----------+----------+----------+----------+------+------+-----------+-------+--------+------+------+------+--------+----------+--------------+--------------+---------------------+------+------------+------+
And there are two airports at the same coordinates:
mysql> select * from airports where y=21.318681;
+-----------------------+----------+---------------+------+------+-------------+-----------+-----------+------+------+----------+------+
| name | city | country | iata | icao | x | y | elevation | apid | uid | timezone | dst |
+-----------------------+----------+---------------+------+------+-------------+-----------+-----------+------+------+----------+------+
| Honolulu Intl | Honolulu | United States | HNL | PHNL | -157.922428 | 21.318681 | 13 | 3728 | NULL | -10 | N |
| Hickam Air Force Base | Honolulu | United States | | PHIK | -157.922428 | 21.318681 | 13 | 7055 | 3 | -10 | N |
+-----------------------+----------+---------------+------+------+-------------+-----------+-----------+------+------+----------+------+
If you run the original query, the subquery will return y=21.318681, which in turn will match either apid 3728 (correct) or apid 7055 (wrong).
What about this:
SELECT name,iata,icao,apid,x,y
FROM airports AS a, flights AS f
WHERE f.src_apid=a.apid OR f.dst_apid=a.apid
ORDER BY y DESC LIMIT 1
You take all flights of the concerned users, order them from northern to southern, and take the first one from the list.
How does the following query perform?
It works by first finding the northmost Y cordinate in the set of airports visited. Then an identical query is performed which is filtered by the Y coordinate in the previous query. The final step is to find the airport.
drop table airports;
drop table flights;
create table airports(
apid int not null
,apname varchar(50) not null
,x int not null
,y int not null
,primary key(apid)
,unique(apname)
);
create table flights(
flight_id int not null auto_increment
,src_apid int not null
,dst_apid int not null
,user_id varchar(20) not null
,foreign key(src_apid) references airports(apid)
,foreign key(dst_apid) references airports(apid)
,primary key(flight_id)
,index(user_id)
);
insert into airports(apid, apname, x, y) values(1, 'Northpole Civilian', 50, 100);
insert into airports(apid, apname, x, y) values(2, 'Northpole Military', 50, 100);
insert into airports(apid, apname, x, y) values(3, 'Transit point', 50, 50);
insert into airports(apid, apname, x, y) values(4, 'Southpole Civilian', 50, 0);
insert into airports(apid, apname, x, y) values(5, 'Southpole Military', 50, 0);
insert into flights(src_apid, dst_apid, user_id) values(4, 3, 'Family guy');
insert into flights(src_apid, dst_apid, user_id) values(3, 1, 'Family guy');
insert into flights(src_apid, dst_apid, user_id) values(5, 3, 'Mr Bazooka');
insert into flights(src_apid, dst_apid, user_id) values(3, 2, 'Mr Bazooka');
select airports.apid
,airports.apname
,airports.x
,airports.y
from (select max(a.y) as y
from flights f
join airports a on (a.apid = f.src_apid or a.apid = f.dst_apid)
where f.user_id = 'Family guy'
) as northmost
join (select a.apid
,a.y
from flights f
join airports a on (a.apid = f.src_apid or a.apid = f.dst_apid)
where f.user_id = 'Family guy'
) as userflights on(northmost.y = userflights.y)
join airports on(userflights.apid = airports.apid);
Edit. Alternative query that may be less confusing to the optimizer
select airports.*
from (select case when s.y > d.y then s.apid else d.apid end as apid
,case when s.y > d.y then s.y else d.y end as northmost
from flights f
join airports s on(f.src_apid = s.apid)
join airports d on(f.dst_apid = d.apid)
where f.user_id = 'Family guy'
order by northmost desc
limit 1
) as user_flights
join airports on(airports.apid = user_flights.apid);
third attempt, using assumed user (userid,name) table
select u.name, ap.name
, ap.iata
, ap.icao
, ap.apid
, ap.x
, max(ap.y)
from users u
, airports ap
, flights f
where u.userid=f.userid
and ( f.src_apid=ap.apid
OR f.dst_apid=ap.apid
)
group by u.name, ap.name,ap.iata,ap.icao,ap.apid,ap.x
you can now restrict the query to the one user you are interested in ..
comment on GROUP BY:
strictly speaking MySQL would allow me to write that group by as 'group by u.name, ap.name'.
Other SQL dialects don't, they ask that all selected fields that are not aggregated be in the GROUP BY statement.
So I tend to be 'chicken' when selecting my GROUP BY fields ...
OK, perhaps something like this:
SELECT name, iata, icao, apid, x, y
FROM airports
WHERE y = (SELECT MAX(A.y)
FROM airports AS a
INNER JOIN flights AS f
ON (F.SRC_APID = A.APID OR
F.DST_APID = A.APID)
WHERE f.uid = [user_id]) AND
apid IN (SELECT SRC_APID AS APID
FROM FLIGHTS
WHERE UID = [user_id]
UNION ALL
SELECT DEST_APID AS APID
FROM FLIGHTS
WHERE UID = [user_id])
Can't guarantee how this will perform, but perhaps it's a step in the right direction.
Share and enjoy.