how do i shorten this SQL? - sql

I run this command :
select * from LIST where JCODE = 8 and
KCODE = 01 and LCODE = 2011
and if the above retruns no rows then perform the below :
insert into LIST
select * from LIST#LNDB where JCODE = 8 and
KCODE = 01 and LCODE = 2011 and ban
in (select BAN from billing_account)
Update LIST set STS = null where JCODE = 8
AND KCODE = 01;
Update LIST set NO = '1' where JCODE = 8 AND
KCODE = 01;
moreover can i use some variable in the begininng which
sets
JCODE= somevalue
KCODE= anothervalue
LCODE=someothervalue
so that i dont have to edit every line every time i run it.
I am using :
Oracle 9i Enterprise Edition release 9.2.8.0 - 64 bit Production

I can't tell for the SELECT, but you should be allowed to UPDATE several fields at once:
UPDATE LIST set STS = null , NO = '1' WHERE JCODE = 8 AND KCODE = 01;
Edit: I don't understand why you need the second SELECT (with LIST#LNDB), but in both queries I don't think you really need all the fields, so instead of using SELECT *, which is heavy for the system, use only and explicitly the primary key's field name (like SELECT id FROM ...).
And there is a way to do it in one request, probably something like:
UPDATE LIST set STS = null , NO = '1' WHERE JCODE = 8 AND KCODE = 01 AND 0<(SELECT COUNT(*) FROM LIST WHERE JCODE = 8 AND KCODE = 01 AND LCODE = 2011);
This way, if there is no result found by SELECT, the WHERE clause in UPDATE will be false for every row, as 0<0 is false. There may also be a way to use COUNT() with a named field instead of *, I don't know Oracle enough for that.
Re-edit: indeed, if your second SELECT is actually an INSERT, you probably need that * :) But I don't think you can apply the same trick on the INSERT as the one on the UPDATE...
Re-re-edit: to write better what I put in the comment - taken from http://www.oradev.com/oracle_insert.jsp - your one and only request could be:
INSERT
WHEN (0=(SELECT COUNT(id) FROM LIST WHERE JCODE=8 AND KCODE=01 AND LCODE=2011))
INTO LIST (field1, field2, field3, STS, field4, field5, NO, field6)
SELECT field1, field2, field3, null, field4, field5, 1, field6
FROM LIST#LNDB
WHERE JCODE=8 AND KCODE=01 AND LCODE=2011
AND ban IN (SELECT BAN FROM billing_account)
Naturally you can add the GuZzie touch, use DECLARE, BEGIN and END to make the writing of parameters easier ;)

You can combine the two update queries.
Update LIST set STS = null, NO = '1' where JCODE = 8 AND KCODE = 01;
If you want to use variables you need to declare them and then simply call them in the query
DECLARE
v_JCODE NUMBER := 8;
v_KCODE NUMBER := 01;
v_LCODE NUMBER := 2011;
BEGIN
Update LIST set STS = null, NO = '1' where JCODE = v_JCODE and KCODE = v_KCODE;
END;
/
EDIT: Due to the discussion and comments below I've made a PL/SQL procedure which should do what you are looking for. please note that you neet to replace the schemaname.procedure in the 1st row as this is the name of the procedure in the scheme you're currently working.
CREATE OR REPLACE PROCEDURE schemaname.procedure is
-- Declare vars
v_JCODE NUMBER := 8;
v_KCODE NUMBER := 01;
v_LCODE NUMBER := 2011;
v_checkvar NUMBER;
BEGIN
select count(*)
into v_checkvar
from LIST
where JCODE = v_JCODE
and KCODE = v_KCODE
and LCODE = v_LCODE;
if v_checkvar = 0 then
insert into LIST
select * from LIST#LNDB
where JCODE = v_JCODE
and KCODE = v_KCODE
and LCODE = v_LCODE
and ban in (select BAN from billing_account);
update LIST
set STS = null, NO = '1'
where JCODE = v_JCODE
and KCODE = v_KCODE;
end if;
END;

Related

SAS - Proc SQL or Merge - Trying to optimise an INNER join that includes a string search (index)

