Delphi query parameter usage when all values is also an option - sql

I have a tquery (going thru BDE or BDE emulating component) that has been used to select either a single record or all records.
Traditionally this has been done as such:
select * from clients where (clientid = :clientid or :clientid = -1)
And then they would put a -1 in the field when they wanted the query to return all values. Going through this code though, I have discovered that when they have done this the query does not use proper indexing for the table and only does a natural read.
Is there a best practices method for achieving this? Perhaps a way to tell a parameter to return all values, or must the script be modified to remove the where clause entirely when all values are desired?
Edit: This is Delphi 7, by the way (And going against Firebird 1.5 sorry for leaving that out)

As you use deprecated BDE, that may be one more reason to migrate from BDE to 3d party solutions. AnyDAC (UniDAC, probably others too. Most are commercial libraries) has macros, which allow to dynamically change a SQL command text, depending on the macro values. So, your query may be written:
ADQuery1.SQL.Text := 'select * from clients {IF &clientid} where clientid = &clientid {FI}';
if clientid >= 0 then
// to get a single record
ADQuery1.Macros[0].AsInteger := clientid
else
// to get all records
ADQuery1.Macros[0].Clear;
ADQuery1.Open;

For the queries with "optional" parameters I always use ISNULL (MSSQL, or NVL Oracle), ie.
SELECT * FROM clients WHERE ISNULL(:clientid, clientid) = clientid
Setting the parameter to NULL then selects all records.
You also have to take care of NULL values in the table fields because NULL <> NULL. This you can overcome with a slight modification:
SELECT * FROM clients WHERE COALESCE(:clientid, clientid, -1) = ISNULL(clientid, -1)

I would use this:
SELECT * FROM CLIENTS WHERE clientid = :clientid or :clientid IS NULL

Using two queries is best:
if (clientid <> -1) then
begin
DBComp.SQL.Text := 'select * from clients where (clientid = :clientid)';
DBComp.ParamByName('clientid').Value := clientid;
end else
begin
DBComp.SQL.Text := 'select * from clients';
end;
DBComp.Open;
...
Alternatively:
DBComp.SQL.BeginUpdate;
try
DBComp.SQL.Clear;
DBComp.SQL.Add('select * from clients');
if (clientid <> -1) then
DBComp.SQL.Add('where (clientid = :clientid)');
finally
DBComp.SQL.EndUpdate;
end;
if (clientid <> -1) then
DBComp.ParamByName('clientid').Value := clientid;
DBComp.Open;
...

Remy's answer may be re-formulated as single query.
It may be better, if you gonna prepare it once and then re-open multiple times.
select * from clients where (clientid = :clientid)
and (:clientid is not null)
UNION ALL
select * from clients where (:clientid is null)
This just aggregates two distinct queries (with same results vector) together. And condition just turns one of those off.
Using would be like that:
DBComp.Prepare.
...
DBComp.Close;
DBComp.ParamByName('clientid').Value := clientid;
DBComp.Open;
...
DBComp.Close;
DBComp.ParamByName('clientid').Clear;
DBComp.Open;
However this query would rely on SQL Server optimizer capability to extract query invariant (:clientid is [not] null) and enable/disable query completely. But well, your original query depends upon that too.
Why still use obsolete FB 1.5 ? Won't FB 2.5.2 work better there ?
I think your original query is formulated poorly.
select * from clients where (:clientid = -1) or ((clientid = :clientid) and (:clientid <> -1))
would probably be easier on SQL Server optimizer. Yet i think FB could do better job there. Try to download later FB, and run your query in it, using IDEs like IBExpert or FlameRobin. Re-arranging parenthesis and changing -1 to NULL are obvious ideas to try.
Using BDE is fragile now. It is not very fast, limiting in datatypes and connectivity (no FB/IB Events for example). And would have all sorts of compatibility problems with Vista/Win7 and Win64. If FB/IB is your server of choice, consider switching to some modern component set:
(FLOSS) Universal Interbase by http://uib.sf.net (RIP all Delphi pages of http://Progdigy.com )
(FLOSS) ZeosLib DBO by http://zeos.firmos.at/
(propr) FIB+ by http://DevRace.com
(propr) IB Objects by http://IBobjects.com
(propr) AnyDAC by http://da-soft.com - sold out to Embarcadero, not-avail for D7
(propr) IB-DAC/UniDAC http://DevArt.com
Also it would be good thing to show the table and indices definition and selectivity of those indices.

