How to truncate double precision value in PostgreSQL by keeping exactly first two decimals? - json

I'm trying to truncate double precision value when I'm build json using json_build_object() function in PostgreSQL 11.8 but with no luck. To be more precise I'm trying to truncate 19.9899999999999984 number to ONLY two decimals but making sure it DOES NOT round it to 20.00 (which is what it does), but to keep it at 19.98.
BTW, what I've tried so far was to use:
1) TRUNC(found_book.price::numeric, 2) and I get value 20.00
2) ROUND(found_book.price::numeric, 2) and I get value 19.99 -> so far this is closesest value but not what I need
3) ROUND(found_book.price::double precision, 2) and I get
[42883] ERROR: function round(double precision, integer) does not exist
Also here is whole code I'm using:
create or replace function public.get_book_by_book_id8(b_id bigint) returns json as
$BODY$
declare
found_book book;
book_authors json;
book_categories json;
book_price double precision;
begin
-- Load book data:
select * into found_book
from book b2
where b2.book_id = b_id;
-- Get assigned authors
select case when count(x) = 0 then '[]' else json_agg(x) end into book_authors
from (select aut.*
from book b
inner join author_book as ab on b.book_id = ab.book_id
inner join author as aut on ab.author_id = aut.author_id
where b.book_id = b_id) x;
-- Get assigned categories
select case when count(y) = 0 then '[]' else json_agg(y) end into book_categories
from (select cat.*
from book b
inner join category_book as cb on b.book_id = cb.book_id
inner join category as cat on cb.category_id = cat.category_id
where b.book_id = b_id) y;
book_price = trunc(found_book.price, 2);
-- Build the JSON response:
return (select json_build_object(
'book_id', found_book.book_id,
'title', found_book.title,
'price', book_price,
'amount', found_book.amount,
'is_deleted', found_book.is_deleted,
'authors', book_authors,
'categories', book_categories
));
end
$BODY$
language 'plpgsql';
select get_book_by_book_id8(186);
How do I achieve to keep EXACTLY ONLY two FIRST decimal digits 19.98 (any suggestion/help is greatly appreciated)?
P.S. PostgreSQL version is 11.8

In PostgreSQL 11.8 or 12.3 I cannot reproduce:
# select trunc('19.9899999999999984'::numeric, 2);
trunc
-------
19.98
(1 row)
# select trunc(19.9899999999999984::numeric, 2);
trunc
-------
19.98
(1 row)
# select trunc(19.9899999999999984, 2);
trunc
-------
19.98
(1 row)
Actually I can reproduce with the right type and a special setting:
# set extra_float_digits=0;
SET
# select trunc(19.9899999999999984::double precision::text::numeric, 2);
trunc
-------
19.99
(1 row)
And a possible solution:
# show extra_float_digits;
extra_float_digits
--------------------
3
(1 row)
select trunc(19.9899999999999984::double precision::text::numeric, 2);
trunc
-------
19.98
(1 row)
But note that:
Note: The extra_float_digits setting controls the number of extra
significant digits included when a floating point value is converted
to text for output. With the default value of 0, the output is the
same on every platform supported by PostgreSQL. Increasing it will
produce output that more accurately represents the stored value, but
may be unportable.

As #pifor suggested I've managed to get it done by directly passing trunc(found_book.price::double precision::text::numeric, 2) as value in json_build_object like this:
json_build_object(
'book_id', found_book.book_id,
'title', found_book.title,
'price', trunc(found_book.price::double precision::text::numeric, 2),
'amount', found_book.amount,
'is_deleted', found_book.is_deleted,
'authors', book_authors,
'categories', book_categories
)
Using book_price = trunc(found_book.price::double precision::text::numeric, 2); and passing it as value for 'price' key didn't work.
Thank you for your help. :)

Related

MySQL 8 update with Replace() or Regex

