How to avoid multiple function executions in case expression without nesting - sql

So let's assume I have the following query:
SELECT
CASE
WHEN SOME_PACKAGE.SOME_EXPENSIVE_FUNCTION IS NOT NULL
THEN SOME_PACKAGE.SOME_EXPENSIVE_FUNCTION || ', random string'
ELSE
'something_else'
END
FROM
SOME_TABLE;
Is there a way to prevent this package function from executing more than once without nested queries?

This should work:
SELECT NVL(NULLIF(SOME_PACKAGE.SOME_EXPENSIVE_FUNCTION
||', random string',', random string'),'something_else')
FROM SOME_TABLE;

Related

SQL How to make nested query with substring/case statement/ trim

New to SQL and trying to understand nested queries and how to use them. I have a substring, case statement, and trim statement that I'm trying to put together but am unsure of how. The substring has to be done first, then the case statement, then the trim. This is what I have at the moment but unsure of how to get it working. The code is random names/tables as an example
SELECT dtXYZ.*
FROM
(
SELECT dt,
SUBSTRING_INDEX(SUBSTRING_INDEX(dt, ..................... ) as lioness,
SUBSTRING_INDEX(SUBSTRING_INDEX(dt, .....................) as tiger,
SUBSTRING_INDEX(dt, .................) as bear
FROM Animaltab
) dtXYZ
SELECT
CASE WHEN length(bear) = 4 THEN bear
ELSE concat('0', bear)
END AS bear_corr,
CASE WHEN length(lion) = 7 THEN lioness
ELSE concat('0', lioness)
END AS lion_corr
trim(lion_corr) || '_' || trim(tiger) || '_' || trim(bear_corr) as new_imp_animal
Spark supports CTE https://spark.apache.org/docs/latest/sql-ref-syntax-qry-select-cte.html
even with databrics this will work see Common Table Expressions (CTEs) in Databricks and Spark
ANd you can nest them like this
WITH dtXYZ(dt,lioness,tiger,bear() AS ( SELECT dt,
SUBSTRING_INDEX(SUBSTRING_INDEX(dt, ..................... ) as lioness,
SUBSTRING_INDEX(SUBSTRING_INDEX(dt, .....................) as tiger,
SUBSTRING_INDEX(dt, .................) as bear
FROM Animaltab),
dtcorrected (dt,bear_corr,lion_corr,tiger) as (
SELECT
dt,
CASE WHEN length(bear) = 4 THEN bear
ELSE concat('0', bear)
END AS bear_corr,
CASE WHEN length(lion) = 7 THEN lioness
ELSE concat('0', lioness)
END AS lion_corr
,tiger
FROM dtXYZ)
SELECT
dt,
trim(lion_corr) || '_' || trim(tiger) || '_' || trim(bear_corr) as new_imp_animal FROM dtcorrected
Order of operations can be tricky with SQL if you're used to ordering things in a procedure. Like nbk commented, CTEs or Common Table Expressions are your best bet. CTEs are defined by the 'with' keyword and are very similar to nested subqueries (you could write the same query nested if you wanted) but are better suited to this operation where the nesting structure of the code doesn't mimic the nesting of the data. I always use CTEs if I'm joining tables that each need independent grouping or filtering. The SQL in the parenthesis essentially creates a view, and the outside SQL is a second higher-order select statement to create a result set. If I'm working with hierarchical data (parent, child, grandchild), I'll go with the nesting in the query to follow that path, but usually, the CTE is easier to organize your ideas. Here's how that would work:
with dtXYZ as
(
SELECT dt,
SUBSTRING_INDEX(SUBSTRING_INDEX(dt, ..................... ) as lioness,
SUBSTRING_INDEX(SUBSTRING_INDEX(dt, .....................) as tiger,
SUBSTRING_INDEX(dt, .................) as bear
FROM Animaltab
)
SELECT
CASE WHEN length(bear) = 4 THEN bear
ELSE concat('0', bear)
END AS bear_corr,
CASE WHEN length(lion) = 7 THEN lioness
ELSE concat('0', lioness)
END AS lion_corr,
trim(lion_corr) || '_' || trim(tiger) || '_' || trim(bear_corr) as new_imp_animal
from
dtXYZ
And in terms of 'order of operations,' case statements and functions in a select can be referenced by other parts of the select statement as inputs. Things can get hairy when you use 'if' ideas that resolve to illogical or error-causing conditions. Still, otherwise, I've had no issues with having many parts of a select refer to each other. It's an excellent way to test out nesting functions.

Postgres truncates CASE statement column

I'm writing some functions for mapping a Postgres database. The functions are used to create dynamic queries for a javascript API.
I've come across a particular query that truncates the output of a column and I cannot determine why.
The width of the column in the following query seems to be truncated to 63 characters:
SELECT
CASE
WHEN t.column_name ~ '_[uid]*$' AND t.has_fn THEN
format(
'format(''%s/%%s'', %s) AS %s',
array_to_string(ARRAY['',t.fk_schema,t.fk_name],'/'),
t.column_name,
regexp_replace(t.column_name, '_[uid]*$', '_link')
)
ELSE t.column_name
END AS column
FROM core.map_type() t; -- core.map_type() is a set returning function
This query is used to create a select list for another query, but the string produced by format() is truncated to 63 characters.
However, if I add || '' to the ELSE branch of the CASE statement, the problem goes away:
SELECT
CASE
WHEN t.column_name ~ '_[uid]*$' AND t.has_fn THEN
format(
'format(''%s/%%s'', %s) AS %s',
array_to_string(ARRAY['',t.fk_schema,t.fk_name],'/'),
t.column_name,
regexp_replace(t.column_name, '_[uid]*$', '_link')
)
ELSE t.column_name || '' -- add empty string here
END AS column
FROM core.map_type() t; -- core.map_type() is a set returning function
Having a column truncated like this is a worrisome problem. The fix here is a total hack and does not feel like a real solution.
Why is this happening? How can it be fixed?
In this case, the value of t.column_name in the ELSE branch of the CASE statement is of type name. name is a type used internally by Postgres for naming things. It has a fixed length of 64 bytes.
The column is truncated to suit the length of the ELSE branch.
Casting t.column_name to text will fix the problem:
SELECT
CASE
WHEN t.column_name ~ '_[uid]*$' AND t.has_fn THEN
format(
'format(''%s/%%s'', %s) AS %s',
array_to_string(ARRAY['',t.fk_schema,t.fk_name],'/'),
t.column_name,
regexp_replace(t.column_name, '_[uid]*$', '_link')
)
ELSE t.column_name::text
END AS column
FROM core.map_type() t;

SQL Query inside a function

I am using PostgreSQL with PostGis. I am executing a query like this:
select st_geomfromtext('point(22 232)',432)
It works fine. But now I want to take a value through a query. for example:
select st_geomfromtext('point((select x from data_name where id=1) 232)' , 432)
Here data_name is some table I am using and x stores some values. Now query inside is treated as a string and no value is returned.
Please help.
ERROR: syntax error at or near "select"
Try this:
select st_geomfromtext('point(' || x || ' 232)', 432) from data_name where id=1
Postgis has a function ST_MakePoint that is faster than ST_GeomFromText.
select ST_SetSRID(ST_MakePoint(x),432) from data_name where id=1;
While #muratgu answer is generally the way to go, one minor note:
A subquery gets you a different result when no row is found for id = 1. Then you get nothing back (no row), instead of:
select st_geomfromtext(NULL, 432)
If you need a drop-in replacement:
select st_geomfromtext('point('
|| (select x from data_name where id=1)
|| ' 232)' , 432)

How to join tables on regex

Say I have two tables msg for messages and mnc for mobile network codes.
They share no relations. But I want to join them
SELECT msg.message,
msg.src_addr,
msg.dst_addr,
mnc.name,
FROM "msg"
JOIN "mnc"
ON array_to_string(regexp_matches(msg.src_addr || '+' || msg.dst_addr, '38(...)'), '') = mnc.code
But query fails with error:
psql:marketing.sql:28: ERROR: argument of JOIN/ON must not return a set
LINE 12: ON array_to_string(regexp_matches(msg.src_addr || '+' || msg...
Is there a way to do such join? Or am I moving wrong way?
A very odd way to join. Every match on one side is combined with every row from the other table ...
regexp_matches() is probably the wrong function for your purpose. You want a simple regular expression match (~). Actually, the LIKE operator will be faster:
Presumably fastest with LIKE
SELECT msg.message
, msg.src_addr
, msg.dst_addr
, mnc.name
FROM mnc
JOIN msg ON msg.src_addr LIKE ('%38' || mnc.code || '%')
OR msg.dst_addr LIKE ('%38' || mnc.code || '%')
WHERE length(mnc.code) = 3;
In addition, you only want mnc.code of exactly 3 characters.
With regexp match
You could write the same with regular expressions but it will most definitely be slower. Here is a working example close to your original:
SELECT msg.message
, msg.src_addr
, msg.dst_addr
, mnc.name
FROM mnc
JOIN msg ON (msg.src_addr || '+' || msg.dst_addr) ~ (38 || mnc.code)
AND length(mnc.code) = 3;
This also requires msg.src_addr and msg.dst_addr to be NOT NULL.
The second query demonstrates how the additional check length(mnc.code) = 3 can go into the JOIN condition or a WHERE clause. Same effect here.
With regexp_matches()
You could make this work with regexp_matches():
SELECT msg.message
, msg.src_addr
, msg.dst_addr
, mnc.name
FROM mnc
JOIN msg ON EXISTS (
SELECT *
FROM regexp_matches(msg.src_addr ||'+'|| msg.dst_addr, '38(...)', 'g') x(y)
WHERE y[1] = mnc.code
);
But it will be slow in comparison.
Explanation:
Your regexp_matches() expression just returns an array of all captured substrings of the first match. As you only capture one substring (one pair of brackets in your pattern), you will exclusively get arrays with one element.
You get all matches with the additional "globally" switch 'g' - but in multiple rows. So you need a sub-select to test them all (or aggregate). Put that in an EXISTS - semi-join and you arrive at what you wanted.
Maybe you can report back with a performance test of all three?
Use EXPLAIN ANALYZE for that.
Your immediate problem is that regexp_matches could return one or more rows.
Try using "substring" instead, which extracts a substring given a regex pattern.
SELECT msg.message,
msg.src_addr,
msg.dst_addr,
mnc.name
FROM "msg"
JOIN "mnc"
ON substring(msg.src_addr || '+' || msg.dst_addr from '38(...)') = mnc.code

What is the string concatenation operator in Oracle?

What is the string concatenation operator in Oracle SQL?
Are there any "interesting" features I should be careful of?
(This seems obvious, but I couldn't find a previous question asking it).
It is ||, for example:
select 'Mr ' || ename from emp;
The only "interesting" feature I can think of is that 'x' || null returns 'x', not null as you might perhaps expect.
There's also concat, but it doesn't get used much
select concat('a','b') from dual;
I would suggest concat when dealing with 2 strings, and || when those strings are more than 2:
select concat(a,b)
from dual
or
select 'a'||'b'||'c'||'d'
from dual
DECLARE
a VARCHAR2(30);
b VARCHAR2(30);
c VARCHAR2(30);
BEGIN
a := ' Abc ';
b := ' def ';
c := a || b;
DBMS_OUTPUT.PUT_LINE(c);
END;
output:: Abc def
There are two ways to concatenate Strings in Oracle SQL. Either using CONCAT function or || operator.
CONCAT function allows you to concatenate two strings together
SELECT CONCAT( string1, string2 ) FROM dual;
Since CONCAT function will only allow you to concatenate two values together. If you want to concatenate more values than two, you can nest multiple CONCAT function calls.
SELECT CONCAT(CONCAT('A', 'B'),'C') FROM dual;
An alternative to using the CONCAT function would be to use the || operator
SELECT 'My Name' || 'My Age' FROM dual;
Using CONCAT(CONCAT(,),) worked for me when concatenating more than two strings.
My problem required working with date strings (only) and creating YYYYMMDD from YYYY-MM-DD as follows (i.e. without converting to date format):
CONCAT(CONCAT(SUBSTR(DATECOL,1,4),SUBSTR(DATECOL,6,2)),SUBSTR(DATECOL,9,2)) AS YYYYMMDD