I've a rudimentary SAS skillset, most of which involves "proc sql", so feel free to challenge the fundamental approach of using this.
I'm attempting to match one set of personal details against another set, the first having some ~400k rows and the other 22 million. The complexity is that the 400k rows feature previous names and postcodes as well as current ones (all on the same row), so my approach (code below) was to concatenate all of the surnames together and all of the postcodes together and search for the string from the second table (single name and postcode) within the concatenated strings using the index(source, excerpt) function.
proc sql;
CREATE TABLE R4 AS
SELECT DISTINCT
BS.CUST_ID,
ED.MATCH_ID
FROM T_RECS_WITH_CONCATS BS
INNER JOIN T_RECS_TO_MATCH ED
ON LENGTH(ED.SinglePostcode) > 4
AND index(BS.AllSurnames,ED.SingleSurname) > 0
AND index(BS.AllPostcodes,ED.SinglePostcode) > 0
;
QUIT;
In the above, AllSurnames can contain up to 9 surnames (delimited by |), and AllPostcodes up to 9 concatenated postcodes (again, delimited by |).
The downside of this is of course that it takes forever to run. Is there are more efficient way of doing this, either within a proc sql step or a real data step?
Here is a way using HASH component object
Presume the data sets are named SHORT_MANY and TALL_ONE. Use the data in SHORT_MANY to populate a multidata hash table that can operate as a lookup for values being checked in TALL_ONE.
Using just surname and postal code as the lookup key could result in many false matches.
Example (with numeric surname & postcode)
data SHORT_MANY;
do cust_id = 1 to 400;
array Surnames surname1-surname9;
array Postcodes postcode1-postcode9;
call missing (of surnames(*));
do index = 1 to dim(surnames);
surnames(index) = ceil (100000 * ranuni(123));
postcodes(index) = ceil ( 99999 * ranuni(123));
if ranuni(123) < 0.15 then leave;
end;
output;
end;
run;
data TALL_ONE(keep=match_id surname postcode forcemark);
do match_id = 1 to 22000;
surname = ceil(100000 * ranuni(1234));
postcode = ceil( 99999 * ranuni(1234));
forcemark = .;
if ranuni(123) < 0.15 then do; * randomly ensure some match will occur;
point = ceil(400*ranuni(123));
set SHORT_MANY point=point;
array surnames surname1-surname9;
array postcodes postcode1-postcode9;
do until (surname ne .);
index = ceil(9 * ranuni(123));
surname = surnames(index);
postcode = postcodes(index);
end;
forcemark = point;
end;
output;
end;
stop;
run;
data WHEN_TALL_MEETS_SHORT(keep=cust_id match_id index);
if 0 then set TALL_ONE SHORT_MANY ; * prep pdv (for hash host variables);
if _n_ = 1 then do;
length index 8;
declare hash lookup(multidata: 'yes');
lookup.defineKey('surname', 'postcode');
lookup.defineData('cust_id', 'index');
lookup.defineDone();
do while (not lookup_filled);
SET SHORT_MANY end=lookup_filled;
array Surnames surname1-surname9;
array Postcodes postcode1-postcode9;
do index = 1 to dim(surnames) while (surnames(index) ne .);
surname = surnames(index);
postcode = postcodes(index);
lookup.add();
end;
end;
end;
call missing (surname, postcode, cust_id, index);
set TALL_ONE;
rc = lookup.find(); * grab just first match -- has_next/find_next to retrieve other lookup matches;
run;

I need a function

I need to create a function that in cm_customers$rt table to verify to resident clients if the TAX_NUMBER is 13 digits otherwise need show the list to the screen, i was create juste select but i need a function,please help me
select TAX_NUMBER,RESIDENT
from cm_customers$rt
WHERE length (TAX_NUMBER) =13 and resident = 'Y'
;
CREATE OR REPLACE FUNCTION f_tax
RETURN NUMBER
IS
V_tax_number int;
BEGIN
SELECT tax_number
INTO V_tax_number
FROM E_EMP
WHERE LENGTH(tax_number ) < 13
AND residence_type = 'Y';
RETURN V_tax_number;
END;

SQL Developer bind variables prompt: force string values

