MySQL function with limit - mysql

I am trying to write a MySQL function with input variable like this
CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
RETURN (
# Write your MySQL query statement below.
SELECT Salary FROM (SELECT * FROM Employee
ORDER BY Salary DESC LIMIT N-1, 1) AS tmp
);
END
And it reports error. However, the following code works
CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
RETURN (
# Write your MySQL query statement below.
SELECT Salary FROM (SELECT * FROM Employee
ORDER BY Salary DESC LIMIT N, 1) AS tmp
);
END
Is there any way to achieve the goal that is to select the Nth highest salary with similar code? Thank you. (I am only allowed to change the content in the 'return' parentheses).

You can write something like this.
CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
RETURN (
SELECT Salary FROM Employee e1
WHERE N-1 = (SELECT COUNT(DISTINCT salary) FROM Employee e2
WHERE e2.salary > e1.salary)
);
END

Related

Fetch all employees if not employee selected from UI else select specific employee data

I want to select all employees if no employee is selected else need data for selected employee.
I have written this query but it is throwing error.
DECLARE #emp_id INT
SET #emp_id=0 -- This will change based on the selected employee from UI
SELECT * FROM employees
WHERE employee_id in (CASE WHEN #emp_id=0 THEN (SELECT employee_id FROM employees) ELSE #emp_id END)
Sample Script for reproducing issue
CREATE TABLE employees (
employee_id INT ,
first_name VARCHAR (20) DEFAULT NULL,
last_name VARCHAR (25) NOT NULL
);
INSERT INTO employees values (1, '1_fname', '1_lname'),(2, '2_fname', '2_lname'),(3, '3_fname', '3_lname'),(4, '4_fname', '4_lname'),(5, '5_fname', '5_lname')
I want to this in single query.
Assuming that you're using prepared statements, you will need to provide the selected employee 2 times - as argument 1 and 2:
SELECT * FROM employees
WHERE CASE WHEN COALESCE(?,0) = 0 THEN TRUE ELSE id = ? END
For single query, we can INNER JOIN with same table to emulate whether employees should shown or not. Using this query will produce desired output.
select e.*
from employees e
inner join (
select employee_id,
case
when #emp_id = 0 then true -- will display all employees
when #emp_id = #emp_id then true -- only display selected employee
else false
end as is_shown
from employees
) filter on e.employee_id = filter.employee_id and filter.is_shown = 1;
Or using another approach
select employee_id, first_name, last_name
from (
select e.*,
case
when #emp_id = 0 then true
when #emp_id = employee_id then true
else false
end as is_shown
from employees e
) employees
where is_shown = 1;
Simple flow control inside stored procedure
delimiter $$
use `precise`$$
drop procedure if exists `GetEmployee`$$
create definer=`user`#`%` procedure `GetEmployee`(empID int)
begin
case when empID = 0 then
select *
from employees;
else
select *
from employees
where employee_id = empID;
end case
;
end$$
delimiter ;

store procedure not giving correct values

I am trying to find the number of employees with a specific salary range using store procedure, I am getting results but they are not equal to the results of the simple query (second query below) which means that the store procedure is not working properly?
I was hoping if someone can help me with this.
-- ANSWER : NULL /there is no salary amount less than 38000, however with the store procedure I am getting 967330
Secondly, is there any other way to shorten the case when statements within the store procedure ? in the current version I have to add subquery each time.
database name: employees
column name: salary
employee_id = emp_no
DELIMITER $$
CREATE PROCEDURE SALARY_RANGE
(INOUT no_employees INT, IN salary INT)
BEGIN
CASE
WHEN (salary <= 38000) THEN (SELECT COUNT(emp_no) INTO no_employees
FROM salaries
WHERE salary <= 38000);
WHEN (salary<= 68000) THEN (SELECT COUNT(emp_no) INTO no_employees
FROM salaries
WHERE salary <= 68000);
WHEN (salary <= 88000) THEN (SELECT COUNT(emp_no) INTO no_employees
FROM salaries
WHERE salary <= 88000);
WHEN (salary <= 100000) THEN (SELECT COUNT(emp_no) INTO no_employees
FROM salaries
WHERE salary <= 100000);
ELSE (SELECT COUNT(emp_no) INTO no_employees
FROM salaries WHERE salary > 100000);
END CASE;
END$$
CALL SALARY_RANGE(#finding_salary_range_count,'38000')$$
select #finding_salary_range_count;
-- to check the if the answer matched the above store procedure
select count(case when salary <= 88000 then emp_no else null end) as count_sal
from salaries;
I would expect code like this:
CREATE PROCEDURE SALARY_RANGE (
OUT out_num_employees INT,
IN in_salary INT
)
BEGIN
SELECT COUNT(emp_no) INTO out_num_employees
FROM salaries
WHERE salary <= in_salary or in_salary IS NULL
END;
I don't see a need for multiple SELECT statements.

Find an average age range over a span of ages

I need to find the most occurrences in a 10yr age range that can be Age 2 to 22, 15 to 25, 10 to 20, etc. in a table with name & age
I've created the SQL that returns the average age:
SELECT age, count(age)
FROM member
GROUP BY age
ORDER BY COUNT(age) DESC
LIMIT 1
Thanks for your help!
Create another table ages to hold the age ranges you are interested in with a field for age_lower, age_upper and a display name age_range such as '2 to 22'
Join the tables with a WHERE clause that puts the age between the lower and upper ranges.
SELECT `age_range`, COUNT(`age`) AS age_count
FROM `member` INNER JOIN `ages`
ON age BETWEEN age_lower AND age_upper
GROUP BY age_range
ORDER BY COUNT(`age`) DESC, `age_range` ASC
SQL Fiddle
This might solve the problem. The only thing I added was a table to hold values 1..x where x is your bucket count. The #T can easily be replaced with your MySQL table name. The results are all possible sets the age falls in, for each age. Then count of how many equal sets.
--IGNORE BUILDING TEST DATA IN SQL SERVER
DECLARE #T TABLE(member INT,age INT)
DECLARE #X INT
SET #X=1
WHILE(#X<=100) BEGIN
INSERT INTO #T SELECT #X, CAST(RAND() * 100 AS INT)
SET #X=#X+1
END
DECLARE #MinAge INT=1
DECLARE #MaxAge INT=100
--YOUR SET TABLE. TO MAKE LIFE EASY YOU NEED A TABLE OF 1..X
DECLARE #SET TABLE (Value INT)
DECLARE #SET_COUNT INT =10
DECLARE #LOOP INT=1
WHILE(#LOOP<=#SET_COUNT) BEGIN
INSERT #SET SELECT #LOOP
SET #LOOP=#LOOP+1
END
SELECT
MinAge,
MaxAge,
SetCount=COUNT(CountFlag)
FROM
(
SELECT
MinAge=AgeMinusSetCount,
MaxAge=AgePlusSetCount,
CountFlag=1
FROM
(
SELECT DISTINCT
ThisAge,
AgeMinusSetCount=(AgeMinusSetCount-1) + Value,
AgePlusSetCount=CASE WHEN (AgeMinusSetCount-1) + Value + #SET_COUNT > #MaxAge THEN #MaxAge ELSE (AgeMinusSetCount-1) + Value + #SET_COUNT END
FROM
(
SELECT
ThisAge=age,
AgeMinusSetCount=CASE WHEN (age - #SET_COUNT) < #MinAge THEN #MinAge ELSE (age) - #SET_COUNT END
FROM
#T
)RANGES
LEFT OUTER JOIN (SELECT Value FROM #SET) AS FanLeft ON 1=1
)AS DETAIL
)AS Summary
GROUP BY
MinAge,
MaxAge
ORDER BY
COUNT(CountFlag) DESC

Table function throwing ERROR: there is no parameter $1

I have the following query which I'd like to turn into a function. I have written the SQL for 1 company_id = 26304
SELECT t.* FROM crosstab(
$$
select company_id, row_number() OVER (ORDER BY year DESC) AS rn2, id
from
(select i.company_id, year, f.id, f.created_at,
row_number() OVER (PARTITION BY year
ORDER BY year DESC, f.created_at DESC NULLS LAST) AS rn
from
public.interactions i
inner join public.financials f on f.interaction_id = i.id
where company_id = 26304
) t1
where rn= 1 limit 3
$$
) AS t (company_id int, financial_id_1 int, financial_id_2 int, financial_id_3 int);
This SQL statement pivots my dataset and returns the following as expected:
company_id financial_id_1 financial_id_2 financial_id_3
26304 6796 6795 6786
However, when I try to turn this into a table function it throws the following error:
CREATE FUNCTION public.returnfinancials (int4) RETURNS TABLE (company_id int, financial_id_1 int, financial_id_2 int, financial_id_3 int)
as
$$
SELECT t.* FROM crosstab(
'
select company_id, row_number() OVER (ORDER BY year DESC) AS rn2, id
from
(select i.company_id, year, f.id, f.created_at,
row_number() OVER (PARTITION BY year ORDER BY year DESC, f.created_at DESC NULLS LAST) AS rn
from
public.interactions i
inner join public.financials f on f.interaction_id = i.id
where company_id = $1
) t1
where rn= 1 limit 3
'
) AS t (company_id int, financial_id_1 int, financial_id_2 int, financial_id_3 int);
$$
LANGUAGE 'sql'
Call:
select * from returnfinancials(23)
Throws:
ERROR: there is no parameter $1
Where: SQL function "returnfinancials" statement 1
Line: 1
The issue is that $1 is within a text string. It's interpreted by crosstab as a query, but within the context of the crosstab function call the parameters to the calling function are not visible. As crosstab its self passes no parameters to the query when it executes it, you get an error.
To solve this you should substitute the parameter value into the query string you pass to crosstab.
In your specific case it's OK to just substitute directly because the parameter is an integer:
'.... where company_id = '||$1||' .... '
but in general you should be careful about SQL injection and it's cleanest and safest to consistently quote your parameters. Note that '11' is a valid integer literal in SQL; it's always legal to quote an identifier, it's just optional for numbers.
So instead, even for numbers, I suggest that you use:
'.... where company_id = '||quote_literal($1)||' ....'
or use the format function to construct the string:
format('.... where company_id = %L ....', $1)
That way if someone later changes company_id to a non-numeric type you don't get a pretty SQL injection hole.
Try replace
where company_id = $1
by
where company_id = '||$1||'

Procedure Limit

I need help please! I tried searching for answers but didn't find anything.
I am using MYSQL6.
What I did is create 2 views and I want to limit these views to get a specific amount of results that is calculated through a function.
FUNCTION:
DELIMITER $$
CREATE FUNCTION `get_total_customers`() RETURNS int(11)
BEGIN
DECLARE TotalCustomers INT;
SELECT (count(customer_id)*0.2) INTO TotalCustomers FROM customer;
RETURN TotalCustomers;
END
VIEW1:
CREATE VIEW PreferredStatus AS
SELECT customer_id, SUM(amount) AS TotalAmount, get_total_customers() AS Total FROM payment
GROUP BY customer_id
ORDER BY TotalAmount DESC;
VIEW2:
CREATE VIEW RegularStatus AS
SELECT customer_id, SUM(amount) AS TotalAmount, get_total_customers() AS Total FROM payment
GROUP BY customer_id
ORDER BY TotalAmount ASC
LIMIT Total;
This is the procedure that would use the view.
> DELIMITER $$
CREATE PROCEDURE `CustomerStatus`(in custID int, out status varchar(200))
BEGIN
declare number1 int;
declare totalvar int;
declare limit2 int;
IF custID in (SELECT customer_id FROM PreferredStatus) THEN
set status = "preferred";
ELSEIF custID in (SELECT customer_id FROM RegularStatus) THEN
set status = "casual";
ELSE
set status = "regular";
End If;
END
HELP PLEASE!
-Thanks.
You can add LIMIT to the SELECT that creates the VIEW:
CREATE VIEW PreferredStatus AS
SELECT customer_id,
SUM(amount) AS TotalAmount,
get_total_customers() AS Total
FROM payment
GROUP BY customer_id
ORDER BY TotalAmount DESC
LIMIT 20;
sqlfiddle demo
A warning from the docs:
If a view definition includes a LIMIT clause, and you select from the
view using a statement that has its own LIMIT clause, it is undefined
which limit applies.