SQL function gives error in some cases, why? - sql

I have the following function which is run for each row as we generate data. If i run the function on a separate entry it works, but it gives error in the compilation process, so there must be some rows which break it, but I cant see what it could be.. Is there anything wrong with the syntax or something i'm not thinking about here? Any help or tips is appreciated greatly
DROP FUNCTION IF EXISTS xx__calc_date;;
CREATE FUNCTION xx_calc_date(date INT, id INT, esId INT)
RETURNS DATE
DETERMINISTIC
BEGIN
IF date IS NULL OR date = 0 THEN
RETURN NULL;
ELSE -- this is where the error lies
IF(SELECT COUNT(b.TimeSort) FROM Booking b
WHERE b.BookingId IN (SELECT DISTINCT e.BookingId
FROM xx.Events e
WHERE e.id = id AND e.esId = esId)
AND TimeSort < 86400) > 0
THEN
RETURN STR_TO_DATE(date, '%Y%m%d');
ELSE
RETURN DATE_ADD(date, INTERVAL 1 DAY);
END IF;
END IF;
END;;

e.id = id is always true.
When in a subquery, you can leave off the table qualification for the inner table, not for the outer.
Suggest using EXISTS ( SELECT 1... ) instead of ( SELECT COUNT(*) ... ) > 0 )
The expression that seems to be doing two "existance" checks. Consider using a JOIN for such.

Related

How to use Case statement in Postgresql?

