I have a Postgres function which contains a select statement. I need to add a condition using a passed in variable containing an array of string values.
CREATE OR REPLACE FUNCTION get_questions(vcode text)
RETURN return_value as $f$
DECLARE vresult return_value;
BEGIN
--snip--
SELECT id, title, code
FROM questions WHERE code NOT IN (vcode);
--snip--
questions table:
id ,title, code
1, "title1", "qcode1"
2, "title2", "qcode2"
3, "title3", "qcode3"
4, "title4", "qcode4"
How should the vcode literal be formatted in PHP and what should be the syntax of the condition?
Using PostgreSQL 9.1.1, PHP 5.3.6, pg_query_params.
SQL NOT IN works with sets. Since you are passing an array, use <> ALL.
You have to be careful not to involve any NULL values with such an expression, because NULL <> anything never evaluates to TRUE and therefore never qualifies in a WHERE clause.
Your function could look like this:
CREATE OR REPLACE FUNCTION get_questions(vcode text[])
RETURNS TABLE(id int, title text, code text)
LANGUAGE sql AS
$func$
SELECT q.id, q.title, q.code
FROM questions q
WHERE q.code <> ALL ($1);
$func$;
Call with array literal:
SELECT * FROM get_questions('{qcode2, qcode2}');
Or with an array constructor):
SELECT * FROM get_questions(ARRAY['qcode2', 'qcode2']);
Or you could use a VARIADIC parameter:
CREATE OR REPLACE FUNCTION get_questions(VARIADIC vcode text[]) ...
... and pass a list of values:
SELECT * FROM get_questions('qcode2', 'qcode2');
Details:
Return rows matching elements of input array in plpgsql function
Major points:
Using a simple SQL function since there is nothing in your question that would require the procedural elements of PL/pgSQL.
The input parameter is an array of text: text[]
To return multiple rows from your query use RETURNS TABLE for the return type.
Referring to the in parameter with the positional parameter $1 since referring by name was only introduced with version 9.2 for SQL functions (as opposed to plpgsql functions where this has been around for some versions now).
Table-qualify column names that would otherwise conflict with OUT parameters of the same name defined in the RETURNS clause.
LEFT JOIN unnest($1) / IS NULL
Faster for long arrays (> ~ 80 elements, it depends):
SELECT q.id, q.title, q.code
FROM questions q
LEFT JOIN unnest($1) c(code) USING (code)
WHERE c.code IS NULL;
This variant (as opposed to the above) ignores NULL values in the input array.
Related
I execute SQL query with Yii2 DAO.
$db->createCommand("
DO $$
DECLARE
rec RECORD;
pos INT := 0;
BEGIN
FOR rec IN (SELECT * FROM table1 WHERE "type" = :t LOOP
UPDATE table1 SET position = pos WHERE id = rec.id;
pos := pos + 2;
END LOOP;
END;
$$ language 'plpgsql'
", [':t' => 0])->execute();
But it fails with error:
SQLSTATE[42P18]: Indeterminate datatype: 7
ERROR: could not determine data type of parameter $1
type column has INT type. I tried to set param type explicitly with [':t' => [0, \PDO::PARAM_INT]]. But the error is still here. If I concatenate the value right into the SQL string, it works, but that's not a solution. :t is only one parameter in this query.
Other simple SQL queries work successfully. This problem exists only for queries with procedures. If I run this query from the DataGrip, it works. But in PHP it fails.
Why does it not work and how can I bind params for such queries?
If you create an actual function and call it, you can pass values as parameters.
But while you execute a DO statement, the function body (enclosed in dollar-quotes $$ in your example) is just a string literal. No parameter passing and nothing returned. You would have to concatenate values as strings into the plpgsql code.
But you don't need either. Use a simple prepared statement instead. Much cheaper than looping anyway:
UPDATE table1 t
SET position = t1.pos
FROM (
SELECT id, (row_number() OVER () - 1) * 2 AS pos
FROM table1
WHERE "type" = :t
) t1
WHERE t.id = t1.id
It's trivial to pass a parameter value now.
Aside: The result is arbitrary, unless you add ORDER BY to the OVER clause. That weakness is in your original, too.
Related:
What are '$$' used for in PL/pgSQL
I have a stored procedure which takes in a single String parameter - the value passed into this parameter is a comma separated list of ID's from PHP - something like 2,3,4,5
`DECLARE tags_in VARCHAR(255);`
Within the Stored procedure I would like to select the rows which have ids corresponding to the ids in the parameter - the query would be like
`SELECT * from tags WHERE tag_id IN (tags_in)`
I pass in the values from PHP to MySQL using the following statement binding the value as a string
`$stmt->bindParam(':tags', '2,3,4', PDO::PARAM_STR);`
Problem - the actual query being executed by MySQL is as below - where the parameters passed in are considered as one string
`SELECT * from tags WHERE tag_id IN ('2,3,4')`
When the query I want executed is as below where the parameters are considered as individual integers
`SELECT * from tags WHERE tag_id IN (2,3,4)`
Any suggestions on I can accomplish this?
SQL placeholders can represent only SINGLE values. If you pass in some comma separated values, they won't be seen as multiple individual values with commas, they'll just be treated like a monolithic string.
e.g.
... WHERE foo IN (:bar)
... WHERE foo = :bar
are functionally identical as far as the SQL parser are concerned, and it won't make allowances for passing in your CSV values. Both will execute the same way:
... WHERE foo IN ('1,2,3')
... WHERE foo = '1,2,3'
You'll either have to limit yourself to only as many values as you have placeholders, or dynamically build your SQL and put in a placeholder for each individual value you're trying to put into the IN clause.
e.g.
$placeholders = array_fill(0, count($values_to_check) -1, '?');
$in_clause = implode(',', $placeholders);
/// builds ?,?,?,?,?,....?
$sql = "SELECT ... WHERE foo IN ($in_clause)";
$stmt = $dbh->prepare($sql);
$stmt->execute($values_to_check);
This is one place where prepared statements fall flat on their faces, and you have to fall back to good old "build some sql dynamically".
There is sometimes another way to accomplish the desired result by casting the integer you're trying to compare as a string surrounded by commas and checking if the result is contained in your list of possible values (with added commas on either side as well). It's not the most efficient for performance maybe, but it allows you to do what you want in a single procedure or query.
For example (in your case) something like this might work:
SELECT * from tags WHERE INSTR (CONCAT(',', tags_in, ','), CONCAT(',', tag_id, ',') );
MySql is a little bit weird in that it does the conversion from int to char within the CONCAT function, some other databases require explicit casting.
I am using PostgreSQL 9.1.11.
I need to return result of SELECT to my php script. The invocation in php is like this:
$res = $pdb->getAssoc("SELECT * FROM my_profile();");
The class code to illustrate what is going on in php
public function getAssoc($in_query) {
$res = pg_query($this->_Link, $in_query);
if($res == FALSE) {
return array("dberror", iconv("utf-8", "windows-1251", pg_last_error($this->_Link)));
}
return pg_fetch_all($res);
}
Next comes my function in Postgres. I fully re-create database by dropping in a script when I update any function. (The project is in the early stage of development.) I have little to no experience doing stored procedures.
I get this error:
structure of query does not match function result type
CONTEXT: PL/pgSQL function "my_profile" line 3 at RETURN QUERY )
Trying to write:
CREATE FUNCTION my_profile()
RETURNS TABLE (_nick text, _email text) AS $$
BEGIN
RETURN QUERY SELECT (nick, email) FROM my_users WHERE id = 1;
END;
$$
LANGUAGE 'plpgsql' SECURITY DEFINER;
Table structure is:
CREATE TABLE my_users(
id integer NOT NULL,
nick text,
email text,
pwd_salt varchar(32),
pwd_hash character(128),
CONSTRAINT users_pk PRIMARY KEY (id)
);
When I return 1 column in a table the query works. Tried to rewrite procedure in LANGUAGE sql instead of plpgsql with some success, but I want to stick to plpgsql.
The Postgres 9.1.11, php-fpm I am using is latest for fully updated amd64 Debian wheezy.
What I want to do is to return a recordset containing from 0 to n rows from proc to php in an associative array.
This part is incorrect:
RETURN QUERY SELECT (nick, email) FROM my_users WHERE id = 1;
You should remove the parentheses around nick,email otherwise they form a unique column with a ROW type.
This is why it doesn't match the result type.
#Daniel already pointed out your immediate problem (incorrect use of parentheses). But there is more:
Never quote the language name plpgsql in this context. It's an identifier, not a string literal. It's tolerated for now since it's a wide-spread anti-pattern. But it may be considered a syntax error in future releases.
The SECURITY DEFINER clause should be accompanied by a local setting for search_path. Be sure to read the according chapter in the manual.
Everything put together, it could look like this:
CREATE FUNCTION my_profile()
RETURNS TABLE (nick text, email text) AS
$func$
BEGIN
RETURN QUERY
SELECT m.nick, m.email FROM my_users m WHERE m.id = 1;
END
$func$
LANGUAGE plpgsql SECURITY DEFINER SET search_path = public, pg_temp;
Replace public whit the actual schema of your table.
To avoid possible naming conflicts between OUT parameters in RETURNS TABLE ... and table columns in the SELECT statement I table-qualified column names with the given alias m.
Hopefully I'm going about this the right way, if not I'm more than open to learning how this could be done better.
I need to pass a comma separated list of integers (always positive integers, no decimals) to a stored procedure. The stored procedure would then use the integers in an IN operator of the WHERE clause:
WHERE [PrimaryKey] IN (1,2,4,6,212);
The front-end is PHP and connection is made via ODBC, I've tried wrapping the parameter in single quotes and filtering them out in the stored procedure before the list gets to the query but that doesn't seem to work.
The error I'm getting is:
Conversion failed when converting the varchar value '1,2,4,6,212' to data type int.
I've never done this before and research so far has yielded no positive results.
Firstly, let's use a SQL Function to perform the split of the delimited data:
CREATE FUNCTION dbo.Split
(
#RowData nvarchar(2000),
#SplitOn nvarchar(5)
)
RETURNS #RtnValue table
(
Id int identity(1,1),
Data nvarchar(100)
)
AS
BEGIN
Declare #Cnt int
Set #Cnt = 1
While (Charindex(#SplitOn,#RowData)>0)
Begin
Insert Into #RtnValue (data)
Select
Data = ltrim(rtrim(Substring(#RowData,1,Charindex(#SplitOn,#RowData)-1)))
Set #RowData = Substring(#RowData,Charindex(#SplitOn,#RowData)+1,len(#RowData))
Set #Cnt = #Cnt + 1
End
Insert Into #RtnValue (data)
Select Data = ltrim(rtrim(#RowData))
Return
END
To use this, you would simply pass the function the delimited string as well as the delimiter, like this:
SELECT
*
FROM
TableName
WHERE
ColumnName IN (SELECT Data FROM dbo.Split(#DelimitedData, ','))
If you still have issues, due to the datatype, try:
SELECT
*
FROM
TableName
WHERE
ColumnName IN (SELECT CONVERT(int,Data) FROM dbo.Split(#DelimitedData, ','))
You can pass a comma separate list of values. However, you cannot use them as you like in an in statement. You can do something like this instead:
where ','+#List+',' like '%,'+PrimaryKey+',%'
That is, you like to see if the value is present. I'm using SQL Server syntax for concatenation because the question is tagged Microsoft.
I have a custom type
CREATE TYPE mytype as (id uuid, amount numeric(13,4));
I want to pass it to a function with the following signature:
CREATE FUNCTION myschema.myfunction(id uuid, mytypes mytype[])
RETURNS BOOLEAN AS...
How can I call this in postgres query and inevitably from PHP?
You can use the alternative syntax with a array literal instead of the array constructor, which is a Postgres function-like construct and may cause trouble when you need to pass values - like in a prepared statement:
SELECT myschema.myfunc('0d6311cc-0d74-4a32-8cf9-87835651e1ee'
, '{"(0d6311cc-0d74-4a32-8cf9-87835651e1ee, 25)"
, "(6449fb3b-844e-440e-8973-31eb6bbefc81, 10)"}'::mytype[]);
I added a line break between the two row types in the array for display. That's legal.
How to find the correct syntax for any literal?
Just ask Postgres. Here is a demo:
CREATE TABLE mytype (id uuid, amount numeric(13,4));
INSERT INTO mytype VALUES
('0d6311cc-0d74-4a32-8cf9-87835651e1ee', 25)
,('6449fb3b-844e-440e-8973-31eb6bbefc81', 10);
SELECT ARRAY(SELECT m FROM mytype m);
Returns:
{"(0d6311cc-0d74-4a32-8cf9-87835651e1ee,25.0000)","(6449fb3b-844e-440e-8973-31eb6bbefc81,10.0000)"}
db<>fiddle here
Any table (including temporary tables) implicitly creates a row type of the same name.
select myschema.myfunc('0d6311cc-0d74-4a32-8cf9-87835651e1ee'
, ARRAY[('ac747f0e-93d4-43a9-bc5b-09df06593239', '25.00')
, ('6449fb3b-844e-440e-8973-31eb6bbefc81', '10.00')]::mytype[]
);
Still need PHP portion of this resolved though, still not sure how to call a function populating with the custom array parameter.