为啥这个 MySQL 存储函数给出的结果与在查询中进行计算不同?

Posted

技术标签:

【中文标题】为啥这个 MySQL 存储函数给出的结果与在查询中进行计算不同?【英文标题】:Why does this MySQL stored function give different results than to doing the calculation in the query?为什么这个 MySQL 存储函数给出的结果与在查询中进行计算不同? 【发布时间】:2011-06-17 18:02:58 【问题描述】:

这是一个关于使用半正弦公式计算地球上两个经纬度点之间距离的问题,用于需要“找到我最近的”功能的项目。

在this post的mysql中已经很好地讨论和解决了haversine公式。

然后我询问this question 是否将其转换为存储函数,以便将来的项目可以使用它,而无需查找、记住或重新输入长格式的公式。

一切都好。除了我的函数在结果上(略有不同)与直接在查询中输入公式不同,其他所有条件都相同。这是为什么呢?

这是我写的函数:

DELIMITER $$

DROP FUNCTION IF EXISTS haversine $$

CREATE FUNCTION `haversine`
    (fromLatitude FLOAT,
     fromLongitude FLOAT,
     toLatitude FLOAT,
     toLongitude FLOAT,
     unit VARCHAR(20)
     )
    RETURNS FLOAT
    DETERMINISTIC    
    COMMENT 'Returns the distance on the Earth between two known points of longitude and latitude'
    BEGIN
    DECLARE radius FLOAT;
    DECLARE distance FLOAT;

    IF unit = 'MILES' THEN SET radius = '3959';
    ELSEIF (unit = 'NAUTICAL_MILES' OR unit='NM') THEN SET radius = '3440.27694';   
    ELSEIF (unit = 'YARDS' OR unit='YD') THEN SET radius = '6967840';
    ELSEIF (unit = 'FEET' OR unit='FT') THEN SET radius = '20903520';
    ELSEIF (unit = 'KILOMETRES' OR unit='KILOMETERS' OR unit='KM') THEN SET radius = '6371.3929';
    ELSEIF (unit = 'METRES' OR UNIT='METERS' OR unit='M') THEN SET radius = '6371392.9';
    ELSE SET radius = '3959'; /* default to miles */
    END IF;

    SET distance = (radius * ACOS(COS(RADIANS(fromLatitude)) * COS(RADIANS(toLatitude)) * COS(RADIANS(toLongitude) - RADIANS(fromLongitude)) + SIN(RADIANS(fromLatitude)) * SIN(RADIANS(toLatitude))));

    RETURN distance;
    END$$

DELIMITER ;

这里有一组测试查询集,用于查找伦敦眼和白金汉宫之间的距离,仅作为示例。显然,通常您会用您想要比较的地理位置“事物”数据库中的字段替换目的地。

SET @milesModifier = 3959;

SET @myLat = 51.503228;
SET @myLong = -0.119703;

SET @destLat = 51.501267;  
SET @destLong = -0.142697;

SELECT  @kilometerModifier AS radius,
    @myLat AS myLat,
    @myLong AS myLong,
    @destLat AS destLat,
    @destLong AS destLong,
    (@milesModifier * ACOS(COS(RADIANS(@myLat)) * COS(RADIANS(@destLat)) * COS(RADIANS(@destLong) - RADIANS(@myLong)) + SIN(RADIANS(@myLat)) * SIN(RADIANS(@destLat)))) AS longFormat,
    haversine(@myLat,@myLong,@destLat,@destLong,'MILES') AS distanceMiles,
    haversine(@myLat,@myLong,@destLat,@destLong,'NAUTICAL_MILES') AS distanceNautical,
    haversine(@myLat,@myLong,@destLat,@destLong,'KM') AS distanceKm,
    haversine(@myLat,@myLong,@destLat,@destLong,'METRES') AS distanceMetres,    
    haversine(@myLat,@myLong,@destLat,@destLong,'YARDS') AS distanceYards,
    haversine(@myLat,@myLong,@destLat,@destLong,'FEET') AS distanceFeet,
    haversine(@myLat,@myLong,@destLat,@destLong,'') AS distanceDefault

在示例中,我们使用英里 - 因此我们将半径(测试中的@milesModifier,函数中的radius)精确设置为 3959。

我得到的结果很有趣(在 MySQL 5.2.6 社区版上),亮点:

| longFormat       | distanceMiles   |
|------------------|-----------------|
| 0.99826000106148 | 0.9982578754425 |

longFormat 是查询中完成的数学运算,distanceMiles 是函数的结果。

结果不同...好吧,所以就在项目中使用该函数而言,这无关紧要,但我很想知道函数内部或外部的相同公式如何产生不同的结果。

我猜这与 FLOAT 的长度有关 - 它们没有在函数中指定,我尝试指定它们(最多 30,15)为我拥有的所有数字提供足够的空间和我期望的输出 - 但结果仍然略有不同。

【问题讨论】:

FLOAT 数据类型是近似的。您是否尝试过使用 DECIMAL 数据类型? dev.mysql.com/doc/refman/5.1/en/numeric-types.html 嗯,你每天都会学到一些新东西......近似的优点是什么,它使计算更快吗? 跟data storage requirements有关。另见Problems with Floating-Point Values。我刚刚用DECIMAL(30,15) 尝试过,两次计算都得到了相同的结果。您也许可以对其进行调整以满足您的确切要求。 好东西,就像我说的……每天都学到新东西!如果您想将此作为官方答案,我会很乐意接受。 【参考方案1】:

FLOAT 是一种近似数据类型 - 请参阅:

Problems with Floating-Point ValuesNumeric Types

尝试将FLOAT 更改为DECIMAL(30,15) 以确保您的精度正确。

如果你想深入讨论浮点,你可以试试这篇文章:

What Every Computer Scientist Should Know About Floating-Point Arithmetic

【讨论】:

看。在经纬度工作中使用浮点数据类型(32 位浮点)提供的更高精度是没有意义的。为什么?因为地球是一个球体的近似值,haversine 大圆公式使用的近似值在您使用小于一米左右的距离时开始失效。而 32 位浮点数比这更精确。 问题中两个结果之间的距离差为 135 密尔(0.135 英寸)。如果您是一名土木工程师计划排水管,这可能很重要。否则不是。 @Ollie Jones:来自 OP 的问题“结果不同......好吧,所以就在项目中使用该功能而言,它微不足道,但我很想知道函数内部或外部的相同公式如何产生不同的结果。”所以我们知道在这种情况下精度超过了要求,但精度水平不是问题。 OP 想知道为什么结果不同,原因是使用了 FLOAT。 @Ollie Jones:来自我对 OP 问题的 cmets:“我刚刚尝试使用 DECIMAL(30,15) 并从两个计算中得到相同的结果。你也许可以调整它以适应您的确切要求。”因此已经建议调整精度水平。

以上是关于为啥这个 MySQL 存储函数给出的结果与在查询中进行计算不同?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 MySQL 给出错误“不允许从函数返回结果集”?

MySQL用户定义函数存储查询结果

为啥这个涉及 floor 函数的公式没有给出我期望的结果?

为啥 web2py 中两个逻辑相似的查询给出不同的结果?

为啥这个 MySQL 函数返回 null?

为啥 PHP 使用大量内存来存储查询结果