Issue while adding values in SQL Server - sql-server

Please read again till end (description updated)
I want something like this.
ex :
if (7200 / 42) is float then
floor(7200/42) + [7200 - {(floor(7200/42)) * 42}] / 10 ^ length of [7200 - {(floor(7200/42)) * 42}]
STEP : 1 => 171 + ((7200 - (171*42))/10 ^ len(7200-7182))
STEP : 2 => 171 + ((7200 - 7182)/10 ^ len(18))
STEP : 3 => 171 + (18/10 ^ 2)
STEP : 4 => 171 + (18/100)
STEP : 5 => 171 + 0.18
STEP : 6 => 171.18
I have written the code in SQL which actually works perfectly but the addition of 171 + 0.18 only gives 171
IF I can get "171/18" instead of "171.18" as string then it'd also be great. (/ is just used as separator and not a divison sign)
Following is the code I written
Here,
(FAP.FQTY + FAP.QTY) = 7200,
PRD.CRT = 42
(values only for example)
select
case when PRD.CRT <> 0 then
case when (FAP.FQTY + FAP.QTY)/PRD.CRT <> FLOOR((FAP.FQTY + FAP.QTY)/PRD.CRT) then --DETERMINE WHETHER VALUE IS FLOAT OR NOT
(floor((FAP.FQTY + FAP.QTY)/PRD.CRT)) +
((FAP.FQTY + FAP.QTY) - floor((FAP.FQTY + FAP.QTY)/PRD.CRT) * PRD.CRT) /
POWER(10, len(floor((FAP.FQTY + FAP.QTY) - floor((FAP.FQTY + FAP.QTY)/PRD.CRT) * PRD.CRT)))
else
(FAP.FQTY + FAP.QTY)/PRD.CRT -- INTEGER
end
else
0
end
from FAP inner join PRD on FAP.Comp_Year = PRD.Comp_Year and
FAP.Comp_No = PRD.Comp_No and FAP.Prd_Code = PRD.Prd_Code
I got all the values correct till 171 + 0.1800 correct but after that I am only receiving 171 in the addition. I want exactly 171.18.
REASON FOR THIS CONFUSING CALCULATION
Its all about accounting
Suppose, a box(or a cartoon) has 42 nos. of items.
A person sends 7200 items. how many boxes he has to send?
So that will be (7200/42) = 171.4257.
But boxes cannot be cut (its whole number i.e 171).
so 171 * 42 ie 7182 items.
Remaining items = 7200 - 7182 = 18.
So answer is 171 boxes and 18 items.
In short 171.18 or "171/18"
Please help me with this..
Thank you in advance.

Recognise that you're not producing an actual numeric result, I'd describe it as unhealthy to try to keep it using such a datatype1.
This produces the strings you're seeking, if I've understood your requirement:
;With StartingPoint as (
select 7200 as Dividend, 42 as Divisor
)
select
CONVERT(varchar(10),Quotient) +
CASE WHEN Remainder > 0 THEN '.' + CONVERT(varchar(10),Remainder)
ELSE '' END as FinalString
from
StartingPoint
cross apply
(select Dividend/Divisor as Quotient, Dividend % Divisor as Remainder) t
(Not tested for negative values. Some adjustments may be required. Technically % computes the modulus rather than the remainder, etc)
1Because someone might try and add two of these values together and I doubt that produces a correct result, not even necessarily if using the same Divisor to compute both.

Just another idea about how to calculate it.
Simple calculate the whole boxes.
And concatinate a dot with the remaining items (using a modulus).
Wrapped it all up in a CASE WHEN (or IIF) to avoid the divide by zero.
Example snippet:
declare #TestTable table (FQTY numeric(18,2), QTY numeric(18,2), CRT numeric(18,0));
insert into #TestTable (FQTY,QTY,CRT) values
(5000, 2200, 42),
(5000, 2200, 0),
( 100, 200, 10);
select *,
(CASE
WHEN CRT>0
THEN CONCAT(CAST(FLOOR((FQTY+QTY)/CRT) as INT),'/',CAST((FQTY+QTY)%CRT as INT))
ELSE '0'
END) AS Boxes
from #TestTable;
Result:
FQTY QTY CRT Boxes
------- ------- --- ------
5000.00 2200.00 42 171/18
5000.00 2200.00 0 0
100.00 200.00 10 30/0
The CONCAT returns a varchar, and so does the CASE WHEN.
But you could wrap that CASE WHEN in a CAST.

You're getting an automatic type conversion from int to decimal(10,0) which is probably not what you want.
https://learn.microsoft.com/en-us/sql/t-sql/data-types/int-bigint-smallint-and-tinyint-transact-sql?view=sql-server-2017
Check out the "Caution" box.
If you want a specific amount of precision, you'll need to explicitly cast() the values to the desired data type.

