mysql: joining two strings in one variable - mysql

so I wrote a procedure that should join two strings in one variable and then store it in the DB. the procedure works fine when i run it but it doesn't store the variable in the db. instead it stores it as 0.
this is the code I used
select memFname, memLname into fn, ln from member where memberid = memid;
SET v_name := fn || ' ' || ln ;
insert into profile(memberid, fullName) values (memid, v_name);
fn, ln, and v_name are variables of type varchar. memid is a variable of type integer.
am I missing something here?
I'm using a trigger to call the procedure.
Trigger code
DELIMITER $$
create trigger prof_name
after insert on member
for each row
begin
call profile_fullname(new.memberid);
END $$;
DELIMITER ;
member table structure:
memberid integer,
memfname varchar,
memlname varchar.
profile table structure:
profid integer,
memberid integer.

By default, the || operator in MySQL is a logical operator. You are using it as a concat, which is why I thought you were using Oracle. As a logical operator, it will convert each argument to a logical expression, which has a value of 0 or 1. Obviously, it is converting them to 0s, which is why you are getting 0 in the table. The alternative would be 1, which isn't any more interesting.
The right solution is to use concat() instead of ||. You can also set the system variable PIPES_AS_CONCAT to use || as you want to, but that would normally only be set if you were trying to make code portable between databases.
Here are examples:
update profile
set fullName = (select concat(memFname, ' ', memLname) from member m where m.memberid = memid)
where profile.memberid = memid;
If you actually want an insert, it looks something like this:
insert into profile(memberid, fullname)
select memid, concat(memFname, ' ', memLname)
from member m
where m.memberid = memid;

Related

PL/SQL Function, how to use SELECT INTO clause to declare variables from an existing table?

