Error to postgresql - sql

Pls Help!
count_rate :=
(
SELECT COUNT(trate.rid) AS count_rate
FROM tlot LEFT JOIN trate ON trate.ridlot = tlot.lid
GROUP BY tlot.lid
);
FULL:
CREATE FUNCTION editstatuswait()
RETURNS void
LANGUAGE plpgsql
AS $$
DECLARE count_rate INTEGER;
BEGIN
count_rate := (SELECT COUNT(trate.rid) AS count_rate FROM tlot LEFT JOIN trate ON trate.ridlot = tlot.lid GROUP BY tlot.lid);
IF (count_rate != 0) THEN
UPDATE tlot SET lstatus = 3
WHERE tlot.lexpirationdate < NOW()
AND tlot.lexpirationdate > NOW()-INTERVAL '24 hours' AND tlot.lstatus = 2;
ELSE
UPDATE tlot SET lstatus = 0
WHERE tlot.lexpirationdate < NOW()
AND tlot.lexpirationdate > NOW()-INTERVAL '24 hours' AND tlot.lstatus = 2;
END IF;
END;
$$;
ERROR: [21000] ERROR: more than one row returned by a subquery used as an expression Где: SQL statement
SELECT (SELECT COUNT(trate.rid) AS count_rate FROM tlot LEFT JOIN trate ON trate.ridlot = tlot.lid GROUP BY tlot.lid
I can not understand how to get rid of this error...

The solution will depend on what you are trying to achieve.
A variable can only contain a single value, so your attempt to store the result of a subselect that returns more than one row in count_rate is bound to fail.
You will have to come up with a subselect that returns at most one row (if it returns no row, NULL will be assigned to the variable).
If you are only interested in the first row (unlikely, since there is no ORDER BY), you could append LIMIT 1 to the query.
If you want only the count for a certain tlot.lid, you should use WHERE tlot.lid = ... instead of a GROUP BY.
If you want to process multiple results, you would use a construction like:
FOR count_rate IN SELECT ... LOOP
...
END LOOP;

Remove the GROUP BY:
count_rate := (SELECT COUNT(trate.rid) AS count_rate FROM tlot LEFT JOIN trate ON trate.ridlot = tlot.lid);
Of course, this may not do what you intend. It will at least fix the error.

Related

Oracle Trigger PLS-00103. Query with multiple rows in temporal variable

I'm trying to write a trigger that raises error when someone attempts to rate a product which has not been bought. I have come up with a query to get the purchase history of a client :
SELECT nref
FROM CartClient a
INNER JOIN PaidCart b
ON a.idpurchase = b.idpurchase
INNER JOIN CartDetails c
ON b.idpurchase = c.idpurchase
WHERE a.Id = '12345672X'
which works fine.
So the next trigger should check if the product of a new rating (:new.NRef) has not been bought, namely is not part of the result of the last query (NOT IN).
CREATE OR REPLACE TRIGGER cant_rate
BEFORE INSERT ON Rating
FOR EACH ROW
BEGIN
IF (:new.NRef NOT IN (SELECT nref FROM CartClient a
INNER JOIN PaidCart b
ON a.idpurchase = b.idpurchase
INNER JOIN CartDetails c
ON b.idpurchase = c.idpurchase
WHERE a.Id =:new.Id)) THEN
RAISE_APPLICATION_ERROR(-20603,'Cant rate a not bought product');
END IF;
END;
I get error:
"PLS-00103:Encountered the symbol “INNER” when expecting one of".
I have tried to store the result of the query in a temporal variable using SELECT INTO. But, it's a multiple row result. What could I do?
How about such an approach?
CREATE OR REPLACE TRIGGER cant_rate
BEFORE INSERT ON Rating
FOR EACH ROW
DECLARE
l_exists NUMBER(1) := 0;
BEGIN
SELECT MAX(1)
INTO l_exists
FROM dual
WHERE EXISTS (SELECT nref FROM CartClient A
INNER JOIN PaidCart b
ON A.idpurchase = b.idpurchase
INNER JOIN CartDetails C
ON b.idpurchase = C.idpurchase
WHERE A.ID = :NEW.ID
AND a.nref = :NEW.nref --> is it "a.nref"?
);
IF l_exists = 0 THEN
RAISE_APPLICATION_ERROR(-20603,'Cant rate a not bought product');
END IF;
END;
Note remark "is it a.nref?" - you never said which table owns that nref column so I presumed it is cartclient; modify it, if necessary.
As of your attempt: if you executed it in SQL*Plus or SQL Developer, you'd see a message regarding subquery in IF; something like this:
LINE/COL ERROR
--------- -------------------------------------------------------------
2/3 PL/SQL: Statement ignored
2/21 PLS-00405: subquery not allowed in this context
So, no - you can't do it that way.
You probably don't get the actual error: PLS-00405 subquery not allowed in this context.
Simple stupid test:
BEGIN
IF 1 NOT IN ( SELECT 2 FROM DUAL )
THEN
NULL;
END IF;
END;
/
You can adjust your trigger in this way or similar:
CREATE OR REPLACE TRIGGER cant_rate
BEFORE INSERT ON Rating
FOR EACH ROW
DECLARE
l_test PLS_INTEGER;
BEGIN
SELECT CASE
WHEN :new.NRef NOT IN ( SELECT nref
FROM CartClient a
INNER JOIN PaidCart b
ON a.idpurchase = b.idpurchase
INNER JOIN CartDetails c
ON b.idpurchase = c.idpurchase
WHERE a.Id = :new.Id
)
THEN 1
ELSE 0
END
INTO l_test;
FROM DUAL;
IF l_test = 1
THEN
...
END IF;
END;
/

Building dymanic query with many laterail joins

I am building a dynamic query based on user-submitted conditions on user-specified fields.
I have a two tables
Visitors(id, name, email...)
and a
Trackings(id, visitor_id, field, string_value, integer_value, boolean_value, date_value)
The conditions comes in the form of an array of SQL fragments built earlier. Default filters for attributes that are hardcoded on the visitors table, and custom filters, for the values that are user-submitted and stored in an EAV schema (trackings)
Example:
Default:
{"name ILIKE 'Jack'", "(last_seen < (current_date - (7 || ' days')::interval))"}
Custom:
{"field = 'number_of_orders' > 10", "is_pro_user = true"}
A visitor can have many trackings, each one recording some custom, user-submitted data field for that visitor. But each visitor also has some default data that lies on the table itself, such as email, name or last_seen etc.
Now, users should be to ask queries such as:
Give me all the visitors for which no custom field named number_of_orders has been recored (is unknown)
Give me all visitors with the default attribute name set to Jack and for who the custom attribute total_purchase_value is greater than 1000
My attempt at solving it was using a stored procedure that dynamically concatenated a series of conditions using AND (for default data on the visitor table), and OR statements (for the custom data in the trackings table) inside a WHERE-caluse
CREATE OR REPLACE FUNCTION find_matching_visitors(app_id text, default_filters text[], custom_filters text[])
RETURNS TABLE (
id varchar
) AS
$body$
DECLARE
default_filterstring text;
custom_filterstring text;
default_filter_length integer;
custom_filter_length integer;
sql VARCHAR;
BEGIN
default_filter_length := COALESCE(array_length(default_filters, 1), 0);
custom_filter_length := COALESCE(array_length(custom_filters, 1), 0);
default_filterstring := array_to_string(default_filters, ' AND ');
custom_filterstring := array_to_string(custom_filters, ' OR ');
IF custom_filterstring = '' or custom_filterstring is null THEN
custom_filterstring := '1=1';
END IF;
IF default_filterstring = '' or default_filterstring is null THEN
default_filterstring := '1=1';
END IF;
sql := format('
SELECT v.id FROM visitors v
LEFT JOIN trackings t on v.id = t.visitor_id
WHERE v.app_id = app_id and (%s) and (%s)
group by v.id
having case when %s > 0 then count(v.id) = %s else true end
', custom_filterstring, default_filterstring, custom_filter_length, custom_filter_length);
RETURN QUERY EXECUTE sql;
END;
$body$
LANGUAGE 'plpgsql';
This works great, but AFAIK there is not way to express the unknown filter for a custom attribute. In that it would require using a left outer join, or a not exists subquery with access to the outer scope.
I am now looking at alternative ways to accomplish the same as above, but also support this kind of query. I am thinking something like the below, using a series of lateral joins for each condition, but this seems like it won't perform very well as soon as there is more than 1-2 conditions/joins.
select v.id, v.name from visitors v
inner join lateral ( <-- custom fields
select * from trackings t
where field = 'admin'
) as t1 on t1.visitor_id = v.id
inner join lateral (
select * from trackings t
where field = 'users_created'
) as t2 on t2.visitor_id = v.id
inner join lateral (
select * from trackings t
where field = 'teams_created' and integer_value > 0
) as t3 on t3.visitor_id = v.id
where v.app_id = 'ASnYW1-RgCl0I' and (v.type = 'lead' or v.type = 'user')
and name ILIKE 'mads' and email is not null // <-- default fields
Any suggestions?

How to declare and assign value to a variable before query?

I have a complicated query that has to use a number called SubUnitRate. This variable comes from another table with special condition. in short we have:
DECLARE
SUBUNITRATE NUMBER;
BEGIN
SELECT NVL (NULLIF (CU.SUBUNITRATE, 0), 1)
INTO SUBUNITRATE
FROM CURRENCYS CU
JOIN ACCOUNTS ACC ON CU.ID = ACC.CURRENCY
WHERE ACC.ID = :ACCOUNTID;
END;
SELECT SUBUNITRATE * 100 FROM DUAL;
My goal is to acquire the result of(in simple case):
SELECT SUBUNITRATE * 100 FROM DUAL;
But how is that possible?
Assuming you want to use the value of SUBUNITRATE multiple times in the same query you could use the WITH clause:
with cte as (
select case
when CU.SUBUNITRATE = 0 then 1
else CU.SUBUNITRATE
end as SUBUNITRATE
FROM CURRENCYS CU
JOIN ACCOUNTS ACC ON CU.ID = ACC.CURRENCY
WHERE ACC.ID = :ACCOUNTID
)
select cte.SUBUNITRATE * 100
from cte;
A PL/SQL block cannot return the results of a query as a query. Instead, you can print the results out.
So, does this do what you want?
DECLARE
SUBUNITRATE NUMBER;
BEGIN
SELECT NVL(NULLIF(CU.SUBUNITRATE, 0), 1)
INTO SUBUNITRATE
FROM CURRENCYS CU JOIN
ACC
ON CU.ID = ACC.CURRENCY
WHERE ACC.ID = :ACCOUNTID;
DBMS_OUTPUT.PUT_LINE(SUBUNITRATE * 100)
END;
No need for PL. APC' solution, simplified and in a form you can use directly in your query (wherever you would say ... = subunitrate, say ... = (select sur from cte) instead - including the parentheses):
with cte_prelim as (select subunitrate from ... etc.),
cte (sur) as select case when subunit rate is null or subunitrate = 0 then 100
else subunitrate * 100 end from cte_prelim)
select... (your query where you need to use the value)

Subquery in PL/SQL returning NO_DATA_FOUND

A PL/SQL query I am looking at uses a subquery when determining a count of records:
SELECT COUNT(*)
INTO v_seqn
FROM SIPR.KDX KDX
WHERE KDX.KDX_STUC NOT IN ( SELECT ADD1.ADD_ADID
FROM SIPR.MEN_ADD ADD1
WHERE ADD1.ADD_ADID = KDX.KDX_STUC)
AND KDX.KDX_STUC = v_stud_id
AND KDX.KDX_SITS = 'A';
If the subquery returns no records, will this cause a NO_DATA_FOUND exception which can be handled, or is it perfectly valid and will simply return a null for the "NOT IN" clause of the main query and result in a count of 0.
Thanks.
You don't need to change anything. If the subquery returns no rows, then the main query will have no results, but since it is a count query without any group by or having clauses it will always return exactly one row with exactly one value, which might be 0.
It will never return null and it will not give you a NO_DATA_FOUND exception. The NO_DATA_FOUND exception is not an SQL but a PL/SQL error that occurs when you try to select a value into a variable, but the (main) select doesn't return any rows.
This will fetch all rows from SIPR.KDX KDX with the other satisfying conditions
in the where clause but a mutually exclusive result of your subquery. This is
similar A-B-C in set language where A is your table, B is your subquery and C is
all other satisfying conditions in the where clause.
Try to use outer join
SELECT
COUNT ( * )
INTO
V_SEQN
FROM
(SELECT
*
FROM
SIPR.KDX KDX
WHERE
KDX.KDX_STUC = V_STUD_ID
AND KDX.KDX_SITS = 'A') TABLE1
LEFT OUTER JOIN
SIPR.MEN_ADD ADD1
ON TABLE1.KDX_STUC = ADD1.ADD_ADID
WHERE
ADD1.ADD_ADID IS NULL;
Or using not exists
SELECT
COUNT ( * )
INTO
V_SEQN
FROM
(SELECT
*
FROM
SIPR.KDX KDX
WHERE
KDX.KDX_STUC = V_STUD_ID
AND KDX.KDX_SITS = 'A') TABLE1
WHERE NOT EXISTS
(SELECT
1
FROM
SIPR.MEN_ADD ADD1
WHERE
TABLE1.KDX_STUC = ADD1.ADD_ADID)

pl sql function select into variable in loop on a returning table

I have a type object as below
Emptable type is empname,empid,rank
Then I have a Plsql function as below and this errors out. I need to run a sql select statement against the returned list of empids and load it the returned list. and below code keeps erroring..
create or replace function emp_details return emptable
is
l_result_col emptable := emptable();
n integer := 0;
rxvalue number;
begin
for r in (select empname,empid from table)
loop
l_result_col.extend;
n := n + 1;
(select sum(xyz) into rxvalue from A inner join B on A.x=B.x and A.id=r.empid);
l_result_col(n) := t_col(r.empname, r.empid,rxvalue);
end loop;
return l_result_col;
end;
/
Any Help is appreciated..
Thank you!
Why not do it straightforward without involving PL/SQL code?
select t_col(r.empname, r.empid, sum(xyz))
bulk collect into l_result_col
from table r
left join (A join B on A.x = B.x) on A.id = r.empid
group by r.empname, r.empid;
I think your select shouldn't be in parentheses. In this context it is not a subselect but a separate PL/SQL statement.