IF statement within a formula in a SQL query - sql

Let's say I have a table with two numeric columns: NUM and DEN.
I need to extract the ratio NUM/DEN only if DEN isn't 0: otherwise the ratio should be 0.
Something like this:
select ID, [...] AS RATIO
from Table
where [...] is some kind of equivalent of the excel formula IF(DEN=0;0;NUM/DEN).
Is there a way to perform this kind of query?
Many thanks!

This should work:
case
when DEN = 0 then 0
else NUM/DEN
end

Yes, what you are looking for is case. It has two versions:
case [variable]
when [value1] then [output1]
when [value2] then [output2]
when [value3] then [output3]
...
else [outputdefault] end
and
case when [Boolean(True/false) expression 1] then [output1]
when [Boolean(True/false) expression 2] then [output2]
when [Boolean(True/false) expression 3] then [output3]
...
else [outputdefault] end

If SQL Server 2012 you can use : IIF
IIF ( boolean_expression, true_value, false_value )

If you are using SQL Server you could use case statement like w0lf mentioned or you could use iif statement like so:
select iif(age > 21, 'Allowed', 'Not Allowed') as status
from test;
Example:
create table test (
fullname varchar (20),
age int
);
insert into test values
('John', 10),
('Matt', 90),
('Jane', 25),
('Ruby', 80),
('Randy', null);
Result
| fullname | status |
|----------|-------------|
| John | Not Allowed |
| Matt | Allowed |
| Jane | Allowed |
| Ruby | Allowed |
| Randy | Not Allowed |
The same thing can be written as
select case when age > 21 then 'Allowed' else 'Not Allowed' end as status
from test;
case statement is used by many database engines.
If you are dealing with null and not null values, you could also use coalesce like so:
select fullname, coalesce(age, 999) as status
from test;
The result will be:
| fullname | status |
|----------|--------|
| John | 10 |
| Matt | 90 |
| Jane | 25 |
| Ruby | 80 |
| Randy | 999 |
At first you may think that coalesce does if age is null then 999 else age. It does that, sort-of, but in particular coalesce outputs the first non-null value in a list. So, coalesce(null, null, 45) will result in 45. coalesce(null, 33, 45) will result in 33.
Feel free to play around with SQL Fiddle: http://sqlfiddle.com/#!6/a41a7/6

Related

Filling nulls with last not null value in SQL Server on Postal Codes

