Big Array DML (FireDAC) work failded with PostgreSQL - postgresql

I need to insert a big batch rows to my PostgreSQL(v9.5) table.
Here is the table:
create table TaskLog(
id serial,
taskid integer,
x double precision,
y double precision,
loc character varying(50),
speed smallint,
gpstime timestamp without time zone,
veh character varying(16),
vin character(17),
regdate date,
enabled boolean,
remake character varying(100),
isdel boolean,
alarm integer,
CONSTRAINT pk_delphi_id PRIMARY KEY (id)
)
I use FireDAC(Delphi XE10.1) to insert the rows:
function RandomStr(aLength : Integer) : string;
var
X: Integer;
begin
if aLength <= 0 then exit;
SetLength(Result, aLength);
for X:=1 to aLength do
Result[X] := Chr(Random(26) + 65);
end;
procedure TForm7.Button6Click(Sender: TObject);
var
i: Integer;
Watch: TStopwatch;
begin
Watch := TStopwatch.StartNew;
try
FDQuery1.SQL.Text :=
'insert into TaskLog values(default, :f1, :f2, :f3, :f4, :f5, :f6, ' +
':f7, :f8, :f9, :f10, :f11, :f12, :f13)';
FDQuery1.Params.ArraySize := StrToInt(Edit1.text); //<--- Change the ArraySize
for i := 0 to FDQuery1.Params.ArraySize - 1 do
begin
FDQuery1.Params[0].AsIntegers[i] := Random(9999999);
FDQuery1.Params[1].AsFloats[i] := Random(114) + Random;
FDQuery1.Params[2].AsFloats[i] := Random(90) + Random;
FDQuery1.Params[3].AsStrings[i] := RandomStr(Random(50));
FDQuery1.Params[4].AsSmallInts[i] := Random(1990);
FDQuery1.Params[5].AsDateTimes[i] := IncSecond(IncDay(Now, -(Random(100) + 1)), Random(99999));
FDQuery1.Params[6].AsStrings[i] := RandomStr(Random(16));
FDQuery1.Params[7].AsStrings[i] := RandomStr(Random(17));
FDQuery1.Params[8].AsDates[i] := IncDay(Now, -(Random(365) + 1));
FDQuery1.Params[9].AsBooleans[i] := Odd(Random(200));
FDQuery1.Params[10].AsStrings[i] := RandomStr(Random(100));
FDQuery1.Params[11].AsBooleans[i] := Odd(Random(100));
FDQuery1.Params[12].AsIntegers[i] := Random(100000);
end;
FDQuery1.Execute(FDQuery1.Params.ArraySize);
Watch.Stop;
Memo1.Lines.Add('Should be inserted ' + IntToStr(FDQuery1.Params.ArraySize) + ' lines');
Memo1.Lines.Add('Actually inserted ' + IntToStr(FDQuery1.RowsAffected) + ' lines');
Memo1.Lines.Add('Take ' + Watch.ElapsedMilliseconds.ToString + ' seconds');
except
Memo1.Lines.Add(Exception(ExceptObject).Message);
end;
end;
It work fine when I set the FDQuery1.Params.ArraySize:=1000,
It work failed when I set the FDQuery1.Params.ArraySize:=10000, no record can been inserted.
Is the ArraySize property have a size limit with PostgreSQL?

const batch_part = 2000;
****
PostgreSQL.Query.Params.ArraySize := Total;
****
// отправка пакетами в цикле / sending packets in a loop
if Total > batch_part then begin
j:= Total div batch_part;
for n := 1 to j do begin
PostgreSQL.Query.Execute(n*batch_part, (n-1)*batch_part); // #Batch INSERT operation
LogFile.Write(Format('FDPostgreSQL.Query.Execute(%d,%d)', [n*batch_part, (n-1)*batch_part]));
end;
if (Total mod batch_part) > 0 then begin // остаток / remnant
PostgreSQL.Query.Execute((Total mod batch_part)+(n-1)*batch_part, (n-1)*batch_part); // #Batch INSERT operation
LogFile.Write(Format('FDPostgreSQL.Query.Execute(%d,%d)', [(Total mod batch_part)+(n-1)*batch_part, (n-1)*batch_part]));
end;
end
else begin
PostgreSQL.Query.Execute(Total, 0);
LogFile.Write(Format('FDPostgreSQL.Query.Execute(%d,0)', [Total]));
end;
LogFile:
[04.08.2022 10:23:21.678] Batch INSERT in FDPostgreSQL...
[04.08.2022 10:23:22.079] FDPostgreSQL Execute(2000,0)
[04.08.2022 10:23:22.154] FDPostgreSQL Execute(4000,2000)
[04.08.2022 10:23:22.227] FDPostgreSQL Execute(6000,4000)
[04.08.2022 10:23:22.299] FDPostgreSQL Execute(8000,6000)
[04.08.2022 10:23:22.373] FDPostgreSQL Execute(10000,8000)
[04.08.2022 10:23:22.443] FDPostgreSQL Execute(12000,10000)
[04.08.2022 10:23:22.516] FDPostgreSQL Execute(13014,12000)
[04.08.2022 10:23:22.540] Batch INSERT finished: 13014 records.
Server side SQL:
INSERT INTO propose_sid VALUES(DEFAULT, $1, $2, $3, $4, $5, $6, $7, $8),(DEFAULT, $9, $10, $11, $12, $13, $14, $15, $16),(DEFAULT, $17, $18, $19, $20, $21, $22, $23, $24)... -- 1999 times

