Table function throwing ERROR: there is no parameter $1 - function

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||'

Related

Unable to resolve symbol 'X' when converting MySQL DDL to Oracle SQL DDL

I am having below query which is working well until i take it to Oracle SQL
SELECT
X.*
FROM
(
SELECT
BusID,
BusName,
CurrentSpeed,
PassengersNo,
SpeedLimit,
dataDateTime,
ROW_NUMBER() OVER (
PARTITION BY CAST(dataDateTime AS DATE), BusId
ORDER BY CurrentSpeed DESC
) AS RowNumOrder
FROM
BUS_DATA
WHERE
busId = '4-3323309834'
) AS X
WHERE
X.RowNumOrder = 1
When i take to oracle sql i get the error
Unable to resolve symbol 'X'.
Below is what i have tried but the query is just running without returning any results.
SELECT *
FROM
(
SELECT
BusID
, BusName
, CurrentSpeed
, PassengersNo
, SpeedLimit
, dataDateTime
, ROW_NUMBER() OVER (PARTITION BY CAST(dataDateTime AS DATE), BusId ORDER BY CurrentSpeed DESC) AS RowNumOrder
FROM BUS_DATA
WHERE busId = '4-3323309834'
) WHERE RowNumOrder = 1
Below is what i tried according to user5683823 answer but its just asking me to run the queries in two seperate ways
SELECT X.*
FROM
(
SELECT
BusID
, BusName
, CurrentSpeed
,PassengersNo
, SpeedLimit
, dataDateTime
, ROW_NUMBER() OVER (PARTITION BY CAST(dataDateTime AS DATE), BusId ORDER BY CurrentSpeed DESC) RowNumOrder
FROM BUS_DATA
WHERE busId = '4-3323309834'
) X
WHERE X.RowNumOrder = 1
Below is how i have edited the query according to how Rick James is suggesting but i am getting Unable to resolve column 'RowNumOrder' error
SELECT
BusID
, BusName
, CurrentSpeed
,PassengersNo
, SpeedLimit
, dataDateTime
, ROW_NUMBER() OVER (PARTITION BY CAST(dataDateTime AS DATE), BusId ORDER BY CurrentSpeed DESC) RowNumOrder
FROM BUS_DATA
WHERE busId = '4-3323309834' HAVING X.RowNumOrder = 1
The original question i asked was here but the solutions provided there were not working for oracle
First, in Oracle SQL, you can give an alias to a subquery (like you were doing in MySQL), but you are not allowed to use the keyword AS - you must remove that word between the subquery (the closing parenthesis) and the alias X. In Oracle SQL, the keyword AS is permitted (and optional!) only before column aliases, but not before aliases for table expressions (tables listed in from clause, subqueries, table function invocations, etc.)
Incidentally, you are using the term DDL incorrectly; no matter what the SQL dialect, a select statement is not DDL, it is a limited form of DML. The distinction is crucial: executing a select statement doesn't implicitly commit earlier DML statements.
For the second query, if you expect results but none are returned, are you sure the data exists in the BUS_DATA table? You can find out, for example, by running a simple query, like select * from bus_data where rownum = 1. If data is missing, perhaps it was inserted in a different session (perhaps even by you, but from a different connection), and has not been committed yet? Otherwise see if there is any data for that specific business id: add and busid = ... to my very simple query, earlier in this paragraph.

Is there any way to have a default integer passed inside a stored procedure?

For my homework assignment, the stored procedure should accept an optional integer between 1 and 15, but default to 3 if no value is passed.
DELIMITER //
CREATE OR REPLACE PROCEDURE rankVideos(rank INT)
BEGIN
if rank = null then
SET rank = 3;
END if;
CREATE OR REPLACE TEMPORARY TABLE all_ranks AS (
SELECT * FROM youtube.homework7a
);
create OR REPLACE TEMPORARY TABLE t2 AS ( SELECT
category,
row_number() OVER (ORDER BY cnt DESC) v_cnt,
row_number() OVER (ORDER BY views DESC) v_views,
row_number() OVER (ORDER BY likes DESC) v_likes,
row_number() OVER (ORDER BY dislikes DESC) v_dislikes,
row_number() OVER (ORDER BY comment_count DESC) v_comment_count FROM all_ranks
);
CREATE OR REPLACE TEMPORARY TABLE t3 AS (
SELECT * FROM t2
WHERE v_cnt <= rank OR v_views <= rank OR v_likes <= rank
OR v_dislikes < rank OR v_comment_count <= rank
);
CREATE OR replace TEMPORARY TABLE t4 AS (
SELECT category,
case when v_cnt <= rank then v_cnt ELSE null END cnt,
case when v_views <= rank then v_views ELSE null END views,
case when v_likes <= rank then v_likes ELSE null END likes,
case when v_dislikes <= rank then v_dislikes ELSE null END dislikes,
case when v_comment_count <= rank then v_comment_count ELSE null END comment_count
FROM t3
)
;
SELECT *,
ifnull(cnt,999)
+ ifnull(views,999)
+ ifnull(likes,999)
+ ifnull(dislikes,999)
+ifnull(comment_count,999) num_non_null_cols,
ifnull(cnt,0)
+ ifnull(views,0)
+ ifnull(likes,0)
+ ifnull(dislikes,0)
+ ifnull(comment_count,0) sum_non_null_cols
FROM t4
ORDER BY num_non_null_cols, sum_non_null_cols;
END
//
DELIMITER ;
When I run the procedure and leave the integer blank I get an error that it has an incorrect integer value.
The syntax you show makes me think you are using MySQL or MariaDB.
These implementations don't support a feature for default values for procedure parameters. This has been requested in MySQL: https://bugs.mysql.com/bug.php?id=15975 But so far, it is not supported.
You're using the best workaround I know of, to set the parameter to your default value if it is NULL.
Another way of coding this is to use the COALESCE() function:
SET rank = COALESCE(rank, 3);
It's just another way to achieve the same thing that your IF/THEN code does.
SQL
MSSQL
create proc MyProc
#rank int = 3
as
...
GO
If you pass in a value, it will use that value. If you don't pass in a value, #rank = 3.

