I've created this function to re-sequence the sequence number on a BOM table (bomitem).
CREATE OR REPLACE FUNCTION seqincr(integer)
RETURNS SETOF bomitem AS
$BODY$
DECLARE
pItemid ALIAS FOR $1;
_row bomitem%ROWTYPE;
seqint int;
_id int;
BEGIN
seqint=8;
FOR _row IN SELECT *
FROM bomitem
WHERE ((bomitem_parent_item_id=pItemid))
LOOP
RETURN NEXT _row;
_id = _row.bomitem_id;
seqint = seqint+2;
update bomitem set bomitem_seqnumber = seqint where bomitem_id=_id;
END LOOP;
RETURN;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100
ROWS 1000;
ALTER FUNCTION seqincr(integer)
OWNER TO admin;
The example works on an individual bomitem_parent_item_id like below:
SELECT * from seqincr(14917);
I would like to rewrite this function to loop through
SELECT distinct bomitem_parent_item_id FROM bomitem;
so that it resequences the entire BOM table.
What you are trying to do is much simpler with a CTE:
WITH x AS (
SELECT bomitem_parent_item_id
, row_number() OVER (ORDER BY bomitem_parent_item_id) AS rn
FROM bomitem
GROUP BY bomitem_parent_item_id
ORDER BY bomitem_parent_item_id
)
UPDATE bomitem b
SET bomitem_seqnumber = 8 + 2 * rn
FROM x
WHERE x.bomitem_parent_item_id = b.bomitem_id;
You need at least PostgreSQL 9.1 for data-modifying CTE.
Or use a subquery, works in earlier versions, too:
UPDATE bomitem b
SET bomitem_seqnumber = 8 + 2 * rn
FROM (
SELECT bomitem_parent_item_id
, row_number() OVER (ORDER BY bomitem_parent_item_id) AS rn
FROM bomitem
GROUP BY bomitem_parent_item_id
ORDER BY bomitem_parent_item_id
) x
WHERE x.bomitem_parent_item_id = b.bomitem_id;
But you need at least PostgreSQL 8.4 for the window function row_number().
Related
I have this select statement inside a trigger procedure:
SELECT 1 FROM some_table WHERE "user_id" = new."user_id"
AND created >= now()::date;
How can i store result in a variable and reuse it in IF statement like this:
IF NOT EXISTS (var_name) THEN ...;
procedure (for now i have select right in IF statement, but i want it separately)
CREATE OR REPLACE FUNCTION add_row() RETURNS TRIGGER AS $$
BEGIN
//need to check if row was created around today
IF NOT EXISTS (SELECT 1 FROM some_table WHERE "user_id" = new."user_id"
AND created >= now()::date) THEN
INSERT INTO another_table VALUES(1, 2, 3);
END IF;
END;
$$ LANGUAGE plpgsql;
To store the result of a query into a variable, you need to declare a variable. Then you can use select .. into .. to store the result. But I would use a boolean and an exists condition for this purpose.
CREATE OR REPLACE FUNCTION add_row()
RETURNS TRIGGER
AS $$
declare
l_row_exists boolean;
BEGIN
select exists (SELECT *
FROM some_table
WHERE user_id = new.user_id
AND created >= current_date)
into l_row_exists;
IF NOT l_row_exists THEN
INSERT INTO another_table (col1, col2, col3)
VALUES(1, 2, 3);
END IF;
END;
$$ LANGUAGE plpgsql;
However, you don't really need an IF statement to begin with. You can simplify this to a single INSERT statement:
INSERT INTO another_table (col1, col2, col3)
SELECT 1,2,3
WHERE NOT EXISTS (SELECT *
FROM some_table
WHERE user_id = new.user_id
AND created >= current_date);
I am trying to do an update on a specific record every 1000 rows using Postgres. I am looking for a better way to do that. My function is described below:
CREATE OR REPLACE FUNCTION update_row()
RETURNS void AS
$BODY$
declare
myUID integer;
nRow integer;
maxUid integer;
BEGIN
nRow:=1000;
select max(uid_atm_inp) from tab into maxUid where field1 = '1240200';
loop
if (nRow > 1000 and nRow < maxUid) then
select uid from tab into myUID where field1 = '1240200' and uid >= nRow limit 1;
update tab
set field = 'xxx'
where field1 = '1240200' and uid = myUID;
nRow:=nRow+1000;
end if;
end loop;
END; $BODY$
LANGUAGE plpgsql VOLATILE
How can I improve this procedure? I think there is something wrong. The loop does not end and takes too much time.
To perform this task in SQL, you could use the row_number window function and update only those rows where the number is divisible by 1000.
Your loop doesn't finish because there is no EXIT or RETURN in it.
I doubt you could ever rival the performance of a standard SQL update with a procedural loop. Instead of doing it a row at a time, just do it all as a single statement:
with t2 as (
select
uid, row_number() over (order by 1) as rn
from tab
where field1 = '1240200'
)
update tab t1
set field = 'xxx'
from t2
where
t1.uid = t2.uid and
mod (t2.rn, 1000) = 0
Per my comment, I am presupposing what you mean by "every 1000th row," as without some designation of how to determine what tuple is what row number. That is easily edited by changing the "order by" criteria.
Adding a second where clause on the update (t1.field1 = '1240200') can't hurt but might not be necessary if these are nested loop.
This might be notionally similar to what Laurenz has in mind.
I solved this way:
declare
myUID integer;
nRow integer;
rowNum integer;
checkrow integer;
myString varchar(272);
cur_check_row cursor for select uid , row_number() over (order by 1) as rn, substr(fieldxx,1,244)
from table where field1 = '1240200' and uid >= 1000 ORDER BY uid;
BEGIN
open cur_check_row;
loop
fetch cur_check_row into myUID, rowNum, myString;
EXIT WHEN NOT FOUND;
select mod(rowNum, 1000) into checkrow;
if checkrow = 0 then
update table
set fieldxx= myString||'O'
where uid in (myUID);
end if;
end loop;
close cur_check_row;
I have a table Answer and Many to Many table Link (Answer n-n Answer)
Link have 2 column : from_id and to_id reference to answer_id.
I want get all descendant of answer by answer_id ( from_id in Link ).
I have written function as below :
CREATE OR REPLACE FUNCTION getAllChild(_answer_id BIGINT)
RETURNS SETOF BIGINT AS $$
DECLARE r link;
BEGIN
FOR r IN
SELECT * FROM link
WHERE from_id = _answer_id
LOOP
RETURN NEXT r.to_id;
RETURN QUERY SELECT * FROM getAllChild(r.to_id);
END LOOP;
RETURN;
END;
$$ LANGUAGE plpgsql STRICT;
SELECT * FROM getAllChild(1);
The result is fine if to_id not duplicate with from_id that already got otherwise I will get recursive infinity.
My question is how I can make loop skip the existed to_id to call getAllChild() in RETURN QUERY
I'd suggest you do this with a recursive CTE, you could use the same approach in a function though.
You can use an array to keep a track of all the from_id's you've dealt with, and then in the next run through you ignore any records for from_id's already in the results. In the code below I'm using the path array to track all the from_id's already seen.
with recursive t as
(
select l.from_id,l.to_id, ARRAY[l.from_id] as path, 1 as depth
from link l where from_id = 2
union all
select l.from_id,l.to_id, array_append(t.path,l.from_id), t.depth+1
from link l
inner join t on l.from_id = t.to_id
where not (l.from_id = ANY (t.path)) -- ignore records already processed
)
select * from t;
Fiddle at: http://sqlfiddle.com/#!15/024e80/1
Updated: As a function
CREATE OR REPLACE FUNCTION getAllChild(_answer_id BIGINT)
RETURNS SETOF BIGINT AS $$
BEGIN
return query
with recursive t as
(
select l.from_id,l.to_id, ARRAY[l.from_id] as path, 1 as depth from link l where from_id = _answer_id
union all
select l.from_id,l.to_id, array_append(t.path,l.from_id), t.depth+1 from link l
inner join t on l.from_id = t.to_id
where not (l.from_id = ANY (t.path))
)
select to_id from t;
END;
$$ LANGUAGE plpgsql STRICT;
Arrays documentation: https://www.postgresql.org/docs/current/static/arrays.html
CTEs: https://www.postgresql.org/docs/current/static/queries-with.html
I am trying to execute dynamic query using PostgreSQL. I have a function with three parameters and need to suffix that parameters with some variables(to make view name) and need to retrieve rows from that variables(view) and return the result.
Example
create or replace function testing(abc varchar,def varchar,ghi varchar)
returns setof record as
$BODY$
Declare
temptable1 varchar :='temp1_';
temptable2 varchar :='temp2_';
viewname varchar :='view_';
Body
temptable1 := temptable1||abc;
temptable2 := temptable2||def;
viewname := viewname||ghi;
execute 'Drop table if exists'||temptable1;
execute 'Drop table if exists'||temptable2;
WITH cm
AS
(
SELECT "ssno","rlno",
DENSE_RANK() OVER(Partition by "ssno" Order By "rlno" )FoundIn
From viewname;
)
SELECT DISTINCT * INTO temptable1
FROM cm
WHERE FoundIn > 1;
SELECT DISTINCT cr."ssno", cdr."rlno"
INTO temptable2
FROM temptable1 l1
INNER JOIN viewname cr on l1."rlno" = cr."rlno"
ORDER BY "rlno";
/* Need to result should be display for below query */
SELECT DISTINCT cr.ssno AS Nos, cr.rlno, FoundIn,cr.Name, cr.Address,
from temptable1 l1
inner join viewname cr on l1.rlno = cr.rlno
order by "rlno"
end;
$BODY$
Language plpgsql;
I have a stored procedure that cannot be modified, I'm going to stress this before anyone suggests I re-write the stored procedure or add the query from inside the stored procedure into a function.
The procedure lives on another database that we have very limited access to; so what I want to do is somehow wrap the stored procedure in a query, function or stored procedure that will allow me to select the top N rows from the returned data.
Ideally I would be able to call something like...
DECLARE #ForeName varchar(50)
DECLARE #Surname varchar(50)
DECLARE #DOB datetime
DECLARE #Sex varchar(1)
SET #Surname = 'Smith'
SELECT TOP 10 (
EXECUTE #RC = [Some_Other_Database].[dbo].[sp_search_demographics]
,#ForeName
,#Surname
,#DOB
,#Sex
)
GO
edit: (I should also note that the stored procedure returns a parameter containing the row count as well as the rows)
edit2: I should also note that I'm using MS SQL Server 2008 R2
I'm aware that this is in no way correct, is there any way to do something like this? at the moment for vague queries we are getting thousands of rows returned; which is slowing the server considerably.
I have done some Googling and stack-overflowing for a solution but unfortunately all the advice I could find involved modifying the stored procedure.
Look up EXEC SP_EXECUTESQL(#SQL)
However the problem will be that the called sp will still return all the rows, so you may not get the improvement in performance you are looking for.
You can also set the number of rows returned by a query - but depends on your access level
http://blog.sqlauthority.com/2007/04/30/sql-server-set-rowcount-retrieving-or-limiting-the-first-n-records-from-a-sql-query/
Hope this helps
Declare #i Numeric(18,2)
Declare #strSQL nvarchar(1000)
select #i = Round(COUNT(1)/10,2) from tb_Item
print(#i)
Declare #j int = 0
Declare #rem numeric(18,2)
select #rem = COUNT(1) - ((COUNT(1)/10) * 10) from tb_Item
while #i > 0
Begin
set #j = (#j + 1);
if #j = 1
Begin
WITH OrderedOrders AS
(
select
ROW_NUMBER() over(order by ItemID) AS RowNumber
,ItemName
from tb_Item
)
SELECT ItemName, RowNumber
FROM OrderedOrders
WHERE RowNumber BETWEEN (#j*10)-10 AND #j*10;
End
Else
Begin
WITH OrderedOrders AS
(
select
ROW_NUMBER() over(order by ItemID) AS RowNumber
,ItemName
from tb_Item
)
SELECT ItemName, RowNumber
FROM OrderedOrders
WHERE RowNumber BETWEEN ((#j*10)-10) + 1 AND #j*10;
End
set #i = #i - 1;
end;
WITH OrderedOrders AS
(
select
ROW_NUMBER() over(order by ItemID) AS RowNumber
,ItemName
from tb_Item
)
SELECT ItemName, RowNumber
FROM OrderedOrders
WHERE RowNumber BETWEEN (#j*10)+1 and (#j*10) + #rem ;