Unit Conversion precision issue - sql

Seeking an solution to the following problem.
In our application we were having an functionality of converting the units as per user selection. e.g . Convert kg to lbs or vice versa based on unit selection by user.
We have a screen where in user can select the unit in which he is providing the value e.g Kg or lbs
We have a table which contains all the formulas for the predefined unit which our system supports e.g 1Kg = 2.20462lbs like so
We fetch the formula from the database and calculate / evaluate the formula with actual values as provided by the user.
Since , user have ability to see any units as per his/her choice. we decided to save the value which user provided on screen in standard unit only in our database for easier maintainablity . in this case let's say we have decided to save in kg only database however , user may have selected 'lbs' on screen , but while saving we do convert the 'lbs' to 'kg' and then we will save to database
while reading from database we do convert to 'lbs' if user intended to see in 'lbs' on screen
Hope till now the implementaton is clear , now the problem to above solution is
Let's assume user have selected value '1' as 'lbs' and saved it
In database we will save in 'kg' so value of '1' is saved as 2.20462 in database
while reading again we will do convert to show in 'lbs' now we get value as 0.999998. this is expected due to precision in formula
But user asking to atleast show the same value what he as entered , in this case '1' but we have to do calculation to convert it back to 'lbs' since we are saving in 'kg' in database
I am seeking an solution to this problem what could be better solution with minimal changes.
Table Definition
Id | UnitConversion
1 | 100
2 | 200
3 | 30
Our application is developed on
Angularjs, webapi and sql server

