SELECT with CASE fail when aggregate function is used - sql

I have the following t-sql query.
DECLARE #Test TABLE(
Points INT
,PointsOf INT
)
INSERT INTO #Test
VALUES (3,12),(2,12),(3,12),(11,12),(12,12),(5,12),(0,12)
DECLARE #Decimal TINYINT = 2
SELECT
CASE #Decimal
WHEN 0 THEN CAST(CAST(SUM(Points) AS DECIMAL(18,0)) / NULLIF(SUM(PointsOf), 0) * 100 AS DECIMAL(18,0))
WHEN 1 THEN CAST(CAST(SUM(Points) AS DECIMAL(18,1)) / NULLIF(SUM(PointsOf), 0) * 100 AS DECIMAL(18,1))
WHEN 2 THEN CAST(CAST(SUM(Points) AS DECIMAL(18,2)) / NULLIF(SUM(PointsOf), 0) * 100 AS DECIMAL(18,2))
END AS Score
FROM #Test
I have an variable #Decimals. When the variable is 0 I need my query to return score in XX format, when its 1 in XX.X format and when its 2 in the XX.XX format.
What happen here is that the CASE enter multiple THEN clauses. When the query above is executed i get 44.86 as result which is correct, but when i change the #Decimals valiable to 0 i get a result 44.00 which is incorect. Its suppose to return just 44 without decimals. The same thing happen when i have #Decimals at 1, it returns 44.90 when it has to be 44.9.
Does any one know why this happen?

As jarlh quite rightly points out the case statement is converting this to the smallest possible data type to hold all the result types. (See the MSDN documentation, specifically the Return Types section)
From my point of view, this is merely a display issue. You want to display 44.9 and not 44.90 (note the trailing zero) when the #decimal variable is set to one.
One way to do this is to add an additional cast to varchar. It's not pretty and not something I would recommend doing but if you insist on doing formatting and UI type things in SQL Server then what can you do?
DECLARE #Test TABLE(
Points INT
,PointsOf INT
)
INSERT INTO #Test
VALUES (3,12),(2,12),(3,12),(11,12),(12,12),(5,12),(0,12)
DECLARE #Decimal TINYINT = 1
SELECT
CASE #Decimal
WHEN 0 THEN cast(Cast(CAST(SUM(Points) AS DECIMAL(18,0)) / NULLIF(SUM(PointsOf), 0) * 100 as decimal(18,0)) as varchar(10))
WHEN 1 THEN cast(Cast(CAST(SUM(Points) AS DECIMAL(18,1)) / NULLIF(SUM(PointsOf), 0) * 100 as decimal(18,1))as varchar(10))
WHEN 2 THEN cast(Cast(CAST(SUM(Points) AS DECIMAL(18,2)) / NULLIF(SUM(PointsOf), 0) * 100 as decimal(18,2))as varchar(10))
END AS Score
FROM #Test

I don't know how MS Server works, but according to the ANSI SQL spec, a column always has one specific data type, which in this case is decimal(20,2), i.e. the smallest data type that can store all of the case's different result types.

Related

Select numbers with more than 4 decimal places

