SQL function: use column 1 and column 2 to give column 3 - sql-server

Looking to write a function where I can call from any two columns in my table, to get the resulting column in the same table.
Table: Acme
Columns: CustomerID, LastName, FirstName, EmailAdress, MailingAddress, City, State, Zip
Can I get EmailAddress from just FirstName and Last Name?
Can I get Zip from CustomerID and City?
What I have so far for EmailAddress:
CREATE FUNCTION fnEmailAddress
(#LastName varchar (255), #FirstName varchar (255))
RETURNS table
RETURN (SELECT EmailAddress
FROM Acme
WHERE FirstName = #FirstName AND LastName = #LastName);
END
So the following would give me brenda.chen#gmail.com:
EXEC fnEmailAddress ('Brenda'+'Chen')
But it doesn't work :(

If I understand you correctly, you want a function that can adapt to several possibilities of input combinations, because what columns you will have ready as input might differ in each function call. If so:
Clearly, your function must have as many parameters as column possibilities you have, i.e. for any column that can possibly become a "column 1" or "column 2", add a parameter.
Since you have many parameters, you need to decide which ones are to be used, or if the inputs are irrational to begin with. For example:
What to do if you have 4 out of 4 parameters provided? Refuse to work? Or can the function adapt to that?
What if parameters 1 and 4 are provided but your function isn't meant to work with that? It's meant to work with 1 + 2 or 3 + 4 for example.
Lastly, choose one of either:
Set any unnecessary inputs to NULL, then query once with a WHERE clause that auto-passes any NULL inputs (example: WHERE COL1 = ISNULL(#PAR1, COL1) AND COL2 = ISNULL(#PAR2, COL2), needs more logic for NULLable columns),
Or, utilizing IF/ELSE commands to switch between several different queries (different queries is probably maintenance headache, so avoid when possible).
Below is an example based on something I already have, although it uses the IF/ELSE approach. Try to do NULL/ISNULL instead:
CREATE FUNCTION [dbo].[SomeWackoName]
(#FirstArm tinyint, #SecondArm tinyint, #FirstLeg tinyint, #SecondLeg tinyint)
RETURNS #Results TABLE(
[Value] varchar(60)
) AS
BEGIN
-- For input, we need both arms, or both legs. It is acceptable to provide three inputs.
-- It is unacceptable to provide all four inputs (confusing), or failing to provide any full pair (like one input, or two unpaired inputs).
DECLARE #Validator tinyint = CASE WHEN #FirstArm + #SecondArm IS NULL THEN 0 ELSE 1 END + CASE WHEN #FirstLeg + #SecondLeg IS NULL THEN 0 ELSE 2 END
IF #Validator NOT BETWEEN 1 AND 2 RETURN --THROW 50000, 'INCORRECT INPUT WHYYYYYYYYYYYYYYY WHYYYYYYYYYYYYYYYYYY', 0;
-- Depending on input provided, decide how to behave.
IF #Validator = 1
INSERT INTO #Results SELECT 'I GOT THE ARMS. POPULATE WITH LEGS!!'
ELSE IF #Validator = 2
INSERT INTO #Results SELECT 'I GOT THE LEGS. POPULATE WITH ARMS!!'
-- Return results.
RETURN
END

CREATE FUNCTION fnEmailAddress
(
#LastName varchar (255)=null, #FirstName varchar (255)=null,
#streetNumber varchar(255)=null,#streetname varchar (255)=null
)
RETURNS table
Select case when #firstname+#lastname is not null then EMail Else LastName End Column3
from person
where
(
case when #firstname+#lastname is not null
then case when FirstName=#firstname and LastName=#lastname
then 1 else 0 End
when #StreetNumber+#streetname is not null
then case when StreetName=#Streetname and StreetNumber=#streetNumber
then 1 Else 0 End
End
)=1
The function should have 4 input parameters, pass first two or the last two parameters only.
CASE in the select statement will get 'Email' or 'LastName' depending upon input parameter null or not.
Similarly, CASE in the where statement will choose which filter should apply depending upon input parameter null or not, and results will be fetched based on that.

Related

Find row values based on if a parameter has been set

I have a stored procedure with 2 parameters: #Name and #Surname which have to be set by an external source.
I need the stored procedure to return values based on what parameter has been set. It should "ignore" null parameters and only take set parameters into account.
SELECT *
FROM ParticipantNames
WHERE -- The if below should go here...
if(#Name iS NOT NULL) - find the row whose [NAME] column LIKE #Name else skip name comparison.
if(#Surname iS NOT NULL) - find the row whose [SURNAME] column LIKE #Surname else skip surname comparison.
if(#Name AND #Surname is NOT NULL) - find the row whose [NAME] column LIKE #Name AND whose [SURNAME] column LIKE #Surname
The following works if all the parameters have been set but return 0 rows when one parameter has not been set.
;WITH tempSearch AS
(
SELECT *
FROM ParticipantNames f
WHERE ((f.Name LIKE CASE WHEN #Name IS NULL THEN NULL ELSE #Name END)
AND (f.Surname LIKE CASE WHEN #Surname IS NULL THEN NULL ELSE #Surname END)
...
I know it returns 0 rows because of the AND clause, but I don't know how to fix it.
Any advice on how to achieve this?
It seems you are trying to use a 'default' pattern on your queries.
The problem here is that you are projecting NULL out of the CASE WHEN, which then leads to WHERE Column LIKE NULL, which won't work.
What you can do is subtitute the actual column back in, like this:
SELECT *
FROM ParticipantNames f
WHERE ((f.Name LIKE CASE WHEN #Name IS NULL THEN Name ELSE #Name END)
AND (f.Surname LIKE CASE WHEN #Surname IS NULL THEN Surname ELSE #Surname END)
This leads to WHERE Column LIKE Column in the default case, which will work.
Note however that queries like these are hard on the query optimizer, given the conditional use of a predicate. There are several similar discussions on how best to approach the application of 'optional parameters'.

SQL Nested Case Statements Within Stored Proc

I'm having problems getting back the data I'd expect from a stored procedure. The procedure is used to both insert and update record, and this determines which parameters are set when called. My example here is assuming the DATE type parameter has the default value of NULL, i.e. they have not been passed into the sp. I have broken the code down into a small section to fix, rather than include the entire procedure code, as follows:
-- these would be sp parameters
declare #CustomerId int = 15
declare #Indicator varchar(5) = 'Yes'
declare #ProjectTypeId tinyint = 1
declare #FutureEffectiveDate as date = null
SELECT
CASE #FutureEffectiveDate
WHEN NULL THEN
CASE #Indicator
WHEN 'Yes' THEN
-- can only be 1, 2 or 3 to return relevant date
CASE #ProjectTypeId
WHEN 1 THEN DI.[NextFormalEffectiveDate]
WHEN 2 THEN DI.[NextInterimEffectiveDate]
WHEN 3 THEN DI.[NextAccountingEffectiveDate]
END
-- data should be NULL if #Indicator not 'Yes'
ELSE NULL
END
ELSE #FutureEffectiveDate
END AS [FutureEffectiveDate]
FROM
[_Staging].[DataImport_2] AS DI
JOIN
[CustomerView] AS CV ON CV.[CustomerNumber] = DI.[BillingInvoiced]
JOIN
[ProjectType] AS PT ON PT.[ProjectType] = DI.[ProjectType]
WHERE
CV.[CustomerID] = #CustomerId AND
PT.[ProjectTypeID] = #ProjectTypeId
So the idea is that, for records where a field contains the text 'Yes', and based on the project type for that record, it selects one of three dates. If the field is not 'Yes' then it should return NULL, ignoring the project type. If the date parameter is NOT null, then it should simply return the parameter passed in. The result is returned as the column 'FutureEffectiveDate'. With the example data I have, I would expect a date to be returned as the relevant field is 'Yes', and the column NextFormalEffectiveDate has a value (as project type is 1).
Oddly enough, if you exclude the outer CASE statement, it works. So the issue is around determining what to do based on the DATE parameter, but i cannot see why the outer CASE statement is breaking the result.
The way you checked #FutureEffectiveDate for NULL in CASE statement is wrong. Here is a small demo
declare #FutureEffectiveDate as date = null
Select Case #FutureEffectiveDate when NULL then 1 else 0 end
The above query will result 0. Because the above CASE statement validates the input expression like #FutureEffectiveDate = NULL which will fail. NULL should be compared using IS operator
Here is the correct way to compare NULL
SELECT CASE
WHEN #FutureEffectiveDate IS NULL THEN
CASE ..

check if value in column is date, and then do something with it

I have a column that holds a rejected reason for a person.
The value is either something like "Person refused" or "10/18/2012"
I'm trying to setup my query to check if the value is a date, then it can put a blank space in its place. Otherwise, keep the text that's in there.
The query that I setup is:
SELECT DISTINCT person_id,
CASE description
WHEN ISDATE(description) THEN ''
END AS refuseReason
FROM person
But this returns an error. If the dates themselves are saved as VARCHAR, then would I have to convert them first to a date, then check to see if ISDATE? If I did that, would it convert the text when it comes accross that?
EDIT: I tried it by casting it to datetime, and i keep getting error as well:
SELECT DISTINCT person_id,
CASE description
WHEN ISDATE(CAST(OBSVALUE AS DATETIME)) THEN ''
END AS refuseReason
FROM person
(Assuming TSQL) IsDate() returns 1 or 0. (Also your CASE syntax was incorrect)
Try:
SELECT DISTINCT person_id,
CASE
WHEN ISDATE(description) = 1 THEN ''
ELSE description
END AS refuseReason
FROM person
The ISDATE() functions returns a boolean 1 or 0
you have to make the clause by using
WHEN ISDATE(#variable) = 1 THEN ''
ELSE #variable

SQL Server : converting varchar to INT

I am stuck on converting a varchar column UserID to INT. I know, please don't ask why this UserID column was not created as INT initially, long story.
So I tried this, but it doesn't work. and give me an error:
select CAST(userID AS int) from audit
Error:
Conversion failed when converting the varchar value
'1581............................................................................................................................' to data type int.
I did select len(userID) from audit and it returns 128 characters, which are not spaces.
I tried to detect ASCII characters for those trailing after the ID number and ASCII value = 0.
I have also tried LTRIM, RTRIM, and replace char(0) with '', but does not work.
The only way it works when I tell the fixed number of character like this below, but UserID is not always 4 characters.
select CAST(LEFT(userID, 4) AS int) from audit
You could try updating the table to get rid of these characters:
UPDATE dbo.[audit]
SET UserID = REPLACE(UserID, CHAR(0), '')
WHERE CHARINDEX(CHAR(0), UserID) > 0;
But then you'll also need to fix whatever is putting this bad data into the table in the first place. In the meantime perhaps try:
SELECT CONVERT(INT, REPLACE(UserID, CHAR(0), ''))
FROM dbo.[audit];
But that is not a long term solution. Fix the data (and the data type while you're at it). If you can't fix the data type immediately, then you can quickly find the culprit by adding a check constraint:
ALTER TABLE dbo.[audit]
ADD CONSTRAINT do_not_allow_stupid_data
CHECK (CHARINDEX(CHAR(0), UserID) = 0);
EDIT
Ok, so that is definitely a 4-digit integer followed by six instances of CHAR(0). And the workaround I posted definitely works for me:
DECLARE #foo TABLE(UserID VARCHAR(32));
INSERT #foo SELECT 0x31353831000000000000;
-- this succeeds:
SELECT CONVERT(INT, REPLACE(UserID, CHAR(0), '')) FROM #foo;
-- this fails:
SELECT CONVERT(INT, UserID) FROM #foo;
Please confirm that this code on its own (well, the first SELECT, anyway) works for you. If it does then the error you are getting is from a different non-numeric character in a different row (and if it doesn't then perhaps you have a build where a particular bug hasn't been fixed). To try and narrow it down you can take random values from the following query and then loop through the characters:
SELECT UserID, CONVERT(VARBINARY(32), UserID)
FROM dbo.[audit]
WHERE UserID LIKE '%[^0-9]%';
So take a random row, and then paste the output into a query like this:
DECLARE #x VARCHAR(32), #i INT;
SET #x = CONVERT(VARCHAR(32), 0x...); -- paste the value here
SET #i = 1;
WHILE #i <= LEN(#x)
BEGIN
PRINT RTRIM(#i) + ' = ' + RTRIM(ASCII(SUBSTRING(#x, #i, 1)))
SET #i = #i + 1;
END
This may take some trial and error before you encounter a row that fails for some other reason than CHAR(0) - since you can't really filter out the rows that contain CHAR(0) because they could contain CHAR(0) and CHAR(something else). For all we know you have values in the table like:
SELECT '15' + CHAR(9) + '23' + CHAR(0);
...which also can't be converted to an integer, whether you've replaced CHAR(0) or not.
I know you don't want to hear it, but I am really glad this is painful for people, because now they have more war stories to push back when people make very poor decisions about data types.
This question has got 91,000 views so perhaps many people are looking for a more generic solution to the issue in the title "error converting varchar to INT"
If you are on SQL Server 2012+ one way of handling this invalid data is to use TRY_CAST
SELECT TRY_CAST (userID AS INT)
FROM audit
On previous versions you could use
SELECT CASE
WHEN ISNUMERIC(RTRIM(userID) + '.0e0') = 1
AND LEN(userID) <= 11
THEN CAST(userID AS INT)
END
FROM audit
Both return NULL if the value cannot be cast.
In the specific case that you have in your question with known bad values I would use the following however.
CAST(REPLACE(userID COLLATE Latin1_General_Bin, CHAR(0),'') AS INT)
Trying to replace the null character is often problematic except if using a binary collation.
This is more for someone Searching for a result, than the original post-er. This worked for me...
declare #value varchar(max) = 'sad';
select sum(cast(iif(isnumeric(#value) = 1, #value, 0) as bigint));
returns 0
declare #value varchar(max) = '3';
select sum(cast(iif(isnumeric(#value) = 1, #value, 0) as bigint));
returns 3
I would try triming the number to see what you get:
select len(rtrim(ltrim(userid))) from audit
if that return the correct value then just do:
select convert(int, rtrim(ltrim(userid))) from audit
if that doesn't return the correct value then I would do a replace to remove the empty space:
select convert(int, replace(userid, char(0), '')) from audit
This is how I solved the problem in my case:
First of all I made sure the column I need to convert to integer doesn't contain any spaces:
update data set col1 = TRIM(col1)
I also checked whether the column only contains numeric digits.
You can check it by:
select * from data where col1 like '%[^0-9]%' order by col1
If any nonnumeric values are present, you can save them to another table and remove them from the table you are working on.
select * into nonnumeric_data from data where col1 like '%[^0-9]%'
delete from data where col1 like '%[^0-9]%'
Problems with my data were the cases above. So after fixing them, I created a bigint variable and set the values of the varchar column to the integer column I created.
alter table data add int_col1 bigint
update data set int_col1 = CAST(col1 AS VARCHAR)
This worked for me, hope you find it useful as well.

Numeric comparison of a varchar field in SQL Server

I want to write a query to see if a category field is within a certain range. The problem is the field can contain null, text or numeric text prefixed by '#' character.
Does anybody know of SQL that will strip the non numerics and allow me to do the following check.
category > 1 and category < 100
Here is a sample of what the field category can contain:
#230.1
#200
Null
text
I am using SQL Server 2000
I appears astander's solution is functional. You should consider however a few points:
If the table holds more than a few thousand rows, and if this type of query is to be run frequently, it may be beneficial to introduce a new column to hold the numeric value of the category (if available, null otherwise). This will be more efficient for two reasons: as written, SQL needs to scan the table, completely, i.e.it needs to review every single row; also it needs to perform all these conversion which are a bit expensive, CPU-wise.
You may consider introducing some extra logic to normalize the category field. For example to get rid of common leading or trailing characters etc. This will "rescue" several category codes which would otherwise translate to null wouldn't be able to participate in these filters.
Try something like this
DECLARE #Table TABLE(
Val VARCHAR(200)
)
INSERT INTO #Table (Val) SELECT '#230.1'
INSERT INTO #Table (Val) SELECT '#200'
INSERT INTO #Table (Val) SELECT '210'
INSERT INTO #Table (Val) SELECT NULL
INSERT INTO #Table (Val) SELECT 'text'
SELECT *
FROM (
SELECT CASE
WHEN ISNUMERIC(Val) = 1
THEN CAST(Val AS FLOAT)
WHEN LEN(Val) > 1 AND ISNUMERIC(RIGHT(Val,LEN(Val)-1)) = 1
THEN CAST(RIGHT(Val,LEN(Val)-1) AS FLOAT)
END Num
FROM #Table
WHERE Val IS NOT NULL
AND (
ISNUMERIC(Val) = 1
OR (
LEN(Val) > 1
AND ISNUMERIC(RIGHT(Val,LEN(Val)-1)) = 1
)
)
) Numbers
WHERE Num BETWEEN 205 AND 230

Resources