Related

INTERVAL add i day is not working in postgresql

I have a table like this:
CREATE TABLE DateInsert(
DateInsert timestamp without time zone,
DateInt integer NOT NULL
);
I want insert list day from 2018-01-01 to 2045-05-18 but it give me an erro
"invalid input syntax for type interval:"
CREATE OR REPLACE FUNCTION insertdate() RETURNS integer AS $$
DECLARE i integer := 0;
d timestamp without time zone := '2018-01-01';
di integer := 0;
BEGIN
while i <10000
LOOP
d := d + INTERVAL ''+ i::character varying + ' day';
di := to_char(d , 'yyyymmdd')::int;
insert into DateInsert(DateInsert,DateInt) values(d, di);
i := i+1;
END LOOP ;
return i;
END;
$$ LANGUAGE plpgsql;
How can I insert to db with timestamp increase 1 in n day loop?
Code In sql server has been working.
declare #i int=0
declare #d datetime
declare #di int = 0
while #i <10000
begin
set #d = DATEADD(DAY, #i, '2018-01-01')
set #di = cast(CONVERT(VARCHAR(10), #d, 112) as int)
insert into DateInsert(DateInsert,DateInt) values(#d, #di)
set #i = #i+1
end
The concatenation operator is || not +. And the prefixed form doesn't seem to like anything else than literals. But you can cast the concatenation expression.
So changing
...
d := d + INTERVAL ''+ i::character varying + ' day';
...
to
...
d := d + (i || ' day')::interval;
...
should work for you.

Oracle to Postgres Conversion trouble shooting

Converted a Standalone Procedure from Oracle to Postgres but not sure why there is an run error even the code is successfully compiled
Converted the below code from Oracle to Postgres
CREATE OR REPLACE FUNCTION ssp2_pcat.pop_hoa_contracts_for_prod(
)
RETURNS void
LANGUAGE 'plpgsql'
COST 100
VOLATILE
AS $BODY$
DECLARE
C1 CURSOR for
SELECT MARKET_CODE, CONTRACT_COMBO_ID, COUNT(*) FROM
ssp2_pcat.VPF_HOA_CONTRACTS_FOR_PROD A
WHERE start_Date IN
(SELECT MAX(start_date)
FROM VPF_HOA_CONTRACTS_FOR_PROD b
WHERE A.MARKET_CODE = b.MARKET_CODE
AND A.CONTRACT_COMBO_ID = b.CONTRACT_COMBO_ID
AND A.CONTRACT_ID = B.CONTRACT_ID
AND b.start_date <= current_date
AND b.end_date > current_date )
GROUP BY MARKET_CODE, CONTRACT_COMBO_ID
ORDER BY MARKET_CODE, CONTRACT_COMBO_ID;
C2 CURSOR(iMktCode VARCHAR, iCombo integer) for
SELECT MARKET_CODE, CONTRACT_COMBO_ID, CONTRACT_ID
FROM ssp2_pcat.VPF_HOA_CONTRACTS_FOR_PROD A
WHERE start_Date IN
(SELECT MAX(start_date)
FROM ssp2_pcat.VPF_HOA_CONTRACTS_FOR_PROD b
WHERE A.MARKET_CODE = b.MARKET_CODE
AND A.CONTRACT_COMBO_ID = b.CONTRACT_COMBO_ID
AND A.CONTRACT_ID = B.CONTRACT_ID
AND b.start_date <= current_date
AND b.end_date > current_date )
AND MARKET_CODE = iMktCode
AND CONTRACT_COMBO_ID = iCombo
ORDER BY MARKET_CODE, CONTRACT_COMBO_ID, START_DATE;
Contracts VARCHAR(32000);
Contract_Val1 VARCHAR(4000) := NULL;
Contract_Val2 VARCHAR(4000) := NULL;
Contract_Val3 VARCHAR(4000) := NULL;
Contract_Val4 VARCHAR(4000) := NULL;
Contract_Val5 VARCHAR(4000) := NULL;
Contract_Val6 VARCHAR(4000) := NULL;
Contract_Val7 VARCHAR(4000) := NULL;
Contract_Val8 VARCHAR(4000) := NULL;
Num INTEGER;
Cont_Num INTEGER;
l_start TIMESTAMP := clock_timestamp();
l_end TIMESTAMP := clock_timestamp();
Time_Taken VARCHAR(20);
i record;
j record;
BEGIN
l_start := clock_timestamp();
DELETE FROM ssp2_pcat.HOA_CONTRACTS_KH;
FOR i IN C1 LOOP
BEGIN
Num := 0;
Contracts := NULL;
Cont_Num := 1;
FOR j IN C2 (i.MARKET_CODE, i.CONTRACT_COMBO_ID) LOOP
Num := Num + 1;
IF Num = 1 THEN
Contracts := '|' || j.CONTRACT_ID || '|';
ELSE
IF LENGTH(Contracts || j.CONTRACT_ID || '|') > 4000 THEN
PERFORM ssp2_pcat.Assign (Cont_Num, SUBSTRING(Contracts, 1,
LENGTH(Contracts)-1));
Num := 1;
Contracts := '|' || j.CONTRACT_ID || '|';
Cont_Num := Cont_Num + 1;
ELSE
Contracts := Contracts || j.CONTRACT_ID || '|';
END IF;
END IF;
END LOOP;
PERFORM ssp2_pcat.Assign (Cont_Num, Contracts);
IF Cont_Num > 5 THEN
raise notice'%', ('MARKET_CODE: ' || i.MARKET_CODE || ', CONTRACT_COMBO_ID: ' || i.CONTRACT_COMBO_ID || ' has more than 32K in size. These Contracts are left out: ' || Contracts);
END IF;
INSERT INTO HOA_CONTRACTS_KH
(
MARKET_CODE,
CONTRACT_COMBO_ID,
CONTRACT_ID,
CONTRACT_ID2,
CONTRACT_ID3,
CONTRACT_ID4,
CONTRACT_ID5,
LAST_UPDATED
)
VALUES
(
i.MARKET_CODE,
i.CONTRACT_COMBO_ID,
Contract_Val1,
Contract_Val2,
Contract_Val3,
Contract_Val4,
Contract_Val5,
CURRENT_TIMESTAMP::TIMESTAMP(0)
);
Contract_Val1 := NULL;
Contract_Val2 := NULL;
Contract_Val3 := NULL;
Contract_Val4 := NULL;
Contract_Val5 := NULL;
Contract_Val6 := NULL;
Contract_Val7 := NULL;
Contract_Val8 := NULL;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
raise notice'%', ('1) POP_HOA_CONTRACTS_FOR_PROD: ' || SQLERRM);
END;
END LOOP;
RAISE NOTICE 'Function excution time Took: %', l_start;
RAISE NOTICE 'Function excution time Took: %',l_end-l_start;
SELECT l_end-l_start INTO Time_Taken;
raise notice'%',('POP_HOA_CONTRACTS_FOR_PROD Took: ' || Time_Taken );
EXCEPTION
WHEN OTHERS THEN
raise notice'%', ('2) POP_HOA_CONTRACTS_FOR_PROD: ' || SQLERRM);
END;
$BODY$;
The code is compiled successfully, but giving a run time error as follows,
NOTICE: 2) POP_HOA_CONTRACTS_FOR_PROD: cannot begin/end transactions in PL/pgSQL
Debugged the whole code and looks like still I'm unable to identify the issue, can any one help me in making me understand more about Postgres as I'm new to this Database. Found out in unit testing that its not calling the assign function mentioned in the code,

