Tại sao sử dụng kiểu dữ liệu địa lý SQL Server 2008?


105

Tôi đang thiết kế lại cơ sở dữ liệu khách hàng và một trong những phần thông tin mới mà tôi muốn lưu trữ cùng với các trường địa chỉ chuẩn (Đường phố, Thành phố, v.v.) là vị trí địa lý của địa chỉ. Trường hợp sử dụng duy nhất mà tôi nghĩ đến là cho phép người dùng lập bản đồ tọa độ trên bản đồ Google khi không thể tìm thấy địa chỉ, điều này thường xảy ra khi khu vực này mới phát triển hoặc ở vùng sâu vùng xa / nông thôn.

Xu hướng đầu tiên của tôi là lưu trữ vĩ độ và kinh độ dưới dạng giá trị thập phân, nhưng sau đó tôi nhớ rằng SQL Server 2008 R2 có một geographykiểu dữ liệu. Tôi hoàn toàn không có kinh nghiệm sử dụng geography, và từ nghiên cứu ban đầu của tôi, nó có vẻ là quá mức cần thiết cho kịch bản của tôi.

Ví dụ: để làm việc với vĩ độ và kinh độ được lưu trữ dưới dạng decimal(7,4), tôi có thể thực hiện điều này:

insert into Geotest(Latitude, Longitude) values (47.6475, -122.1393)
select Latitude, Longitude from Geotest

nhưng với geography, tôi sẽ làm điều này:

insert into Geotest(Geolocation) values (geography::Point(47.6475, -122.1393, 4326))
select Geolocation.Lat, Geolocation.Long from Geotest

Mặc dù nó không phải phức tạp hơn nhiều, tại sao add phức tạp nếu tôi không phải?

Trước khi tôi từ bỏ ý định sử dụng geography, có điều gì tôi nên cân nhắc không? Sẽ nhanh hơn khi tìm kiếm vị trí bằng chỉ mục không gian so với lập chỉ mục các trường Kinh độ và Vĩ độ? Có những lợi ích khi sử dụng geographymà tôi không biết? Hoặc, ngược lại, có những cảnh báo mà tôi nên biết có thể làm tôi nản lòng khi sử dụng geography?


Cập nhật

@Erik Philips mang đến khả năng thực hiện các tìm kiếm ở vùng lân cận geography, điều này rất tuyệt vời.

Mặt khác, một thử nghiệm nhanh cho thấy việc đơn giản selectđể lấy vĩ độ và kinh độ chậm hơn đáng kể khi sử dụng geography(chi tiết bên dưới). , và một nhận xét về câu trả lời được chấp nhận cho một câu hỏi SO khác geographyđã giúp tôi thoải mái:

@SaphuA Không có chi. Như một chú thích phụ, hãy RẤT cẩn thận khi sử dụng chỉ mục không gian trên cột kiểu dữ liệu GEOGRAPHY có thể bỏ qua. Có một số vấn đề nghiêm trọng về hiệu suất, vì vậy hãy đặt cột GEOGRAPHY đó không thể null ngay cả khi bạn phải sửa sang lại giản đồ của mình. - Tomas ngày 18 tháng 6 lúc 11:18

Nói chung, cân nhắc giữa khả năng thực hiện các tìm kiếm vùng lân cận so với sự đánh đổi về hiệu suất và độ phức tạp, tôi đã quyết định từ bỏ việc sử dụng geographytrong trường hợp này.


Chi tiết về bài kiểm tra tôi đã chạy:

Tôi đã tạo hai bảng, một bảng sử dụng geographyvà một bảng khác sử dụng decimal(9,6)cho vĩ độ và kinh độ:

CREATE TABLE [dbo].[GeographyTest]
(
    [RowId] [int] IDENTITY(1,1) NOT NULL,
    [Location] [geography] NOT NULL,
    CONSTRAINT [PK_GeographyTest] PRIMARY KEY CLUSTERED ( [RowId] ASC )
) 

CREATE TABLE [dbo].[LatLongTest]
(
    [RowId] [int] IDENTITY(1,1) NOT NULL,
    [Latitude] [decimal](9, 6) NULL,
    [Longitude] [decimal](9, 6) NULL,
    CONSTRAINT [PK_LatLongTest] PRIMARY KEY CLUSTERED ([RowId] ASC)
) 

và chèn một hàng duy nhất sử dụng cùng các giá trị kinh độ và vĩ độ vào mỗi bảng:

insert into GeographyTest(Location) values (geography::Point(47.6475, -122.1393, 4326))
insert into LatLongTest(Latitude, Longitude) values (47.6475, -122.1393)

