mysql - MYSQL按距离排序但无法分组?

标签 mysql

我有这个问题

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/

相关文章:

sql - 数据库设计,关于实现的问题

php - MySQL - 比较一列中的所有值并写入另一列

mysql - 如何获取多对多表中的相邻元素?

mysql - 关于mysql的外键

mysql - 获取不同MySQL记录的重复情况

MySQL创建函数困难

mysql - 如何在vertica数据库中存储整数数组?

java - 程序退出时MySQL连接会自动关闭吗?

mysql - 在 select 中定义一个变量并在同一个 select 中使用它

mysql - 根据另一个表中的值删除一个表中的值