Postgres generate a unique username - sql

I have a requirement where I need to generate to unique usernames. I know how I would do it server side but want to make it as efficient as possible and do it on the database as part of a procedure but unsure where to start as I don't work with SQL too often.
I want to be able to create a username using the first letter of their forename and their surname plus some numbers and if a user already exists with that username then update it by 1. e.g.
I have users
Bob Jones bjones1
Bill Jones bjones2
If a user Beatrice Jones is added then that will be bjones3.
However users can choose to pick their username so someone may have already chosen bjones3 so I want it to automatically generate bjones4.
If there are no user B Jones then the first username created should be bjones
Ideally this would all happen as part of the user insert into the database so my Go code just executes a single database call and the user gets inserted.
Update:
When a user chooses their own username I check if it exists and block them from using it if it exists.

Hmmm . . . I think this will do:
select 'bjones' || coalesce(lpad( ((regexp_match(max(username), '[0-9]+$'))[1]::int + 1)::text, 5, '0'), '00000')
from t
where username ~ '^bjones[0-9]{5}$';
Here is a db<>fiddle.

More strict and parameterized, with 4-digit sequential number.
with
unp as -- user name prefix
(select left(lower(:givenname), 1) || lower(:surname) unp),
maxno as -- max existing seq.number for this username prefix
(
select coalesce(max((regexp_match(username, '(\d+)$'))[1]::integer), 0) maxno
from usernames
where username ~ ('^'||(select unp from unp)||'\d+$')
)
select (select unp from unp) || to_char((select maxno + 1 from maxno), 'FM0009') username;

I did something similar with the following code:
_username = left(lower(_firstname), 1) || lower(_lastname);
PERFORM id FROM users WHERE username = _username;
IF FOUND THEN
FOR i in 1 .. 999
LOOP
_username := _username || i;
PERFORM id FROM users WHERE username = _username;
IF NOT FOUND THEN
EXIT;
END IF;
END LOOP;
END IF;

Related

How AND/OR operators works? [duplicate]

This question already has answers here:
SQL Logic Operator Precedence: And and Or
(5 answers)
Closed 4 years ago.
I'm Reading book about sql and I see some statements using or/and and I don't understand them:
this is the main statement:
SELECT ∗
FROM administrators
WHERE username = ’’ AND password = ’’;
if some one try to do an sql bypass , he will do this:
SELECT ∗
FROM administrators
WHERE username = ” OR ‘1’=‘1’ AND password = ”;
or this
SELECT ∗
FROM administrators
WHERE (username = ’’) OR (‘1’=‘1’ AND password = ’’);
how these 2 statements get the same results, I don't understand how AND/OR works in theses statements ..
and the last question how these statements return all value in database (bypass the auth):
select *
from users
where (username = '') or (1=1) or (1=1 AND password = '') ;
OR
SELECT ∗
FROM administrators
WHERE username = ’’ AND
password = ’’ OR
1’=‘1’;
In simple explanations:
SELECT ∗ FROM administrators WHERE username = ” OR ‘1’=‘1’ AND password = ”;
Here if the username exists and password is wrong, it will return all columns for that username else returns nothing.
SELECT ∗ FROM administrators WHERE (username = ’’) OR (‘1’=‘1’ AND password = ’’);
This returns the same thing as the above, the brackets don't matter.
SELECT ∗ FROM administrators WHERE username = ’’AND password = ’’ OR ‘1’=‘1’ ;
This makes a difference, even if both username and password are wrong, it will return the full * columns. [best option for a SQL injection for full table data]
select * from users where (username = '') or (1=1) or (1=1 AND password = '') ;
same results as above
Its easy to think of it this way:
any AND/OR condition introduced after where is paired for the first constraint, any other introduced after that is a constraint of its own.
WHERE condition1 OR/AND condition1-pair AND separate condition
You can consider AND as multiplication, OR as addition, true statement as 1 and false statement as 0. For example statement
SELECT .... WHERE y = 0 AND x < 1 OR 1 = 1
will always be true, because
1*1 + 1 = 1
0*0 + 1 = 1
1*0 + 1 = 1
(0 and 1 are Boolean, not decimal)

How to decrypt PBEWithMD5AndDES String in Oracle?

In my user table, the username column is encoded via PBEWithMD5AndDES algorithm. Now I want something like this:
WITH TEMP
AS (SELECT E.UAC_USER_ID AS ID,
E.UAC_USER_USERNAME AS USERNAME,
E.UAC_USER_FIRSTNAME || ' ' || E.UAC_USER_LASTNAME AS NAME,
E.UAC_USER_PERSONELCODE AS PERSONELCODE
FROM UAC_USERS E
WHERE E.UAC_USER_ISENABLED = 1)
SELECT *
FROM TEMP
WHERE 1 = 1 AND DECODE (USERNAME) = 'admin'
is there any embedded function in oracle that can decode the selected field?
I can only give you direction to solve problem, not answer.
Oracle has package dbms_crypto which may be can help you.
See examples https://docs.oracle.com/database/121/ARPLS/d_crypto.htm#ARPLS65690
You can find encryption_type
encryption_type PLS_INTEGER := -- total encryption type
DBMS_CRYPTO.ENCRYPT_AES256
+ DBMS_CRYPTO.CHAIN_CBC -- description in doc
+ DBMS_CRYPTO.PAD_PKCS5; -- description in doc
In DBMS_CRYPTO there is another constant DBMS_CRYPTO.ENCRYPT_PBE_MD5DES.
Theoretically this may help:
encryption_type PLS_INTEGER := -- total encryption type
DBMS_CRYPTO.ENCRYPT_PBE_MD5DES
+ DBMS_CRYPTO.CHAIN_CBC -- description in doc
+ DBMS_CRYPTO.PAD_PKCS5; -- description in doc
But using this encryption_type in code I get error. I also try several combination and change code but finally i give up. May be one of the reason is the constant DBMS_CRYPTO.ENCRYPT_PBE_MD5DES doesn't described in doc or I should change smt else in example. .