How to replace tablename with a variable in a DB2 cursor in an anonymous block

I want to replace the table name with a variable that is derived from another cursor, but no matter what logic I tried to use I just could not get it right, I am seeing a couple of examples for Oracle and SQL Server but I failed to interpret that code into the DB2 SQL. Please help.
Declare
v_user VarCHAR(100);
v_schema VARCHAR(1000);
V_Studio_svr VARCHAR(1000);
v_db2_schema VARCHAR(1000);
v_oracle_string varchar(5000) ;
v_db2_string varchar(5000) := '(' ;
v_sys_columns varchar(2000);
v_sys_values varchar(2000);
V_UID iNTEGER := 41;
begin
-- Main Table to Get Table Name From
FOR v In ( Select app_id,Upper(alias) ALIAS
From FREEDOM.FORMS where app_id = '5e988af8-ef0f-48c7-9794-9bc4f1134c80' ) Loop
v_schema := 'S__'||V.app_ID||'_1';
v_schema := replace(v_schema,'-','_');
v_studio_svr := 'PTU'||SUBSTR(v.alias,2,LENGTH(v.alias));
v_db2_schema := 'TF'||SUBSTR(v.alias,2,LENGTH(v.alias));
-- This is where I want to use Table Name as Variable Coming From Cursor V
For P in
(Select * from studio_svr||'.'||v_studio_svr) loop
-- Table to get Data Type Mappings
For i in
(Select * From fREEDOM.DB2_DT_MAPPING
Where Table_Name = v.alias ) Loop
IF I.DB2_DATATYPE LIKE 'DECIMAL%' THEN
v_ORACLE_STRING := Nvl(v_ORACLE_STRING,'')||'CAST('||'INTEGER('||I.STUDIO_SVR_COLUMN||') AS DECIMAL(22,6)),';
ELSE
v_ORACLE_STRING := Nvl(v_ORACLE_STRING,'')||I.STUDIO_SVR_COLUMN||',';
END IF;
v_DB2_STRING := v_DB2_STRING||I.DB2_COLUMN||',';
End Loop;
v_DB2_STRING := SUBSTR(v_DB2_STRING,1,LENGTH(v_DB2_STRING)-1)||')';
execute immediate 'Insert Into ' || v_schema || '.' || v_db2_schema || ' '|| v_db2_string ||' SELECT '|| v_oracle_string ||' FROM Studio_svr.' || v_studio_svr || 'where S__recordid ='||p.s__recordid ;
v_db2_string := '(';
v_oracle_string := '';
v_uid := v_uid + 1;
commit;
End loop;
END lOOP;
END
Obviously, you need to use dynamic SQL for that cursor, like so:
Declare
v_user VarCHAR(100);
...
V_UID iNTEGER := 41;
--->
v_cursor_studio SYS_REFCURSOR;
begin
-- Main Table to Get Table Name From
FOR v In ( Select app_id,Upper(alias) ALIAS
From FREEDOM.FORMS where app_id = '5e988af8-ef0f-48c7-9794-9bc4f1134c80' ) Loop
v_schema := 'S__'||V.app_ID||'_1';
v_schema := replace(v_schema,'-','_');
v_studio_svr := 'PTU'||SUBSTR(v.alias,2,LENGTH(v.alias));
v_db2_schema := 'TF'||SUBSTR(v.alias,2,LENGTH(v.alias));
-- This is where I want to use Table Name as Variable Coming From Cursor V
--->
OPEN v_cursor_studio for 'Select * from ' || studio_svr||'.'||v_studio_svr;
For P in v_cursor_studio
...
The code is not tested, but I hope you get the idea.

