CaseSensitivity in an user-defined function T-SQL - tsql

I have an user-defined function in T-SQL which uses CHARINDEX() internally. I take #caseSensitivity(bit) parameter as an input, which should decide the casing(collation) of the CHARINDEX() function. Is there a way to do that without making two if-constructs for every occurence of CHARINDEX()?

Unfortunately, there isn't a way to set a connection-level collation override, if that is what you were thinking (I was).
However, if you are just looking for a way to be able to control this easier, then you may be able to evaluate all the CHARINDEX lookups as case-sensitive and then use UPPER to make it case-insensitive when it #caseSensitive is zero:
DECLARE #caseSensitivity BIT = 1
DECLARE #input VARCHAR(3) = 'Abc'
DECLARE #inputCheck VARCHAR(3) = 'B'
IF #caseSensitivity = 0
BEGIN
SET #input = UPPER(#input)
SET #inputCheck = UPPER(#input)
END
SELECT CHARINDEX(#inputCheck COLLATE SQL_Latin1_General_CP1_CS_AS, #input COLLATE SQL_Latin1_General_CP1_CS_AS, 0)

Related

increment sequence in PostgreSQL stored procedure

How to auto increment sequence number once for every run of a stored procedure and how to use it in the where condition of an update statement?
I already assigned a sequence number to the next value in each run, but I'm not able to use it in the where condition.
CREATE OR REPLACE FUNCTION ops.mon_connect_easy()
RETURNS void
LANGUAGE plpgsql
AS $function$
declare
_inserted_rows bigint = 0;
sql_run bigint = 0;
--assigning the sequence number to the variable
select nextval('ops.mon_connecteasy_seq') into run_seq_num;
-- use for selection iteration_id. this is hwere I'm getting stuck
update t_contract c
set end_date = ce.correct_end_date, status='Active',
orig_end_date =ce.correct_end_date
from ops.t_mon_ConnectEasy ce
where c.contract_id = ce.contract_id
and run_seq_num = ??;
nextval() advances the sequence automatically before returning the resulting value. You don't need anything extra. Just use the function in your query directly:
update t_contract c
set end_date = ce.correct_end_date
, status = 'Active'
, orig_end_date = ce.correct_end_date
from ops.t_mon_ConnectEasy ce
where c.contract_id = ce.contract_id
and iteration_id = nextval('ops.mon_connecteasy_seq');
Be aware that concurrent transactions also might advance the sequence, creating virtual gaps in the sequential numbers.
And I have a nagging suspicion that this might not be the best way to achieve your undisclosed goals.

How can I query whether a value lies before or after a range?

I have a Postgres range and a value, and want to be able to determine if the value lies before, within, or after the range.
Determining if the value lies within the range is trivial:
SELECT '[1,10]'::int4range #> 3; -- t
But looking at range functions and operators, the #> operator is the only one I see that doesn't require both operands to be ranges, so determining whether the value lies before or after the range is not as straightforward.
I'm currently constructing a trivial range to represent my value (inclusive endpoints, both equal to the value), and then using << (strictly left of) and >> (strictly right of):
SELECT '[1,10]'::int4range << '[11,11]'::int4range; -- t
SELECT '[1,10]'::int4range >> '[-3,-3]'::int4range; -- t
This works, but having to construct this trivial range representing a single discrete value just so I can use the << and >> operators feels a bit kludge-y to me. Is there some built-in function or operator I'm overlooking that would allow me to do these queries using the value directly?
I considered and rejected an approach based on using lower(range) > value and upper(range) < value, as that doesn't account for the inclusivity/exclusivity of the range's bounds.
I'm using Postgres 9.6.5, but it doesn't look like anything has changed in this regard in Postgres 10.
I [...] rejected an approach based on using lower(range) > value and upper(range) < value, as that doesn't account for the inclusivity/exclusivity of the range's bounds.
I am not sure what you mean with that. lower() and upper() do account for inclusive/exclusive: lower('(1,10]'::int4range); returns 2 and lower('[1,10]'::int4range); returns 1
It seems to me creating an operator for this would be quite easy:
Create two functions to compare an int to an int4range:
create function int_smaller_than_range(p_value int, p_check int4range)
returns boolean
as
$$
select p_value < lower(p_check);
$$
language sql;
create function int_greater_than_range(p_value int, p_check int4range)
returns boolean
as
$$
select p_value > upper(p_check);
$$
language sql;
Then create the operators:
create operator < (
procedure = int_smaller_than_range,
leftarg = int,
rightarg = int4range,
negator = >
);
create operator > (
procedure = int_greater_than_range,
leftarg = int,
rightarg = int4range,
negator = <
);
This can now be used like this:
select 4 > int4range(5,10); -> false
select 4 < int4range(4,10,'[]'); -> false
select 4 < int4range(4,10,'(]'); -> true
select 5 > int4range(4,10,'[]'); -> false
select 11 > int4range(4,10,'[]'); -> false
select 11 > int4range(4,10,'[)'); -> true

T-SQL Join on foreign key that has leading zero

I need to link various tables that each have a common key (a serial number in this case). In some tables the key has a leading zero e.g. '037443' and on others it doesn't e.g. '37443'. In both cases the serial refers to the same product. To confound things serial 'numbers' are not always just numeric e.g. may be "BDO1234", in these cases there is never a leading zero.
I'd prefer to use the WHERE statement (WHERE a.key = b.key) but could use joins if required. Is there any way to do this?
I'm still learning so please keep it simple if possible. Many thanks.
Based on the accepted answer in this link, I've written a small tsql sample to show you what I meant by 'the right direction':
Create the test table:
CREATE TABLE tblTempTest
(
keyCol varchar(20)
)
GO
Populate it:
INSERT INTO tblTempTest VALUES
('1234'), ('01234'), ('10234'), ('0k234'), ('k2304'), ('00034')
Select values:
SELECT keyCol,
SUBSTRING(keyCol, PATINDEX('%[^0]%', keyCol + '.'), LEN(keyCol)) As trimmed
FROM tblTempTest
Results:
keyCol trimmed
-------------------- --------------------
1234 1234
01234 1234
10234 10234
0k234 k234
k2304 k2304
00034 34
Cleanup:
DROP TABLE tblTempTest
Note that the values are alpha-numeric, and only leading zeroes are trimmed.
One possible drawback is that if there is a 0 after a white space it will not be trimmed, but that's an easy fix - just add ltrim:
SUBSTRING(LTRIM(keyCol), PATINDEX('%[^0]%', LTRIM(keyCol + '.')), LEN(keyCol)) As trimmed
You need to create a function
CREATE FUNCTION CompareSerialNumbers(#SerialA varchar(max), #SerialB varchar(max))
RETURNS bit
AS
BEGIN
DECLARE #ReturnValue AS bit
IF (ISNUMERIC(#SerialA) = 1 AND ISNUMERIC(#SerialB) = 1)
SELECT #ReturnValue =
CASE
WHEN CAST(#SerialA AS int) = CAST(#SerialB AS int) THEN 1
ELSE 0
END
ELSE
SELECT #ReturnValue =
CASE
WHEN #SerialA = #SerialB THEN 1
ELSE 0
END
RETURN #ReturnValue
END;
GO
If both are numeric then it compares them as integers otherwise it compares them as strings.

UDTF returning a Table on DB2 V5R4 with Dynamic SQL

I must to write a UDF returning a Table. I’ve done it with Static SQL.
I’ve created Procedures preparing a Dynamic and Complex SQL sentence and returning a cursor.
But now I must to create a UDF with Dynamic SQL and return a table to be used with an IN clause inside other select.
It is possible on DB2 v5R4? Do you have an example?
Thanks in advance...
I don't have V5R4, but I have i 6.1 and V5R3. I have a 6.1 example, and I poked around in V5R3 to find how to make the same example work there. I can't guarantee V5R4, but this ought to be extremely close. Generating the working V5R3 code into 'Run SQL Scripts' gives this:
DROP SPECIFIC FUNCTION SQLEXAMPLE.DYNTABLE ;
SET PATH "QSYS","QSYS2","SYSPROC","SYSIBMADM","SQLEXAMPLE" ;
CREATE FUNCTION SQLEXAMPLE.DYNTABLE (
SELECTBY VARCHAR( 64 ) )
RETURNS TABLE (
CUSTNBR DECIMAL( 6, 0 ) ,
CUSTFULLNAME VARCHAR( 12 ) ,
CUSTBALDUE DECIMAL( 6, 0 ) )
LANGUAGE SQL
NO EXTERNAL ACTION
MODIFIES SQL DATA
NOT FENCED
DISALLOW PARALLEL
CARDINALITY 100
BEGIN
DECLARE DYNSTMT VARCHAR ( 512 ) ;
DECLARE GLOBAL TEMPORARY TABLE SESSION.TCUSTCDT
( CUSTNBR DECIMAL ( 6 , 0 ) NOT NULL ,
CUSTNAME VARCHAR ( 12 ) ,
CUSTBALDUE DECIMAL ( 6 , 2 ) )
WITH REPLACE ;
SET DYNSTMT = 'INSERT INTO Session.TCustCDt SELECT t2.CUSNUM , (t2.INIT CONCAT '' '' CONCAT t2.LSTNAM) as FullName , t2.BALDUE FROM QIWS.QCUSTCDT t2 ' CONCAT CASE WHEN SELECTBY = '' THEN '' ELSE SELECTBY END ;
EXECUTE IMMEDIATE DYNSTMT ;
RETURN SELECT * FROM SESSION . TCUSTCDT ;
END ;
COMMENT ON SPECIFIC FUNCTION SQLEXAMPLE.DYNTABLE
IS 'UDTF returning dynamic table' ;
And in 'Run SQL Scripts', the function can be called like this:
SELECT t1.* FROM TABLE(sqlexample.dyntable('WHERE STATE = ''TX''')) t1
The example is intended to work over IBM's sample QCUSCDT table in library QIWS. Most systems will have that table available. The table function returns values from two QCUSCDT columns, CUSNUM and BALDUE, directly through two of the table function's columns, CUSTNBR and CUSTBALDUE. The third table function column, CUSTFULLNAME, gets its value by a concatenation of INIT and LSTNAM from QCUSTCDT.
However, the part that apparently relates to the question is the SELECTBY parameter of the function. The usage example shows that a WHERE clause is passed in and used to help built a dynamic 'INSERT INTO... SELECT...statement. The example shows that rows containingSTATE='TX'` will be returned. A more complex clause could be passed in or the needed condition(s) could be retrieved from somewhere else, e.g., from another table.
The dynamic statement inserts rows into a GLOBAL TEMPORARY TABLE named SESSION.TCUSTCDT. The temporary table is defined in the function. The temporary column definitions are guaranteed (by the developer) to match the 'RETURNS TABLE` columns of the table function because no dynamic changes can be made to any of those elements. This allows SQL to handle reliably columns returned from the function, and that lets it compile the function.
The RETURN statement simply returns whatever rows are in the temporary table after the dynamic statement completes.
The various field definitions take into account the somewhat unusual definitions in the QCUSTCDT file. Those don't make great sense, but they're useful enough.

SQL invalid conversion return null instead of throwing error

I have a table with a varchar column, and I want to find values that match a certain number. So lets say that column contains the following entries (except with millions of rows in real life):
123456789012
2345678
3456
23 45
713?2
00123456789012
So I decide I want all the rows which are numerically 123456789012 write a statement that looks something like this:
SELECT * FROM MyTable WHERE CAST(MyColumn as bigint) = 123456789012
It should return the first and last row, but instead the whole query blows up because it can't convert the "23 45" and "713?2" to bigint.
Is there another way to do the conversion that will return NULL for values that can't convert?
SQL Server does NOT guarantee boolean operator short-circuit, see On SQL Server boolean operator short-circuit. So all solution using ISNUMERIC(...) AND CAST(...) are fundamentally flawed (they may work, but hey can arbitrarily fail later dependiong on the generated plan). A better solution is using CASE, as Thomas suggests: CASE ISNUMERIC(...) WHEN 1 THEN CAST(...) ELSE NULL END. But, as gbn pointed out, ISNUMERIC is notoriously finicky in identifying what 'numeric' means and many cases where one would expect it to return 0 it returns 1. So mixing the CASE with the LIKE:
CASE WHEN MyRow NOT LIKE '%[^0-9]%' THEN CAST(MyRow as bigint) ELSE NULL END
But the real problem is that if you have millions of rows and you have to search them like this, you'll always end up scanning end-to-end since the expression is not SARG-able (no matter how we rewrite it). The real issue here is data purity, and should be addressed at the appropriate level, where the data is populated. Another thing to consider is if is possible to create a persisted computed column with this expression and create a filtered index on it which eliminates NULL (ie. non-numeric). That would speed up things a little.
If you are using SQL Server 2012 you can use the 2 new methods:
TRY_CAST()
TRY_CONVERT()
Both methods are equivalent. They return a value cast to the specified data type if the cast succeeds; otherwise, returns null. The only difference is that CONVERT is SQL Server specific, CAST is ANSI. using CAST will make your code more portable (although not sure if any other database provider implements TRY_CAST)
ISNUMERIC will accept empty string and values like 1.23 or 5E-04 so could be unreliable.
And you don't know what order things will be evaluated in so it could still fail (SQL is declarative, not procedural, so the WHERE clause probably won't be evaluated left to right)
So:
you want to accept value that consist only of the characters 0-9
you need to materialise the "number" filter so it's applied before CAST
Something like:
SELECT
*
FROM
(
SELECT TOP 2000000000 *
FROM MyTable
WHERE MyColumn NOT LIKE '%[^0-9]%' --double negative rejects anything except 0-9
ORDER BY MyColumn
) foo
WHERE
CAST(MyColumn as bigint) = 123456789012 --applied after number check
Edit: quick example that fails.
CREATE TABLE #foo (bigintstring varchar(100))
INSERT #foo (bigintstring )VALUES ('1.23')
INSERT #foo (bigintstring )VALUES ('1 23')
INSERT #foo (bigintstring )VALUES ('123')
SELECT * FROM #foo
WHERE
ISNUMERIC(bigintstring) = 1
AND
CAST(bigintstring AS bigint) = 123
SELECT *
FROM MyTable
WHERE ISNUMERIC(MyRow) = 1
AND CAST(MyRow as float) = 123456789012
The ISNUMERIC() function should give you what you need.
SELECT * FROM MyTable
WHERE ISNUMERIC(MyRow) = 1
AND CAST(MyRow as bigint) = 123456789012
And to add a case statement like Thomas suggested:
SELECT * FROM MyTable
WHERE CASE(ISNUMERIC(MyRow)
WHEN 1 THEN CAST(MyRow as bigint)
ELSE NULL
END = 123456789012
http://msdn.microsoft.com/en-us/library/ms186272.aspx
SELECT *
FROM MyTable
WHERE (ISNUMERIC(MyColumn) = 1) AND (CAST(MyColumn as bigint) = 123456789012)
Additionally you can use a CASE statement in order to get null values.
SELECT
CASE
WHEN (ISNUMERIC(MyColumn) = 1) THEN CAST(MyColumn as bigint)
ELSE NULL
END AS 'MyColumnAsBigInt'
FROM tableName
If you require additional filtering, for numerics which are not valid to be cast to bigint, you can use the following instead of ISNUMERIC:
PATINDEX('%[^0-9]%',MyColumn)) = 0
If you need decimal values instead of integers, cast to float instead and change the regex to '%[^0-9.]%'