I have two tables PostalCodes (with one column with values from 00-00 to 99-999) and Customers (which has, beside all the customer's data, a postal code and ID of employee which is serving the customer).
So these two I am simply joining via postal code:
SELECT DISTINCT
KP.postal,
K.IDemp
FROM
PostalCodes KP
LEFT JOIN
[Customers] K ON K.postal = KP.postal
and I'm getting this:
| postal | IDemp |
+--------+-------+
| 00-000 | NULL |
| 00-001 | NULL |
| 00-001 | 12PH |
| 00-002 | NULL |
| 00-003 | NULL |
| 00-004 | NULL |
| 00-004 | 10PH |
| 00-005 | NULL |
| ... | ... |
So as you can see not all postal codes are used in the Customers table, but for my aim I need all postal codes assigned to some employee to created something like "area of service", so to do that I want to fill null values with last not null value to get something like this:
| postal | IDemp |
+--------+-------+
| 00-000 | NULL |
| 00-001 | 12PH |
| 00-002 | 12PH |
| 00-003 | 12PH |
| 00-004 | 10PH |
| 00-005 | 10PH |
| ... | ... |
I was trying to use LAG() function, but it was not working (or at least I don't know how use it properly)
LAG(K.IDemp) OVER (ORDER BY KP.postal)
I found few similar questions already, but could not come up how to use their answers to my case.
A correlated sub-query might work:
SELECT DISTINCT
KP.postal,
(SELECT TOP 1 K.IDemp
FROM [Customers] K
WHERE K.postal <= KP.postal
AND K.IDemp Is Not Null
ORDER BY K.postal DESC) As IDemp
FROM
PostalCodes KP
SQL Server doesn't support the ignore nulls option on LAG (yet), but you can get around this by creating a binary value from the column you want to order by, and the column you want to retrieve and calling MAX which does ignore nulls. A full working solution would be:
IF OBJECT_ID(N'tempdb..#T', 'U') IS NOT NULL
DROP TABLE #T;
CREATE TABLE #T (Postal VARCHAR(6) NOT NULL, IDemp VARCHAR(4) NULL);
INSERT #T (Postal, IDemp)
VALUES
('00-000', NULL),
('00-001', '12PH'),
('00-002', NULL),
('00-003', NULL),
('00-004', '10PH'),
('00-005', NULL);
SELECT *,
LastNonNull = CONVERT(VARCHAR(6),
SUBSTRING(
MAX(CONVERT(BINARY(6), Postal) + CONVERT(BINARY(4), IDemp))
OVER(ORDER BY Postal), 7,4))
FROM #T;
It might help explain if this is broken down a bit and we look at the results of this:
SELECT *,
BinaryValue = CONVERT(BINARY(6), Postal) + CONVERT(BINARY(4), IDemp)
FROM #T
Postal
IDemp
BinaryValue
00-000
NULL
NULL
00-001
12PH
0x30302D30303131325048
00-002
NULL
NULL
00-003
NULL
NULL
00-004
10PH
0x30302D30303431305048
00-005
NULL
NULL
Since concatenating null value yields a null value, you only get a value where it is not null. You can then take advantage of binary sorting (left to right) and get the maximum value of this binary within a windowed function, that is the part: MAX(...) OVER(ORDER BY Postal).
This removes all the NULL values (since MAX ignores NULL) apart from the first row, since there is no previous non null value and gives data as follows:
Postal
IDemp
MaxBinaryValue
00-000
NULL
NULL
00-001
12PH
0x30302D30303131325048
00-002
NULL
0x30302D30303131325048
00-003
NULL
0x30302D30303131325048
00-004
10PH
0x30302D30303431305048
00-005
NULL
0x30302D30303431305048
It is then just a case of extracting the portion of the binary you are interested in (characters 7-10) and converting back to varchar using SUBSTRING and CONVERT

SQL "substr" combine with "not in"

I have a table(ne) with this elements:
LECOT113_A42401
DA_RIMUOVERE
TVCCVC16_A46C01
CBCELEN1_A46C01
SPCBA440_A46C02
582
ghhtthth
TESTVMM
SACCALEN_A46C0da_cancellare
MICTEST
DA_CANCELLARE2
and i use this query:
select ne.NODE
from ne
where substr(ne.NODE,9,2) not in ('_A')
why result is:
DA_RIMUOVERE
DA_CANCELLARE2
and not this (TARGET):
582
DA_RIMUOVERE582
ghhtthth
TESTVMM
SACCALEN_A46C0da_cancellare
MICTEST
DA_CANCELLARE2
Thanks!
The function substr() returns null when the starting position is greater than the length of the string, so comparing null to '_A' gives wrong results.
So change to this:
select ne.NODE
from ne
where length(ne.NODE) < 9 or length(ne.NODE) <> 15 or substr(ne.NODE,9,2) not in ('_A')
See the demo.
Results:
> | NODE |
> | :-------------------------- |
> | DA_RIMUOVERE |
> | 582 |
> | ghhtthth |
> | TESTVMM |
> | SACCALEN_A46C0da_cancellare |
> | MICTEST |
> | DA_CANCELLARE2 |
In fact in SQL NULL neither true not false. In your case some of your string has length less then 9, so substr(...) return NULL. Value of NULL not in (...) is NULL.
Additional information can be found on Ask Tom
So, just rewrite condition on something like
select ne.NODE
from ne
where nvl(substr(ne.NODE,9,2),'x') not in ('_A')
The reason is that Oracle confuses the empty string ('') and NULL values. So, the substring returns NULL -- and then most comparisons return NULL rather than false.
One method would use LIKE:
where ne.NODE not like '_________$_A%' escape '$'
Or regexp_like():
where regexp_like(ne.NODE, '^.{9}_A')
Or being sure that the string is long enough:
where substr(ne.NODE || 'xxxxxxxxxxx', 9, 2) not in ('_A')
Or checking for the length as well as the patter.
Try the following query:
SELECT
NODE
FROM
(
SELECT
NE.NODE,
SUBSTR(NE.NODE, 9, 2) SUBST
FROM
NE
)
WHERE
SUBST <> '_A'
OR SUBST IS NULL
db<>fiddle demo
Cheers!!

The difference between CASE and UPIVOT to find the max date across the columns; ORA-00904 wrong message

I have millions of IDs and I need to find the max date from 3 different dates for each ID.
Then, I need the start date of the month of the max date.
Here's a reference:
+---------+-----------+---------------+--------------------+
| ID | SETUP_DT | REINSTATE_DT | LOCAL_REINSTATE_DT |
+---------+-----------+---------------+--------------------+
| C111111 | 2018/1/1 | Null | Null |
| C111112 | 2015/12/9 | 2018/10/25 | 2018/10/25 |
| C111113 | 2018/10/1 | Null | Null |
| C111114 | 2018/10/6 | 2018/12/14 | 2018/12/14 |
+---------+-----------+---------------+--------------------+
And what I want is below:
+---------+-----------+
| ID | APP_MON |
+---------+-----------+
| C111111 | 2018/1/1 |
| C111112 | 2018/10/1 |
| C111113 | 2018/10/1 |
| C111114 | 2018/12/1 |
+---------+-----------+
I try different code to get the result.
When I used case and unpivot to find some specific IDs, the result looks all fine.
/* case */
SELECT DIST_ID as ID,
trunc(
case
when REINSTATE_DT is not null and LOCAL_REINSTATE_DT is not null then greatest(LOCAL_REINSTATE_DT, REINSTATE_DT)
when REINSTATE_DT is null and LOCAL_REINSTATE_DT is not null then LOCAL_REINSTATE_DT
when REINSTATE_DT is not null and LOCAL_REINSTATE_DT is null then REINSTATE_DT
else SETUP_DT
end, 'MM') AS CN_APP_MON
FROM DISTRIBUTOR
where DIST_ID in ('CN111111','CN111112','CN111113','CN111114');
/* unpivot */
SELECT DIST_ID as ID,
trunc(MAX(Date_value),'MM') AS CN_APP_MON
FROM DISTRIBUTOR
UNPIVOT (Date_value FOR Date_type IN (SETUP_DT, REINSTATE_DT, LOCAL_REINSTATE_DT))
where DIST_ID in ('CN111111','CN111112','CN111113','CN111114')
GROUP BY DIST_ID;
However, when I change the condition and tried to use the date period to pull out the data, the result is weird.
To be more specific, I tried to replace
where DIST_ID in ('CN111111','CN111112','CN111113','CN111114')` <br>
by
where REINSTATE_DT
between TO_DATE('2018/01/01','yyyy/mm/dd') and TO_DATE('2018/01/02','yyyy/mm/dd')`
But the unpivot function was not work. It showed:
ORA-00904: "REINSTATE_DT": invalid identifier
00904. 00000 - "%s: invalid identifier"
I want to know:
Which method is more efficient, or what else more efficient way to do that?
Why the unpivot method didn't work? What difference is between the 2 methods?
Thank you so much!
Assuming your dates are stored as dates, you can do this using greatest(). I'm not a fan of "magic" values in queries, so I like coalesce() for this purpose.
All your rows seem to have a setup_dt it can be used as a "default" using coalesce(). So:
select id,
trunc(greatest(setup_dt,
coalesce(reinstate_dt, setup_dt,
coalesce(local_reinstate_dt, setup_dt)
),
'mm') as app_mon
from distributor;
You don't need such daunting tasks, greatest with nvl function resolves your problem.
with distributor( ID, setup_dt, reinstate_dt, local_reinstate_dt ) as
(
select 'C111111',date'2018-01-01', Null, Null from dual union all
select 'C111112',date'2015-12-09',date'2018-10-25',date'2018-10-25' from dual union all
select 'C111113',date'2018-10-01',Null,Null from dual union all
select 'C111114',date'2018-10-06',date'2018-12-14',date'2018-12-14' from dual
)
select id, trunc(greatest(nvl(setup_dt,date'1900-01-01'),
nvl(reinstate_dt,date'1900-01-01'),
nvl(local_reinstate_dt,date'1900-01-01')),'mm')
as app_mon
from distributor;
ID APP_MON
------- ----------
C111111 01.01.2018
C111112 01.10.2018
C111113 01.10.2018
C111114 01.12.2018
Rextester Demo
P.S.: Using SETUP_DT, REINSTATE_DT or LOCAL_REINSTATE_DT columns can not be allowed In your query's where clause, because they are converted to Date_type in the unpivot part.

<SQL Server>DOB and Age field as blank using SQL

I am running a SQL query where I am trying to get both the DOB and Age field as blank (' ') as opposed to NULL.
I have managed to use the ISNULL function to change the DOB from 1900-01-01 to ' '. Originally my DOB was
SELECT isnull(DOB,'') DOB
which was bring back 1900-01-01 instead of NULL
With the code below the Age field is appearing as '0' rather then ' '. I'm not sure how to use the ISNULL function, as the Age field does not exist in the db table.
SELECT ISNULL(CASE WHEN CONVERT(DATE, DOB) = '1900-01-01' THEN ''
ELSE CONVERT(CHAR(10), DOB, 103) END, '') AS DOB,
ISNULL (DATEDIFF(hour,dob,GETDATE())/8766,'')Age,
Any help will be much appreciated, thanks :)
Some dummy data of the table:
ID | Name |Address |DOB | Gender | Email |
---------------------------------------------------------
01 | Max |Abc Road| 2000-12-19 | Male |Max#mail.net |
02 | Sam |TBH Road| null | Male |Sam#mail.net |
This is what im getting with my query
ID | Name |Address |DOB | Age | Email |
---------------------------------------------------------
01 | Max |Abc Road|2000-12-19 | 15 |Max#Gmail.net |
02 | Sam |TBH Road| | 0 |Sam#Gmail.net |
What I want to get however is:
ID | Name |Address |DOB | Age | Email |
---------------------------------------------------------
01 | Max |Abc Road|2000-12-19 | 15 |Max#Gmail.net |
02 | Sam |TBH Road| | |Sam#Gmail.net |
This is your age:
DATEDIFF(hour,dob,GETDATE())/8766
It is a number. To make it a string, do this:
cast(DATEDIFF(hour,dob,GETDATE())/8766 as varchar (15))
Then you can use isnull because your datatypes are the same
isnull(cast(DATEDIFF(hour,dob,GETDATE())/8766 as varchar (15)), '')
You could use a simple CASE statement.
case
when dob = ''
then ''
else datediff(hour, dob, getdate())/8766
end as [Age]
There's probably a more elegant way to do it though.
You need to convert the other to varchar because it is coming as int then all other cases for that column will come as int if u have'nt mention anything it will default takes 0
so need to change the all output to varchar()
SELECT ISNULL(CASE WHEN CONVERT(DATE, DOB) = '1900-01-01' THEN ''
ELSE CONVERT(CHAR(10), DOB, 103) END, '') AS DOB,
IIf((DATEDIFF(hour,DOB,GETDATE())/8766) is null,'',
cast((DATEDIFF(hour,DOB,GETDATE())/8766) as nvarchar(3))) Age
You can use nullif and cast for this
select ISNULL(CONVERT(CHAR(10), nullif(DOB,'1900-01-01'), 103),'') as DOB,
isnull(cast(DATEDIFF(hour,nullif(DOB,'1900-01-01'),GETDATE())/8766 as varchar),'') Age

Search an SQL table that already contains wildcards?

I have a table that contains patters for phone numbers, where x can match any digit.
+----+--------------+----------------------+
| ID | phone_number | phone_number_type_id |
+----+--------------+----------------------+
| 1 | 1234x000x | 1 |
| 2 | 87654311100x | 4 |
| 3 | x111x222x | 6 |
+----+--------------+----------------------+
Now, I might have 511132228 which will match with row 3 and it should return its type. So, it's kind of like SQL wilcards, but the other way around and I'm confused on how to achieve this.
Give this a go:
select * from my_table
where '511132228' like replace(phone_number, 'x', '_')
select *
from yourtable
where '511132228' like (replace(phone_number, 'x','_'))
Try below query:
SELECT ID,phone_number,phone_number_type_id
FROM TableName
WHERE '511132228' LIKE REPLACE(phone_number,'x','_');
Query with test data:
With TableName as
(
SELECT 3 ID, 'x111x222x' phone_number, 6 phone_number_type_id from dual
)
SELECT 'true' value_available
FROM TableName
WHERE '511132228' LIKE REPLACE(phone_number,'x','_');
The above query will return data if pattern match is available and will not return any row if no match is available.