How to debug PostgreSQL trigger error "query has no destination for result data"? - postgresql

I have three table, like this:
data_buku table
+----+----------+----------------+
| kode_buku | * | * | stock |
+----+----------+----------------+
| 111 | * | * | 50 |
| 222 | * | * | 50 |
| 333 | * | * | 50 |
| 444 | * | * | 50 |
| 555 | * | * | 50 |
| 666 | * | * | 50 |
+----+-------+-----+----+--------+
data_pinjam table
+---------------+----------------------------+
| no_transaksi | kode_buku | * | jumlah |
+---------------+-------------+----+---------+
| 1 | 111 | * | 3 |
| 1 | 222 | * | 2 |
| 1 | 333 | * | 4 |
+---------------+-------------+----+---------+
data_kembali table
+---------------+-----+----+---------+
| no_transaksi | * | * | status |
+---------------+-----+----+---------+
| 1 | * | * | back |
+---------------+-----+----+---------+
From my Tables, I create a function and trigger on table data_kembali. While Insert query to table data_kembali, function will make action to sum jumlah on table data_pinjam where no_transaksi in table data_kembali same with data_pinjam, and will update stock in table data_buku. Each rows with same kode_buku values. Stock + Jumlah.
I have create function
CREATE OR REPLACE FUNCTION kembali()
RETURNS TRIGGER AS
$BODY$
DECLARE
CURRENT_STOK INT4;
r data_pinjam%ROWTYPE;
BEGIN
FOR r IN
SELECT *
FROM data_pinjam p
WHERE p.no_transaksi = new.no_transaksi
LOOP
select CURRENT_STOK stock from data_buku where kode_buku = r.kode_buku;
CURRENT_STOK = CURRENT_STOK + r.jumlah;
UPDATE data_buku SET STOCK = CURRENT_STOK WHERE kode_buku = r.kode_buku;
END LOOP;
UPDATE data_pinjam SET status = 'kembali' WHERE no_transaksi = new.no_transaksi;
update data_transaksi set status = 'kembali' where no_transaksi = new.no_transaksi;
RETURN NEW;
END;
$BODY$
LANGUAGE PLPGSQL VOLATILE
COST 100;
but while running, get output
ERROR: query has no destination for result data
SQL state: 42601
Hint: If you want to discard the results of a SELECT, use PERFORM instead.
Context: PL/pgSQL function kembali() line 13 at SQL statement
Can someone advise me regarding this trigger and function for update with loop?

The SELECT statement in the cursor loop requires a destination for the selected value:
SELECT stock
INTO CURRENT_STOK
FROM data_buku
WHERE kode_buku = r.kode_buku;
The variable CURRENT_STOK would also need to be declared (before the BEGIN).
But perhaps the intent of the cursor for loop is this?
UPDATE data_buku
SET STOCK = STOCK + r.jumlah
WHERE kode_buku = r.kode_buku;

Related

POSTGRESQL Subquery with a Order by Function does not return values

I have the following code that does not return values to the select because of my order by (Function)
Select sub.enrollmentseqnumemp ,sub.membercodedep,a.Subscriber_ID
From elan_staging.check_register_1 a
Left join (
Select enrollmentseqnumemp ,membercodedep
from elan.elig
ORDER BY public.idx(array['e','s','1','2','3','4','5'],membercodedep) Limit 1) sub
On sub.enrollmentseqnumemp=a.Subscriber_ID
| enrollmentseqnumemp | membercodedep | Subscriber_ID |
|:----------------------|:----------------|:----------------|
| [null] | [null] | "462852" |
| [null] | [null] | "462852" |
| [null] | [null] | "407742" |
If I run it without the Order By function, it works correctly
Select sub.enrollmentseqnumemp
,sub.membercodedep,a.Subscriber_ID
From elan_staging.check_register_1 a
Left join (
Select enrollmentseqnumemp ,membercodedep
from elan.elig
ORDER BY 1) sub
On sub.enrollmentseqnumemp=a.Subscriber_ID
Limit 1
| enrollmentseqnumemp | membercodedep | Subscriber_ID |
|:----------------------|:----------------|:----------------|
| 111111 | e | "462852" |
| 222222 | 3 | "462852" |
| 333333 | s | "407742" |
Code for the function from the Postgres snippets repository:
CREATE FUNCTION idx(anyarray varchar (1) ARRAY[4], anyelement varchar (1))
RETURNS int AS
$$
SELECT i FROM (
SELECT generate_series(array_lower($1,1),array_upper($1,1))
) g(i)
WHERE $1[i] = $2
LIMIT 1;
$$ LANGUAGE sql IMMUTABLE;
Is there a way to fix it so that it returns the values?
The first query as you have written it can return non-NULLs out of elig only for the one row with globally smallest value of public.idx(...). If you want values for the smallest public.idx(...) within each enrollmentseqnumemp, you could use distinct on, like :
Select sub.enrollmentseqnumemp ,sub.membercodedep,a.Subscriber_ID
From check_register_1 a
Left join (
Select distinct(enrollmentseqnumemp) enrollmentseqnumemp, membercodedep, public.idx(array['e','s','1','2','3','4','5'],membercodedep)
from elig
ORDER BY enrollmentseqnumemp, public.idx(array['e','s','1','2','3','4','5'],membercodedep)) sub
On sub.enrollmentseqnumemp=a.Subscriber_ID;