I'm trying to use SQL Developer bind variables prompt to speed up query execution, but I'm not getting the desired output: looks like the values I put in get converted in number.
Table description:
Nome Null Type
------------------ -------- ------------
NUM NOT NULL VARCHAR2(13)
IS_OK NUMBER(1)
initial situation:
select NUM, IS_OK from numbers_table where NUM = cast(:dn as varchar2(13));
NUM |IS_OK |
------------|------|
08331930078 |1 |
working updates:
1.
update numbers_table set IS_OK = 0 where NUM = 08331930078;
update numbers_table set IS_OK = 0 where NUM = '08331930078';
ouput:
'1 row updated'
non-working updates:
1.
update numbers_table set IS_OK = 0 where NUM = :dn;
update numbers_table set IS_OK = 0 where NUM = cast(:dn as varchar2(13));
output:
'0 rows updated'
Don't know what else can I do to force the value being parsed as a string.
SQL Developer version 4.1.3.20
That's interesting, and looks like a bug. You don't actually need to cast, the value from the 'enter binds' window is a string anyway, so this works:
update numbers_table set IS_OK = 0 where NUM = :dn;
when the zero-padded string 08331930078 is entered in the dialog.
The cast is not needed but ought to still work. If you run as a script instead, with a defined bind variable, then both forms do work:
var dn varchar2(13);
exec :dn := '08331930078';
update numbers_table set IS_OK = 0 where NUM = :dn;
rollback;
update numbers_table set IS_OK = 0 where NUM = cast(:dn as varchar2(13));
rollback;
You get 1 row updated for both statements. Going back to running as a statement still prompts and still has the same (odd) behaviour even when the bind variable has been defined in a script in the same session.
Incidentally, when you do:
update numbers_table set IS_OK = 0 where NUM = 08331930078;
what you're actually doing, as you can see form the execution plan's predicate section, is:
update numbers_table set IS_OK = 0 where to_number(NUM) = 8331930078;
which will stop any index on the num column being used, and may result in unexpected results - in this case, if these are e.g. UK phone numbers you probably won't have the same value with and without the leading zero, but it's something to be wary of generally.

Firedac multiparametric query with macro

