Is there a way to compare Lat/long of two tables - sql

I have two tables:
address_points
kmldata
address_points table columns: ID address Latitude1 Longitude2
kmldata table columns: Locname Lat Long
Now I want to see all the records of the address_points table whose Latitude1 and Longitude2 values fall in the range of Lat and Long values of kmldata table.
I have not handled comparison of locations before in the SQL server so don't know which function I can use here. I thought of the BETWEEN operator but can seem to use it here properly here. Any guidance on how I can acheive this?

You need to use the spatial functions within SQL Server. First you will need to aggregate all the points in your kmldata table to create an area, and then use STWithin to check which points fall within that area:
declare #kmlarea geography
select #kmlarea = geography::EnvelopeAggregate(geography::Point(Lat, Long, 4326))
from kmldata
select *
from address_points a
where geography::Point(Latitude1, Longitude2, 4326).STWithin(#kmlarea) = 1

There are several ways of calculating the geographical distance between two sets of coordinates. Already posted is the built in Geography method. There are also several good "home grown" functions based on a round earth model.
The hard part is making the actual comparison when you have large row counts in your source and destination tables. Comparing every source to every destination creates an unnecessarily large Cartesian product. I say "unnecessarily" because there's no point in calculating the distance between a source in Florida and a destination in California when I'm only interested in destinations withing 15 miles of the source.
To solve the problem, I created a "bounding box" function that calculates a square(ish) box around a single set of coordinates. The code is posted below...
CREATE FUNCTION dbo.tfn_LatLngBoundingBox
/* ===================================================================
12/03/2019 JL, Created: Calculates the bounding box for a given set of Lat/Lng coordinates.
=================================================================== */
--===== Define I/O parameters
(
#Lat DECIMAL(8,5),
#Lng DECIMAL(8,5),
#MaxDistance DECIMAL(8,3),
#DistanceUnit CHAR(1) -- 'M'=miles ; 'K'=kilometers
)
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
SELECT
MinLat = CONVERT(decimal(8, 5), (x.MinLat / PI()) * 180),
MaxLat = CONVERT(decimal(8, 5), (x.MaxLat / PI()) * 180),
MinLng = CONVERT(decimal(8, 5), (y.MinLng / PI()) * 180),
MaxLng = CONVERT(decimal(8, 5), (y.MaxLng / PI()) * 180)
FROM
( VALUES (
CASE
WHEN #DistanceUnit = 'K' THEN #MaxDistance / 6366.707019 -- Earth sphere radius in kilometers
WHEN #DistanceUnit = 'M' THEN (#MaxDistance * 1.609344) / 6366.707019
END,
(#Lat / 180) * PI(),
(#Lng / 180) * PI()
) ) r (DistRad, rLat, rLng)
CROSS APPLY ( VALUES (r.rLat - r.DistRad, r.rLat + r.DistRad) ) x (MinLat, MaxLat)
CROSS APPLY ( VALUES (ASIN(SIN(r.rLat) / COS(r.DistRad))) ) lt (LatT) -- = 1.4942 rad
CROSS APPLY ( VALUES (ACOS( ( COS(r.DistRad) - sin(lt.LatT) * sin(r.rLat) ) / ( cos(lt.LatT) * cos(r.rLat) ) ) ) ) dl (DeltaLng)
CROSS APPLY ( VALUES (r.rLng - dl.DeltaLng, r.rLng + dl.DeltaLng) ) y (MinLng, MaxLng);
GO
The use case looks like the following...
SELECT
s.Lat,
s.Lng,
d.Lat,
d.Lng,
dm.DistanceInMiles
FROM
dbo.[Source] s
CROSS APPLY dbo.tfn_LatLngBoundingBox(s.Lat, s.Lng, 15, 'M') bb
LEFT JOIN dbo.Destination d
ON d.lat BETWEEN bb.MinLat AND bb.MaxLat
AND d.Lng BETWEEN bb.MinLng AND bb.MaxLng
CROSS APPLY dbo.tfn_LatLonDistanceInMiles(s.Lat, s.Lng, d.Lat, d.Lng) dm
WHERE
dm.DistanceInMiles <= 15;

Related

Allocate groups by size, preliminarily rounded and grouped

You are given a database of notebooks that contains two tables.
the table notebooks\brand contains data about the name of notebook brands.
the table notebooks\notebook contains data about the name of the notebook, its diagonal, width, depth, and height, and has a link to the brand to which this model belongs.
You need to select groups of notebooks by size. To do this, the size should first be rounded up to the nearest 0 or 5 and then grouped by the same size by counting the number of laptops in each group. Sort the data by size.
I Wrote a query that calculates how many laptops are represented in each brand:
cursor.execute("""SELECT brnd.title,
COUNT(brnd.id)
FROM notebooks_notebook AS ntbk
JOIN notebooks_brand AS brnd
ON ntbk.brand_id = brnd.id
GROUP BY brnd.title """)
('HP', 225)
('Prestigio', 1)
('Huawei', 6)
('ASUS', 223)
('Haier', 2)
('Xiaomi', 13)
('MSI', 34)
('HONOR', 15)
('Gigabyte', 5)
('Digma', 4)
('Lenovo', 253)
('Dell', 75)
('Acer', 82)
('Chuwi', 4)
('Apple', 55)
Postgres does integer division. Assuming that your size columns are defined as integers, we can round to the nearest 5 with an expression like :
width / 5 * 5
We can apply this logic to your query ; starting from your existing joins, we can compute the rounded values in a lateral join, then aggregate
select x.width, x.depth, x.height, count(*) cnt
from notebooks_notebook n
inner join notebooks_brand as b on n.brand_id = b.id
cross join lateral (values (width / 5 * 5, depth / 5 * 5, height / 5 * 5)) x(width, depth, height)
group by x.width, x.depth, x.height

find the maximum in a column, but only when two other columns match

I need help in PostgreSQL.
I have two tables
Predicton - predicts future disasters and casualties for each city.
Measures fits the type of damage control providers for each type of disaster (incl. cost and percent of "averted casualties")
Each disaster and provider combination has an amount of averted casualties (the percent from measures * amount of predicted casualties for that disaster*0.01).
For each combination of city and disaster, I need to find two providers that
1) their combined cost is less than a million
2) have the biggest amount of combined averted casualties.
My work and product so far
select o1.cname, o1.etype, o1.provider as provider1, o2.provider as provider2, (o1.averted + o2.averted) averted_casualties
from (select cname, m.etype, provider, mcost, (percent*Casualties*0.01)averted
from measures m, prediction p
where (m.etype = p.etype)) as o1, (select cname, m.etype, provider, mcost, (percent*Casualties*0.01)averted
from measures m, prediction p
where (m.etype = p.etype)) as o2
where (o1.cname = o2.cname) and (o1.etype = o2.etype) and (o1.provider < o2.provider) and (o1.mcost + o2.mcost < 1000000)
How do I change this query so It Will show me the best averted_casualties for each city/disaster combo (not just max of all table, max for each combo)
This is the desired outcome:
P.S. I'm not allowed to use ordering, views or functions.
First, construct all pairs of providers and do the casualty and cost calculation:
select p.*, m1.provider as provider_1, m2.provider as provider_2,
p.casualties * (1 - m1.percent / 100.0) * (1 - m2.percent / 100.0) as net_casualties,
(m1.mcost + m2.mcost) as total_cost
from measures m1 join
measures m2
on m1.etype = m2.etype and m1.provide < m2.provider join
prediction p
on m1.etype = p.etype;
Then, apply your conditions. Normally, you would use window functions, but since ordering isn't allowed for this exercise, you want to use a subquery:
with pairs as (
select p.*, m1.provider as provider_1, m2.provider as provider_2,
p.casualties * (1 - m1.percent / 100.0) * (1 - m2.percent / 100.0) as net_casualties,
(m1.mcost + m2.mcost) as total_cost
from measures m1 join
measures m2
on m1.etype = m2.etype and m1.provide < m2.provider join
prediction p
on m1.etype = p.etype;
)
select p.*
from pairs p
where p.total_cost < 1000000 and
p.net_casualties = (select min(p2.net_casualties)
from pairs p2
where p2.city = p.city and p2.etype = p.etype and
p2.total_cost < 1000000
);
The biggest number of averted casualties results in the smallest number of net casualties. They are the same thing.
As for your attempted solution. Just seeing the , in the from clause tells me that you need to study up on join. Simple rule: Never use commas in the from clause. Always use proper, explicit, standard join syntax.
Your repeated subqueries also suggest that you need to learn about CTEs.

Using geometry with srid 4326, what unit of measure does STDistance return?

So here is my set up:
I have a table such as:
Id, Lat, Long, GeoPoint, GeomPoint
both GeoPoint (geography) and GeomPoint (geometry) are set to SRID 4326
I have the following query:
DECLARE #radiiCollection TABLE
( [ID] INT IDENTITY(1, 1) PRIMARY KEY,
[Radius] GEOMETRY,
[RefPoint] GEOMETRY,
[RefAddr] VARCHAR(255),
[MinLat] VARCHAR(255),
[MaxLat] VARCHAR(255),
[MinLng] VARCHAR(255),
[MaxLng] VARCHAR(255))
DECLARE #point GEOMETRY = geometry::Point(-111.84493459999999, 33.3902569, 4326)
DECLARE #gpoint GEOGRAPHY = geography::Point(33.3902569, -111.84493459999999, 4326);
INSERT INTO #radiicollection
(radius,
refpoint,
refaddr,
maxlat,
maxlng,
minlat,
minlng)
VALUES ( #point.MakeValid().STBuffer(16093.40),
#point,
'10 miles of 85210',
33.51734689767781,
-111.6923852740045,
33.26298081643247,
-111.99703818130439 )
SELECT
GeomPoint,
GeoPoint
INTO #temp
FROM (
SELECT row_number() OVER (
PARTITION BY [ds].[ADDR],
[ds].[APT],
[ds].[ZIP] ORDER BY [ds].[IND_ID] ASC
) recid1, rGeop1.geompoint, rgeop1.GeoPoint
FROM [r].[main] ds
JOIN [r].[GeoPoint] rGeoP1
ON rGeoP1.[UID] = ds.[UID]
JOIN #radiiCollection rr
ON GeomPoint.STWithin(rr.radius) = 1
WHERE 1 = 1
AND (
(
(
(
try_cast(latitude AS DECIMAL(9, 1)) BETWEEN CONVERT(DECIMAL(9, 1), 33.26298081643247)
AND CONVERT(DECIMAL(9, 1), 33.51734689767781)
AND try_cast(longitude AS DECIMAL(9, 1)) BETWEEN CONVERT(DECIMAL(9, 1), - 111.99703818130439)
AND CONVERT(DECIMAL(9, 1), - 111.6923852740045)
)
)
)
)
) f
WHERE recid1 = 1
So, pulling both, I then pull STDistance for both the GeoPoint and the GeomPoint like so:
select top 10
try_cast(GeoPoint.STDistance(#gpoint) as float) DistanceGeo,
try_cast(GeoMPoint.STDistance(#point.MakeValid()) as float) DistanceGeom
from #temp
but what I'm getting is a little weird:
DistanceGeo DistanceGeom
-----------------------------------------
10495.1674191715 0.111544285781801
10249.4175883919 0.100540150724826
12307.1907929483 0.1262738924781
11804.655587608 0.116453906202276
10249.4175883919 0.100540150724826
9607.03640753812 0.101614826463312
11130.8413059431 0.100596791997409
10249.4175883919 0.100540150724826
6973.69243171186 0.0644901191669685
9605.88647121359 0.0967178499759486
From what I understand SRID determines unit of measure for the spatial column, but obviously, since both of these are SRID 4326 that is not the case? Any help would be appreciated. I believe what I'm seeing here is degrees instead of meters. Is there an easy way in SQL to convert degrees to meters? Or a way to change what unit STDistance uses for output?
You can get a definitive answer on which unit of measurement STDistance is using with the following query. For 4326 (for example), it's the meter.
SELECT *
FROM sys.spatial_reference_systems
WHERE spatial_reference_id = 4326
With SqlGeomety, you are dealing with "projected coordinates".
In your case, using SqlGeometry with WGS84 (4326), your units are degrees.
So STDistance(), STArea(), ... will return degrees.
You have to use a projected coordinate system that uses meters (like Lambert 93 for france or UTM for USA) to have coordinates in meters and calculations in meters.
Geometry assumes a flat map, geography takes in the shape of the earth. Remember the distance between lattitudes and longitudes vary dependant on where on the globe you are (at the north pole, the distances between longitudes is near 0).
If you want to bring out the big differences, put in a few more extreme examples into your code...pick a point near the north pole and another one a few degree's away. Compare that to a couple points on the equator...geometric distance and geography distance are two very seperate terms

Calculating Geometrically Linked Returns in SQL SERVER 2008

Calculating geometrically link returns
How do you multiply record2 * record1?
The desire is to return a value for actual rate and annulized rate
Given table unterval:
EndDate PctReturn
-------------------------------
1. 05/31/06 -0.2271835
2. 06/30/06 -0.1095986
3. 07/31/06 0.6984908
4. 08/31/06 1.4865360
5. 09/30/06 0.8938896
The desired output should look like this:
EndDate PctReturn Percentage UnitReturn
05/31/06 -0.2271835 -0.002272 0.997728
06/30/06 -0.1095986 -0.001096 0.996634669
07/31/06 0.6984908 0.006985 1.00359607
08/31/06 1.4865360 0.014865 1.018514887
09/30/06 0.8938896 0.008939 1.027619286
Percentage = PctReturn/100
UnitReturn (1 + S1) x (1 + S2) x ... (1 + Sn) - 1
Aggregating values desired:
Actual Rate 2.761928596
Annulized 6.757253223
Mathematics on aggregating value:
Actual Rate 1.027619 1.027619-1 = 0.027619 * 100 = 2.761928596
Annulized Rate 6.757253 (ActualRate^(12/number of intervals)-1)*100
Number of intervals in Example = 5
there are only 5 records or intervals
I did try utilizing the sum in the select statement but this did not allow for multiplying record2 by record1 to link returns. I thought utilizing the while function would allow for stepping record by record to multiply up the values of unitreturn. My starter level in SQL has me looking for help.
You have two option for getting a product in SQL Server.
1. Simulate using logs and exponents:
SQL Fiddle
create table returns
(
returnDate date,
returnValue float
)
insert into returns values('05/31/06', -0.002271835)
insert into returns values('06/30/06', -0.001095986)
insert into returns values('07/31/06', 0.006984908)
insert into returns values('08/31/06', 0.014865360)
insert into returns values('09/30/06', 0.008938896)
select totalReturn = power
(
cast(10.0 as float)
, sum(log10(returnValue + 1.0))
) - 1
from returns;
with tr as
(
select totalReturn = power
(
cast(10.0 as float)
, sum(log10(returnValue + 1.0))
) - 1
, months = cast(count(1) as float)
from returns
)
select annualized = power(totalReturn + 1, (1.0 / (months / 12.0))) - 1
from tr;
This leverages logs and exponents to simulate a product calculation. More info: User defined functions.
The one issue here is that it will fail for return < -100%. If you don't expect these it's fine, otherwise you'll need to set any values < 100% to -100%.
You can then use this actual return to get an annualized return as required.
2. Define a custom aggregate with CLR:
See Books Online.
You can create a CLR custom function and then link this an aggregate for use in your queries. This is more work and you'll have to enable CLRs on your server, but once it's done once you can use it as much as required.

Help structure query to list concerts/venues by distance

SQL noob here needing some help. I've got an idea of how to do this in PHP/SQL, but I would really like to condense this into one SELECT statement. OK:
The site I am working on is a list of concerts and venues. Venues have a latitude and longitude, and so do accounts corresponding to that users location.
I have three tables, accounts (users), concerts, I would like to SELECT a list of concerts (and join on venues for that info) that are happening at venues within x miles of the account, using this cheap formula for distance calculation (the site only lists venues in the UK so the error is acceptable):
x = 69.1 * (accountLatitude - venueLatitude);
y = 69.1 * (accountLongitude - venueLongitude) * cos(venueLatitude / 57.3);
distance = sqrt(x * x + y * y);
How can I achieve this in a single query?
Thanks in advance xD
This is done exactly as your formulas suggest.
Just substitute x and y into the distance formula.
If this is for MySQL the below should work (just replace the correct table names / column names).
SELECT concert.name, venue.name,
SQRT(POW(69.1 * (account.Latitude - venue.Latitude), 2) + POW(69.1 * (account.Longitude - venue.Longitude) * cos(venue.Latitude / 57.3), 2)) AS distance
FROM account, venue
LEFT JOIN concert ON conert.ID = venue.concertID
WHERE account.id = UserWhoIsLoggedIn
ORDER BY 3;
This should return all concert names, venue names and distance from user order by the distance.
If you are not using MySQL you may need to change either the POW or SQRT functions.
Also be aware that sin & cos functions take there inputs in Radians.
For everyone elses benefit heres now I did it in the end:
$q = 'SELECT gigs.date, bands.idbands, venues.latitude, venues.longitude, venues.idvenues, bands.name AS band, venues.name AS venue,
SQRT(POW(69.1 * (' . $lat . ' - venues.latitude), 2) + POW(69.1 * (' . $lon . ' - venues.longitude) * cos(venues.latitude / 57.3), 2)) AS distance
FROM gigs
LEFT JOIN bands ON bands.idbands=gigs.bands_idbands
LEFT JOIN venues ON venues.idvenues=gigs.venues_idvenues
WHERE 1
ORDER BY distance';
where $lat and $lon are the latitude and longitude of the user currently logged in!
this selects every single gig thats happening at every venue, and arranges them in order of distance