How to replicate MIN and MAX in SQL - sql-server

I have an excel formula that I need to recreate in SQL:
=MIN(150,0.75*MAX(100-0-0/0.9,0))
It can be done with nested case statements, but I am using a program to generate SQL which currently does not support this:
select
case
when 150 <
case
when 0.75*100-0-0/0.9 < 0
THEN 0
ELSE 0.75*100-0-0/0.9
END
THEN 150
ELSE 0.75*100-0-0/0.9
END
Is there another way of doing this without creating a stored procedure?
Please note: I cannot use any new functionality from SQL Server 2012 or onwards.

MIN and MAX in SQL Server don't work with a delimited list; they get to minimum/maximum value for an expression across a dataset. Thus, there's no such functionality as SELECT MIN(ColumnA, ColumnB, ColumnC);, instead you'd have something like:
WITH Numbers AS(
SELECT *
FROM (VALUES (1),(2),(3)) V(N)
)
SELECT MAX(N), MIN(N)
FROM Numbers;
If you need to get the MAXIMUM between columns, then yes, you'll need to use a CASE expression. Using your data, and a bit of giuess work, this results in something like:
WITH Numbers AS(
SELECT *
FROM (VALUES (150,0.75,100-0-0,0.9,0)) V(A,B,C,D,E))
SELECT CASE WHEN 150 > CASE WHEN C/D > E THEN C/D
ELSE E END THEN 150
ELSE CASE WHEN C/D > E THEN C/D
ELSE E END
END AS MaxN
FROM Numbers;
The other alternative, would be to pivot your data, and get the MAX/MIN that way. I've not posted a solution here for that one though, as we only have constants to work with, and I don't want to guess an answer.

Try to use subqueries with UNION ALL
SELECT MIN(Value)
FROM
(
SELECT 150 Value
UNION ALL
SELECT 0.75*MAX(Value)
FROM
(
SELECT 100-0-0/0.9 Value
UNION ALL
SELECT 0
) q
) q
And this query with variables
DECLARE
#value1 float=100-0-0/0.9,
#value2 float=0,
#value3 float=150
SELECT MIN(Value)
FROM
(
SELECT #value3 Value
UNION ALL
SELECT 0.75*MAX(Value)
FROM
(
SELECT #value1 Value
UNION ALL
SELECT #value2
) q
) q

Related

SQL Server 2014: How to convert a VARCHAR column mixed with characters and numbers to corresponding numbers

I have a column called result in SQL Server 2014 which has various kinds of lab test results. The values for result can be characters, numbers (integer or decimals or scientific notations) like this:
positive
negative
not detect
n/a
101
15.3
78.002
-12.1
3.49952E-10
7.3E9
I want to only select those representing numbers, which are...
101
15.3
78.002
-12.1
3.49952E-10
7.3E9
And, I want to convert them into a numeric column with the corresponding values. I also want to get AVG, stdev, min, and max of them.
Can someone help me please?
Thanks a lot!
You could use ISNUMERIC function and CAST it to number
DECLARE #SampleData AS TABLE (Value varchar(30))
INSERT INTO #SampleData
VALUES ('positive'),('negative'),('101'),('15.3'),
('78.002'),('-12.1'),('3.49952E-10'),('7.3E9')
SELECT CAST(sd.[Value] AS float) AS Value
FROM #SampleData sd
WHERE isnumeric(sd.[Value]) = 1
Demo link: Rextester
In SQL Server 2012 and newer, you can also use the TRY_CAST function to try to convert a string to a numeric value - if it fails, it will not crash and burn, but instead just simply return NULL.
Based on that, you could use something like this:
-- define a CTE - an "inline" view which handles the conversion
;WITH CTE AS
(
SELECT NumValue = TRY_CAST(YourColumnName AS FLOAT)
FROM dbo.YourTable
)
-- select only those rows from the CTE that have a non-NULL "NumValue"
SELECT *
FROM CTE
WHERE NumValue IS NOT NULL
You could also use pattern matching by using LIKE operator,
SELECT AVG(NumValue) AS Average
,STDEV(NumValue) AS StDev
,MIN(NumValue) AS Min
,MAX(NumValue) AS Max
FROM
(SELECT CONVERT(FLOAT,YourColumn) AS NumValue
FROM YourTable
WHERE YourColumn LIKE '%[0-9]%') x
This subquery will display any data that has number in it, and would return error if there is alphanumeric data other than exponential notation (i.e 3.49952E-10), in that case you could just specified the pattern after LIKE operator.
by using LIKE operator we can restrict string data
;WITH Cte (TextData)
AS
(
SELECT 'positive' UNION ALL
SELECT 'negative' UNION ALL
SELECT 'not detect' UNION ALL
SELECT 'n/a' UNION ALL
SELECT '101' UNION ALL
SELECT '15.3' UNION ALL
SELECT '78.002' UNION ALL
SELECT '-12.1' UNION ALL
SELECT '3.49952E-10'UNION ALL
SELECT '7.3E9'
)
SELECT *
FROM Cte
WHERE TextData LIKE '%[0-9]%'

