How to generate random string for all rows in postgres - sql

I have foo table and would like to set bar column to a random string. I've got the following query:
update foo
set bar = select string_agg(substring('0123456789bcdfghjkmnpqrstvwxyz', round(random() * 30)::integer, 1), '')
from generate_series(1, 9);
But it generates the random string once and reuse it for all rows. How can I make it to generate one random string for each row?
I know I can make it a function like this:
create function generate_bar() returns text language sql as $$
select string_agg(substring('0123456789bcdfghjkmnpqrstvwxyz', round(random() * 30)::integer, 1), '')
from generate_series(1, 9)
$$;
and then call the function in the update query. But I'd prefer to do it without a function.

The problem is that the Postgres optimizer is just too smart and deciding that it can execute the subquery only once for all rows. Well -- it is really missing something obvious -- the random() function makes the subquery volatile so this is not appropriate behavior.
One way to get around this is to use a correlated subquery. Here is an example:
update foo
set bar = array_to_string(array(select string_agg(substring('0123456789bcdfghjkmnpqrstvwxyz', round(random() * 30)::integer, 1), '')
from generate_series(1, 9)
where foo.bar is distinct from 'something'
), '');
Here is a db<>fiddle.

For a random mixed-case numeric-inclusive string containing up to 32 characters use:
UPDATE "foo" SET "bar"= substr(md5(random()::text), 0, XXX);
and replace XXX with the length of desired string plus one.
To replace all with length 32 strings, Example:
UPDATE "foo" SET "bar"= substr(md5(random()::text), 0, 33);
14235ccd21a408149cfbab0a8db19fb2 might be a value that fills one of the rows. Each row will have a random string but not guaranteed to be unique.
For generating strings with more than 32 characters
Just combine the above with a CONCAT

Related

How to replace characters at specific position in several words using REGEX_REPLACE

I have a query similar to this:
SELECT YEAR_CODE FROM YEAR_CODES
and it returns several records: typically 1 but sometimes 2 or 3. The returned records look like this: 2018FOO, 2019BAR
I need to get the matching previous year of the returned codes. For instance:
2018FOO becomes 2017FOO
2019BAR becomes 2018BAR
Looking for something similar to:
REGEX_REPLACE(SELECT YEAR_CODE FROM YEAR_CODES, 4th character, 4th character minus 1)
You don't need regexp_replace(), using substr() string operator with concat() function (or concatenation operators ||) is enough :
with year_codes(year_code) as
(
select '2018FOO' from dual union all
select '2019BAR' from dual
)
select concat(substr(year_code,1,4) - 1,substr(year_code,-3)) as year_code
from year_codes;
YEAR_CODE
---------
2017FOO
2018BAR
to_number() conversion is redundant, since Oracle implicitly considers a string as a number which is completely composed of digits for an arithmetic operation.
You can do use string operations:
with c as (
<your query here>
)
select
from year_code yc
where to_number(substr(yc.code, 1, 4)) = to_number(substr(c.code)) - 1 and
substr(yc.code, 5) = substr(c.code, 5)

Generate sequential number in SQL - not by using Identity