if i understand your logic correctly you want the remainder of 7200 divide by 42 and the remainder is to divide by 100
declare
#dividend int = 7200,
#divisor int = 42
select (#dividend / #divisor)
+ convert(decimal(10,4),
(#dividend % #divisor) * 1.0 / power(10, len(#dividend % #divisor)))
EDIT: change to handle the 10^len(remainder)

Related

Is there a way in SQL server to interpret the underlying varchar(4) bits as an INT?

I have data harvested from a binary file that has been encoded as a SQL column with type varchar(4). This is not changeable. The 4 bytes used to create this varchar need to be interpreted sometimes as an int value (big endian). It would be nice if we could do this entirely inside SQL.
Printing the values in this varchar(4) column is not helpful as most of the bytes get interpreted as unprintable control characters.
I can't figure out how CAST or CONVERT can help since they seem to be tailored to converting a varchar like "0054" to int 54. Instead, I need the underlying bits to be interpreted as an int (big endian)--not the varchar characters as an int.
For example, one record prints this column as no visible characters, but STRING_ESCAPE(#value,'json')
will display
\u0000\u0000\u0000\u0007
This needs to be interpreted somehow to be the int 7
Here's a few more examples of what STRING_ESCAPE returns and what the int value should be:
\u0000\u0000\u0000\b ==> 8
\u0000\u0000\u0000\t ==> 9
\u0000\u0000\u0000\n ==> 10
\u0000\u0000\u0000\u000b ==> 11
\u0000\u0000\u0000\f ==> 12
\u0000\u0000\u0000\r ==> 13
\u0000\u0000\u0000\u000e ==> 14
\u0000\u0000\u0000\u000f ==> 15
\u0000\u0000\u0000\u0010 ==> 16
Thanks for your brain!
So, here is a table of sample data. The first row represents your main example. But you don't have any examples where any one of the first 3 characters is not character 0. So I threw in another row where this is the case.
declare #values table (value char(4))
insert #values values
(char(0) + char(0) + char(0) + char(7)),
(char(13) + char(9) + char(14) + char(8));
In the query below, I isolate each character using substring. Then I call ascii to retrieve the character code. What is not clear, however, is how you would take those integer values and combine them. I give 3 possibilities. 'Option1' concatenates them. 'Option2' sums them together. 'Option3' concatenates them like option1, but pads them first so that there is a leading '0' if it is only one digit long.
select escapedVal = string_escape(value,'json'),
ap.*,
option1 = convert(int,concat(pos1, pos2, pos3, pos4)),
option2 = pos1 + pos2 + pos3 + pos4,
option3 = convert(int,
right('00' + convert(varchar(2),pos1),2) +
right('00' + convert(varchar(2),pos2),2) +
right('00' + convert(varchar(2),pos3),2) +
right('00' + convert(varchar(2),pos4),2)
)
from #values v
cross apply (select
pos1 = ascii(substring(value,1,1)),
pos2 = ascii(substring(value,2,1)),
pos3 = ascii(substring(value,3,1)),
pos4 = ascii(substring(value,4,1))
) ap;
This produces:
escapedVal
pos1
pos2
pos3
pos4
option1
option2
option3
\u0000\u0000\u0000\u0007
0
0
0
7
7
7
7
\r\t\u000e\b
13
9
14
8
139148
44
13091408
CAST(CAST(#value as BINARY(4)) as INT)
The part I was missing is specifying the size of binary as 4. Without the size, it always casts to 0!

Types and rounds in SQL Server

I have the following query:
DECLARE #A as numeric(36,14) = 480
DECLARE #B as numeric(36,14) = 1
select #B/#A
select cast(#B as decimal)/cast(#A as decimal)
Why does the first calculation returns 0.002083 and the second one returns 0.00208333333333333?
IsnĀ“t numeric(36,14) good enough to have a good precision (just as the second query)?
If I use only numeric, instead of numeric(36,14), I have a good precision again:
select cast(#B as numeric)/cast(#A as numeric)
You can calculate precision and scale by yourself using this documentation from SQL Server Books online.
I tried to calculate precision and scale for your case (operation=division, p=36, s=14) and I got a pretty strange results...
precision of the result: [p1 - s1 + s2 + max(6, s1 + p2 + 1)] -> 36-14+14+max(6,14+36+1)=36+51=87
scale of the result : [max(6, s1 + p2 + 1)] -> max(6,14+36+1)=51
In this situation precision is greater than 38 and in this case (as stated in the documentation)
*The result precision and scale have an absolute maximum of 38. When a result precision is greater than 38, the corresponding scale is
reduced to prevent the integral part of a result from being truncated.
scale must be reduced by (87-38=) 49, that is (51-49=) 2 ...
I think that minimum scale length is 6 (because of expression scale=[max(6, s1 + p2 + 1)]) and it can't be reduced lower than 6 - that we have as a result (0.002083).
Just contributing for the understanding of the problem (going deeper on #Andrey answer), the things could be tricky, depending on the order of calculations.
Consider the variables:^
DECLARE #A as NUMERIC(36,19) = 100
DECLARE #B as NUMERIC(36,19) = 480
DECLARE #C as NUMERIC(36,19) = 100
Calculating A/B*C
If you want to calculate A/B*C, using the formulaes, we have:
A/B is of type NUMERIC(38,6) --> as calculated by #Andrey
The result will be 0.208333 (with scale of 6)
Multiplying by 100, we will get 20.833300
Calculating A*C/B
The result of A*C is 10000 of type NUMERIC(38,6). Diving by C, the result will be 20.833333 of type NUMERIC(38,6)
Then, the result may vary depending on the order of calculation (the same problem was pointed in https://dba.stackexchange.com/questions/77664/how-does-sql-server-determine-precision-scale).

creating a histogram using SQL

I am extremely new to SQL and wish to create a histogram which should look as the following - 2 columns, one with "z" values, ranging from 0 to 1, with an interval of 0.01, and the second which will include the number of count(z) per each interval. Visually, it should look something like this:
z | count(z)
-------------
0-0.01| 12312
0.01 - 0.02 | 143565
0.02 - 0.03 | 23445
and so on...
I tried looping, concatinating string and using EXEC, but nothing seems to work :(
the closest I've got to extracting some useful data was by using the following code, which produced a 2D matrix with the first column containing the data and the rest NULL:
DECLARE #i float = 0
WHILE #i < 0.1
BEGIN
exec ('select count(z) as ''' +#i +''' from specObj where z BETWEEN
' +#i +' and (' +#i +'+0.01)')
SET #i = #i + 0.01
END
Thanks
There is no need to loop. Just use some arithmetic in the group by. Here is the basic idea:
select cast(z * 100 as int)/100.0 as range_start, (1 + cast(z * 100 as int))/100.0 as range_end,
count(*)
from table t
group by cast(z * 100 as int);
Actually turning the range start and range end into a string (such as '[0.01-0.02]') requires string manipulations that, alas, depend on the particular database, which is not specified in the question.

ASCII increment with defined range

Client wants to append a field with a literal increment based on a count.
The range goes from 'aa' to 'zz'.
'aa' represents a count of 1 and 'zz' represents the max value in the range: 676
I have sql that almost works but would appreciate an expert eye to get me over the last hurdle.
--Constants
DECLARE #START_ASCII INT = 97
DECLARE #ASCII_OFFSET INT = 1
DECLARE #ALPHABET_LETTER_COUNT INT = 26
--Variables
DECLARE #RecordCount INT = 0
DECLARE #FirstLetter VARCHAR(1) = NULL
DECLARE #SecondLetter VARCHAR(1) = NULL
SET #RecordCount = 1 --Range is 1 to 676 (e.g. 'aa' to 'zz')
SET #FirstLetter = CHAR(round(#RecordCount / #ALPHABET_LETTER_COUNT, 2, 1) + #START_ASCII)
SET #SecondLetter = CHAR((((#RecordCount - #ASCII_OFFSET) % #ALPHABET_LETTER_COUNT) + #START_ASCII))
SELECT #FirstLetter + #SecondLetter
The problem with the above sql involves the first letter. It works till the end of the alphabet is reached for the second letter. For example, at a count of 26, I expect 'az', but instead get 'bz'.
I want to keep the SQL small and tight (e.g. no CASE statements). Is there a small tweak I can make to the above code so that it will work?
Or, if there is just a smarter way to skin this cat, I'd like to know that.
I would think of this as computing the base-26 representation of #RecordCount-1 (range 0 to 675). Then map the two-digits of the base-26 number to the ASCII characters:
SET #FirstLetter = CHAR(floor((#RecordCount-1) / #ALPHABET_LETTER_COUNT) + #START_ASCII)
SET #SecondLetter = CHAR(((#RecordCount-1) % #ALPHABET_LETTER_COUNT) + #START_ASCII)

How to determine the field value which can not convert to (decimal, float,int) in SQL Server

I have a SQL Server database.
One field has values which are like
ID VALUE
1 NEGATIF
2 11.4
3 0.2
4 A RH(+)
5 -----
6 >>>>>
7 5.6<
8 -13.9
I want to CONVERT VALUE field to decimal, of course convert-able fields.
What kind of SQL statement can do this?
How can I understand which value is raising error while converting?
PS: I think this can solve WHERE VALUE LIKE '[a-z]' but how can I add more filter like [-+ ()] ?
Plain ISNUMERIC is rubbish
Empty string, +, - and . are all valid
So is +. etc
1e-3 is valid for float but not decimal (unless you CAST to float then to decimal)
For a particularly cryptic but failsafe solution, append e0 or .0e0 then use ISNUMERIC
SELECT
ISNUMERIC(MyCOl + 'e0') --decimal check,
ISNUMERIC(MyCOl + '.0e0') --integer check
So
SELECT
ID, VALUE,
CAST(
CASE WHEN ISNUMERIC(VALUE + 'e0') = 1 THEN VALUE ELSE NULL END
AS decimal(38, 10)
) AS ConvertedVALUE
FROM
Mytable

Resources