Using a function in a query that returns a string or a null - sql-server

I want to join 2 tables 'addresses' and 'user_info' on user_id and app_id
(which is a number, or it is null), like these 2 examples:
select * from user_info
left outer join addresses on addresses.user_id = user_info.user_id
and addresses.app_id is null
select * from user_info
left outer join addresses on addresses.user_id = user_info.user_id
and addresses.app_id = 1234
What the app_id should be is complicated and I have written a function to return it. It returns a string, which would be for example "is null" or "= 1234".
I'm trying to call it with this syntax:
select * from user_info
left outer join addresses on addresses.user_id = user_info.user_id
and addresses.app_id dbo.fnGetAppId(addresses.user_id)
I get this error:
Msg 4145, Level 15, State 1, Line 3 An
expression of non-boolean type
specified in a context where a
condition is expected, near 'dbo'.
I'd like to keep the query very simple as it is without having to determine if the function is returning a null or not.
Can you suggest the best way to keep the calling query very simple?
(I'm in sql server 2005.)

NULL != NULL. If either address.app_id = NULL or fnGetAppID = NULL, the comparison will fail. I would write the comparison as:
coalesce(address.app_id, 'NULLSTRING') = coalesce(dbo.fnGetAppID(addresses.user_id), 'NULLSTRING')

It looks like you're just missing an = sign
addresses.app_id dbo.fnGetAppId(addresses.user_id)
rather than
addresses.app_id = dbo.fnGetAppId(addresses.user_id)

So if fnGetAppId is null then this query looks like the following?
select * from user_info left outer join addresses on addresses.user_id = user_info.user_id and null
I doubt that is what you want. :)
You may want to do a simple check in your logic, before calling the query, to properly handle a null for fnGetAppId and as Clyde mentioned you also need an = sign for a non-null
.