How to Join the subquery using JSON document within MySQL?

The Mysql version is 8.0.18-commercial. The primary key of the table is id.
I have written the following query which displays hostname and details columns
select hostname, details from table t1;
hostname: abc123
details:
[
{
"Msg": "Job Running",
"currentTask": "IN_PROGRESS",
"activityDate": "2020-07-20 16:25:15"
},
{
"Msg": "Job failed",
"currentTask": "IN_PROGRESS",
"activityDate": "2020-07-20 16:35:24"
}
]
I want the Msg value only from the element having most recent activityDate
My desired output is displaying hostname alongwith Msg of the element with latest date :
hostname Msg
abc123 Job failed
I have written following query and it is running successfully but not displaying anything at all. Morever, it is taking 17secs to execute.
select hostname,
(select Msg
from (
select x.*, row_number() over(partition by t.id order by x.activityDate) rn
from table1 t
cross join json_table(
t.audits,
'$[*]' columns(
Msg varchar(50) path '$.Msg',
activityDate datetime path '$.activityDate'
)
) x
) t
where rn = 1) AS Msg
from table1;
You need to fix the JSON's format by removing the commas at the end
of the lines starting with "activityDate" keys
A conversion function such as STR_TO_DATE() should be applied to
the derived activityDate columns in order to get date ordered(not
characterwise) results.
A subquery is not needed through putting ROW_NUMBER() analytic
function next to the ORDER BY Clause( with descending order ), and adding a LIMIT 1 Clause
at the end of the query
So, you can rewrite the query as
SELECT t1.hostname,
j.Msg
FROM t1
CROSS JOIN
JSON_TABLE(details, '$[*]'
COLUMNS (
Msg VARCHAR(100) PATH '$.Msg',
activityDate VARCHAR(100) PATH '$.activityDate'
)
) j
ORDER BY ROW_NUMBER()
OVER ( -- PARTITION BY id
ORDER BY STR_TO_DATE(j.activityDate, '%Y-%m-%d %H:%i:%S') DESC)
LIMIT 1
Demo
Update :
For the case of having several id values, you may consider using the ROW_NUMBER() function within a subquery and filter out the values returning equal to 1 in the main query :
SELECT id, Msg
FROM
(
SELECT t1.*, j.Msg,
ROW_NUMBER()
OVER (PARTITION BY id
ORDER BY STR_TO_DATE(j.activityDate, '%Y-%m-%d %H:%i:%S') DESC) AS rn
FROM t1
CROSS JOIN
JSON_TABLE(details, '$[*]'
COLUMNS (
Msg VARCHAR(100) PATH '$.Msg',
activityDate VARCHAR(100) PATH '$.activityDate'
)
) j
) q
WHERE rn= 1
Demo
One another method uses ROW_NUMBER() function together with LIMIT clause contains Correlated Subquery, and works for records with multiple id values :
SELECT t.id,
( SELECT j.Msg
FROM t1
CROSS JOIN
JSON_TABLE(details, '$[*]'
COLUMNS (
Msg VARCHAR(100) PATH '$.Msg',
activityDate VARCHAR(100) PATH '$.activityDate'
)
) j
WHERE t1.id = t.id
ORDER BY ROW_NUMBER()
OVER (ORDER BY STR_TO_DATE(j.activityDate, '%Y-%m-%d %H:%i:%S') DESC)
LIMIT 1 ) AS Msg
FROM t1 AS t
Demo
Maybe I'm old school, but the date field should be stored as a separate field either in additional to the JSON, to allow for easy queries.
Is the ID auto increment, and is the data inserted in timestamp order? If yes, then you can run a query like this to give you the last row for each hostname:
SELECT id, hostname, details
FROM table t1
WHERE NOT EXISTS (SELECT 1 FROM table t2 WHERE t2.hostname = t1.hostname AND t2.id > t1.id) ;

MySQL function with limit

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

Mysql Stored procedure returns some weird results

When I run the following select Query in SQl
SELECT Count(*)
FROM workordercurrent
WHERE office_id = 1
AND ( ( scheduleddate = '2018-11-01' )
OR ( schedulestopdate = '2018-11-01' )
OR ( scheduleddate = '0000-00-00'
AND orderdate = '2018-11-01' ) )
AND worktype <> 6
The query returns 694 as count which is right
When I write the same Query in the SQL Procedure with 2 input parameters
office_id(int) and order_date (DATE)
BEGIN
SELECT Count(*)
FROM workordercurrent
WHERE office_id = office_id
AND ( ( scheduleddate = order_date )
OR ( schedulestopdate = order_date )
OR ( scheduleddate = '0000-00-00'
AND orderdate = order_date ) )
AND worktype <> 6;
END
It returns the count as 3260
What is the problem here as both queries exactly same. Here is how I am running the Stored procedure
You should avoid using Stored procedure's parameter name same as the columns/aliases used in your SP. WHERE office_id = office_id is behaving weird due to ambiguous name. MySQL is probably not able to resolve it as either a column name or parameter.
I normally prefix in_ or out_ or inout_ to param names; which also shows the type of param (for readability).
So you can rename the parameters to in_office_id and in_order_date instead.