Cuối cùng, chạy đoạn mã sau cho thấy rằng, trên máy của tôi, việc chọn vĩ độ và kinh độ chậm hơn khoảng 5 lần khi sử dụng geography.

declare @lat float, @long float,
        @d datetime2, @repCount int, @trialCount int, 
        @geographyDuration int, @latlongDuration int,
        @trials int = 3, @reps int = 100000

create table #results 
(
    GeographyDuration int,
    LatLongDuration int
)

set @trialCount = 0

while @trialCount < @trials
begin

    set @repCount = 0
    set @d = sysdatetime()

    while @repCount < @reps
    begin
        select @lat = Location.Lat,  @long = Location.Long from GeographyTest where RowId = 1
        set @repCount = @repCount + 1
    end

    set @geographyDuration = datediff(ms, @d, sysdatetime())

    set @repCount = 0
    set @d = sysdatetime()

    while @repCount < @reps
    begin
        select @lat = Latitude,  @long = Longitude from LatLongTest where RowId = 1
        set @repCount = @repCount + 1
    end

    set @latlongDuration = datediff(ms, @d, sysdatetime())

    insert into #results values(@geographyDuration, @latlongDuration)

    set @trialCount = @trialCount + 1

end

select * 
from #results

select avg(GeographyDuration) as AvgGeographyDuration, avg(LatLongDuration) as AvgLatLongDuration
from #results

drop table #results

Các kết quả:

GeographyDuration LatLongDuration
----------------- ---------------
5146              1020
5143              1016
5169              1030

AvgGeographyDuration AvgLatLongDuration
-------------------- ------------------
5152                 1022

Điều đáng ngạc nhiên hơn là ngay cả khi không có hàng nào được chọn, chẳng hạn như chọn nơi RowId = 2không tồn tại, geographyvẫn chậm hơn:

GeographyDuration LatLongDuration
----------------- ---------------
1607              948
1610              946
1607              947

AvgGeographyDuration AvgLatLongDuration
-------------------- ------------------
1608                 947

4
Tôi đang nghĩ đến việc làm cả hai, lưu Vĩ độ và Kinh độ trong các cột của riêng chúng và có một cột khác cho đối tượng Địa lý, vì vậy nếu tôi chỉ cần Vĩ độ / Kinh độ, tôi lấy chúng từ các cột và nếu tôi cần tìm kiếm vùng lân cận, tôi sẽ sử dụng Địa lý. Điều này có khôn ngoan không? Có bất kỳ nhược điểm nào không (ngoài việc nó chiếm nhiều không gian hơn ...)?
Yuval A.

@YuvalA. điều đó chắc chắn nghe có vẻ hợp lý và có thể là một sự thỏa hiệp tốt. Mối quan tâm duy nhất mà tôi trăn trở là liệu việc đặt cột Địa lý trong bảng có ảnh hưởng gì đến các truy vấn đối với bảng hay không - Tôi không có kinh nghiệm về điều đó nên bạn sẽ cần phải kiểm tra để xác minh.
Jeff Ogata

1
Tại sao bạn liên tục cập nhật câu hỏi của mình bằng các câu hỏi mới thay vì đặt câu hỏi mới?
Chad

@Chad không chắc ý của bạn. Tôi đã cập nhật nội dung câu hỏi một lần, và nó không phải để hỏi thêm.
Jeff Ogata

6
Điều đáng chú ý là bây giờ, đối với những người tìm thấy câu hỏi này, SQL Server 2012 bao gồm việc tăng hiệu suất đáng kể với việc lập chỉ mục không gian. Cũng cần lưu ý là thực tế là miễn là bạn đang lưu trữ thông tin vị trí, bạn có thể thêm thông tin không gian sau đó bằng cách sử dụng dịch vụ tra cứu để mã hóa địa lý các địa chỉ đã được lưu trữ của mình.
Volvox

Câu trả lời:


66

Nếu bạn dự định thực hiện bất kỳ phép tính không gian nào, EF 5.0 cho phép các Biểu thức LINQ như:

private Facility GetNearestFacilityToJobsite(DbGeography jobsite)
{   
    var q1 = from f in context.Facilities            
             let distance = f.Geocode.Distance(jobsite)
             where distance < 500 * 1609.344     
             orderby distance 
             select f;   
    return q1.FirstOrDefault();
}

Sau đó, có một lý do rất tốt để sử dụng Địa lý.

Giải thích về không gian trong Khung thực thể .

Cập nhật bằng cách tạo cơ sở dữ liệu không gian hiệu suất cao

Như tôi đã lưu ý trên Noel Abrahams Trả lời :

