mysql where columnname in (function(a value)) not working - mysql

I have a strange situation here with the mysql query:
When the WHERE unterkategorie IN (children_csv(1)) is used there is no result.
Second "WHERE unterkategorie IN (11,12,13,28,29,32,14,15,16,30,31,33,34,35)" is fetching records when I substitute function name with the results of the function when executed separately
the full query is:
SELECT k.name category_name,
p.unterkategorie,
p.artikelnummer,
p.hauptkategorie,
p.id,
p.name product_name,
p.preis,
p.sortierung,
p.verpackungseinheit
FROM produkte p, kategorie k
WHERE unterkategorie IN (children_csv(1))
WHERE unterkategorie IN (11,12,13,28,29,32,14,15,16,30,31,33,34,35)
AND p.unterkategorie = k.id
ORDER BY unterkategorie, p.sortierung
Following is the function definition
delimiter //
CREATE DEFINER=`root`#`localhost` FUNCTION `children_csv`(child int)RETURNS varchar(1000) CHARSET utf8
BEGIN
declare return_value varchar(1000);
SELECT GROUP_CONCAT(Level SEPARATOR ',')childrens into return_value FROM (
SELECT #Ids := (
SELECT GROUP_CONCAT(`id` SEPARATOR ',')
FROM `kategorie`
WHERE FIND_IN_SET(`parent`, #Ids)
ORDER BY parent, sortierung
) Level
FROM `kategorie`
JOIN (SELECT #Ids := child) temp1
WHERE FIND_IN_SET(`parent`, #Ids)
) temp2;
RETURN return_value;
END;
//
delimiter ;

Your function is returning a single value, a string. It is not returning a a list of values (because MySQL functions do not do that). If you want to use the function directly, you can use find_in_set():
WHERE find_in_set(unterkategorie, children_csv(1))
I will caution you that MySQL cannot use an index on unterkategorie, so this might be slower.
If you want a faster query, then you can construct a query as a string (called dynamic SQL) and use prepare and exec to run it.
If you are coming from another programming language, you need to learn that functions are not the route to better performance in SQL. Moving the logic into a function generally does not help performance.

Related

MySQL Use table name for function

When we use a statement like select count(*) from TABLE, the function count() automatically knows which table it is counting. Is it possible to grab the table and use it in a user defined function.
drop function if exists related_count;
create function related_count(parent int(11)) returns int(11) deterministic
begin
declare count int(11) default 0;
set count=(select count(*) from TABLENAME where id=parent);
return count;
end;
So that I can use it like this:
select count(*),related_count(id) from TABLENAME
So that I can use the same function regardless of table instead of defining multiple functions because of multiple tables.
Is there a way to switch between select count(*) from TABLENAME1 where id=parent or select count(*) from TABLENAME2 where id=parent dependent on a variable related_count('TABLE1',id)
The comment above from #RajeevRanjan mentions using dynamic SQL. This won't work, but if it did it would look like this:
create function related_count(tablename varchar(64), parent int) returns int reads sql data
begin
declare count int default 0;
set #sql = concat('select count(*) into count from `', tablename, '` where id = ', parent);
prepare stmt from #sql;
execute stmt;
return count;
end
However, this is not allowed:
ERROR 1336 (0A000): Dynamic SQL is not allowed in stored function or trigger
The reason it doesn't work is that your stored function could be called by an expression in an SQL statement that is itself a dynamic SQL execution. I guess MySQL only allows one level "deep" of prepare/execute. You can't make a prepared query run another prepared query.
To do this, you'd have to hard-code each table name like the following:
create function related_count(tablename varchar(64), parent int) returns int reads sql data
begin
declare count int default null;
if tablename = 'foo' then set count = (select count(*) from foo where id = parent);
elseif tablename = 'bar' then set count = (select count(*) from bar where id = parent);
elseif tablename = 'baz' then set count = (select count(*) from baz where id = parent);
end if;
return count;
end
This also has an advantage that it isn't an SQL injection vulnerability, whereas the PREPARE/EXECUTE solution (if it had worked) would be.
PS: A function that reads from other tables is not deterministic.

MySQL turn a JOIN statement into a Stored Procedure or Function?

So I have a JOIN statement that I will be using in multiple places. It essentially finds an IP address and matches it to said location from the intermediate table. I need to pass in two variables - one being the prefix of my database/schema and the other being the IP Address itself.
Therefore used is the CONCAT() function in order to piece it together.
So at the moment I have a procedure that looks something like this:
CREATE DEFINER=`root`#`%` PROCEDURE `LocationFromIp`(
ipAddress VARCHAR(16),
clientComp VARCHAR(32)
)
BEGIN
DECLARE test VARCHAR(255);
SET #networkSql = CONCAT("
SET #Location =
(SELECT `network`.`Name`
FROM `", clientComp, "-settings`.`iptable` AS `iptable`
LEFT JOIN `", clientComp, "-settings`.`network` AS `network`
ON `network`.`Subnet` = `iptable`.`Subnet`
WHERE `iptable`.`IP` = '", ipAddress, "'
LIMIT 1);
");
PREPARE test1 FROM #networkSql;
EXECUTE test1;
SELECT #Location AS `Location`;
It returns the result I want however I don't know how I can use this in a statement.
SELECT `IPAddress` AS CALL LocationFromIp('clientComp', `IPAddress`)
(
SELECT `IPAddress`
FROM `clientComp-data`.`tablename`
WHERE #date > `Date`;
)
GROUP BY `IPAddress`
The above does not work but I hope you can understand my thinking!
So how would I do it?
Why can't you adjust your stored procedure to include all the fields that you need from the very beginning?

PostgreSQL Function: Return Multiple Rows as JSON

I have a PostgreSQL function which successfully returns its data as
table(key character varying, value integer)
but I want to return JSON as it's more convenitent. I have looked at some other answers, and documentation, but it gets complicated when aliases are used (I'd like to return 'key' and 'value' as the column names).
Can someone please recommend the most simple and concise way of expressing this function, in a way that does not require extra complexity to call:
DROP FUNCTION get_document_metadata_integer_records_by_document_id(integer);
CREATE OR REPLACE FUNCTION get_document_metadata_integer_records_by_document_id(document_id integer)
RETURNS table(key character varying, value integer) AS
$BODY$
SELECT document_metadata_records.key, document_metadata_integer_records.value FROM document_metadata_integer_records
LEFT OUTER JOIN document_metadata_records
ON document_metadata_integer_records.document_metadata_record_id = document_metadata_records.id
WHERE document_metadata_records.document_id = $1
$BODY$
LANGUAGE sql VOLATILE
COST 100;
ALTER FUNCTION get_document_metadata_integer_records_by_document_id(integer)
OWNER TO postgres;
Use cross join lateral as a clean and easy path to create a composite type that can be used by row_to_json
create or replace function get_document_metadata_integer_records_by_document_id(
_document_id integer
) returns setof json as $body$
select row_to_json(s)
from
document_metadata_integer_records dmir
left outer join
document_metadata_records dmr on dmir.document_metadata_record_id = dmr.id
cross join lateral
(select dmr.key, dmir.value) s
where dmr.document_id = _document_id
;
$body$ language sql stable;
then use it as
select get_document_metadata_integer_records_by_document_id(1) as pair;
or
select pair from get_document_metadata_integer_records_by_document_id(1) j(pair);
or make it return a table
) returns table (pair json) as $body$
and use as
select pair from get_document_metadata_integer_records_by_document_id(1);
With older versions of Postgresql without lateral it is possible to do it in a subquery
select row_to_json(s)
from
(
select dmr.key, dmir.value
from
document_metadata_integer_records dmir
left outer join
document_metadata_records dmr on dmir.document_metadata_record_id = dmr.id
where dmr.document_id = _document_id
) s