I would like to create a PL/SQL Function that calculates the age of any person from an existing table "Family tree" (Familienbaum), based on their Name. The Table has the needed Values Name, BirthYear (Geburtsjahr), YearOfDeath (Sterbejahr), etc.
Now I want to calculate the age of the person in two ways:
If the Person has a YearOfDeath, it should subtract the BirthYear from the YearofDeath to calculate
If the Person has no YearOfDeath, it should subtract the BirthYear from the Current System Year of the Oracle SQL Server
So far I have tried using the SELECT INTO clause to declare the variables needed for the calculation from the table Family Tree (Familienbaum):
CREATE OR REPLACE FUNCTION BerechneAlter(Person VARCHAR2)
RETURN INTEGER IS
BEGIN
SELECT Name, Sterbejahr, Geburtsjahr FROM Familienbaum
WHERE Person = Name;
RETURN (CASE
WHEN Sterbejahr IS NULL THEN (year(curDate()) - Geburtsjahr)
WHEN Sterbejahr IS NOT NULL THEN (Sterbejahr - Geburtsjahr)
END);
END BerechneAlter;
The SELECT INTO clause is giving me a lot of problems, does anyone know what needs to be changed in the syntax?
I also tried using cursors, but it seems more complicated than needed:
create or replace FUNCTION BerechneAlter(Person VARCHAR2)
RETURN INTEGER IS
Sterbejahr INTEGER;
Geburtsjahr INTEGER;
CURSOR SJ IS SELECT familienbaum.sterbejahr FROM familienbaum WHERE familienbaum.name=Person;
CURSOR GJ IS SELECT familienbaum.geburtsjahr FROM familienbaum WHERE familienbaum.name=Person;
BEGIN
OPEN SJ;
FETCH SJ INTO Sterbejahr;
CLOSE SJ;
OPEN GJ;
FETCH GJ INTO Geburtsjahr;
CLOSE GJ;
RETURN (CASE
WHEN Sterbejahr IS NULL THEN (2022 - Geburtsjahr)
WHEN Sterbejahr IS NOT NULL THEN (Sterbejahr - Geburtsjahr)
END);
END BerechneAlter;
If you are using a SQL SELECT statement within an anonymous block or function or procedure, etc (in PL/SQL - between the BEGIN and the END keywords) you must select INTO something so that PL/SQL can utilize a variable to hold your result from the query. It is important to note here that if you are selecting multiple columns, (which you are by "SELECT Name, Sterbejahr, Geburtsjahr"), you must specify multiple variables or a record to insert the results of your query into.
for example:
SELECT 1
INTO v_dummy
FROM dual;
SELECT 1, 2
INTO v_dummy, v_dummy2
FROM dual;
It is also worth pointing out that if your SELECT , ... FROM.... will return multiple rows, PL/SQL will throw an error. You should only expect to retrieve 1 row of data from a SELECT INTO.
In your case, it would look something like this (note - I haven't confirmed that your logic is correct, also note that I don't know your datatypes so you'll have to work on that bit too):
CREATE OR REPLACE FUNCTION BerechneAlter(p_person VARCHAR2)
RETURN INTEGER IS
v_name VARCHAR2(100);
v_sterbejahr VARCHAR2(100);
v_geburtsjahr VARCHAR2(100)
BEGIN
SELECT Name, Sterbejahr, Geburtsjahr
INTO v_name, v_sterbejahr, v_geburtsjahr
FROM Familienbaum
WHERE Name = p_person;
RETURN (CASE
WHEN v_sterbejahr IS NULL THEN (year(curDate()) - v_geburtsjahr)
WHEN v_sterbejahr IS NOT NULL THEN (v_sterbejahr - v_geburtsjahr)
END);
END BerechneAlter;
I think the function's logic is overcomplicated. You can get data and calculate age in an SQL statement, so only one var is needed.
create or replace function get_age(p_name varchar2) return number
is
l_aelter number;
begin
select nvl(sterbejahr, to_char(sysdate, 'YYYY')) - geburtsjahr aelter
into l_aelter
from familienbaum
where name = p_name;
return l_aelter;
end;
If using plsql is not required, you might wanted to use a standalone SQL statement to perform all the calculations you need:
select nvl(sterbejahr, to_char(sysdate, 'YYYY')) - geburtsjahr aelter
from familienbaum
where name = p_name;

I Getting error when run this below query : RETURN statements in scalar valued functions must include an argument

I am getting this error message
RETURN statements in scalar valued functions must include an argument
when run this query:
create function gender(#gender nvarchar(40))
returns nvarchar(40)
as
begin
(select name,cast(DOB as date) as DOB from datenames where gender = #gender)
return
end
The write way to create a function in mysql for your example is as follows:
DELIMITER \\
create function gender(Igender nvarchar(40))
returns nvarchar(40)
begin
DECLARE customerLevel NVARCHAR(40);
IF EXISTS (select name,cast(DOB as date) as DOB from datenames where gender = Igender) THEN
SET customerLevel = 'SOMETHING1';
ELSE
SET customerLevel = 'SOMETHING2';
END IF;
RETURN (customerLevel);
end
No need to as
No need to # before input
You need to return something.
Don't forget to use DELIMITER.
If you use phpmyadmin and has problem with nvarchar read this post: Unrecognize data type nvarchar in database or simply change it to to varchar.

How to use declared variable to create a json element

I have a stored procedure, in this, I have declared a variable which holds a value from a select function. I need to use this value to create a json element, but it throws an exception
function jsonb_set(jsonb, unknown, character varying, boolean) does not exist
This is the function:
CREATE OR REPLACE FUNCTION test ( ) RETURNS
INTEGER AS $$
DECLARE
intent varchar;
BEGIN
select id into intent from customer;
UPDATE orders
SET data = jsonb_set(
data,
'{Items}', -- the array in which we operate
to_jsonb(
(WITH ar AS(
WITH temp AS(
SELECT data->'Items' AS items -- the array in which we operate
FROM orders
WHERE id = 1 -- the filtered order we are updating
)
SELECT jsonb_set(
jsonb_array_elements(items),
'{Quantity}', -- the new field we are adding
intent, -- this is where i need to replace the variable
true)
FROM temp)
SELECT (array_agg(ar.jsonb_set))
FROM ar)),
false)
WHERE id = 1;
return 0;
EXCEPTION WHEN others THEN
return 1;
END;
$$ LANGUAGE plpgsql;
Copying the snippet where I need to replace the variable:
SELECT jsonb_set(
jsonb_array_elements(items),
'{Quantity}', -- the new field we are adding
intent, -- this is where i need to replace the variable
true)
You have to explicitly cast intent to jsonb using CAST(intent AS jsonb) or intent::jsonb.
The reason it works with a string literal is that such literals are of the (internal) type unknown which can be transformed to most other types, but there is no implicit cast between character varying and jsonb, so you have to use an explicit one.

Get max id of all sequences in PostgreSQL

We have a monitor on our databases to check for ids approaching max-int or max-bigint. We just moved from MySQL, and I'm struggling to get a similar check working on PostgreSQL. I'm hoping someone can help.
Here's the query in MySQL
SELECT table_name, auto_increment FROM information_schema.tables WHERE table_schema = DATABASE();
I'm trying to get the same results from PostgreSQL. We found a way to do this with a bunch of calls to the database, checking each table individually.
I'd like to make just 1 call to the database. Here's what I have so far:
CREATE OR REPLACE FUNCTION getAllSeqId() RETURNS SETOF record AS
$body$
DECLARE
sequence_name varchar(255);
BEGIN
FOR sequence_name in SELECT relname FROM pg_class WHERE (relkind = 'S')
LOOP
RETURN QUERY EXECUTE 'SELECT last_value FROM ' || sequence_name;
END LOOP;
RETURN;
END
$body$
LANGUAGE 'plpgsql';
SELECT last_value from getAllSeqId() as(last_value bigint);
However, I need to somehow add the sequence_name to each record so that I get output in records of [table_name, last_value] or [sequence_name, last_value].
So I'd like to call my function something like this:
SELECT sequence_name, last_value from getAllSeqId() as(sequence_name varchar(255), last_value bigint);
How can I do this?
EDIT: In ruby, this creates the output we're looking for. As you can see, we're doing 1 call to get all the indexes, then 1 call per index to get the last value. Gotta be a better way.
def perform
find_auto_inc_tables.each do |auto_inc_table|
check_limit(auto_inc_table, find_curr_auto_inc_id(auto_inc_table))
end
end
def find_curr_auto_inc_id(table_name)
ActiveRecord::Base.connection.execute("SELECT last_value FROM #{table_name}").first["last_value"].to_i
end
def find_auto_inc_tables
ActiveRecord::Base.connection.execute(
"SELECT c.relname " +
"FROM pg_class c " +
"WHERE c.relkind = 'S'").map { |i| i["relname"] }
end
Your function seems quite close already. You'd want to modify it a bit to:
include the sequences names as literals
returns a TABLE(...) with typed columns instead of SET OF RECORD because it's easier for the caller
Here's a revised version:
CREATE OR REPLACE FUNCTION getAllSeqId() RETURNS TABLE(seqname text,val bigint) AS
$body$
DECLARE
sequence_name varchar(255);
BEGIN
FOR sequence_name in SELECT relname FROM pg_class WHERE (relkind = 'S')
LOOP
RETURN QUERY EXECUTE 'SELECT ' || quote_literal(sequence_name) || '::text,last_value FROM ' || quote_ident(sequence_name);
END LOOP;
RETURN;
END
$body$
LANGUAGE 'plpgsql';
Note that currval() is not an option since it errors out when the sequence has not been set in the same session (by calling nextval(), not sure if there's any other way).
Would something as simple as this work?
SELECT currval(sequence_name) from information_schema.sequences;
If you have sequences that aren't keys, I guess you could use PG's sequence name generation pattern to try to restrict it.
SELECT currval(sequence_name) from information_schema.sequences
WHERE sequence_name LIKE '%_seq';
If that is still too many false positives, you can get table names from the information_schema (or the pg_* schemata that I don't know very well) and refine the LIKE parameter.

SSRS multi-value parameter using a stored procedure

I am working on a SSRS report that uses a stored procedure containing a few parameters. I am having problems with two of the parameters because I want to have the option of selecting more than one item.
Here's a condensed version of what I have:
CREATE PROCEDURE [dbo].[uspMyStoredProcedure]
(#ReportProductSalesGroupID AS VARCHAR(MAX)
,#ReportProductFamilyID AS VARCHAR(MAX)
,#ReportStartDate AS DATETIME
,#ReportEndDate AS DATETIME)
--THE REST OF MY QUERY HERE WHICH PULLS ALL OF THE NEEDED COLUMNS
WHERE DateInvoicedID BETWEEN #ReportStartDate AND #ReportEndDate
AND ProductSalesGroupID IN (#ReportProductSalesGroupID)
AND ProductFamilyID IN (#ReportProductFamilyID)
When I try to just run the stored procedure I only return values if I enter only 1 value for #ReportProductSalesGroupID and 1 value #ReportProductFamilyID. If I try to enter two SalesGroupID and/or 2 ProductFamilyID it doesn't error, but I return nothing.
-- Returns data
EXEC uspMyStoredProcedure 'G23', 'NOF', '7/1/2009', '7/31/2009'
-- Doesn't return data
EXEC uspMyStoredProcedure 'G23,G22', 'NOF,ALT', '7/1/2009', '7/31/2009'
In SSRS I get an error that says:
Incorrect syntax near ','
It appears that the , separator is being included in the string instead of a delimiter
You need three things:
In the SSRS dataset properties, pass the multi-value param to the stored procedure as a comma-delimited string
=Join(Parameters!TerritoryMulti.Value, ",")
In Sql Server, you need a table-value function that can split a comma-delimited string back out into a mini table (eg see here). edit: Since SQL Server 2016 you can use the built-in function STRING_SPLIT for this
In the stored procedure, have a where clause something like this:
WHERE sometable.TerritoryID in (select Item from dbo.ufnSplit(#TerritoryMulti,','))
... where ufnSplit is your splitting function from step 2.
(Full steps and code in my blog post 'SSRS multi-value parameters with less fail'):
Let us assume that you have a multi value list #param1
Create another Internal Parameter on your SSRS report called #param2 and set the default value to:
=Join(Parameters!param1.value, 'XXX')
XXX can be any delimiter that you want, EXCEPT a comma (see below)
Then, you can pass #param2 to your query or stored procedure.
If you try to do it any other way, it will cause any string function that uses commas to separate arguments, to fail. (e.g. CHARINDEX, REPLACE).
For example Replace(#param2, ',', 'replacement') will not work. You will end up with errors like "Replace function requires 3 arguments".
Finally I was able to get a simple solution for this problem. Below I have provided all (3) steps that I followed.
I hope you guys will like it :)
Step 1 - I have created a Global Temp Table with one column.
CREATE GLOBAL TEMPORARY TABLE TEMP_PARAM_TABLE(
COL_NAME VARCHAR2(255 BYTE)
) ON COMMIT PRESERVE ROWS NOCACHE;
Step 2 - In the split Procedure, I didn't use any array or datatable, I have directly loaded the split values into my global temp table.
CREATE OR REPLACE PROCEDURE split_param(p_string IN VARCHAR2 ,p_separator IN VARCHAR2
)
IS
v_string VARCHAR2(4000);
v_initial_pos NUMBER(9) := 1;
v_position NUMBER(9) := 1;
BEGIN
v_string := p_string || p_separator;
delete from temp_param_policy;
LOOP
v_position :=
INSTR(v_string, p_separator, v_initial_pos, 1);
EXIT WHEN(NVL(v_position, 0) = 0);
INSERT INTO temp_param_table
VALUES (SUBSTR(v_string, v_initial_pos
, v_position - v_initial_pos));
v_initial_pos := v_position + 1;
END LOOP;
commit;
END split_param;
/
Step 3 - In the SSRS dataset parameters, I have used
=Join(Parameters!A_COUNTRY.Value, ",")
Step 4: In the start of your stored procedure executes the Procedure
Exec split_param(A_Country, ‘,’);
Step 5: In your stored procedure sql use the condition like below.
Where country_name in (select * from TEMP_PARAM_TABLE)
When SSRS passes the parameter it is in the form: Param1,Param2,Param3.
In the procedure, you just need to put identifiers around each parameter. And also identifiers around the value that is returned by the dataset. In my case, I used semicolons.
CREATE OR REPLACE PROCEDURE user.parameter_name (
i_multivalue_parameter
)
AS
l_multivalue_parameter varchar2(25555) := ';' || replace(i_multivalue_parameter,',',';') || ';';
BEGIN
select something
from dual
where (
instr(l_multivalue_parameter, ';' || database_value_that_is_singular || ';') > 0
)
END;
i_multivalue_parameter is passed in via SSRS.
l_multivalue_parameter reads the parameter passed in via SSRS and puts identifiers around each value.
database_value_that_is_singular is the value returned for each record.
So if 'Type1,Type2,Type3'is passed in via SSRS:
i_multivalue_parameter is: Type1,Type2,Type3
l_multivalue_parameter is: ;Type1;Type2;Type3;
database_value_that_is_singular is: ;Type1; or ;Type2; or ;Type3;
Instr will return a value over 0 if the parameter matches.
This works even if each parameters are similar. EG: "Type A" and "Type AA". That is "Type A" will not match "Type AA".
I found a simple way for my solution. Define the parameter value in the report as an expression like this
="'" + Join(Parameters!parm.Value,"','") + "'"
(in case you can't read it the first and last literals are double quote, single quote, double quote. The join literal is double quote, single quote, comma, single quote, double quote)
Then in the stored procedure you can use dynamic sql to create your statement. I did this to create a temp table of values to join to in a later query, like this:
CREATE #nametable (name nvarchar(64))
SET #sql = N'SELECT Name from realtable where name in (' + #namelist + ')'
INSERT INTO #nametable exec sp_executesql #sql
#namelist would be the name of the stored procedure parameter.