How can i update data using replace or regex-like method from
id | jdata
---------------
01 | {"name1":["number","2"]}
02 | {"val1":["number","12"],"val2":["number","22"]}
to
id | jdata
---------------
01 | {"name1":2 }
02 | {"val1": 12,"val2":22 }
I need to make a proper json entry for numbers and replace an array with a number from that array. Column "jdata" can have any number of similar attributes from the example. Something similar to this would do:
UPDATE table SET jdata = REPLACE(jdata, '["number","%d"]', %d);
Two ways:
The long, more clumsy way, using JSON_ARRAY:
UPDATE table1,
(
SELECT
id,
JSON_EXTRACT(jdata, "$.name1[0]") as A,
JSON_EXTRACT(jdata, "$.name1[1]") as B,
JSON_EXTRACT(jdata, "$.val1[0]") as C,
JSON_EXTRACT(jdata, "$.val1[1]") as D,
JSON_EXTRACT(jdata, "$.val2[0]") as E,
JSON_EXTRACT(jdata, "$.val2[1]") as F
FROM table1
) x
SET jdata = CASE WHEN table1.id=1 THEN JSON_ARRAY("name1",x.B)
ELSE JSON_ARRAY("val1",x.D,"val2",F) END
WHERE x.id=table1.id;
Or using JSON_REPLACE:
update table1
set jdata = JSON_REPLACE(jdata, "$.name1",JSON_EXTRACT(jdata,"$.name1[1]"))
where id=1;
update table1
set jdata = JSON_REPLACE(jdata, "$.val1",JSON_EXTRACT(jdata,"$.val1[1]"),
"$.val2",JSON_EXTRACT(jdata,"$.val2[1]"))
where id=2;
see: DBFIDDLE for both options
EDIT: To get more depth in the query, you can start with below, and create a new JSON message from this stuff without the number:
WITH RECURSIVE cte1 as (
select 0 as x
union all
select x+1 from cte1 where x<10
)
select
id,
x,
JSON_UNQUOTE(JSON_EXTRACT(JSON_KEYS(jdata),CONCAT("$[",x,"]"))) j,
JSON_EXTRACT(jdata,CONCAT("$.",JSON_UNQUOTE(JSON_EXTRACT(JSON_KEYS(jdata),CONCAT("$[",x,"]"))))) v,
JSON_UNQUOTE(JSON_EXTRACT(jdata,CONCAT("$.",JSON_UNQUOTE(JSON_EXTRACT(JSON_KEYS(jdata),CONCAT("$[",x,"]"))),"[0]"))) v1,
JSON_UNQUOTE(JSON_EXTRACT(jdata,CONCAT("$.",JSON_UNQUOTE(JSON_EXTRACT(JSON_KEYS(jdata),CONCAT("$[",x,"]"))),"[1]"))) v2
from table1
cross join cte1
where x<JSON_DEPTH(jdata)
and not JSON_EXTRACT(JSON_KEYS(jdata),CONCAT("$[",x,"]")) is null
order by id,x;
output:
id
x
j
v
v1
v2
1
0
name1
["number", "2"]
number
2
2
0
val1
["number", "12"]
number
12
2
1
val2
["number", "22"]
number
22
This should take care of JSON message which also contains values like val3, val4, etc, until a maximum depth which is now fixed to 10 in cte1.
EDIT2: When it is just needed to remove the "number" from the JSON message, you can also repeat this UPDATE until all "number" tags are removed (you can repeat this in a stored procedure, I am not going to write the stored procedure for you 😉)
update
table1,
( WITH RECURSIVE cte1 as (
select 0 as x
union all
select x+1 from cte1 where x<10
) select * from cte1 )x
set jdata = JSON_REMOVE(table1.jdata, CONCAT("$.",JSON_UNQUOTE(JSON_EXTRACT(JSON_KEYS(jdata),CONCAT("$[",x,"]"))),"[0]"))
where JSON_UNQUOTE(JSON_EXTRACT(jdata,CONCAT("$.",JSON_UNQUOTE(JSON_EXTRACT(JSON_KEYS(jdata),CONCAT("$[",x,"]"))),"[0]"))) = "number"
An example, where I do run the update 2 times, is in this DBFIDDLE

How I can get limit_exceded value if value is less than or greater than in "limit_exceded" column i would get 1 else 0 in mysql

