How to find a value, in which Range that it lies without using Between or Comparison operator in expression - sql

I have a Table
Low High Item
0 45 A
45.01 75.24 B
75.24 108.00 C
108..01 122.00 D
Example : If My input is 30 should find in which range does it lies and return the corresponding Item i.e, A ( without using Between or Comparison operator in expression)

Assuming the ranges are non-overlapping:
select top 1 t.*
from table t
order by (#input - low)*(high - #input) desc;
The expression (#input - low)*(high - #input) should be positive only for values in the range. The descending sort will put them first.
This is using SQL Server syntax. Other databases might use limit or something else.

Why not this
Qyery 1 :
Select *
From Table1
Where 30 > Low AND 30 < High
SQL FIDDLE DEMO
Qyery 2 :
Select *
From Table1
Where 50.02 Between Low and high
SQL FIDDLE DEMO
Query 3 :
select t.*
from table1 t
where high - 99.9 > 0 and Low - 99.99 < 0
SQL FIDDLE DEMO
Hope this help you

Related

WHILE Window Operation with Different Starting Point Values From Column - SQL Server [duplicate]

In SQL there are aggregation operators, like AVG, SUM, COUNT. Why doesn't it have an operator for multiplication? "MUL" or something.
I was wondering, does it exist for Oracle, MSSQL, MySQL ? If not is there a workaround that would give this behaviour?
By MUL do you mean progressive multiplication of values?
Even with 100 rows of some small size (say 10s), your MUL(column) is going to overflow any data type! With such a high probability of mis/ab-use, and very limited scope for use, it does not need to be a SQL Standard. As others have shown there are mathematical ways of working it out, just as there are many many ways to do tricky calculations in SQL just using standard (and common-use) methods.
Sample data:
Column
1
2
4
8
COUNT : 4 items (1 for each non-null)
SUM : 1 + 2 + 4 + 8 = 15
AVG : 3.75 (SUM/COUNT)
MUL : 1 x 2 x 4 x 8 ? ( =64 )
For completeness, the Oracle, MSSQL, MySQL core implementations *
Oracle : EXP(SUM(LN(column))) or POWER(N,SUM(LOG(column, N)))
MSSQL : EXP(SUM(LOG(column))) or POWER(N,SUM(LOG(column)/LOG(N)))
MySQL : EXP(SUM(LOG(column))) or POW(N,SUM(LOG(N,column)))
Care when using EXP/LOG in SQL Server, watch the return type http://msdn.microsoft.com/en-us/library/ms187592.aspx
The POWER form allows for larger numbers (using bases larger than Euler's number), and in cases where the result grows too large to turn it back using POWER, you can return just the logarithmic value and calculate the actual number outside of the SQL query
* LOG(0) and LOG(-ve) are undefined. The below shows only how to handle this in SQL Server. Equivalents can be found for the other SQL flavours, using the same concept
create table MUL(data int)
insert MUL select 1 yourColumn union all
select 2 union all
select 4 union all
select 8 union all
select -2 union all
select 0
select CASE WHEN MIN(abs(data)) = 0 then 0 ELSE
EXP(SUM(Log(abs(nullif(data,0))))) -- the base mathematics
* round(0.5-count(nullif(sign(sign(data)+0.5),1))%2,0) -- pairs up negatives
END
from MUL
Ingredients:
taking the abs() of data, if the min is 0, multiplying by whatever else is futile, the result is 0
When data is 0, NULLIF converts it to null. The abs(), log() both return null, causing it to be precluded from sum()
If data is not 0, abs allows us to multiple a negative number using the LOG method - we will keep track of the negativity elsewhere
Working out the final sign
sign(data) returns 1 for >0, 0 for 0 and -1 for <0.
We add another 0.5 and take the sign() again, so we have now classified 0 and 1 both as 1, and only -1 as -1.
again use NULLIF to remove from COUNT() the 1's, since we only need to count up the negatives.
% 2 against the count() of negative numbers returns either
--> 1 if there is an odd number of negative numbers
--> 0 if there is an even number of negative numbers
more mathematical tricks: we take 1 or 0 off 0.5, so that the above becomes
--> (0.5-1=-0.5=>round to -1) if there is an odd number of negative numbers
--> (0.5-0= 0.5=>round to 1) if there is an even number of negative numbers
we multiple this final 1/-1 against the SUM-PRODUCT value for the real result
No, but you can use Mathematics :)
if yourColumn is always bigger than zero:
select EXP(SUM(LOG(yourColumn))) As ColumnProduct from yourTable
I see an Oracle answer is still missing, so here it is:
SQL> with yourTable as
2 ( select 1 yourColumn from dual union all
3 select 2 from dual union all
4 select 4 from dual union all
5 select 8 from dual
6 )
7 select EXP(SUM(LN(yourColumn))) As ColumnProduct from yourTable
8 /
COLUMNPRODUCT
-------------
64
1 row selected.
Regards,
Rob.
With PostgreSQL, you can create your own aggregate functions, see http://www.postgresql.org/docs/8.2/interactive/sql-createaggregate.html
To create an aggregate function on MySQL, you'll need to build an .so (linux) or .dll (windows) file. An example is shown here: http://www.codeproject.com/KB/database/mygroupconcat.aspx
I'm not sure about mssql and oracle, but i bet they have options to create custom aggregates as well.
You'll break any datatype fairly quickly as numbers mount up.
Using LOG/EXP is tricky because of numbers <= 0 that will fail when using LOG. I wrote a solution in this question that deals with this
Using CTE in MS SQL:
CREATE TABLE Foo(Id int, Val int)
INSERT INTO Foo VALUES(1, 2), (2, 3), (3, 4), (4, 5), (5, 6)
;WITH cte AS
(
SELECT Id, Val AS Multiply, row_number() over (order by Id) as rn
FROM Foo
WHERE Id=1
UNION ALL
SELECT ff.Id, cte.multiply*ff.Val as multiply, ff.rn FROM
(SELECT f.Id, f.Val, (row_number() over (order by f.Id)) as rn
FROM Foo f) ff
INNER JOIN cte
ON ff.rn -1= cte.rn
)
SELECT * FROM cte
Not sure about Oracle or sql-server, but in MySQL you can just use * like you normally would.
mysql> select count(id), count(id)*10 from tablename;
+-----------+--------------+
| count(id) | count(id)*10 |
+-----------+--------------+
| 961 | 9610 |
+-----------+--------------+
1 row in set (0.00 sec)

How can I find values with more than 2 decimal places?

I am validating data and trying to find if there are any values in a single column (allowed_amount) with more than 2 decimal places (24.1145678, 234.444, -1234.09012).
with t1 as (
select (allowed_amount - round(allowed_amount,2)) as ck
from export_core_report_client_output
where runid = '0c7c2d34-6cc3-43b0-ae4b-4bd8f4bddfb0'
)
select min(ck) as min, max(ck) as max from t1
One option would be to use this formula:
SELECT
num,
CASE WHEN 100*num - CAST(100*num AS int) > 0 THEN 'yes' ELSE 'no' END AS field
FROM yourTable;
Demo
For example, for the value 24.1234, the above formula computes:
2412.34 - 2412 = 0.34 > 0
But for 24.12, we get:
2412 - 2412 = 0
You can use Charindex to do that, supposing the allowed_amount column is varchar or nvarchar
select len(substring(allowed_amount,charindex('.',allowed_amount)+1,len(allowed_amount))) from export_core_report_client_output
This will give you a count of decimal values after and then you can use the same statement in where clause to scrutinize like:
select len(substring(allowed_amount,charindex('.',allowed_amount)+1,len(allowed_amount))) from export_core_report_client_output
where len(substring(allowed_amount,charindex('.',allowed_amount)+1,len(allowed_amount)))> 2
any questions fire up in the comments

SQL Filter numbers between x and x and ignore strings

I have a table in SQL Server where I need to filter rooms by name and type. The problem is, that the names are stored as varchar and there are also some rooms with letters. I need to filter out the rooms with letters before I can compare them as int or otherwhise I will get an error.
Here's an example from Room.Name:
030
210a
210b
Lan-Room-A
240
I can work around the room names with a or b with LEFT(Rooms.Name, 3) but if I want to add (LEFT(Rooms.Name, 3) BETWEEN 0 and 350 and it gets to Lan-Room-A it oviously can't convert a string to int. I also need to do additional filtering like Room.Type = 6 for example.
SELECT
Room.Name,
Room.Descr,
Room.MainUser
WHERE
LEFT(Room.Name, 1) NOT LIKE '%[0-9]%'
AND LEFT(Room.Name, 3) BETWEEN 0 AND 350
AND Room.Type = 6
(Removed some joins for simplicity)
I simply need to filter out the rows which contain strings before the when clause, but I have no idea how.
Do you guys have any idea?
Please note that I can't edit the database.
Thanks in advance.
You could use TRY_CAST:
SELECT *
FROM Rooms
WHERE TRY_CAST(LEFT(Rooms.Name, 3) AS INT) BETWEEN 0 and 350;
DBFiddle Demo
Your second approach is not guaranteed to work even with correct check:
WHERE LEFT(Room.Name, 1) NOT LIKE '%[0-9]%'
AND LEFT(Room.Name, 3) BETWEEN 0 AND 350
and Room.Type = 6
Query optimizer could check conditions in any order so LEFT(Room.Name, 3) BETWEEN 0 AND 350 could yield conversion error.
I don't understand the issue. You can use strings:
WHERE Room.Name NOT LIKE '[0-9]%' AND
LEFT(Room.Name, 3) BETWEEN '0' AND '350' AND
Room.Type = 6
However, I suspect your intention is captured by:
WHERE Room.Name >= '000' AND
Room.Name < '351' AND
Room.Type = 6
This works because you have zero-padded the numbers, so string comparisons will work.
For 2008. Please use this.
Solution-
;WITH CTE AS
(
SELECT '030' a UNION ALL
SELECT '210a' UNION ALL
SELECT '210b' UNION ALL
SELECT 'Lan-Room-A' UNION ALL
SELECT '240'
)
SELECT * FROM CTE
WHERE
PATINDEX('%[0-9]%', a) = 1 AND
1 = CASE WHEN CAST(LEFT(a, 3) AS INT) BETWEEN 0 and 350 THEN 1 ELSE 0 END
OUTPUT
a
----------
030
210a
210b
240
(4 rows affected)

SQL - Min difference between two integer fields

How I can get min difference between two integer fields(value_0 - value)?
value_0 >= value always
value_0 | value
-------------------
15 | 10
12 | 10
15 | 11
11 | 11
Try this:
SELECT MIN(value_0-value) as MinDiff
FROM TableName
WHERE value_0>=value
With the sample data you have given,
Output is 0. (11-11)
See demo in SQL Fiddle.
Read more about MIN() here.
Here is one way:
select min(value_0 - value)
from table t;
This is pretty basic SQL. If you want to see other values on the same row as the minimum, use order by and choose one row:
select (value_0 - value)
from table t
order by (value_0 - value)
limit 1;
The limit 1 works in some databases for getting one row. Others use top 1 in the select clause. Or fetch first 1 rows only. Or even something else.

Selecting SUM of TOP 2 values within a table with multiple GROUP in SQL

I've been playing with sets in SQL Server 2000 and have the following table structure for one of my temp tables (#Periods):
RestCTR HoursCTR Duration Rest
----------------------------------------
1 337 2 0
2 337 46 1
3 337 2 0
4 337 46 1
5 338 1 0
6 338 46 1
7 338 2 0
8 338 46 1
9 338 1 0
10 339 46 1
...
What I'd like to do is to calculate the Sum of the 2 longest Rest periods for each HoursCTR, preferably using sets and temp tables (rather than cursors, or nested subqueries).
Here's the dream query that just won't work in SQL (no matter how many times I run it):
Select HoursCTR, SUM ( TOP 2 Duration ) as LongestBreaks
FROM #Periods
WHERE Rest = 1
Group By HoursCTR
The HoursCTR can have any number of Rest periods (including none).
My current solution is not very elegant and basically involves the following steps:
Get the max duration of rest, group by HoursCTR
Select the first (min) RestCTR row that returns this max duration for each HoursCTR
Repeat step 1 (excluding the rows already collected in step 2)
Repeat step 2 (again, excluding rows collected in step 2)
Combine the RestCTR rows (from step 2 and 4) into single table
Get SUM of the Duration pointed to by the rows in step 5, grouped by HoursCTR
If there are any set functions that cut this process down, they would be very welcome.
The best way to do this in SQL Server is with a common table expression, numbering the rows in each group with the windowing function ROW_NUMBER():
WITH NumberedPeriods AS (
SELECT HoursCTR, Duration, ROW_NUMBER()
OVER (PARTITION BY HoursCTR ORDER BY Duration DESC) AS RN
FROM #Periods
WHERE Rest = 1
)
SELECT HoursCTR, SUM(Duration) AS LongestBreaks
FROM NumberedPeriods
WHERE RN <= 2
GROUP BY HoursCTR
edit: I've added an ORDER BY clause in the partitioning, to get the two longest rests.
Mea culpa, I did not notice that you need this to work in Microsoft SQL Server 2000. That version doesn't support CTE's or windowing functions. I'll leave the answer above in case it helps someone else.
In SQL Server 2000, the common advice is to use a correlated subquery:
SELECT p1.HoursCTR, (SELECT SUM(t.Duration) FROM
(SELECT TOP 2 p2.Duration FROM #Periods AS p2
WHERE p2.HoursCTR = p1.HoursCTR
ORDER BY p2.Duration DESC) AS t) AS LongestBreaks
FROM #Periods AS p1
SQL 2000 does not have CTE's, nor ROW_NUMBER().
Correlated subqueries can need an extra step when using group by.
This should work for you:
SELECT
F.HoursCTR,
MAX (F.LongestBreaks) AS LongestBreaks -- Dummy max() so that groupby can be used.
FROM
(
SELECT
Pm.HoursCTR,
(
SELECT
COALESCE (SUM (S.Duration), 0)
FROM
(
SELECT TOP 2 T.Duration
FROM #Periods AS T
WHERE T.HoursCTR = Pm.HoursCTR
AND T.Rest = 1
ORDER BY T.Duration DESC
) AS S
) AS LongestBreaks
FROM
#Periods AS Pm
) AS F
GROUP BY
F.HoursCTR
Unfortunately for you, Alex, you've got the right solution: correlated subqueries, depending upon how they're structured, will end up firing multiple times, potentially giving you hundreds of individual query executions.
Put your current solution into the Query Analyzer, enable "Show Execution Plan" (Ctrl+K), and run it. You'll have an extra tab at the bottom which will show you how the engine went about the process of gathering your results. If you do the same with the correlated subquery, you'll see what that option does.
I believe that it's likely to hammer the #Periods table about as many times as you have individual rows in that table.
Also - something's off about the correlated subquery, seems to me. Since I avoid them like the plague, knowing that they're evil, I'm not sure how to go about fixing it up.