Need Assistance with a Postgres Trigger and Function - function

I have a lookup table that holds a column of sources (from various hardcoded campaigns captured through a webservice API I created) and the respective brands that should be associated with them. This is so I can give a brand to records where brand is null - so that they can be welcomed with a certain template through a marketing automation tool.
I'm eventually deprecating this API and replacing it with one where brand is required, but in the meantime I have to craft a temporary solution until I give all my brand teams time to change their API calls.
I wrote this function:
CREATE OR REPLACE FUNCTION public.brand_lookup(IN i_brand TEXT )
RETURNS SETOF RECORD VOLATILE AS
$$
BEGIN
RETURN QUERY
UPDATE subscriber
SET brand = (SELECT brand FROM brand_translation
WHERE source = subscriber.source);
END;
$$
LANGUAGE plpgsql;
And a trigger to fire the function when a record is inserted:
CREATE TRIGGER brand_translation
AFTER INSERT ON subscriber
FOR EACH ROW EXECUTE PROCEDURE public.brand_lookup();
But my trigger comes back with an error that "ERROR: function public.brand_lookup() does not exist" (but it created successfully)". Besides the fact that my trigger doesn't see my function, will that function do what I'm intending? I'm fairly noob at functions (as you can probably tell).

It might work like this:
CREATE OR REPLACE FUNCTION public.f_brand_lookup()
RETURNS trigger AS
$func$
BEGIN
SELECT INTO NEW.brand
bt.brand
FROM brand_translation bt
WHERE bt.source = NEW.source;
RETURN NEW;
END
$func$
LANGUAGE plpgsql;
CREATE TRIGGER brand_insert_before_lookup
BEFORE INSERT ON subscriber
FOR EACH ROW EXECUTE PROCEDURE public.f_brand_lookup();
There is just too much completely wrong with your example.
You need to start by studying the basics. As always, I suggest the very fine manual.
Start here and here.

Postres allows for overloading of functions depending on the input parameters-- so when you call public.brand_lookup() with no parameters, the function you created-- public.brand_lookup(text)-- is not found. I think you need to pass the necessary parameter in the call to the function, or remove the parameter in the function definition.

Related

Can I neatly skip all or part of a mySQL script (esp that contains creation of stored procedures)?

I have a mySQL script, that I have to execute, but for a bunch of useless reasons (and more importantly boring), I might want it to skip most of that script.
Let's say before each part of the script I want to check select okToRun from options returns true before running the script.
So my script looks something like this
DELIMITER $$
DROP PROCEDURE IF EXISTS procedure1$$
CREATE PROCEDURE procedure1()
BEGIN
IF (select okToRun from options) THEN
... -- do some stuff
END IF;
END$$
CALL procedure1()$$
DROP PROCEDURE IF EXISTS procedure2$$
CREATE PROCEDURE procedure2()
BEGIN
IF (select okToRun from options) THEN
... -- do some other stuff
END IF;
END$$
DROP PROCEDURE IF EXISTS procedure3$$
CREATE PROCEDURE procedure3()
BEGIN
IF (select okToRun from options) THEN
... -- do even more stuff!
END IF;
END$$
CALL procedure2()$$
CALL procedure3()$$
I hate repeated code, so how can I put the check right at the start of the script, rather than having to keep repeating myself?
I thought I'd be clever and wrap the whole thing in one stored procedure and do my if statement there... but you can't really create a stored procedure within another. And I need to keep the other stuff within their own stored procedures.
I also realise it'd be better to do the check before executing the script at all, but, well, I can't ;)
If the answer simply is that I can't do this, fair enough. But I want to be sure before repeating code like a big fat heathen.
The MySQL client script syntax does not support conditionals or loops or other programming logic.
The best idea I could come up with is to use prepared statements, but unfortunately CREATE PROCEDURE is not supported in this manner.
SET #do_create := true;
SET #create_proc1 := IF(#do_create, 'CREATE PROCEDURE ...', 'DO 1');
PREPARE stmt FROM #create_proc1
I got this error:
ERROR 1295 (HY000): This command is not supported in the prepared statement protocol yet
The list of commands supported in this way is documented here: http://dev.mysql.com/doc/refman/5.7/en/sql-syntax-prepared-statements.html
In MySQL 5.7, they introduced a new client shell called mysqlsh that is supposed to support a more featureful scripting environment, allowing you to write Python code and so on. I haven't tried this tool yet.
But you could do the same thing by writing your own script in Python or any other language you want. Then you can code the conditional logic to create procedures or whatever. That's your best bet.

