SQL Server SP error Msg 4145, Level 15, State 1 - sql

When I try to run individual query for select and/or insert, it works. When I put it in SP and run it for all DB, it errors out.
Msg 4145, Level 15, State 1, Line 45 An expression of non-boolean type
specified in a context where a condition is expected, near 'Addr'.
Declare #command varchar(MAX)
Select #command = '
USE [?]
IF DB_NAME() not like ''%_VER''
BEGIN RETURN END
ElSE
BEGIN
Insert Into [TEST_VER].[dbo].[TestTable]
(ClientName,ID,Type,Filled,FilledDate,Cancelled,CancelledDate,CancellationReason,Deleted,NumberOfPosition,Address,City,State,Country,Annual,AnnualMax,FeePercentage,FeeTotal,GrossProfit,NetProfit,Rate,OTRate,CRate,COTRate,GrossProfit,GrossMargin,ProfitMargin,RegularMarkup,OTMarkup)
select
DB_NAME() as ClientName,
ID,
Type.Description as Type,
Filled,
FilledDate,
Cancelled,
CancelledDate,
CancellationReason.Description as CancellationReason,
Item.Deleted,
NumberOfPosition,
Address.Description as Address,
City.Description as City,
ProvinceState.Description as State,
Country.Description as Country,
PayP.Annual,
PayP.AnnualMaximum,
PayP.FeePercentage,
PayP.FeeTotal,
PayP.GrossProfit,
PayP.NetProfit,
PayT.Rate,
PayT.OTRate,
PayT.CRate,
PayT.COTRate,
PayT.GrossProfit,
PayT.GrossMargin,
PayT.ProfitMargin,
PayT.RegularMarkup,
PayT.OTMarkup
from [Item]
left join [Type] on Item.TypeID = Type.TypeID AND Type.LanguageId = 1
left join [CancellationReason] on Item.CancellationReasonID = CancellationReason.CancellationReasonID AND CancellationReason.LanguageID = 1
left join [Address] on Item.LocationID = Address.AddressID
left join [City] on Address.CityId = City.CityID
left join [ProvinceState] on Address.ProvinceStateId = ProvinceState.ProvinceStateID
left join [Country] on Address.CountryId = Country.CountryID
left join [PayP] on (Item.PaymentID=PayP.ID and Item.TypeID = 1)
left join [PayT] on (Item.PaymentID=PayT.ID and Item.TypeID > 1)
END'
EXEC sp_MSforeachdb #command