database table_img for better understanding
Pardon me for weak English writing
my sql query that i am getting value,low_value,high_value and limit_exceded_. issue is in limit_exceded to not get correct values, it shows all 0.
SELECT `myvalues`.`value`, `sub_types`.`low_value`, `sub_types`.`high_value`,
(case when myvalues.value > sub_types.low_value and myvalues.value < sub_types.high_value then 1 else 0 end) as limit_exceded
FROM `myvalues`
JOIN `sub_types` ON `myvalues`.`sub_type_id` = `sub_types`.`id`
WHERE `myvalues`.`sub_type_id` IN('68')
AND `myvalues`.`observation_id` IN('455', '471', '470', '469', '468', '467', '466', '465', '462', '461', '460', '459', '458', '457', '456', '372', '453', '373', '376', '439', '440', '441', '442', '443', '445', '446', '447', '448', '452', '454')
I want to get int 1 in front of those values whose value is less than 40 or greater than 180. Also it would it be appreciated if extract max and min from this list and count limit_exceded values
expected result is set value 1 where values is 900,9 and 1 etc
issue is in limit_exceded to not get correct values, it shows all 0
I suspect the issue is the types. The way the data lines up in the image and the use of strings in the WHERE clause suggest that the values are strings not numbers. A simply way to convert is to use + 0:
SELECT v.value, st.low_value, st.high_value,
(v.value + 0) > (st.low_value + 0) and (v.value + 0) < (st.high_value + 0) as limit_exceded
FROM myvalues v JOIN
sub_types st
ON v.sub_type_id = st.id
WHERE v.sub_type_id IN ('68') AND
v.observation_id IN ('455', '471', '470', '469', '468', '467', '466', '465', '462', '461', '460', '459', '458', '457', '456', '372', '453', '373', '376', '439', '440', '441', '442', '443', '445', '446', '447', '448', '452', '454')
Notes:
Table aliases make the query easier to write and to read.
Unnecessary backticks just make the query harder to write and to read.
In MySQL, you don't need the CASE expression -- you can just use the boolean expression.
That said, you should fix the data, not the query. If the values are numbers, store them as numbers, not string.

Is there a ROUNDDOWN() function in sql as there is in EXCEL

Say I have a table which has two columns i.e. Quantity and Percentages where my percentages are in decimals. Now I want to multiply these two columns and Round the value down to 2 decimals. Rounding down here means that all the numbers from 1-9 are rounded down. Is there an inbuilt function in SQL to do so as there is in Excel?
Examples:
13.567 should round to 13.56
136.7834 should round to 136.78
0.7699 should round to 0.76
I have tried searching for such a function online but couldn't come across an appropriate solution.
There's a FLOOR function, which can be adapted to your use case:
SELECT FLOOR(value * 100) / 100 AS RoundedValue
You can use TRUNCATE () for this rounddown
select TRUNCATE(2.847, 2) as rounddown
or
SELECT Floor(135.675); //for integer rounding, like 135
You can also use
select round(123.456, 2, 1) as rounddown
The 3rd parameter being non-zero will cause a truncation after the number of decimal points specified in the 2nd parameter.
DB Fiddle
https://www.ibm.com/support/knowledgecenter/en/SSEPEK_10.0.0/sqlref/src/tpc/db2z_bif_truncate.html
https://www.w3schools.com/sql/func_sqlserver_floor.asp
The solution to the problem is to truncate the extra decimal which can be achieved by using the extra parameter of the ROUND function which is ROUND(number, decimal_places, 0/1). Here if the last parameter is anything other than 0, it will truncate the rather than rounding off which is equivalent to the ROUNDDOWN() function of excel that I was looking for.
Alternatively, you can use the TRUNCATE() function, passing the number of decimal places to keep as the second parameter, which will drop off any extra decimals, acting as a ROUNDDOWN() function.
I hope this rounding utility helps somebody:
CREATE FUNCTION `get_round`(val DOUBLE, nDigits INT, RoundStyle VARCHAR(255)) RETURNS double
NO SQL
BEGIN
DECLARE a DOUBLE DEFAULT 0;
SET nDigits = ifnull(nDigits, 0);
CASE
WHEN UCASE(RoundStyle) IN ('ROUND NEAREST','', 'NEAREST', '', 'RND','ROUND', 'DEFAULT','DFLT', null) THEN #normal rounding, but up from 10.50#
SET a = round(val, nDigits);
WHEN UCASE(RoundStyle) IN('ROUND UP', 'UP') THEN #ROUND 10.554 to 10.56
SET a = ceil(val * (power(10, nDigits) )) / (power(10, nDigits));
WHEN UCASE(RoundStyle) IN('ROUND DOWN', 'DOWN') THEN #ROUND 10.555 to 10.55
SET a = truncate(val, nDigits) ;
WHEN UCASE(RoundStyle) IN('ROUND BANKER', 'BANKER','BANKERS ROUNDING') THEN #ROUND TO THE NEAREST EVEN 10.555 is 10.56 and 10.565 is 10.56
SET a = IF(ABS(val - TRUNCATE(val, nDigits)) * POWER(10, nDigits + 1) = 5
AND NOT CONVERT(TRUNCATE(ABS(val) * POWER(10, nDigits), 0), UNSIGNED) % 2 = 1,
TRUNCATE(val, nDigits), ROUND(val, nDigits));
WHEN UCASE(RoundStyle) IN('ROUND UP INTEGER', 'INT UP','UP INT') THEN #10.4 rounds to 11.0
SET a = ceiling(val);
WHEN UCASE(RoundStyle) IN('ROUND DOWN INTEGER', 'INT DOWN','DOWN INT') THEN #10.6 rounds to 10.0
SET a = floor(val);
END CASE;
RETURN ifnull(a, 0);
END
yes there are some Function in sql for round
ex:
SELECT ProductName, Price, FlOOR(Price) AS RoundedPrice
FROM Products;