I am working on a task where my query will produce a fixed width column. One of the fields in the fixed width column needs to be a sequentially generated number.
Below is my query:
select
_row_ord = 40,
_cid = t.client_num,
_segment = 'ABC',
_value =
concat(
'ABC*',
'XX**', --Hierarchical ID number-this field should be sequentially generated
'20*',
'1*','~'
)
from #temp1 t
My output:
Is there a way to declare #num as a parameter that generates number sequentially?
PS: The fields inside the CONCAT function is all hardcoded. Only the 'XX' i.e., the sequential number has to be dynamically generated
Any help?!
You could create a SEQUENCE object, then call the NEXT VALUE FOR the SEQUENCE in your query.
Something along these lines:
CREATE SEQUENCE dbo.ExportValues
START WITH 1
INCREMENT BY 1 ;
GO
And then:
select
_row_ord = 40,
_cid = t.client_num,
_segment = 'ABC',
_value =
concat(
'ABC*',
RIGHT(CONCAT('000000000000000', NEXT VALUE FOR dbo.ExportValues),15)
'**',
'20*',
'1*','~'
)
from #temp1 t
You'd have to tweak how many zeros there are for the padding and how many digits to trim it to for your requirements. If duplicate values are ok, you could have the SEQUENCE reset periodically. See the documentation for more on that. It's just another line in the CREATE statement.
You can use row_number() -- made a little more complicated because you are zero-padding it:
select _row_ord = 40, _cid = t.client_num, _segment = 'ABC',
_value = concat('ABC*',
right('00' + convert(varchar(255), row_number() over (order by ?)), 2),
'XX**', --Hierarchical ID number-this field should be sequentially generated
'20*',
'1*','~'
)
from #temp1 t;
Note that the ? is for the column that specifies the ordering. If you don't care about the ordering of the numbers, use (select null) in place of the ?.

How to multi-split a string (WHERE IN query)

I am passing a string as param (via massivejs) into my query. The string is formatted as: param = 'red, blue, green'. The param itself does not have a fixed length (',' being the delimiter) as it is populated through what the user sends in (but is maxed out at 10 elements).
How would I break the string down into individual strings inside my query?
Eg of what I am trying to do:
SELECT * FROM table
WHERE name IN (param);
I know this works but is very very crude:
SELECT * FROM table
WHERE name IN (split_part(param, ',', 1), split_part(param, ',', 2) .......)) -- keep going.
Essentially I want to have ('red', 'blue', 'green' ....) inside the IN parenthesis. Is there a nicer way of accomplishing this?
You could use the string_to_array function to split the string to an array and then use the any function to check if your element is contained in it:
SELECT *
FROM mytable
WHERE name = ANY(STRING_TO_ARRAY(param, ','));

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"

Select a portion of a comma delimited string in DB2/DB2400

