This might give you an idea:
create table Customer (
c_firstname varchar2(50),
c_lastname varchar2(50),
c_userid varchar2(50)
);
insert into Customer values ('Micky' , 'Mouse', 'mm');
insert into Customer values ('Donald', 'Duck' , 'dd');
insert into Customer values ('Peter' , 'Pan' , 'pp');
create or replace function GetCustomer(
FirstN varchar2 := null,
LastN varchar2 := null,
CID varchar2 := null
) return sys_refcursor
as
stmt varchar2(4000);
ret sys_refcursor;
begin
stmt := 'select * from Customer where 1=1';
if FirstN is not null then
stmt := stmt || ' and c_firstname like ''%' || FirstN || '%''';
end if;
if LastN is not null then
stmt := stmt || ' and c_lastname like ''%' || LastN || '%''';
end if;
if CID is not null then
stmt := stmt || ' and c_userid like ''%' || CID || '%''';
end if;
dbms_output.put_line(stmt);
open ret for stmt;
return ret;
end;
/
Later, in SQL*Plus:
set serveroutput on size 100000 format wrapped
declare
c sys_refcursor;
fn Customer.c_firstname%type;
ln Customer.c_lastname %type;
id Customer.c_userid %type;
begin
c := GetCustomer(LastN => 'u');
fetch c into fn, ln, id;
while c%found loop
dbms_output.put_line('First Name: ' || fn);
dbms_output.put_line('Last Name: ' || ln);
dbms_output.put_line('user id: ' || id);
fetch c into fn, ln, id;
end loop;
close c;
end;
/
Edit: The comment is right, and the procedure is subject to SQL injection. So, in order to prevent that, you could go with bind variables such as in this modified procedure:
create or replace function GetCustomer(
FirstN varchar2 := null,
LastN varchar2 := null,
CID varchar2 := null
) return sys_refcursor
as
stmt varchar2(4000);
ret sys_refcursor;
type parameter_t is table of varchar2(50);
parameters parameter_t := parameter_t();
begin
stmt := 'select * from Customer where 1=1';
if FirstN is not null then
parameters.extend;
parameters(parameters.count) := '%' || FirstN || '%';
stmt := stmt || ' and c_firstname like :' || parameters.count;
end if;
if LastN is not null then
parameters.extend;
parameters(parameters.count) := '%' || LastN || '%';
stmt := stmt || ' and c_lastname like :' || parameters.count;
end if;
if CID is not null then
parameters.extend;
parameters(parameters.count) := '%' || CID || '%';
stmt := stmt || ' and c_userid like :' || parameters.count;
end if;
if parameters.count = 0 then
open ret for stmt;
elsif parameters.count = 1 then
open ret for stmt using parameters(1);
elsif parameters.count = 2 then
open ret for stmt using parameters(1), parameters(2);
elsif parameters.count = 3 then
open ret for stmt using parameters(1), parameters(2), parameters(3);
else raise_application_error(-20800, 'Too many parameters');
end if;
return ret;
end;
/
Note, that now, whatever the input, the select statement becomes something like select ... from ... where 1=1 and col1 like :1 and col2 :2 ...
which is obviously much safer.
After some processing this boiled down to:
While your predicate d."SettlementPointName" = 'John'
is filtering a single value for "SettlementPointName" anyway, simplify to:
SELECT count( d."SettlementPointPrice" < 10.5 OR NULL) AS da_00_10
, count(d."SettlementPointPrice" >= 10.5 AND d."SettlementPointPrice" < 20.5 OR NULL) AS da_11_20
, count(d."SettlementPointPrice" >= 20.5 AND d."SettlementPointPrice" < 30.5 OR NULL) AS da_21_30
FROM public.da d
JOIN public.rt_aggregate r USING ("DeliveryDate", "SettlementPointName")
WHERE d."SettlementPointName" = 'John'
AND d."DeliveryDate" >= '2015-02-01'
AND d."DeliveryDate" <= '2015-02-20'
AND r."DeliveryHour" = 14
AND date_part('hour', d."DeliveryHour") = r."DeliveryHour";
About the counting technique:
Or better, yet, use the new aggregate filter technique in pg 9.4:
SELECT d."SettlementPointName"
, count(*) FILTER (WHERE d."SettlementPointPrice" < 10.5) AS da_00_10
, count(*) FILTER (WHERE d."SettlementPointPrice" >= 10.5
AND d."SettlementPointPrice" < 20.5) AS da_11_20
, count(*) FILTER (WHERE d."SettlementPointPrice" >= 20.5
AND d."SettlementPointPrice" < 30.5) AS da_21_30
FROM public.da d
JOIN public.rt_aggregate r USING ("DeliveryDate", "SettlementPointName")
WHERE d."DeliveryDate" >= '2015-02-01'
AND d."DeliveryDate" <= '2015-02-20'
AND r."DeliveryHour" = 14
AND date_part('hour', d."DeliveryHour") = r."DeliveryHour"
GROUP BY 1;
This time, selecting all names and returning one row per name like you asked in the comment.
Details for FILTER
:
Best Answer
Your suggestion is bad practice
From the code given it looks like you want to
$1
fld1
is set to a specific set of values.OR
however, if your condition is complex and you want to maintain it that can get to be very messy. Here is another way.Here was your code,
However, I would unroll this as a JOIN, and later spring it off into another table. Really
CASE
should likely never be used in aWHERE
clause.From there you can even use CTE's which may make it faster, and it'll look more maintainable.
From the CTE, you can just as well make
cond(fld1,values)
its own table if it gets too bloody.