查询以计算Mysql中连续行中距离(经度,纬度)的总和

Posted

技术标签:

【中文标题】查询以计算Mysql中连续行中距离(经度,纬度)的总和【英文标题】:Query to calculate sum of distance (longitude, latitude) in consecutive rows in Mysql 【发布时间】:2015-05-15 12:20:16 【问题描述】:

我对 sql 很陌生,我被卡住了。我正在尝试计算每个用户旅行的(每年)距离总和。我有一个具有以下结构的表(我们称之为 dist_table):

rowid     user_name   date             LAT        LONG
1         maria       2005-01-01       51.555     5.014
2         maria       2005-01-01       51.437     5.474
3         peter       2005-02-03       51.437     5.474
4         john        2005-02-03       51.858     5.864
5         maria       2005-02-04       51.858     5.864
6         john        2005-02-03       51.437     5.474
7         john        2006-02-04       0          0
8         john        2006-02-04       51.858     5.864
9         john        2006-02-04       51.858     5.864
10        john        2006-02-04       51.437     5.474

这是计算的中间步骤(只是为了澄清我的意思):

rowid     user_name   date             LAT        LONG      distance
1         maria       2005-01-01       51.555     5.014     0
2         maria       2005-01-01       51.437     5.474     34.452
3         peter       2005-02-03       51.437     5.474     0
4         john        2005-02-03       51.858     5.864     0
5         maria       2005-02-04       51.858     5.864     54.012
6         john        2005-03-03       51.437     5.474     54.012
7         john        2006-02-04       0          0         
8         john        2006-02-04       51.858     5.864     54.012
9         john        2006-02-04       51.858     5.864     0     
10        john        2006-02-04       51.437     5.474     54.012

这是我需要的最终结果:

user_name   date       sum(distance)
maria       2005       88.464
peter       2005       0
john        2005       54.012
john        2006       108.024

我正在考虑使用这个公式(Haversine)来计算连续行之间的距离,然后将其相加:

SELECT user_name,date,dist_table.LAT,dist_table.LONG, 6373 * 2 * ASIN(SQRT(POWER(SIN((orig_latitude - abs(next_latitude)) * pi()/180 / 2),2)
+ COS(orig_latitude * pi()/180) * COS(abs(next_latitude) * pi()/180) * POWER(SIN((orig_longitude - next_longitude) * pi()/180 / 2),2)  ))
AS distance FROM dist_table WHERE dist_table.LAT !=0 AND dist_table.LONG !=0;

但是,我无法弄清楚如何调用连续行。到目前为止,这是我试图弄清楚如何连接行时得到的结果:

SELECT user_name, date, LAT,
IFNULL( (
    SELECT MAX( LAT ) 
    FROM dist_table
    WHERE user_name = t1.user_name
    AND ( date < t1.date )
) ,0) AS next_latitude
FROM dist_table AS t1 ORDER BY user_name, date; 

问题是对于每个用户,可以有多个行满足这个条件,这会选择最大值而不是前一个。此外,有时经度和/或纬度为 0,我需要忽略这些行。

我认为这可能会解决,如果我首先创建具有基于 user_name 和 date 的行顺序的列,然后在条件中添加类似 date+1 = t1.date 的内容。不幸的是,我在使用的服务器上的权限非常有限,因此这可能必须使用用户定义的变量来处理,但我不知道该怎么做。

我正在使用 mysql 5.6.19-log。

有人可以帮帮我吗?

【问题讨论】:

SQL 表代表 无序 集。没有“下一个”行的概念,除非您有一个指定排序的列。您是否有这样的列,可能是自动递增的 id 或日期/时间列? 请注意,LONG 是保留字。 是的,我知道 LONG,不幸的是,这是数据库中的名称,对此我无能为力。这就是为什么我将 dist_table.LONG 添加到名称中,这似乎可以解决计算时的问题。 @GordonLinoff 不幸的是,我没有。这些是我唯一可用的列。 @jonas 在这种情况下,您的问题在技术上是无法解决的——尽管有人可能会想出一个 hack。如果你能找到一些建立 PK 的方法,那就更好了。 【参考方案1】:

所以这是第一部分问题的解决方案......

DROP TABLE IF EXISTS my_table;

CREATE TABLE my_table
(id     INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,user_name   VARCHAR(12) NOT NULL
,date             DATE NOT NULL
,LAT        DECIMAL(5,3) NOT NULL
,LON DECIMAL (5,2) NOT NULL
);

