web-dev-qa-db-ja.com

緯度と経度の選択クエリの最適化

以下のスキーマの表があります。

CREATE TABLE `gdata` ( 
`alarmTypeID` tinyint(4) NOT NULL DEFAULT '0', 
`fleetID` smallint(11) NOT NULL, 
`fleetGroupID` smallint(11) DEFAULT NULL, 
`fleetSubGroupID` smallint(11) DEFAULT NULL, 
`deviceID` mediumint(11) NOT NULL, 
`vehicleID` mediumint(11) NOT NULL, 
`gDateTime` datetime NOT NULL, 
`insertDateTime` datetime NOT NULL, 
`latitude` float NOT NULL, 
`longitude` float NOT NULL, 
`speed` smallint(11) NOT NULL, 
(see full text) 
ALTER TABLE `gdata` 
ADD PRIMARY KEY (`vehicleID`,`gDateTime`,`alarmTypeID`), 
ADD KEY `gDateTime` (`gDateTime`), 
ADD KEY `fleetID` (`fleetID`,`vehicleID`,`gDateTime`); 
COMMIT; 

私の問題は、次のようにクエリを実行できることですEXPLAIN select vehicleID,latitude,longitude from gdata where gDateTime between '2017-07-03 00:00:00' and '2017-07-04 00:00:00'と説明の結果は

1   SIMPLE  gdata       range   gDateTime   gDateTime   5       1251    100.00  Using index condition   

緯度と経度が長方形に収まっているかどうかをテストする次の関数があります。

DELIMITER $$
--
-- Functions
--
CREATE DEFINER=`root`@`localhost`
    FUNCTION `geoProcessor`(pt POINT, mp MULTIPOLYGON)
        RETURNS int(1)
        DETERMINISTIC
BEGIN 

DECLARE str, xy TEXT; 
DECLARE x, y, p1x, p1y, p2x, p2y, m, xinters DECIMAL(16, 13) DEFAULT 0; 
DECLARE counter INT DEFAULT 0; 
DECLARE p, pb, pe INT DEFAULT 0; 

SELECT MBRWithin(pt, mp) INTO p; 
IF p != 1 OR ISNULL(p) THEN 
RETURN p; 
END IF; 

SELECT X(pt), Y(pt), ASTEXT(mp) INTO x, y, str; 
SET str = REPLACE(str, 'POLYGON((',''); 
SET str = REPLACE(str, '))', ''); 
SET str = CONCAT(str, ','); 

SET pb = 1; 
SET pe = LOCATE(',', str); 
SET xy = SUBSTRING(str, pb, pe - pb); 
SET p = INSTR(xy, ' '); 
SET p1x = SUBSTRING(xy, 1, p - 1); 
SET p1y = SUBSTRING(xy, p + 1); 
SET str = CONCAT(str, xy, ','); 

WHILE pe > 0 DO 
SET xy = SUBSTRING(str, pb, pe - pb); 
SET p = INSTR(xy, ' '); 
SET p2x = SUBSTRING(xy, 1, p - 1); 
SET p2y = SUBSTRING(xy, p + 1); 
IF p1y < p2y THEN SET m = p1y; ELSE SET m = p2y; END IF; 
IF y > m THEN 
IF p1y > p2y THEN SET m = p1y; ELSE SET m = p2y; END IF; 
IF y <= m THEN 
IF p1x > p2x THEN SET m = p1x; ELSE SET m = p2x; END IF; 
IF x <= m THEN 
IF p1y != p2y THEN 
SET xinters = (y - p1y) * (p2x - p1x) / (p2y - p1y) + p1x; 
END IF; 
IF p1x = p2x OR x <= xinters THEN 
SET counter = counter + 1; 
END IF; 
END IF; 
END IF; 
END IF; 
SET p1x = p2x; 
SET p1y = p2y; 
SET pb = pe + 1; 
SET pe = LOCATE(',', str, pb); 
END WHILE; 

RETURN counter % 2; 

END$$

DELIMITER ;

私の問題では、時間範囲の値内の長方形内にある車両を見つけたいと思います。 2つの個別のクエリを実行して、最初のクエリを実行して最初に緯度と経度の値を取得し、2番目のクエリを使用して長方形内かどうかをテストする必要があります。これを最適化するには?

1
user8012596

誤って、5000万を超えるNMEA行を含むテーブルを持つDBがあります。テーブルには複雑なインデックス(タイムスタンプ、緯度、経度)があります。簡単なクエリが起動しました:

SELECT DISTINCT vehicle_id
  FROM nmea 
 WHERE ts  BETWEEN '2016-05-10' AND '2016-05-11'
   AND lat BETWEEN 50.00 AND 50.20
   AND lon BETWEEN 30.00 AND 30.20
;

だから私は得ました:

+------------+
| vehicle_id |
+------------+
|      12787 |
|       3123 |
|       9734 |
|      11201 |
|       7241 |
|      21002 |
|       4509 |
+------------+
7 rows in set (0.01 sec)

EXPLAINは次を示します(少しトリミングされています):

+----+-------------+-------+-------+------------------+------------+---------+------+-------+----------------------------------------+
| id | select_type | table | type  | possible_keys    | key        | key_len | ref  | rows  | Extra                                  |
+----+-------------+-------+-------+------------------+------------+---------+------+-------+----------------------------------------+
|  1 | SIMPLE      | w     | range | ...ts_lat_lon... | ts_lat_lon | 12      | NULL | 57362 | Using index condition; Using temporary |
+----+-------------+-------+-------+------------------+------------+---------+------+-------+----------------------------------------+
1 row in set (0.00 sec)

ここで、適切なインデックスでBETWEENsを使用してもペナルティがないことがわかります。

ここに提案があります:緯度経度座標は異常を本初子午線の近くに持っています。 PMの左と右にある2つの異なる長方形に分割し、個別にチェックする必要があります。

3
Kondybas

緯度と経度が長方形に収まっているかどうかをテストする次の関数があります。

[〜#〜] nooooo [〜#〜]。ホイールを再作成しています。

地理空間を使用

最初に列を追加する必要があり、次にそれを設定します。

_ALTER TABLE geom ADD pt POINT;

UPDATE geom
  SET pt = point(longitude, latitude);

ALTER TABLE geom DROP COLUMN latitude, DROP COLUMN longitude;
_

この時点で冗長であるため、古い列を削除しました。経度と緯度は、それぞれST_X(pt)ST_Y(pt)を使用してアクセスできます。

次に、インデックスを追加します。

_ALTER TABLE geom ADD SPATIAL INDEX(g);
_

これで、 インデックス可能な操作を使用できますST_MakeEnvelope

_SELECT *
FROM geom
WHERE MBRContains(
  ST_MakeEnvelope(Point(x1,y1),Point(x2,y2)),
  pt
);
_

RexTester上のMySQL 5.7で動作する上記の例 を参照してください。

新しい列で使用できる MySQLの空間関数 のリストについては、 ここ をクリックしてください。 GISについて話すとき、私は常にMySQLユーザーをお勧めします より良いデータベースにアップグレードしてください。=)

3
Evan Carroll