The short response is that your sql command is too long.
This undocumented stored procedure sp_msforeachdb doesn't accept a varchar as long as varchar(max). In fact, on SQL SERVER 2019 as seen in the db -fiddle below it only accepts 2000 characters which results in part of your query being cut off. As a result your join expression is incomplete resulting in the error message Msg 4145, Level 15, State 1, Line 45 An expression of non-boolean type specified in a context where a condition is expected, near 'Addr'..
I would recommend a shorter query or creating a stored procedure with your query in a generally accessible db and then running the command from there or rewriting your query to run differently. I've also included a few suggestions at the end of this answer.
You may use the stored procedure as I have used below sp_helptext to find out more about the definition in your db version.
The code and reproducible sample fiddle to prove the above is shown below.
select ##version;
GO
| (No column name) |
| :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Microsoft SQL Server 2019 (RTM-CU6) (KB4563110) - 15.0.4053.23 (X64) <br> Jul 25 2020 11:26:55 <br> Copyright (C) 2019 Microsoft Corporation<br> Express Edition (64-bit) on Windows Server 2019 Standard 10.0 <X64> (Build 17763: ) (Hypervisor)<br> |
exec sp_helptext 'sp_msforeachdb'
GO
| Text |
| :---------------------------------------------------------------------------------------------------------------------------------------------------
/*
* The following table definition will be created by SQLDMO at start of each connection.
* We don't create it here temporarily because we need it in Exec() or upgrade won't work.
*/
create proc sys.sp_MSforeachdb
#command1 nvarchar(2000), #replacechar nchar(1) = N'?', #command2 nvarchar(2000) = null, #command3 nvarchar(2000) = null,
#precommand nvarchar(2000) = null, #postcommand nvarchar(2000) = null
as
set deadlock_priority low
/* This proc returns one or more rows for each accessible db, with each db defaulting to its own result set */
/* #precommand and #postcommand may be used to force a single result set via a temp table. */
/* Preprocessor won't replace within quotes so have to use str(). */
declare #inaccessible nvarchar(12), #invalidlogin nvarchar(12), #dbinaccessible nvarchar(12)
select #inaccessible = ltrim(str(convert(int, 0x03e0), 11))
select #invalidlogin = ltrim(str(convert(int, 0x40000000), 11))
select #dbinaccessible = N'0x80000000' /* SQLDMODbUserProf_InaccessibleDb; the negative number doesn't work in convert() */
if (#precommand is not null)
exec(#precommand)
declare #origdb nvarchar(128)
select #origdb = db_name()
/* If it's a single user db and there's an entry for it in sysprocesses who isn't us, we can't use it. */
/* Create the select */
exec(N'declare hCForEachDatabase cursor global for select name from master.dbo.sysdatabases d ' +
N' where (d.status & ' + #inaccessible + N' = 0)' +
N' and (DATABASEPROPERTYEX(d.name, ''UserAccess'') <> ''SINGLE_USER'' and (has_dbaccess(d.name) = 1))' )
declare #retval int
select #retval = ##error
if (#retval = 0)
exec #retval = sys.sp_MSforeach_worker #command1, #replacechar, #command2, #command3, 1
if (#retval = 0 and #postcommand is not null)
exec(#postcommand)
declare #tempdb nvarchar(258)
SELECT #tempdb = REPLACE(#origdb, N']', N']]')
exec (N'use ' + N'[' + #tempdb + N']')
return #retval
Here I run the query with the 2000 max and you will see that part of the query is cut off
Declare #command varchar(2000)
Select #command = '
USE [?]
IF DB_NAME() not like ''%_VER''
BEGIN RETURN END
ElSE
BEGIN
Insert Into [TEST_VER].[dbo].[TestTable]
(ClientName,ID,Type,Filled,FilledDate,Cancelled,CancelledDate,CancellationReason,Deleted,NumberOfPosition,Address,City,State,Country,Annual,AnnualMax,FeePercentage,FeeTotal,GrossProfit,NetProfit,Rate,OTRate,CRate,COTRate,GrossProfit,GrossMargin,ProfitMargin,RegularMarkup,OTMarkup)
select
DB_NAME() as ClientName,
ID,
Type.Description as Type,
Filled,
FilledDate,
Cancelled,
CancelledDate,
CancellationReason.Description as CancellationReason,
Item.Deleted,
NumberOfPosition,
Address.Description as Address,
City.Description as City,
ProvinceState.Description as State,
Country.Description as Country,
PayP.Annual,
PayP.AnnualMaximum,
PayP.FeePercentage,
PayP.FeeTotal,
PayP.GrossProfit,
PayP.NetProfit,
PayT.Rate,
PayT.OTRate,
PayT.CRate,
PayT.COTRate,
PayT.GrossProfit,
PayT.GrossMargin,
PayT.ProfitMargin,
PayT.RegularMarkup,
PayT.OTMarkup
from [Item]
left join [Type] on Item.TypeID = Type.TypeID AND Type.LanguageId = 1
left join [CancellationReason] on Item.CancellationReasonID = CancellationReason.CancellationReasonID AND CancellationReason.LanguageID = 1
left join [Address] on Item.LocationID = Address.AddressID
left join [City] on Address.CityId = City.CityID
left join [ProvinceState] on Address.ProvinceStateId = ProvinceState.ProvinceStateID
left join [Country] on Address.CountryId = Country.CountryID
left join [PayP] on (Item.PaymentID=PayP.ID and Item.TypeID = 1)
left join [PayT] on (Item.PaymentID=PayT.ID and Item.TypeID > 1)
END'
select 1
select #command
GO
| (No column name) |
| ---------------: |
| 1 |
USE [?]
IF DB_NAME() not like '%_VER'
BEGIN RETURN END
ElSE
BEGIN
Insert Into [TEST_VER].[dbo].[TestTable]
(ClientName,ID,Type,Filled,FilledDate,Cancelled,CancelledDate,CancellationReason,Deleted,NumberOfPosition,Address,City,State,Country,Annual,AnnualMax,FeePercentage,FeeTotal,GrossProfit,NetProfit,Rate,OTRate,CRate,COTRate,GrossProfit,GrossMargin,ProfitMargin,RegularMarkup,OTMarkup)
select
DB_NAME() as ClientName,
ID,
Type.Description as Type,
Filled,
FilledDate,
Cancelled,
CancelledDate,
CancellationReason.Description as CancellationReason,
Item.Deleted,
NumberOfPosition,
Address.Description as Address,
City.Description as City,
ProvinceState.Description as State,
Country.Description as Country,
PayP.Annual,
PayP.AnnualMaximum,
PayP.FeePercentage,
PayP.FeeTotal,
PayP.GrossProfit,
PayP.NetProfit,
PayT.Rate,
PayT.OTRate,
PayT.CRate,
PayT.COTRate,
PayT.GrossProfit,
PayT.GrossMargin,
PayT.ProfitMargin,
PayT.RegularMarkup,
PayT.OTMarkup
from [Item]
left join [Type] on Item.TypeID = Type.TypeID AND Type.LanguageId = 1
left join [CancellationReason] on Item.CancellationReasonID = CancellationReason.CancellationReasonID AND CancellationReason.LanguageID = 1
left join [Address] on Item.LocationID = Address.AddressID
left join [City] on Address.CityId = City.CityID
left join [ProvinceState] on Address.ProvinceStateId = P
db<>fiddle here
#Larnu has also made some suggestions about alternatives such as option 1 and option 2 which you may consider in your own time.

Related

Replacing Is Null Or Exist Inner Query Logic to Reduce Stored Procedure Execution Time

My stored procedure is currently using Is Null Or Exist logic combined with an inner query to filter out the records. The stored procedure is converting multiple comma-separated input values to temp tables (in the production scenario, the input record count will be much higher). And the inner query is using these temp tables for filter conditions. Due to the concern over query execution time would like to change the existing inner-query with an alternate (like left join). But need to retain the same Is Null Or Exist logic. Any suggestions?
DECLARE #SelectedOfferes varchar(1000) = 'FLT10,SPL20'
DECLARE #SelectedBrandCode varchar(1000) = '208,406'
DECLARE #CategoryCode varchar(1000) = 'GMOVN2,CELSMR,LCDTV38IN'
CREATE TABLE #SelectedOfferes
(
DiscountCode VARCHAR(20)
)
CREATE TABLE #BrandCode
(
BrandCode VARCHAR(20)
)
CREATE TABLE #CategoryCode
(
CategoryCode VARCHAR(20)
)
IF #SelectedOfferes IS NOT NULL
BEGIN
INSERT INTO #SelectedOfferes
SELECT part
FROM dbo.[FormatTextByDelimiter] (#SelectedOfferes, ',')
END
IF #SelectedBrandCode IS NOT NULL
BEGIN
INSERT INTO #BrandCode
SELECT part
FROM dbo.[FormatTextByDelimiter] (#SelectedBrandCode, ',')
END
IF #CategoryCode IS NOT NULL
BEGIN
INSERT INTO #CategoryCode
SELECT part
FROM dbo.[FormatTextByDelimiter] (#CategoryCode, ',')
END
SELECT *
FROM Products P
INNER JOIN Discount D ON P.DiscountCode = D.DiscountCode
INNER JOIN AvailableBrand AB ON P.BrandCode = AB.BrandCode
INNER JOIN Category C ON P.CategoryCode = C.CategoryCode
WHERE (#SelectedOfferes IS NULL
OR (EXISTS (SELECT 1 FROM #SelectedOfferes OFR
WHERE OFR.DiscountCode = P.DiscountCode)))
AND (#SelectedBrandCode IS NULL
OR (EXISTS (SELECT 1 FROM #BrandCode BC
WHERE BC.BrandCode = P.BrandCode)))
AND (#CategoryCode IS NULL
OR (EXISTS (SELECT 1 FROM #CategoryCode CAT
WHERE CAT.CategoryCode = P.CategoryCode)))
Dynamic SQL version
I have some questions about your string split function, is it set-based or a looping query? If it's not set-based then you should probably replace it with Jeff Moden's DelimitedSplit8K available at http://www.sqlservercentral.com/articles/Tally+Table/72993/ .
The below example should work the same as what you supplied but should be faster since it removes the ORs and the correlated subqueries from the WHERE clause. I'm not a fan of using dynamic SQL but sometimes it is the best way to get the job done. Maybe someone else can come up with a non-dynamic solution that works as well or better.
DECLARE #SelectedOfferes varchar(1000) = 'FLT10,SPL20'
DECLARE #SelectedBrandCode varchar(1000) = '208,406'
DECLARE #CategoryCode varchar(1000) = 'GMOVN2,CELSMR,LCDTV38IN'
CREATE TABLE #SelectedOfferes
(
DiscountCode VARCHAR(20)
)
CREATE TABLE #BrandCode
(
BrandCode VARCHAR(20)
)
CREATE TABLE #CategoryCode
(
CategoryCode VARCHAR(20)
)
IF #SelectedOfferes IS NOT NULL
BEGIN
INSERT INTO #SelectedOfferes
SELECT part
FROM dbo.[FormatTextByDelimiter] (#SelectedOfferes, ',')
END
IF #SelectedBrandCode IS NOT NULL
BEGIN
INSERT INTO #BrandCode
SELECT part
FROM dbo.[FormatTextByDelimiter] (#SelectedBrandCode, ',')
END
IF #CategoryCode IS NOT NULL
BEGIN
INSERT INTO #CategoryCode
SELECT part
FROM dbo.[FormatTextByDelimiter] (#CategoryCode, ',')
END
DECLARE #SQL NVarchar(4000);
SET #SQL = N'SELECT *
FROM Products P
INNER JOIN Discount D ON P.DiscountCode = D.DiscountCode
INNER JOIN AvailableBrand AB ON P.BrandCode = AB.BrandCode
INNER JOIN Category C ON P.CategoryCode = C.CategoryCode'
IF #SelectedOfferes IS NOT NULL
SET #SQL = #SQL + N'
INNER JOIN #SelectedOfferes OFR ON OFR.DiscountCode = P.DiscountCode';
IF #SelectedBrandCode IS NOT NULL
SET #SQL = #SQL + N'
INNER JOIN #BrandCode BC ON BC.BrandCode = P.BrandCode';
IF #CategoryCode IS NOT NULL
SET #SQL = #SQL + N'
INNER JOIN #CategoryCode CAT ON CAT.CategoryCode = P.CategoryCode';
EXEC sys.sp_executesql #stmt = #SQL;
This method doesn't quite do what the OP wanted but is valid in many other cases
I have some questions about your string split function, is it set-based or a looping query? If it's not set-based then you should probably replace it with Jeff Moden's DelimitedSplit8K available at http://www.sqlservercentral.com/articles/Tally+Table/72993/ .
But either way the below change to your last query should help quite a bit. The IS NULL parts aren't needed since it is a LEFT JOIN and the table will be empty if the variable it is built with is NULL, so you get the same result with less work for the engine.
SELECT *
FROM Products P
INNER JOIN Discount D ON P.DiscountCode = D.DiscountCode
INNER JOIN AvailableBrand AB ON P.BrandCode = AB.BrandCode
INNER JOIN Category C ON P.CategoryCode = C.CategoryCode
LEFT JOIN #SelectedOfferes OFR ON OFR.DiscountCode = P.DiscountCode
LEFT JOIN #BrandCode BC ON BC.BrandCode = P.BrandCode
LEFT JOIN #CategoryCode CAT ON CAT.CategoryCode = P.CategoryCode

SELECT Column AS #ParameterName in SQL Server stored procedure

I have a stored procedure and I need to pass in a column alias as a parameter, how can I make this work?
This is the line of the stored procedure giving me trouble:
ManufacturerPriceListQty.Price As #PriceLevelAlias
and here is the stored procedure:
ALTER PROCEDURE [dbo].[Export_Products]
#PriceLevelAlias AS VARCHAR(25),
#PriceListCodes AS VARCHAR(250) --Exmaple: 'Des', 'Designer', 'Non-Stocking', 'NonStocking'
AS
BEGIN
SET NOCOUNT ON;
--PRINT #PriceListCodes
--SELECT * FROM dbo.Split(#PriceListCodes,',')
-- Insert statements for procedure here
SELECT
CAST(p.ManufacturerID as varchar(2))+'-'+p.ProductNumber AS ItemID,
SUBSTRING(p.ProductName,0,100) as ItemName,
p.ProductName AS [Description],
ManufacturerPriceListQty.Price As #PriceLevelAlias,
ManufacturerPriceListQty.Qty as OnHandQuantity,
ManufacturerPriceListQty.MultipleQty as OrderMinimumQuantity,
ManufacturerPriceListQty.MultipleQty as OrderMultipleQuantity,
Manufacturer.CompanyName AS CatalogName,
Manufacturer.CompanyName AS CatalogCode,
p.ProductNumber as UDF1,
CAST(p.ManufacturerID as varchar(2)) AS UDF2,
'%'+CAST(p.ProductID as varchar(10)) as UDF5,
CASE
WHEN P.Active ='1' THEN 'FALSE'
ELSE 'TRUE'
END AS IsDeleted,
#PriceLevelAlias AS PriceLevel,
ManufacturerPriceList.PriceListCode,
ManufacturerPriceListProduct.PriceListID
FROM
ManufacturerPriceListProduct
INNER JOIN
ManufacturerPriceList ON ManufacturerPriceListProduct.PriceListID = ManufacturerPriceList.PriceListID
INNER JOIN
Manufacturer ON ManufacturerPriceList.ManufacturerID = Manufacturer.ManufacturerID
INNER JOIN
ManufacturerPriceListQty ON ManufacturerPriceListProduct.PriceListProductID = ManufacturerPriceListQty.PriceListProductID
INNER JOIN
Product p ON ManufacturerPriceListProduct.ProductID = p.ProductID
WHERE
(Manufacturer.Active = 1)
AND p.Discontinued = 0
AND PriceListCode IN (SELECT * FROM dbo.Split(#PriceListCodes, ','))
END
instead of the direct script, try using the final select statement as Query string. Try the below :
ALTER PROCEDURE [dbo].[Export_Products]
#PriceLevelAlias AS VARCHAR(25),
#PriceListCodes AS VARCHAR(250) --Exmaple: 'Des', 'Designer', 'Non-Stocking', 'NonStocking'
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
--PRINT #PriceListCodes
--SELECT * FROM dbo.Split(#PriceListCodes,',')
-- Insert statements for procedure here
DECLARE #v_Qry VARCHAR(MAX)
SELECT
#v_Qry = '
SELECT
CAST(p.ManufacturerID as varchar(2))+''-''+p.ProductNumber AS ItemID
,SUBSTRING(p.ProductName,0,100) as ItemName
,p.ProductName AS [Description]
,ManufacturerPriceListQty.Price As '+#PriceLevelAlias+'
,ManufacturerPriceListQty.Qty as OnHandQuantity
,ManufacturerPriceListQty.MultipleQty as OrderMinimumQuantity
,ManufacturerPriceListQty.MultipleQty as OrderMultipleQuantity
,Manufacturer.CompanyName AS CatalogName
,Manufacturer.CompanyName AS CatalogCode
,p.ProductNumber as UDF1
,CAST(p.ManufacturerID as varchar(2)) AS UDF2
,''%''+CAST(p.ProductID as varchar(10)) as UDF5
,CASE
WHEN P.Active =''1'' THEN ''FALSE''
ELSE ''TRUE''
END AS IsDeleted
,#PriceLevelAlias AS PriceLevel
,ManufacturerPriceList.PriceListCode
,ManufacturerPriceListProduct.PriceListID
FROM ManufacturerPriceListProduct INNER JOIN
ManufacturerPriceList ON ManufacturerPriceListProduct.PriceListID = ManufacturerPriceList.PriceListID INNER JOIN
Manufacturer ON ManufacturerPriceList.ManufacturerID = Manufacturer.ManufacturerID INNER JOIN
ManufacturerPriceListQty ON ManufacturerPriceListProduct.PriceListProductID = ManufacturerPriceListQty.PriceListProductID INNER JOIN
Product p ON ManufacturerPriceListProduct.ProductID = p.ProductID
WHERE (Manufacturer.Active = 1)
AND p.Discontinued=0
AND PriceListCode
IN(SELECT * FROM dbo.Split(#PriceListCodes,'',''))'
EXEC(#v_Qry)
END
It is not possible to set a column's alias dynamically. The only way to achieve this, is to create the full statement dynamically (there is an answer already).
With a closed set of possible values you could use a structure with IF to define your statement in all possible combinations.
The following might help you, if you have a closed set of values and the calling code knows, which column name is to be used.
As your SP is returning a result set, you might include the same value with all possible names. Look at this example
SELECT *
,type_desc AS Name1
,type_desc AS Name2
,type_desc AS Name3
--add all possible types here
FROM sys.objects
Which column you use after the call, will then be the decision of the calling code...
There were two other variables that need to be taken outside of the string:
`ALTER PROCEDURE [dbo].[Export_Products]
#PriceLevelAlias AS VARCHAR(25),
#PriceListCodes AS VARCHAR(250) --Exmaple: 'Des', 'Designer', 'Non-Stocking', 'NonStocking'
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
--PRINT #PriceListCodes
--SELECT * FROM dbo.Split(#PriceListCodes,',')
-- Insert statements for procedure here
DECLARE #v_Qry VARCHAR(MAX)
SELECT
#v_Qry = '
SELECT
CAST(p.ManufacturerID as varchar(2))+''-''+p.ProductNumber AS ItemID
,SUBSTRING(p.ProductName,0,100) as ItemName
,p.ProductName AS [Description]
,ManufacturerPriceListQty.Price As '+#PriceLevelAlias+'
,ManufacturerPriceListQty.Qty as OnHandQuantity
,ManufacturerPriceListQty.MultipleQty as OrderMinimumQuantity
,ManufacturerPriceListQty.MultipleQty as OrderMultipleQuantity
,Manufacturer.CompanyName AS CatalogName
,Manufacturer.CompanyName AS CatalogCode
,p.ProductNumber as UDF1
,CAST(p.ManufacturerID as varchar(2)) AS UDF2
,''%''+CAST(p.ProductID as varchar(10)) as UDF5
,CASE
WHEN P.Active =''1'' THEN ''FALSE''
ELSE ''TRUE''
END AS IsDeleted
,''' + #PriceLevelAlias + ''' AS PriceLevel
,ManufacturerPriceList.PriceListCode
,ManufacturerPriceListProduct.PriceListID
FROM ManufacturerPriceListProduct INNER JOIN
ManufacturerPriceList ON ManufacturerPriceListProduct.PriceListID = ManufacturerPriceList.PriceListID INNER JOIN
Manufacturer ON ManufacturerPriceList.ManufacturerID = Manufacturer.ManufacturerID INNER JOIN
ManufacturerPriceListQty ON ManufacturerPriceListProduct.PriceListProductID = ManufacturerPriceListQty.PriceListProductID INNER JOIN
Product p ON ManufacturerPriceListProduct.ProductID = p.ProductID
WHERE (Manufacturer.Active = 1)
AND p.Discontinued=0
AND PriceListCode
IN(SELECT * FROM dbo.Split(''' + #PriceListCodes + ''','',''))'
EXEC(#v_Qry)
END`

SELECT case using a variable which can be set based on a parameter

I'd like to select a particular value from a table while using an information from another database that is set based on a current database's value.
So a select case to find the operator code and set the DB path.. then use the same path and collate the result.
DECLARE #DB varchar (1000)
CASE
WHEN #Operator= 1 THEN SET #DB = '{SERVERNAME\ENTITY\DBNAME}'
WHEN #Operator= 2 THEN SET #DB = '{SERVERNAME2\ENTITY2\DBNAME2}'
WHEN #Operator= 3 THEN SET #DB = '{SERVERNAME3\ENTITY3\DBNAME3}'
Select transItem_item collate SQL_Latin1General_CI_AS
FROM Group_Transactions
INNER JOIN #DB.Table_Trans
ON (transItem.item_id collate SQL_Latin1General_CI-AS = Table_Trans.item_id)
Where ---Condition
Control flow method (likely to be the most efficient):
IF #Operator = 1
BEGIN
SELECT stuff
FROM Group_Transactions
INNER
JOIN "Server1\Instance1".Database1.Schema.Table_Trans
ON Group_Transactions... = Table_Trans...
WHERE things...
;
END
ELSE IF #Operator = 2
BEGIN
SELECT stuff
FROM Group_Transactions
INNER
JOIN "Server2\Instance2".Database2.Schema.Table_Trans
ON Group_Transactions... = Table_Trans...
WHERE things...
;
END
ELSE IF #Operator = 3
BEGIN
SELECT stuff
FROM Group_Transactions
INNER
JOIN "Server3\Instance3".Database3.Schema.Table_Trans
ON Group_Transactions... = Table_Trans...
WHERE things...
;
END
;
Single [conditional] query method:
SELECT Group_Transactions.stuff
, trans1.other_thing As other_thing1
, trans2.other_thing As other_thing2
, trans3.other_thing As other_thing3
, Coalesce(trans1.other_thing, trans2.other_thing, trans3.other_thing) As other_thing
FROM Group_Transactions
LEFT
JOIN "Server1\Instance1".Database1.Schema.Table_Trans As trans1
ON trans1... = Group_Transactions...
AND trans1.things...
AND #Operator = 1
LEFT
JOIN "Server2\Instance2".Database2.Schema.Table_Trans As trans2
ON trans2... = Group_Transactions...
AND trans2.things...
AND #Operator = 2
LEFT
JOIN "Server3\Instance3".Database3.Schema.Table_Trans As trans3
ON trans3... = Group_Transactions...
AND trans3.things...
AND #Operator = 3
;
If this is TSQL (I am guessing from your colation names) then you are best trying out OPENQUERY to run your join against another database server. If you are querying a database on the same server you could build your query up as a parameter and then run it using EXEC.
Gvee's Control Flow method may be a verbose, but it would work. You might want to create a look up table like my #tbl_Databases if you have a bunch of databases. Here's a dynamic SQL solution:
DECLARE #Operator INT = 1,
#DB VARCHAR(1000);
DECLARE #tbl_Databases TABLE (ID INT IDENTITY(1,1),DB VARCHAR(1000))
INSERT INTO #tbl_Databases(DB)
VALUES ('{SERVERNAME\ENTITY\DBNAME}'),('{SERVERNAME2\ENTITY2\DBNAME2}'),('{SERVERNAME3\ENTITY3\DBNAME3}');
SELECT #DB = DB
FROM #tbl_Databases
WHERE ID = #Operator
SELECT #DB
SELECT
(
'SELECT transItem_item COLLATE SQL_Latin1General_CI_AS
FROM Group_Transactions
INNER JOIN ' + #DB + '.dbo.Table_Trans
ON (transItem.item_id collate SQL_Latin1General_CI-AS = Table_Trans.item_id)
Where 1 = 1'
)

SQL WHERE ... IN clause with possibly null parameter

I am having some problems with my WHERE clause (using SQL 2008) . I have to create a stored procedure that returns a list of results based on 7 parameters, some of which may be null. The ones which are problematic are #elements, #categories and #edu_id. They can be a list of ids, or they can be null. You can see in my where clause that my particular code works if the parameters are not null. I'm not sure how to code the sql if they are null. The fields are INT in the database.
I hope my question is clear enough. Here is my query below.
BEGIN
DECLARE #elements nvarchar(30)
DECLARE #jobtype_id INT
DECLARE #edu_id nvarchar(30)
DECLARE #categories nvarchar(30)
DECLARE #full_part bit
DECLARE #in_demand bit
DECLARE #lang char(2)
SET #jobtype_id = null
SET #lang = 'en'
SET #full_part = null -- full = 1, part = 0
SET #elements = '1,2,3'
SET #categories = '1,2,3'
SET #edu_id = '3,4,5'
select
jobs.name_en,
parttime.fulltime_only,
jc.cat_id category,
je.element_id elem,
jt.name_en jobtype,
jobs.edu_id minEdu,
education.name_en edu
from jobs
left join job_categories jc
on (jobs.job_id = jc.job_id)
left join job_elements je
on (jobs.job_id = je.job_id)
left join job_type jt
on (jobs.jobtype_id = jt.jobtype_id)
left join education
on (jobs.edu_id = education.edu_id)
left join
(select job_id, case when (jobs.parttime_en IS NULL OR jobs.parttime_en = '') then 1 else 0 end fulltime_only from jobs) as parttime
on jobs.job_id = parttime.job_id
where [disabled] = 0
and jobs.jobtype_id = isnull(#jobtype_id,jobs.jobtype_id)
and fulltime_only = isnull(#full_part,fulltime_only)
-- each of the following clauses should be validated to see if the parameter is null
-- if it is, the clause should not be used, or the SELECT * FROM ListToInt... should be replaced by
-- the field evaluated: ie if #elements is null, je.element_id in (je.element_id)
and je.element_id IN (SELECT * FROM ListToInt(#elements,','))
and jc.cat_id IN (SELECT * FROM ListToInt(#categories,','))
and education.edu_id IN (SELECT * FROM ListToInt(#edu_id,','))
order by case when #lang='fr' then jobs.name_fr else jobs.name_en end;
END
Something like
and (#elements IS NULL OR je.element_id IN
(SELECT * FROM ListToInt(#elements,',')))
and (#categories IS NULL OR
jc.cat_id IN (SELECT * FROM ListToInt(#categories,',')))
....
should do the trick
je.element_id IN (SELECT * FROM ListToInt(#elements,',')) OR #elements IS NULL
that way for each one
Have you tried explicitly comparing to NULL?
and (#elements is null or je.element_id IN (SELECT * FROM ListToInt(#elements,','))
And so on.

The server principal "sa" is not able to access the database under the current security context

I believe there are many StackOverflow posts related to this error, but none seems to have a straightforward solution which I am looking for. I'll be thankful if anyone of you can look into this.
The issue: I am using a dynamic sql stored procedure which uses FundTransfer Database tables in cte expression and then joins with WebbnkDb database.
But, I run into the exception mentioned in the title above. Everything works fine if I remove WITH EXECUTE AS SELF command but unfortunately I can't get rid of it as it is used for some security reasons. Please suggest me solution in easy words.
USE [WebBank]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[usp_naSearchV2_20131504]
#Debug BIT = 0,
#UserName varchar(50)=NULL, --This will be used to potentially limit the results per user & also for logging
#SSN char(9) = NULL,
#FName varchar(25) = NULL,
#LName varchar(30) = NULL,
#dtApplicationStart datetime = NULL,
#dtApplicationEnd datetime = NULL,
#CompanyName varchar(50) = NULL,
#DaysInTask int = NULL, --This will be how many days it's been in the current task...
#AcctNum varchar(11) = NULL,
#BranchNums varchar(1500) = NULL, --This will be passed to an IN. Don't enclose each in single quotes - for example, '45, 145, 1, 15'
#WorkflowID int = NULL, --1 = HSA, 2 = Personal, 3 = SEI
#OriginationID tinyint = NULL, --This comes from the Applicant record.
#QueueID int = NULL,
#TaskStageIDs varchar(500) = NULL, --Will be passed to an IN, so multiple TaskStageIDs can be passed.
#TaskIDs VARCHAR(1500)=NULL,
#DaysAged int = NULL, --Days since application was entered (not including time spent in approved/declined/open states)
#LastActivityStart datetime=NULL,
#LastActivityEnd datetime=NULL,
#SOAApplID int = NULL, --SEI ID
#Market VARCHAR(50) = NULL, --from luAffinityMarket
#IncludeSecondary bit=0,
#IncludeAliasName bit=0,
#EmailTypeIDs varchar(500) = NULL
WITH EXECUTE AS SELF --This is needed because we're using dynamic SQL & don't want to grant access to underlying tables.
AS
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
/*
** New Account - Search.
**
** This will be done in dynamic SQL. The reason is because when searching on multiple optional parameters,
** SQL cannot use indexes without using dynamic SQL. This makes the proc sssssslllllooooooooowwwwwwwwwww (when not using dynamic sql).
** See http://www.sommarskog.se/dyn-search-2005.html
**
** In addition to the basics (name, social, branch, product, "workflow"), also show Task, Queue, Check-Out info, etc
**
*/
/*
I have to create new version of this store procedure since we continue making changes to resolve helpdesk tickets and
for AOT Part 2. Some tables that we are going to use for AOT project part 2 will not be moved to production until 12/05/10.
New version will be called usp_naSearchV2 and will contain new tables for AOT Part 2
*/
--CAST(ROUND(ISNULL(cteAge.Age + 1, 0), 2) AS DECIMAL(8, 2)) AS DaysAged,
DECLARE #SQL nvarchar(max),#paramlist nvarchar(max)
DECLARE #SOASQL nvarchar(MAX)
SET #FName = '%' + #FName + '%'
SET #LName = '%' + #LName + '%'
SET #CompanyName = '%' + #CompanyName + '%'
SELECT #SQL = '
WITH
cteAutoApprove (AcctID, AutoApproved)
AS (
SELECT awt.AcctID, MIN(CAST(awt.autoEnter AS SMALLINT)) AS AutoApproved
FROM dbo.AccountWorkflowTask awt JOIN dbo.WorkflowTask wt ON awt.WorkflowTaskID = wt.WorkflowTaskID
WHERE (wt.TaskID IN (9, 17) AND ReasonIDExit = 1)
OR (wt.TaskID IN (209, 309, 409, 509, 609, 709, 809, 909) AND ReasonIDExit = 40)
--OR ReasonIDExit IN(216,202) OR ReasonIDEnter=215
or(wt.TaskID=201 and ReasonIDExit is NULL) GROUP BY awt.AcctID),
cteAge (AcctID, Age)
AS (SELECT AcctID, SUM(CASE WHEN t.TaskStageID IN (2, 3, 4) OR t.TaskID = 1 THEN 0 '--don''t count Pending Completion, Open, Approved, or Declined in age
+ 'ELSE DATEDIFF(minute, dtEnter, ISNULL(dtExit, GETDATE())) END) / 60 / 24.0 Age
FROM dbo.AccountWorkflowTask awt JOIN WorkflowTask wt ON awt.WorkflowTaskID = wt.WorkflowTaskID JOIN Task t ON wt.TaskID = t.TaskID
GROUP BY AcctID),
**cteFundingStatus(AcctID,FundingStatus,SourceAccountTypeDescription)
AS
(SELECT TransferStaging.AcctID,luTransferStatus.TransferStatusDesc, luAcctType.AcctTypeDesc from
FundsTransfer.dbo.TransferStaging
JOIN FundsTransfer.dbo.luTransferType ON luTransferType.TransferTypeID = TransferStaging.TransferTypeID
JOIN FundsTransfer.dbo.luAcctType ON luTransferType.SourceAcctTypeID = luAcctType.AcctTypeID
JOIN FundsTransfer.dbo.luTransferStatus ON luTransferStatus.TransferStatusID = TransferStaging.TransferStatusID),**
cteFulfillment(AcctID, Request, TemplateName)
AS
(SELECT ful.AcctID, CAST(Request AS NVARCHAR(max))Request, lt.TemplateName FROM dbo.fulfillment ful left join LetterRequest lr on lr.LetterID = ful.LetterID
LEFT JOIN luLetterTemplate lt ON lt.TemplateID = lr.TemplateID
WHERE (Request IS NOT NULL OR ful.LetterID IS NOT NULL) AND FulfillmentID=(SELECT MAX(FulfillmentID) FROM fulfillment sub WHERE ful.AcctID=sub.AcctID AND (Request IS NOT NULL OR LetterID IS NOT NULL)) ),
cteNote(AcctID,userEntered,dtEntered,Note,NoteReasonDesc,ReasonCode,NoteReasonID)
as
(SELECT AcctID,userEntered,dtEntered,Note,NoteReasonDesc,ReasonCode,n.NoteReasonID FROM note n JOIN
dbo.luNoteReason lu ON lu.NoteReasonID=n.NoteReasonID WHERE '
IF #EmailTypeIDs IS NOT NULL
SELECT #SQL=#SQL+' n.NoteReasonID IN (' + #EmailTypeIDs + ') AND '
SELECT #SQL=#SQL+ ' dtEntered=(SELECT MAX(dtEntered)FROM note sub WHERE sub.AcctId=n.AcctID '
IF #EmailTypeIDs IS NOT NULL
SELECT #SQL=#SQL+ ' AND sub.NoteReasonID IN (' + #EmailTypeIDs + ')'
SELECT #SQL=#SQL+')) '
SELECT #SQL=#SQL+'SELECT a.ApplID, acct.AcctID, acct.dtApplication, ai.FName, ai.MName, ai.LName, ai.SSN, a.Email, ao.CompanyName,'
SELECT #SQL=#SQL+'ao.DBAName, ao.TaxID, acct.AcctNum, acct.AcctAffinityNum, luA.AffinityNum, luA.AffinityName, t.TaskDesc, awt.dtEnter,'
SELECT #SQL=#SQL+'DATEDIFF(day, awt.dtEnter, GETDATE()) + 1 DaysInTask, q.QueueDesc, w.WorkflowID, w.WorkflowDesc,'
SELECT #SQL=#SQL+'luO.OriginationID, luO.OriginationDesc, aco.dtCheckOut, aco.UserCheckOut, aco.GUIDCheckout, lts.TaskStageDesc,'
SELECT #SQL=#SQL+'DATEDIFF(day, acct.dtApplication, GETDATE()) + 1 DaysAgedOld,CAST(ROUND(ISNULL(cteAge.Age + 1, 0), 2) AS int) AS DaysAged,'
SELECT #SQL=#SQL+'asa.SOAApplID, case when (w.WorkflowID=1 and luO.OriginationID=4) then ''Low''when luO.OriginationID=9 then ''Low'''
SELECT #SQL=#SQL+'else''High'' end as RiskType, awt.userEnter, awt.dtEnter, case when cteAutoApprove.AutoApproved=1 then ''Automated'''
SELECT #SQL=#SQL+'when cteAutoApprove.AutoApproved=0 then ''Manual'' else '''' end as DecisionType,acctLam.Market,ful.Request,ful.TemplateName,fun.SourceAccountTypeDescription,fun.FundingStatus, acct.BrokerCode,
COALESCE(ai.SSN, ao.TAXID) as TIN, case when bup.BusPurposeDesc like ''%Other%'' then ao.BusPurposeOther else bup.BusPurposeDesc end as BusPurpose
,note.Note,note.NoteReasonDesc,note.ReasonCode,aa.RelationshipCode,luRel.RelationshipCodeDesc, Addr.Address1, Addr.Address2, Addr.City, Addr.State, Addr.Zip FROM dbo.Applicant a JOIN dbo.APPLICANTACCOUNT aa ON a.ApplID = aa.ApplID '
IF #IncludeSecondary=0
SELECT #SQL=#SQL+' AND aa.RelationshipCode = ''000'' '
SELECT #SQL=#SQL+'LEFT JOIN dbo.ApplicantIndiv ai ON a.ApplID = ai.ApplID LEFT JOIN dbo.ApplicantOrg ao ON a.ApplID = ao.ApplID JOIN dbo.AFFINITYGROUP ag ON a.AffGroupID = ag.AffGroupID JOIN dbo.luAffinity luA ON ag.AffinityID = luA.AffinityID
JOIN dbo.Account acct ON aa.AcctID = acct.AcctID JOIN dbo.AccountWorkflowTask awt ON acct.AcctID = awt.AcctID AND awt.dtExit IS NULL --join to current AccountWorkflowTask
JOIN dbo.WorkflowTask wt ON awt.WorkflowTaskID = wt.WorkflowTaskID JOIN dbo.Task t ON wt.TaskID = t.TaskID
JOIN dbo.Workflow w ON wt.WorkflowID = w.WorkflowID JOIN dbo.luTaskStage lts ON t.TaskStageID = lts.TaskStageID
LEFT JOIN dbo.Queue q ON t.QueueID = q.QueueID LEFT JOIN dbo.luOrigination luO on a.OriginationID = luO.OriginationID
LEFT JOIN dbo.accountCheckOut aco ON acct.AcctID = aco.AcctID AND aco.dtCheckIn IS NULL LEFT JOIN AccountSOAApplication asa ON acct.AcctID = asa.AcctID
LEFT JOIN cteAutoApprove on cteAutoApprove.AcctID = acct.AcctID LEFT JOIN cteAge ON cteAge.AcctID = acct.AcctID
LEFT JOIN luAffinityMarket lam ON CAST(luA.AffinityNum AS INT) = CAST(lam.BRNCH_NBR AS INT) LEFT JOIN luAffinityMarket acctLam ON acct.AcctAffinityNum = CAST(acctLam.BRNCH_NBR AS INT)
LEFT JOIN cteFulfillment ful on acct.AcctID=ful.AcctID
left Join **cteFundingStatus** fun on fun.AcctID=acct.AcctID
left Join luBusPurpose bup on bup.BusPurposeID=ao.BusPurposeID
Left join cteNote note on acct.AcctID=note.AcctID
left join luRelationshipCode luRel on aa.RelationshipCode=luRel.RelationshipCode
LEFT JOIN Address Addr ON Addr.ApplID = aa.ApplID AND Addr.AddrTypeID = 1
WHERE 1 = 1 ' --this is in here so that the following statements in the WHERE clause can start with "AND (...)".
-- IF #debug = 1 PRINT LEN(#SQL) v_AOTInitialAccountFunding
--SELECT #SQL = REPLACE(#SQL, CHAR(9), '') --remove tabs to save string size
IF #debug = 1 PRINT LEN(#SQL)
IF #SSN IS NOT NULL
SELECT #sql = #sql + ' AND (ai.SSN = #xSSN OR REPLACE(ao.TaxID, ''-'', '''') = #xSSN)'
IF #IncludeAliasName <>1 AND #FName IS NOT NULL
SELECT #sql = #sql + ' AND (ai.FName LIKE #xFName)'
IF #IncludeAliasName <>1 AND #LName IS NOT NULL
SELECT #sql = #sql + ' AND (ai.LName LIKE #xLName)'
IF #IncludeAliasName <>0 AND #FName IS NOT NULL
SELECT #sql = #sql + ' AND (ai.AliasFName LIKE #xFName OR ai.FName LIKE #xFName)'
IF #IncludeAliasName <>0 AND #LName IS NOT NULL
SELECT #sql = #sql + ' AND (ai.AliasLName LIKE #xLName OR ai.LName LIKE #xLName)'
IF #dtApplicationStart IS NOT NULL
SELECT #sql = #sql + ' AND (CONVERT(char(10), acct.dtApplication, 101) >= #xdtApplicationStart)'
IF #dtApplicationEnd IS NOT NULL
SELECT #sql = #sql + ' AND (CONVERT(char(10), acct.dtApplication, 101) <= #xdtApplicationEnd)'
IF #CompanyName IS NOT NULL
SELECT #sql = #sql + ' AND (ao.CompanyName LIKE #xCompanyName)'
IF #DaysInTask IS NOT NULL
SELECT #sql = #sql + ' AND (DATEDIFF(day, awt.dtEnter, GETDATE()) >= #xDaysInTask)'
IF #AcctNum IS NOT NULL
SELECT #sql = #sql + ' AND (acct.AcctNum LIKE #xAcctNum)'
IF #BranchNums IS NOT NULL
--Can't use a parameter of the executesql for the list of affinity numbers.
SELECT #sql = #sql + ' AND (acct.AcctAffinityNum IN (' + #BranchNums + ') OR luA.AffinityNum IN (' + #BranchNums + '))'
IF #WorkflowID IS NOT NULL
SELECT #sql = #sql + ' AND (w.WorkflowID = #xWorkflowID)'
IF #OriginationID IS NOT NULL
SELECT #sql = #sql + ' AND (a.OriginationID = #xOriginationID)'
IF #QueueID IS NOT NULL
SELECT #sql = #sql + ' AND (t.QueueID = #xQueueID)'
IF #TaskStageIDs IS NOT NULL
--Can't use a parameter of the executesql for the list of affinity numbers.
SELECT #sql = #sql + ' AND (lts.TaskStageID IN (' + #TaskStageIDs + '))'
IF #TaskIDs IS NOT NULL
--Can't use a parameter of the executesql for the list of affinity numbers.
SELECT #sql = #sql + ' AND (t.TaskID IN (' + #TaskIDs + '))'
IF #DaysAged IS NOT NULL
SELECT #sql = #sql + ' AND ISNULL(cteAge.Age + 1, 0) <= #xDaysAged'
--SELECT #sql = #sql + ' AND (DATEDIFF(day, acct.dtApplication, GETDATE()) + 1 = #xDaysAged)'
IF #LastActivityStart IS NOT NULL
SELECT #sql = #sql + ' AND (CONVERT(char(10), awt.dtEnter, 101) >= #xLastActivityStart)'
IF #LastActivityEnd IS NOT NULL
SELECT #sql = #sql + ' AND (CONVERT(char(10), awt.dtEnter, 101) <= #xLastActivityEnd)'
IF #Market IS NOT NULL
SELECT #sql = #sql + ' AND (lam.Market = #xMarket OR acctLam.Market = #xMarket)'
IF #EmailTypeIDs IS NOT NULL
SELECT #sql = #sql + ' AND (note.NoteReasonID IN (' + #EmailTypeIDs + '))'
IF #SOAApplID IS NOT NULL
SELECT #sql = #sql + ' AND asa.SOAApplID = #xSOAApplID UNION
SELECT NULL ApplID, NULL AcctID, sa.dtAdded dtApplication, sap.FName, sap.MName, sap.LName, sap.SSN, sap.Email, NULL CompanyName,NULL DBAName,
NULL TaxID, NULL AcctNum, 145 AcctAffinityNum, ''145'' AffinityNum, luA.AffinityName, NULL TaskDesc, NULL dtEnter,
NULL DaysInTask, NULL QueueDesc, NULL WorkflowID, ''SEI Online App'' WorkflowDesc,NULL OriginationID, NULL OriginationDesc, NULL dtCheckOut,
NULL UserCheckOut, NULL GUIDCheckout, NULL TaskStageDesc,
0, DATEDIFF(day, sa.dtAdded, GETDATE()) + 1 DaysAged, sa.SOAApplID,'' '', '' '', '' '' dtEnter, '' ''DecisionType,'' '' Market,
'' ''Request,'' ''SourceAccountTypeDescription,'' ''FundingStatus,'' ''BrokerCode, '' ''TIN,'' ''BusPurpose,'' ''Note,'' ''t,
'' ''t1,'' '' RelationshipCode, '' '' RelationshipCodeDesc FROM SOAApplication sa LEFT JOIN AccountSOAApplication asa
ON sa.SOAApplID = asa.SOAApplID JOIN SOAApplicant sap ON sa.SOAApplID = sap.SOAApplID JOIN luAffinity luA ON luA.AffinityNum = ''145''
WHERE asa.SOAApplID IS NULL AND sa.SOAApplID = #xSOAApplID AND sap.PrimaryContact = 1'
IF #debug = 1
PRINT #sql
IF #debug = 1
PRINT #sql
SELECT #paramlist =
'#xSSN char(9),
#xFName varchar(25),
#xLName varchar(30),
#xdtApplicationStart datetime,
#xdtApplicationEnd datetime,
#xCompanyName varchar(50),
#xDaysInTask int,
#xAcctNum varchar(11),
#xWorkflowID int,
#xOriginationID tinyint,
#xQueueID int,
#xDaysAged int,
#xMarket varchar(50),
#xSOAApplID int,
#xLastActivityStart datetime,
#xLastActivityEnd datetime'
IF #Debug = 1 PRINT LEN(#SQL)
EXEC sp_executesql #sql, #paramlist,
#SSN,
#FName,
#LName,
#dtApplicationStart,
#dtApplicationEnd,
#CompanyName,
#DaysInTask,
#AcctNum,
#WorkflowID,
#OriginationID,
#QueueID,
#DaysAged,
#Market,
#SOAApplID,
#LastActivityStart,
#LastActivityEnd
So when you add EXECUTE AS SELF to a procedure, it's the same as saying "Execute this procedure as though the person who created it is running it". So whoever deploys the procedure (under whatever principal account) is the one that will be the basis for what the procedure uses.
I'm presuming that your deployment strategy is to have an administrator run the CREATE/ALTER steps using the sa account. Your DBAs are probably following best practice and not having the sa account own the databases on the server (and possibly not have read access at all), so you get the security error.
Given all that, in your current situation, you're probably not going to use EXECUTE AS SELF, or at least I suspect so. In terms of when you would want to use it in a more general sense, it's hard to give a blanket answer. Short version is if you have a situation where you ("you" being a principal you can log in as) need to run an object at your level of permissions rather than whatever permissions the caller has.