This is my SQL Query
CASE WHEN (1>2) THEN(
select * from rate limit 10
)
ELSE
(
select * from rate limit 1
)
When I use Case statement like above , I can get error like below.
ERROR: syntax error at or near "CASE"
LINE 2: CASE WHEN (1>2) THEN(
^
SQL state: 42601
Character: 2
Can anyone help me to solve this
Put a SELECT in front of the CASE statement. Also, you need an END after the last statement of the CASE. You need a place for the result of the CASE expression to be stored. The examples in the documentation are not executing statements that return a value; just variable assignment. So you don't need a SELECT there. If you don't care about the return from the overall query, you can use PERFORM instead of SELECT.
SELECT CASE
WHEN 1 > 2 -- always false
THEN (SELECT * FROM rate LIMIT 10)
ELSE (SELECT * FROM rate LIMIT 1)
END
;
You can also assign the result of the subqueries to a variable if this CASE is defined inside of a function.
DO
$$
DECLARE
rec RECORD;
BEGIN
CASE WHEN 1 > 2
THEN (SELECT * INTO rec FROM rate LIMIT 10)
ELSE (SELECT * INTO rec FROM rate LIMIT 1)
END
;
END;
$$ LANGUAGE PLPGSQL;

SQL - SELECT with inner IF or CASE

Trying to have a row that will display 'YES' or 'NO' depending on values found (if a tree has been treated before the date given in argument say YES else NO).
Here's my function:
CREATE OR REPLACE FUNCTION tree_care(care_date DATE)
RETURNS TABLE(name VARCHAR(32), type VARCHAR(32), treated TEXT) AS
$$
BEGIN
RETURN QUERY
SELECT tree.name,
tree.type,
IF EXISTS (SELECT * FROM treatment
JOIN tree ON tree.name = treatment.tree_name
WHERE treatment.date < care_date) THEN
'YES'::text
ELSE
'NO'::text
END IF
FROM tree;
END;
$$
And I get the following error:
ERROR: syntax error at or near "EXISTS"
LINE 8: IF EXISTS (SELECT * FROM treatment
How does one implement an IF statement inside a SELECT?
PS: Using postgresql 9.4
IF is control flow. Use CASE because this is within a SELECT statement:
SELECT tree.name,
tree.type,
(CASE WHEN EXISTS (SELECT 1
FROM treatment
WHERE tree.name = treatment.tree_name AND
treatment.date < care_date
) THEN
THEN 'YES'::text
ELSE 'NO'::text
END) as Flag
FROM tree;
I am also guessing that you intend a correlated subquery, rather than an independent subquery.

PG Rule - Using NEW in subquery

I have a rule that runs on update to a source table. The rule queries data across multiple other tables, formats the data, and inserts it into another transform table. Here is an example of what I have so far.
CREATE OR REPLACE RULE
value_insert
AS ON UPDATE TO
source_table
DO ALSO INSERT INTO transform_table(
username
,status
,section
)
SELECT
username
,MAX(status)
,MAX(section)
FROM
(
SELECT
username
,CASE
WHEN item = status
THEN value
ELSE NULL
END AS status
,CASE
WHEN item = section
THEN value
ELSE NULL
END AS section
FROM
(
SELECT
username
,item
,value
FROM
table1
,table2
WHERE
item = status
OR item = section
AND source_table.username = NEW.username
)
)
GROUP BY
username
I am trying to pass the NEW value into the subquery, but I receive the error "ERROR: subquery in FROM cannot refer to other relations of same query level". Using NEW in the outermost where statement works, but the query take a long time due to the large amount of data in the tables.
Is it possible to pass the NEW value into the subquery of this rule? I am using PG 8.3 and PGAdmin 1.12
Solved this by implementing a trigger function. As far as I can tell, you cannot pass the NEW value to a subquery in a rule (PG 8.3).
The script below will collect only data from table1 and table2 that corresponds to updated record in old_table, reformat the data, and insert it into new_table. By switching to a trigger and inserting the argument into the base query, the processing time has dropped from ~2 seconds to ~50 ms.
The function:
CREATE OR REPLACE FUNCTION get_data()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO new_table(
username
,status
,section
)
SELECT
username
,MAX(status)
,MAX(section)
FROM
(
SELECT
username
,CASE
WHEN item = status
THEN value
ELSE NULL
END AS status
,CASE
WHEN item = section
THEN value
ELSE NULL
END AS section
FROM
(
SELECT
username
,item
,value
FROM
table1
,table2
WHERE
(item = status OR item = section)
AND table1.username = table2.username
AND table2.username = old_table.username
AND old_table.username = NEW.username
)
)
GROUP BY
username;
RETURN NEW;
END;
$$
LANGUAGE plpgSQL;
The trigger:
CREATE TRIGGER
old_table_trigger
BEFORE UPDATE ON
old_table
FOR EACH ROW EXECUTE PROCEDURE get_data();

Replacing In clause with exists

HI Gurus,
I'm looking to replace an IN clause with exists, but despite reading other similar cases on here I've not been able to apply them to my dataset.
I am looking to add in a column to my main query which tells me if a fund is found within a separate list, and if it does then label it 'emergency' and if not then 'non-emergency'
The list is defined like so:
select
f.id
FROM _audit a
INNER JOIN _fund f ON a.article_id = f.id
WHERE a.entity_name = 'Fund'
AND a.Changes LIKE
'%finance_code2%OldValue>3%'
)
UNION
(
select
id AS fund_reference
FROM _fund
WHERE (finance_code2 LIKE '3%'
OR finance_code2 LIKE '9%')
AND finance_code2 IS NOT NULL
And so what I am looking for is essentially something like:
SELECT
...Main query here...
,CASE WHEN fund_id IN (list_details) THEN 'emergency' else 'non-emergency' end
I know that it would be more efficient to do something like
SELECT
...Main query here...
,SELECT CASE WHEN EXISTS
(SELECT fund_id FROM list_details WHERE fund_id IS NOT NULL) THEN 'emergency' else 'non-emergency' END
But every time I try it keeps returning false values (saying that funds are contained within the list when they are not)
In case it helps I'm using sql server 2005 and the main query is listed below, where the list_details result (id) is joined onto donation_fund_allocation on list_details.id = donation_fund_allocation.fund_id
As always any clue would be massively appreciated :)
Thanks!
Main query
SELECT
don.supporter_id AS contact_id
,don.id AS gift_id
,YEAR(don.date_received) AS calendar_year
,YEAR(don.date_received) - CASE WHEN MONTH(don.date_received) < 4 THEN 1 ELSE 0 END AS financial_year
,don.date_received AS date_received
,don.event_id AS event_id
,SUM(CASE WHEN don.gift_aid_status <> 4 THEN don.value_gross * ((dfa.percentage) / 100)
WHEN don.gift_aid_status = 4 AND don.value_net > don.value_gross
AND don.value_net <> 0 THEN don.value_net * ((dfa.percentage) / 100)
ELSE don.value_gross * ((dfa.percentage) / 100)
END
) AS donation_value
--**List details query to go in here**
FROM donation don WITH (nolock)
INNER JOIN donation_fund_allocation dfa WITH (nolock) ON dfa.donation_id = don.id
WHERE don.supporter_id IS NOT NULL
AND don.status = 4
AND don.value_gross <> 0
GROUP BY don.supporter_id
,don.id
,don.date_received
,don.event_id
You need to correlate the exists call with the outer query. As written you are just asking if there exist any rows in list_details where fund_id isn't null
So, what you actually want is
SELECT
...Main query here...
,SELECT CASE WHEN EXISTS
(SELECT 1 FROM list_details WHERE fund_id = outer.fund_id) THEN 'emergency' else 'non-emergency' END
Where outer is the table alias for where fund_id can be found in your main select
You could write a function which takes the fund_id and returns an appropriate string value of "emergency" or "non-emergency".

What's the most efficient way to check if a record exists in Oracle?

A)
select decode(count(*), 0, 'N', 'Y') rec_exists
from (select 'X'
from dual
where exists (select 'X'
from sales
where sales_type = 'Accessories'));
B)
select decode(count(*), 0, 'N', 'Y') rec_exists
from (select 'X'
from sales
where sales_type = 'Accessories');
C) Something else (specify)
EDIT: It was hard to pick the "correct" answer, as the best approach depends on what you want to do after checking if the value exists, as pointed out by APC. I ended up picking the answer by RedFilter, since I had originally envisioned this check as a function by itself.
select case
when exists (select 1
from sales
where sales_type = 'Accessories')
then 'Y'
else 'N'
end as rec_exists
from dual;
What is the underlying logic you want to implement? If, for instance, you want to test for the existence of a record to determine to insert or update then a better choice would be to use MERGE instead.
If you expect the record to exist most of the time, this is probably the most efficient way of doing things (although the CASE WHEN EXISTS solution is likely to be just as efficient):
begin
select null into dummy
from sales
where sales_type = 'Accessories'
and rownum = 1;
-- do things here when record exists
....
exception
when no_data_found then
-- do things here when record doesn't exists
.....
end;
You only need the ROWNUM line if SALES_TYPE is not unique. There's no point in doing a count when all you want to know is whether at least one record exists.
select count(1) into existence
from sales where sales_type = 'Accessories' and rownum=1;
Oracle plan says that it costs 1 if seles_type column is indexed.
here you can check only y , n
if we need to select a name as well that whether this name exists or not.
select name , decode(count(name),0, 'N', 'Y')
from table
group by name;
Here when it is Y only then it will return output otherwise it will give null always. Whts ths way to get the records not existing with N like in output we will get Name , N. When name is not existing in table
Simply get a count of the record(s) you're looking for. If count > 0 then record(s) exist.
DECLARE
rec_count NUMBER := 0;
BEGIN
select count(*)
into rec_count
from EMPLOYEETABLE
WHERE employee_id = inEMPLOYEE_ID
AND department_nbr = inDEPARTMENT_NBR;
if rec_count > 0 then
{UPDATE EMPLOYEETABLE}
else
{INSERT INTO EMPLOYEETABLE}
end if;
END;
select decode(count(*), 0, 'N', 'Y') rec_exists
from sales
where sales_type = 'Accessories';
select NVL ((select 'Y' from dual where exists
(select 1 from sales where sales_type = 'Accessories')),'N') as rec_exists
from dual
1.Dual table will return 'Y' if record exists in sales_type table
2.Dual table will return null if no record exists in sales_type table and NVL will convert that to 'N'
The most efficient and safest way to determine if a row exists is by using a FOR-LOOP...
You won't even have a difficult time if you are looking to insert a row or do something based on the row NOT being there but, this will certainly help you if you need to determine if a row exists. See example code below for the ins and outs...
If you are only interested in knowing that 1 record exists in your potential multiple return set, than you can exit your loop after it hits it for the first time.
The loop will not be entered into at all if no record exists. You will not get any complaints from Oracle or such if the row does not exist but you are bound to find out if it does regardless. Its what I use 90% of the time (of course dependent on my needs)...
EXAMPLE:
DECLARE
v_exist varchar2(20);
BEGIN
FOR rec IN
(SELECT LOT, COMPONENT
FROM TABLE
WHERE REF_DES = (SELECT REF_DES FROM TABLE2 WHERE ORDER = '1234')
AND ORDER = '1234')
LOOP
v_exist := "IT_EXISTS"
INSERT INTO EAT_SOME_SOUP_TABLE (LOT, COMPONENT)
VALUES (rec.LOT, rec.COMPONENT);**
--Since I don't want to do this for more than one iteration (just in case there may have been more than one record returned, I will EXIT;
EXIT;
END LOOP;
IF v_exist IS NULL
THEN
--do this
END IF;
END;
--This is outside the loop right here The IF-CHECK just above will run regardless, but then you will know if your variable is null or not right!?. If there was NO records returned, it will skip the loop and just go here to the code you would have next... If (in our case above), 4 records were returned, I would exit after the first iteration due to my EXIT;... If that wasn't there, the 4 records would iterate through and do an insert on all of them. Or at least try too.
By the way, I'm not saying this is the only way you should consider doing this... You can
SELECT COUNT(*) INTO v_counter WHERE ******* etc...
Then check it like
if v_counter > 0
THEN
--code goes here
END IF;
There are more ways... Just determine it when your need arises. Keep performance in mind, and safety.
select CASE
when exists (SELECT U.USERID,U.USERNAME,U.PASSWORDHASH
FROM TBLUSERS U WHERE U.USERID =U.USERID
AND U.PASSWORDHASH=U.PASSWORDHASH)
then 'OLD PASSWORD EXISTS'
else 'OLD PASSWORD NOT EXISTS'
end as OUTPUT
from DUAL;
Another solution:
There are lot of solutions I saw in SO, which are good, count(1) or count(*) , when exists, where exists, left join, loop with no data exception..
I prefer below options, hope this is performant in large data, didn't check though :) :
Return 1 or 0 ( Can be used if you have checks with a number variable )
SELECT NVL(MIN(1),0)
FROM SALES
WHERE SALES_TYPE = 'ACCESSORIES' AND ROWNUM = 1
Return Y or N ( Can be used if you have checks with a string variable )
SELECT DECODE(MIN(1),1, 'Y', 'N')
FROM SALES
WHERE SALES_TYPE = 'ACCESSORIES' AND ROWNUM = 1
There are multiple options, Kindly do a performance test for your scenario and choose the best option.
SELECT 'Y' REC_EXISTS
FROM SALES
WHERE SALES_TYPE = 'Accessories'
The result will either be 'Y' or NULL.
Simply test against 'Y'