I'm trying to determine whether the content of a field is an integer value.
In firebird 2.5 there is "similar to", but this wasn't available in 2.1 yet.
Thank you for your answer.
For now I'll go with:
substring(fieldname from 1 for 1) > '0' and
substring(fieldname from 1 for 1) < '9'
This procedure using error handling, returns field value as is if contents is integer, otherwise return 0
SET TERM ^ ;
create or alter procedure INT_CHECK (
IN_STR varchar(100))
returns (
ORESULT integer)
as
BEGIN
/* because WHEN works for the entire block use a separate BEGIN..END*/
begin -- START OF BLOCK
oresult = cast(:in_str as integer);
when any do
begin
oresult = 0;
end
end -- END OF BLOCK
suspend;
END^
SET TERM ; ^
Related
This is a working function in SQL Server 2014 Express
CREATE FUNCTION [dbo].[Func_Account_FollowingCustomer]
(#AccountNumber NVARCHAR(20))
RETURNS BIT
AS
BEGIN
DECLARE #bResult BIT
IF (SELECT COUNT(AccountNumber) FROM dbo.Account AS A
WHERE DetailByAccountObject = 1
AND AccountObjectType = 1
AND AccountNumber = #AccountNumber) > 0
SET #bResult = 1
ELSE
SET #bResult = 0
RETURN #bResult
END
I try to convert to PostgreSQL 14, I have
CREATE FUNCTION public.func_account_following_customer(IN account_number character varying)
RETURNS bit
LANGUAGE 'sql'
declare #b_result bit
begin
if(select count(account_number) from account as a where detail_by_account_object = 1 and account_object_type = 1 and account_number = #account_number) > 0
set #b_result = 1
else
set #b_result = 0
return #b_result;
end;
ALTER FUNCTION public.func_account_following_customer(character varying)
OWNER TO postgres;
Error
ERROR: syntax error at or near "declare" LINE 5: declare #b_result bit ^
How to fix it?
language sql can't use variables or procedural elements (like IF), you need language plpgsql - but the syntax for variable names is different and the assignment is not done using set
The function body is a string constant, typically specified using dollar quoting.
If you want to return true/false flags, use boolean instead of bits.
Parameters or variables are referenced using # but simply with their name.
But you don't need procedural code for such a simple SQL query that just returns true/false.
CREATE FUNCTION public.func_account_following_customer(IN p_account_number character varying)
RETURNS boolean
LANGUAGE sql
as
$$
select count(*) > 0
from account as a
where detail_by_account_object = 1
and account_object_type = 1
and account_number = p_account_number;
$$
;
As a PL/pgSQL function this would be:
CREATE FUNCTION public.func_account_following_customer(IN p_account_number character varying)
RETURNS boolean
LANGUAGE plpgsql
as
$$
declare
l_result boolean;
begin
if (select count(*)
from account as a
where detail_by_account_object = 1
and account_object_type = 1
and account_number = p_account_number) > 0
then
l_result := true;
else
l_result := false;
end if;
return l_result;
end;
$$
;
I'm converting a BDE query (Paradox) to a Firebird (2.5, not 3.x) and I have a very convenient conversion in it:
select TRIM(' 1') as order1, CAST(' 1' AS INTEGER) AS order2 --> 1
select TRIM(' 1 bis') as order1, CAST(' 1 bis' AS INTEGER) AS order2 --> 1
Then ordering by the cast value then the trimmed value (ORDER order2, order1) provide me the result I need:
1
1 bis
2 ter
100
101 bis
However, in Firebird casting an incorrect integer will raise an exception and I did not find any way around to provide same result. I think I can tell if a number is present with something like below, but I couldn't find a way to extract it.
TRIM(' 1 bis') similar to '[ [:ALPHA:]]*[[:DIGIT:]]+[ [:ALPHA:]]*'
[EDIT]
I had to handle cases where text were before the number, so using #Arioch'The's trigger, I got this running great:
SET TERM ^ ;
CREATE TRIGGER SET_MYTABLE_INTVALUE FOR MYTABLE ACTIVE
BEFORE UPDATE OR INSERT POSITION 0
AS
DECLARE I INTEGER;
DECLARE S VARCHAR(13);
DECLARE C VARCHAR(1);
DECLARE R VARCHAR(13);
BEGIN
IF (NEW.INTVALUE is not null) THEN EXIT;
S = TRIM( NEW.VALUE );
R = NULL;
I = 1;
WHILE (I <= CHAR_LENGTH(S)) DO
BEGIN
C = SUBSTRING( S FROM I FOR 1 );
IF ((C >= '0') AND (C <= '9')) THEN LEAVE;
I = I + 1;
END
WHILE (I <= CHAR_LENGTH(S)) DO
BEGIN
C = SUBSTRING( S FROM I FOR 1 );
IF (C < '0') THEN LEAVE;
IF (C > '9') THEN LEAVE;
IF (C IS NULL) THEN LEAVE;
IF (R IS NULL) THEN R=C; ELSE R = R || C;
I = I + 1;
END
NEW.INTVALUE = CAST(R AS INTEGER);
END^
SET TERM ; ^
Converting such a table, you have to add a special indexed integer column for keeping the extracted integer data.
Note, this query while using "very convenient conversion" is actually rather bad: you should use indexed columns to sort (order) large amounts of data, otherwise you are going into slow execution and waste a lot of memory/disk for temporary sorting tables.
So you have to add an extra integer indexed column and to use it in the query.
Next question is how to populate that column.
Better would be to do it once, when you move your entire database and application from BDE to Firebird. And from that point make your application when entering new data rows fill BOTH varchar and integer columns properly.
One time conversion can be done by your convertor application, then.
Or you can use selectable Stored Procedure that would repeat the table with such and added column. Or you can make Execute Block that would iterate through the table and update its rows calculating the said integer value.
How to SELECT a PROCEDURE in Firebird 2.5
If you would need to keep legacy applications, that only insert text column but not integer column, then I think you would have to use BEFORE UPDATE OR INSERT triggers in Firebird, that would parse the text column value letter by letter and extract integer from it. And then make sure your application never changes that integer column directly.
See a trigger example at Trigger on Update Firebird
PSQL language documentation: https://www.firebirdsql.org/file/documentation/reference_manuals/fblangref25-en/html/fblangref25-psql.html
Whether you would write procedure or trigger to populate the said added integer indexed column, you would have to make simple loop over characters, copying string from first digit until first non-digit.
https://www.firebirdsql.org/file/documentation/reference_manuals/fblangref25-en/html/fblangref25-functions-scalarfuncs.html#fblangref25-functions-string
https://www.firebirdsql.org/file/documentation/reference_manuals/fblangref25-en/html/fblangref25-psql-coding.html#fblangref25-psql-declare-variable
Something like that
CREATE TRIGGER my_trigger FOR my_table
BEFORE UPDATE OR INSERT
AS
DECLARE I integer;
DECLARE S VARCHAR(100);
DECLARE C VARCHAR(100);
DECLARE R VARCHAR(100);
BEGIN
S = TRIM( NEW.MY_TXT_COLUMN );
R = NULL;
I = 1;
WHILE (i <= CHAR_LENGTH(S)) DO
BEGIN
C = SUBSTRING( s FROM i FOR 1 );
IF (C < '0') THEN LEAVE;
IF (C > '9') THEN LEAVE;
IF (C IS NULL) THEN LEAVE;
IF (R IS NULL) THEN R=C; ELSE R = R || C;
I = I + 1;
END
NEW.MY_INT_COLUMN = CAST(R AS INTEGER);
END;
In this example your ORDER order2, order1 would become
SELECT ..... FROM my_table ORDER BY MY_INT_COLUMN, MY_TXT_COLUMN
Additionally, it seems your column actually contains a compound data: an integer index and an optional textual postfix. If so, then the data you have is not normalized and the table better be restructured.
CREATE TABLE my_table (
ORDER_Int INTEGER NOT NULL,
ORDER_PostFix VARCHAR(24) CHECK( ORDER_PostFix = TRIM(ORDER_PostFix) ),
......
ORDER_TXT COMPUTED BY (ORDER_INT || COALESCE( ' ' || ORDER_PostFix, '' )),
PRIMARY KEY (ORDER_Int, ORDER_PostFix )
);
When you would move your data from Paradox to Firebird - make your convertor application check and split those values like "1 bis" into two new columns.
And your query then would be like
SELECT ORDER_TXT, ... FROM my_table ORDER BY ORDER_Int, ORDER_PostFix
if you're using fb2.5 you can use the following:
execute block (txt varchar(100) = :txt )
returns (res integer)
as
declare i integer;
begin
i=1;
while (i<=char_length(:txt)) do begin
if (substring(:txt from i for 1) not similar to '[[:DIGIT:]]')
then txt =replace(:txt,substring(:txt from i for 1),'');
else i=i+1;
end
res = :txt;
suspend;
end
in fb3.0 you have more convenient way to do the same
select
cast(substring(:txt||'#' similar '%#"[[:DIGIT:]]+#"%' escape '#') as integer)
from rdb$database
--assuming that the field is varchar(15))
select cast(field as integer) from table;
Worked in firebird version 2.5.
Updating my TFS 2013 Update 4 collection to TFS 2015 Update 3. Using a backup of the production collection data in a DEV location. Did the backup with the production collection being detached. Didn't have any errors. The backup is 254GB.
This is the error currently stopping me from attaching the collection:
Msg 3732, Level 16, State 1, Line 93
Cannot drop type 'typ_ItemSpec2' because it is being referenced by object 'prc_QueryPendingChanges_MS'. There may be other objects that reference this type.
SET XACT_ABORT ON
SET NOCOUNT ON
DECLARE #status INT
DECLARE #procedureName SYSNAME = N'upd_VersionControlToDev14M80_PostSchema'
DECLARE #tfError NVARCHAR(255)
IF EXISTS (
SELECT *
FROM sys.triggers
WHERE name = 'trg_tbl_VCFirstRunProject'
)
BEGIN
DROP TRIGGER trg_tbl_VCFirstRunProject
END
IF EXISTS (
SELECT *
FROM sys.indexes
WHERE name = 'IX_tbl_VCFirstRunProject_OldServerItemPrefix'
AND object_id = OBJECT_ID('dbo.tbl_VCFirstRunProject')
)
BEGIN
-- Delete upgrade-only rows for $\, a few partitions at a time
-- We need dynamic SQL for this to be rerunnable.
EXEC #status = sp_executesql N'
DECLARE #batchStart INT = 1
DECLARE #batchEnd INT
DECLARE #end INT
DECLARE #batchSize INT = 50
-- Get the partition range
SELECT TOP (1)
#end = PartitionId
FROM tbl_VCFirstRunProject
ORDER BY PartitionId DESC
WHILE (#batchStart <= #end)
BEGIN
SET #batchEnd = #batchStart + #batchSize
DELETE tbl_VCFirstRunProject
WHERE PartitionId BETWEEN #batchStart AND #batchEnd
AND OldServerItemPrefix = N''''
OPTION (OPTIMIZE FOR (#batchStart=1, #batchEnd=50))
SET #batchStart = #batchEnd + 1
END
'
IF (#status <> 0)
BEGIN
SET #tfError = dbo.func_GetMessage(500004); RAISERROR(#tfError, 16, -1, #procedureName, #status, N'sp_executesql', N'DELETE tbl_VCFirstRunProject')
RETURN
END
DROP INDEX IX_tbl_VCFirstRunProject_OldServerItemPrefix ON tbl_VCFirstRunProject
END
IF EXISTS (
SELECT *
FROM sys.columns
WHERE object_id = Object_ID(N'dbo.tbl_VCFirstRunProject', N'U')
AND name = N'OldServerItemPrefix'
)
BEGIN
ALTER TABLE tbl_VCFirstRunProject
DROP COLUMN OldServerItemPrefix, NewServerItemPrefix
END
IF TYPE_ID('dbo.typ_BranchObject2') IS NOT NULL
BEGIN
DROP TYPE typ_BranchObject2
END
IF TYPE_ID('dbo.typ_BuildMappingInput2') IS NOT NULL
BEGIN
DROP TYPE typ_BuildMappingInput2
END
IF TYPE_ID('dbo.typ_CreateLabelInput') IS NOT NULL
BEGIN
DROP TYPE typ_CreateLabelInput
END
IF TYPE_ID('dbo.typ_ExpandedChange2') IS NOT NULL
BEGIN
DROP TYPE typ_ExpandedChange2
END
IF TYPE_ID('dbo.typ_ItemSpec2') IS NOT NULL
BEGIN
DROP TYPE typ_ItemSpec2
END
IF TYPE_ID('dbo.typ_LocalPendingChange3') IS NOT NULL
BEGIN
DROP TYPE typ_LocalPendingChange3
END
IF TYPE_ID('dbo.typ_LocalVersion3') IS NOT NULL
BEGIN
DROP TYPE typ_LocalVersion3
END
IF TYPE_ID('dbo.typ_LockConflictCandidate2') IS NOT NULL
BEGIN
DROP TYPE typ_LockConflictCandidate2
END
IF TYPE_ID('dbo.typ_LockObject') IS NOT NULL
BEGIN
DROP TYPE typ_LockObject
END
IF TYPE_ID('dbo.typ_Mapping2') IS NOT NULL
BEGIN
DROP TYPE typ_Mapping2
END
IF TYPE_ID('dbo.typ_PendingAdd2') IS NOT NULL
BEGIN
DROP TYPE typ_PendingAdd2
END
IF TYPE_ID('dbo.typ_PendingChangeObject') IS NOT NULL
BEGIN
DROP TYPE typ_PendingChangeObject
END
IF TYPE_ID('dbo.typ_PendingChangeSecurity') IS NOT NULL
BEGIN
DROP TYPE typ_PendingChangeSecurity
END
IF TYPE_ID('dbo.typ_PendingMerge2') IS NOT NULL
BEGIN
DROP TYPE typ_PendingMerge2
END
IF TYPE_ID('dbo.typ_PendingPropertyChange2') IS NOT NULL
BEGIN
DROP TYPE typ_PendingPropertyChange2
END
IF TYPE_ID('dbo.typ_VersionedItemId') IS NOT NULL
BEGIN
DROP TYPE typ_VersionedItemId
END
None of the standard queries from Microsoft have _MS as a suffix. I suspect that someone hand-tweaked the original prc_QueryPendingChanges and left this around as a backup. In that case you should be able to drop this procedure and retry the upgrade.
I have dropped a PL/pgSQL function and I try to recreate it with one more parameter (with default value). But I get a strange error:
ERROR: function vytvor_kod_sj(text, integer, integer) does not exist
SQL state: 42883
I would expect such an error while dropping or calling the function, not while creating it. I made sure that the error occurs exactly while trying to create it, not in any of the other functions or triggers I created from the same sql file.
I made a dummy function without the last parameter and it works now, but it is definitely not what I want - not only I don't need the function without the last parameter anymore, I usually call the function only with the first two or three parameter, so I need to drop it just after creating the new version of my function to avoid mismatches. Fortunately, there are no errors while doing this, but it is hardly optimal solution.
So does anyone know how to solve this mysterious problem?
I have PostgreSQL 9.3.4 on Windows 32; I use pgAdmin 1.18.1
My code:
CREATE OR REPLACE FUNCTION vytvor_kod_sj( _akce text, typ_sj integer,
podtyp integer DEFAULT 0, styl integer DEFAULT NULL )
RETURNS text AS $$
DECLARE
--styl integer;
_nazev_seq text;
_min integer;
_max integer;
_nazev text;
_soucasna integer;
BEGIN
IF styl IS NULL THEN
SELECT nomenklatura INTO styl FROM akce WHERE kod_akce = _akce;
END IF;
IF NOT EXISTS( SELECT id_nom FROM pro_nom_pravidlo_sj
WHERE id_nom = styl AND sj = typ_sj AND typ = podtyp ) THEN
IF podtyp = 0 OR NOT EXISTS( SELECT id_nom FROM pro_nom_pravidlo_sj
WHERE id_nom = styl AND sj = typ_sj AND typ = 0 ) THEN
RAISE NOTICE 'Pro daný typ stratigrafické jednotky není vytvořeno žádné pravidlo!';
RETURN NULL;
ELSE
podtyp := 0;
END IF;
END IF;
_nazev_seq := _akce || '_' || typ_sj || '_' || podtyp || '_seq';
IF NOT EXISTS (SELECT 0 FROM pg_class where relname = _nazev_seq ) THEN
SELECT min, max INTO _min, _max FROM pro_nom_pravidlo_sj
WHERE id_nom = styl AND sj = typ_sj AND typ = podtyp;
IF _max IS NOT NULL THEN
EXECUTE format('CREATE SEQUENCE %I MINVALUE %s MAXVALUE %s ', _nazev_seq, _min, _max);
ELSE
EXECUTE format('CREATE SEQUENCE %I MINVALUE %s ', _nazev_seq, _min);
END IF;
END IF;
--IF strict IS TRUE THEN
RETURN (
SELECT predpona FROM pro_nom_pravidlo_sj
WHERE id_nom = styl AND sj = typ_sj AND typ = podtyp
) || CAST(nextval(_nazev_seq) AS TEXT);
--END IF;
END;
$$ LANGUAGE plpgsql
SECURITY DEFINER;
ALTER FUNCTION vytvor_kod_sj( text, integer, integer ) OWNER TO ins_daemon;
The old version had styl variable declared, not as a parameter, and there was no test whether it is null. Otherwise I didn't change anything.
I noticed the cause of the problem just after posting the question:
ALTER FUNCTION vytvor_kod_sj( text, integer, integer ) OWNER TO ins_daemon;
It tries to alter the old function.
I call this procedure without any problem :
SELECT SP_getGlobalVariable('current_user_id')::INT ;
If I call it inside another stored procedure like this :
-- Get current User Id --
SELECT SP_getGlobalVariable('current_user_id')::INT INTO __currentUserId ;
I have this error :
ERROR : Syntax error near "current_user_id"
LINE 25: SELECT SP_getGlobalVariable('current_user_id')::INT INTO _...
It is probably a stupid syntax error... If someone can help a poor MySQL user starting using PG !
EDIT 1 :
SELECT version();
PostgreSQL 9.3.3 on i686-pc-linux-gnu, compiled by gcc-4.4.real (Debian 4.4.5-8) 4.4.5, 32-bit
This is the result of the version() call.
CREATE OR REPLACE FUNCTION SP_getGlobalVariable (__variableName VARCHAR(64))
RETURNS VARCHAR
AS
'
DECLARE
BEGIN
RETURN (SELECT value FROM tmp_variables
WHERE name = __variableName) ;
END ;
'
LANGUAGE 'plpgsql';
The procedure to get the global variable in the temporary table.*
EDIT 2 :
CREATE OR REPLACE FUNCTION SP_insertSite (__siteName VARCHAR(70), __siteDescription TEXT, __siteLatitude NUMERIC, __siteLongitude NUMERIC)
RETURNS INT
AS
'
DECLARE
-- Variables
__right_edge_father INT ;
__left_edge_father INT ;
__depth_father INT ;
__nbrSites INT ;
__insertId INT ;
__currentUserId INT ;
BEGIN
-- Check if tree is empty --
SELECT COUNT(*) INTO __nbrSites
FROM site ;
-- Get current User Id --
SELECT SP_getGlobalVariable('current_user_id')::INT INTO __currentUserId ;
IF __nbrSites = 0 THEN
INSERT INTO site (site_name, site_description, site_latitude, site_longitude, site_left_edge, site_right_edge, site_depth, site_create_dt, site_create_user_id)
VALUES(__siteName, __siteDescription, 0.0, 0.0, 1, 2, 0, LOCALTIMESTAMP, __currentUserId)
RETURNING site_id INTO __insertId;
ELSE
-- Get father edges --
SELECT site_left_edge, site_right_edge, site_depth
INTO __left_edge_father, __right_edge_father, __depth_father
FROM site
WHERE site_id = (SELECT site_id FROM site WHERE site_depth = 0 LIMIT 1) ;
-- Updates left edges --
UPDATE site
SET site_left_edge = site_left_edge + 2
WHERE site_left_edge >= __right_edge_father ;
-- Updates right edges --
UPDATE site
SET site_right_edge = site_right_edge + 2
WHERE site_right_edge >= __right_edge_father ;
-- Insert new node --
INSERT INTO site (site_name, site_description, site_latitude, site_longitude, site_left_edge, site_right_edge, site_depth, site_create_dt, site_create_user_id)
VALUES(__siteName, __siteDescription, __siteLatitude, __siteLongitude, __right_edge_father, __right_edge_father+1, __depth_father+1, LOCALTIMESTAMP, __currentUserId)
RETURNING site_id INTO __insertId;
END IF ;
RETURN __insertId ;
END ;
'
LANGUAGE plpgsql;
The problem is that you are using single quotes to encapsulate the function body, and so, when you try to add single quotes inside the body, you need to escape it, for instance:
CREATE OR REPLACE FUNCTION SP_insertSite (...)
RETURNS INT
AS
'
DECLARE
...
-- Get current User Id --
SELECT SP_getGlobalVariable(''current_user_id'')::INT INTO __currentUserId ;
...
RETURN __insertId ;
END ;
'
LANGUAGE plpgsql;
That would work nice. But I recommend you to use dollar-quoting and so you don't have to worry with single-quote escaping inside the function body, as the following:
CREATE OR REPLACE FUNCTION SP_insertSite (...)
RETURNS INT
AS
$$
DECLARE
...
-- Get current User Id --
SELECT SP_getGlobalVariable('current_user_id')::INT INTO __currentUserId ;
...
RETURN __insertId ;
END ;
$$
LANGUAGE plpgsql;
I'd recommend you to read PostgreSQL docs about the usage of dollar-quoting in PL/pgSQL (the entire document, not just this sub-section is also interesting to read).