SQL заявка за получаване на три най-близки местоположения

Ето моята главоблъсканица за запитване: - Имам таблица с клиенти с дължина и ширина за техния пощенски код. - Имам таблица с доставчици също с географска дължина и ширина за техния пощенски код. - Няма връзки между таблиците. - Имам дефинирана от потребителя функция, която изчислява разстоянието между два набора от дължина и ширина.

Имам следната заявка, която може да покаже (до) трите най-близки доставчика в радиус от 50 мили за даден клиент (dbo.latlngdistance е дефинираната от потребителя функция):

select top 3 dname1, dcity, dstate, dzip, dbo.latlngdistance(dlr.latitude, dlr.longitude, 
    (select lat from homeowner where ho_identifier = 16), 
    (select lng from homeowner where ho_identifier = 16)) AS SortDistance
from dlr
where dbo.latlngdistance(dlr.latitude, dlr.longitude, 
    (select lat from homeowner ho_identifier = 16), 
    (select lng from homeowner where ho_identifier = 16)) between 0 and 50
Order By SortDistance

Имам друга заявка, която връща всички доставчици в радиус от 50 мили от всички клиенти (ограничил съм броя на клиентите в този пример, защото производителността за тази заявка е ужасна):

select ho.ho_identifier, ho.ho_firstname, ho.ho_lastname, ho.ho_email, dname1, dcity, dstate, dzip
from dlr
    cross join homeowner ho
where dbo.latlngdistance(dlr.latitude, dlr.longitude, 
    (select lat from homeowner ho2 where ho.ho_identifier = ho2.ho_identifier), 
    (select lng from homeowner ho3 where ho.ho_identifier = ho3.ho_identifier)) between 0 and 50
and ho.ho_identifier in (16, 17, 18, 19, 20)
Order By ho.ho_identifier

Въпросът ми е как да получа (до) трите най-близки доставчика в радиус от 50 мили за всички клиенти?


person Vin Allen    schedule 09.10.2014    source източник
comment
опитайте да направите друга дефинирана от потребителя функция или процедура за съхранение, за да направите първата си заявка в цикъл?   -  person Luo Sen    schedule 09.10.2014
comment
Трябваше да спомена, че съм заседнал на SQL Server 2000 -- знам, нали?   -  person Vin Allen    schedule 09.10.2014
comment
това е гадно, има начин да се направи чрез използване на множество съединения, но не мисля, че този начин е добър начин...   -  person Luo Sen    schedule 10.10.2014
comment
Ако можете да надстроите базата данни до нещо, създадено през последното десетилетие, можете да използвате тип пространствени данни, който има вградена поддръжка за определяне на k-най-близките съседи (blogs.msdn.com/b/isaac/archive/2008/10/23/).   -  person Justin R.    schedule 10.10.2014


Отговори (2)


Бих създал изглед с дефиниция, подобна на тази:

SELECT 
    ho.ho_identifier, dlr.dname1, --dlr.id would be even better, but you didnt give the name of the id column
    dbo.latlngdistance(dlr.latitute, dlr.longtitude, ho.lat, ho.lng) AS distance
FROM dlr d
CROSS JOIN homeowner ho

Като се има предвид, че този изглед ще се наричаcalculedDistances, можете да намерите най-близкия доставчик с нещо като:

SELECT ho.ho_identifier, dlr.dname1
FROM homeowner ho
JOIN calculatedDistances dis
    ON dis.ho_identifier = ho.ho_identifier
WHERE 
    NOT EXISTS(
        SELECT 1
        FROM calculatedDistances nearer
        WHERE
            nearer.distance < dis.distance
            AND nearer.ho_identifier = ho.ho_identifier
    )

Намирането на три най-близки би било по-трудно и може да доведе до някои сериозни проблеми с производителността. Бихте могли да направите нещо като:

SELECT 
    dis.ho_identifier, dis.dname1
FROM calculatedDistances dis
LEFT JOIN calculatedDistances nearer
    ON nearer.ho_identifier = dis.ho_identifier
    AND nearer.distance < dis.distance
