SQL help - count all serialNumbers except those with specific cases - sql

I need help with writing a case statement inside of a where clause.
Let's say we have a bunch of serial numbers stored in a database.
They are prefixed with some letters to denote their group.
Here is what I need to accomplish:
Exclude all serial numbers that begin with 'GIU' unless some conditions apply:
If the serial number starts with GIU and is 15 chars long and the last 4 numbers are less than 5000, then exclude it, otherwise include it.
If the serial number starts with GIU and is 16 chars long and the last 5 numbers are less than 10000, then exclude it, otherwise include it.
ex. GIU930798246071 should be included because: begins with GIU -> 15 chars long -> 6071 > 5000.
ex2. GIU9307982410621 should be included because it begins with GIU -> 16 chars long -> 10621 > 10000.
ex3. GIU930798243071 should be excluded because 15 chars long -> last 4 digits (3071) < 5000.
If I wanted to run a query to count these results, I tried:
select count(serialNum)
FROM serialNumbers
WHERE serialNum not like (
case when LEN(serialNum) = 15 and SUBSTRING(serialNum, 1, 3) = 'GIU' and RIGHT(serialNum, 4) < 5000 then 'GIU%'
when LEN(serialNum) = 16 and SUBSTRING(serialNum, 1, 3) = 'GIU' and RIGHT(serialNum, 5) < 10000 then 'GIU%' end)
I'm trying to append the results of the case statement to the not like statement if those conditions apply.
Running the query returns null, so I know some part of the case is ruining the entire query.
Also, there are other serialNum who prefix start with other letters which it should also be counting.
I basically just need it to count all serialNums except the ones that start with GIU for these specific cases.
Any and all help is appreciated.

So, what you could do is do a column to confirm what you are looking for (or not). Once you get it confirmed, then do it. By taking a slight shift from what Bart provided, you need to explicitly INCLUDE the "GIU" as part of the condition
what DONT you want...
( left( serialNum, 3 ) = 'GIU'
AND LEN(serialNum) = 15
AND CAST(RIGHT(serialNum, 4) AS INT) < 5000 )
OR
( left( serialNum, 3 ) = 'GIU'
AND LEN(serialNum) = 16
AND CAST(RIGHT(serialNum, 5) AS INT) < 10000 )
So if the where clause was the ABOVE, you would ONLY get those in the excluded realm. So to invert this,
WHERE NOT ( above condition1 or condition2 )
thus
WHERE
NOT (
( left( serialNum, 3 ) = 'GIU'
AND LEN(serialNum) = 15
AND CAST(RIGHT(serialNum, 4) AS INT) < 5000 )
OR
( left( serialNum, 3 ) = 'GIU'
AND LEN(serialNum) = 16
AND CAST(RIGHT(serialNum, 5) AS INT) < 10000 )
)

Sometimes things might be simpler than you think. I rearranged your WHERE-clause just to match the specs you provide in the description. I get something like this.
select count(serialNum)
FROM serialNumbers
WHERE
serialNum not like 'GIU%' or
(LEN(serialNum) = 15 and CAST(RIGHT(serialNum, 4) AS INT) >= 5000) or
(LEN(serialNum) = 16 and CAST(RIGHT(serialNum, 5) AS INT) >= 10000)
Does this help?
Note that I explicitly casted the last 4 or 5 positions of the serial number to an integer. I assume the query will fail if those positions are not numeric. There are solutions for such cases as well, but for clarity I omitted them here.
Edit:
There is an alternative solution, assuming that the above query's logic is correct.
A 4-digit number larger than or equal to 5000 has the form 5nnn, 6nnn, 7nnn, 8nnn, or 9nnn. A 5-digit number larger than or equal to 10000 does not start with a 0.
Using LIKE pattern matching for those cases, you could probably use the following query as well:
select count(serialNum)
FROM serialNumbers
WHERE
serialNum not like 'GIU%' or
(LEN(serialNum) = 15 and serialNum like '%[56789]___') or
(LEN(serialNum) = 16 and serialNum like '%[^0]____')
Note that I used an underscore in the LIKE-patterns here to match any single character. I simply assume that they will actually be digits.
Edit 2:
Sorry. Re-reading your question, I probably got the last digits checking the wrong way around. Fixed it.

Related

Oracle 11g Nested Case Statement Calculation