bytea in postgres storing and retrieving bytes

I am trying to understand how to work with binary data in postgresql (v 8.3).
Let's say I have a following table
Table "public.message"
Column | Type | Modifiers
---------+---------+-----------
id | integer |
message | bytea |
I would like to store a packet in the message field in this format:
version (1 byte), identifier (1 byte), epoch (4 bytes)
I would like to pack this data into the message field. Lets say I have version=1, identifier=8 and epoch=123456. How would I pack this data into the message field? How would I convert my integer values to hex.. or octal?
I also need to get the message back and parse it. I was looking at the get_byte function, unless there is another way to parse the data out..
Thanks!
Here is some sample code showing how to do it with server-side Perl. Annoyingly, pack/unpack are considered untrusted operations by PG so this has to be created with plperlu by a superuser and then access granted with GRANT EXECUTE to non superusers.
On the other hand, this choice of language makes it easy to deal with more complex packed structures, which is a significant advantage over code that would be based on the SQL get_bytes()/set_bytes() functions. See Perl's pack() features.
1) first step: define a SQL composite type representing an non-packed record.
create type comp as (a smallint, b smallint, c int);
2) make a function to pack the record value into bytea:
create function pack_comp(comp) returns bytea
as $body$
my $arg=shift;
my $retval = pack("CCL", $arg->{a},$arg->{b},$arg->{c});
# encode bytea according to PG doc. For PG>=9.0, use encode_bytea() instead
$retval =~ s!(\\|[^ -~])!sprintf("\\%03o",ord($1))!ge; # from PG doc
return $retval;
$body$ language plperlu;
3) make a function to unpack bytea into the composite type:
create or replace function unpack_comp(bytea) returns comp
as $body$
my $arg=shift;
# decode bytea according to PG doc. For PG>=9.0, use decode_bytea() instead
$arg =~ s!\\(?:\\|(\d{3}))!$1 ? chr(oct($1)) : "\\"!ge;
my ($v,$i,$e)= unpack("CCL", $arg);
return {"a"=>$v, "b"=>$i, "c"=>$e};
$body$ language plperlu;
4) usage:
# select encode(pack_comp((254,14,1000000)::comp), 'hex');
encode
--------------
fe0e40420f00
# select unpack_comp(decode('fe0e40420f00','hex'));
unpack_comp
------------------
(254,14,1000000)
# select * from unpack_comp(decode('fe0e40420f00','hex'));
a | b | c
-----+----+---------
254 | 14 | 1000000
So I was able to figure out how to do it in plpg
Here's the code to pack
CREATE FUNCTION pack_numeric_bytes(i_values NUMERIC[], i_byte_sizes NUMERIC[], i_big_endian BOOLEAN)
RETURNS BYTEA
DECLARE
v_bytes BYTEA := NULL;
v_start INTEGER := 1;
v_byte BYTEA;
v_byte_size INTEGER;
v_value NUMERIC;
v_binary_value TEXT;
v_num NUMERIC;
i INTEGER;
x INTEGER;
v_sql TEXT;
BEGIN
IF array_upper(i_values, 1) != array_upper(i_byte_sizes, 1) THEN
RETURN v_bytes;
END IF;
FOR x IN array_lower(i_values, 1) .. array_upper(i_values, 1) LOOP
/* Given value and size at x position */
v_byte_size := i_byte_sizes[x]::INTEGER;
v_value := i_values[x];
/* Convert number to binary form */
v_sql := $$SELECT $$|| v_value ||$$::bit($$|| v_byte_size*8 ||$$);$$;
EXECUTE v_sql INTO v_binary_value;
IF i_big_endian IS TRUE THEN
/* Convert each byte at a time */
FOR i IN 1 .. v_byte_size LOOP
/* Extract byte from our binary value.
Big endian starts at 1 and then increments of 8 */
v_byte := substring(v_binary_value, v_start, 8);
/* Convert binary 8 bits to an integer */
v_sql := $$SELECT B$$||quote_literal(v_byte)||$$::int8$$;
EXECUTE v_sql INTO v_num;
/* Build bytea of bytes */
v_bytes := COALESCE(v_bytes, '') || set_byte(E' '::BYTEA, 0, v_num::INTEGER);
v_start := v_start + 8;
END LOOP;
ELSE
/* Small endian is extracted starting from last byte */
v_start := (v_byte_size * 8) + 1;
/* Convert each byte at a time */
FOR i IN 1 .. v_byte_size LOOP
v_start := v_start - 8;
v_byte := substring(v_binary_value, v_start, 8);
/* Convert binary 8 bits to an integer */
v_sql := $$SELECT B$$||quote_literal(v_byte)||$$::int8$$;
EXECUTE v_sql INTO v_num;
/* Build bytea of bytes */
v_bytes := COALESCE(v_bytes, '') || set_byte(E' '::BYTEA, 0, v_num::INTEGER);
END LOOP;
END IF; /* END endian check */
v_start := 1;
END LOOP;
RETURN v_bytes;
END;
And here's the code to unpack:
CREATE OR REPLACE FUNCTION public.unpack_numeric_bytes(i_bytes bytea, i_byte_sizes INTEGER[], i_big_endian BOOLEAN)
RETURNS NUMERIC[]
SECURITY DEFINER AS
DECLARE
v_bytes BYTEA;
v_start INTEGER := 1;
v_byte_index INTEGER := 0;
v_bit_shift INTEGER := 0;
v_length INTEGER;
v_size INTEGER;
v_sum_byte_sizes INTEGER;
v_vals NUMERIC[] := '{}';
v_val BIGINT := 0;
i INTEGER;
x INTEGER;
v_sql TEXT;
BEGIN
v_sql := $$SELECT $$|| array_to_string(i_byte_sizes, '+')||$$;$$;
EXECUTE v_sql INTO v_sum_byte_sizes;
IF length(i_bytes) != v_sum_byte_sizes::INTEGER THEN
RETURN v_vals;
END IF;
/* Loop through values of bytea (split by their sizes) */
FOR x IN array_lower(i_byte_sizes, 1) .. array_upper(i_byte_sizes, 1) LOOP
v_size := i_byte_sizes[x];
v_bytes := substring(i_bytes, v_start, v_size);
v_length := length(v_bytes);
IF i_big_endian IS TRUE THEN
v_byte_index := v_length - 1;
FOR i IN 1..v_length LOOP
v_val := v_val + (get_byte(v_bytes, v_byte_index) << v_bit_shift);
v_bit_shift := v_bit_shift + 8;
v_byte_index := v_byte_index - 1;
END LOOP;
ELSE
FOR i IN 1..v_length LOOP
v_val := v_val + (get_byte(v_bytes, v_byte_index) << v_bit_shift);
v_bit_shift := v_bit_shift + 8;
v_byte_index := v_byte_index + 1;
END LOOP;
END IF;
v_vals := array_append(v_vals, v_val::NUMERIC);
/* Calculate next value start index */
v_start := v_start + v_size;
v_byte_index := 0;
v_bit_shift := 0;
v_val := 0;
END LOOP;
RETURN v_vals;
END;
I hope this will help someone.