Entity Framework stored procedure: Function Import to Complex Type, Error on Get Column Information

I am working on a project in VS2012 while using MySQL database and Entity Framework 5.0.0.
When I try to create a new Complex type from a stored procedure, I get an error when clicking "Get Column Information" button:
The selected stored procedure or function returns no columns.
My stored procedure code is as following:
DROP PROCEDURE IF EXISTS SearchAlgemeenSet;
Delimiter //
CREATE PROCEDURE SearchAlgemeenSet(IN in_searchQuery VARCHAR(255))
BEGIN
SELECT Blokken, Jaargang, Werk_Uren
FROM algemeensets
WHERE Blokken LIKE in_searchQuery
OR Jaargang LIKE in_searchQuery
OR Werk_Uren LIKE in_searchQuery;
END
//
Delimiter ;
I'm positive that it returns columns if the in_searchQuery parameter has a match.
In my research, I have found plenty solutions for Microsoft SQL database. But none of those solutions apply to MySQL database.
How to solve this?
I'm positive that it returns columns, if the in_searchQuery
parameter has a match.
Because you are not using any wild card for partial search, unless it finds an exact match, for in_searchQuery value, no rows will be returned. For partial match to happen, you need to use wild card symbol '%' with the in_searchQuery value.
Modified procedure, should be like the following:
DROP PROCEDURE IF EXISTS SearchAlgemeenSet;
Delimiter //
CREATE PROCEDURE SearchAlgemeenSet(IN in_searchQuery VARCHAR(255))
BEGIN
set #search_criteria := concat( '%', in_searchQuery, '%' );
SELECT Blokken, Jaargang, Werk_Uren
FROM algemeensets
WHERE Blokken LIKE #search_criteria
OR Jaargang LIKE #search_criteria
OR Werk_Uren LIKE #search_criteria;
END;
//
Delimiter ;
When no search criteria has any matches found, and empty set would be returned.
In your scripting language, you have to check, in advance, whether the procedure has returned any results or not. Based on that, you can show a message that no data found or you can perform other actions.

Table name as a PostgreSQL function parameter