I created the main query that returns values for a whole, with 2 secondary conditions to restrict choice to be taken in the side combo box.
Everything works with the set parameters. I wish I could turn off or turn on these conditions with side combo box, how should I proceed?
my code is in Delphi:
procedure TForm1.Button3Click(Sender: TObject);
begin
FDQuery3.Close;
FDquery3.Params[0].Value := Datetimepicker1.Date;
FDquery3.Params[1].Value := Datetimepicker2.Date;
FDQuery3.Params[2].Value := Combobox3.Items [Combobox3.Itemindex];
FDQuery3.Params[3].Value := Combobox5.Items [Combobox5.Itemindex];
FDQuery3.Open;
end;
SQL text is:
select
G.NUM_PROG,T.DATA,T.ORA,C.DESCRIZIONE,
(select DESKEY from ANAFORN where CODKEY=T.CODICE ) as Cliente,
O.NOMINATIVO, T.TERMINALE,T.INCASSO
from LG_RIGHE G
inner join LG_TESTA T on G.NUM_PROG =T.NUM_PROG
inner join OPERATORI O on T.OPERATORE = O.CODICE
inner join LG_CAUSA C on T.CAUSALE = C.CODICE
where T.DATA >= :data1
and T.DATA <= :data2
and T.INCASSO = :pagamento
and T.TERMINALE = :terminale
order by G.NUM_PROG
i want turn on/off only Params[2][ and Params[3] (name: pagamento, terminale)
1) The typical way to optionally ignore a condition is to add one more "toggle" parameter. Consider this Delphi code
Result := True; // or False. Actually - some previous part of formula
If Need_Check_Option_A then
Result := Result and ( Option_A > 20 );
If Need_Check_Option_B then
Result := Result and ( Option_B < 10 );
Got the idea?
But very long that is, is there a more concise way to write it ?
Result := .....some other parts....
and (Ignore_Option_A or (Option_A > 20 ))
and (Ignore_Option_B or (Option_A < 10 ))
and ....
Now let's re-phrase it from Delphi to SQL WHERE clause
WHERE (.......) and (......)
AND ( ( :Use_pagamento = 0 ) or ( T.INCASSO = :pagamento ) )
AND ( ( :Use_terminale = 0 ) or ( T.TERMINALE = :terminale ) )
Whether you set that USE_xxxx parameter to zero (similar to false) then the second check would be shortcut out, ignored.
And the calling code would be something like
FDquery3.ParamByName('data1').AsDate := Datetimepicker1.Date;
FDquery3.ParamByName('data2').AsDate := Datetimepicker2.Date;
FDQuery3.ParamByName('pagamento').AsString := Combobox3.Items [Combobox3.Itemindex];
FDQuery3.ParamByName('terminale').AsString := Combobox5.Items [Combobox5.Itemindex];
FDQuery3.ParamByName('Use_pagamento').AsSmallInt := Ord( CheckBox3.Checked );
FDQuery3.ParamByName('Use_terminale').AsSmallInt := Ord( CheckBox5.Checked );
Some more suggestions follow:
2) using names like ComboBox3 are bad. You would not understand what they mean, what was they intended to be for. Look at your SQL - you give names there! You do not make it like
SELECT FIELD1, FIELD2 FROM TABLE1 WHERE FIELD3 < :PARAM1
And you have to give reasonable names to your Delphi objects too!
That FDQuery3, that Checkbox3 that Combobox5 - rename them all, give them some meaningful names!
3) you have a nested select there as the Cliente column. Unless very special circumstances that is slow and inefficient - change it to JOIN too (maybe to LEFT JOIN, if sometimes there is no matching value)
select
G.NUM_PROG,T.DATA,T.ORA,C.DESCRIZIONE,
-- (select DESKEY from ANAFORN where CODKEY=T.CODICE ) as Cliente,
A.DESKEY as Cliente,
O.NOMINATIVO, T.TERMINALE,T.INCASSO
from LG_RIGHE G
inner join LG_TESTA T on G.NUM_PROG =T.NUM_PROG
inner join OPERATORI O on T.OPERATORE = O.CODICE
inner join LG_CAUSA C on T.CAUSALE = C.CODICE
/* left */ join ANAFORN A on A.CODKEY=T.CODICE
where T.DATA >= :data1
and T.DATA <= :data2
AND ( ( :Use_pagamento = 0 ) or ( T.INCASSO = :pagamento ) )
AND ( ( :Use_terminale = 0 ) or ( T.TERMINALE = :terminale ) )
order by G.NUM_PROG
4) Depending on the circumstances you may just want to alter the SQL text.
If the parameter would be ignored - then simply remove it!
This option is not universal, it has good and bad sides though.
But in your case it would rather do good or nothing - because you have human to re-open the query and human would not be able to do it more often than once per second.
Good: then the server gets your SQL text it prepares the QUERY PLAN. The internal program of how to fetch your data. And it does not know yet what your parameters would be, so it prepares the PLAN to always check those parameters. Even if you later would ignore them. Sometimes it might make server choose slow PLAN where it could choose faster one if it knew the parameter would be not used. Sometimes it would make no difference. Game of luck.
Bad: if you keep the SQL text the same, then you can PREPARE the query once and the server would not build different PLAN when you re-open the query with different parameters. But if you do change the SQL text, then server would have to parse that new query and PREPARE the PLAN again before it would give you data. Sometimes it would take considerable time when you open-close queries, say, 1000 times per second. OF course, when you use a human to set those checkboxes, comboboxes and then press buttons, he would not do it that frequently, so in this case that risk is moot.
So in your case you might do something like this instead of introducing those toggle-parameters:
var qt: TStrings; // SQL query text
.....
qt := FDQuery3.SQL;
qt.Clear; // changing SQL Text would auto-close the query
qt.Add('select G.NUM_PROG,T.DATA,T.ORA,C.DESCRIZIONE, ');
qt.Add(' A.DESKEY as Cliente, O.NOMINATIVO, T.TERMINALE,T.INCASSO ');
qt.Add('from LG_RIGHE G ');
qt.Add(' join LG_TESTA T on G.NUM_PROG = T.NUM_PROG ');
qt.Add(' left join ANAFORN A on A.CODKEY=T.CODICE');
qt.Add(' join OPERATORI O on T.OPERATORE = O.CODICE ');
qt.Add(' join LG_CAUSA C on T.CAUSALE = C.CODICE ');
qt.Add('where T.DATA >= :data1 and T.DATA <= :data2 ');
if CheckBox3.Checked then
qt.Add(' and T.INCASSO = :pagamento ');
if CheckBox5.Checked then
qt.Add(' and T.TERMINALE = :terminale ');
qt.Add('order by G.NUM_PROG');
FDquery3.ParamByName('data1').AsDate := Datetimepicker1.Date;
FDquery3.ParamByName('data2').AsDate := Datetimepicker2.Date;
if CheckBox3.Checked then
FDQuery3.ParamByName('pagamento').AsString := Combobox3.Items [Combobox3.Itemindex];
if CheckBox3.Checked then
FDQuery3.ParamByName('terminale').AsString := Combobox5.Items [Combobox5.Itemindex];
FDQuery3.Open;
In this option you do not introduce extra toggle-parameters, but instead you only add value-parameters when user checked to use them. If user unchecked them - then you do not include them into your SQL text and consequently you do not assign them any values (they would not be found anyway).
5) you may use BETWEEN - it may be easier to read.
...
where ( T.DATA BETWEEN :data1 AND :data2 )
and T.INCASSO = :pagamento
....

