Store Operators like >= OR <= in Microsoft Access - sql-server

I have certain values that are needed for validation in Forms, either they look like Value X >= 0 but it could also be X <= 0, it depends on what operator should be used. How can I store such a value?
(I use MS SQL Server + Access as Frontend)
I basicly wanna store the Value and if it needs to be bigger than or smaller than.

Store the value, as usual, in a field, and the operator in another field as Short Text.
The you can use Eval:
Result = Eval("" & [ValueField] & [OperatorField] & "0")

You can store your value as it is, and to check if this value is positive or not you have two ways
First one
Create a computed column to check the Value column as
CREATE TABLE YourTable(
YourValue INT,
IsPositive AS CASE WHEN YourValue < 0 THEN 0 ELSE 1 END
);
INSERT INTO YourTable (YourValue) VALUES
(1), (-1);
SELECT *
FROM YourTable;
Second one
Use a CASE expression (or even you can create a view) as
SELECT CASE WHEN YourValue < 0 THEN 0 ELSE 1 END IsPositive,
--...
FROM YourTable;

Related

SQL Server CHOOSE() function behaving unexpectedly with RAND() function

I've encountered an interesting SQL server behaviour while trying to generate random values in T-sql using RAND and CHOOSE functions.
My goal was to try to return one of two given values using RAND() as rng. Pretty easy right?
For those of you who don't know it, CHOOSE function accepts in an index number(int) along with a collection of values and returns a value at specified index. Pretty straightforward.
At first attempt my SQL looked like this:
select choose(ceiling((rand()*2)) ,'a','b')
To my surprise, this expression returned one of three values: null, 'a' or 'b'. Since I didn't expect the null value i started digging. RAND() function returns a float in range from 0(included) to 1 (excluded). Since I'm multiplying it by 2, it should return values anywhere in range from 0(included) to 2 (excluded). Therefore after use of CEILING function final value should be one of: 0,1,2. After realising that i extended the value list by 'c' to check whether that'd be perhaps returned. I also checked the docs page of CEILING and learnt that:
Return values have the same type as numeric_expression.
I assumed the CEILINGfunction returned int, but in this case would mean that the value is implicitly cast to int before being used in CHOOSE, which sure enough is stated on the docs page:
If the provided index value has a numeric data type other than int,
then the value is implicitly converted to an integer.
Just in case I added an explicit cast. My SQL query looks like this now:
select choose(cast(ceiling((rand()*2)) as int) ,'a','b','c')
However, the result set didn't change. To check which values cause the problem I tried generating the value beforehand and selecting it alongside the CHOOSE result. It looked like this:
declare #int int = cast(ceiling((rand()*2)) as int)
select #int,choose( #int,'a','b','c')
Interestingly enough, now the result set changed to (1,a), (2,b) which was my original goal. After delving deeper in the CHOOSE docs page and some testing i learned that 'null' is returned in one of two cases:
Given index is a null
Given index is out of range
In this case that would mean that index value when generated inside the SELECT statement is either 0 or above 2/3 (I'm assuming that negative numbers are not possible here and CHOOSE function indexes from 1). As I've stated before 0 should be one of possibilities of:
ceiling((rand()*2))
,but for some reason it's never 0 (at least when i tried it 1 million+ times like this)
set nocount on
declare #test table(ceiling_rand int)
declare #counter int = 0
while #counter<1000000
begin
insert into #test
select ceiling((rand()*2))
set #counter=#counter+1
end
select distinct ceiling_rand from #test
Therefore I assume that the value generated in SELECT is greater than 2/3 or NULL. Why would it be like this only when generated in SELECT statement? Perhaps order of resolving CAST, CELING or RAND inside SELECT is different than it would seem? It's true I've only tried it a limited number of times, but at this point the chances of it being a statistical fluctuation are extremely small. Is it somehow a floating-point error? I truly am stumbled and looking forward to any explanation.
TL;DR: When generating a random number inside a SELECT statement result set of possible values is different then when it's generated before the SELECT statement.
Cheers,
NFSU
EDIT: Formatting
You can see what's going on if you look at the execution plan.
SET SHOWPLAN_TEXT ON
GO
SELECT (select choose(ceiling((rand()*2)) ,'a','b'))
Returns
|--Constant Scan(VALUES:((CASE WHEN CONVERT_IMPLICIT(int,ceiling(rand()*(2.0000000000000000e+000)),0)=(1) THEN 'a' ELSE CASE WHEN CONVERT_IMPLICIT(int,ceiling(rand()*(2.0000000000000000e+000)),0)=(2) THEN 'b' ELSE NULL END END)))
The CHOOSE is expanded out to
SELECT CASE
WHEN ceiling(( rand() * 2 )) = 1 THEN 'a'
ELSE
CASE
WHEN ceiling(( rand() * 2 )) = 2 THEN 'b'
ELSE NULL
END
END
and rand() is referenced twice. Each evaluation can return a different result.
You will get the same problem with the below rewrite being expanded out too
SELECT CASE ceiling(( rand() * 2 ))
WHEN 1 THEN 'a'
WHEN 2 THEN 'b'
END
Avoid CASE for this and any of its variants.
One method would be
SELECT JSON_VALUE ( '["a", "b"]' , CONCAT('$[', FLOOR(rand()*2) ,']') )

If the sum of the whole column is zero, then change the value of each row in SQL Server

I have a column named Percentage from a temporary table info, which if the sum of the whole column is zero, I want to change the value of each row to 0.5, and if the statement is false just leave it that way.
I have tried to do the following:
select case when Percentage = 0 then replace(Percentage, 0,0.5) end from ##info
I know the above will change the fields whenever they are 0 regardless of the sum of the whole column, and if I do:
select case when sum(Percentage) = 0 then replace(Percentage, 0,0.5) end from ##info
it would throw an error.
Another thing is that I want to replace the temporary table with that column and the above code would only give me the actual column without replacing the original column.
Any suggestions are appreciated
If you want to select it:
SELECT
CASE
WHEN (SELECT SUM(Percentage) FROM ##info) = 0 THEN .5
ELSE Percentage
END
FROM ##info
If you want to update the values in the table:
UPDATE ##info
SET Percentage = .5
WHERE (SELECT SUM(Percentage) FROM ##info) = 0
Please find below answer.
declare #v_val numeric(12,2), #v_new_val numeric(12,2)
set #v_val = 0.00
set #v_new_val = 100
select case when #v_val ='0.00' then #v_new_val else #v_val end

Can I write a conditional AND to test for "ISNUMERIC" and then CAST a string to an int in T-SQL?

I thought (hoped) this would work but it doesn't, it evaluates both sided of the AND statement even if the left side is going to be false:
SELECT PropertyNumber
FROM Properties
WHERE PropertyNumber = '203a'
AND
(
NOT PropertyNumber LIKE '[^0-9]' AND CONVERT(INT,PropertyNumber) > 0
)
So I get:
Conversion failed when converting the nvarchar value '203a' to data type int.
Is there anyway to do conditional AND or any other way to solve this problem?
You can do this in two steps:
SELECT
OtherField, CAST(PropertyNumber AS INT) AS PropertyNumber
FROM
(SELECT OtherField, PropertyNumber FROM Properties WHERE ISNUMERIC(PropertyNumber) = 1) AS X
This will discard records with non-numeric property numbers. If you want to keep them...
SELECT
P.OtherField, P.PropertyNumber AS PropertyNumberAsText, CAST(X.PropertyNumber AS INT) AS PropertyNumberAsInt
FROM
Properties AS P
LEFT JOIN (SELECT DISTINCT PropertyNumber FROM Properties WHERE ISNUMERIC(PropertyNumber) = 1) AS X ON P.PropertyNumber = X.PropertyNumber
Because ISNUMERIC() approves of some values which cannot be cast to INT, I tend to use something like this:
CASE WHEN Field IN ('-', '.') THEN NULL
WHEN Field LIKE '%,%' THEN NULL
WHEN ISNUMERIC(Field) = 1 THEN CAST(Field AS INT) END
Note that this will reject values like "1,234", which is a valid integer in some regions. Code to identify and handle all possible formats for numeric values is a separate question, and has probably been asked on SO before.
try this:
SELECT PropertyNumber
FROM #Temp
WHERE PropertyNumber = '203a'
AND
(
case When IsNumeric('-' + PropertyNumber + 'e0') = 1
Then Convert(Int, PropertyNumber)
Else NULL
End > 0
)
The trick here is to use the knowledge that IsNumeric will return 1 only under certain conditions. By putting a '-' sign in front of your data, you are effectively making sure that property numbers are positive. By putting 'e0' after the column name, you are preventing floating point values.

AND/OR based on variable value in stored procedures

I would like to use AND/OR between the conditions in a stored procedure, and the decision is dependent on the parameter value whether it was 0 (AND) or 1 (OR)
Can anyone help me with this please, i guess this is an easy thing to do but i can't seem to figure it out. Thanks
The easiest way (on first glance) would be to concatenate the query string using dynamic SQL, but dynamic SQL has its issues.
See The Curse and Blessings of Dynamic SQL for an in-depth explanation.
So I would try to avoid dynamic SQL, which is no big deal if your queries are not too complex.
The easiest way is just to fire two different queries depending on the parameter value:
CREATE PROCEDURE spTest
#AndOr bit
AS
BEGIN
if #AndOr = 0 begin
select * from YourTable where foo = 1 and bar = 2
end
else begin
select * from YourTable where foo = 1 or bar = 2
end
END
This is of course an example with a very simple query.
If you have lots of queries, or if your queries are very complex, this might not be the best solution because it forces you to duplicate all queries...but as always, it depends :-)
You can implement your logic on a CASE statement. Something like this:
CREATE PROCEDURE dbo.MySP #OrAnd BIT
AS
BEGIN
SELECT *
FROM MyTable
WHERE CASE WHEN Condition1 AND Condition2 AND #OrAnd = 0 THEN 1
WHEN (Condition1 OR Condition2) AND #OrAnd = 1 THEN 1 ELSE 0 END = 1
END
If you convert the simple conditions' boolean results into numeric ones (0 or 1), you will be able to use your parameter in the following way:
(
(CASE WHEN condition1 THEN 1 ELSE 0 END ^ #AndOr)
&
(CASE WHEN condition2 THEN 1 ELSE 0 END ^ #AndOr)
) ^ #AndOr = 1
Here #AndOr is your parameter, ^ is the Transact-SQL bitwise exclusive OR operator, & stands for the bitwise AND in Transact-SQL, and the CASE expressions are used to convert the boolean results into 0 or 1.
If #AndOr = 0 (which means we want AND between the conditions), the above expression effectively boils down to this:
case1 & case2 = 1
because X XOR 0 yields X and so neither individual values of case1 and case2 nor the entire result of the & operator are not affected by the ^ operators. So, when #AndOr is 0, the result of the original expression would be equivalent to the result of condition1 AND condition2.
Now, if #AndOr = 1 (i.e. OR), then every ^ operator in the expression returns the inverted value of its left operand, in other words, negates the left operand, since 1 XOR 1 = 0 and 0 XOR 1 = 1. Therefore, the original expression would essentially be equivalent to the following:
¬ (¬ case1 & ¬ case2) = 1
where ¬ means negation. Or, converting it back to the booleans, it would be this:
NOT (NOT condition1 AND NOT condition2)
According to one of De Morgan's laws,
(NOT A) AND (NOT B) = NOT (A OR B)
Applying it to the above condition, we get:
NOT (NOT condition1 AND NOT condition2) = NOT (NOT (condition1 OR condition2)) =
= condition1 OR condition2
So, when #AndOr is 1, the expression given in the beginning of my answer is equivalent to condition1 OR condition2. Thus, it works like expected based on the value of #AndOr.
Having the input parameter you can use a IF clause to make different selects.
If input parameter = 0 make the AND conditions, otherwise make the OR conditions.
I can't see any particular elegant way to do it. So here's the straightforward approach
create function myfun (#parm1 int, #parm2 int, #andor int) returns int
begin
if (#andor = 0 AND #parm1 = 99 AND #parm2 = 99) return 1
else if (#andor = 1 AND (#parm1 = 99 OR #parm2 = 99)) return 1
return 0
end
go
select dbo.myfun(99,98,0) -- AND condition should return 0
select dbo.myfun(99,98,1) -- OR condition should return 1
select dbo.myfun(98,98,0) -- AND condition should return 0
select dbo.myfun(98,98,1) -- OR condition shoujld return 0

Conversion failed when converting the nvarchar to int

I have a field which is varchar and contains numbers and dates as strings. I want to update all numbers in this field that is greater than 720. I have attempted firstly to do a select but I get this error:
Conversion failed when converting the nvarchar value '16:00' to data type int.
This is my query:
select id, case(isnumeric([other08])) when 1 then [other08] else 0 end
from CER where sourcecode like 'ANE%' --and other08 > 720
It fails when I uncomment the last part.
I am trying to get all numerics greater than 720, but I can't do the comaprison. It also fails when casting and converting.
Thanks all for any help
You also need to perform the checks and conversion in the WHERE clause:
SELECT
id,
CASE WHEN isnumeric([other08]) = 1 THEN CAST([other08] AS INT) ELSE 0 END
FROM CER
WHERE sourcecode LIKE 'ANE%'
AND CASE WHEN isnumeric([other08]) = 1 THEN CAST([other08] AS INT) ELSE 0 END > 720
You need to use IsNumeric in your where clause, to avoid trying to compare strings to the number 720. Eg:
select id, case(isnumeric([other08])) when 1 then [other08] else 0 end
from CER
where sourcecode like 'ANE%' and ISNUMERIC(other08) = 1 and other08 > 720
EDIT
As #Abs pointed out, the above approach won't work. We can use a CTE to compute a reliable field to filter on, however:
WITH Data AS (
select id
, case WHEN isnumeric([other08]) THEN CAST([other08] AS int) else 0 end AS FilteredOther08
, CER.*
from CER
where sourcecode like 'ANE%'
)
SELECT *
FROM Data
WHERE [FilteredOther08] > 720

Resources