Why find_in_set works but IN clause

I have code like this.
DELIMITER $$
CREATE PROCEDURE `sp_deleteOrderData`(orderid BIGINT(11))
BEGIN
DECLARE shipmentnumbers VARCHAR(1000);
DECLARE cartonid VARCHAR(1000);
SELECT GROUP_CONCAT(a_shipmentid) FROM t_shipment WHERE a_orderid = orderid INTO shipmentnumbers;
SELECT GROUP_CONCAT(a_cartonid) FROM t_carton WHERE a_shipmentid IN (shipmentnumbers) INTO cartonid;
SELECT shipmentnumbers;
/*SELECT cartonid; */
END$$
DELIMITER ;
Here shipmentnumbers returns 100020,100021,100022
Ideally cartonid should be returned as 11,12,13
But i get only 11 as cartonid.
But when i use below code i get proper result.
SELECT GROUP_CONCAT(a_cartonid) FROM t_carton WHERE FIND_IN_SET( a_shipmentid, shipmentnumbers ) INTO cartonid;
I wanted to know what exactly is the difference between IN and FIND_IN_SET and hwo di i decide what to use.
IN accepts a list or parameters to search, FIND_IN_SET accepts a string parameter containing a comma-separated list:
SELECT 1 IN (1, 2, 3, 4)
SELECT FIND_IN_SET(1, '1,2,3,4')
If you try to apply IN to a comma-separated string, it will treat it as a single parameter and will match it as a whole:
SELECT 1 IN ('1,2,3,4')
Of course, the string '1' is not equal to the string '1,2,3,4' so the query above returns false.
FIND_IN_SET searches a string inside a set of string separated by a comma.
MySQL FIND_IN_SET
But you don't need to use GROUP_CONCAT to concatenate the rows to be used in the IN clause, try this,
SELECT GROUP_CONCAT(a_cartonid)
FROM t_carton
WHERE a_shipmentid IN
(
SELECT a_shipmentid
FROM t_shipment
WHERE a_orderid = orderid
)
or use JOIN
SELECT GROUP_CONCAT(DISTINCT a.a_cartonid)
FROM t_carton a
INNER JOIN
(
SELECT a_shipmentid
FROM t_shipment
WHERE a_orderid = orderid
) b ON a.a_shipmentid = b.a_shipmentid

