Convert Excel formulae (logic) to SQL Server - sql-server

I'm looking for help on converting an Excel formula to SQL Server.
=If(AND(N3="A", R3>O3),
R3,If(AND(N3="P",S3>O3),S3,If(N3="D","",If(OR(Q3="P",Q3="A")*AND(P3>TODAY(),P3>O3),P3,O3))))
SQL formula I tried ....Colum N & Q consists of varchar and other fields are datetime in SQL Server. In below SQL statement, I have replaced and (BOLD) with OR condition. When I use "AND"(bold) am getting right data in few cases if I use (OR), am getting right data in few other cases. Here is database structure with insert statements.
https://www.db-fiddle.com/f/iHYxufV2NuyXwHeBM832NS/4
create table dbo.test (id int, N varchar(10), O datetime, P datetime, Q varchar(10), R datetime, S datetime)
select case when N='A' and R>O THEN R
when N='P' and S>O then S
when N='D' then ''
when (Q='P' or Q='Á') **and** p>getdate() and P>O then P else O end data
from test
output for above fiddler =
id-data
1-2020-11-20 00:00:00
2-2021-02-15 00:00:00
3-2021-04-11 00:00:00
4-2021-04-16 00:00:00
5-2021-04-30 00:00:00

The problem is * is a multiplication operator, but both sides of the expression are boolean rather than numeric. I think what's going on is Excel is converting the boolean true/false values to 1 and 0 for the multiplication operation.
If this is correct, then AND is the correct operator and almost everything else in the translation is correct.
There is one other mistake. when N='D' then '' is wrong, because the other result values all appear to be DateTime columns. You can't mix strings and dates. Instead, you need when N='D' then NULL.
CASE WHEN N = 'A' AND R > O THEN R
WHEN N = 'P' AND S > O THEN S
WHEN N = 'D' THEN NULL
WHEN Q IN ('P', 'A') AND P > current_timestamp AND P > O THEN P
ELSE O END
If you really need an empty string, you can convert the result and coalesce to empty string at a different level, but don't do it inside the CASE expression.
It's also worth noting the DateTime/String mismatch could entirely explain the strange results. If you have a sample somewhere for testing with the columns represented as Varchar values instead of Date or DateTime, then the comparisons could be wrong, throwing off the results. For example, O comes before S in the third row of sample data if they are compared as strings instead of dates.

If this is your verbatim code, you have an accented A in this line:
when (Q='P' or Q='Á') and p>getdate() and P>O then P else O end data
A and Á are not equivalent, so that may be short circuiting your OR and failing to return values for any Q = 'A' values that aren't handled further up in the logic.
Other than that your logic looks equivalent. The use of OR(...)*AND(...) is odd but does produce a 1/0 value, and your conversion into SQL has the correct boolean operators to match that logic.

Related

Leetcode SQL 1440. Evaluate Boolean Expression

Table Variables:
Column Name
Type
name
varchar
value
int
name is the primary key for this table.
This table contains the stored variables and their values.
Table Expressions:
Column Name
Type
left_operand
varchar
operator
enum
right_operand
varchar
(left_operand, operator, right_operand) is the primary key for this table.
This table contains a boolean expression that should be evaluated.
operator is an enum that takes one of the values ('<', '>', '=')
The values of left_operand and right_operand are guaranteed to be in the Variables table.
Write an SQL query to evaluate the boolean expressions in Expressions table.
Return the result table in any order.
I am working on a SQL problem as shown in the above. I used MS SQL server and tried
SELECT
left_operand, operator, right_operand,
IIF(
(left_values > right_values AND operator = '>') OR
(left_values < right_values AND operator = '<' ) OR
(left_values = right_values AND operator = '='), 'true', 'false') as 'value'
FROM
(SELECT *,
IIF(left_operand = 'x', (SELECT value FROM Variables WHERE name='x')
, (SELECT value FROM Variables WHERE name='y')) as left_values,
IIF(right_operand = 'x', (SELECT value FROM Variables WHERE name='x')
, (SELECT value FROM Variables WHERE name='y')) as right_values
FROM Expressions) temp;
It works well on the test set but gets wrong when I submit it.
I think my logic is correct, could anyone help take a look at it and let me know what my problem is?
Thank you!
It feels like your example code is a lot more complicated than it needs to be. That's probably why it's failing the check. In your FROM you're using sub-selects but really a simple inner join would work much simpler. Also, if there were variables other than X and Y it doesn't look like your example code would work. Here's my code that I wrote in Postgres (should work in any SQL though).
SELECT e.left_operand, l.value as left_val, e.operator, e.right_operand, r.value as right_val,
CASE e.operator
WHEN '<' THEN
(l.value < r.value)
WHEN '=' THEN
(l.value = r.value)
WHEN '>' THEN
(l.value = r.value)
END as eval
FROM
expression as e
JOIN
variable as l on e.left_operand = l.name
JOIN
variable as r on e.right_operand = r.name
Here's a screenshot of my output:
I also have a db-fiddle link for you to check out.
https://www.db-fiddle.com/f/fdnJVSUQHS9Vep4uDSe5ZP/0