Oracle UPDATE has no effect

I am working on Sql Developper an I created the following procedure in a package:
PROCEDURE VALIDER(a_session IN NUMBER) AS
i NUMBER;
TYPE type_tab IS TABLE OF PANIER%ROWTYPE;
tabSeances type_tab;
BEGIN
SELECT * BULK COLLECT INTO tabSeances
FROM PANIER
WHERE a_session = sessionweb;
i:=0;
FOR i IN 1 .. tabSeances.count LOOP
-- UPADTE DU NOMBRE DE PLACES LIBRES
BEGIN
UPDATE PROJECTION
SET remaining_seats = (remaining_seats - tabseances(i).nbrplaces)
WHERE num_copy = tabseances(i).num_copy
AND day = tabseances(i).dateseance
AND time_slot = tabseances(i).time_slot
AND movie = tabseances(i).movie;
COMMIT;
--UPDATE ON PANIER
UPDATE PANIER
SET valide = 1
WHERE sessionweb = a_session
AND num_copy = tabseances(i).num_copy
AND dateseance = tabseances(i).dateseance
AND time_slot = tabseances(i).time_slot
AND movie = tabseances(i).movie;
COMMIT;
EXCEPTION
WHEN NO_DATA_FOUND THEN raise_application_error(-20035, 'Pas de données');
WHEN OTHERS THEN raise_application_error(-20006,'Autres Erreurs');
END;
END LOOP;
END VALIDER;
The procedure executes normaly and I don't get an error.
I have a kind of product cart: "PANIER". I loop all the entries in thsi cart for one person (session) to validate them in the database and decrement the total number of seats.
But the field "remaining-seats" (from PROJECTIONS) in the first update don't work. The field isn't updated. I have already tried with other values but nothing.
I am sure that the procedure is executetd because the second update still works. It marks the cart entry as "CONFIRMED".
I don't have any trigger on this field.
My tables contains valid data (<>NULL).
I execute this procedure like this (in a BEGIN END; block):
CMDPLACES.VALIDER(1);
Thank for your reply.
Is it day or dateseance in your first update?
UPDATE PROJECTION
SET remaining_seats = (remaining_seats - tabseances(i).nbrplaces)
WHERE num_copy = tabseances(i).num_copy
AND dateseance = tabseances(i).dateseance
AND time_slot = tabseances(i).time_slot
AND movie = tabseances(i).movie;
Also as #ThorstenKettner was mentioning, the timestamp data in the date , may fail while comparing, so we have TRUNCATE the timestamp data using TRUNC() [if needed]!
If the date column is indexed, beware the index will not be used by the database .
To handle NO Data in UPDATE, you can check (SQL%ROWCOUNT > 0) to identify the number of rows updated!
Your first update compares days. What data type are these? In case you deal with DATETIME, make sure to compare without the time part if any. Use TRUNC to achieve this.
AND TRUNC(day) = TRUNC(tabseances(i).dateseance)

Firebird Update append

So what I need is to append a string in a column on a firebird database everytime I run the update, I'm trying:
update clients set cliobs = coalesce(cliobs, '') || 'newstring' where cod = 1
I get the 1 record(s) was(were) updated but the end result is:
coalesce(cliobs, ') || newstring
Any ideas how to do this?

Running a query and displaying result it in a TEdit

I have a TComboBox containing a list of names gathered from my database. Next to it is a TEdit that I intend on using for the purposes for displaying the ID number associated to each person.
Since firstName and lastName are separate fields within the table, but displayed together in the TCombobox I have written a small section to split the firstName and lastName into two separate variables:
pos := AnsiPos(' ', cbStudents.Text);
firstName := Copy(cbStudents.Text, 0, pos-1);
lastName := Copy(cbStudents.Text, pos+1, Length(cbStudents.Text));
Then I execute the SQL code:
try
query.Open;
query.SQL.Add('Select studentID');
query.SQL.Add('From student');
query.SQL.Add('Where firstName = ' + StrToQuote(firstName));
query.SQL.Add('And lastName = ' + StrToQuote(lastName));
editID.Text := query
finally
query.Free;
end;
Note: StrToQuote encapsulates the variable firstName and lastName with double quotes (" ")
The error that I am receiving is:
Argument out of range
What am I doing wrong? Thank you in advanced for the help.
Your code can not work. It opens the query first, then it sets the SQL query string. Instead of
try
query.Open;
query.SQL.Add('Select studentID');
query.SQL.Add('From student');
query.SQL.Add('Where firstName = ' + StrToQuote(firstName));
query.SQL.Add('And lastName = ' + StrToQuote(lastName));
finally
query.Free;
end;
use
// create or reset query here
query := ...
try
query.SQL.Add('SELECT studentID');
query.SQL.Add('FROM student');
query.SQL.Add('WHERE firstName = :firstname');
query.SQL.Add('AND lastName = :lastName');
// set parameter values here
query.Open;
// now transfer data from fields to the user interface (TEdit)
finally
query.Free;
end;
Your approach does not seam optimal for me (splitting Displayed Name), but your problem here would be accessing query.Fields[0].AsString after freeing the query.