I need to select a value within a comma delimited string using only SQL. Is this possible?
Data
A B C
1 Luigi Apple,Banana,Pineapple,,Citrus
I need to select specifically the 2nd item in column C, in this case banana. I need help. I cannot create new SQL functions, I can only use SQL. This is the as400 so the SQL is somewhat old tech.
Update..
With help from #Sandeep we were able to come up with
SELECT xmlcast(xmlquery('$x/Names/Name[2]' passing xmlparse(document CONCAT(CONCAT('<?xml version="1.0" encoding="UTF-8" ?><Names><Name>',REPLACE(ODWDATA,',','</Name><Name>')),'</Name></Names>')) as "x") as varchar(1000)) FROM ACL00
I'm getting this error
Keyword PASSING not expected. Valid tokens: ) ,.
New update. Problem solved by using UDF of Oracle's INSTR
I'm assuming db2 which I don't use, so the following syntax may not be bang on but the approach works.
In Oracle I'd use INSTR() and SUBSTR(), Google suggests LOCATE() and SUBSTR() for db2
Use LOCATE to get the position of the first comma, and use that value in SUBSTR to grab the end of YourColumn starting after the first comma
SUBSTR(YourColumn, LOCATE(YourColumn, ',') + 1)
You started with "Apple,Banana,Pineapple,,Citrus", you should now have "Banana,Pineapple,,Citrus", so we use LOCATE and SUBSTR again on the string returned above.
SUBSTR(SUBSTR(YourColumn, LOCATE(YourColumn, ',') + 1), 1, LOCATE(SUBSTR(YourColumn, LOCATE(YourColumn, ',') + 1), ',') - 1)
First SUBSTR is getting the right hand side of the string so we only need a start position parameter, second SUBSTR is grabbing the left side of the string so we need two, the start position and the length to return.
If you want 2nd item only than you can use substring function:
DECLARE #TABLE TABLE
(
A INT,
B VARCHAR(100),
C VARCHAR(100)
)
DECLARE #NTH INT = 3
INSERT INTO #TABLE VALUES (1,'Luigi','Apple,Banana,Pineapple,,Citrus')
SELECT REPLACE(REPLACE(CAST(CAST('<Name>'+ REPLACE(C,',','</Name><Name>') +'</Name>' AS XML).query('/Name[sql:variable("#NTH")]') AS VARCHAR(1000)),'<Name>',''),'</Name>','') FROM #TABLE
I am answering my own question now. It is impossible to do this with the built in functions within AS400
You have to create an UDF of Oracle's INSTR
Enter this within STRSQL it will create a new function called INSTRB
CREATE FUNCTION INSTRB (C1 VarChar(4000), C2 VarChar(4000), N integer, M integer)
RETURNS Integer
SPECIFIC INSTRBOracleBase
LANGUAGE SQL
CONTAINS SQL
NO EXTERNAL ACTION
DETERMINISTIC
BEGIN ATOMIC
DECLARE Pos, R, C2L Integer;
SET C2L = LENGTH(C2);
IF N > 0 THEN
SET (Pos, R) = (N, 0);
WHILE R < M AND Pos > 0 DO
SET Pos = LOCATE(C2,C1,Pos);
IF Pos > 0 THEN
SET (Pos, R) = (Pos + 1, R + 1);
END IF;
END WHILE;
RETURN (Pos - 1)*(1-SIGN(M-R));
ELSE
SET (Pos, R) = (LENGTH(C1)+N, 0);
WHILE R < M AND Pos > 0 DO
IF SUBSTR(C1,Pos,C2L) = C2 THEN
SET R = R + 1;
END IF;
SET Pos = Pos - 1;
END WHILE;
RETURN (Pos + 1)*(1-SIGN(M-R));
END IF;
END
Then to select the nth delimited value within a comma delimited string... in this case the 14th
use this query utilizing the new function
SELECT SUBSTRING(C,INSTRB(C,',',1,13)+1,INSTRB(C,',',1,14)-INSTRB(C,',',1,13)-1) FROM TABLE
A much prettier solution IMO would be to encapsulate a Recursive Common Table Expression (recursive CTE aka RCTE) of the data from the column C to generate a result TABLE [i.e. a User Defined Table Function (a Table UDF aka UDTF)] then use a Scalar Subselect to choose which effective record\row number.
select
a
, b
, ( select S.token_vc
from table( split_tokens(c) ) as S
where S.token_nbr = 2
) as "2nd Item of column C"
from The_Table /* in OP described with columns a,b,c but no DDL */
Yet prettier would be to make the result of that same RCTE a scalar value, so as to allow being invoked simply as a Scalar UDF with the effective row number [as another argument] defining specifically which element to select.
select
a
, b
, split_tokens(c, 2) as "2nd Item of column C"
from The_Table /* in OP described with columns a,b,c but no DDL */
The latter could be more efficient, limiting the row-data produced by the RCTE, to only the desired numbered token and those preceding numbered tokens. I can not comment on the efficiency with regard to impacts on CPU and storage as contrasted with any of the other answers offered, but my own experience with the temporary-storage implementation and the overall quickness of the RCTE results has been positive especially when other row selection limits the number of derived-table results that must be produced for the overall query request.
The UDF [and\or UDTF and the RCTE that implements them] is left as an exercise for the reader; mostly, because I do not have a system on a release that has support for recursive table expressions. If asked [e.g. in a comment to this answer], I could provide untested code source.
I have found the locate_in_string function to work very well in this case.
select substr(
c,
locate_in_string(c, ',')+1,
locate_in_string(c, ',', locate_in_string(c, ',')+1) - locate_in_string(c, ',')-1
) as fruit2
from ACL00 for read only with ur;