Just for fun, here is a stripped down version of my conversion utility. Just showing MASS here. By storing the conversion factors as varchar, precision is at the individual record level.
Declare #Map table (MapType varchar(50),MapName varchar(50),MapValue varchar(50))
Insert Into #Map values
('Mass','tonnes (metric)','1000'),
('Mass','tons (US)' ,'907.18474'),
('Mass','tons (UK)' ,'1016.0469088'),
('Mass','stones' ,'6.35029318'),
('Mass','slugs(g-pounds)','14.593903'),
('Mass','Solar masses' ,'1.989e30'),
('Mass','pounds (troy)' ,'0.3732417216'),
('Mass','pounds' ,'0.45359237'),
('Mass','picograms' ,'1e-15'),
('Mass','ounces' ,'0.028349523'),
('Mass','ounces (troy)' ,'0.0311034768'),
('Mass','nanograms' ,'1e-12'),
('Mass','milligrams' ,'1e-6'),
('Mass','micrograms' ,'1e-9'),
('Mass','megatonnes' ,'1e9'),
('Mass','kilotonnes' ,'1e6'),
('Mass','kilograms' ,'1'), --- << Base
('Mass','hundredweights' ,'50.80234544'),
('Mass','hectograms' ,'0.1'),
('Mass','grams' ,'1e-3'),
('Mass','grains' ,'0.00006479891'),
('Mass','femtograms' ,'1e-18'),
('Mass','Earth masses' ,'5.980e24'),
('Mass','decagrams' ,'0.01'),
('Mass','cental' ,'45.359237'),
('Mass','carats (metric)','0.0002')
Declare #Value float = 1
Declare #From varchar(50)= 'kilograms'
Declare #To varchar(50)= 'pounds'
Select #Value * Max(IIF(MapName=#From,cast(MapValue as float),null)) / Max(IIF(MapName=#To,cast(MapValue as float),null))
From #Map
Where MapName in(#From,#To)
Returns
2.20462262184878

At Sql server side choose a finer scale then that you need to return to a client. For example, if you need 5 digits to the right of the decimal point, set scale to 7 for DB column
declare #n decimal(20,5) = 2.20462;
declare #t table (
m decimal(20,7)
);
insert #t(m) values
(1./#n),
(10./#n),
(1000./#n);
select cast(m*#n as decimal(20,5)) as r
from #t;
Note "Decimal and numeric are synonyms and can be used interchangeably." msdn.microsoft.com/en-us/library/ms187746.aspx

Related

Parse a varchar column containing XML like data into row wise

i have a column in table which contain data like XML,i would like to get data in rows.
My table data as-
select printDataColumn from Mytable
It returns value-
<PrintData>
<Line1>.MERCHANT ID: *****4005</Line1>
<Line2>.CLERK ID: ADMIN</Line2>
<Line3>.</Line3>
<Line4>. VOID SALE</Line4>
<Line5>.</Line5>
<Line6>.VISA ************0006</Line6>
<Line7>.ENTRY METHOD: SWIPED</Line7>
<Line8>.DATE: 03/05/2019 TIME: 16:57:20</Line8>
<Line9>.</Line9>
<Line10>.INVOICE: 1551785225020</Line10>
<Line11>.REFERENCE: 1008</Line11>
<Line12>.AUTH CODE: 08354A</Line12>
<Line13>.</Line13>
<Line14>.AMOUNT USD$ 1.14</Line14>
<Line15>. ==========</Line15>
<Line16>.TOTAL USD$ 1.14</Line16>
<Line17>.</Line17>
<Line18>. APPROVED - THANK YOU</Line18>
<Line19>.</Line19>
<Line20>.I AGREE TO PAY THE ABOVE TOTAL AMOUNT</Line20>
<Line21>.ACCORDING TO CARD ISSUER AGREEMENT</Line21>
<Line22>.(MERCHANT AGREEMENT IF CREDIT VOUCHER)</Line22>
<Line23>.</Line23>
<Line24>.</Line24>
<Line25>.</Line25>
<Line26>.x_______________________________________</Line26>
<Line27>. Merchant Signature</Line27>
<Line28>.</Line28>
</PrintData>
but i want to use this information in another way like that
MERCHANT ID: *****4005
CLERK ID: ADMIN
SALE
AMEX ***********1006
ENTRY METHOD: CHIP
DATE: 03/07/2019 TIME: 14:37:23
INVOICE: 1551949638173
REFERENCE: 1005
AUTH CODE: 040749. . . . .and so on.
any help is appreciable.
Besides the fact, that it always is a good idea to use the appropriate type to store your data, you can use a cast on the fly to use your xml-like-data with XML methods:
DECLARE #tbl TABLE(ID INT IDENTITY,PrintData VARCHAR(4000));
INSERT INTO #tbl VALUES
('<PrintData>
<Line1>.MERCHANT ID: *****4005</Line1>
<Line2>.CLERK ID: ADMIN</Line2>
<Line3>.</Line3>
<Line4>. VOID SALE</Line4>
<!-- more lines -->
</PrintData>');
SELECT t.ID
,A.Casted.value(N'(/PrintData/Line1/text())[1]','nvarchar(max)') AS Line1
FROM #tbl t
CROSS APPLY(SELECT CAST(t.PrintData AS XML)) A(Casted);
In this case I use CROSS APPLY to add a column A.Casted to the result set, which is a row-wise casted XML.
This will break, in cases of invalid XML (of course). You might try TRY_CAST instead. This would return NULL, but will hide data errors...
Some more background
The cast to XML is a rather expensive operation. Doing this whenever you want to read out of your data is some heavy load for your server. Furthermore, using VARCHAR is prone to two major errors:
If there are foreign characters you might get question marks
If the XML is not valid, you will not see it - until you use it.
If possible, try to change the table's design to use native XML.
And one more hint
It is a bad approach to name-number elements (same for columns). Instead of <Line1><Line2><Line3> better use <Line nr="1"><Line nr="2"><Line nr="3">...

Given a point, how can I query SQL Server to find the stored polygons that encompass it?

Given a point, how can I query SQL Server to find the stored polygons that encompass it?
I have a database table that has the polygons of all 50 US states. I need a query that will allow me to search for which states are within 90 miles of that point.
Here is my table structure and data for three states:
CREATE TABLE dbo.States (
State varchar(20) NOT NULL,
CoordinatorEmail varchar(255) NULL,
Borders geography(-1) NULL
);
INSERT INTO States (State, Borders)
VALUES ('Alaska', (geography::STGeomFromText('POLYGON((-141.0205 70.0187,-141.7291 70.1292,-144.8163 70.4515,-148.4583 70.7471,-151.1609 70.7923,-152.6221 71.1470,-153.9954 71.1185,-154.8853 71.4307,-156.7529 71.5232,-157.9449 71.2796,-159.6313 71.2249,-161.8671 70.6363,-163.5809 70.0843,-165.2399 69.3028,-166.8768 69.1782,-168.0414 68.3344,-165.9155 67.6844,-164.6082 67.2933,-164.0149 66.7789,-165.7507 66.5810,-167.5745 66.2867,-168.9862 66.0269,-168.9478 65.4970,-167.4756 65.0420,-167.0142 64.3922,-165.7343 64.0554,-163.2294 64.0193,-162.1143 63.9615,-163.6029 63.6877,-165.3717 63.4530,-166.3715 62.4133,-166.9867 61.6534,-166.4429 60.8556,-167.8381 60.5357,-167.7118 59.5482,-165.8002 59.4115,-164.5972 59.3696,-162.8558 59.1168,-162.5427 58.1185,-160.6421 58.1359,-159.5050 58.0285,-158.8953 57.6336,-159.9060 56.9090,-160.6531 56.3926,-161.8835 56.2342,-162.9822 55.7240,-164.3994 55.2478,-165.3168 54.7753,-167.1075 54.1463,-168.5852 53.5632,-169.9146 53.1402,-169.5959 52.5964,-168.2227 52.9089,-162.7734 54.2139,-159.1452 54.6786,-155.4634 55.6567,-152.1400 57.3510,-150.8203 59.2209,-147.4461 59.7695,-145.9850 60.3521,-144.1544 59.8917,-141.6811 59.8172,-140.5124 59.5225,-138.8548 59.0292,-136.8526 57.9032,-136.0725 56.9157,-134.9794 56.1555,-134.0057 55.3237,-133.6418 54.6341,-130.6261 54.7135,-129.9930 55.2869,-130.0108 55.9869,-130.1083 56.1057,-131.5887 56.6086,-132.8755 57.8404,-133.8423 58.7276,-134.9121 59.3108,-135.4724 59.8020,-136.3445 59.6039,-136.8251 59.1619,-137.6079 59.2441,-139.2119 60.0902,-139.0938 60.3575,-140.0056 60.1866,-140.9999 60.3059,-141.0205 70.0187,-141.0205 70.0187))', 4326)));
INSERT INTO States (State, Borders)
VALUES ('Alabama', (geography::STGeomFromText('POLYGON((-88.1955 35.0041,-85.6068 34.9918,-85.1756 32.8404,-84.8927 32.2593,-85.0342 32.1535,-85.1358 31.7947,-85.0438 31.5200,-85.0836 31.3384,-85.1070 31.2093,-84.9944 31.0023,-87.6009 30.9953,-87.5926 30.9423,-87.6256 30.8539,-87.4072 30.6745,-87.3688 30.4404,-87.5240 30.1463,-88.3864 30.1546,-88.4743 31.8939,-88.1021 34.8938,-88.1721 34.9479,-88.1461 34.9107,-88.1955 35.0041))', 4326)));
INSERT INTO States (State, Borders)
VALUES ('Arkansas', (geography::STGeomFromText('POLYGON((-94.0416 33.0225,-91.2057 33.0075,-91.1989 33.1180,-91.1041 33.1824,-91.1343 33.3053,-91.1646 33.4211,-91.2263 33.4337,-91.2524 33.5403,-91.1797 33.6112,-91.2524 33.6855,-91.1261 33.6946,-91.1412 33.7883,-91.0451 33.7700,-91.0341 33.8328,-91.0863 33.9399,-90.9256 34.0208,-90.9036 34.0856,-90.9586 34.1345,-90.9132 34.1675,-90.8501 34.1380,-90.9325 34.2311,-90.6935 34.3446,-90.5603 34.4409,-90.5548 34.5348,-90.5768 34.5959,-90.5301 34.7213,-90.5328 34.7574,-90.4546 34.8780,-90.3529 34.8454,-90.2911 34.8690,-90.3104 35.0255,-90.2843 35.1154,-90.1772 35.1323,-90.1112 35.1985,-90.1524 35.2826,-90.1332 35.4383,-90.0206 35.5579,-89.9780 35.6740,-89.9547 35.7287,-89.6594 35.9169,-89.6883 35.9658,-89.7130 36.0013,-90.3735 35.9958,-90.2664 36.1268,-90.0934 36.2875,-90.0742 36.3892,-90.1511 36.4180,-90.1566 36.4997,-94.6198 36.4986,-94.4412 35.3801,-94.4893 33.6318,-94.4522 33.6421,-94.4000 33.5597,-94.2462 33.5883,-94.1885 33.5872,-94.0375 33.5345,-94.0430 33.4314,-94.0430 33.0213,-94.0416 33.0225))', 4326)));
I have been trying the following query just to make sure I can get the state of the point (without yet worrying about the 90 mile radius), but I haven't figure this part out yet.
DECLARE #LittleRock geography;
SET #LittleRock = geography::Point(34.742000, -92.276543, 4326);
Select State from States
where States.Borders.STIntersects(#LittleRock) = 1;
SELECT State from States
where States.Borders.STContains(#LittleRock) = 1;
Neither STIntersets() nor STContains() returns anything. Thoughts?
When I ran your code on my local instance, I got the following error (abbreviated):
This operation cannot be completed because the instance is not valid.
Use MakeValid to convert the instance to a valid instance.
Next, I did what the robot told me to do:
UPDATE dbo.States
SET Borders = Borders.MakeValid();
Once that was done, I was able to determine that Little Rock is indeed in Arkansas with both STIntersects() and STContains(). So there was something malformed in at least one of your geography instances.
EDIT
Given the OPs full data set, it has another issue with some of the states. Specifically, 21 states appear to have a ring orientation problem. With Geography polygons, the order in which you specify the vertexes is important. I never remember whether clockwise or counter-clockwise is the right direction. But I do remember the heuristic I use. If the envelope angle for the polygon is greater than 90°, I probably got it wrong. Luckily, that is also easy to correct for.
UPDATE s
SET s.Borders = s.Borders.ReorientObject()
FROM dbo.States AS s
WHERE s.Borders.EnvelopeAngle() > 90
With the raw data set, your Little Rock query returns 22 states. With the update above run, it returns only Arkansas.

Extracting number from the line of text if the number is not present make it null

I have data which has a number which i need to extract the number is typically followed by "sh-" and should return a null if doesnt have any 'sh-' in it.
the sample of data i have is like this :
Tnsf from sh-849116 Act-13383.38 Unam-13383.38 04/12/12
Tnsf from sh-849116 Act() Unam() 04/12/12
System added, to accommodate the fact that the commencement date was not at the beginning of a quarter.Tnsf from sh-849116 Act() Unam() 04/12/12
RECLASSED PAYMENT IN SAP TO CAR WASH MATERIAL CODE. COULDN'T DELETE FORECOURT PAYMENT SINCE IT WAS ALREADY PAID OUT.
AMO CHANGE. SEE FILE. CAS 5.21.10*Tnsf from sh-849116 Act-12451.20 Unam-12451.20 04/12/12*
The output should be:
84916
84916
84916
null
84916
Thanking you in advance
I have edited it
This will get you the number following the sh-:
DECLARE #string VARCHAR(255) = 'Tnsf from sh-849116 Act-13383.38 Unam-13383.38 04/12/12'
SELECT SUBSTRING(#string,CHARINDEX('sh-',#string)+3,CHARINDEX(' ',#string,CHARINDEX('sh-',#string))-CHARINDEX('sh-',#string)-3)
In your query I would add a CASE statement if sh- is not on every row:
SELECT CASE WHEN CHARINDEX('sh-',StringField) > 0
THEN SUBSTRING(StringField,CHARINDEX('sh-',StringField)+3,CHARINDEX(' ',StringField,CHARINDEX('sh-',StringField))-CHARINDEX('sh-',StringField)-3)
END
FROM YourTable
Declare #string nvarchar(250)
select #string='Tnsf from sh-849116 Act-13383.38 Unam-13383.38 04/12/12'
SELECT SUBSTRING(#string,CHARINDEX('sh-',#string)+3,6)

Geography data type vs. Geometry data type in SQL Server

Environment: SQL Server 2012
I'm using an online tool, the only one I could find so far, to plot polygons and points on the earth. http://www.birdtheme.org/useful/googletool.html
I have two tables. One stores "areas" as polygons and the other table stores points amongst other things irrelevant to my question.
For simplicity, I'll just reduce my scenario to sql variables.
In the query below, I'm using the geography data type for well known points of interest.
I drew a polygon around Robben Island, a point in Robben Island and a point in Alcatraz.
DECLARE #robben_island geography = ('POLYGON((18.351803 -33.788421,18.382788 -33.787494,18.386736 -33.820515,18.354464 -33.822369,18.351803 -33.788421))')
DECLARE #point_in_robben_island geography= ('POINT(18.369226 -33.80554)')
DECLARE #point_in_alcatraz geography= ('POINT(-122.423401 37.827006)')
SELECT #robben_island.STContains(#point_in_robben_island) --returns 'False', but it's not what I expected
SELECT #robben_island.STContains(#point_in_alcatraz) --returns 'True', but it's not what I expected
This query above, if I understand it correctly, tells me that my #point_in_robben_island is not contained in #robben_island, rather my #point_in_alcatraz exists in #robben_island which as we all know, is not true.
Now when I change the data types from geography to geometry, everything works fine, but I'm afraid that if I continue using the geometry data type I might come across a few gotchas. I'm just wondering if I won't be negatively affected by fact that geometry doesn't quite account for earth's curvature. touch wood.
DECLARE #robben_island geometry = ('POLYGON((18.351803 -33.788421,18.382788 -33.787494,18.386736 -33.820515,18.354464 -33.822369,18.351803 -33.788421))')
DECLARE #point_in_robben_island geometry= ('POINT(18.369226 -33.80554)')
DECLARE #point_in_alcatraz geometry= ('POINT(-122.423401 37.827006)')
SELECT #robben_island.STContains(#point_in_robben_island) --returns 'True' as it should
SELECT #robben_island.STContains(#point_in_alcatraz) --returns 'False' as it should
Now my question is, why does the geography data type return unexpected results while geometry works as expected? Thank you very much.
The geography type is a little bit more restrictive than geometry. It can't cross different hemispheres and the outer ring must be drawn counter-clockwise.
Unfortunately (some find this a good thing), SQL Server 2012 no longer throws an error when you create the invalid geography. You need to invert the order of the points in the Roben Island geometry, like:
DECLARE #robben_island geography = ('POLYGON((18.351803 -33.788421, 18.354464 -33.822369,18.386736 -33.820515, 18.382788 -33.787494, 18.351803 -33.788421))')
DECLARE #point_in_robben_island geography= ('POINT(18.369226 -33.80554)')
DECLARE #point_in_alcatraz geography= ('POINT(-122.423401 37.827006)')
SELECT #robben_island.STContains(#point_in_robben_island) --returns 'True'
SELECT #robben_island.STContains(#point_in_alcatraz) --returns 'False'

Update colum with value from another column + text

I imported an e-store's database values into my own, and it mostly worked out fine. However, there were no image file names. So, I need to update the entire database- over 6,000 records, so that under 'image' we get a path + model name + jpg, so each product can be associated with an image. Im having trouble mixing the dynamic column value with the static path. Here is what I need to accomplish:
UPDATE `store`.`pr_product`
SET `image` = 'data/products/`**model_no**`.jpg'
WHERE `pr_product`.`product_id` = `pr_product`.`product_id` ;
But cannot get it to recognize the dynamic nature of 'model_no'
Thanks in advance
Max,
Please what you means about dynamic nature of 'model_no'?
Is this column's datatype int or long or varchar
Please need more explaination with example
you can test the following if e.g model_no is column in pr_product table
UPDATE store.pr_product
SET image = 'data/products/'+pr_product.model_no+'.jpg'
WHERE pr_product.product_id = pr_product.product_id ;
Best Regards,
Mohammed Thabet Zaky