Exclude where clause based on function specification - tsql

I developed the following function:
create function kv_fn_ValuationPerItem_AW (#dDate date, #active bit)
returns table
as
return
(
select
Code ItemCode
, Description_1 ItemDescription
, ItemGroup
, Qty_On_Hand CurrentQtyOnHand
, AveUCst CurrentAvgCost
, Qty_On_Hand*AveUCst CurrentValue
from _bvSTTransactionsFull t
inner join StkItem s on t.AccountLink = s.StockLink
where ServiceItem = 0
and ItemActive = #active
and TxDate <= #dDate
group by Code, Description_1, ItemGroup, Qty_On_Hand, AveUCst
)
The function requires two parameters:
Date
Is the item Active - 1 = Active & 0 = Inactive
If I use the function as stipulated above, by specifying 1 for the Active Parameter, then the results will only be for Active Items.
If I specify 0, then it'll return all inactive Items.
How do I alter this function to cater for Active Items or both Active & Inactive?
i.e. if the parameter is 1, the where clause should read as ItemActive = #active, but when it's 0, the where clause should read as ItemActive in (1,0), How do I change the function to work like this?
I tried a case, but my syntax is not correct...

It's as simple as adding an or to your where cluase:
...
and (ItemActive = 1 OR #active = 0)
...
BTW, you might want to do it like this instead:
and (ItemActive = #active OR #active IS NULL)
which means that when you pass in 1 as #active you'll get only the active items, when you pass in 0 you'll get only the inactive members, but when you pass in null you'll get all records, regardless of the value in the ItemActive column.

Thanks Shnugo & Zohar for your answers,
Please amend your answers, then I'll mark yours as the answer.
The solution to my problem was to alter the Function as following:
create function kv_fn_ValuationPerItem_AW (#dDate date, #active bit)
returns table
as
return
(
select
Code ItemCode
, Description_1 ItemDescription
, ItemGroup
, Qty_On_Hand CurrentQtyOnHand
, AveUCst CurrentAvgCost
, Qty_On_Hand*AveUCst CurrentValue
from _bvSTTransactionsFull t
inner join StkItem s on t.AccountLink = s.StockLink
where ServiceItem = 0
and ItemActive in (1,#active)
and TxDate <= #dDate
group by Code, Description_1, ItemGroup, Qty_On_Hand, AveUCst
)

I think you are looking for this:
DECLARE #mockup TABLE(ID INT IDENTITY,SomeValue VARCHAR(100),Active BIT);
INSERT INTO #mockup VALUES('Row 1 is active',1)
,('Row 2 is active',1)
,('Row 3 is inactive',0)
,('Row 4 is inactive',0);
DECLARE #OnlyActive BIT=0; --set this to 1 to see active rows only
SELECT *
FROM #mockup m
WHERE (#OnlyActive=0 OR m.Active=1);
The idea is: If the parameter is set to 0 this expression is always true, if not, the column Active must be set to 1.
Hint: I used paranthesis, which was not needed in this simple case. But in your more complex WHERE clause they will be needed...
Hint2: I named the parameter OnlyActive, which expresses a bit better what you are looking for. You might turn the parameter to ShowAll with an invers logic too...

Related

PostgreSQL ERROR: invalid input syntax for integer: "1e+06"

The full error message is:
ERROR: invalid input syntax for integer: "1e+06"
SQL state: 22P02
Context: In PL/R function sample
The query I'm using is:
WITH a as
(
SELECT a.tract_id_alias,
array_agg(a.pgid ORDER BY a.pgid) as pgids,
array_agg(a.sample_weight_geo ORDER BY a.pgid) as block_weights
FROM results_20161109.block_microdata_res_joined a
WHERE a.tract_id_alias in (66772, 66773, 66785, 66802, 66805, 66806, 66813)
AND a.bldg_count_res > 0
GROUP BY a.tract_id_alias
)
SELECT NULL::INTEGER agent_id,
a.tract_id_alias,
b.year,
unnest(shared.sample(a.pgids,
b.n_agents,
1 * b.year,
True,
a.block_weights)
) as pgid
FROM a
LEFT JOIN results_20161109.initial_agent_count_by_tract_res_11 b
ON a.tract_id_alias = b.tract_id_alias
ORDER BY b.year, a.tract_id_alias, pgid;
And the shared.sample function I'm using is:
CREATE OR REPLACE FUNCTION shared.sample(ids bigint[], size integer, seed integer DEFAULT 1, with_replacement boolean DEFAULT false, probabilities numeric[] DEFAULT NULL::numeric[])
RETURNS integer[] AS
$BODY$
set.seed(seed)
if (length(ids) == 1) {
s = rep(ids,size)
} else {
s = sample(ids,size, with_replacement,probabilities)
}
return(s)
$BODY$
LANGUAGE plr VOLATILE
COST 100;
ALTER FUNCTION shared.sample(bigint[], integer, integer, boolean, numeric[])
OWNER TO "server-superusers";
I'm pretty new to this stuff, so any help would be appreciated.
Not a problem of the function. Like the error messages says: The string '1e+06' cannot be cast to integer.
Obviously, the columns n_agents in your table results_20161109.initial_agent_count_by_tract_res_11 is not an integer column. Probably type text or varchar? (That info would help in your question.)
Either way, the assignment cast does not work for the target type integer. But it does for numeric:
Does not work:
SELECT '1e+06'::text::int; -- error as in question
Works:
SELECT '1e+06'::text::numeric::int;
If my assumptions hold, you can use this as stepping stone.
Replace b.n_agents in your query with b.n_agents::numeric::int.
It's your responsibility that numbers stay in integer range, or you get the next exception.
If that did not nail it, you need to look into function overloading:
Is there a way to disable function overloading in Postgres
And function type resolution:
PostgreSQL function call
The schema search path is relevant in many related cases, but you did schema-qualify all objects, so we can rule that out.
How does the search_path influence identifier resolution and the "current schema"
Your query generally looks good. I had a look and only found minor improvements:
SELECT NULL::int AS agent_id -- never omit the AS keyword for column alias
, a.tract_id_alias
, b.year
, s.pgid
FROM (
SELECT tract_id_alias
, array_agg(pgid) AS pgids
, array_agg(sample_weight_geo) AS block_weights
FROM ( -- use a subquery, cheaper than CTE
SELECT tract_id_alias
, pgid
, sample_weight_geo
FROM results_20161109.block_microdata_res_joined
WHERE tract_id_alias IN (66772, 66773, 66785, 66802, 66805, 66806, 66813)
AND bldg_count_res > 0
ORDER BY pgid -- sort once in a subquery. cheaper.
) sub
GROUP BY 1
) a
LEFT JOIN results_20161109.initial_agent_count_by_tract_res_11 b USING (tract_id_alias)
LEFT JOIN LATERAL
unnest(shared.sample(a.pgids
, b.n_agents
, b.year -- why "1 * b.year"?
, true
, a.block_weights)) s(pgid) ON true
ORDER BY b.year, a.tract_id_alias, s.pgid;

How to set a bit based on a value existing in a table

I have a table. I have 2 variables, one is a bit, the other is an int.
Table: WorkGroupCollectionDetail
Variables: #WorkgroupID int, #IsFSBP bit
The table has WorkGroupId int PK and WorkGroupCollectionCode varchar PK. That's it.
I can run a query like this:
SELECT WorkGroupId
FROM WorkGroupCollectionDetail
WHERE WorkGroupCollectionCode = 'FSBP'
and it gives me a list of WorkGroupID.
So what I need to do is if the value of #WorkgroupID is inside the results of that query, I need to set the bit variable to true.
select #IsFBSP = case
when exists (
select 42 from WorkGroupDetailCollection
where WorkGroupCollectionCode = 'FSBP' and WorkGroupId = #WorkGroupId ) then 1
else 0 end
which is logically equivalent to:
select #IsFBSP = case
when #WorkGroupId in (
select WorkGroupId from WorkGroupDetailCollection
where WorkGroupCollectionCode = 'FSBP' ) then 1
else 0 end
A query using EXISTS often performs better than a query using IN. You can check the execution plans to see how they compare in your particular case.
Note that these examples include setting the bit value to zero as well as one.
You could modify the SELECT to include the check for the WorkGroupId and update the #IsFSBP accordingly:
IF EXISTS(SELECT WorkGroupId
FROM WorkGroupCollectionDetail
WHERE WorkGroupCollectionCode = 'FSBP'
AND WorkGroupId = #WorkgroupID)
BEGIN
SELECT #IsFSBP = 1;
END
SQL Fiddle example
I'm guessing you're looking for
Set #BitVariable = count(*)
From TestTable
WHERE TestCode = 'TestValue' and TestID = #TestID

Assign case statement result to variable

When I try to run the following statement, I get this error:
"A SELECT statement that assigns a value to a variable must not be combined with data-retrieval operations."
DECLARE #OFR DECIMAL(18,2)
;
SELECT #OFR =
CASE SUM(ofr.Quantity)
WHEN 0 THEN 0
ELSE SUM(ofr.Gross) / SUM(ofr.Quantity)
END
FROM DistributionCosts ofr
;
SELECT #OFR
I just want to stick the result of that case statement into a variable and I'm getting stuck here.
Any ideas?
Thanks.
Are you sure this is where your error is? I just ran the statement below and it worked:
DECLARE #OFR DECIMAL(18,2)
;
with DistributionCosts as
(
select 1 as quantity, 5 as gross
union
select 5 , 20
)
SELECT #OFR =
CASE SUM(ofr.Quantity)
WHEN 0 THEN 0
ELSE SUM(ofr.Gross) / SUM(ofr.Quantity)
END
FROM DistributionCosts ofr
;
SELECT #OFR
If I select a column in addition to setting a variable I do get your error... If you are selecting another column you will need to either stop selecting that column or assign that column to a local variable as well.

Setting a variable in T-SQL from a Query

So I have this query:
select ens_use_new_models_bit from cfo_transaction
inner join dbo.cfo_trans_entity_rel on te_tr_transaction_id=tr_transaction_id
inner join cfo_tran_quote on tq_tr_transaction_id = tr_transaction_id
left outer join cfo_engine_sponsor on ens_rs_sponsor_id = te_co_re_entity_id
where te_rv_rel_type_id=713 and tq_tran_quote_id = 3
It returns a bit value, which can also be NULL. I hardcoded 3 for testing but in reality another proc passes this value in, but that's not important here.
Now, in a stored proc, I need to set a variable that's declared in the proc:
SET #vRtn = NULL
as the string - either 'VBEngines' or 'WFModels', or keep it NULL if the bit from above returns NULL.
'VBEngines' if the bit is off, 'WFModels' if the bit is on.
Then after that, I need to perform a T-SQL condition on the value, to see if it's NULL or not. How would I do this? I'm so bad with SQL.
Thanks.
Assuming that you don't need the bit value itself stored in a variable as that was just a means to an end then this should do it.
select #vRtn = case ens_use_new_models_bit
when 0 then 'VBEngines'
when 1 then 'WFModels'
end /*Implicit Else NULL case*/
from cfo_transaction ...
In the case 0 rows are returned no assignment is made so #vRtn keeps its initial value,
declare #newmodelsbit bit, #vRtn varchar(10)
select #newmodelsbit = ens_use_new_models_bit from cfo_transaction
inner join dbo.cfo_trans_entity_rel on te_tr_transaction_id=tr_transaction_id
inner join cfo_tran_quote on tq_tr_transaction_id = tr_transaction_id
left outer join cfo_engine_sponsor on ens_rs_sponsor_id = te_co_re_entity_id
where te_rv_rel_type_id=713 and tq_tran_quote_id = 3
if #newmodelsbit is null
begin
set #vRtn = null
end
else
begin
if #newmodelsbit = 1 --bit is on
begin
set #vRtn = 'WFModels'
end
else -- bit is off
begin
set #vRtn = ' VBEngines'
end
end

How to conditionally filter on a column in a WHERE clause?

OK, the umpteenth conditional column question:
I'm writing a stored proc that takes an input parameter that's mapped to one of several flag columns. What's the best way to filter on the requested column? I'm currently on SQL2000, but about to move to SQL2008, so I'll take a contemporary solution if one's available.
The table queried in the sproc looks like
ID ... fooFlag barFlag bazFlag quuxFlag
-- ------- ------- ------- --------
01 1 0 0 1
02 0 1 0 0
03 0 0 1 1
04 1 0 0 0
and I want to do something like
select ID, name, description, ...
from myTable
where (colname like #flag + 'Flag') = 1
so if I call the sproc like exec uspMyProc #flag = 'foo' I'd get back rows 1 and 4.
I know I can't do the part in parens directly in SQL. In order to do dynamic SQL, I'll have to stuff the entire query into a string, concatenate the #flag param in the WHERE clause and then exec the string. Aside from the dirty feeling I get when doing dynamic SQL, my query is fairly large (I'm selecting a couple dozen fields, joining 5 tables, calling a couple of functions), so it's a big giant string all because of a single line in a 3-line WHERE filter.
Alternately, I could have 4 copies of the query and select among them in a CASE statement. This leaves the SQL code directly executable (and subject to syntax hilighting, etc.) but at the cost of repeating big chunks of code, since I can't use the CASE on just the WHERE clause.
Are there any other options? Any tricky joins or logical operations that can be applied? Or should I just get over it and exec the dynamic SQL?
There are a few ways to do this:
You can do this with a case statement.
select ID, name, description, ...
from myTable
where CASE
WHEN #flag = 'foo' then fooFlag
WHEN #flag = 'bar' then barFlag
END = 1
You can use IF.
IF (#flag = 'foo') BEGIN
select ID, name, description, ...
from myTable
where fooFlag = 1
END ELSE IF (#flag = 'bar') BEGIN
select ID, name, description, ...
from myTable
where barFlag = 1
END
....
You can have a complicated where clause with a lot of parentheses.
select ID, name, description, ...
from myTable
where (#flag = 'foo' and fooFlag = 1)
OR (#flag = 'bar' and barFlag = 1) OR ...
You can do this with dynamic sql:
DECLARE #SQL nvarchar(4000)
SELECT #SQL = N'select ID, name, description, ...
from myTable
where (colname like ''' + #flag + 'Flag'') = 1'
EXECUTE sp_ExecuteSQL #SQL, N''
There are more, but I think one of these will get you going.
"Alternately, I could have 4 copies of the query and select among them in a CASE statement."
You don't need to copy your entire query 4 times, just add all the possibilities into the where clauses in your single copy of the query:
select ID, name, description, ...
from myTable
where (#flag = 'foo' and fooFlag = 1) OR (#flag = 'bar' and barFlag = 1) OR ...
What I would do is CASE some variables at the beginning. Example:
DECLARE
#fooFlag int,
#barFlag int,
#bazFlag int,
#quuxFlag int
SET #fooFlag = CASE WHEN #flag = 'foo' THEN 1 ELSE NULL END
SET #barFlag = CASE WHEN #flag = 'bar' THEN 1 ELSE NULL END
SET #bazFlag = CASE WHEN #flag = 'baz' THEN 1 ELSE NULL END
SET #quuxFlag = CASE WHEN #flag = 'quux' THEN 1 ELSE NULL END
SELECT ID, name, description, ...
FROM myTable
WHERE (fooFlag >= ISNULL(#fooFlag, 0) AND fooFlag <= ISNULL(#fooFlag, 1))
AND (barFlag >= ISNULL(#barFlag, 0) AND barFlag <= ISNULL(#barFlag, 1))
AND (bazFlag >= ISNULL(#bazFlag, 0) AND bazFlag <= ISNULL(#bazFlag, 1))
AND (quuxFlag >= ISNULL(#quuxFlag, 0) AND quuxFlag <= ISNULL(#quuxFlag, 1))
The good thing about this query is that, because the possible values for "flags" are bounded, you can calculate all your conditionals as prerequisites instead of wrapping columns in them. This guarantees a high-performance index seek on whichever columns are indexed, and doesn't require writing any dynamic SQL. And it's better than writing 4 separate queries for obvious reasons.
You could have a parameter for each possible flag column, then check if the parameter is null or the value in the column is equal to the parameter. Then you pass in a 1 for the flags that you want to check and leave the others null.
select id, name, description, ...
from myTable
where (#fooFlag is null or fooFlag = #fooFlag) AND
(#barFlag is null or barFlag = #barFlag) AND
...
Honestly, though, this seems like an ideal candidate for building a dynamic LINQ query and skipping the SPROC once you get to SQL2008.
int should be accepted as varchar value
declare #CompanyID as varchar(10) = '' -- or anyother value
select * from EmployeeChatTbl chat
where chat.ConversationDetails like '%'+#searchKey+'%'
and
(
(0 = CASE WHEN (#CompanyID = '' ) THEN 0 ELSE 1 END)
or
(chat.CompanyID = #CompanyID)
)
working
when the companyID is present , then filtration based on it is done, other wise , filtration is skipped.
where
case when #value<>0 then Field else 1 end
=
case when #value<>0 then #value else 1 end