INSERT INTO my_table VALUES
( 1,'maria','2005-01-01',51.555 ,5.014),
( 2,'maria','2005-01-01',51.437 ,5.474),
( 3,'peter','2005-02-03',51.437 ,5.474),
( 4,'john' ,'2005-02-03',51.858 ,5.864),
( 5,'maria','2005-02-04',51.858 ,5.864),
( 6,'john' ,'2005-02-03',51.437 ,5.474),
( 7,'john' ,'2006-02-04',0      ,0),
( 8,'john' ,'2006-02-04',51.858 ,5.864),
( 9,'john' ,'2006-02-04',51.858 ,5.864),
(10,'john' ,'2006-02-04',51.437 ,5.474);


SELECT x.user_name
     , x.id from_id
     , MIN(y.id) to_id
  FROM my_table x
  JOIN my_table y
    ON y.user_name = x.user_name
   AND y.id > x.id
 WHERE (y.lat <> 0 AND y.lon <> 0)
   AND (x.lat <> 0 AND x.lon <> 0)
 GROUP 
    BY x.id;

+-----------+---------+-------+
| user_name | from_id | to_id |
+-----------+---------+-------+
| maria     |       1 |     2 |
| maria     |       2 |     5 |
| john      |       4 |     6 |
| john      |       6 |     8 |
| john      |       8 |     9 |
| john      |       9 |    10 |
+-----------+---------+-------+

对于剩下的问题,类似以下的方法应该可以工作。

我的数据库中有一个名为 geo_distance_km 的函数。它看起来像这样,并且每次都省去输入半正弦公式:

delimiter //
create DEFINER = CURRENT_USER function geo_distance_km (lat1 double, lon1 double, lat2 double, lon2 double) returns double
 begin
   declare R int DEFAULT 6372.8;
   declare phi1 double;
   declare phi2 double;
   declare d_phi double;
   declare d_lambda double;
   declare a double;
   declare c double;
   declare d double;
   set phi1 = radians(lat1);
   set phi2 = radians(lat2);
   set d_phi = radians(lat2-lat1);
   set d_lambda = radians(lon2-lon1);
   set a = sin(d_phi/2) * sin(d_phi/2) +
         cos(phi1) * cos(phi2) *
         sin(d_lambda/2) * sin(d_lambda/2);
   set c = 2 * atan2(sqrt(a), sqrt(1-a));
   set d = R * c;
   return d;
   end;
//
delimiter ;

我们可以将它与我们已有的结合起来......

SELECT user_name
     , YEAR(date) year
     , COALESCE(SUM(distance),0) total
  FROM 
     ( SELECT a.*
            , b.lat to_lat
            , b.lon to_lon
            , ROUND(geo_distance_km(from_lat,from_lon,b.lat,b.lon),3) distance
         FROM
            ( SELECT x.user_name
                   , x.date
                   , x.id from_id
                   , x.lat from_lat
                   , x.lon from_lon
                   , MIN(y.id) to_id
                FROM my_table x
                LEFT
                JOIN my_table y
                  ON y.user_name = x.user_name
                 AND y.id > x.id
                 AND (y.lat <> 0 OR y.lon <> 0)
                 WHERE (x.lat <> 0 AND x.lon <> 0)
               GROUP
                  BY x.id
            ) a
         LEFT
         JOIN my_table b
           ON b.id = a.to_id
     ) n
 GROUP
    BY user_name
     , year;

+-----------+------+---------+
| user_name | year | total   |
+-----------+------+---------+
| john      | 2005 | 108.024 |
| john      | 2006 |  54.012 |
| maria     | 2005 |  88.464 |
| peter     | 2005 |   0.000 |
+-----------+------+---------+

我不太明白您如何处理重叠年份的距离,但这应该可以让您接近您所追求的。

【讨论】:

谢谢!这完美地工作。目前,我只关注同一年内的旅行距离,所以我总是将当年的第一条记录作为起点,而忽略与年份重叠的旅行。因此,我包含了似乎可以解决它的条件 year(x.date)=year(y.date)。

以上是关于查询以计算Mysql中连续行中距离(经度,纬度)的总和的主要内容,如果未能解决你的问题,请参考以下文章

查找两个经度纬度之间的距离-Mysql

mysql中怎么根据经纬度计算距离

在mysql中计算哪些点(纬度,经度)在一定距离内?

C# 在 Linq 查询 WHERE 语句中返回两个纬度/经度坐标之间的计算距离

用于查找使用纬度和经度之间的距离的Sql返回错误的距离

PHP,Mysql根据经纬度计算距离并排序