i have following query how to check latitude and longitude are in circle or not but its show wrong result please help
declare #latitudeCar float = 25.681137335685306;
declare #longitudeCar float = 66.697998046875;
declare #latitudePlace float = 25.918526162075153;
declare #longitudePlace float = 66.170654296875;
declare #source geography = 'POINT(' + cast(#latitudeCar as nvarchar) + ' ' + cast(#longitudeCar as nvarchar) + ')'
declare #target geography = 'POINT(' + cast(#latitudePlace as nvarchar) + ' ' + cast(#longitudePlace as nvarchar) + ')'
declare #radius float = 164096.68447201277
declare #check float
declare #isPastCircle bit
set #check = (select #source.STDistance(#target)/1609.344)
--select #source.STDistance(#target)/1000
set #isPastCircle = (select case when #check > #radius then 1 else 0 end);
select #isPastCircle [isPastCircle], #check [carDistance], #radius [acceptableRadius]
Always result is Wrong :(
It looks like you're trying to do your calculations in some fixed radius specified in miles but you're tripping the conversions. Below is a cleaned up version of the code:
declare #latitudeCar float = 25.681137335685306,
#longitudeCar float = 66.697998046875,
#latitudePlace float = 25.918526162075153,
#longitudePlace float = 66.170654296875;
declare #source geography = geography::Point(#latitudeCar, #longitudeCar, 4326),
#target geography = geography::Point(#latitudePlace, #longitudePlace, 4326);
declare #radius_in_miles int = 100;
declare #radius_in_meters float = #radius_in_miles * 1609.344;
select #target.STBuffer(#radius_in_meters).STContains(#source) AS [isWithinCircle],
#target.STDistance(#source) AS [distance_in_meters],
#target.STBuffer(#radius_in_meters).STDisjoint(#source) AS [isPastCircle];
TL;DR - Try, as often as possible, to abstract your conversions away. In this case, I'm specifying my desired radius in the units I'm used to (i.e. miles) and then using a different variable to hold the desired radius that the spatial reference uses (if you're ever in doubt, just query sys.spatial_reference_systems). Also note that I simplified the within/outside of specified radius calculations to use STContains() and STDisjoint() (respectively). I'm assuming that you don't actually care about the distance between the two points, so using those alone should speed things up a bit.
Your query seems correct other than the "<" in the select case. It should be:
set #isPastCircle = (select case when #check > #radius then 1 else 0 end);
Related
I'm trying to return all the rows from [store] with distance of less than 10 miles. Table [Store] has a column of type Geography.
I understand how to find the distance between two specific points, something like this:
declare #origin geography
select #origin = geography::STPointFromText('POINT(' + CAST(-73.935242 AS
VARCHAR(20)) + ' ' + CAST(40.730610 AS VARCHAR(20)) + ')', 4326)
declare #destination geography
select #destination = geography::STPointFromText('POINT(' + CAST(-93.732666 AS VARCHAR(20)) + ' ' + CAST(30.274096 AS VARCHAR(20)) + ')', 4326)
select #origin.STDistance(#destination)/ 1609.344 as 'distance in miles'
I'm having trouble applying this logic to a SELECT statement. Instead of getting the distance between #origin and #destination, I would like to get the distance in miles between #origin and store.Geolocation for all rows.
The STDistance method, used from one instance of Geography and applied to another, returns the distance between the two points. It can be used with variables, e.g. #Origin.STDistance( #Destination ), columns or a combination thereof, e.g. to find all of the stores within 10 miles of a particular #Origin:
select *
from Store
where #Origin.STDistance( Store.Geolocation ) < 1609.344 * 10.0;
Note: As BenThul pointed out, spatial index handling is a bit fickle. An STDistance compared to a constant is SARGable: #Origin.STDistance( Store.Geolocation ) < 1609.344 * 10.0, but this mathematically equivalent expression is not: #Origin.STDistance( Store.Geolocation ) / 1609.344 < 10.0. This "feature" is documented here.
How can I convert (money/int)*100 to a float datatype?
a.ResidualValue is of type money, while ListPriceCar is of type int.
The following outputs a number with a comma as the decimal separator, so I presume it is still a money field, and not a float as I wish:
CONVERT(float, CONVERT(float, REPLACE(CONVERT(nvarchar(10), a.ResidualValue), ',', '.'))/ListPriceCar)*100 AS 'Residual value %'
Result: 62,825
What is issue with simple cast as below:
DECLARE #money AS money
SET #money = $2345234.35
SELECT #money, CAST(#money AS float)
What's the value of ResidualValue? If ResidualValue contains a comma, then comma is your decimal char. You shouldn't convert it into a point then. You can divide a money value by an int value.
declare #a money = 10.5;
declare #b int = 20;
select (#a / #b) * 100
select cast((#a / #b) * 100 as float)
In your example, the result was already given as an float. You can test this by running the following example code.
--INPUT VARIABLES
declare #ResidualValue money = 5000.54
declare #ListPriceCar int = 15000
--OUTPUT VARIABLE
declare #ResultCalculation as sql_variant;
--CALCULATION
set #ResultCalculation = CONVERT(float, CONVERT(float, REPLACE(CONVERT(nvarchar(10), #ResidualValue), ',', '.'))/#ListPriceCar)*100
--GET INFORMATION ON THE OUTPUT VARIABLE
select sql_variant_property(#ResultCalculation,'BaseType') AS 'Base Type',
sql_variant_property(#ResultCalculation,'Precision') AS 'Precision',
sql_variant_property(#ResultCalculation,'Scale') AS 'Scale',
sql_variant_property(#ResultCalculation,'TotalBytes') AS 'TotalBytes',
sql_variant_property(#ResultCalculation,'Collation') AS 'Collation',
sql_variant_property(#ResultCalculation,'MaxLength') AS 'MaxLength';
There is an easier way to calculate your answer:
CAST(#ResidualValue/#ListPriceCar * 100 as float)
The select returns right at 23,000 rows
The except will return between 60 to 200 rows (and not the same rows)
The except should return 0 as it is select a except select a
PK: [docSVenum1].[enumID], [docSVenum1].[valueID], [FTSindexWordOnce].[wordID]
[tf] is a float and and I get float is not exact
But I naively thought avg(float) would be repeatable
Avg(float) does appear to be repeatable
What is the solution?
TF is between 0 and 1 and I only need like 5 significant digits
I just need avg(TF) to be the same number run to run
Decimal(9,8) gives me enough precision and if I cast to decimal(9,8) the except properly returns 0
I can change [TF] to decimal(9,8) but it will be bit of work and lot of regression testing as some of the test that use [tf] take over a day to run
Is change [TF] to decimal(9,8) the best solution?
SELECT [docSVenum1].[enumID], [docSVenum1].[valueID], [FTSindexWordOnce].[wordID]
, avg([FTSindexWordOnce].[tf]) AS [avgTFraw]
FROM [docSVenum1]
JOIN [docFieldLock]
ON [docFieldLock].[sID] = [docSVenum1].[sID]
AND [docFieldLock].[fieldID] = [docSVenum1].[enumID]
AND [docFieldLock].[lockID] IN (4, 5) /* secLvl docAdm */
JOIN [FTSindexWordOnce]
ON [FTSindexWordOnce].[sID] = [docSVenum1].[sID]
GROUP BY [docSVenum1].[enumID], [docSVenum1].[valueID], [FTSindexWordOnce].[wordID]
except
SELECT [docSVenum1].[enumID], [docSVenum1].[valueID], [FTSindexWordOnce].[wordID]
, avg([FTSindexWordOnce].[tf]) AS [avgTFraw]
FROM [docSVenum1]
JOIN [docFieldLock]
ON [docFieldLock].[sID] = [docSVenum1].[sID]
AND [docFieldLock].[fieldID] = [docSVenum1].[enumID]
AND [docFieldLock].[lockID] IN (4, 5) /* secLvl docAdm */
JOIN [FTSindexWordOnce]
ON [FTSindexWordOnce].[sID] = [docSVenum1].[sID]
GROUP BY [docSVenum1].[enumID], [docSVenum1].[valueID], [FTSindexWordOnce].[wordID]
order by [docSVenum1].[enumID], [docSVenum1].[valueID], [FTSindexWordOnce].[wordID]
In this case tf is term frequency of tf-idf
tf normalization is subjective and does not require much precision
Avg(tf) needs to be consistent from select to select or the results are not consistent
In a single select with joins I need a consistent avg(tf)
Going with decimal and a low precision for tf got consistent results
This is very similiar to: SELECT SUM(...) is non-deterministic when adding the column-values of datatype float.
The problem is that with inaccurate datatype (FLOAT/REAL) the order of of arithmetic operations on floating point matters. Demo from connect:
DECLARE #fl FLOAT = 100000000000000000000
DECLARE #i SMALLINT = 0
WHILE (#i < 100)
BEGIN
SET #fl = #fl + CONVERT(float, 5000)
SET #i = #i + 1
END
SET #fl = #fl - 100000000000000000000
SELECT CONVERT(NVARCHAR(40), #fl, 2)
-- 0.000000000000000e+000
DECLARE #fl FLOAT = 0
DECLARE #i SMALLINT = 0
WHILE (#i < 100)
BEGIN
SET #fl = #fl + CONVERT(float, 5000)
SET #i = #i + 1
END
SET #fl = #fl + 100000000000000000000
SET #fl = #fl - 100000000000000000000
SELECT #fl
-- 507904
LiveDemo
Possible solutions:
CAST all arguments to accurate datatype like DECIMAL/NUMERIC
alter table and change FLOAT to DECIMAL
you can try to force query optimizer to calculate the sum with the same order.
The good news is that when a stable query result matters to your
application, you can force the order to be the same by preventing
parallelism with OPTION (MAXDOP 1).
It looks like intial link is dead. WebArchive
I have coordinates in my database stored as (55.573012889640765, 9.72362365248182). I want to make a function that will get this value and put them on #latitude = 55.573012889640765 and
#long=9.72362365248182.
So practically the function gets the coordinates and returns me the two points separately . I want to get this so I can calculate the distance between two points later on with a function like this one:
CREATE FUNCTION dbo.fnCalcDistanceKM(#lat1 FLOAT, #lon1 FLOAT, #lat2 FLOAT, #lon2 FLOAT)
RETURNS FLOAT
AS
BEGIN
RETURN ACOS(SIN(PI()*#lat1/180.0)*SIN(PI()*#lat2/180.0)+COS(PI()*#lat1/180.0)*COS(PI()*#lat2/180.0)*COS(PI()*#lon2/180.0-PI()*#lon1/180.0))*6371
END
As you see this function requires the points to be separated and in my database I have them as one .
Can you please le me know how to divide the parts or modify the above function to fit my code.
Thank you in advance
You can save yourself some effort by using SQL's geometry functions.
declare #coords1 nvarchar(64) = '(55.573012889640765, 9.72362365248182)'
, #coords2 nvarchar(64) = '(56.573012889640765, 9.72362365248182)'
declare #g1 geometry = geometry::STGeomFromText('POINT' + replace(#coords1,',',' '), 0)
, #g2 geometry = geometry::STGeomFromText('POINT' + replace(#coords2,',',' '), 0)
SELECT #g1.STDistance(#g2);
More info here: http://msdn.microsoft.com/en-us/library/bb933952.aspx
Alternatively, if you're just looking to split the string around the comma, take a look at this example: How do I split a string so I can access item x?
I prefer JonLBevan's answer but this is literally what was asked for:
DECLARE #string nvarchar(max) = '(55.573012889640765, 9.72362365248182)';
SELECT #string;
DECLARE #latitude float;
DECLARE #long float;
SELECT #latitude = CONVERT(float, SUBSTRING(#string, 2, charindex(',', #string)-2)),
#long = CONVERT(float, SUBSTRING(#string, charindex(',', #string)+1, LEN(#string)-charindex(',', #string)-1));
SELECT #latitude, #long;
It is worth pointing out though, that it would be better not to store the values in this way as you are unable to take any advantage of indexing when doing range searches due to the functions involved.
Say I have the latitude and longitude of a city and I need to find out all the airport that are within 100 miles of this location. How would I accomplish this? My data resides in SQL Server. 1 table has all the city info with lat and long and the other has the airport info with lat and long.
First ... convert city's data point
DECLARE #point geography;
SELECT geography::STPointFromText('POINT(' + CAST(#lat AS VARCHAR(20)) + ' ' +
CAST(#lon AS VARCHAR(20)) + ')', 4326)
where #lat and #lon are the latitude and longitude of the city in question.
Then you can query the table ...
SELECT [column1],[column2],[etc]
FROM [table]
WHERE #point.STBuffer(160934.4).STIntersects(geography::STPointFromText(
'POINT(' + CAST([lat] AS VARCHAR(20)) + ' ' +
CAST([lon] AS VARCHAR(20)) + ')', 4326) );
where 160934.4 is the number of meters in 100 miles.
This will be slow, though. If you wanted to do even more spatial work, you could add a persisted computed column (because lat and lon points aren't really going to change) and then use a spatial index.
ALTER TABLE [table]
ADD geo_point AS geography::STPointFromText('POINT(' + CAST([lat] AS VARCHAR(20))
+ ' ' + CAST([lon] AS VARCHAR(20)) + ')', 4326) PERSISTED;
CREATE SPATIAL INDEX spix_table_geopt
ON table(geo_point)
WITH ( BOUNDING_BOX = ( 0, 0, 500, 200 ) ); --you'd have to know your data
I used/wrote this several years ago, and it was close enough for what I needed. Part of the formula takes into account the curvature of the earth if I remember correctly, but it has been a while. I used zip codes, but you could easily adapt for cities instead - same logic.
ALTER PROCEDURE [dbo].[sp_StoresByZipArea] (#zip nvarchar(5), #Radius float) AS
DECLARE #LatRange float
DECLARE #LongRange float
DECLARE #LowLatitude float
DECLARE #HighLatitude float
DECLARE #LowLongitude float
DECLARE #HighLongitude float
DECLARE #istartlat float
DECLARE #istartlong float
SELECT #iStartlat=Latitude, #iStartLong=Longitude from zipcodes where zipcode=#ZIP
SELECT #LatRange = #Radius / ((6076 / 5280) * 60)
SELECT #LongRange = #Radius / (((cos((#iStartLat * 3.141592653589 / 180)) * 6076.) / 5280.) * 60)
SELECT #LowLatitude = #istartlat - #LatRange
SELECT #HighLatitude = #istartlat + #LatRange
SELECT #LowLongitude = #istartlong - #LongRange
SELECT #HighLongitude = #istartlong + #LongRange
/** Now you can create a SQL statement which limits the recordset of cities in this manner: **/
SELECT * FROM ZipCodes
WHERE (Latitude <= #HighLatitude) AND (Latitude >= #LowLatitude) AND (Longitude >= #LowLongitude) AND (Longitude <= #HighLongitude)