trigger didn't fired by using copy from command

I have populate a table using the copy from command which in turn will create record in summary table. While after the copy command successfully run, I can not see any record in the summary table. Anyone can shed some light on me? Pls find the table as well as the store procedure below:-
CREATE TABLE apache_log (
log_name character varying(255),
line integer,
client_address character varying(255),
rfc1413 character varying(32),
user_name character varying(32),
local_time timestamp with time zone,
log_date date,
log_hour smallint,
tenminute_bucket smallint,
fiveminute_bucket smallint,
method character varying(10),
url character varying(8192),
protocol character varying(10),
status_code smallint,
bytes_sent integer,
referer character varying(8192),
agent character varying(8192),
canon_name character varying(512)
);
CREATE INDEX apache_log_local_time ON apache_log USING btree (local_time);
CREATE INDEX apache_log_client_address ON apache_log USING btree (client_address);
CREATE INDEX apache_log_user_name ON apache_log USING btree (user_name);
CREATE INDEX apache_log_canon_name ON apache_log USING btree (canon_name);
CREATE INDEX apache_log_url ON apache_log USING btree (url);
CREATE INDEX apache_log_method ON apache_log USING btree (method);
CREATE INDEX apache_log_status_code ON apache_log USING btree (status_code);
CREATE UNIQUE INDEX apache_log_name_line ON apache_log (log_name, line);
CREATE TABLE tenminute_summary (
log_date date,
log_hour smallint,
bucket smallint,
hit integer,
bytes_sent bigint,
status_code smallint
);
CREATE INDEX tenminute_summary_log_date_log_hour_bucket ON tenminute_summary (log_date, log_hour, bucket);
CREATE UNIQUE INDEX tenminute_summary_log_date_log_hour_bucket_status_code ON tenminute_summary (log_date, log_hour, bucket, status_code);
CREATE TABLE fiveminute_summary (
log_date date,
log_hour smallint,
bucket smallint,
hit integer,
bytes_sent bigint,
status_code smallint
);
CREATE INDEX fiveminute_summary_log_date_log_hour_bucket ON fiveminute_summary (log_date, log_hour, bucket);
CREATE UNIQUE INDEX fiveminute_summary_log_date_log_hour_bucket_status_code ON fiveminute_summary (log_date, log_hour, bucket, status_code);
CREATE OR REPLACE FUNCTION update_history(history_log_date date, history_log_hour smallint, history_status_code smallint, history_fiveminute_bucket smallint, history_tenminute_bucket smallint, history_fiveminute_bytes_sent bigint, history_fiveminute_hit integer, history_fiveminute_bytes_sent bigint, history_fiveminute_hit integer) RETURNS INTEGER AS
$update_history$
BEGIN
IF ( history_fiveminute_bucket IS NOT NULL) THEN
<<fiveminute_update>>
LOOP
UPDATE fiveminute_summary
SET bytes_sent = bytes_sent + history_fiveminute_bytes_sent,
hit = hit + history_fiveminute_hit
WHERE log_date = history_log_date AND
log_hour = history_log_hour AND
bucket = history_fiveminute_bucket AND
status_code = history_status_code;
EXIT fiveminute_update WHEN found;
BEGIN
INSERT INTO fiveminute_summary (
log_date,
log_hour,
bucket,
status_code,
bytes_sent,
hit)
VALUES (
history_log_date,
history_log_hour,
history_fiveminute_bucket,
history_status_code,
history_fiveminute_bytes_sent,
history_fiveminute_hit);
EXIT fiveminute_update;
EXCEPTION
WHEN UNIQUE_VIOLATION THEN
-- do nothing
END;
END LOOP fiveminute_update;
END IF;
IF ( history_tenminute_bucket IS NOT NULL) THEN
<<tenminute_update>>
LOOP
UPDATE tenminute_summary
SET bytes_sent = bytes_sent + history_tenminute_bytes_sent,
hit = hit + history_tenminute_hit
WHERE log_date = history_log_date AND
log_hour = history_log_hour AND
bucket = history_tenminute_bucket AND
status_code = history_status_code;
EXIT tenminute_update WHEN found;
BEGIN
INSERT INTO tenminute_summary (
log_date,
log_hour,
bucket,
status_code,
bytes_sent,
hit)
VALUES (
history_log_date,
history_log_hour,
history_tenminute_bucket,
history_status_code,
history_tenminute_bytes_sent,
history_tenminute_hit);
EXIT tenminute_update;
EXCEPTION
WHEN UNIQUE_VIOLATION THEN
-- do nothing
END;
END LOOP tenminute_update;
END IF;
RETURN 0;
END;
$update_history$
LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION update_apache_log() RETURNS TRIGGER AS $update_apache_log$
DECLARE
history_log_date date := null;
history_log_hour smallint := null;
history_status_code smallint := null;
history_fiveminute_bucket smallint := null;
history_tenminute_bucket smallint := null;
history_fiveminute_bytes_sent bigint := null;
history_fiveminute_hit integer := null;
history_tenminute_bytes_sent bigint := null;
history_tenminute_hit integer := null;
future_log_date date := null;
future_log_hour smallint := null;
future_status_code smallint := null;
future_fiveminute_bucket smallint := null;
future_tenminute_bucket smallint := null;
future_fiveminute_bytes_sent bigint := null;
future_fiveminute_hit integer := null;
future_tenminute_bytes_sent bigint := null;
future_tenminute_hit integer := null;
dummy integer := 0;
BEGIN
IF (TG_OP = 'DELETE') THEN
history_log_date := OLD.log_date;
history_log_hour := OLD.log_hour;
history_fiveminute_bucket := OLD.fiveminute_bucket;
history_tenminute_bucket := OLD.tenminute_bucket;
history_status_code := OLD.status_code;
history_fiveminute_bytes_sent := 0 - OLD.bytes_sent;
history_fiveminute_hit := -1;
history_tenminute_bytes_sent := 0 - OLD.bytes_sent;
history_tenminute_hit := -1;
dummy:=update_history(history_log_date, history_log_hour, history_status_code, history_fiveminute_bucket, history_tenminute_bucket, history_fiveminute_bytes_sent, history_fiveminute_hit, history_fiveminute_bytes_sent, history_fiveminute_hit);
RETURN OLD;
ELSIF (TG_OP = 'INSERT') THEN
NEW.log_date := extract(date from NEW.log_date AT TIME ZONE 'GMT+8');
NEW.log_hour := extract(hour from NEW.log_date AT TIME ZONE 'GMT+8');
NEW.fiveminute_bucket := floor(extract(minute from NEW.log_date AT TIME ZONE 'GMT+8') / 5);
NEW.tenminute_bucket := floor(extract(minute from NEW.log_date AT TIME ZONE 'GMT+8') / 10);
future_log_date := NEW.log_date;
future_log_hour := NEW.log_hour;
future_status_code := NEW.status_code;
future_fiveminute_bucket := NEW.fiveminute_bucket;
future_tenminute_bucket := NEW.tenminute_bucket;
future_fiveminute_bytes_sent := NEW.bytes_sent;
future_fiveminute_hit := 1;
future_tenminute_bytes_sent := NEW.bytes_sent;
future_tenminute_hit := 1;
dummy:=update_history(future_log_date, future_log_hour, future_status_code, future_fiveminute_bucket, future_tenminute_bucket, future_fiveminute_bytes_sent, future_fiveminute_hit, future_fiveminute_bytes_sent, future_fiveminute_hit);
RETURN NEW;
ELSIF (TG_OP = 'UPDATE') THEN
IF (NEW.log_date <> OLD.log_date) THEN
NEW.date := extract(date from NEW.log_date AT TIME ZONE 'GMT+8');
NEW.hour := extract(hour from NEW.log_date AT TIME ZONE 'GMT+8');
NEW.fiveminute_bucket := floor(extract(minute from NEW.log_date AT TIME ZONE 'GMT+8') / 5);
NEW.tenminute_bucket := floor(extract(minute from NEW.log_date AT TIME ZONE 'GMT+8') / 10);
history_log_date := OLD.log_date;
history_log_hour := OLD.log_hour;
history_fiveminute_bucket := OLD.fiveminute_bucket;
history_tenminute_bucket := OLD.tenminute_bucket;
history_status_code := OLD.status_code;
IF (OLD.status_code = NEW.status_code) THEN
history_fiveminute_bytes_sent := 0 - OLD.bytes_sent;
history_fiveminute_hit := -1;
history_tenminute_bytes_sent := 0 - OLD.bytes_sent;
history_tenminute_hit := -1;
future_log_date := NEW.log_date;
future_log_hour := NEW.log_hour;
future_status_code := NEW.status_code;
future_fiveminute_bucket := NEW.fiveminute_bucket;
future_tenminute_bucket := NEW.tenminute_bucket;
future_fiveminute_bytes_sent := NEW.bytes_sent;
future_fiveminute_hit := 1;
future_tenminute_bytes_sent := NEW.bytes_sent;
future_tenminute_hit := 1;
dummy:=update_history(future_log_date, future_log_hour, future_status_code, future_fiveminute_bucket, future_tenminute_bucket, future_fiveminute_bytes_sent, future_fiveminute_hit, future_fiveminute_bytes_sent, future_fiveminute_hit);
ELSE
IF (OLD.fiveminute_bucket = NEW.fiveminute_bucket AND OLD.log_date = NEW.log_date AND OLD.log_hour = OLD.log_hour) THEN
history_fiveminute_bytes_sent := NEW.bytes_sent - OLD.bytes_sent;
history_tenminute_bytes_sent := NEW.bytes_sent - OLD.bytes_sent;
history_tenminute_hit := 0;
ELSE
history_fiveminute_bytes_sent := 0 - OLD.bytes_sent;
history_fiveminute_hit := -1;
future_log_date := NEW.log_date;
future_log_hour := NEW.log_hour;
future_status_code := NEW.status_code;
future_fiveminute_bucket := NEW.fiveminute_bucket;
future_fiveminute_bytes_sent := NEW.bytes_sent;
future_fiveminute_hit := 1;
IF (OLD.tenminute_bucket = NEW.tenminute_bucket) THEN
history_tenminute_bytes_sent := NEW.bytes_sent - OLD.bytes_sent;
history_tenminute_hit := 0;
ELSE
history_tenminute_bytes_sent := 0 - OLD.bytes_sent;
history_tenminute_hit := -1;
future_tenminute_bucket := NEW.tenminute_bucket;
future_tenminute_bytes_sent := NEW.bytes_sent;
future_tenminute_hit := 1;
END IF;
dummy:=update_history(future_log_date, future_log_hour, future_status_code, future_fiveminute_bucket, future_tenminute_bucket, future_fiveminute_bytes_sent, future_fiveminute_hit, future_fiveminute_bytes_sent, future_fiveminute_hit);
END IF;
END IF;
ELSE
history_log_date := OLD.log_date;
history_log_hour := OLD.log_hour;
history_status_code := OLD.status_code;
history_fiveminute_bucket := OLD.fiveminute_bucket;
history_tenminute_bucket := OLD.tenminute_bucket;
IF (OLD.status_code <> NEW.status_code) THEN
history_fiveminute_bytes_sent := 0 - OLD.bytes_sent;
history_fiveminute_hit := -1;
history_tenminute_bytes_sent := 0 - OLD.bytes_sent;
history_tenminute_hit := -1;
future_log_date := NEW.log_date;
future_log_hour := NEW.log_hour;
future_status_code := NEW.status_code;
future_fiveminute_bucket := NEW.fiveminute_bucket;
future_tenminute_bucket := NEW.tenminute_bucket;
future_fiveminute_bytes_sent := NEW.bytes_sent;
future_fiveminute_hit := 1;
future_tenminute_bytes_sent := NEW.bytes_sent;
future_tenminute_hit := 1;
dummy:=update_history(future_log_date, future_log_hour, future_status_code, future_fiveminute_bucket, future_tenminute_bucket, future_fiveminute_bytes_sent, future_fiveminute_hit, future_fiveminute_bytes_sent, future_fiveminute_hit);
ELSIF (OLD.bytes_sent <> NEW.bytes_sent) THEN
history_fiveminute_bytes_sent := NEW.bytes_sent - OLD.bytes_sent;
history_tenminute_bytes_sent := NEW.bytes_sent - OLD.bytes_sent;
END IF;
END IF;
dummy:=update_history(history_log_date, history_log_hour, history_status_code, history_fiveminute_bucket, history_tenminute_bucket, history_fiveminute_bytes_sent, history_fiveminute_hit, history_fiveminute_bytes_sent, history_fiveminute_hit);
RETURN NEW;
END IF;
RETURN NULL;
END;
$update_apache_log$ LANGUAGE plpgsql;
CREATE TRIGGER update_apache_log
BEFORE INSERT OR UPDATE OR DELETE ON apache_log
FOR EACH ROW EXECUTE PROCEDURE update_apache_log();
The function update_history uses two parameters with the same name twice:
ERROR: parameter name "history_fiveminute_bytes_sent" used more than once
SQL status:42P13
See:
CREATE OR REPLACE FUNCTION update_history(
history_log_date date,
history_log_hour smallint,
history_status_code smallint,
history_fiveminute_bucket smallint,
history_tenminute_bucket smallint,
history_fiveminute_bytes_sent bigint, <=== See errormessage
history_fiveminute_hit integer, <=== And this one as well
history_fiveminute_bytes_sent bigint, <===
history_fiveminute_hit integer <===
) RETURNS INTEGER AS
PostgreSQL 9.0 beta doesn't like this and it doesn't make sense. Older versions might not complain but might have the same problems with execution. Did you check the errorlogs?
And raise in both functions a notice, just to see if the trigger is activated.
RAISE NOTICE 'function X is doing something';
I found a lot of errors while Postgresql 8.4 didn't complaint at all. Anyway, I have given up the store procedure approach and decided to populate the table using sql directly since I am doing batch update onto the table in a very adhoc manner. Also populating the table using sql is much more efficient in terms of time spent on the whole process.