GROUP BY dis.ho_identifier, dis.dname1
HAVING ISNULL(COUNT(1), 0) < 3

Още веднъж - може да се наложи да съхраните изчисленото разстояние в някои други таблици, ако имате проблеми с производителността.

Сега е доста късно там, където живея, така че съжалявам, ако съм направил грешки. Ако е така, моля, коментирайте този отговор и аз ще се опитам да се коригирам утре.

person Apokralipsa    schedule 09.10.2014
comment
Изгледът, който предлагате, създава декартово съединение, което предвид размера на таблиците, с които се занимавам (+50 000 доставчици и +26 000 собственици на жилища) създава сериозни проблеми с производителността (напр. Убих заявката, използвайки един ho_identifier след две минути). Но ми харесва посоката, в която вървите! Между другото, имам колона dlr_identifier. - person Vin Allen; 09.10.2014
comment
Това са проблемите, за които съм писал. От друга страна - трябва да имате много сходни планове за изпълнение с вашите заявки. Може би бихте могли да създадете таблица, която да съдържа изчислените резултати на функцията? - person Apokralipsa; 09.10.2014
comment
Е, аз ограничих изгледа до разстояния между 0 и 50 мили, което помогна за единичната заявка ho_identifier, но заявката за 5 ho_identifiers отнема повече от три минути досега. Между другото, използвам последната заявка, за да намеря трите най-близки. - person Vin Allen; 09.10.2014
comment
Друго нещо, което може да помогне за подобряване на производителността, е да пренапише изгледа, за да не използва скаларната функция, а да прави изчисленията вградени. Полезно четене: databasejournal.com/features/mssql/article.php/3845381/ - person Apokralipsa; 10.10.2014

Ето какво предложи бивш колега:

Мисля, че ще се опитам да събера целевите собственици на жилища във временна таблица, за да запазя нещата, и след това да използвам SQL, за да премина процедурно през всяка стъпка през всяка от тях.

като например (имайте предвид, че не мога да тествам нищо от това)

Select HO_Identifier, zip.lat, zip.lng INTO MyTempHO
From DBC_Homeowner
LEFT Join Zip on zip.zip = left(5,HO_Zip) AND Zip.ll = 'L'  //I think there might also be a function to get lat/lng from zip....
WHERE <Your homeowner selection criteria>

Това трябва да ви даде по-малка маса, с която да работите. Проверете дали всички полета Lat, Lng са попълнени, може да има проблем с циповете на някои. Разбира се отляво (5 работи само за САЩ, не за Канада)

Създайте друга таблица, за да съхранявате резултатите, напр.

CREATE TABLE MyResults (HO_Identifier int,DLR_Identifier int)

Записва цикъл, за да премине през всеки ред и да намери първите 3 дилъра, като напр

//изпълнете всичко това като един израз

Declare @HO_Identifier int, @HO_Lat double, @HO_Lng double

Declare Cursor HO from select HO_Identifier , Lat, Lang from MyTempHO

OPEN HO
FETCH FROM HO Into @HO_Identifier, @HO_Lat, @HO_Lng 
While @@INDEX = 0
BEGIN
    Insert MyResults (@HO_Identifier, Dlr.dlr_Identifier) Select TOP 3 DLR_Identifier from dlr 
        where dbo.LAtLngDistance(@HO_Lat, @HO_Lng, dlr.lat, dlr.lng) < 100
        AND <Your other dlr criteria>
        AND dlr.lng between HO_Lng + <some negative amount to limit results> AND HO_Lng - <Same negative amount> //Note lng is negative!
        ORDER BY dbo.LAtLngDistance(@HO_Lat, @HO_Lng, dlr.lat, dlr.lng)

    FETCH NEXT FROM HO Into @HO_Identifier, @HO_Lat, @HO_Lng 
END

CLOSE HO
DEALLOCATE HO
person Vin Allen    schedule 16.10.2014