I have a SQL table with one float column populated with values like these:
1.4313
3.35
2.55467
6.22456
3.325
I need to select rows containing only values with more than 4 decimals. In this case, the select must return:
2.55467
6.22456
Ideas? Thanks!
This is what I have tried so far
select *
from table
where CAST(LATITUDE AS DECIMAL(10,5)) - LATITUDE = 0
DECLARE #tbl TABLE (val float)
INSERT INTO #tbl SELECT 1234.567
INSERT INTO #tbl SELECT 1234.5678
INSERT INTO #tbl SELECT -1234.5678
INSERT INTO #tbl SELECT 1234.56789
SELECT *
from #tbl
where (((val*10000) - CONVERT(INT,(val*10000))) <> 0)
Why cant we make it simple by this query:-
SELECT * FROM table WHERE val LIKE '%.____%'
This selects what we want
Another solution also:
SELECT * from table
where (round(value,2) - round(value,4) <> 0)
Given answers did not work for me with MaxDb, but this did:
where FLOOR(value * 10000) != value * 10000
Source
Reduce/Increase 0`s for less/more precision.
This works on Postgres 11:
SELECT * FROM mytable WHERE mycolumn != ROUND(mycolumn::numeric,2)
http://sqlfiddle.com/#!3/abadc/3/0
Seems like something like this should work...
all it does is convert the number to an integer to drop off decimals after multiplying it * 10 to power of decimals you need then it compares that int version of the number to the base number after it too was multiplied by 10 to the power of # of decimals.
If the numbers don't match, then you have decimals beyond 4. If they do match, then it was 4 or fewer.
Select *
from foo
where cast(myNum*power(10,4) as int) <> myNum*power(10,4)
Please try something like:
select * from table
where RIGHT(CAST(value as DECIMAL(10,5)), value), 1) != 0
SELECT *
FROM table WHERE
(abs(val)*100000)%10 <> 0
It's an older question but it checks out.
select val
from table
where ((val * 100) % 1) > 0
Change 100 to your precision.
You can multiply it with 10000 and subtract it from the original number replacing . with ''.
Fiddle
select * from tablename
where replace(numcolumn,'.','') - numcolumn * 10000 > 0
Below is the Code that will check the precision for 4 decimal places:
Replace MyNum, with column you are checking for precision
Replace MyTbl, with the table you are using
Replace 4, with whatever precision you are checking for
Sql:
SELECT MyNum
, LEN(CAST (MyNum AS CHAR))
, -------1. length of decimal number, after conversion to CHAR
CHARINDEX('.', CAST (MyNum AS CHAR))
, ---2.length of numbers after the '.'
LEN(CAST (MyNum AS CHAR)) - CHARINDEX('.', CAST (MyNum AS CHAR)) -----subtracting 1-2, to get the length of numbers after decimal point '.'
FROM MyTbl
WHERE LEN(CAST(MyNum AS CHAR)) - CHARINDEX('.', CAST(MyNum AS CHAR)) > 4; --checking if there are more than 4 numbers after the decimal point '.'
Cast the number as text
Split the text using '.' as separator
Use the 2nd index and apply a length
Filter
--i.e. with postgreSQL.
--1)
select data_numeric, length(arr[2]) as str_length
from (
select data_numeric, regexp_split_to_array(data_numeric::text, '\.') as arr from TABLE
) temp;
--2)
with t1 as (
select data_numeric, regexp_split_to_array(data_numeric::text, '\.') as arr from TABLE
), t2 as (
select data_numeric, arr[2] as decimals, length(arr[2]) as length from t1
)
select * from t2;
Select numbers with more than 2 decimal places:
I had an example, where ((abs(val)*100) and CONVERT(INT,(abs(val)*100)) for value "2.32" in float type column returned two different values.
(abs(2.32)*100) = 232
CONVERT(INT,(abs(2.32)*100)) = 231
That caused wrong select query answers in case for comparing to 0.
I suppose that MSSQL CONVERT() function round numbers in such way that for some float number cases, posted solution would not work.
Here is how I did it for more than 2 decimal places:
DECLARE #tbl TABLE (val float)
INSERT INTO #tbl SELECT 2.32
INSERT INTO #tbl SELECT 1234.54
INSERT INTO #tbl SELECT 1234.545
INSERT INTO #tbl SELECT 1234.5456
INSERT INTO #tbl SELECT 1234.54567
select * from #tbl where abs(val-round((val),2)) > 0.001
You can use the scale function, since postgresql 9.6.
select LATITUDE FROM TABLE where scale(LATITUDE) > 4;
If your data type is float you will get SQL Error [42883]: ERROR: function scale(double precision) does not exist. Below would fix it.
select LATITUDE FROM TABLE where scale(cast(LATITUDE as numeric)) > 4;
This is the simplest solution, Use WITH (NOLOCK) if it's necessary in your case otherwise you can remove it. This will return the records having at least 4 decimal points in ColumnName table.
SELECT * FROM TableName WITH (NOLOCK) WHERE ColumnName LIKE '%.____'

SQL - Divide a value and make sure it re-sums to original

I need to split an invoice line into (in this instance) 3 parts (each with 2 decimal places), but I have ensure that the sum of the splits adds up to the original value.
For example: if I split 5.13 by 3%, 42% and 55% (each rounded to 2DP) I end up with:
0.15
2.82
2.15
Sum = 5.12.
The only way I can figure out how to do it is to use a case statement and split to 2DP on all but the last row.
For the last row, sum all the previous values and subtract this from the original value. This approach requires a few sub-selects. I'm sure there's a better way to do it, but drawing a blank.
(FYI, it's a 3rd party vendor product, so I have to start with a MONEY and return a insert into a MONEY type column and all values must be 2DP
Here's my example:
DECLARE
#CurrExtPrice money = 5.13
DECLARE #tGLSplit AS TABLE
(
ID INT IDENTITY(1,1)
,GLCode NVARCHAR(50)
,PercentSplit DECIMAL(18, 2)
)
INSERT INTO #tGLSplit
([GLCode], [PercentSplit])
Select
'Split1', 0.03
UNION ALL
Select
'Split2', 0.55
UNION ALL
Select
'Split3', 0.42
-- Source Date
SELECT * FROM #tGLSplit [tgs]
SELECT
#CurrExtPrice AS OriginalValue
,tg.[PercentSplit]
,CAST(#CurrExtPrice * tg.[PercentSplit] AS DECIMAL(18,2)) AS Split2DP
,CASE
WHEN tg.ID < (SELECT MAX(ID) FROM #tGLSplit)
THEN
CAST(#CurrExtPrice * tg.[PercentSplit] AS DECIMAL(18,2))
ELSE
(SELECT #CurrExtPrice - SUM(CAST(#CurrExtPrice * [PercentSplit] AS decimal(18,2))) FROM #tGLSplit WHERE ID < (SELECT MAX(ID) FROM #tGLSplit) )
END AS NewSplitValue
FROM #tGLSplit tg
Anyone have a magic algorithm?
TIA
Mark
This was successful for me. Rounding to the 1000's at a line level seemed to produce a sum that worked when rounded to the 100's. Cleanup and modify to your specific needs.
IF OBJECT_ID('tempdb..#Temp') IS NOT NULL DROP TABLE #Temp
GO
DECLARE #Total MONEY = 5.13;
;WITH
SplitData AS
(
SELECT .03 AS SplitPercent
UNION
SELECT .42
UNION
SELECT .55 AS SplitPercent
),
Percents AS
(
SELECT S.SplitPercent,
#Total AS FullMoneyValue,
#Total * S.SplitPercent AS Total,
ROUND(#Total * S.SplitPercent,3,0) AS RoundedTotal
FROM SplitData AS S
)
SELECT P.*
INTO #Temp
FROM Percents AS P
SELECT SUM(P.Total), ROUND(SUM(P.RoundedTotal),2,0)
FROM #Temp AS P

parsing an integer field in sql

I want to split every digit in an integer field in an sql table . example of that would be:
financialNb = 7869
i need the 7 as first digit, 8 as second, 6 as third and 9 as fourth. this is needed because I want every digit to be used as 1 data field in a crystal report?
First you have to convert or cast numbers into text before you can manipulate them in this way. The functions you are looking for are CAST() and SUBSTRING(). To get the numbers to start from the right, you can use REVERSE().
Try this example:
SELECT 7869 AS field1
INTO #tmp
SELECT SUBSTRING(REVERSE(CAST(field1 AS VARCHAR(255))),8,1) AS [Column8]
,SUBSTRING(REVERSE(CAST(field1 AS VARCHAR(255))),7,1) AS [Column7]
,SUBSTRING(REVERSE(CAST(field1 AS VARCHAR(255))),6,1) AS [Column6]
,SUBSTRING(REVERSE(CAST(field1 AS VARCHAR(255))),5,1) AS [Column5]
,SUBSTRING(REVERSE(CAST(field1 AS VARCHAR(255))),4,1) AS [Column4]
,SUBSTRING(REVERSE(CAST(field1 AS VARCHAR(255))),3,1) AS [Column3]
,SUBSTRING(REVERSE(CAST(field1 AS VARCHAR(255))),2,1) AS [Column2]
,SUBSTRING(REVERSE(CAST(field1 AS VARCHAR(255))),1,1) AS [Column1]
FROM #tmp
DROP TABLE #tmp
Presumably your question is about parsing 7869. Using the substring function for this:
select substring(cast(<col> as char(4)), 1, 1) as FirstChar,
substring(cast(<col> as char(4)), 2, 1) as SecondChar,
substring(cast(<col> as char(4)), 3, 1) as ThirdChar,
substring(cast(<col> as char(4)), 4, 1) as FourthChar
from YourTable
I might be interpreting the ordering of the digits incorrectly, and this assumes the strings are always 4 digits and that you want characters. An alternative way is just to look at this as numbers:
select <col%10 as FirstNum,
(col/10) %10 as SecondNum,
(col/100)%10 as ThirdNum,
(col/1000)%10 as FourthNum
You can use the modulo operator (%) to get the digits of an integer, without using the slower string manipulation functions, like this
select
value % 100000000 / 10000000,
value % 10000000 / 1000000,
value % 1000000 / 100000,
value % 100000 / 10000,
value % 10000 / 1000,
value % 1000 / 100,
value % 100 / 10,
value % 10
from testData
Here is a SQL Fiddle to play with.
If the field is always 4 numbers long, you can get away with:
select
value / 1000 as Thousands,
value % 1000 / 100 as Hundreds,
value % 100 / 10 as Tens,
value % 10 as Units
from testData
If, however, you need to use it on arbitrary numbers, you could create a user-defined table valued function, that will return the digits in a table, like this:
create function dbo.getDigits(#Input int)
returns #Digits table
(
Digit int,
Position int
)
as begin
declare #pos int
declare #digit int
set #pos = 0
if #input = 0
begin
-- zero is just a single zero digit at position 1
insert into #digits values (0,1)
return
end
while #input<>0 begin
set #pos=#pos+1
set #digit = #input % 10
set #input = #input / 10
insert into #digits values (#digit, #pos)
end
return
end
and use it like this:
SELECT td.ID, td.Value, d.Digit, d.Position
FROM testData td
CROSS APPLY dbo.getDigits(td.Value) AS d
order by td.ID, d.Position Desc
(Here's another SQL Fiddle, based on the previous one)

Sql Server display decimals only if they are bigger than 0?

I have this select: select isnull(Pricea,0)-isnull(Priceb,0) as Differences
The format of the columns is decimal(12,4).
My question is: I could somehow to return decimals only if they are bigger than 0?
It seems to be confusing if the result will be for e.g 4.0000 so I would want to display the decimals only if they are bigger than 0. Is this possible?
When ceiling(Num) = floor(Num), the number is a integer
select case when ceiling(Num) = floor(Num)
then CONVERT(varchar, CAST(Num as decimal))
else CONVERT(varchar, Num)
end
It's just the kind of beeing displayed in MangementStudio for the Datatypes.
For just adapting the display kind you could do something like
Declare #a table (a decimal(12,4),b decimal(12,4))
insert into #a Values(12.45,10.45)
insert into #a Values(12.45,10.4512)
insert into #a Values(12.4512,10.4500)
Select Cast(Case when a-b<>Floor(a-b) then Cast(a-b as float) else a-b end as Varchar(30)) as Diff
from #a

Generate random int value from 3 to 6

Is it possible in Microsoft SQL Server generate random int value from Min to Max (3-9 example, 15-99 e.t.c)
I know, I can generate from 0 to Max, but how to increase Min border?
This query generate random value from 1 to 6. Need to change it from 3 to 6.
SELECT table_name, 1.0 + floor(6 * RAND(convert(varbinary, newid()))) magic_number
FROM information_schema.tables
Added 5 sec later:
SELECT table_name, 3.0 + floor(4 * RAND(convert(varbinary, newid()))) magic_number
FROM information_schema.tables
A helpful editor added the 'Select' before each statement but the point of this item is that it can generate unique keys for each row in a return, not just one item (For that I would us the Rand() function).
For example:
Select top 100 Rand(),* from tblExample
Would return the same random value for all 100 rows.
While:
Select top 100 ABS(CHECKSUM(NEWID()) % 10),* from tblexample
Would return a different random value between 0 and 9 on each row in the return.
So while the select makes it easier to copy and paste, you can copy the logic into a select statement if that is what is required.
This generates a random number between 0-9
SELECT ABS(CHECKSUM(NEWID()) % 10)
1 through 6
SELECT ABS(CHECKSUM(NEWID()) % 6) + 1
3 through 6
SELECT ABS(CHECKSUM(NEWID()) % 4) + 3
Dynamic (Based on Eilert Hjelmeseths Comment - thanks to jiraiya for providing the visual presentation)
SELECT ABS(CHECKSUM(NEWID()) % (#max - #min + 1)) + #min
Updated based on comments:
NEWID generates random string (for each row in return)
CHECKSUM takes value of string and creates number
modulus (%) divides by that number and returns the remainder (meaning max value is one less than the number you use)
ABS changes negative results to positive
then add one to the result to eliminate 0 results (to simulate a dice roll)
I see you have added an answer to your question in SQL Server 2008 you can also do
SELECT 3 + CRYPT_GEN_RANDOM(1) % 4 /*Random number between 3 and 6*/
FROM ...
A couple of disadvantages of this method are
This is slower than the NEWID() method
Even though it is evaluated once per row the query optimiser does not realise this which can lead to odd results.
but just thought I'd add it as another option.
You can do this:
DECLARE #maxval TINYINT, #minval TINYINT
select #maxval=24,#minval=5
SELECT CAST(((#maxval + 1) - #minval) *
RAND(CHECKSUM(NEWID())) + #minval AS TINYINT)
And that was taken directly from this link, I don't really know how to give proper credit for this answer.
Here is the simple and single line of code
For this use the SQL Inbuild RAND() function.
Here is the formula to generate random number between two number (RETURN INT Range)
Here a is your First Number (Min) and b is the Second Number (Max) in Range
SELECT FLOOR(RAND()*(b-a)+a)
Note: You can use CAST or CONVERT function as well to get INT range number.
( CAST(RAND()*(25-10)+10 AS INT) )
Example:
SELECT FLOOR(RAND()*(25-10)+10);
Here is the formula to generate random number between two number (RETURN DECIMAL Range)
SELECT RAND()*(b-a)+a;
Example:
SELECT RAND()*(25-10)+10;
More details check this: https://www.techonthenet.com/sql_server/functions/rand.php
Simply:
DECLARE #MIN INT=3; --We define minimum value, it can be generated.
DECLARE #MAX INT=6; --We define maximum value, it can be generated.
SELECT #MIN+FLOOR((#MAX-#MIN+1)*RAND(CONVERT(VARBINARY,NEWID()))); --And then this T-SQL snippet generates an integer between minimum and maximum integer values.
You can change and edit this code for your needs.
Nice and simple, from Pinal Dave's site:
http://blog.sqlauthority.com/2007/04/29/sql-server-random-number-generator-script-sql-query/
DECLARE #Random INT;
DECLARE #Upper INT;
DECLARE #Lower INT
SET #Lower = 3 ---- The lowest random number
SET #Upper = 7 ---- One more than the highest random number
SELECT #Random = ROUND(((#Upper - #Lower -1) * RAND() + #Lower), 0)
SELECT #Random
(I did make a slight change to the #Upper- to include the upper number, added 1.)
In general:
select rand()*(#upper-#lower)+#lower;
For your question:
select rand()*(6-3)+3;
<=>
select rand()*3+3;
SELECT ROUND((6 - 3 * RAND()), 0)
Lamak's answer as a function:
-- Create RANDBETWEEN function
-- Usage: SELECT dbo.RANDBETWEEN(0,9,RAND(CHECKSUM(NEWID())))
CREATE FUNCTION dbo.RANDBETWEEN(#minval TINYINT, #maxval TINYINT, #random NUMERIC(18,10))
RETURNS TINYINT
AS
BEGIN
RETURN (SELECT CAST(((#maxval + 1) - #minval) * #random + #minval AS TINYINT))
END
GO
DECLARE #min INT = 3;
DECLARE #max INT = 6;
SELECT #min + ROUND(RAND() * (#max - #min), 0);
Step by step
DECLARE #min INT = 3;
DECLARE #max INT = 6;
DECLARE #rand DECIMAL(19,4) = RAND();
DECLARE #difference INT = #max - #min;
DECLARE #chunk INT = ROUND(#rand * #difference, 0);
DECLARE #result INT = #min + #chunk;
SELECT #result;
Note that a user-defined function thus not allow the use of RAND(). A workaround for this (source: http://blog.sqlauthority.com/2012/11/20/sql-server-using-rand-in-user-defined-functions-udf/) is to create a view first.
CREATE VIEW [dbo].[vw_RandomSeed]
AS
SELECT RAND() AS seed
and then create the random function
CREATE FUNCTION udf_RandomNumberBetween
(
#min INT,
#max INT
)
RETURNS INT
AS
BEGIN
RETURN #min + ROUND((SELECT TOP 1 seed FROM vw_RandomSeed) * (#max - #min), 0);
END