Optimizing SQL Server stored procedures that use functions?

I'd like some help in optimizing the following query:
SELECT DISTINCT TOP (#NumberOfResultsRequested) dbo.FilterRecentSearchesTitles(OriginalSearchTerm) AS SearchTerms
FROM UserSearches
WHERE WebsiteID = #WebsiteID
AND LEN(OriginalSearchTerm) > 20
--AND dbo.FilterRecentSearchesTitles(OriginalSearchTerm) NOT IN (SELECT KeywordUrl FROM PopularSearchesBaseline WHERE WebsiteID = #WebsiteID)
GROUP BY OriginalSearchTerm, GeoID
It runs fine without the line that is commented out. I have an index set on UserSearches.OriginalSearchTerm, WebsiteID, and PopularSearchesBaseline.KeywordUrl, but the query still runs slow with this line in there.
-- UPDATE --
The function used is as follows:
ALTER FUNCTION [dbo].[FilterRecentSearchesTitles]
(
#SearchTerm VARCHAR(512)
)
RETURNS VARCHAR(512)
AS
BEGIN
DECLARE #Ret VARCHAR(512)
SET #Ret = dbo.RegexReplace('[0-9]', '', REPLACE(#SearchTerm, '__s', ''), 1, 1)
SET #Ret = dbo.RegexReplace('\.', '', #Ret, 1, 1)
SET #Ret = dbo.RegexReplace('\s{2,}', ' ', #Ret, 1, 1)
SET #Ret = dbo.RegexReplace('\sv\s', ' ', #Ret, 1, 1)
RETURN(#Ret)
END
Using the Reglar Expression Workbench code.
However, as I mentioned - without the line that is currently commented out it runs fine.
Any other suggestions?
I am going to guess that dbo.FilterRecentSearchesTitles(OriginalSearchTerm) is a function. My suggestion would be to see about rewriting it into a table valued function so you can return a table that could be joined on.
Otherwise you are calling that function for each row you are trying to return which is going to cause your problems.
If you cannot rewrite the function, then why not create a stored proc that will only execute it once, similar to this:
SELECT DISTINCT TOP (#NumberOfResultsRequested) dbo.FilterRecentSearchesTitles(OriginalSearchTerm) AS SearchTerms
INTO #temp
WHERE WebsiteID = #WebsiteID
SELECT *
FROM #temp
WHERE SearchTerms NOT IN (SELECT KeywordUrl
FROM PopularSearchesBaseline
WHERE WebsiteID = #WebsiteID)
Then you get your records into a temp table after executing the function once and then you select on the temp table.
I might try to use a persisted computed column in this case:
ALTER TABLE UserSearches ADD FilteredOriginalSearchTerm AS dbo.FilterRecentSearchesTitles(OriginalSearchTerm) PERSISTED
You will probably have to add WITH SCHEMABINDING to your function (and the RegexReplace function) like so:
ALTER FUNCTION [dbo].[FilterRecentSearchesTitles]
(
#SearchTerm VARCHAR(512)
)
RETURNS VARCHAR(512)
WITH SCHEMABINDING -- You will need this so the function is considered deterministic
AS
BEGIN
DECLARE #Ret VARCHAR(512)
SET #Ret = dbo.RegexReplace('[0-9]', '', REPLACE(#SearchTerm, '__s', ''), 1, 1)
SET #Ret = dbo.RegexReplace('\.', '', #Ret, 1, 1)
SET #Ret = dbo.RegexReplace('\s{2,}', ' ', #Ret, 1, 1)
SET #Ret = dbo.RegexReplace('\sv\s', ' ', #Ret, 1, 1)
RETURN(#Ret)
END
This makes your query look like this:
SELECT DISTINCT TOP (#NumberOfResultsRequested) FilteredOriginalSearchTerm AS SearchTerms
FROM UserSearches
WHERE WebsiteID = #WebsiteID
AND LEN(OriginalSearchTerm) > 20
AND FilteredOriginalSearchTerm NOT IN (SELECT KeywordUrl FROM PopularSearchesBaseline WHERE WebsiteID = #WebsiteID)
GROUP BY OriginalSearchTerm, GeoID
Which could potentially be optimized for speed (if necessary) with a join instead of not in, or maybe different indexing (perhaps on the computed column, or some covering indexes). Also, DISTINCT with a GROUP BY is somewhat of a code smell to me, but it could be legit.
Instead of using using the function on SELECT, I modified the INSERT query to include this function. That way, I avoid calling the function for every row when I later want to retrieve the data.