Lederboards in PostgreSQL and get 2 next and previous rows

We use Postgresql 14.1
I have a sample data that contains over 50 million records.
base table:
+------+----------+--------+--------+--------+
| id | item_id | battles| wins | damage |
+------+----------+--------+--------+--------+
| 1 | 255 | 35 | 52.08 | 1245.2 |
| 2 | 255 | 35 | 52.08 | 1245.2 |
| 3 | 255 | 35 | 52.08 | 1245.3 |
| 4 | 255 | 35 | 52.08 | 1245.3 |
| 5 | 255 | 35 | 52.09 | 1245.4 |
| 6 | 255 | 35 | 52.08 | 1245.3 |
| 7 | 255 | 35 | 52.08 | 1245.3 |
| 8 | 255 | 35 | 52.08 | 1245.7 |
| 1 | 460 | 18 | 47.35 | 1010.1 |
| 2 | 460 | 27 | 49.18 | 1518.9 |
| 3 | 460 | 16 | 50.78 | 1171.2 |
+------+----------+--------+--------+--------+
We need to get the target row number and 2 next and 2 previous rows as quickly as possible.
Indexed columns:
id
item_id
Sorting:
damage (DESC)
wins (DESC)
battles (ASC)
id (ASC)
At the example, we need to find the row number and +- 2 rows where id = 4 and item_id = 255. The result table should be:
+------+----------+--------+--------+--------+------+
| id | item_id | battles| wins | damage | rank |
+------+----------+--------+--------+--------+------+
| 5 | 255 | 35 | 52.09 | 1245.4 | 2 |
| 3 | 255 | 35 | 52.08 | 1245.3 | 3 |
| 4 | 255 | 35 | 52.08 | 1245.3 | 4 |
| 6 | 255 | 35 | 52.08 | 1245.3 | 5 |
| 7 | 255 | 35 | 52.08 | 1245.3 | 6 |
+------+----------+--------+--------+--------+------+
How can I do this with Row number windows function?
Is there is any way optimize in query to make it faster because other columns have no indexes?
CREATE OR REPLACE FUNCTION find_top(in_id integer, in_item_id integer) RETURNS TABLE (
r_id int,
r_item_id int,
r_battles int,
r_wins real,
r_damage real,
r_rank bigint,
r_eff real,
r_frags int
) AS $$
DECLARE
center_place bigint;
BEGIN
SELECT place INTO center_place FROM
(SELECT
id, item_id,
ROW_NUMBER() OVER (ORDER BY damage DESC, wins DESC, battles, id) AS place
FROM
public.my_table
WHERE
item_id = in_item_id
AND battles >= 20
) AS s
WHERE s.id = in_id;
RETURN QUERY SELECT
s.place, pt.id, pt.item_id, pt.battles, pt.wins, pt.damage
FROM
(
SELECT * FROM
(SELECT
ROW_NUMBER () OVER (ORDER BY damage DESC, wins DESC, battles, id) AS place,
id, item_id
FROM
public.my_table
WHERE
item_id = in_item_id
AND battles >= 20) x
WHERE x.place BETWEEN (center_place - 2) AND (center_place + 2)
) s
JOIN
public.my_table pt
ON pt.id = s.id AND pt.item_id = s.item_id;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION find_top(in_id integer, in_item_id integer) RETURNS TABLE (
r_id int,
r_item_id int,
r_battles int,
r_wins real,
r_damage real,
r_rank bigint,
r_eff real,
r_frags int
) AS $$
BEGIN
RETURN QUERY
SELECT c.*, B.ord -3 AS row_number
FROM
( SELECT array_agg(id) OVER w AS id
, array_agg(item_id) OVER w AS item_id
FROM public.my_table
WINDOW w AS (ORDER BY damage DESC, wins DESC, battles, id ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING)
) AS a
CROSS JOIN LATERAL unnest(a.id, a.item_id) WITH ORDINALITY AS b(id, item_id, ord)
INNER JOIN public.my_table AS c
ON c.id = b.id
AND c.item_id = b.item_id
WHERE a.item_id[3] = in_item_id
AND a.id[3] = in_id
ORDER BY b.ord ;
END ; $$ LANGUAGE plpgsql;
test result in dbfiddle