'<number>D' causes CAST to error

I'm finding this to be truly bizarre behaviour.
Here is my T-SQL:
declare #testText nvarchar(1000);
set #testText = '17D4,A,1';
select txt_value from fn_ParseText2Table (#testText , ',' )
where fn_ParseText2Table is a function that parses the text into a table where you can get the txt, int, and floating point values, if they work.
The 17D4 is a product code that I'm trying to extract within a larger query, and all other 3817 records work fine.
select (
select txt_value
from fn_ParseText2Table(t.primaryKeyValues , ',' ) as pk
where position = 1) as product_NBR
from database.dbo.tablesToParse as t
where t.tableName = 'ProductData'
I found the function here.
What I've found is that if the string starts with some numbers (I've tested anywhere from 1-4 ) followed by 'D', it fails with the 'Error converting data type varchar to numeric.' message.
All other combinations of text work. Lower case d is fine. C is fine, E, F, etc. So '17F5,A,1' is fine. Also 'asdf 17D5,A,1' is fine. '1D,A,1' is not fine.
I'm very confused. Is there a special escape character in T-SQL for 'D'?
Update:
I should clarify that the error occurs inside fn_ParseText2Table()
Update 2
It's SQL server 10 - 64 bit, running on a windows 2008 server.
As well, I've tested this in a sql mgr query window:
declare #testText nvarchar(1000);
set #testText = '17D4';
select isnumeric( #testText )
The IsNumeric() call returns 1, which is why the fn_ParseText2Table() function tries to cast it to an in and it fails. I could add an extra check to that function to lower the text first and see if that's also numeric.
I figured it was related to floating-point literals but I was surprised it wouldn't cast to a numeric type. I guess it only works when float is the target type. You'll find the explanation here:
http://www.sqlservercentral.com/Forums/Topic202581-8-1.aspx
This behavior doesn't match up with other SQL Server literals, ie, constants:
select 1d -- literal 1 with d treated as the column alias
select 1e -- literal 1.0 as a float
select cast('1d' as float), cast('1e' as float) -- neither of these will cast without an exponent value
select cast('1d0' as float), cast('1e0' as float) -- these work though

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.

'Converting varchar to data type numeric' error after successful conversion to decimal(18,2)

I have a temporary table I'm using for parsing, #rp.
#rp contains an nvarchar(max) column, #rp.col8, which holds positive and negative numbers to two decimal places of precision e.g. `1234.26'.
I'm able to run the following query and get out a set of converted values out:
select * from
(
select CONVERT(decimal(18,2),rp.col8) as PARSEAMT
from #rp
where
--#rp filtering criteria
)q
However, when I try to query for PARSEAMT = 0 in the following manner, I get the standard '8114, Error converting data type varchar to numeric.':
select * from
(
select CONVERT(decimal(18,2),col8) as PARSEAMT
from #rp
where
--#rp filtering criteria
)q
where q.PARSEAMT = 0
Without that where clause, the query runs fine and generates the expected values.
I've also tried other clauses like where q.PARSEAMT = 0.00 and where q.PARSEAMT = convert(decimal(18,2),0).
What am I doing wrong in my comparison?
I was going to suggest you select PARSEAMT into another temp-table/table-variable but I can see you've already done that from your comments.
Out of interest what does the following yield?
select
col8
from
#rp
where
-- ISNUMERIC returns 1 when the input expression evaluates to a valid
-- numeric data type; otherwise it returns 0. Valid numeric data types
-- include the following:
isnumeric(col8) <> 1

Converting text to Datetime in specific format

I need an expression which will convert a text (VARCHAR) column to a DATETIME if, and only if, it matches dd/MM/yyyy, d/MM/yyyy, dd/M/yyyy or d/M/yyyy. If it doesn't match then I want a NULL.
I have this...
CASE ISDATE([DateField])
WHEN 1 THEN CONVERT(DATETIME,[DateField],103)
ELSE NULL
END
However this fails for '15/04/76' for example - with a "Conversion failed when converting datetime from character string" error - whereas I would want it to return NULL
Example output
'1/6/1976' -> 1976-06-01
'01/06/1976' -> 1976-06-01
'13/06/2001' -> 2001-06-13
'06/13/2001' -> NULL
'13/06/76' -> NULL
Is there a way of forcing ISDATE to validate a given format?
The documentation seems to suggest so...
ISDATE is deterministic only if used with the CONVERT function, the
CONVERT style parameter is specified and style is not equal to 0, 100,
9, or 109.
But ISDATE only takes one argument, so how do I "use it with CONVERT function" if I am not doing so already?
You could do a nested case statement here. The first could check to see if you have a 10 character string 2 for day, 2 for month, 4 for year and 2 for separators = 10 characters.
SET DATEFORMAT DMY;
Case When DateField Like '%/%/[0-9][0-9][0-9][0-9]'
Then Case When IsDate(DateField) = 1
Then CONVERT(DATETIME,[DateField],103)
End
End
Revised: I changed the code to use a like search which forces there to be a /YYYY at the end of the string, and then does an IsDate check to allow for a single day and/or month.
Well, first off, why on earth are you storing datetime values in a varchar column? This is a cardinal sin for a variety of reasons, not the least of which is that you get no validation whatsoever that the data is (or is convertible to) a datetime. You should also consider validating the input, even if you leave the column as varchar, so you don't have such a wide variety of potential formats that you want to consider valid.
So here is one way, borrowing a bit from #G Mastros:
DECLARE #f TABLE(i INT, d VARCHAR(32));
INSERT #f VALUES
(1,'15/04/76'),
(2,'15/04/1976'),
(3,'1/3/1976'),
(4,'1/3/76'),
(5,'15/3/1976'),
(6,'22/22/22'),
(7,'Yesterday');
SET DATEFORMAT DMY;
SELECT i, d, d2 = CASE WHEN ISDATE(d) = 1
AND d LIKE '%/[0-9][0-9][0-9][0-9]'
THEN CONVERT(DATETIME, d, 103) END
FROM #f;
Results:
i d d2
- ---------- -----------------------
1 15/04/76 NULL
2 15/04/1976 1976-04-15 00:00:00.000
3 1/3/1976 1976-03-01 00:00:00.000
4 1/3/76 NULL
5 15/3/1976 1976-03-15 00:00:00.000
6 22/22/22 NULL
7 Yesterday NULL
PS this will be a great case for TRY_CONVERT in SQL Server 2012. It does exactly what you're asking - it tries to convert to the specified data type; if it can't, it returns NULL.
Thanks for the responses folks.
I've done it like this...
CASE ISDATE([DateField]) WHEN 1 THEN
CASE WHEN SUBSTRING([DateField],LEN([DateField])-4,1) = '/' THEN
CASE WHEN CHARINDEX('/',[DateField],LEN([DateField])-3)=0 THEN
CONVERT(datetime, [DateField] , 103)
END
END
END
which is pretty nasty business so would still appreciate something neater!
But this doesn't work either - it still errors on mm/dd/yyyy format dates!
Scrap that last comment - it does seem to work now? Probably something to do with SET DATEFORMAT

Resources