I want to pass a table name as a parameter in a Postgres function. I tried this code:
CREATE OR REPLACE FUNCTION some_f(param character varying) RETURNS integer
AS $$
BEGIN
IF EXISTS (select * from quote_ident($1) where quote_ident($1).id=1) THEN
return 1;
END IF;
return 0;
END;
$$ LANGUAGE plpgsql;
select some_f('table_name');
And I got this:
ERROR: syntax error at or near "."
LINE 4: ...elect * from quote_ident($1) where quote_ident($1).id=1)...
^
********** Error **********
ERROR: syntax error at or near "."
And here is the error I got when changed to this select * from quote_ident($1) tab where tab.id=1:
ERROR: column tab.id does not exist
LINE 1: ...T EXISTS (select * from quote_ident($1) tab where tab.id...
Probably, quote_ident($1) works, because without the where quote_ident($1).id=1 part I get 1, which means something is selected. Why may the first quote_ident($1) work and the second one not at the same time? And how could this be solved?
Before you go there: for only few, known tables names, it's typically simpler to avoid dynamic SQL and spell out the few code variants in separate functions or in a CASE construct.
That said, what you are trying to achieve can be simplified and improved:
CREATE OR REPLACE FUNCTION some_f(_tbl regclass, OUT result integer)
LANGUAGE plpgsql AS
$func$
BEGIN
EXECUTE format('SELECT (EXISTS (SELECT FROM %s WHERE id = 1))::int', _tbl)
INTO result;
END
$func$;
Call with schema-qualified name (see below):
SELECT some_f('myschema.mytable'); -- would fail with quote_ident()
Or:
SELECT some_f('"my very uncommon table name"');
Major points
Use an OUT parameter to simplify the function. You can directly select the result of the dynamic SQL into it and be done. No need for additional variables and code.
EXISTS does exactly what you want. You get true if the row exists or false otherwise. There are various ways to do this, EXISTS is typically most efficient.
You seem to want an integer back, so I cast the boolean result from EXISTS to integer, which yields exactly what you had. I would return boolean instead.
I use the object identifier type regclass as input type for _tbl. That does everything quote_ident(_tbl) or format('%I', _tbl) would do, but better, because:
.. it prevents SQL injection just as well.
.. it fails immediately and more gracefully if the table name is invalid / does not exist / is invisible to the current user. (A regclass parameter is only applicable for existing tables.)
.. it works with schema-qualified table names, where a plain quote_ident(_tbl) or format(%I) would fail because they cannot resolve the ambiguity. You would have to pass and escape schema and table names separately.
It only works for existing tables, obviously.
I still use format(), because it simplifies the syntax (and to demonstrate how it's used), but with %s instead of %I. Typically, queries are more complex so format() helps more. For the simple example we could as well just concatenate:
EXECUTE 'SELECT (EXISTS (SELECT FROM ' || _tbl || ' WHERE id = 1))::int'
No need to table-qualify the id column while there is only a single table in the FROM list. No ambiguity possible in this example. (Dynamic) SQL commands inside EXECUTE have a separate scope, function variables or parameters are not visible there - as opposed to plain SQL commands in the function body.
Here's why you always escape user input for dynamic SQL properly:
db<>fiddle here demonstrating SQL injection
Old sqlfiddle
If at all possible, don't do this.
That's the answer—it's an anti-pattern. If the client knows the table it wants data from, then SELECT FROM ThatTable. If a database is designed in a way that this is required, it seems to be designed sub-optimally. If a data access layer needs to know whether a value exists in a table, it is easy to compose SQL in that code, and pushing this code into the database is not good.
To me this seems like installing a device inside an elevator where one can type in the number of the desired floor. After the Go button is pressed, it moves a mechanical hand over to the correct button for the desired floor and presses it. This introduces many potential issues.
Please note: there is no intention of mockery, here. My silly elevator example was *the very best device I could imagine* for succinctly pointing out issues with this technique. It adds a useless layer of indirection, moving table name choice from a caller space (using a robust and well-understood DSL, SQL) into a hybrid using obscure/bizarre server-side SQL code.
Such responsibility-splitting through movement of query construction logic into dynamic SQL makes the code harder to understand. It violates a standard and reliable convention (how a SQL query chooses what to select) in the name of custom code fraught with potential for error.
Here are detailed points on some of the potential problems with this approach:
Dynamic SQL offers the possibility of SQL injection that is hard to recognize in the front end code or the back end code alone (one must inspect them together to see this).
Stored procedures and functions can access resources that the SP/function owner has rights to but the caller doesn't. As far as I understand, without special care, then by default when you use code that produces dynamic SQL and runs it, the database executes the dynamic SQL under the rights of the caller. This means you either won't be able to use privileged objects at all, or you have to open them up to all clients, increasing the surface area of potential attack to privileged data. Setting the SP/function at creation time to always run as a particular user (in SQL Server, EXECUTE AS) may solve that problem, but makes things more complicated. This exacerbates the risk of SQL injection mentioned in the previous point, by making the dynamic SQL a very enticing attack vector.
When a developer must understand what the application code is doing in order to modify it or fix a bug, he'll find it very difficult to get the exact SQL query being executed. SQL profiler can be used, but this takes special privileges and can have negative performance effects on production systems. The executed query can be logged by the SP but this increases complexity for questionable benefit (requiring accommodating new tables, purging old data, etc.) and is quite non-obvious. In fact, some applications are architected such that the developer does not have database credentials, so it becomes almost impossible for him to actually see the query being submitted.
When an error occurs, such as when you try to select a table that doesn't exist, you'll get a message along the lines of "invalid object name" from the database. That will happen exactly the same whether you're composing the SQL in the back end or the database, but the difference is, some poor developer who's trying to troubleshoot the system has to spelunk one level deeper into yet another cave below the one where the problem exists, to dig into the wonder-procedure that Does It All to try to figure out what the problem is. Logs won't show "Error in GetWidget", it will show "Error in OneProcedureToRuleThemAllRunner". This abstraction will generally make a system worse.
An example in pseudo-C# of switching table names based on a parameter:
string sql = $"SELECT * FROM {EscapeSqlIdentifier(tableName)};"
results = connection.Execute(sql);
While this does not eliminate every possible issue imaginable, the flaws I outlined with the other technique are absent from this example.
Inside plpgsql code, The EXECUTE statement must be used for queries in which table names or columns come from variables. Also the IF EXISTS (<query>) construct is not allowed when query is dynamically generated.
Here's your function with both problems fixed:
CREATE OR REPLACE FUNCTION some_f(param character varying) RETURNS integer
AS $$
DECLARE
v int;
BEGIN
EXECUTE 'select 1 FROM ' || quote_ident(param) || ' WHERE '
|| quote_ident(param) || '.id = 1' INTO v;
IF v THEN return 1; ELSE return 0; END IF;
END;
$$ LANGUAGE plpgsql;
I know this is an old thread, but I ran across it recently when trying to solve the same problem - in my case, for some fairly complex scripts.
Turning the entire script into dynamic SQL is not ideal. It's tedious and error-prone work, and you lose the ability to parameterize: parameters must be interpolated into constants in the SQL, with bad consequences for performance and security.
Here's a simple trick that lets you keep the SQL intact if you only need to select from your table - use dynamic SQL to create a temporary view:
CREATE OR REPLACE FUNCTION some_f(_tbl varchar) returns integer
AS $$
BEGIN
drop view if exists myview;
execute format('create temporary view myview as select * from %s', _tbl);
-- now you can reference myview in the SQL
IF EXISTS (select * from myview where myview.id=1) THEN
return 1;
END IF;
return 0;
END;
$$ language plpgsql;
The first doesn't actually "work" in the sense that you mean, it works only in so far as it does not generate an error.
Try SELECT * FROM quote_ident('table_that_does_not_exist');, and you will see why your function returns 1: the select is returning a table with one column (named quote_ident) with one row (the variable $1 or in this particular case table_that_does_not_exist).
What you want to do will require dynamic SQL, which is actually the place that the quote_* functions are meant to be used.
If the question was to test if the table is empty or not (id=1), here is a simplified version of Erwin's stored proc :
CREATE OR REPLACE FUNCTION isEmpty(tableName text, OUT zeroIfEmpty integer) AS
$func$
BEGIN
EXECUTE format('SELECT COALESCE ((SELECT 1 FROM %s LIMIT 1),0)', tableName)
INTO zeroIfEmpty;
END
$func$ LANGUAGE plpgsql;
If you want table name, column name and value to be dynamically passed to function as parameter
use this code
create or replace function total_rows(tbl_name text, column_name text, value int)
returns integer as $total$
declare
total integer;
begin
EXECUTE format('select count(*) from %s WHERE %s = %s', tbl_name, column_name, value) INTO total;
return total;
end;
$total$ language plpgsql;
postgres=# select total_rows('tbl_name','column_name',2); --2 is the value
I have 9.4 version of PostgreSQL and I always use this code:
CREATE FUNCTION add_new_table(text) RETURNS void AS
$BODY$
begin
execute
'CREATE TABLE ' || $1 || '(
item_1 type,
item_2 type
)';
end;
$BODY$
LANGUAGE plpgsql
And then:
SELECT add_new_table('my_table_name');
It works good for me.
Attention! Above example is one of those which shows "How do not if we want to keep safety during querying the database" :P

If conditional in SQL Script for Mysql

In a sql script that does sequential execution, is there a way one can introduce an IF THEN ELSE conditional to control the flow of query execution?
I happened to run into this http://www.bennadel.com/blog/1340-MySQL-Does-Not-Support-IF-ELSE-Statements-In-General-SQL-Work-Flow.htm
which says that the IF THEN ELSE will not work in a sql script.
Is there another way around?
Basically, I want to run a particular "select colName from table" command and check if colName corresponds to a particular value. If it does, proceed with the rest of the script. Else, halt execution.
Please advise.
I just wrap my SQL script in a procedure, where conditional code is allowed. If you'd rather not leave the statements lying around, you can drop the procedure when you're done. Here's an example:
delimiter //
create procedure insert_games()
begin
set #platform_id := (select id from platform where name = 'Nintendo DS');
-- Only insert rows if the platform was found
if #platform_id is not null then
insert into game(name, platform_id) values('New Super Mario Bros', #platform_id);
insert into game(name, platform_id) values('Mario Kart DS', #platform_id);
end if;
end;
//
delimiter ;
-- Execute the procedure
call insert_games();
-- Drop the procedure
drop procedure insert_games;
If you haven't used procedures, the "delimiter" keyword might need some explanation. The first line switches the delimiter to "//" so that we can include semi-colons in our procedure definition without MySQL attempting to interpret them yet. Once the procedure has been created, we switch the delimiter back to ";" so we can execute statements as usual.
After doing some research I think I may have found a way to work around this. I was looking for a way to verify if a script had already executed against a target database. This will be primarily for version control of my databases. I have a table created to keep track of the scripts that have been executed and wanted some flow inside my scripts to check that table first before execution. While I have not completely solved the problem yet I have created a simple script that basically does what I need, I just need to wrap the DDL into the selects based on the value of the variables.
step 1 - Setup a bit variable to hold the result
step 2 - do your select and set the variable if the result is found
step 3 - Do what you need to do on false result
step 4 - Do what you need to do on true result
Here is the example script
set #schemachangeid = 0;
select #schemachangeid := 1 from SchemaChangeLog where scriptname = '1_create_tables.sql';
select 'scriptalreadyran' from dual where #schemachangeid = 1;
select 'scriptnotran' from dual where #schemachangeid = 0;
I also recognize this is an old thread but maybe this will help someone out there trying to do this kind of thing outside of a stored procedure like me.

Filtering A MySQL Result Set Based On The Return Value Of A Function

I'm a SQL noob, and I need a little bit of help understanding the big picture of if it is possible, and if so how to go about filtering a result set based on the return value of a function which is passed one of the fields of the record.
Let's say I have a table called "Numbers" with just one field: "Value".
How could I correctly specify the following "pseudo-sql"?:
SELECT Value FROM numbers WHERE IsPrime(Value)=true
Can I accomplish such a thing, and if so, where/how do I put/store "IsPrime"?
I'm using MySQL.
I agree with extraneon that it's usually better to store this value in the database rather than compute it in the where clause. This way you can compute it once per row and index the data for faster performance.
As long as you are on MySQL 5.x, I would recommend a stored function, as opposed to a UDF. You can add an IS_PRIME column of type TINYINT to your DB, add an index on that column, then use the stored function to calculate the value at insert time. You can even calculate the IS_PRIME value using a before insert trigger if you don't want to change the insert code.
The stored function would look something like this:
DELIMITER $$
DROP FUNCTION IF EXISTS IS_PRIME $$
CREATE FUNCTION IS_PRIME(P_INT BIGINT) RETURNS TINYINT
BEGIN
DECLARE V_FACTOR BIGINT;
DECLARE V_MAX_FACTOR BIGINT;
SET V_FACTOR := 2;
SET V_MAX_FACTOR := round(sqrt(P_INT),0);
WHILE (V_FACTOR <= V_MAX_FACTOR)
DO
IF (P_INT % V_FACTOR) = 0
THEN
RETURN FALSE;
END IF;
SET V_FACTOR := V_FACTOR + 1;
END WHILE;
RETURN TRUE;
END $$
DELIMITER ;
I think you may find some help with the doc about the user-defined functions: http://dev.mysql.com/doc/refman/5.1/en/adding-functions.html
I don't know anything about user defined functions, but I could imagine that for computation-intensive functions it might be best to have that value precomputed and stored in the database somewhere.
Depending on how the data gets in the database you could require the client to compute isPrime (that can be done if the client is a web service or app on your own server), or perhaps a scheduled job which processes every record with isPrime is null or something like that. That is not instantaneous, but for some scenario's it may be good enough.
If isPrime is only used sometimes you could also post-process/filter the data on the client when retrieving data.