I'm attempting to query a MySQL database. The fields that are stored in the database as integers are returned as integers which is as I would expect. When there is a value calculated in a Stored Procedure it is always returned as longlong, even if I can guarantee that the number will be either 0 or 1.
I need a way to ensure that the result of a calculated field is still returned as an int.
Interestingly, doing the calculation in a view, and then querying that view seems to fix the problem, but it takes an enormous performance hit.
Edit
An example of the sorts of procedures I'm trying to use would be:
DELIMITER //
CREATE PROCEDURE getProjectFinance(IN projectID varchar(30))
BEGIN
SELECT p.projectID as id,
(Select sum(COALESCE(v.Cost, 0))
from variations v
where v.projectID = p.projectID) as total
FROM Projects p
WHERE p.projectID = projectID;
END//
DELIMITER ;
DELIMITER //
CREATE PROCEDURE getAllProjectsFinance()
BEGIN
SELECT p.projectID as id,
(Select sum(COALESCE(v.Cost, 0))
from variations v
where v.projectID = p.projectID) as total
FROM Projects p
END//
DELIMITER ;
Edit 2
I've attempted to simplify the problem slightly. The following SQL command returns a record with a single field (count) which is of type longlong (8 bytes). I want it to be of type integer (4 bytes)
SELECT (Select 1) as count;
Using cast as follows doesn't help either:
SELECT cast((Select 1) as signed integer) as count;
What if you just cast the calculated value?
cast((
Select sum(COALESCE(v.Cost, 0))
from variations v
where v.projectID = p.projectID
)
as integer
) as total
CREATE FUNCTION name(parameter paramType, .... ) RETURNS returnType instead of create procedure ... you should also use a return clause at the end of the body of your function. For more information look at the documentation the common and function specific parts.
Related
I'm working on a LeetCode problem that asks the user to return the Nth highest salary from a table called Employee with columns Id and Salary.
The code that the user is given to start with is
CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
RETURN (
# Write your MySQL query statement below.
);
END
The temptation is to put something like
SELECT e.salary as getNthHighestSalary
FROM employee e
ORDER BY e.salary DESC
LIMIT 1 OFFSET (N-1)
But evidently that doesn't work. Successful queries define another variable M=N-1 outside the return block, and then use OFFSET M.
My question:
What is the principle involved here? Clearly there are some
limitations as to what operations you're allowed to do inside the return block.
A reference is totally fine; some searching didn't yield anything.
In short: you cannot use offset (N-1) there, because the syntax doesn't allow it. (And you should get a syntax error).
In general, limit and offset expect the actual number:
The LIMIT clause can be used to constrain the number of rows returned by the SELECT statement. LIMIT takes one or two numeric arguments, which must both be nonnegative integer constants.
There is an exceptions though inside stored procedures:
Within stored programs, LIMIT parameters can be specified using integer-valued routine parameters or local variables.
So you can use a variable there, but not an expression. That is why it works if you use the precalculated variable M instead.
DELIMITER //
CREATE FUNCTION getNthHighestSalary2(N INT) RETURNS INT
DETERMINISTIC
BEGIN
RETURN
(SELECT salary
FROM employee
ORDER BY salary DESC
LIMIT 1 OFFSET N);
END//
DELIMITER ;
The function return an integer, because you select only 1 value with LIMIT 1.
A result set with two rows, would produce an error.
the DETERMINISTIC i added because mysql wants there an option
Also tables can't be used as return value.
If you want to return more variables, you have to add user define variables after N INT, which can the be used in the SEELCT where you call the function
I would like to do something like the following:
SELECT AVG([1,2,3]);
This would of course return 2. The closest I've got is the following which is less than ideal but can be put in one line:
drop table nums;
create temporary table nums (num int);
insert into nums(num) values(1),(2),(3);
select avg(num) from nums;
If there's a way I would assume this would also be possible with other functions such as variance() and others.
Edit: This idea is out of curiosity not a real problem I need to solve.
AVG can only have 1 argument. You'd need to do SELECT AVG(num) FROM nums
You could also do SELECT SUM(num) / COUNT(num) FROM nums
Just know since you're dividing using ints that it will not be precise.
You are using the wrong tools to solve your problem.
If you want to calculate the variance of a list, use some kind of a scripting language, be it Php, Python, etc. If you want to firstly store the data and only then calculate the variance, of course, use something like MySql.
I also think that MySQL may not be the right tool (since you didn't want to store the numbers), but the answer to your question is: yes, you can do it with MySQL without creating tables if you want.
I didn't find any built-in functions/structures for this, but I came up with the following solution. My idea is to create a custom function which accepts the numbers in a delimited string, then it splits the string and calculates the average of the numbers. Here's an implementation which works with integers. The input should be num,num,num and so on, it should end with a number (see the examples at the end).
DROP FUNCTION IF EXISTS AVGS;
DELIMITER $$
CREATE FUNCTION AVGS(s LONGTEXT) RETURNS DOUBLE
DETERMINISTIC
BEGIN
DECLARE sum BIGINT DEFAULT 0;
DECLARE count BIGINT DEFAULT 0;
DECLARE pos BIGINT DEFAULT 0;
DECLARE lft TEXT DEFAULT '';
-- can we split?
SET pos = LOCATE(',', s);
WHILE 0 < pos DO -- while we can split
SET lft = LEFT(s, pos - 1); -- get the first number
SET s = SUBSTR(s FROM pos + 1); -- store the rest
SET sum = sum + CAST(lft AS SIGNED);
SET count = count + 1;
SET pos = LOCATE(',', s); -- split again
END WHILE;
-- handle last number
SET sum = sum + CAST(s AS SIGNED);
SET count = count + 1;
RETURN sum / count;
END $$
DELIMITER ;
SELECT AVGS("1"); -- prints: 1
SELECT AVGS("1,2"); -- prints: 1.5
SELECT AVGS("1,2,3"); -- prints: 2
See the live working demo here.
Variance may be much more complex, but I hope you get the idea.
I've a plsql function which I'm calling in my select query something like this:
select TEST_PKG.GET_VAL(db.id) as value from test_table db where (some condition lets say) db.id>11
plsql function returns an nested table of type number.
eg : type test_type is table of number
test_type is the return type of my function which is an array of numbers.for a single id it can return multiple results or numbers
now when I execute my query I get column value with output as:
schema_name.functionname() i.e sa.GET_VAL() //if no record is found for an id
and sa.GET_VAL(21,33,11,33) //as output if function returns a value.
Now how can I get my hands on the value 21,33,11,33? If I do
select * from table (TEST_PKG.GET_VAL(12334))// I get numbers in each row. this is consumable.
What can I do in my initial select query to get the values to become consumable? it's also ok if I write the values returned by the select query to some temp table with data type as int and consume from there.
I'm working with oracle 11g.
Any help is much appreciated.any guidance is welcomed.
P.S: I cannot change the function.
What can I do in my initial select query to get the values to become consumable?
A few ways to SELECT PL/SQL functions that return tables:
Set up a function that returns TABLE OF NUMBER...
create type matt_tab_typ IS TABLE OF NUMBER;
create or replace function matt_tab_fnc ( p_id number ) RETURN matt_tab_typ IS
BEGIN
-- dummy logic
return new matt_tab_typ(21, 33, 11, 33);
END;
As a table:
select o.object_id, matt_tab_fnc(object_id)
FROM all_objects o where rownum <= 10;
As a cursor:
select o.object_id, cursor(SELECT * FROM TABLE(matt_tab_fnc(object_id)))
FROM all_objects o where rownum <= 10;
As a join:
select o.object_id, t.column_value FROM all_objects o
CROSS JOIN LATERAL ( SELECT * FROM table(matt_tab_fnc(object_id))) t where rownum <= 10;
If you want to consume the results in Java, the second option ("as a cursor") works well. You can select the CURSOR column as an Object and then cast it to a ResultSet to iterate over and fetch the contents.
You can create a wrapper function if you cannot modify TEST_PKG.GET_VAL function. This wrapper function will return a pl/sql sys_refcursor which is less hassle for java (look for articles about how to access sys_refcursor from java).
The wrapper function code is below:
create or replace function fn_get_val_wrapper(v_id in number) return SYS_REFCURSOR as
v_ret_cur SYS_REFCURSOR;
begin
open v_ret_cur for
select tgv.column_value as val from table(test_pkg.get_val(v_id)) tgv;
return v_ret_val;
end;
/
I have a search query that is able to sort results by relevance according to how many of the words from the query actually show up.
SELECT id,
thesis
FROM activity p
WHERE p.discriminator = 'opinion'
AND ( thesis LIKE '%gun%'
OR thesis LIKE '%crucial%' )
ORDER BY ( ( CASE
WHEN thesis LIKE '%gun%' THEN 1
ELSE 0
end )
+ ( CASE
WHEN thesis LIKE '%crucial%' THEN 1
ELSE 0
end ) )
DESC
This query however, does not sort according to how many times 'gun' or 'crucial' show up. I want to make it so records with more occurrences of 'gun' show up above records with less occurrences. (I.E, add a point for every time gun shows up rather than adding a point because gun shows up at least once)
I might be wrong but without use of stored procedures or UDF You won't be able to count string occurrences. Here's sample stored function that counts substrings:
drop function if exists str_count;
delimiter |
create function str_count(sub varchar(255), str varchar(255)) RETURNS INTEGER
DETERMINISTIC NO SQL
BEGIN
DECLARE count INT;
DECLARE cur INT;
SET count = 0;
SET cur = 0;
REPEAT
SET cur = LOCATE(sub, str, cur+1);
SET count = count + (cur > 0);
UNTIL (cur = 0)
END REPEAT;
RETURN(count);
END|
You might want to change varchar(255) to varchar(65536) or TEXT. You can now use it in order by query:
SELECT id,
thesis
FROM activity p
WHERE p.discriminator = 'opinion'
AND ( thesis LIKE '%gun%'
OR thesis LIKE '%crucial%' )
ORDER BY STR_COUNT('gun',thesis) + STR_COUNT('crucial', thesis)
If Your dataset is large and performance is important for You I suggest to write custom UDF in C.
Depending on how your database is set up, you may find MySQL's full text indexing to be a better fit for your use case. It allows you to index fields and search for words in them, ordering the results by relevance related to the number of occurrences.
See the documentation here: http://dev.mysql.com/doc/refman/5.0/en/fulltext-search.html
This is a useful question that gives some examples, and may help: How can I manipulate MySQL fulltext search relevance to make one field more 'valuable' than another?
Finally, if full text searches aren't an option for you, the comment posted by Andrew Hanna on the string functions reference may do the trick: http://dev.mysql.com/doc/refman/5.0/en/string-functions.html (search the page for "Andrew Hanna"). They create a function on the server which can count the number of times a string occurs.
Hope this helps.
I am using oracle 10g database.
Function is :
create or replace FUNCTION FUNC_FAAL(myCode number,firstDate date
, secondDate date)
RETURN INTEGER as
rtr integer;
BEGIN
select count(*) into rtr
from my_table tbl where tbl.myDateColumn between firstDate and
secondDate and tbl.kkct is null and tbl.myNumberColumn = myCode ;
return (rtr);
END FUNC_FAAL;
This function returns 117177 as result.
But if I run same query in the function seperately ;
select count(*)
from my_table tbl
where tbl.myDateColumn between firstDate and secondDate
and tbl.kkct is null and tbl.myNumberColumn = myCode ;
I get different result 11344 (which is the right one).
What can be the problem ?
Thanks.
You've obfuscated your code, and I suspect hidden the problem in the process.
I suspect your code is more like
create or replace FUNCTION FUNC_FAAL(myNumberColumn number,firstDate date
, secondDate date)
RETURN INTEGER as
rtr integer;
BEGIN
select count(*) into rtr
from my_table tbl where tbl.myDateColumn between firstDate and
secondDate and tbl.kkct is null and tbl.myNumberColumn = myNumberColumn ;
return (rtr);
END FUNC_FAAL;
where the parameter or local variable has the same name as the column in the table. In the SQL, the table column takes precedence and so the variable isn't used and the column is compared to itself, giving a greater number of matches.
It is best to prefix variables and parameters (eg v_ and p_) to avoid such problems.
the function could be in a schema which also has the table in question. it could be working with respect to that table. when you are running the query independently, you could be using a table in a different schema. this is one possibility.
if this is the case, specifying the table name as a fully qualified one (schema.table) should solve the problem.
I'd run TKPROF to see what SQL you are actually processing in the database, specifically to see how the date variables are being recognised.