In Oracle 11g, I am trying to get to a sell price from a query of data. Yes I can export this and write the code somewhere else, but I want to try to do this elegantly in the query.
I only seem to get the first part of the equation and not the last CASE where I use:
WHEN sales_code
What I am ultimately trying to do is take the result from the top and divide it by the bottom except in the case of SALE_CODE 4 where I add 1+1 or 2 to the top result and then divide by the equation.
round(to_number(price) *
CASE WHEN class_code='X'
THEN .48
ELSE .5
END * e1.set_qty +
CASE WHEN carton_pack_qty = '1'
THEN 0
ELSE (
CASE WHEN NVL(SUBSTR(size, 1,NVL(LENGTH(size) - 2,0)),1) > '35'
THEN 3.5
ELSE 3
END)
END +
CASE
WHEN sales_code='1' THEN 0 /(1-17/100)
WHEN sales_code='2' THEN 0 /(1-5/100)
WHEN sales_code='3' THEN 0 /(1-18/100)
WHEN sales_code='4' THEN 1+1 / (1-9.5/100)
WHEN sales_code='5' THEN 0 /(1-17/100)
WHEN sales_code='6' THEN 0 /(1-8/100)
WHEN sales_code='7' THEN 0 /((1-150)/100)
ELSE (100/100)
END,2) AS "Price",
I get a result from the query, but not the whole calculation. I tried this many other ways and there was always an error with parentheses or some other arbitrary error.
Any help would be appreciated.
I think this is your problem:
WHEN sales_code='1' THEN 0 /(1-17/100)
CASE returns a scalar, a number. You're trying to have it return the second half of the formula in your calculation. You need something more like this:
...
END +
CASE WHEN sales_code='4' THEN 1 ELSE 0 END /
CASE
WHEN sales_code='1' THEN (1-17/100)
WHEN sales_code='2' THEN (1-5/100)
WHEN sales_code='3' THEN (1-18/100)
WHEN sales_code='4' THEN (1-9.5/100)
WHEN sales_code='5' THEN (1-17/100)
WHEN sales_code='6' THEN (1-8/100)
WHEN sales_code='7' THEN ((1-150)/100)
ELSE 1 END ...
Actually, I'm not entirely sure what you're trying to do with sales_code='4', but that looks close.
I think I understand now what you are trying to do. Almost at least :-)
The first thing you should do is write down the complete formula with parentheses where needed. Something like:
final = ((price * class_code_factor * set_qty) + quantity_summand + two_if_sales_code4) * sales_code_factor
(That last part looks like a percentage factor, not a divisor to me. I may be wrong of course.)
Once you have the formula right, translate this to SQL:
ROUND
(
(
(
TO_NUMBER(price) *
CASE WHEN class_code = 'X' THEN 0.48 ELSE 0.5 END *
e1.set_qty
)
+
CASE WHEN carton_pack_qty = 1 THEN 0
ELSE CASE WHEN NVL(SUBSTR(size, 1,NVL(LENGTH(size) - 2,0)),1) > '35'
THEN 3.5
ELSE 3
END
END
+
CASE WHEN sales_code = 4 THEN 2 ELSE 0 END
)
*
CASE
WHEN sales_code = 1 THEN 1 - (17 / 100)
WHEN sales_code = 2 THEN 1 - (5 / 100)
WHEN sales_code = 3 THEN 1 - (18 / 100)
WHEN sales_code = 4 THEN 1 - (9.5 / 100)
WHEN sales_code = 5 THEN 1 - (17 / 100)
WHEN sales_code = 6 THEN 1 - (8 / 100)
WHEN sales_code = 7 THEN (1 - 150) / 100)
ELSE 1
END
, 2 ) AS "Price",
Adjust this to the formula you actually want. There are some things I want to point out:
Why is price not a number in your database, but a string that you must convert to a number with TO_NUMBER? That must not be. Store values in the appropriate format in your database.
In a good database you would not have to get a substring of size. It seems you are storing two different things in this column, which violates database normalization. Separate the two things and store them in separate columns.
The substring thing looks strange at that, too. You are taking the left part of the size leaving out the last two characters. It seems hence that you don't know the lenth of the part you are getting, so let's say that this can be one, two or three characers. (I don't know of course.) Now you compare this result with another string; a string that contains a numeric value. But as you are comparing strings, '4' is greater than '35', because '4' > '3'. And '200' is lesser than '35' because '2' < '3'. Is this really intended?
There are more things you treated as strings and I took the liberty to change this to numbers. It seems for instance that a quantity (carton_pack_qty) should be stored as a number. So do this and don't compare it to the string '1', but to the number 1. The sales code seems to be numeric, too. Well, again, I may be wrong.
In a good database there would be no magic numbers in the query. Knowledge belongs in the database, not in the query. If a class code 'X' means a factor of 0.48 and other class codes mean a factor of 0.5, then why is there no table of class codes showing what a class code represents and what factor to apply? Same for the mysterious summand 3 resp. 3.5; there should be a table holding these values and the size and quantity ranges they apply to. And at last there is the sales code which should also be stored in a table showing the summand (2 for code 4, 0 elsewise) and the factor.
The query part would then look something like this:
ROUND((price * cc.factor * el.set_qty) + qs.value + sc.value) * sc.factor, 2) AS "Price"
Breaking the dividend into a sub query worked and then adding parentheses around it to divide by in the main query worked.
(
select
style,
to_number(price) *
CASE WHEN class_code='X'
THEN .48
ELSE .5
END * set_qty +
CASE WHEN carton_pack_qty = '1'
THEN 1
ELSE (
CASE WHEN to_number(NVL(SUBSTR(size, 1,NVL(LENGTH(size) - 2,0)),1)) > 35
THEN 3.5
ELSE 3
END)
END as Price
FROM STYL1 s1,STY2 s2
WHERE s1.style=s2.style
) P1