Mask integer field in mysql

I need to mask integer field in mysql such that 9999911111 becomes 9900001111. I want to keep first 2 digits and last 4 digits and need to mark rest of the digits as 0 for the integers stored in the field.
I have created a query and it's working but I am not sure whether this is right way to do for integers or not.
update table_name
set field_name=CONCAT(SUBSTR(field_name, 1, 2),
REPEAT('0', CHAR_LENGTH(field_name) - 6),
SUBSTR(field_name, CHAR_LENGTH(field_name)-3, CHAR_LENGTH(field_name)));
Just trying a different approach .
SET #myVar = 344553543534;
SELECT #myVar - (SUBSTRING(#myVar, 4, LENGTH(#myVar) - 7) * 10000) ;
Above mentioned formula will give 344000003534 as the result. Tried with different combination and found it working.
So your query need to change as given below
UPDATE table_name
SET field_name=
(field_name - (SUBSTRING(field_name, 4, LENGTH(field_name) - 7) * 10000));
Explanation :
Consider Number, a = 344553543534;
Expected Result, b = 344000003534;
c = (a - b) = 344553543534 - 344000003534 = 553540000;
Now if you consider the result, c, 55354 is the numbers where masking required, and 0000 indicates the last 4 number to be left open.
So to get masked value, we can use the formula, b = a - c;
So now to get c, used SUBSTRING(a, 4, LENGTH(a) - 7) * 10000
EDIT : To keep only first two numbers, use 3 instead of 4 and 6 instead of 7. I assumed that you needed to keep first 3.
SET #myVar = 344553543534;
SELECT #myVar - (SUBSTRING(#myVar, 3, LENGTH(#myVar) - 6) * 10000) ;

Why use the SQL Server 2008 geography data type?