Can i use a case statement to convert a varchar to decimal and use that in my where clause?

I have a column which I want to convert to decimal so I can then use it to compare in my where clause. I want to make sure all values from the column are greater or equal to 1.3. I converted the column successfully in the select statement but when attempting to do the same convert in the where clause I get the following error:
Arithmetic overflow error converting varchar to data type numeric.
I am using SQL Server 2008.
SELECT ID,
CASE
WHEN ISNUMERIC(USER_3) = 1
THEN Convert(varchar(50), CONVERT(decimal(14,2), USER_3))
END AS KG_M
FROM PART
WHERE USER_3 IS NOT NULL
AND CASE
WHEN ISNUMERIC(USER_3) = 1
THEN Convert(varchar(50), CONVERT(decimal(14,2), USER_3))
END >= 1.3
Sure, why not? Here's a self-contained example:
select a.ID
, b.KG_M
from (values
(1, N'12345678')
, (2, N'ABCDEFGH')
) as a (ID, USER_3)
cross apply (values(
case IsNumeric(a.USER_3)
when 1 then Convert(varchar(50), Convert(decimal(14, 2), a.USER_3))
else a.USER_3
end
)) as b (KG_M)
where b.KG_M >= '1.3';
We simply use the APPLY operator to contain our calculation for reuse later.
You need to choose one way to convert. I would use the native type for comparison, decimal.
SELECT * FROM
(
SELECT ID, KG_M=CAST(USER_3 AS decimal(14,2))
FROM PART
WHERE
ISNUMERIC(USER_3) = 1
)AS X
WHERE
X.KG_M >= 1.3
Allow strings that are not numbers in outoput
SELECT * FROM
(
SELECT
ID,
USER_3_AsDecimal=CASE WHEN ISNUMERIC(USER_3) THEN CAST(USER_3 AS decimal(14,2)) ELSE NULL END,
USER_3
FROM PART
WHERE
NOT USER_3 IS NULL
)AS X
WHERE
X.USER_3_AsDecimal IS NULL
OR
X.USER_3_AsDecimal >= 1.3
The problem was a syntax error, the case in the where clause was a success the entire time.
"you should use >= '1.3' since you are converting to varchar" credit to #Lamak in comments

T-SQL select value where value contains less than 3 of the declared characters