How to search terms containing slash ("/") with Sphinx / Manticore?

I'm trying to find matches of term "/book" and not just "book", but Manticore returns same result for both terms. Index type is rt and charset_table includes slash ("/"). How can I get only "/book" matches?
RT mode
mysql> drop table if exists t; create table t(f text) charset_table = 'non_cjk, /'; insert into t(f) values ('book'), ('/book'); select * from t where match('\\/book'); select * from t where match('book');
--------------
drop table if exists t
--------------
Query OK, 0 rows affected (0.00 sec)
--------------
create table t(f text) charset_table = 'non_cjk, /'
--------------
Query OK, 0 rows affected (0.00 sec)
--------------
insert into t(f) values ('book'), ('/book')
--------------
Query OK, 2 rows affected (0.01 sec)
--------------
select * from t where match('\\/book')
--------------
+---------------------+-------+
| id | f |
+---------------------+-------+
| 1514651075267788906 | /book |
+---------------------+-------+
1 row in set (0.00 sec)
--------------
select * from t where match('book')
--------------
+---------------------+------+
| id | f |
+---------------------+------+
| 1514651075267788905 | book |
+---------------------+------+
1 row in set (0.00 sec)
Plain mode
Plain index
source src {
type = csvpipe
csvpipe_command = echo "1,book" && echo "2,/book"
csvpipe_field = f
}
index idx {
path = /tmp/idx
source = src
charset_table = non_cjk, /
stored_fields = f
}
searchd {
listen = 127.0.0.1:9315:mysql41
log = sphinx_min.log
pid_file = searchd.pid
binlog_path =
}
mysql> select * from idx where match('\\/book');
+------+-------+
| id | f |
+------+-------+
| 2 | /book |
+------+-------+
1 row in set (0.00 sec)
mysql> select * from idx where match('book');
+------+------+
| id | f |
+------+------+
| 1 | book |
+------+------+
1 row in set (0.00 sec)
RT index
index t {
type = rt
path = /tmp/idx
rt_field = f
charset_table = non_cjk, /
stored_fields = f
}
searchd {
listen = 127.0.0.1:9315:mysql41
log = sphinx_min.log
pid_file = searchd.pid
binlog_path =
}
mysql> insert into t(f) values ('book'), ('/book'); select * from t where match('\\/book'); select * from t where match('book');
Query OK, 2 rows affected (0.00 sec)
+---------------------+-------+
| id | f |
+---------------------+-------+
| 1514659513871892482 | /book |
+---------------------+-------+
1 row in set (0.00 sec)
+---------------------+------+
| id | f |
+---------------------+------+
| 1514659513871892481 | book |
+---------------------+------+
1 row in set (0.00 sec)

Trigger to update a count number based on another table records