Ckeck the presence of xml tag using oracle

<wbi:appData>
<wbi:content wbi:name="1st_status">
<wbi:value xsi:type="xsd:string">Success</wbi:value>
</wbi:content>
</wbi:appData>
this xml is in a table which has a column in the form of CLOB type.
I wanted to find if "wbi:value" tag exists in this xml or not ?
I tried using existsnode but in sql developer it is saying an error as to declare existsnode.
yes use existsnode:
SQL> with yourdata as (select to_clob('<wbi:event xmlns:wbi="http://foo" xmlns:xsi="http://x" xmlns:xsd="http://d">
2 <wbi:appData>
3 <wbi:content wbi:name="1st_status">
4 <wbi:value xsi:type="xsd:string">Success</wbi:value>
5 </wbi:content>
6 <wbi:content wbi:name="2nd_status">
7 <wbi:value xsi:type="xsd:string">Failure</wbi:value>
8 </wbi:content>
9 </wbi:appData>
10 </wbi:event>') c from dual)
11 select existsnode(xmltype(c), '/wbi:event/wbi:appData/wbi:content','xmlns:wbi="http://foo"') is_exist
12 from yourdata t
13 /
IS_EXIST
----------
1
ie
existsnode(xmltype(c), '/wbi:event/wbi:appData/wbi:content','xmlns:wbi="http://foo"')
1 = exists
0 = does not exist.
note that in my sample, i had two matching nodes (as i didn't filter on wbi:name). you can filter the xpath of course. eg:
/wbi:event/wbi:appData/wbi:content[#wbi:name="1st_status"]
to limit matches to the "1st_status" one
select count(*)
from clobtab
where existsNode(xmltype.createxml(clobcol),'/wbi:appData/wbi:content/wbi:value') = 1;
If it reurns more than 0 then it exists otherwise not.
So your trigger would be-
CREATE TRIGGER Tab_a
BEFORE INSERT
FOR EACH ROW
declare
xml_a xmltype;
begin
xml_a:=xmltype(:new.value);
if existsNode(xml_a,'/wbi:appData/wbi:content/wbi:value','xmlns:wbi="http://pat.namespace.com"') = 1
then
----insert ....
end if;
end;
actually you can use oracle's instr function, which is fast.
like:
where instr(field, 'wbi:value') > 0
You can use XMLEXISTS:
SELECT DESCRIPTOR_XML FROM TABLE_WITH_AN_XMLTYPE_COLUMN
WHERE
XMLEXISTS('//functions[function/arg[#name="class.name" and not(starts-with(., "com.example.apps.YouShantSeeMeClass"))]]'
PASSING BY VALUE DESCRIPTOR_XML);