FireDAC: possibility to make query faster - Delphi - sql

I use bind variables in Delphi and on the other side there is Oracle database with Database link (#dblink). When I build a SELECT statement without bind variables, it has no problem with performance. Only if I query SELECT statement with bind variables it takes very long (sometimes 1-2 hour). Is it possible in FireDAC to make the query faster without changing SQL-query? I need to use bind variables to avoid SQL injection.
This SQL is very Fast in Delphi:
SELECT
DLGH_START_D Datum,
TRUNC((d.DLGH_ENDE_D - d.DLGH_START_D) * 24 * 60)||' Min '||
TRUNC(MOD(((d.DLGH_ENDE_D - d.DLGH_START_D) * 24 * 3600), 60))||' Sek' Dauer
FROM
dialoghistory d
WHERE
d.DLGH_PARAMETER_C = 'Name of Parameter' AND <--
d.dlgh_funktion_c = 'SQLS' AND
d.DLGH_START_D > '01.02.2020' <--
order by 1
This SQL is very slow in Delphi:
SELECT
DLGH_START_D Datum,
TRUNC((d.DLGH_ENDE_D - d.DLGH_START_D) * 24 * 60)||' Min '||
TRUNC(MOD(((d.DLGH_ENDE_D - d.DLGH_START_D) * 24 * 3600), 60))||' Sek' Dauer
FROM
dialoghistory d
WHERE
d.DLGH_PARAMETER_C = :B_Name AND <--
d.dlgh_funktion_c = 'SQLS' AND
d.DLGH_START_D > :Datum <--
order by 1
---------------------------
//slow execution period , because of bind variables (1 h)
qry := TFDQuery.CreateSQL(Application, sSqlText_.Text);
with qry do begin
...
Param.AsString := value; //set value of bind variable
...
Open;
Table dialoghistory looks like this

My Solution:
tmpQuery := TFDQuery.Create(self); // Create Query
tmpQuery.Sql.Text := 'SELECT * FROM dlgHistory d where d.DLGH_PARAMETER = :par';
...
// Set Data Type, Param Type and Size yourself
with tmpQuery.Params do begin
Clear;
with Add do begin
Name := 'par';
DataType := ftString;
Size := 128;
ParamType := ptInput;
end;
end;
tmpQuery.Params[0].AsString := 'Value'; //assign a value
tmpQuery.Prepare;
tmpQuery.Open; //And it works perfect!

Related

adding to variables inside of a oracle procedure

I created a procedure that takes the account number of a user and it returns every transaction they've made plus their running balance. everything works except the balance. The code compiles and runs but nothing shows up in the running balance column of the dbms.output
CREATE OR REPLACE PROCEDURE print_dett(
in_account_nbr NUMBER
)
IS
dCount NUMBER;
BEGIN
dbms_output.put_line(' Date : transaction amount : balance - For: ' || in_account_nbr);
FOR r IN(
SELECT t.tx_nbr, t.tx_date, t.tx_amount, t.tx_type_code, a.balance
FROM transaction t, account a
WHERE t.account_nbr = a.account_nbr
AND t.account_nbr = in_account_nbr
ORDER BY tx_date)
LOOP
IF r.tx_type_code = 'D' OR r.tx_type_code = 'R' THEN
dCount := dCount + r.tx_amount;
ELSE
dCount := dCount - r.tx_amount;
END IF;
dbms_output.put_line(r.tx_date || ' : ' || r.tx_amount || ' : ' || dCount );
END LOOP;
END;
What can I change in the code so the running balance will actually show up when printed?
Number variables get initialized with NULL. Your variable dCount is hence null first and adding values doesn't change this.
Initialize it with zero instead:
CREATE OR REPLACE PROCEDURE print_dett(
in_account_nbr NUMBER
)
IS
dCount NUMBER := 0;
...

Perform multiple SQL Server updates without looping

I have something like this:
var
text: string ;
fid: string;
for a = 1 to 100 do
begin
text:=stringgrid.cells[5,a];
fid:=stringgrid.cells[0,a];
query.SQL.Text := 'Update dbtable Set atext=' + text + ' Where id=' + fid;
query.ExecSQL;
end;
Is there a way to avoid calling ExecSQL() 100 times?
In this particular instance, yes, it is easy to do the updates with a single call to ExecSQL().
If the IDs are sequential, you can do this:
var
text: string ; // data i take from table in my loop.
query.SQL.Text := 'Update dbtable Set atext=' + QuotedStr(text) + ' Where id >= 1 and id <= 100';
query.ExecSQL;
If the IDs are not sequential, you could use an in clause instead, but this is less efficient if you have a large list of IDs:
var
text: string ; // data i take from table in my loop.
query.SQL.Text := 'Update dbtable Set atext=' + QuotedStr(text) + ' Where id in [1, ..., 100]';
query.ExecSQL;
Another option would be to create a stored procedure in the DB. Have it take the text and IDs as paramenters, and then perform any needed looping internally. You can then perform a single SQL statement in your code to execute the stored procedure with parameter values.
With a minimum of information I suggestd you can use the functionality of Delphi.
Do something like this:
var
Text: string ; // data i take from table in my loop.
query.open('select * from dbtable where id between 1 and 100');
query.beginBatch;
for a = 1 to 100 do
begin
if query.locate('id',id) then
begin
query.edit;
query.fieldbyname('atext').asString:=text;
end;
end;
query.endBatch;
query.post;
I can't say anything to the performance of this. Yout have to test it by your own.
procedure TForm1.Button1Click(Sender: TObject);
var
a: Integer;
text: String;
begin
query.SQL.Clear;
for a := 1 to 100 do
begin
query.SQL.Add('Update dbtable Set atext=' + QuotedStr(text) +
' Where id=' + IntToStr(a) + ';');
end;
query.ExecSQL;
end;

How to generate a random, unique, alphanumeric ID of length N in Postgres 9.6+?

I've seen a bunch of different solutions on StackOverflow that span many years and many Postgres versions, but with some of the newer features like gen_random_bytes I want to ask again to see if there is a simpler solution in newer versions.
Given IDs which contain a-zA-Z0-9, and vary in size depending on where they're used, like...
bTFTxFDPPq
tcgHAdW3BD
IIo11r9J0D
FUW5I8iCiS
uXolWvg49Co5EfCo
LOscuAZu37yV84Sa
YyrbwLTRDb01TmyE
HoQk3a6atGWRMCSA
HwHSZgGRStDMwnNXHk3FmLDEbWAHE1Q9
qgpDcrNSMg87ngwcXTaZ9iImoUmXhSAv
RVZjqdKvtoafLi1O5HlvlpJoKzGeKJYS
3Rls4DjWxJaLfIJyXIEpcjWuh51aHHtK
(Like the IDs that Stripe uses.)
How can you generate them randomly and safely (as far as reducing collisions and reducing predictability goes) with an easy way to specify different lengths for different use cases, in Postgres 9.6+?
I'm thinking that ideally the solution has a signature similar to:
generate_uid(size integer) returns text
Where size is customizable depending on your own tradeoffs for lowering the chance of collisions vs. reducing the string size for usability.
From what I can tell, it must use gen_random_bytes() instead of random() for true randomness, to reduce the chance that they can be guessed.
Thanks!
I know there's gen_random_uuid() for UUIDs, but I don't want to use them in this case. I'm looking for something that gives me IDs similar to what Stripe (or others) use, that look like: "id": "ch_19iRv22eZvKYlo2CAxkjuHxZ" that are as short as possible while still containing only alphanumeric characters.
This requirement is also why encode(gen_random_bytes(), 'hex') isn't quite right for this case, since it reduces the character set and thus forces me to increase the length of the strings to avoid collisions.
I'm currently doing this in the application layer, but I'm looking to move it into the database layer to reduce interdependencies. Here's what the Node.js code for doing it in the application layer might look like:
var crypto = require('crypto');
var set = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
function generate(length) {
var bytes = crypto.randomBytes(length);
var chars = [];
for (var i = 0; i < bytes.length; i++) {
chars.push(set[bytes[i] % set.length]);
}
return chars.join('');
}
Figured this out, here's a function that does it:
CREATE OR REPLACE FUNCTION generate_uid(size INT) RETURNS TEXT AS $$
DECLARE
characters TEXT := 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
bytes BYTEA := gen_random_bytes(size);
l INT := length(characters);
i INT := 0;
output TEXT := '';
BEGIN
WHILE i < size LOOP
output := output || substr(characters, get_byte(bytes, i) % l + 1, 1);
i := i + 1;
END LOOP;
RETURN output;
END;
$$ LANGUAGE plpgsql VOLATILE;
And then to run it simply do:
generate_uid(10)
-- '3Rls4DjWxJ'
Warning
When doing this you need to be sure that the length of the IDs you are creating is sufficient to avoid collisions over time as the number of objects you've created grows, which can be counter-intuitive because of the Birthday Paradox. So you will likely want a length greater (or much greater) than 10 for any reasonably commonly created object, I just used 10 as a simple example.
Usage
With the function defined, you can use it in a table definition, like so:
CREATE TABLE users (
id TEXT PRIMARY KEY DEFAULT generate_uid(10),
name TEXT NOT NULL,
...
);
And then when inserting data, like so:
INSERT INTO users (name) VALUES ('ian');
INSERT INTO users (name) VALUES ('victor');
SELECT * FROM users;
It will automatically generate the id values:
id | name | ...
-----------+--------+-----
owmCAx552Q | ian |
ZIofD6l3X9 | victor |
Usage with a Prefix
Or maybe you want to add a prefix for convenience when looking at a single ID in the logs or in your debugger (similar to how Stripe does it), like so:
CREATE TABLE users (
id TEXT PRIMARY KEY DEFAULT ('user_' || generate_uid(10)),
name TEXT NOT NULL,
...
);
INSERT INTO users (name) VALUES ('ian');
INSERT INTO users (name) VALUES ('victor');
SELECT * FROM users;
id | name | ...
---------------+--------+-----
user_wABNZRD5Zk | ian |
user_ISzGcTVj8f | victor |
I'm looking for something that gives me "shortcodes" (similar to what Youtube uses for video IDs) that are as short as possible while still containing only alphanumeric characters.
This is a fundamentally different question from what you first asked. What you want here then is to put a serial type on the table, and to use hashids.org code for PostgreSQL.
This returns 1:1 with the unique number (serial)
Never repeats or has a chance of collision.
Also base62 [a-zA-Z0-9]
Code looks like this,
SELECT id, hash_encode(foo.id)
FROM foo; -- Result: jNl for 1001
SELECT hash_decode('jNl') -- returns 1001
This module also supports salts.
Review,
26 characters in [a-z]
26 characters in [A-Z]
10 characters in [0-9]
62 characters in [a-zA-Z0-9] (base62)
The function substring(string [from int] [for int]) looks useful.
So it looks something like this. First we demonstrate that we can take the random-range and pull from it.
SELECT substring(
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
1, -- 1 is 'a', 62 is '9'
1,
);
Now we need a range between 1 and 63
SELECT trunc(random()*62+1)::int+1
FROM generate_series(1,1e2) AS gs(x)
This gets us there.. Now we just have to join the two..
SELECT substring(
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
trunc(random()*62)::int+1
1
)
FROM generate_series(1,1e2) AS gs(x);
Then we wrap it in an ARRAY constructor (because this is fast)
SELECT ARRAY(
SELECT substring(
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
trunc(random()*62)::int+1,
1
)
FROM generate_series(1,1e2) AS gs(x)
);
And, we call array_to_string() to get a text.
SELECT array_to_string(
ARRAY(
SELECT substring(
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
trunc(random()*62)::int+1,
1
)
FROM generate_series(1,1e2) AS gs(x)
)
, ''
);
From here we can even turn it into a function..
CREATE FUNCTION random_string(randomLength int)
RETURNS text AS $$
SELECT array_to_string(
ARRAY(
SELECT substring(
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
trunc(random()*62)::int+1,
1
)
FROM generate_series(1,randomLength) AS gs(x)
)
, ''
)
$$ LANGUAGE SQL
RETURNS NULL ON NULL INPUT
VOLATILE LEAKPROOF;
and then
SELECT * FROM random_string(10);
Thanks to Evan Carroll answer, I took a look on hashids.org.
For Postgres you have to compile the extension or run some TSQL functions.
But for my needs, I created something simpler based on hashids ideas (short, unguessable, unique, custom alphabet, avoid curse words).
Shuffle alphabet:
CREATE OR REPLACE FUNCTION consistent_shuffle(alphabet TEXT, salt TEXT) RETURNS TEXT AS $$
DECLARE
SALT_LENGTH INT := length(salt);
integer INT = 0;
temp TEXT = '';
j INT = 0;
v INT := 0;
p INT := 0;
i INT := length(alphabet) - 1;
output TEXT := alphabet;
BEGIN
IF salt IS NULL OR length(LTRIM(RTRIM(salt))) = 0 THEN
RETURN alphabet;
END IF;
WHILE i > 0 LOOP
v := v % SALT_LENGTH;
integer := ASCII(substr(salt, v + 1, 1));
p := p + integer;
j := (integer + v + p) % i;
temp := substr(output, j + 1, 1);
output := substr(output, 1, j) || substr(output, i + 1, 1) || substr(output, j + 2);
output := substr(output, 1, i) || temp || substr(output, i + 2);
i := i - 1;
v := v + 1;
END LOOP;
RETURN output;
END;
$$ LANGUAGE plpgsql VOLATILE;
The main function:
CREATE OR REPLACE FUNCTION generate_uid(id INT, min_length INT, salt TEXT) RETURNS TEXT AS $$
DECLARE
clean_alphabet TEXT := 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
curse_chars TEXT := 'csfhuit';
curse TEXT := curse_chars || UPPER(curse_chars);
alphabet TEXT := regexp_replace(clean_alphabet, '[' || curse || ']', '', 'gi');
shuffle_alphabet TEXT := consistent_shuffle(alphabet, salt);
char_length INT := length(alphabet);
output TEXT := '';
BEGIN
WHILE id != 0 LOOP
output := output || substr(shuffle_alphabet, (id % char_length) + 1, 1);
id := trunc(id / char_length);
END LOOP;
curse := consistent_shuffle(curse, output || salt);
output := RPAD(output, min_length, curse);
RETURN output;
END;
$$ LANGUAGE plpgsql VOLATILE;
How-to use examples:
-- 3: min-length
select generate_uid(123, 3, 'salt'); -- output: "0mH"
-- or as default value in a table
CREATE SEQUENCE IF NOT EXISTS my_id_serial START 1;
CREATE TABLE collections (
id TEXT PRIMARY KEY DEFAULT generate_uid(CAST (nextval('my_id_serial') AS INTEGER), 3, 'salt')
);
insert into collections DEFAULT VALUES ;
This query generate required string. Just change second parasmeter of generate_series to choose length of random string.
SELECT
string_agg(c, '')
FROM (
SELECT
chr(r + CASE WHEN r > 25 + 9 THEN 97 - 26 - 9 WHEN r > 9 THEN 64 - 9 ELSE 48 END) AS c
FROM (
SELECT
i,
(random() * 60)::int AS r
FROM
generate_series(0, 62) AS i
) AS a
ORDER BY i
) AS A;
So I had my own use-case for something like this. I am not proposing a solution to the top question, but if you are looking for something similar like I am, then try this out.
My use-case was that I needed to create a random external UUID (as a primary key) with as few characters as possible. Thankfully, the scenario did not have a requirement that a large amount of these would ever be needed (probably in the thousands only). Therefore a simple solution was a combination of using generate_uid() and checking to make sure that the next sequence was not already used.
Here is how I put it together:
CREATE OR REPLACE FUNCTION generate_id (
in length INT
, in for_table text
, in for_column text
, OUT next_id TEXT
) AS
$$
DECLARE
id_is_used BOOLEAN;
loop_count INT := 0;
characters TEXT := 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
loop_length INT;
BEGIN
LOOP
next_id := '';
loop_length := 0;
WHILE loop_length < length LOOP
next_id := next_id || substr(characters, get_byte(gen_random_bytes(length), loop_length) % length(characters) + 1, 1);
loop_length := loop_length + 1;
END LOOP;
EXECUTE format('SELECT TRUE FROM %s WHERE %s = %s LIMIT 1', for_table, for_column, quote_literal(next_id)) into id_is_used;
EXIT WHEN id_is_used IS NULL;
loop_count := loop_count + 1;
IF loop_count > 100 THEN
RAISE EXCEPTION 'Too many loops. Might be reaching the practical limit for the given length.';
END IF;
END LOOP;
END
$$
LANGUAGE plpgsql
STABLE
;
here is an example table usage:
create table some_table (
id
TEXT
DEFAULT generate_id(6, 'some_table', 'id')
PRIMARY KEY
)
;
and a test to see how it breaks:
DO
$$
DECLARE
loop_count INT := 0;
BEGIN
-- WHILE LOOP
WHILE loop_count < 1000000
LOOP
INSERT INTO some_table VALUES (DEFAULT);
loop_count := loop_count + 1;
END LOOP;
END
$$ LANGUAGE plpgsql
;

How do I merge the multiple If Else using decode in PL/SQL?

The code goes like-
If X=x1
then
select a,b
from tab
where zz=yy;
elsif X=x2 or X=x3
then
select a,b
from tab
where zz=yy
and yy= 123;
elsif X=x4
then
select a,b
from tab
where zz=yy
and yy= 456;
end if;
The code in select is being repeated. How should i merge it?
At first I thought that a CASE statement would be appropriate, but then I noticed that your logic always conditionally selects the same two columns a and b. So I think that what you need here is a WHERE clause to handle all the cases.
SELECT a, b
FROM tab
WHERE (x = 'x1' OR
((x = 'x2' OR x = 'x3') AND yy = 123) OR
(x = 'x4' AND yy = 456)) AND
zz = yy
You can use dynamic SQL and compose your query from different parts.
For example:
-- common part of query
common_query := 'SELECT a, b FROM tab WHERE zz= yy';
-- set additional condition
IF X=x1 THEN add_where := '';
ELSIF X=x2 OR X=x3 THEN add_where := ' AND yy= 123';
ELSIF X=x4 THEN add_where := ' AND yy= 456';
END IF;
-- compose final query
final_query:= common_query||add_where;
EXECUTE IMMEDIATE final_query;
Of couse when you use dynamic SQL you can use bind variables and you need to remember about return values.

COUNT function for files?

Is it possible to use COUNT in some way that will give me the number of tuples that are in a .sql file? I tried using it in a query with the file name like this:
SELECT COUNT(*) FROM #q65b;
It tells me that the table is invalid, which I understand because it isn't a table, q65b is a file with a query saved in it. I'm trying to compare the number of rows in q65b to a view that I have created. Is this possible or do I just have to run the query and check the number of rows at the bottom?
Thanks
You can do this in SQL*Plus. For example:
Create the text file, containing the query (note: no semicolon!):
select * from dual
Save it in a file, e.g. myqueryfile.txt, to the folder accessible from your SQL*Plus session.
You can now call this from within another SQL query - but make sure the # as at the start of a line, e.g.:
SQL> select * from (
2 #myqueryfile.txt
3 );
D
-
X
I don't personally use this feature much, however.
Here is one approach. It's a function which reads a file in a directory, wraps the contents in a select count(*) from ( .... ) construct and executes the resultant statement.
1 create or replace function get_cnt
2 ( p_file in varchar2 )
3 return number
4 as
5 n pls_integer;
6 stmt varchar2(32767);
7 f_line varchar2(255);
8 fh utl_file.file_type;
9 begin
10 stmt := 'select count(*) from (';
11 fh := utl_file.fopen('SQL_SCRIPTS', p_file, 'R');
12 loop
13 utl_file.get_line(fh, f_line );
14 if f_line is null then exit;
15 elsif f_line = '/' then exit;
16 else stmt := stmt ||chr(10)||f_line;
17 end if;
18 end loop;
19 stmt := stmt || ')';
20 execute immediate stmt into n;
21 return n;
22* end get_cnt;
SQL>
Here is the contents of a sql file:
select * from emp
/
~
~
~
"scripts/q_emp.sql" 3L, 21C
And here is how the script runs:
SQL> select get_cnt ('q_emp.sql') from dual
2 /
GET_CNT('Q_EMP.SQL')
--------------------
14
SQL>
So it works. Obviously what I have posted is just a proof of concept. You will need to include lots of error handling for the UTL_FILE aspects - it's a package which can throw lots of exceptions - and probably some safety checking of the script that gets passed.