Related

Syntax error on WITH clause

I am working on a web app and there are some long winded stored procedures and just trying to figure something out, I have extracted this part of the stored proc, but cant get it to work. The guy who did this is creating alias after alias.. and I just want to get a section to work it out. Its complaining about the ending but all the curly brackets seem to match. Thanks in advance..
FInputs is another stored procedure.. the whole thing is referred to as BASE.. the result of this was being put in a temp table where its all referred to as U. I am trying to break it down into separate sections.
;WITH Base AS
(
SELECT
*
FROM F_Inputs(1,1,100021)
),
U AS
(
SELECT
ISNULL(q.CoverPK,r.CoverPK) AS CoverPK,
OneLine,
InputPK,
ISNULL(q.InputName,r.InputName) AS InputName,
InputOrdinal,
InputType,
ParentPK,
InputTriggerFK,
ISNULL(q.InputString,r.InputString) AS InputString,
PageNo,
r.RatePK,
RateName,
Rate,
Threshold,
ISNULL(q.Excess,r.Excess) AS Excess,
RateLabel,
RateTip,
Refer,
DivBy,
RateOrdinal,
RateBW,
ngRequired,
ISNULL(q.RateValue,r.RateValue) AS RateValue,
ngClass,
ngPattern,
UnitType,
TableChildren,
TableFirstColumn,
parentRatePK,
listRatePK,
NewParentBW,
NewChildBW,
ISNULL(q.SumInsured,0) AS SumInsured,
ISNULL(q.NoItems,0) AS NoItems,
DisplayBW,
ReturnBW,
StringBW,
r.lblSumInsured,
lblNumber,
SubRateHeading,
TrigSubHeadings,
ISNULL(q.RateTypeFK,r.RateTypeFK) AS RateTypeFK,
0 AS ListNo,
0 AS ListOrdinal,
InputSelectedPK,
InputVis,
CASE
WHEN ISNULL(NewChildBW,0) = 0
THEN 1
WHEN q.RatePK is NOT null
THEN 1
ELSE RateVis
END AS RateVis,
RateStatus,
DiscountFirstRate,
DiscountSubsequentRate,
CoverCalcFK,
TradeFilter,
ngDisabled,
RateGroup,
SectionNo
FROM BASE R
LEFT JOIN QuoteInputs Q
ON q.RatePK = r.RatePK
AND q.ListNo = 0
AND q.QuoteId = 100021 )
Well, I explained the issue in the comments section already. I'm doing it here again, so future readers find the answer more easily.
A WITH clause is part of a query. It creates a view on-the-fly, e.g.:
with toys as (select * from products where type = 'toys') select * from toys;
Without the query at the end, the statement is invalid (and would not make much sense anyhow; if one wanted a permanent view for later use, one would use CREATE VIEW instead).

Select #variable not working