Lưu ý về không gian, mỗi tọa độ được lưu trữ dưới dạng số dấu phẩy động có độ chính xác kép dài 64 bit (8 byte) và giá trị nhị phân 8 byte gần tương đương với 15 chữ số của độ chính xác thập phân, vì vậy hãy so sánh một số thập phân (9 , 6) chỉ có 5 byte, không phải là một so sánh chính xác. Số thập phân phải có tối thiểu là Số thập phân (15,12) (9 byte) cho mỗi LatLong (tổng số 18 byte) để so sánh thực tế.

So sánh các loại lưu trữ:

CREATE TABLE dbo.Geo
(    
geo geography
)
GO

CREATE TABLE dbo.LatLng
(    
    lat decimal(15, 12),   
    lng decimal(15, 12)
)
GO

INSERT dbo.Geo
SELECT geography::Point(12.3456789012345, 12.3456789012345, 4326) 
UNION ALL
SELECT geography::Point(87.6543210987654, 87.6543210987654, 4326) 

GO 10000

INSERT dbo.LatLng
SELECT  12.3456789012345, 12.3456789012345 
UNION
SELECT 87.6543210987654, 87.6543210987654

GO 10000

EXEC sp_spaceused 'dbo.Geo'

EXEC sp_spaceused 'dbo.LatLng'

Kết quả:

name    rows    data     
Geo     20000   728 KB   
LatLon  20000   560 KB

Kiểu dữ liệu địa lý chiếm thêm 30% dung lượng.

Ngoài ra, kiểu dữ liệu địa lý không giới hạn ở việc chỉ lưu trữ một Điểm, bạn cũng có thể lưu trữ LineString, CircularString, CompoundCurve, Polygon, CurvePolygon, GeometryCollection, MultiPoint, MultiLineString và MultiPolygon và hơn thế nữa . Bất kỳ nỗ lực nào để lưu trữ ngay cả những loại Địa lý đơn giản nhất (như Vĩ độ / Kinh độ) ngoài một Điểm (ví dụ: ví dụ LINESTRING (1 1, 2 2)) sẽ phát sinh thêm các hàng cho mỗi điểm, một cột để sắp xếp thứ tự cho mỗi điểm và một cột khác để nhóm các dòng. SQL Server cũng có phương pháp cho các kiểu dữ liệu địa lý trong đó bao gồm việc tính toán Diện tích, ranh giới, Chiều dài, Khoảng cách, và nhiều hơn nữa .

Có vẻ không khôn ngoan khi lưu trữ Vĩ độ và Kinh độ dưới dạng Thập phân trong Máy chủ Sql.

Cập nhật 2

Nếu bạn dự định thực hiện bất kỳ phép tính nào như khoảng cách, diện tích, v.v., thì việc tính toán chính xác chúng trên bề mặt trái đất là rất khó. Mỗi loại Địa lý được lưu trữ trong SQL Server cũng được lưu trữ với một ID tham chiếu không gian . Các id này có thể là các hình cầu khác nhau (trái đất là 4326). Điều này có nghĩa là các tính toán trong SQL Server sẽ thực sự tính toán chính xác trên bề mặt trái đất (thay vì như những con ruồi có thể xuyên qua bề mặt trái đất).

nhập mô tả hình ảnh ở đây


1
Để thêm vào thông tin này, việc sử dụng Địa lý thực sự mở rộng khả năng tìm kiếm sql từ vĩ độ / kinh độ giữa các vĩ độ / kinh độ khác (thường chỉ là hình chữ nhật) vì loại dữ liệu Địa lý cho phép bạn tạo nhiều vùng với hầu hết mọi kích thước và hình dạng.
Erik Philips

1
cảm ơn một lần nữa. Tôi đã hỏi lý do để xem xét sử dụng geographyvà bạn đã cung cấp một số lý do tốt. Cuối cùng, tôi quyết định chỉ sử dụng decimalcác trường trong trường hợp này (xem bản cập nhật dài dòng của tôi), nhưng thật tốt khi biết rằng tôi có thể sử dụng geographynếu tôi cần làm bất cứ điều gì xa lạ hơn là chỉ lập bản đồ tọa độ.
Jeff Ogata 14/09/11

6

Một điều khác cần xem xét là không gian lưu trữ được sử dụng bởi mỗi phương pháp. Loại địa lý được lưu trữ dưới dạng a VARBINARY(MAX). Thử chạy tập lệnh này:

CREATE TABLE dbo.Geo
(
    geo geography

)

GO

CREATE TABLE dbo.LatLon
(
    lat decimal(9, 6)
,   lon decimal(9, 6)

)

GO

