Is there an equivalent to Oracle's BITAND() function in Postgres? - sql

I know there is a Postgres function BIT_AND(), which is not the same thing - in fact it is the opposite of my end goal. I would like to be able to decode the results of an operation like Postgres BIT_AND() back into the original bits. I have code that does this in Oracle, like so:
select NVL(DECODE(BITAND(foo.bar, POWER (2, 1)), POWER (2, 1), 1), 0) first_bit,
NVL(DECODE(BITAND(foo.bar, POWER (2, 2)), POWER (2, 2), 1), 0) second_bit
from
(select 1234 bar from dual
union select 12345 bar from dual) foo
If the first bit of foo.bar is set, first_bit would return 1, otherwise 0, etc. I can translate everything to Postgres except BITAND(), and the only thing I've found discussing the issue online is a thread on forums.devshed.com from 2010 with zero responses - appreciate any insight.
EDIT: Answered below, thank you! Here are the tweaks I needed to make on my side to make it work, all because I'm actually dealing with up to 43 options that are coded as bits, and I really want to be able to use the power() function to make it very clear which bit I'm going for, so I had to convert data types.
select ((foo.bar & power(2,1)::bigint) > 0)::int as first_bit,
((foo.bar & power(2,43)::bigint) > 0)::int as forty_third_bit
from (select 1031 as bar union all
select 8796160131072
) foo

In Postgres, the bitwise and operator is &.
You would seem to want something like this:
select ((foo.bar & 2) > 0)::int as first_bit,
((foo.bar & 4) > 0)::int as second_bit
from (select 1234 as bar union all
select 12345
) foo;
I'm not sure why you are counting the bits from the second one, but the operator does the same thing as the Oracle function.
Here is a SQL Fiddle that better illustrates the operator.

Related

Db2 locate_in_string equivalent in PostgreSQL

while migration from DB2 to PostgreSQL, i found some views using db2's locate_in_string() function, which returns the position of a specified instance of a given substring.
For example:
LOCATE_IN_STRING('aaabaabbaaaab','b',1,3); -- returns 8, for the 3d instance of 'b'
LOCATE_IN_STRING('aaabaabbaaaab','b',1,1); -- returns 4, for the 1st instance of 'b'
Unfortunately PostgreSQLs function position() gives me only the position for the first instance.
I didn't find something similar in PostgreSQL.
Is there any alternative or workaround (maybe regex?)?
There may be a different method, this is rather brute force.
It splits the string based on the pattern you are looking for. It then adds up the length of the pieces:
select v.*,
(select coalesce(sum(length(el)), 0) + count(*) * length(v.splitter)
from unnest( (regexp_split_to_array(v.val, v.splitter))[1:v.n] ) el
) as pos
from (values ('aaabaabbaaaab', 3, 'b'), ('aaabaabbaaaab', 1, 'b')
) v(val, n, splitter);

Select hardcoded values in Informix DB

I need to select hardcoded values in one column, so I will be able to join them with table in Informix DB. So I try in different variations to do something like this:
select a from ( values (1), (2), (3) ) ;
And I expect to get results:
1
2
3
I think in other DB this or some other variations that I tried would return the values. However, in Informix it does not work.
Could anyone suggest the solution working in Informix please?
Although what Gordon Linoff suggests will certainly work, there are also more compact notations available using Informix-specific syntax.
For example:
SELECT a
FROM TABLE(SET{1, 2, 3}) AS t(a)
This will generate a list of integers quite happily (and succinctly). You can use LIST or MULTISET in place of SET. A MULTISET can have repeated elements, unlike a SET; a LIST preserves order as well as allowing repeats.
Very often, you won't spot order not being preserved with simple values — just a few items in the list. Order is not guaranteed for SET or MULTISET; if order matters, use LIST.
You can find information about this in the IBM Informix 12.10 manual under Collection Constructors. No, it isn't obvious how you get to it — I started at SELECT, then FROM, then 'Selecting from a collection variable' and thence to 'Expression'; I spent a few seconds staring blankly at that, then looked at 'Constructor expressions' and hence 'Collection Constructors'.
INSERT INTO cccmte_pp ( cmte, pref, nro, eje, id_tri, id_cuo, fecha, vto1, vto2, id_tit, id_suj, id_bie, id_gru )
SELECT *
FROM TABLE (MULTISET {
row('RC', 4, 10, 2020, 1, 5, MDY(05,20,2020), MDY(05,20,2020),MDY(05,27,2020),101, 1, 96, 1 ),
row('RC', 4, 11, 2020, 1, 5, MDY(05,20,2020), MDY(05,20,2020),MDY(05,27,2020),101, 1, 96, 1 )
})
AS t( cmte, pref, nro, eje, id_tri, id_cuo, fecha, vto1, vto2, id_tit, id_suj, id_bie, id_gru )
IS SIMPLE SOLUTION FOR BULK INSERT, and SELECT PART SOLVING THE REST!
IS VERY SIMPLE! :) ENJOY
Informix requires an actual query statement. I think this will work:
select a
from (select 1 as a from systables where tabid = 1 union all
select 2 as a from systables where tabid = 1 union all
select 3 as a from systables where tabid = 1
) t;