I can't seem to wrap my mind around a little situation I have. I am trying to set a variable using an if condition; I have also tried using a case statement as well, but keep receiving error. Below is what I am working with and it's dynamic SQL as well... The variable I am trying to set is #EBP_Allow... Can someone shine some light on this?
What I need it to do for example...
SELECT
#EBP_Allow = IF #Year > 2014 THEN DO a SELECT ELSE DO Something else...
What I have now...
SELECT
#OU_Allow = Optional_Unit_Allowed_Flag,
#BU_Allow = Basic_Unit_Allowed_Flag,
#EU_Allow = Enterprise_Unit_Allowed_Flag,
#WU_Allow = Whole_Farm_Unit_Allowed_Flag,
#EBP_Allow = I NEED TO USE SUB SELECT, IF OR CASE TO SET THIS
FROM dbo.#YEAR_Insurance_Offer
WHERE
(dbo.#YEAR_Insurance_Offer.State_Code = #StateCode) AND
(dbo.#YEAR_Insurance_Offer.County_Code = #CountyCode) AND
(dbo.#YEAR_Insurance_Offer.Crop_Code = ''#CropCode'') AND
(dbo.#YEAR_Insurance_Offer.Insurance_Plan_ID = #PlanId) #TypeCondition #PracticeCondition
Here's an update; it seems that when ran it's still jumping over my condition...
CASE
WHEN #YEAR < 2015
THEN ''N''
WHEN #YEAR > 2014
THEN (
SELECT Enterprise_Unit_By_Practice_Allowed_Flag
FROM dbo.#YEAR_Insurance_Offer
WHERE
(dbo.#YEAR_Insurance_Offer.State_Code = #StateCode) AND
(dbo.#YEAR_Insurance_Offer.County_Code = #CountyCode) AND
(dbo.#YEAR_Insurance_Offer.Crop_Code = ''#CropCode'') AND
(dbo.#YEAR_Insurance_Offer.Insurance_Plan_ID = #PlanId) #TypeCondition #PracticeCondition
)
END
If I replace the WHEN #Year > 2014 with this...
WHEN #YEAR > 2014
THEN ''N''
It work's just fine... For some reason or another when I have the select in there it's telling me that Enterprise_Unit_By_Practice_Allowed_Flag is an invalid column, but it's not?
In order to set #EPB_Allow you can use a CASE statement and you can add multiple WHEN clauses to treat different situations:
SELECT
#OU_Allow = Optional_Unit_Allowed_Flag,
#BU_Allow = Basic_Unit_Allowed_Flag,
#EU_Allow = Enterprise_Unit_Allowed_Flag,
#WU_Allow = Whole_Farm_Unit_Allowed_Flag,
#EBP_Allow = CASE
WHEN #Year > 2014
THEN (SELECT column FROM Table where conditions1...)
WHEN #Year > 2013
THEN (SELECT column FROM Table where conditions2...)
ELSE (SELECT column FROM Table where conditions3...)
END
FROM dbo.#YEAR_Insurance_Offer
WHERE
(dbo.#YEAR_Insurance_Offer.State_Code = #StateCode) AND
(dbo.#YEAR_Insurance_Offer.County_Code = #CountyCode) AND
(dbo.#YEAR_Insurance_Offer.Crop_Code = ''#CropCode'') AND
(dbo.#YEAR_Insurance_Offer.Insurance_Plan_ID = #PlanId)
#TypeCondition #PracticeCondition
Also, make sure that if you have defined #EBP_Allow as NVARCHAR then you have to make sure that the datatype returned by the query which selects what value you want to assign the variable is of the same datatype (otherwise use conversion functions).
And you have to make sure that the query that you specify in the THEN part will return only one result.
OK first it seems you have fallen into the horrible practice of creating new tables with each year. This is a huge database antipattern and if this is the first year you have done this, I suggest an immediate redesign as this just gets harder and harder to deal with as you add more years.
If you can't, well then at least learn from this and never allow anyone to design a table like this in the future. In enterprise level datbase you can partition by date and in smaller ones, you dont need to have differnt tables just a where clause to filter by year and good indexing.
But you seem to be stuck using dynamic SQL to do everything (another reason why this is a horrible design choice). So you might as well learn how to use dynamic SQl correctly.
First read and thorughly understand this link before you try to write code against this database design:
http://www.sommarskog.se/dynamic_sql.html
Here is an example below of how you build and view the generated SQl for a dynamic SQl statement. This will help you corretly build the statement before you try to execute it.
declare #SQl nvarchar (max), #year nchar (4) = '2014'
set #sql = 'SELECT Enterprise_Unit_By_Practice_Allowed_Flag
FROM dbo.' +#YEAR+ '_Insurance_Offer
WHERE dbo.'+ #YEAR + '_Insurance_Offer.State_Code = #StateCode'
Print #sql
Since you are doing this in Stored procs, all of them should be designed with a debug variable that is used to display any SQL built. It doesn't need to run in the debug mode in prodcution, but when you have a problem (and you can't possibly forsee every variable that will ever be part of creating a dynamic sql statement and there is close to a 100% probability that you will encounter wierd bugs that you have to troubleshoot later. So you need to build in troubleshooting ability into every proc you write with dynamic SQl.
Of course to execute you need to learn to use sp_executesql.
But really read the link, share the link with your managers and coworkers and think about better ways to do this.
dbo.#YEAR_Insurance_Offer.State_Code
[databaseowner].[table].[fieldname]
Table name as variable
According to this table names need to be static.
Honestly, I have never seen a variable table name. Further, I can't think of a good reason to do this. So this is either a logic error or a syntax error?

Handle null values within SQL IN clause

I have following sql query in my hbm file. The SCHEMA, A and B are schema and two tables.
select
*
from SCHEMA.A os
inner join SCHEMA.B o
on o.ORGANIZATION_ID = os.ORGANIZATION_ID
where
case
when (:pass = 'N' and os.ORG_ID in (:orgIdList)) then 1
when (:pass = 'Y') then 1
end = 1
and (os.ORG_SYNONYM like :orgSynonym or :orgSynonym is null)
This is a pretty simple query. I had to use the case - when to handle the null value of "orgIdList" parameter(when null is passed to sql IN it gives error). Below is the relevant java code which sets the parameter.
if (_orgSynonym.getOrgIdList().isEmpty()) {
query.setString("orgIdList", "pass");
query.setString("pass", "Y");
} else {
query.setString("pass", "N");
query.setParameterList("orgIdList", _orgSynonym.getOrgIdList());
}
This works and give me the expected output. But I would like to know if there is a better way to handle this situation(orgIdList sometimes become null).
There must be at least one element in the comma separated list that defines the set of values for the IN expression.
In other words, regardless of Hibernate's ability to parse the query and to pass an IN(), regardless of the support of this syntax by particular databases (PosgreSQL doesn't according to the Jira issue), Best practice is use a dynamic query here if you want your code to be portable (and I usually prefer to use the Criteria API for dynamic queries).
If not need some other work around like what you have done.
or wrap the list from custom list et.

Strategy to alter database synonym during normal production operation

Currently I have a scenario that involves switching a synonym definition after the completion of a scheduled job. The job will create a table with an identifier of even or odd to correspond with the hour being even or odd. What we are currently doing is this:
odd_job:
create foo_odd ...
replace foo_syn as foo_odd
and
even_job:
create foo_even ...
replace foo_syn as foo_even
What is happening is that during normal production the foo_syn is in a locked state. So what we are looking for is a production capable way of swapping synonym definitions.
The question is how can we swap a synonym definition in a production level system with minimum user interruption in Oracle 10g?
From the comments
Does foo_syn have any dependent objects?
No foo_syn is nothing more than a pointer to a table that I generate. That is there are no procedures that need to be recompiled for this switch.
That sounds like a really strange thing to do. Can you explain a bit
what that switch is for/how it is used?
Sure. We have an application that interfaces with the database, the SQL that is executed from Java (business logic queries) has a reference to foo_syn. Because of the dynamic nature of the data it is a guarantee that the hourly swap will give new results that are important as we try to get closer to real time. Prior to this it was a once a day and be happy with it type scenario.
The reasoning for the swap is I do not want dynamic SQL (in terms of table names) to be a part of my application queries. So therefore the database does a switch on the newer data set without changing the name of the synonym that is referenced as part of my application.
If using dynamic SQL is distasteful to you (and I'll quickly point out that in my experience dynamic SQL has never proved to be a performance issue, but YMMV) then a UNION query might be what you're looking for - something like
SELECT *
FROM EVEN_DATA_TABLE
WHERE TO_NUMBER(TO_CHAR(SYSDATE, 'HH')) IN (0, 2, 4, 6, 8, 10, 12)
UNION ALL
SELECT *
FROM ODD_DATA_TABLE
WHERE TO_NUMBER(TO_CHAR(SYSDATE, 'HH')) IN (1, 3, 5, 7, 9, 11)
This also eliminates the need to have a periodic job to change the synonym as it's driven off of SYSDATE.
This makes the assumption that the columns in EVEN_DATA_TABLE and ODD_DATA_TABLE are the same.
Share and enjoy.
The solution that we came up with is as follows:
1) Define a function that will return which set of tables you should be looking at:
create or replace function which_synonym return varchar2 as
to_return varchar2(4) := NULL;
is_valid number :=- 1;
current_time number := to_number(to_char(sysdate,'HH'));
is_odd boolean := FALSE;
BEGIN
if = mod(current_time,2) -- it is an even time slot
then
select success into is_valid
from success_table
where run='EVEN';
else
select success into is_valid
from success_table
where run='ODD';
end if;
if is_valid=0 and is_odd=TRUE
then to_Return ='ODD';
else
to_return='EVEN';
end if;
Return to_return;
END which_synonym;
De Morgan's laws omitted for conciseness.
2) Configure the application procedures to take advantage of this flipping:
a) Tokenize enumerated sql strings with a sequence that you want to match on:
select * from foo_&&&
b) write the function that will replace this sequence:
public String whichSynonym(String sql)
{
if(null==sql || "".equals(sql.trim()))
{
throw new IllegalArgumentException("Cannot process null or empty sql");
}
String oddEven = "";
//removed boilerplate
PreparedStatement statement = conn.prepareStatement("Select which_synonym from dual");
statement.execute();
ResultSet results = statement.getResults();
while(results.next())
{
oddEven=results.getString(1);
}
return sql.replace("&&&",oddEven);
}

Having problems converting conditional where clause in LINQ back over to SQL

I've got myself in a bit of a pickle!
I've done a snazzy LINQ statement that does the job in my web app, but now I'd like to use this in a stored procedure:
var r = (from p in getautocompleteweightsproducts.tblWeights
where p.MemberId == memberid &&
p.LocationId == locationid
select p);
if (level != "0")
r = r.Where(p => p.MaterialLevel == level);
if (column == "UnitUserField1")
r = r.Where(p => p.UnitUserField1 == acitem);
if (column == "UnitUserField2")
r = r.Where(p => p.UnitUserField2 == acitem);
return r.OrderBy(p => p.LevelNo).ToList();
However, I can't for the life of me get the conditional where clause to work!!
If someone can point me in the right direction, I'd be most grateful.
Kind regards
Maybe something like this?
SELECT *
FROM dbo.weights
WHERE member_id = #memberid
AND location_id = #locationid
AND material_level = CASE WHEN #level = '0' THEN material_level
ELSE #level END
AND #acitem = CASE #column WHEN 'UnitUserField1' THEN unit_user_field_1
WHEN 'UnitUserField2' THEN unit_user_field_2
ELSE #acitem END
ORDER BY level_no
Have you tried LinqPAD, I'm pretty sure last time I played with that you could enter "LINQ to SQL" code and see the resulting SQL that produced. Failing that, place a SQL trace/profiler on your code running the LinqTOSQL and find the query being executed in the trace.
LukeH's answer will give you the correct rows, but there is something lost when you try to replace a query-generating-machine with a single query. There are parts of that query that are opaque to the optimizer.
If you need the original queries as-would-have-been-generated-by-linq, there are two options.
Generate every possible query and control which one runs by IF ELSE.
Use Dynamic sql to construct each query (although this trades away many of the benefits of using a stored procedure).
If you do decide to use dynamic sql, you should be aware of the curse and blessings of it.