Im trying to write a select statement which returns the value if it doesnt have at least 3 of the declared characters but I cant think of how to get it working, can someone point me in the right direction?
One thing to consider, I am not allowed to create a temporary table for this exercise.
I havn't really got any SQL so far as I cant think of a way to do it without a temp table.
the declared characters are any alpha characters between a and z, so if the value in the db is '1873' then it would return the value because it doesnt have at least 3 of the declared characters, but if the value was 'abcdefg' then it would not be returned as it has at least 3 of the declared characters.
Is anyone able to point me in a starting direction for this?
This will find all sys.objects with an x or a z:
Some explanations, as this is an exercise and you want to learn something:
You can split a delimitted string by transforming it into XML. x,z comes out as <x>x</x><x>z</x>. You can use this to create a derived table.
I use a CTE to avoid a created or declared table...
You can use CROSS APPLY for row-wise actions. Here I use CHARINDEX to find the position(s) of the chars you are looking for.
If all of them are not found, there SUM is zero. I use GROUP BY and HAVING to check this.
Hope this is clear :-)
DECLARE #chars VARCHAR(100)='x,z';
WITH Splitted AS
(
SELECT A.B.value('.','char') AS TheChar
FROM
(
SELECT CAST('<x>' + REPLACE(#chars,',','</x><x>')+ '</x>' AS XML) AS AsXml
) AS tbl
CROSS APPLY AsXml.nodes('/x') AS A(B)
)
SELECT name
FROM sys.objects
CROSS APPLY (SELECT CHARINDEX(TheChar,name) AS Found FROM Splitted) AS Found
GROUP BY name,Found
HAVING SUM(Found)>0
With
SrcTab As (
Select *
From (values ('Contains x y z')
, ('Contains x and y')
, ('Contains y only')) v (SrcField)),
CharList As ( --< CTE instead of temporary table
Select *
From (values ('x')
, ('y')
, ('z')) v (c))
Select SrcField
From SrcTab, CharList
Group By SrcField
Having SUM(SIGN(CharIndex(C, SrcField))) < 3 --< Count hits
;
If Distinct is not desirable and we need to only check count for each row:
With
SrcTab As ( --< Sample Data CTE
Select *
From (values ('Contains x y z')
, ('Contains x and y')
, ('Contains y only')
, ('Contains y only')) v (SrcField))
Select SrcField
From SrcTab
Where (
Select Count(*) --< Count hits
From (Values ('x'), ('y'), ('z')) v (c)
Where CharIndex(C, SrcField) > 0
) < 3
;
Using Numbers Table and Joins..I used declared characters as only 4 for demo purposes
Input:
12345
abcdef
ab
Declared table:used only 3 for demo..
a
b
c
Output:
12345
ab
Demo:
---Table population Scripts
Create table #t
(
val varchar(20)
)
insert into #t
select '12345'
union all
select 'abcdef'
union all
select 'ab'
create table #declarecharacters
(
dc char(1)
)
insert into #declarecharacters
select 'a'
union all
select 'b'
union all
select 'c'
Query used
;with cte
as
(
select * from #t
cross apply
(
select substring(val,n,1) as strr from numbers where n<=len(val))b(outputt)
)
select val from
cte c
left join
#declarecharacters dc1
on
dc1.dc=c.outputt
group by val
having
sum(case when dc is null then 0 else 1 end ) <3

How to query number based SQL Sets with Ranges in SQL

What I'm looking for is a way in MSSQL to create a complex IN or LIKE clause that contains a SET of values, some of which will be ranges.
Sort of like this, there are some single numbers, but also some ranges of numbers.
EX: SELECT * FROM table WHERE field LIKE/IN '1-10, 13, 24, 51-60'
I need to find a way to do this WITHOUT having to specify every number in the ranges separately AND without having to say "field LIKE blah OR field BETWEEN blah AND blah OR field LIKE blah.
This is just a simple example but the real query will have many groups and large ranges in it so all the OR's will not work.
One fairly easy way to do this would be to load a temp table with your values/ranges:
CREATE TABLE #Ranges (ValA int, ValB int)
INSERT INTO #Ranges
VALUES
(1, 10)
,(13, NULL)
,(24, NULL)
,(51,60)
SELECT *
FROM Table t
JOIN #Ranges R
ON (t.Field = R.ValA AND R.ValB IS NULL)
OR (t.Field BETWEEN R.ValA and R.ValB AND R.ValB IS NOT NULL)
The BETWEEN won't scale that well, though, so you may want to consider expanding this to include all values and eliminating ranges.
You can do this with CTEs.
First, create a numbers/tally table if you don't already have one (it might be better to make it permanent instead of temporary if you are going to use it a lot):
;WITH Numbers AS
(
SELECT
1 as Value
UNION ALL
SELECT
Numbers.Value + 1
FROM
Numbers
)
SELECT TOP 1000
Value
INTO ##Numbers
FROM
Numbers
OPTION (MAXRECURSION 1000)
Then you can use a CTE to parse the comma delimited string and join the ranges with the numbers table to get the "NewValue" column which contains the whole list of numbers you are looking for:
DECLARE #TestData varchar(50) = '1-10,13,24,51-60'
;WITH CTE AS
(
SELECT
1 AS RowCounter,
1 AS StartPosition,
CHARINDEX(',',#TestData) AS EndPosition
UNION ALL
SELECT
CTE.RowCounter + 1,
EndPosition + 1,
CHARINDEX(',',#TestData, CTE.EndPosition+1)
FROM CTE
WHERE
CTE.EndPosition > 0
)
SELECT
u.Value,
u.StartValue,
u.EndValue,
n.Value as NewValue
FROM
(
SELECT
Value,
SUBSTRING(Value,1,CASE WHEN CHARINDEX('-',Value) > 0 THEN CHARINDEX('-',Value)-1 ELSE LEN(Value) END) AS StartValue,
SUBSTRING(Value,CASE WHEN CHARINDEX('-',Value) > 0 THEN CHARINDEX('-',Value)+1 ELSE 1 END,LEN(Value)- CHARINDEX('-',Value)) AS EndValue
FROM
(
SELECT
SUBSTRING(#TestData, StartPosition, CASE WHEN EndPosition > 0 THEN EndPosition-StartPosition ELSE LEN(#TestData)-StartPosition+1 END) AS Value
FROM
CTE
)t
)u INNER JOIN ##Numbers n ON n.Value BETWEEN u.StartValue AND u.EndValue
All you would need to do once you have that is query the results using an IN statement, so something like
SELECT * FROM MyTable WHERE Value IN (SELECT NewValue FROM (/*subquery from above*/)t)

Getting "Subquery returned more than 1 value error message " while using Case statement inside Where Not In

If I do
SELECT * FROM cte1 c
WHERE c.GrantNumber NOT IN
(
SELECT t1.nai_grant_number FROM #temp t1
)
This works fine.
But if I do
SELECT * FROM cte1 c
WHERE c.GrantNumber NOT IN
(
CASE WHEN
#AutoRenewalChk = 1 THEN
(
SELECT t1.nai_grant_number FROM #temp t1
) END
)
getting error
Msg 512, Level 16, State 1, Line 33 Subquery returned more than 1
value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
What is the reason? Cannot we use a case statement like the above?
Thanks
SELECT * FROM cte1 AS c
WHERE NOT EXISTS
(
SELECT 1 FROM #temp AS t1
WHERE t1.nai_grant_number = CASE #AutoRenewalChk
WHEN 1 THEN c.GrantNumber END
);
Here is why NOT IN will not work here. Try these two queries. Would you expect the same result in each case?
SELECT x FROM (SELECT x = 1) AS x WHERE x NOT IN
(SELECT y = 2);
SELECT x FROM (SELECT x = 1) AS x WHERE x NOT IN
(SELECT y = 2 UNION ALL SELECT NULL);
Adding a possible NULL here (which Dan P suggested as an ELSE in his answer, or if #temp can contain even a single row where nai_grant_number is NULL) completely changes the semantics of NOT IN. Therefore just about any time you are thinking about writing a NOT IN query, you should re-think it as a NOT EXISTS (or LEFT OUTER JOIN, or other struct).
Just like the error says, your query iS returning more than one row. If you change your subs select to select top 1... It will work
Try this instead as others have mentioned the CASE When works on a single column or value not a rowset. Assuming c.GrantNumber is never null this should work for you.
SELECT * FROM cte1 c
WHERE c.GrantNumber NOT IN
(
SELECT DISTINCT
CASE WHEN #AutoRenewalChk = 1
THEN t1.nai_grant_number
ELSE NULL
END
FROM #temp t1
)

Resources