As James Black pointed out, you have AND where you probably want WHERE. Beyond this, I suggest you make the function a boolean one (passing address.app_id to it as one more argument), so it can perform an IS NULL or = 1234 as appropriate (Bill's COALESCE solution is clever indeed, but putting the appropriate comparison inside the function is more straightforward, IMO).

Related

Is there any way to optimize this query?

I need to optimize the following query:
IF object_id('tempdb..#TAB001') IS NOT NULL
DROP TABLE #TAB001;
select *
into #TAB001
from dbo.uvw_TAB001
where 1 = 1
and isnull(COD_CUSTOMER,'') = isnull(#cod_customer,isnull(COD_CUSTOMER,''))
and isnull(TAXCODE,'') = isnull(#taxcode, isnull(TAXCODE,''))
and isnull(SURNAME,'') = isnull(#surname,isnull(SURNAME,''))
and isnull(VATCODE,'') = isnull(#vatCode,isnull(VATCODE,''))
The goal is to improve the performance of this query.
It is currently quite fast but I would like to speed it up even more.
This query has the optional parameters for which it is necessary to make a query that regardless of whether all or 1 parameter is set, returns results in the shortest possible time.
What you have here is known as a "catch-all" or "kitchen sink" query, which need a little helping hand sometimes.
Firstly, you need to get rid of those ISNULLs; they are making the query non-SARGable. Also, I would suggest getting rid of the SELECT * and limiting the query to the columns you need.
Then, finally, we can add OPTION (RECOMPILE) to the query; why is discussed in the articles I linked above. This gives you the following:
SELECT * --Replace with Column Names
INTO #TAB001 --Do you actually need to do this?
FROM dbo.uvw_TAB001
--Removed WHERE 1 = 1 as it's always true, thus pointless
WHERE (COD_CUSTOMER = #cod_customer OR #cod_customer IS NULL)
AND (TAXCODE = #taxcode OR #taxcode IS NULL)
AND (SURNAME = #surname OR #surname IS NULL)
AND (VATCODE = #vatCode OR #vatCode IS NULL)
OPTION (RECOMPILE);
Note I am assuming that when a variable (for example #cod_customer) has the value NULL you mean that the variable should be "ignored" and not matched against NULL.
If you actually want {Column} = #{Variable} including NULL then use SQL with the format below instead:
({Column} = #{Variable} OR ({Column} IS NULL AND #{Variable} IS NULL))

Executing part of stored procedure based on condition

I am trying to load to a table based on load types - Full or Incremental that is being passed as parameter in stored procedure. I was able to try with substitution variable with one line of code previously, but the below code doesn't seem to work -
Stored procedure possible arguments:
LOAD_TYPE=FULL
LOAD_TYPE=INCR
var incr_condition = (load_type=='INCR')?"INNER JOIN temp_table"
with temp_table(
select data
from table a
where dt between 01-01-2019 and 09-09-2020
)
select *
from table b
${incr_condition} -- execute only if load_type=INCR
INNER JOIN TABLE C ON B.ID = C.ID
Is there any way to restrict the with clause to execute only if the load_type==INCR? Please advice.
I think the conditional operator (question mark) must have a false part in addition to the true part. Otherwise, it generates a syntax error when there's a semicolon ending the line. This example obviously doesn't run anything, but it will return the values assigned to the "out" variable, which would be run.
Since you're using a replacement variable ${incr_condition} be sure to use backticks to open and close your SQL string.
create or replace procedure foo(LOAD_TYP string)
returns string
language javascript
as
$$
var load_type = LOAD_TYP;
var incr_condition = (load_type === 'INCR') ? "INNER JOIN temp_table" : "";
var out = `
with temp_table(
select data
from table a
where dt between 01-01-2019 and 09-09-2020
)
select *
from table b
${incr_condition} -- execute only if load_type=INCR
INNER JOIN TABLE C ON B.ID = C.ID
`;
return out;
$$;
call foo('INCR'); --Adds the inner join
call foo('FULL'); --Does not add the inner join
I also recommend changing your comparison on strings from == to ===. For details on why, reference What is the correct way to check for string equality in JavaScript?.

SSMS Error "An expression of non-boolean type specified in a context where a condition is expected, near '('"

I am getting the titled error from a number of SQL Server views I am trying to create. They are modified from a MS Access database I'm upgrading to use SQL as the back end. See SQL below:
SELECT dbo.Site_Info_All.SiteID,
IIf(dbo.Site_Info_All.Fixed_Charge, N'Yes', N'No') AS [Fixed Charge],
dbo.Site_Info_All.Fixed_Charge_Date, dbo.MG_Definition.MG_Definition
FROM dbo.MG_Definition INNER JOIN
dbo.Site_Info_All ON dbo.MG_Definition.MG_DefinitionID =
dbo.Site_Info_All.MG_DefinitionID
It looks as though I'm being told that the Fixed_Charge field is not boolean, except that it is. I'm encountering this issue with multiple views. What am I doing wrong?
It's not boolean. It's a bit. :)
A bit column doesn't directly evaluate to true or false, the way a bool does in other languages. You actually have to compare it to another bit value to return the boolean value.
declare #bit bit = 1;
if (#bit) print '#bit was true'; -- this does not work
if (#bit = 1) print '#bit = 1 was true'; -- this works
What you want is:
... IIf(dbo.Site_Info_All.Fixed_Charge = 1, N'Yes', N'No') ...
SELECT dbo.Site_Info_All.SiteID,
IIf(dbo.Site_Info_All.Fixed_Charge = 1, N'Yes', N'No') AS [Fixed Charge],
dbo.Site_Info_All.Fixed_Charge_Date,
dbo.MG_Definition.MG_Definition
FROM dbo.MG_Definition
INNER JOIN dbo.Site_Info_All ON dbo.MG_Definition.MG_DefinitionID = dbo.Site_Info_All.MG_DefinitionID
In the end, I decided that IIF simply wasn't working for whatever reason. An answer to that would be very welcome, btw.
I used
SELECT dbo.Site_Info_All.SiteID,
CASE WHEN dbo.Site_Info_All.Fixed_Charge = 1
THEN N'Yes'
ELSE N'No'
END AS [Fixed Charge], dbo.Site_Info_All.Fixed_Charge_Date,
dbo.MG_Definition.MG_Definition
FROM dbo.MG_Definition INNER JOIN
dbo.Site_Info_All ON dbo.MG_Definition.MG_DefinitionID =
dbo.Site_Info_All.MG_DefinitionID
That gave me the results I was after, but only after pulling out what little hair I have left.

Yet another subquery issue

Hello from an absolute beginner in SQL!
I have a field I want to populate based on another table. For this I have written this query, which fails with: Msg 512, Level 16, State 1, Line 1
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
The statement has been terminated.
oK, here goes:
Update kre.CustomerOrderLineCopy
SET DepNo = (SELECT customerordercopy.DepNo
FROM kre.CustomerOrderCopy , kre.CustomerOrderLineCopy
WHERE CustomerOrderLineCopy.OrderCopyNo =kre.CustomerOrderCopy.OrderCopyNo)
WHERE CustomerOrderLineCopy.OrderCopyNo = (SELECT CustomerOrderCopy.OrderCopyNo
FROM kre.CustomerOrderCopy, kre.CustomerOrderLineCopy
WHERE kre.CustomerOrderLineCopy.OrderCopyNo = kre.CustomerOrderCopy.OrderCopyNo)
What I'm trying to do is to change DepNo in CustomerOrderLineCopy, with the value in DepNo in CustomerOrderCopy - based on the same OrderCopyNo in both tables.
I'm open for all suggestion.
Thanks,
ohalvors
If you just join the tables together the update is easier:
UPDATE A SET A.DepNo = B.DepNo
FROM kre.CustomerOrderLineCopy A
INNER JOIN kre.CustomerOrderCopy B ON A.OrderCopyNo = B.OrderCopyNo
The problem is that at least one of your sub queries return more than one value. Think about this:
tablePerson(name, age)
Adam, 11
Eva, 11
Sven 22
update tablePerson
set name = (select name from tablePerson where age = 11)
where name = 'Sven'
Which is equivalent to: set Sven's name to Adam and Eva. Which is not possible.
If you want to use sub queries, either make sure your sub queries can only return one value or force one value by using:
select top 1 xxx from ...
This may be enough to quieten it down:
Update kre.CustomerOrderLineCopy
SET DepNo = (SELECT customerordercopy.DepNo
FROM kre.CustomerOrderCopy --, kre.CustomerOrderLineCopy
WHERE CustomerOrderLineCopy.OrderCopyNo =kre.CustomerOrderCopy.OrderCopyNo)
WHERE CustomerOrderLineCopy.OrderCopyNo = (SELECT CustomerOrderCopy.OrderCopyNo
FROM kre.CustomerOrderCopy --, kre.CustomerOrderLineCopy
WHERE kre.CustomerOrderLineCopy.OrderCopyNo = kre.CustomerOrderCopy.OrderCopyNo)
(Where I've commented out kre.CustomerOrderLineCopy in the subqueries) That is, you were hopefully trying to correlate these subqueries with the outer table - not introduce another instance of kre.CustomerOrderLineCopy.
If you still get an error, then you still have multiple rows in kre.CustomerOrderCopy which have the same OrderCopyNo. If that's so, you need to give us (and SQL Server) the rules that you want to apply for how to select which row you want to use.
The danger of switching to the FROM ... JOIN form shown in #Avitus's answer is that it will no longer report if there are multiple matching rows - it will just silently pick one of them - which one is never made clear.
Now I look at the query again, I'm not sure it even needs a WHERE clause now. I think this is the same:
Update kre.CustomerOrderLineCopy
SET DepNo = (
SELECT customerordercopy.DepNo
FROM kre.CustomerOrderCopy
WHERE CustomerOrderLineCopy.OrderCopyNo = kre.CustomerOrderCopy.OrderCopyNo)

Query with integers not working

I've been searching here on stackoverflow and other sources but not found a solution to this
The query below works as expected expect for when either custinfo.cust_cntct_id or custinfo.cust_corrcntct_id = '' (blank not NULL) then I get no results. Both are integer fields and if both have an integer value then I get results. I still want a value returned for either cntct_email or corrcntct_email even if custinfo.cust_cntct_id or custinfo.cust_corrcntct_id = blank
Can someone help me out in making this work? The database is PostgreSQL.
SELECT
cntct.cntct_email AS cntct_email,
corrcntct.cntct_email AS corrcntct_email
FROM
public.custinfo,
public.invchead,
public.cntct,
public.cntct corrcntct
WHERE
invchead.invchead_cust_id = custinfo.cust_id AND
cntct.cntct_id = custinfo.cust_cntct_id AND
corrcntct.cntct_id = custinfo.cust_corrcntct_id;
PostgreSQL won't actually let you test an integer field for a blank value (unless you're using a truly ancient version - 8.2 or older), so you must be using a query generator that's "helpfully" transforming '' to NULL or a tool that's ignoring errors.
Observe this, on Pg 9.2:
regress=> CREATE TABLE test ( a integer );
CREATE TABLE
regress=> insert into test (a) values (1),(2),(3);
INSERT 0 3
regress=> SELECT a FROM test WHERE a = '';
ERROR: invalid input syntax for integer: ""
LINE 1: SELECT a FROM test WHERE a = '';
If you are attempting to test for = NULL, this is not correct. You must use IS NOT NULL or IS DISTINCT FROM NULL instead. Testing for = NULL always results in NULL, which is treated as false in a WHERE clause.
Example:
regress=> insert into test (a) values (null);
INSERT 0 1
regress=> SELECT a FROM test WHERE a = NULL;
a
---
(0 rows)
regress=> SELECT a FROM test WHERE a IS NULL;
a
---
(1 row)
regress=> SELECT NULL = NULL as wrong, NULL IS NULL AS right;
wrong | right
-------+-------
| t
(1 row)
By the way, you should really be using ANSI JOIN syntax. It's more readable and it's much easier to forget to put a condition in and get a cartesian product by accident. I'd rewrite your query for identical functionality and performance but better readability as:
SELECT
cntct.cntct_email AS cntct_email,
corrcntct.cntct_email AS corrcntct_email
FROM
public.custinfo ci
INNER JOIN public.invchead
ON (invchead.invchead_cust_id = ci.cust_id)
INNER JOIN public.cntct
ON (cntct.cntct_id = ci.cust_cntct_id)
INNER JOIN public.cntct corrcntct
ON (corrcntct.cntct_id = ci.cust_corrcntct_id);
Use of table aliases usually keeps it cleaner; here I've aliased the longer name custinfo to ci for brevity.

Resources