enter code hereI asked a question back in May about how to convert a number from a table that inches, such as 300.9 to a Ft' In" display. I got two very good answers...
CONVERT(VARCHAR(20),finlength /12) + '''' + CONVERT(VARCHAR(20),finlength %12)+'"' as FinishLen
replace(replace('<feet>'' <inches>', '<feet>', FinLength / 12), '<inches>', FinLength % 12) as FinishLen
Both worked well until I ran into a table that the inches are declared as "REAL" numbers. Now I ran into this error...
"The data types real and int are incompatible in the modulo operator."
How can I display that? I can't change the table declarations. Other users need that data as well.
Thanks and Kuddos for the great site.
Guess the full query might help, sorry.
SELECT TOP 1000 ProdWkYr
,Product
,Grade
,CONVERT(VARCHAR(20),finlength /12) + '''' + CONVERT(VARCHAR(20),finlength %12)+'"' as FinishLen
,BlmWeight
,BlmsNeeded
,BlmFootWgt
FROM NYS2MiscOrderInfo
where ProdWkYr = 3215
order by product, Grade
Just include a floor() in your expression like
-- -------------------------------------------------------------------
-- set-up some test data using a CTE:
WITH tst as ( SELECT 13.7 finlength UNION ALL SELECT 123 )
-- alternatively: generate a table [tst] with a single column [finlength]
-- -------------------------------------------------------------------
SELECT CONVERT(VARCHAR(20),FLOOR(finlength / 12)) + ''''
+ CONVERT(VARCHAR(20),finlength % 12)+'"' as FinishLen
FROM tst
-- results:
FinishLen
1'1.70"
10'3."
This will turn the first (ft) value into an integer while the second one (in) will still show all the digits after the decimal point.
UPDATE
When I ran the select from a #tmp table I got the same error as OP. I then modified and ended up with this:
It is as ugly as hell now, but at least it works now, see here SQL Demo:
create table #tst (finlength float);
INSERT INTO #tst VALUES (13.7),(123.),(300.9);
SELECT CONVERT(VARCHAR(20),FLOOR(finlength / 12)) + '''' -- ft
+CONVERT(VARCHAR(20),finlength-FLOOR(finlength) -- in: fractional part
+CAST(FLOOR(finlength) as int) %12)+'"' -- in: integer part
as FinishLen
FROM #tst
Please note: The formula will return reasonable results for positive values. For "negative distances" further changes are necessary. If similar output is required in different places then a UDF makes sense here. Something like:
CREATE FUNCTION ftinstr(#v float) RETURNS varchar(32) BEGIN
DECLARE #l int;
SELECT #l=FLOOR(ABS(#v));
RETURN CAST(SIGN(#v)*(#l/12) AS varchar(6))+''''
+CAST( ABS(#v)-#l+#l%12 AS varchar(20))+'"'
END
would do the trick, To be called like dbo.ftinstr( floatval ).
Maybe I can beautify it a little still ...
Related
I have a simple calculation to do in a stored procedure. Depending on the order I put the variables I get a different result
If you copy/past this into SQL Query Analyzer you can easily reproduce the issue where I get a different result. The result I was looking for was the second calculation (57364.32)
DECLARE #mnyDocTotal MONEY;
DECLARE #mnyUSDTotal MONEY
DECLARE #mnyDetailLine MONEY
SET #mnyDocTotal = 78000
SET #mnyUSDTotal = 86046.48
SET #mnyDetailLine = 52000
PRINT 'Result: ' + CAST(ROUND(#mnyDetailLine / #mnyDocTotal * #mnyUSDTotal,2) as char(20))
PRINT 'Result: ' + CAST(ROUND(#mnyDetailLine * #mnyUSDTotal / #mnyDocTotal,2) as char(20))
--Result: 57358.58
--Result: 57364.32
I believe that / and * are on the same level and operate from left to right in this case.
If you run the numbers with a calculator, you will always get 57364.32.
This caused me about 2 hours of effort to figure this out. In all my years I've never had this issue occur. Why is the result different?
This article does a pretty good job explaining why you should not use money.
They are not numeric values. They are stored as integers. And they have rounding problems. So:
(a * b) / c
can product a different result due to rounding from:
(a / c) * b
This is actually true of integers in general, as this simple example illustrates:
select (2 * 4 / 3), (2 / 3 * 4)
If you use numeric, you won't have a problem. Here is a db<>fiddle.
i have transaction codes like
"A0004", "1B2005","20CCCCCCC21"
I need to extract the rightmost number and increment the transaction code by one
"AA0004"----->"AA0005"
"1B2005"------->"1B2006"
"20CCCCCCCC21"------>"20CCCCCCCC22"
in SQL Server 2012.
unknown length of string
right(n?) always number
dealing with unsignificant number of string and number length is out of my league.
some logic is always missing.
LEFT(#a,2)+RIGHT('000'+CONVERT(NVARCHAR,CONVERT(INT,SUBSTRING( SUBSTRING(#a,2,4),2,3))+1)),3
First, I want to be clear about this: I totally agree with the comments to the question from a_horse_with_no_name and Jeroen Mostert.
You should be storing one data point per column, period.
Having said that, I do realize that a lot of times the database structure can't be changed - so here's one possible way to get that calculation for you.
First, create and populate sample table (Please save us this step in your future questions):
DECLARE #T AS TABLE
(
col varchar(100)
);
INSERT INTO #T (col) VALUES
('A0004'),
('1B2005'),
('1B2000'),
('1B00'),
('20CCCCCCC21');
(I've added a couple of strings as edge cases you didn't mention in the question)
Then, using a couple of cross apply to minimize code repetition, I came up with that:
SELECT col,
LEFT(col, LEN(col) - LastCharIndex + 1) +
REPLICATE('0', LEN(NumberString) - LEN(CAST(NumberString as int))) +
CAST((CAST(NumberString as int) + 1) as varchar(100)) As Result
FROM #T
CROSS APPLY
(
SELECT PATINDEX('%[^0-9]%', Reverse(col)) As LastCharIndex
) As Idx
CROSS APPLY
(
SELECT RIGHT(col, LastCharIndex - 1) As NumberString
) As NS
Results:
col Result
A0004 A0005
1B2005 1B2006
1B2000 1B2001
1B00 1B01
20CCCCCCC21 20CCCCCCC22
The LastCharIndex represents the index of the last non-digit char in the string.
The NumberString represents the number to increment, as a string (to preserve the leading zeroes if they exists).
From there, it's simply taking the left part of the string (that is, up until the number), and concatenate it to a newly calculated number string, using Replicate to pad the result of addition with the exact number of leading zeroes the original number string had.
Try This
DECLARE #test nvarchar(1000) ='"A0004", "1B2005","20CCCCCCC21"'
DECLARE #Temp AS TABLE (ID INT IDENTITY,Data nvarchar(1000))
INSERT INTO #Temp
SELECT #test
;WITH CTE
AS
(
SELECT Id,LTRIM(RTRIM((REPLACE(Split.a.value('.' ,' nvarchar(max)'),'"','')))) AS Data
,RIGHT(LTRIM(RTRIM((REPLACE(Split.a.value('.' ,' nvarchar(max)'),'"','')))),1)+1 AS ReqData
FROM
(
SELECT ID,
CAST ('<S>'+REPLACE(Data,',','</S><S>')+'</S>' AS XML) AS Data
FROM #Temp
) AS A
CROSS APPLY Data.nodes ('S') AS Split(a)
)
SELECT CONCAT('"'+Data+'"','-------->','"'+CONCAT(LEFT(Data,LEN(Data)-1),CAST(ReqData AS VARCHAR))+'"') AS ExpectedResult
FROM CTE
Result
ExpectedResult
-----------------
"A0004"-------->"A0005"
"1B2005"-------->"1B2006"
"20CCCCCCC21"-------->"20CCCCCCC22"
STUFF(#X
,LEN(#X)-CASE PATINDEX('%[A-Z]%',REVERSE(#X)) WHEN 0 THEN LEN(#X) ELSE PATINDEX('%[A-Z]%',REVERSE(#X))-1 END+1
,LEN(((RIGHT(#X,CASE PATINDEX('%[A-Z]%',REVERSE(#X)) WHEN 0 THEN LEN(#X) ELSE PATINDEX('%[A-Z]%',REVERSE(#X))-1 END)/#N)+1)#N)
,((RIGHT(#X,CASE PATINDEX('%[A-Z]%',REVERSE(#X)) WHEN 0 THEN LEN(#X) ELSE PATINDEX('%[A-Z]%',REVERSE(#X))-1 END)/#N)+1)#N)
works on number only strings
99 becomes 100
mod(#N) increments
I have SQL query like
SELECT *, dbo.func(#param1, a.point) as fValue
FROM dbo.table AS a
WHERE dbo.func(#param1, a.point) < #param2
When this query is executed only once, everything is fine, but when I have array of input #param1 values let's say, over 100 values, executing and fetching results for every value take s a lot of time.
Is it possible to pass array of #param1 into the query somehow, and receive dataset for all the input values, instead of executing it for each value?
function func() doing some math on 2 values. #param1 and a.point are type of double. and, yeah, a.point - is not an ID, and it is not a unique value.
I know, it should be really easy, but it looks like I'm missing something.
You still need to execute that function 100 times for each row, right? I don't see any shortcuts here.
If you wanted to get them all at once, you could do
SELECT dbo.func(#param1, a.point) as fValue1,
dbo.func(#param2, a.point) as fValue2 ...
or something like that, but looping through them just seems more efficient to me anyway.
I suppose you could use a cursor to retrieve each a.point value once, then act on it 100 times, but that's a lot of coding, and not necessarily a simpler solution.
What exactly does dbo.func() do? Is it possible that you could insert the 100 values into a table structure, and perform that operation on the set of 100 all at once, instead of 1x1 100 times?
As an example, let's say you have this function, which just turns a comma-separated list of float values into a single-column table:
CREATE FUNCTION dbo.ListFloats
(
#List VARCHAR(MAX)
)
RETURNS TABLE
RETURN
(
SELECT i = CONVERT(FLOAT, Item)
FROM
(
SELECT Item = x.i.value('(./text())[1]', 'FLOAT')
FROM
(
SELECT [XML] = CONVERT(XML, '<i>'
+ REPLACE(#List, ',', '</i><i>')
+ '</i>').query('.')
) AS a
CROSS APPLY
[XML].nodes('i') AS x(i)
) AS y
WHERE Item IS NOT NULL
);
GO
Now you should be able to get your floats in a set by simply saying:
SELECT i FROM dbo.ListFloats('1.5, 3.0, 2.45, 1.9');
Taking that a step further, let's say dbo.func() takes these two inputs and says something like:
RETURN (SELECT (#param1 + #param2 / #param2));
Now, I know that you've always been told that modularization and encapsulation are good, but in the case of inline functions, I would suggest you avoid the function that gets this result (again you haven't explained what dbo.func() does, so I'm just guessing this will be easy) and do it inline. So instead of calling dbo.func() - twice for each row, no less - you can just say:
DECLARE
#Param1Array VARCHAR(MAX) = '1.5, 3.0, 2.45, 1.9',
#Param2 FLOAT = 2.0;
WITH x AS
(
SELECT t.point, x.i, fValue = ((x.i + t.point)/t.point)
FROM dbo.[table] AS t
CROSS JOIN dbo.ListFloats(#Param1Array) AS x
)
SELECT point, i, fValue FROM x
--WHERE fValue < #Param2
;
The keys are:
Avoiding processing each parameter individually.
Avoiding the individual calculations off in its own separate module.
Performing calculations as few times as possible.
If you can't change the structure this much, then at the very least, avoid calculating the function twice by writing instead:
;WITH x AS
(
SELECT *, dbo.func(#param1, a.point) as fValue
FROM dbo.table AS a
)
SELECT * FROM x
WHERE fValue < #param2;
If you provide details about the data types, what dbo.func() does, etc., people will be able to provide more tangible advice.
Do you have any indexes on this table? If you have an index on a.point, then you will never hit it using this code, ie will always table scan. This is to do with Search Arguments (you can google this). Example:
If you have table xTable with index on column xColumn, then this:
select colA, colB from xTable where xColumn/2 >= 5
will never use the index, but this probably will:
select colA, colB from xTable where xColumn >=10
So you might need something like this:
WHERE a.point < Otherfunc(#param1, #param2 )
I am working on sql server 2005 and I am taking count from a specific table
SELECT count(StudentIdReference) as studentCount FROM StudentTable
Right now this select statement is returning me result like 2 or 78 or 790. But in future it will grow rapidly and on UI I don't have sufficient space to show the digit like 1000000.
What I want that after 3 digit, I will get the number like 1K or 1.6K, just as we see on stackoverflow.
This would be simpler to be done in the Presentation Layer of your application.
You coud write a user function and do something like this....
CREATE FUNCTION prettyPrint
(#number int)
RETURNS varchar(30)
AS
BEGIN
declare #return varchar(30)
set #return = cast(#number as varchar(3))
if #number > 1000
set #return = ''+ cast((#number/1000) as varchar(3)) + '.' + cast((#number % 1000)/100 as varchar(3)) +'K'
-- here must be more 'exceptions' or change all this about the magic number 1000
return #return
end
select dbo.prettyPrint(1500)
SELECT prettyPrint(count(StudentIdReference)) as studentCount FROM StudentTable
As others have stated you should really be doing this in your Presentation Layer not at the DB, however, this will do it for you:
Declare #StudentCount int,
#StudentCountFormatted varchar(10)
Select #StudentCount = Count(StudentIdReference) as studentCount FROM StudentTable
If #StudentCount > 999
Begin
Select #StudentCountFormatted = Convert(Varchar(10), Convert(numeric(19,1), (#StudentCount/ 1000.00))) + 'K'
End
Else
Begin
Select #StudentCountFormatted = #StudentCount
End
Select #StudentCountFormatted
You need to write your own logic to show such text. There is no built-in method.
I would return the COUNT as-is from SQL Server and leave the formatting up to the UI. This is because:
1) usually easier/performant to do formatting/string manipulation outside of SQL
2) different places in your code using the same query may want to use the data in different ways (maybe not now, but could do in future) so returning the count as-is gives you that flexibility - i.e. won't need 1 version to return the count as an INT and another to return the same as a formatted VARCHAR
You could do it in SQL, but in general I believe in pushing this in to the UI as it's a display/formatting behaviour.
You can always try something like this
SELECT
CASE
WHEN len(cast(count(*) as varchar(10)))< 4 then cast(count(*) as varchar(10))
WHEN len(cast(count(*) as varchar(10)))> 4 and len(cast(count(*)as varchar(10)))< 7
THEN cast(cast(count(*) / 1000.0 as decimal(10,1)) as varchar(10)) + 'k'
ELSE cast(cast(count(*) / 1000000.0 as decimal(10,1)) as varchar(10)) + 'm'
END StudentCount
FROM StudentTable
I need to return two fields from a database concatenated as 'field1-field2'. The second field is an int, but needs to be returned as a fixed length of 5 with leading 0's. The method i'm using is:
SELECT Field1 + '-' + RIGHT('0000' + CAST(Field2 AS varchar),5) FROM ...
Is there a more efficient way to do this?
That is pretty much the way: Adding Leading Zeros To Integer Values
So, to save following the link, the query looks like this, where #Numbers is the table and Num is the column:
SELECT RIGHT('000000000' + CONVERT(VARCHAR(8),Num), 8) FROM #Numbers
for negative or positive values
declare #v varchar(6)
select #v = -5
SELECT case when #v < 0
then '-' else '' end + RIGHT('00000' + replace(#v,'-',''), 5)
Another way (without CAST or CONVERT):
SELECT RIGHT(REPLACE(STR(#NUM),' ','0'),5)
If you can afford/want to have a function in your database you could use something like:
CREATE FUNCTION LEFTPAD
(#SourceString VARCHAR(MAX),
#FinalLength INT,
#PadChar CHAR(1))
RETURNS VARCHAR(MAX)
AS
BEGIN
RETURN
(SELECT Replicate(#PadChar, #FinalLength - Len(#SourceString)) + #SourceString)
END
I would do it like this.
SELECT RIGHT(REPLICATE('0', 5) + CAST(Field2 AS VARCHAR(5),5)
Not necessarily all that "Easier", or more efficient, but better to read. Could be optimized to remove the need for "RIGHT"
If you want to get a consistent number of total strings in the final result by adding different number of zeros, here is a little bit modification (for vsql)
SELECT
CONCAT(
REPEAT('0', 9-length(TO_CHAR(var1))),
CAST(var1 AS VARCHAR(9))
) as var1
You can replace 9 by any number for your need!
BRD