Select Where Like regular expression

I need to create a SQL Query.
This query need to select from a table where a column contains regular expression.
For example, I have those values:
TABLE test (name)
XHRTCNW
DHRTRRR
XHRTCOP
CPHCTPC
CDDHRTF
PEOFOFD
I want to select all the data who have "HRT" after 1 char (value 1, 2 and 3 - Values who looks like "-HRT---") but not those who might have "HRT" after 1 char (value 5).
So I'm not sure how to do it because a simple
SELECT *
FROM test
WHERE name LIKE "%HRT%"
will return value 1, 2, 3 and 5.
Sorry if I'm not really clear with what I want/need.
You can also change the pattern. Instead of using % which means zero-or-more anything, you can use _ which means exactly one.
SELECT * FROM test WHERE name like '_HRT%';
You can use substring.
SELECT * FROM test WHERE substring(name from 2 for 3) = 'HRT'
Are the names always 7 letters? Do:
SELECT substring (2, 4, field) from sometable
That will just select the 2-4th characters and then you can use like "%HRT"

Is this simple UPDATE SQL an error waiting to happen? How to rewrite it?

I need to examine ACCT_NUMS values om TABLE_1. If the ACCT_NUM is prefixed by "GF0", then I need to disregard the "GF0" prefix and take the rightmost 7 characters of the remaining string. If this resulting value is not found in account_x_master or CW_CLIENT_STAGE, then, the record is to be flagged as an error.
The following seems to do the trick, but I have a concern...
UPDATE
table_1
SET
Error_Ind = 'GW001'
WHERE
LEFT(ACCT_NUM, 3) = 'GF0'
AND RIGHT(SUBSTRING(ACCT_NUM, 4, LEN(ACCT_NUM) - 3), 7) NOT IN
(
SELECT
acct_num
FROM
account_x_master
)
AND RIGHT(SUBSTRING(ACCT_NUM, 4, LEN(ACCT_NUM) - 3), 7) NOT IN
(
SELECT
CW_CLIENT_STAGE.AGS_NUM
FROM
dbo.CW_CLIENT_STAGE
)
My concern is that SQL Server may attempt to perform a SUBSTRING operation
SUBSTRING(ACCT_NUM, 4, LEN(ACCT_NUM) - 3)
that results in a computed negative value and causing the SQL to fail. Of course, this wouldn't fail is the SUBSTRING operation were only applied to those records that we at least 3 characters long, which would always be the case if the
LEFT(ACCT_NUM, 3) = 'GF0'
were applied first. If possible, I'd like to avoid adding new columns to the table. Bonus points for simplicity and less overhead :-)
How can I rewrite this UPDATE SQL to protect against this?
As other people said, your concern is valid.
I'd make two changes to your query.
1) To avoid having negative value in the SUBSTRING parameter we can rewrite it using STUFF:
SUBSTRING(ACCT_NUM, 4, LEN(ACCT_NUM) - 3)
is equivalent to:
STUFF(ACCT_NUM, 1, 3, '')
Instead of extracting a tail of a string we replace first three characters with empty string. If the string is shorter than 3 characters, result is empty string.
By the way, if your ACCT_NUM may end with space(s), they will be trimmed by the SUBSTRING version, because LEN doesn't count trailing spaces.
2) Instead of
LEFT(ACCT_NUM, 3) = 'GF0'
use:
ACCT_NUM LIKE 'GF0%'
If you have an index on ACCT_NUM and only relatively small number of rows start with GF0, then index will be used. If you use a function, such as LEFT, index can't be used.
So, the final query becomes:
UPDATE
table_1
SET
Error_Ind = 'GW001'
WHERE
ACCT_NUM LIKE 'GF0%'
AND RIGHT(STUFF(ACCT_NUM, 1, 3, ''), 7) NOT IN
(
SELECT
acct_num
FROM
account_x_master
)
AND RIGHT(STUFF(ACCT_NUM, 1, 3, ''), 7) NOT IN
(
SELECT
CW_CLIENT_STAGE.AGS_NUM
FROM
dbo.CW_CLIENT_STAGE
)
You have a very valid concern, because SQL Server will rearrange the order of evaluation of expressions in the WHERE.
The only way to guarantee the order of operations in a SQL statement is to use case. I don't think there is a way to catch failing calls to substring() . . . there is no try_substring() analogous to try_convert().
So:
WHERE
LEFT(ACCT_NUM, 3) = 'GF0' AND
(CASE WHEN LEN(ACCT_NUM) > 3 THEN RIGHT(SUBSTRING(ACCT_NUM, 4, LEN(ACCT_NUM) - 3), 7) END) NOT IN (SELECT acct_num
FROM account_x_master
) AND
(CASE WHEN LEN(ACCT_NUM) > 3 THEN RIGHT(SUBSTRING(ACCT_NUM, 4, LEN(ACCT_NUM) - 3), 7) END) NOT IN (SELECT CW_CLIENT_STAGE.AGS_NUM
FROM dbo.CW_CLIENT_STAGE
)
This is uglier. And, there may be ways around it, say by using LIKE with wildcards rather than string manipulation. But, the case will guarantee that the SUBSTRING() is only run on strings long enough so no error is generated.
Please try the below query.
Since there is no short circuit and or in SQL WHERE clause, only way to achieve is via CASE syntax.
I noticed that you had two NOT IN comparisons in different parts of WHERE which I combined into one.
Note that CASE condition is >=3 and not >3, as RIGHT('',x) is allowed.
Also note the proper use of CASE with NOT IN
UPDATE table_1
SET
Error_Ind = 'GW001'
select * from table_1
WHERE
LEFT(ACCT_NUM, 3) = 'GF0'
AND CASE
WHEN LEN(ACCT_NUM)>=3
THEN RIGHT(SUBSTRING(ACCT_NUM, 4, LEN(ACCT_NUM) - 3), 7)
ELSE NULL END NOT IN
(
SELECT acct_num as num
FROM account_x_master
UNION
SELECT CW_CLIENT_STAGE.AGS_NUM as num
FROM dbo.CW_CLIENT_STAGE
)