select where number combination matches part of integer SQL

Assuming a simple one but Im looking up a customer ID ie: 930566615552 and only have the last 8 digits 66615552 - how would one do this?
I've attempted a WHERE RIGHT, LIKE and Cast the column as vachar but no avail.
I'm sure this question has been asked before, I know I'm just searching the wrong terms to find the answer!
You could query
... WHERE CAST(id AS text) LIKE '%66615552'
Indexing for such a query would require a trigram index.
there is modulus operator %
So
5 % 10 = 5
325 % 100 = 25
457322844 % 10000 = 2844
but it has caveats:
you need to specify correct base that will be dynamic in your case
it ignores index for this numeric column
A couple of options if the column is an INT and you always have the last 8 digits of the id:
SELECT * FROM customer WHERE id % 100000000 = 66615552;
SELECT * FROM customer WHERE RIGHT(CAST(id AS TEXT), 8) = '66615552';
Try with right function:
SELECT * FROM customer WHERE RIGHT(id, 8) = '66615552'
OR Use like operator
SELECT * FROM customer WHERE id, 8 like '%66615552'

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)

WHERE varchar = variable length of 0's

Table_A
ID Number
-- ------
1 0
2 00
3 0123
4 000000
5 01240
6 000
The 'Number' column is data type varchar.
EDIT for clarity.
My question is, can I easily pull back all rows of data which contain a variable length string of 0's?
I have tried:
SELECT *
FROM Table_A
WHERE LEFT(Number,1) = '0' AND RIGHT(Number,1) = '0'
Using the above, it would still return the below, using the example table provided.
ID Number
-- ------
1 0
2 00
4 000000
5 01240
6 000
I was looking for a function which I could pass the LEN(Number) int into, and then it generates a string of a specfic character (in my case a string of 0's). I wasn't able to find anything though.
Oh, and I also tried adding a SUBSTRING to the WHERE clause, but sometimes the Number column has a number which has a 0's in the middle, so it still returned strings with other numbers except only 0.
SUBSTRING(Number,ROUND(LEN(Number)/2,0),1) = '0'
Any help is appreciated.
So, you want a string that doesn't contain anything that isn't a 0? Sounds like it's time for a double-negative:
SELECT *
FROM Table_A
WHERE NOT Number like '%[^0]%'
AND number like '0%' --But we do want it to contain at least one zero
(The final check is so that we don't match the empty string)
Answer:
Where number like '%0%'
Your can use this query :
SELECT * FROM Table_A WHERE Number LIKE '%0%';
It'll solve your problem.
SELECT *
FROM yourtable
WHERE len(Number) - len(replace(number,'0','')) >= 0
One more approach
You can use this following one also,you will get your expected result.
SELECT *
FROM Table_A
WHERE Nunber not like '%[1-9]%'
Thanks.

Finding even or odd ID values

I was working on a query today which required me to use the following to find all odd number ID values
(ID % 2) <> 0
Can anyone tell me what this is doing? It worked, which is great, but I'd like to know why.
ID % 2 is checking what the remainder is if you divide ID by 2. If you divide an even number by 2 it will always have a remainder of 0. Any other number (odd) will result in a non-zero value. Which is what is checking for.
For finding the even number we should use
select num from table where ( num % 2 ) = 0
As Below Doc specify
dividend % divisor
Returns the remainder of one number divided by another.
https://learn.microsoft.com/en-us/sql/t-sql/language-elements/modulo-transact-sql#syntax
For Example
13 % 2 return 1
Next part is <> which denotes Not equals.
Therefor what your statement mean is
Remainder of ID when it divided by 2 not equals to 0
Be careful because this is not going to work in Oracle database. Same Expression will be like below.
MOD(ID, 2) <> 0
ID % 2 reduces all integer (monetary and numeric are allowed, too) numbers to 0 and 1 effectively.
Read about the modulo operator in the manual.
In oracle,
select num from table where MOD (num, 2) = 0;
dividend % divisor
Dividend is the numeric expression to divide. Dividend must be any expression of integer data type in sql server.
Divisor is the numeric expression to divide the dividend. Divisor must be expression of integer data type except in sql server.
SELECT 15 % 2
Output
1
Dividend = 15
Divisor = 2
Let's say you wanted to query
Query a list of CITY names from STATION with even ID numbers only.
Schema structure for STATION:
ID Number
CITY varchar
STATE varchar
select CITY from STATION as st where st.id % 2 = 0
Will fetch the even set of records
In order to fetch the odd records with Id as odd number.
select CITY from STATION as st where st.id % 2 <> 0
% function reduces the value to either 0 or 1
It's taking the ID , dividing it by 2 and checking if the remainder is not zero; meaning, it's an odd ID.
<> means not equal. however, in some versions of SQL, you can write !=