INSERT dbo.Geo
SELECT geography::Point(36.204824, 138.252924, 4326) UNION ALL
SELECT geography::Point(51.5220066, -0.0717512, 4326) 

GO 10000

INSERT dbo.LatLon
SELECT  36.204824, 138.252924 UNION
SELECT 51.5220066, -0.0717512

GO 10000

EXEC sp_spaceused 'dbo.Geo'
EXEC sp_spaceused 'dbo.LatLon'

Kết quả:

name    rows    data     
Geo     20000   728 KB   
LatLon  20000   400 KB

Kiểu dữ liệu địa lý chiếm gần gấp đôi dung lượng.


2
Lưu ý về không gian, mỗi tọa độ được lưu trữ dưới dạng số dấu phẩy động có độ chính xác kép dài 64 bit (8 byte) và giá trị nhị phân 8 byte gần tương đương với 15 chữ số của độ chính xác thập phân , vì vậy hãy so sánh một số thập phân (9 , 6) chỉ có 5 byte , không phải là một so sánh chính xác. Số thập phân phải có tối thiểu là Số thập phân (15,12) (9 byte) cho mỗi LatLong (tổng số 18 byte) để so sánh thực tế.
Erik Philips

9
@ErikPhilips vấn đề là tại sao lại sử dụng số thập phân (15, 12) khi tất cả những gì bạn cần là số thập phân (9, 6)? So sánh ở trên là một thực tế - không phải là một bài tập học thuật.
Noel Abrahams

-1
    CREATE FUNCTION [dbo].[fn_GreatCircleDistance]
(@Latitude1 As Decimal(38, 19), @Longitude1 As Decimal(38, 19), 
            @Latitude2 As Decimal(38, 19), @Longitude2 As Decimal(38, 19), 
            @ValuesAsDecimalDegrees As bit = 1, 
            @ResultAsMiles As bit = 0)
RETURNS decimal(38,19)
AS
BEGIN
    -- Declare the return variable here
    DECLARE @ResultVar  decimal(38,19)

    -- Add the T-SQL statements to compute the return value here
/*
Credit for conversion algorithm to Chip Pearson
Web Page: www.cpearson.com/excel/latlong.aspx
Email: chip@cpearson.com
Phone: (816) 214-6957 USA Central Time (-6:00 UTC)
Between 9:00 AM and 7:00 PM

Ported to Transact SQL by Paul Burrows BCIS
*/
DECLARE  @C_RADIUS_EARTH_KM As Decimal(38, 19)
SET @C_RADIUS_EARTH_KM = 6370.97327862
DECLARE  @C_RADIUS_EARTH_MI As Decimal(38, 19)
SET @C_RADIUS_EARTH_MI = 3958.73926185
DECLARE  @C_PI As Decimal(38, 19)
SET @C_PI =  pi()

DECLARE @Lat1 As Decimal(38, 19)
DECLARE @Lat2 As Decimal(38, 19)
DECLARE @Long1 As Decimal(38, 19)
DECLARE @Long2 As Decimal(38, 19)
DECLARE @X As bigint
DECLARE @Delta As Decimal(38, 19)

If @ValuesAsDecimalDegrees = 1 
Begin
    set @X = 1
END
Else
Begin
    set @X = 24
End 

-- convert to decimal degrees
set @Lat1 = @Latitude1 * @X
set @Long1 = @Longitude1 * @X
set @Lat2 = @Latitude2 * @X
set @Long2 = @Longitude2 * @X

-- convert to radians: radians = (degrees/180) * PI
set @Lat1 = (@Lat1 / 180) * @C_PI
set @Lat2 = (@Lat2 / 180) * @C_PI
set @Long1 = (@Long1 / 180) * @C_PI
set @Long2 = (@Long2 / 180) * @C_PI

-- get the central spherical angle
set @Delta = ((2 * ASin(Sqrt((power(Sin((@Lat1 - @Lat2) / 2) ,2)) + 
    Cos(@Lat1) * Cos(@Lat2) * (power(Sin((@Long1 - @Long2) / 2) ,2))))))

If @ResultAsMiles = 1 
Begin
    set @ResultVar = @Delta * @C_RADIUS_EARTH_MI
End
Else
Begin
    set @ResultVar = @Delta * @C_RADIUS_EARTH_KM
End

    -- Return the result of the function
    RETURN @ResultVar

END

2
Những câu trả lời mới luôn được hoan nghênh, nhưng hãy thêm một số ngữ cảnh. Giải thích ngắn gọn cách ở trên giải quyết vấn đề làm cho câu trả lời hữu ích hơn cho người khác.
Leigh,
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.