Base 36 to Base 10 conversion using SQL only

A situation has arisen where I need to perform a base 36 to base 10 conversion, in the context of a SQL statement. There doesn't appear to be anything built into Oracle 9, or Oracle 10 to address this sort of thing. My Google-Fu, and AskTom suggest creating a pl/sql function to deal with the task. That is not an option for me at this point. I am looking for suggestions on an approach to take that might help me solve this issue.
To put this into a visual form...
WITH
Base36Values AS
(
SELECT '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' myBase36 FROM DUAL
),
TestValues AS
(
SELECT '01Z' BASE36_VALUE,
71 BASE10_VALUE FROM DUAL
)
SELECT *
FROM Base36Values,
TestValues
I am looking for something to calculate the value 71, based on the input 01Z.
EDIT - that is backwards... given 01Z translate it to 71.
As a bribe, each useful answer gets a free upvote.
Thanks
Evil.
select sum(position_value) from
(
select power(36,position-1) * case when digit between '0' and '9'
then to_number(digit)
else 10 + ascii(digit) - ascii('A')
end
as position_value
from (
select substr(input_string,length(input_string)+1-level,1) digit,
level position
from (select '01Z' input_string from dual)
connect by level <= length(input_string)
)
)
For T-SQL the following logic will perform the task that the Oracle code above does. This is generic general solution and will support Base-X to Base-10:
select
sum(power(base,pos-1) *
case when substring(cnv,pos,1) between '0' and '9' then
cast(substring(cnv,pos,1) as int)
else 10 + ascii(upper(substring(cnv,pos,1))) - ascii('A') end)
from (values(reverse('01Z'), 36)) as t(cnv,base)
left join (values(1),(2),(3),(4),(5),(6)) as x(pos)
on pos <= len(cnv)
To use with other bases just use:
from (select cnv = reverse('FF'), base=16) as t
or
from (select cnv = reverse('101'), base=2) as t
Note that to support strings longer than 6 you would need to add more values to the position vector.