我有这个问题
SELECT zip,
( 3959 * acos( cos( radians(34.12520) ) * cos( radians( zip_info.latitude ) ) * cos(radians( zip_info.longitude ) - radians(-118.29200) ) + sin( radians(34.12520) ) * sin( radians( zip_info.latitude ) ) ) ) AS distance,
user_info.*, office_locations.*
FROM zip_info
RIGHT JOIN office_locations ON office_locations.zipcode = zip_info.zip
RIGHT JOIN user_info ON office_locations.doctor_id = user_info.id
WHERE user_info.status='yes'
HAVING distance < 50 ORDER BY distance ASC
It输出
距离|医生|身份证|等。
七、五等
八、四等
34——4——等
49——5——等
当我选择30或更少的距离时,它也会显示前两个结果,这是很好的。
问题是:我不希望每个医生id显示多个结果,所以我按用户信息进行分组。医生id在距离小于50时不显示结果。出于某种原因,它希望将所有结果分组,否则就行不通。有什么建议吗?你还需要帮助我吗?
所以我想要的是
距离|医生|身份证|等。
七、五等
八、四等
即使它想给我所有的4行结果,我只想把它们分组,所以只有最小的距离每个唯一的用户的信息。医生的id显示。记住距离是一个虚拟的不存在的表。
根据llion的查询,结果如下:
(concat(user_info.id)) zip distance id
1 NULL 6.6643992 1
它只给出一个结果,为了让它工作,我不得不改变和再次有距离。
最佳答案
我不相信一群人会给你想要的结果。不幸的是,MySQL不支持分析函数(这就是我们在Oracle或SQL Server中解决这个问题的方法)
通过使用用户定义的变量,可以模拟一些基本的分析函数。
在这种情况下,我们要模拟:
ROW_NUMBER() OVER(PARTITION BY doctor_id ORDER BY distance ASC) AS seq
所以,从最初的查询开始,我更改了顺序,使它先按
doctor_id
排序,然后按计算的distance
排序。(在我们知道这些距离之前,我们不知道哪一个是“最近的”。)根据这个排序结果,我们基本上“编号”每个医生id的行,最近的一个为1,第二个最近的为2,依此类推。当我们得到一个新的医生id时,我们从最接近的1开始。
为此,我们使用用户定义的变量。我们使用一个来分配行号(变量名是@i,返回的列有alias seq)。另一个变量用于“记住”前一行的医生id,这样我们就可以检测到医生id中的“中断”,这样我们就可以知道何时在1重新开始行编号。
以下是问题:
SELECT z.*
, @i := CASE WHEN z.doctor_id = @prev_doctor_id THEN @i + 1 ELSE 1 END AS seq
, @prev_doctor_id := z.doctor_id AS prev_doctor_id
FROM
(
/* original query, ordered by doctor_id and then by distance */
SELECT zip,
( 3959 * acos( cos( radians(34.12520) ) * cos( radians( zip_info.latitude ) ) * cos(radians( zip_info.longitude ) - radians(-118.29200) ) + sin( radians(34.12520) ) * sin( radians( zip_info.latitude ) ) ) ) AS distance,
user_info.*, office_locations.*
FROM zip_info
RIGHT JOIN office_locations ON office_locations.zipcode = zip_info.zip
RIGHT JOIN user_info ON office_locations.doctor_id = user_info.id
WHERE user_info.status='yes'
ORDER BY user_info.doctor_id ASC, distance ASC
) z JOIN (SELECT @i := 0, @prev_doctor_id := NULL) i
HAVING seq = 1 ORDER BY z.distance
我假设原始查询返回的是您需要的结果集,它有太多的行,您希望消除每个医生id的“最近的”(距离值最小的行)以外的所有行。
我已将您的原始查询包装在另一个查询中;我对原始查询所做的唯一更改是按医生id排序结果,然后按距离排序,并删除
HAVING distance < 50
子句。(如果您只想返回小于50的距离,请继续执行该条款。目前尚不清楚这是否是您的意图,也不清楚这是否是在试图将每个医生id的行数限制为一行时指定的。)需要注意的几个问题:
替换查询返回两个额外的列;结果集中实际上并不需要这些列,除了作为生成结果集的方法。(可以将整个SELECT再次包装到另一个SELECT中,以省略那些列,但这确实比它的价值更混乱。我只需要检索列,并知道我可以忽略它们。)
另一个问题是,在内部查询中使用
.*
有点危险,因为我们确实需要确保该查询返回的列名是唯一的。(即使列名现在是不同的,向其中一个表中添加列可能会在查询中引入“不明确”的列异常。最好避免出现这种情况,通过用要返回的列列表替换.*
并为任何“重复”列名指定别名,可以很容易地解决这个问题。(只要我们控制z.*
返回的列,就不必担心在外部查询中使用z
)附录:
我注意到一组人不会给你你需要的结果集。虽然可以使用GROUPBY通过查询获取结果集,但返回正确结果集的语句将非常繁琐。您可以指定
MIN(distance) ... GROUP BY doctor_id
,这将获得最小的距离,但不能保证SELECT列表中的其他非聚合表达式来自具有最小距离的行,而不是其他行。(MySQL在GROUP BY和aggregates方面是非常自由的。为了使MySQL引擎更加谨慎(并与其他关系数据库引擎保持一致),SET sql_mode = ONLY_FULL_GROUP_BY
附录2:
Darious报告的性能问题“有些查询需要7秒。”
为了加快速度,您可能需要缓存函数的结果。基本上,构建一个查找表。例如
CREATE TABLE office_location_distance
( office_location_id INT UNSIGNED NOT NULL COMMENT 'PK, FK to office_location.id'
, zipcode_id INT UNSIGNED NOT NULL COMMENT 'PK, FK to zipcode.id'
, gc_distance DECIMAL(18,2) COMMENT 'calculated gc distance, in miles'
, PRIMARY KEY (office_location_id, zipcode_id)
, KEY (zipcode_id, gc_distance, office_location_id)
, CONSTRAINT distance_lookup_office_FK
FOREIGN KEY (office_location_id) REFERENCES office_location(id)
ON UPDATE CASCADE ON DELETE CASCADE
, CONSTRAINT distance_lookup_zipcode_FK
FOREIGN KEY (zipcode_id) REFERENCES zipcode(id)
ON UPDATE CASCADE ON DELETE CASCADE
) ENGINE=InnoDB
这只是个主意。(我希望您正在搜索与特定zipcode的office_location distance,因此上的索引(zipcode、gc_distance、office_location_id)是您的查询所需的覆盖索引。(由于FLOAT数据类型的查询性能差,我将避免将计算出的距离存储为FLOAT)
INSERT INTO office_location_distance (office_location_id, zipcode_id, gc_distance)
SELECT d.office_location_id
, d.zipcode_id
, d.gc_distance
FROM (
SELECT l.id AS office_location_id
, z.id AS zipcode_id
, ROUND( <glorious_great_circle_calculation> ,2) AS gc_distance
FROM office_location l
CROSS
JOIN zipcode z
ORDER BY 1,3
) d
ON DUPLICATE KEY UPDATE gc_distance = VALUES(gc_distance)
缓存和索引函数结果后,查询速度应该快得多。
SELECT d.gc_distance, o.*
FROM office_location o
JOIN office_location_distance d ON d.office_location_id = o.id
WHERE d.zipcode_id = 63101
AND d.gc_distance <= 100.00
ORDER BY d.zipcode_id, d.gc_distance
我在向缓存表添加INSERT/UPDATE上的HAVING谓词时犹豫不决;(如果纬度/经度错误,并且在100英里以下计算了一个错误的距离;在lat/long之后的后续运行是固定的,并且该距离计算为1000英里。。。如果从查询中排除行,则缓存表中的现有行将不被更新。(您可以清除缓存表,但这不是真正必要的,这只是数据库和日志的大量额外工作。如果维护查询的结果集太大,则可以将其分解为对每个zipcode或每个office_位置迭代运行。)
另一方面,如果您对某个值上的任何距离都不感兴趣,可以添加
HAVING gc_distance <
谓词,并大大减少缓存表的大小。
关于mysql - MYSQL按距离排序但无法分组?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11109127/