I am redesigning a customer database and one of the new pieces of information I would like to store along with the standard address fields (Street, City, etc.) is the geographic location of the address. The only use case I have in mind is to allow users to map the coordinates on Google maps when the address cannot otherwise be found, which often happens when the area is newly developed, or is in a remote/rural location.
My first inclination was to store latitude and longitude as decimal values, but then I remembered that SQL Server 2008 R2 has a geography data type. I have absolutely no experience using geography, and from my initial research, it looks to be overkill for my scenario.
For example, to work with latitude and longitude stored as decimal(7,4), I can do this:
insert into Geotest(Latitude, Longitude) values (47.6475, -122.1393)
select Latitude, Longitude from Geotest
but with geography, I would do this:
insert into Geotest(Geolocation) values (geography::Point(47.6475, -122.1393, 4326))
select Geolocation.Lat, Geolocation.Long from Geotest
Although it's not that much more complicated, why add complexity if I don't have to?
Before I abandon the idea of using geography, is there anything I should consider? Would it be faster to search for a location using a spatial index vs. indexing the Latitude and Longitude fields? Are there advantages to using geography that I am not aware of? Or, on the flip side, are there caveats that I should know about which would discourage me from using geography?
Update
#Erik Philips brought up the ability to do proximity searches with geography, which is very cool.
On the other hand, a quick test is showing that a simple select to get the latitude and longitude is significantly slower when using geography (details below). , and a comment on the accepted answer to another SO question on geography has me leery:
#SaphuA You're welcome. As a sidenote be VERY carefull of using a
spatial index on a nullable GEOGRAPHY datatype column. There are some
serious performance issue, so make that GEOGRAPHY column non-nullable
even if you have to remodel your schema. – Tomas Jun 18 at 11:18
All in all, weighing the likelihood of doing proximity searches vs. the trade-off in performance and complexity, I've decided to forgo the use of geography in this case.
Details of the test I ran:
I created two tables, one using geography and another using decimal(9,6) for latitude and longitude:
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)
)
and inserted a single row using the same latitude and longitude values into each table:
insert into GeographyTest(Location) values (geography::Point(47.6475, -122.1393, 4326))
insert into LatLongTest(Latitude, Longitude) values (47.6475, -122.1393)
Finally, running the following code shows that, on my machine, selecting the latitude and longitude is approximately 5 times slower when using 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
Results:
GeographyDuration LatLongDuration
----------------- ---------------
5146 1020
5143 1016
5169 1030
AvgGeographyDuration AvgLatLongDuration
-------------------- ------------------
5152 1022
What was more surprising is that even when no rows are selected, for example selecting where RowId = 2, which doesn't exist, geography was still slower:
GeographyDuration LatLongDuration
----------------- ---------------
1607 948
1610 946
1607 947
AvgGeographyDuration AvgLatLongDuration
-------------------- ------------------
1608 947
If you plan on doing any spatial computation, EF 5.0 allows LINQ Expressions like:
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();
}
Then there is a very good reason to use Geography.
Explanation of spatial within Entity Framework.
Updated with Creating High Performance Spatial Databases
As I noted on Noel Abrahams Answer:
A note on space, each coordinate is stored as a double-precision floating-point number that is 64 bits (8 bytes) long, and 8-byte binary value is roughly equivalent to 15 digits of decimal precision, so comparing a decimal(9,6) which is only 5 bytes, isn't exactly a fair comparison. Decimal would have to be a minimum of Decimal(15,12) (9 bytes) for each LatLong (total of 18 bytes) for a real comparison.
So comparing storage types:
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'
Result:
name rows data
Geo 20000 728 KB
LatLon 20000 560 KB
The geography data-type takes up 30% more space.
Additionally the geography datatype is not limited to only storing a Point, you can also store LineString, CircularString, CompoundCurve, Polygon, CurvePolygon, GeometryCollection, MultiPoint, MultiLineString, and MultiPolygon and more. Any attempt to store even the simplest of Geography types (as Lat/Long) beyond a Point (for example LINESTRING(1 1, 2 2) instance) will incur additional rows for each point, a column for sequencing for the order of each point and another column for grouping of lines. SQL Server also has methods for the Geography data types which include calculating Area, Boundary, Length, Distances, and more.
It seems unwise to store Latitude and Longitude as Decimal in Sql Server.
Update 2
If you plan on doing any calculations like distance, area, etc, properly calculating these over the surface of the earth is difficult. Each Geography type stored in SQL Server is also stored with a Spatial Reference ID. These id's can be of different spheres (the earth is 4326). This means that the calculations in SQL Server will actually calculate correctly over the surface of the earth (instead of as-the-crow-flies which could be through the surface of the earth).
Another thing to consider is the storage space taken up by each method. The geography type is stored as a VARBINARY(MAX). Try running this script:
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'
Result:
name rows data
Geo 20000 728 KB
LatLon 20000 400 KB
The geography data-type takes up almost twice as much space.
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