Firebird 2.5 - add unique ID to each row in a table from stored procedure - firebird

I have a table which doesn't have an unique ID. I want to make a stored procedure which is adding to each row the number of the row as ID, but I don't know how to get the current row number. This is what I have done until now
CREATE OR ALTER PROCEDURE INSERTID_MYTABLE
returns (
cnt integer)
as
declare variable rnaml_count integer;
begin
/* Procedure Text */
Cnt = 1;
for select count(*) from MYTABLE r into:rnaml_count do
while (cnt <= rnaml_count) do
begin
update MYTABLE set id=:cnt
where :cnt = /*how should I get the rownumber here from select??*/
Cnt = Cnt + 1;
suspend;
end
end

I think better way will be:
Add new nullable column (let's call it ID).
Create a generator/sequence (let's call it GEN_ID).
Create a before update/insert trigger that fetches new value from sequence whenever the NEW.ID is null. Example.
Do update table set ID = ID. (This will populate the keys.)
Change the ID column to not null.
A bonus. The trigger can be left there, because it will generate the value in new inserted rows.

Related

simple stored procedure in Postgres

I am copying data (importing)from table tmp_header into as_solution2 table, first IdNumber and Date needs to be checked on destiny table, to not copy repeated values. if date and idNumber are found in destiny table, i don't copy the row, if not found ,row is copied into table as_solution2.
Source table has 800.000 records and destiny table already contains 200.000 records.
caveat: the id_solution pk in "as_solution2" table is not serial, so I created a sequence and start from the last id.
v_max_cod_solicitud := (select max(id_solution)+1 from municipalidad.as_solution2);
CREATE SEQUENCE increment START v_max_cod_solicitud;
this provokes an errorerror
tmp_header (id, cod_cause, idNumber , date_sol(2012-05-12), glosa_desc)
as_solution2(id_solution, cod_cause, idNumber, date_sol, desc )
CREATE OR REPLACE FUNCTION municipalidad.as_importar()
RETURNS integer AS
$$
DECLARE
v_max_cod_solicitud numeric;
id_solution numeric;
begin
v_max_cod_solicitud := (select max(id_solution)+1 from municipalidad.as_solution2);
CREATE SEQUENCE increment START v_max_cod_solicitud;
INSERT INTO municipalidad.as_solution2(
id_solution,
cod_cause,
idNumber,
date_sol,
desc,
)
SELECT
(SELECT nextval('increment')), <-- when saving i need to start from the last sequence number
cod_causingreso,
idNumber,
date_sol,
glosa_atenc,
FROM municipalidad.tmp_header as tmp_e
WHERE(SELECT count(*)
FROM municipalidad.as_solution2 as s2
WHERE s2.idNumber = tmp_e.idNumber AND s2.date_sol::date = tmp_e.date_sol::date)=0;
drop sequence increment;
return 1;
end
$$
LANGUAGE 'plpgsql'
thanks in advance
You can brute-force the execution of the sequence with the start parameter as follows:
execute (format ('CREATE SEQUENCE incremento start %s', v_max_cod_solicitud));
Unrelated, but I think you will gain efficiencies by changing your insert to use an anti-join instead of the Where select count (*) = 0:
INSERT INTO as_solution2(
id_solution,
cod_cause,
idNumber,
date_sol,
description
)
SELECT
nextval('incremento'), -- when saving i need to start from the last sequence number
cod_causingreso,
idNumber,
date_sol,
glosa_atenc
FROM tmp_header as tmp_e
WHERE not exists (
select null
from as_solution2 s2
where
s2.idNumber = tmp_e.idNumber AND
s2.date_sol::date = tmp_e.date_sol::date
)
This will scale very nicely as your dataset increases in size.
Even though it's not listed as a reserved key word in https://www.postgresql.org/docs/9.5/sql-keywords-appendix.html, the increment in your create sequence statement might not be allowed here:
CREATE SEQUENCE increment START v_max_cod_solicitud;
As the parser expects this:
ALTER SEQUENCE name [ INCREMENT [ BY ] increment ]
It probably thinks you forgot the name

Postgresql trigger syntax error at or near "NEW"

Here is what i'm trying to do:
ALTER TABLE publishroomcontacts ADD COLUMN IF NOT EXISTS contactorder integer NOT NULL default 1;
CREATE OR REPLACE FUNCTION publishroomcontactorder() RETURNS trigger AS $publishroomcontacts$
BEGIN
IF (TG_OP = 'INSERT') THEN
with newcontactorder as (SELECT contactorder FROM publishroomcontacts WHERE publishroomid = NEW.publishroomid ORDER BY contactorder limit 1)
NEW.contactorder = (newcontactorder + 1);
END IF;
RETURN NEW;
END;
$publishroomcontacts$ LANGUAGE plpgsql;
CREATE TRIGGER publishroomcontacts BEFORE INSERT OR UPDATE ON publishroomcontacts
FOR EACH ROW EXECUTE PROCEDURE publishroomcontactorder();
I've been looking into a lot of examples and they all look like this. Most of them a couple of years old tho. Has this changed or why doesn't NEW work? And do i have to do the insert in the function or does postgres do the insert with the returned NEW object after the function is done?
I'm not sure what you're trying to do, but your syntax is wrong here:
with newcontactorder as (SELECT contactorder FROM publishroomcontacts WHERE publishroomid = NEW.publishroomid ORDER BY contactorder limit 1)
NEW.contactorder = (newcontactorder + 1);
Do not use CTE query if there is no select that comes afterwards. If you want to increment contactorder column for particular publishroomid whenever new one is being added and this is your sequence (auto increment) mechanism then you should replace it with:
NEW.contactorder = COALESCE((
SELECT max(contactorder)
FROM publishroomcontacts
WHERE publishroomid = NEW.publishroomid
), 1);
Note the changes:
there's no CTE, just variable assignment with SELECT query
use MAX() aggregate function instead of ORDER BY + LIMIT
wrapped up with COALESCE(x,1) function to properly insert first contacts for rooms, it will return 1 if your query does return NULL
Your trigger should look like this
CREATE OR REPLACE FUNCTION publishroomcontactorder() RETURNS trigger AS $publishroomcontacts$
BEGIN
IF (TG_OP = 'INSERT') THEN
NEW.contactorder = COALESCE((
SELECT max(contactorder) + 1
FROM publishroomcontacts
WHERE publishroomid = NEW.publishroomid
), 1);
END IF;
RETURN NEW;
END;
$publishroomcontacts$ LANGUAGE plpgsql;
Postgres will insert the row itself, you don't have to do anything, because RETURN NEW does that.
This solution does not take care of concurrent inserts which makes it unsafe for multi-user environment! You can work around this by performing an UPSERT !
WITH is not an assignment in PL/pgSQL.
PL/pgSQL interprets the line as SQL statement, but that is bad SQL because the WITH clause is followed by NEW.contactorder rather than SELECT or another CTE.
Hence the error; it has nothing to do with NEW as such.
You probably want something like
SELECT contactorder INTO newcontactorder
FROM publishroomcontacts
WHERE publishroomid = NEW.publishroomid
ORDER BY contactorder DESC -- you want the biggest one, right?
LIMIT 1;
You'll have to declare newcontactorder in the DECLARE section.
Warning: If there are two concurrent inserts, they might end up with the same newcontactorder.

Fill Firebird column with incremental data using Flame Robin

I have a huge Firebird database with a table that counts 41 millions of rows. Recently I have added a new float column and would like to fill it with incremental data. Each next value should be a previous incremented by RAND(). The very first value is also RAND().
How to do this?
The query
SELECT ID FROM MY_TABLE WHERE MY_COLUMN IS NULL ROWS 1;
takes up to 15 seconds so I wouldn't count on this query executed in a loop.
The table has an indexed ID column which is a part of composite primary key.
something like
update MyTable set MyColumn = Gen_ID( TempGen,
round( rand() * 100000) ) / 100000.0
Create a temporary Generator - https://www.firebirdsql.org/manual/generatorguide.html
use the integer generator as your float value scaled by some coefficient, like 100 000 would stand for 1.0 and 10 000 for 0.1, etc
use the GEN_ID function to forward a generator for a specified number of integer units
drop the generator
alternatively use Stored Procedure or EXECUTE BLOCK
https://www.firebirdsql.org/refdocs/langrefupd20-execblock.html
http://firebirdsql.su/doku.php?id=execute_block
something like
execute block
as
declare f double precision = 0;
declare i int;
begin
for select ID FROM MY_TABLE WHERE MY_COLUMN IS NULL order by id into :I
do begin
f = f + rand();
update MY_TABLE SET MY_COLUMN = :f where ID = :i;
end;
end
Or you may try using cursors, but I did not try so I do not know for sure how it would work.
https://www.firebirdsql.org/refdocs/langrefupd25-psql-forselect.html
execute block
as
declare f double precision = 0;
begin
for select ID FROM MY_TABLE WHERE MY_COLUMN IS NULL order by id
as cursor C do begin
f = f + rand();
update MY_TABLE SET MY_COLUMN = :f where current of C;
end;
end

not sure how to populate a table based on the contents of another table

I've tried to figure my way around this but I'm relatively new to tsql.
These are my two tables:
This is my dbo.UsersAccountLink table:
This is my Company.Token tables:
Right now the UsersAccountLink.CorporationId is blank and I need to populate it based on what is in the Company.Token table.
So, I need to loop through each record in the Company.Token table and get the Company.Token.TokenId value and then query the Company.Token table with the TokenId, then lastly, I need to update the record on the dbo.UsersAccountLink table with the CorporationId.
Ultimately I want to update the dbo.UsersAccountLink.CorporationId with the value from Company.Token.CorporationId.
I hope that makes sense.
Well, here is what I have so far... It's not much but I'm struggling.
USE SuburbanPortal
go
-- Get the number of rows in the looping table
DECLARE #RowCount INT
SET #RowCount = (SELECT COUNT(*) FROM dbo.UsersAccountLink)
-- Declare an iterator
DECLARE #I INT
-- Initialize the iterator
SET #I = 1
-- Loop through the rows of a table #myTable
WHILE (#I <= #RowCount)
BEGIN
-- Declare variables to hold the data which we get after looping each record
DECLARE #CorpId UNIQUEIDENTIFIER, #TokenId UNIQUEIDENTIFIER
-- Get the data from table and set to variables
SET #TokenId = (SELECT [TokenId] FROM [SuburbanPortal].[dbo].[UsersAccountLink])
SET #CorpId = (SELECT [CorporationId] FROM [SuburbanPortal].[Company].[Token] WHERE #TokenId = ???)
-- Increment the iterator
SET #I = #I + 1
END
Welcome to SQL Server. Your code indicates that you are coming from a programming background with this pattern called "row-by-agonizing-row" (ROAR). The first order of business is to replace the "loop" thinking with "join". Instead of looping through a table then search for match in the other, use join:
UPDATE UAL
SET UAL.CorporationId = T.CorporationId
FROM dbo.UserAccountLink UAL
INNER JOIN Company.Token T ON UAL.TokenId = T.TokenId

postgres count from table efficient way

In my application we are using postgresql,now it has one million records in summary table.
When I run the following query it takes 80,927 ms
SELECT COUNT(*) AS count
FROM summary_views
GROUP BY question_id,category_type_id
Is there any efficient way to do this?
COUNT(*) in PostgreSQL tends to be slow. It's a feature of MVCC. One of the workarounds of the problem is a row counting trigger with a helper table:
create table table_count(
table_count_id text primary key,
rows int default 0
);
CREATE OR REPLACE FUNCTION table_count_update()
RETURNS trigger AS
$BODY$
begin
if tg_op = 'INSERT' then
update table_count set rows = rows + 1
where table_count_id = TG_TABLE_NAME;
elsif tg_op = 'DELETE' then
update table_count set rows = rows - 1
where table_count_id = TG_TABLE_NAME;
end if;
return null;
end;
$BODY$
LANGUAGE 'plpgsql' VOLATILE;
Next step is to add proper trigger declaration for each table you'd like to use it with. For example for table tab_name:
begin;
insert into table_count values
('tab_name',(select count(*) from tab_name));
create trigger tab_name_table_count after insert or delete
on tab_name for each row execute procedure table_count_update();
commit;
It is important to run in a transaction block to keep actual count and helper table in sync in case of delete or insert between initial count and trigger creation. Transaction guarantees this. From now on to get current count instantly, just invoke:
select rows from table_count where table_count_id = 'tab_name';
Edit: In case of your group by clause, you'll need more sophisticated trigger function and count table.