I have these two tables Review & Listing
| listing_id | review_id | comment |
|------------|----------------------------|--------------------|
| 5629709 | 123 | Beautiful |
| 4156372 | 231 | Wonderful |
| 4156372 | 432 | Very Good |
| 4156372 | 649 | Excellent |
| listing_id | number_of_reviews |
|------------|----------------------------|
| 5629709 | 1 |
| 4156372 | 2 |
Is there a way to create an trigger function that when the Review table has an update (insert or delete) then the number_of_reviews column in the Listing table updates also(+1 or -1)?
CREATE OR REPLACE FUNCTION function_number_of_reviews() RETURNS TRIGGER AS
$BODY$
BEGIN
if TG_OP='INSERT' then
Update public."Listing" set number_of_reviews = number_of_reviews + 1 where id = new.listing_id;
end if;
if TG_OP='DELETE' then
Update public."Listing" set number_of_reviews = number_of_reviews - 1 where id = old.listing_id;
end if;
RETURN new;
END;
$BODY$
language plpgsql;
CREATE TRIGGER trig_number_of_reviews
AFTER INSERT OR DELETE ON public."Review"
FOR EACH ROW
EXECUTE PROCEDURE function_number_of_reviews();
This is the wright way
Materializing values that can be calculated from other values bears a severe risk for inconsistencies. If possible avoid that.
In your case drop the number_of_reviews column in listing and create a view calculating the numbers for that column instead.
CREATE VIEW listing_with_number_of_reviews
AS
SELECT l.listing_id,
count(r.review_id) number_of_reviews
FROM listing l
LEFT JOIN review r
ON r.listing_id = l.listing_id
GROUP BY l.listing_id;

PostgreSQL 9.5 IF-THEN-ELSE inside FUNCTION not available?

My problem
While trying to CREATE a FUNCTION in my PostgreSQL database, version 9.5, I get the following error:
ERROR: syntax error at or near "IF"
LINE 3: IF strpos(trem_outcome, 'VALIDATED') = 0 THEN
Here's the FUNCTION I'm talking about:
CREATE OR REPLACE FUNCTION countValid(outcome text) RETURNS integer AS $$
BEGIN
IF strpos(outcome, 'VALIDATED') = 0 THEN
RETURN 1;
END IF;
RETURN 0;
END
$$ LANGUAGE SQL
Looks like the IF-THEN-ELSE control statements aren't available to me here, although I'm inside a FUNCTION right?
What am I missing?
Some context
I ultimately wanna sum(countValid()) of a huge set of data, like follows:
SELECT
table1.tbl1_pk,
count(table2.tbl2_outcome) as registered,
sum(countValid(table2.tbl2_outcome)) as validated
FROM table1 JOIN table2 ON table2.tbl2_tbl1_fk = table1.tbl1_pk
GROUP BY table1.tbl1_pk;
Where my tables are like:
+---------------------------------------+
| table1 |
+---------------+-----------------------+
| tbl1_pk (int) | tbl1_other_crap (w/e) |
+---------------+-----------------------+
| 1 | ... |
| 2 | ... |
| 3 | ... |
+---------------+-----------------------+
+------------------------------------------------+
| table2 |
+---------+-----------------------+--------------+
| tbl2_pk | tbl2_tbl1_fk | tbl2_outcome |
| (int) | (int -> tbl1.tbl1_pk) | (text) |
+---------+-----------------------+--------------+
| 1 | 1 | VALIDATED |
| 2 | 1 | FLUNKED |
| 3 | 3 | VALIDATED |
| 4 | 3 | VALIDATED |
| 5 | 1 | FLUNKED |
| 6 | 2 | VALIDATED |
| 7 | 3 | VALIDATED |
+---------+-----------------------+--------------+
I'd expect the following result set:
+---------------+------------------+-----------------+
| tbl1_pk (int) | registered (int) | validated (int) |
+---------------+------------------+-----------------+
| 1 | 3 | 1 |
| 2 | 1 | 1 |
| 3 | 3 | 3 |
+---------------+------------------+-----------------+
Minimal reproducible example
I can reproduce my issue on an even simpler function:
CREATE OR REPLACE FUNCTION countValid(outcome text) RETURNS integer AS $$
BEGIN
IF 1 = 1 THEN
RETURN 1;
END IF;
RETURN 0;
END
$$ LANGUAGE SQL
... which triggers:
ERROR: syntax error at or near "IF"
LINE 3: IF 1 = 1 THEN
I'm open to entirely different approaches, although I'd much rather have my work executed in a single query.
You do not need that function. Just use
count(table2.tbl2_outcome = 'VALIDATED' or null)