SQL Concatenate column values and store in an extra column - sql

I am using SQL Server 2019 (v15.0.2080.9) and I've got a simple task but somehow I am not able to solve it...
I have a little database with one table containing a first name and a last name column
CREATE TABLE [dbo].[person]
(
[first_name] [nchar](200) NULL,
[last_name] [nchar](200) NULL,
[display_name] [nchar](400) NULL
) ON [PRIMARY]
GO
and I want to store the combination of first name with an extra whitespace in between in the third column (yes I really have to do that...).
So I thought I might use the CONCAT function
UPDATE [dbo].[person]
SET display_name = CONCAT(first_name, ' ', last_name)
But my display_name column is only showing me the first name... so what's wrong with my SQL?
Kind regards
Sven

Your method should work and does work. The issue, though is that the data types are nchar() instead of nvarchar(). That means they are padded with spaces and the last name is starting at position 201 in the string.
Just fix the data type.
In addition, I would suggest that you use a computed column:
alter table person add display_name as (concat(first_name, ' ', last_name));
This ensures that the display_name is always up-to-date -- even when the names change.
Here is a db<>fiddle.
As a note: char() and nchar() are almost never appropriate. The one exception is when you have fixed length strings, such as state or country abbreviations or account codes. In almost all cases, you want varchar() or nvarchar().

Related

Oracle SQL - Not null code is not working

I am trying to retrieve the monetary amount associated with project IDs, however I only want data where a project ID exists (not blank)
When I type my SQL code below...
SELECT project_id, monetary_amount, journal_line_date
FROM PS_JRNL_LN
where project_id is not null
and journal_line_date BETWEEN to_date ('2020/01/01','yyyy/mm/dd')
AND TO_DATE ('2020/03/04','yyyy/mm/dd')
this query works however, I am still getting blank values in my result
You dont have nulls but blank spaces add below in your query
SELECT project_id, monetary_amount,
journal_line_date
FROM PS_JRNL_LN
where ( project_id is not null or
( project_id is not
null
and LTRIM( RTrim(project_id)) not
like '')
and
journal_line_date BETWEEN
to_date ('2020/01/01','yyyy/mm/dd')
AND TO_DATE
('2020/03/04','yyyy/mm/dd')
Here is something that can help you find out what is happening in the project_id column. (Most likely, a bunch of ' ' values, meaning non-empty string consisting of a single space.)
select project_id, dump(project_id)
from ps_jrnl_ln
where ltrim(project_id, chr(32) || chr(9)) is null
and project_id is not null
;
DUMP shows you exactly what is stored in your table. 32 is the ASCII code for a single space; 9 (or 09) is the code for horizontal tab. I expect you will get rows where the DUMP column shows a single character, with code 32. But - who knows; you may find other things as well.
That will help you understand what's in the column. (You may also check describe ps_jrnl_ln - you may find out that the column is declared not null!!!)
If you find a bunch of rows where the project id is a single space, of course, in your actual query you will have to change
where project_id is not null
to
where ltrim(project_id, chr(32) || chr(9)) is not null
Or, perhaps, if indeed a single space is used as placeholder for null:
where project_id != ' '
Few things you must implement in your table design to prevent the problem at first place than struggling with the data:
Add a NOT NULL constraint to the column.
Add a CHECK constraint to prevent unwanted characters like whitespaces etc. and only allow the data you want to load.
If you don't want a check constraint, then handle it during loading the data using TRIM.
If necessary, make the PROJECT_ID column the primary key, that would implicitly not allow NULL values. Usually, the ID column in a table suggests it's a primary key but it could vary in your use case.
If you are not allowed to alter the design by doing none of the above, then at least you could handle the data insertion at application level where you might be taking it as input.
Inner join that journal table to the source of truth for Project ID's. Assuming there are no "blank" ID's in that table, then you won't get "blanks" in your result.
e.g.
SELECT j.project_id, j.monetary_amount, j.journal_line_date
FROM PS_JRNL_LN J
INNER JOIN PROJECT_MASTER P ON j.project_id = p.id /* should remove "blanks" */
where j.journal_line_date >= to_date ('2020/01/01','yyyy/mm/dd')
and j.journal_line_date < TO_DATE ('2020/03/05','yyyy/mm/dd')
Note also, I never use between for date ranges, the above pattern using >= & < is more reliable (as it works regardless of the time precision of the data).
Try using filter condition:
ltrim(rtrim(project_id)) <> ''

Alter column from varchar to decimal when nulls exist

How do I alter a sql varchar column to a decimal column when there are nulls in the data?
I thought:
ALTER TABLE table1
ALTER COLUMN data decimal(19,6)
But I just get an error, I assume because of the nulls:
Error converting data type varchar to numeric. The statement has been terminated.
So I thought to remove the nulls I could just set them to zero:
ALTER TABLE table1
ALTER COLUMN data decimal(19,6) NOT NULL DEFAULT 0
but I dont seem to have the correct syntax.
Whats the best way to convert this column?
edit
People have suggested it's not the nulls that are causing me the problem, but non-numeric data. Is there an easy way to find the non-numeric data and either disregard it, or highlight it so I can correct it.
If it were just the presence of NULLs, I would just opt for doing this before the alter column:
update table1 set data = '0' where data is null
That would ensure all nulls are gone and you could successfully convert.
However, I wouldn't be too certain of your assumption. It seems to me that your new column is perfectly capable of handling NULL values since you haven't specified not null for it.
What I'd be looking for is values that aren't NULL but also aren't something you could turn in to a real numeric value, such as what you get if you do:
insert into table1 (data) values ('paxdiablo is good-looking')
though some may argue that should be treated a 0, a false-y value :-)
The presence of non-NULL, non-numeric data seems far more likely to be causing your specific issue here.
As to how to solve that, you're going to need a where clause that can recognise whether a varchar column is a valid numeric value and, if not, change it to '0' or NULL, depending on your needs.
I'm not sure if SQL Server has regex support but, if so, that'd be the first avenue I'd investigate.
Alternatively, provided you understand the limitations (a), you could use isnumeric() with something like:
update table1 set data = NULL where isnumeric(data) = 0
This will force all non-numeric values to NULL before you try to convert the column type.
And, please, for the love of whatever deities you believe in, back up your data before attempting any of these operations.
If none of those above solutions work, it may be worth adding a brand new column and populating bit by bit. In other words set it to NULL to start with, and then find a series of updates that will copy data to this new column.
Once you're happy that all data has been copied, you should then have a series of updates you can run in a single transaction if you want to do the conversion in one fell swoop. Drop the new column and then do the whole lot in a single operation:
create new column;
perform all updates to copy data;
drop old column;
rename new column to old name.
(a) From the linked page:
ISNUMERIC returns 1 for some characters that are not numbers, such as plus (+), minus (-), and valid currency symbols such as the dollar sign ($).
Possible solution:
CREATE TABLE test
(
data VARCHAR(100)
)
GO
INSERT INTO test VALUES ('19.01');
INSERT INTO test VALUES ('23.41');
ALTER TABLE test ADD data_new decimal(19,6)
GO
UPDATE test SET data_new = CAST(data AS decimal(19,6));
ALTER TABLE test DROP COLUMN data
GO
EXEC sp_RENAME 'test.data_new' , 'data', 'COLUMN'
As people have said, that error doesn't come from nulls, it comes from varchar values that can't be converted to decimal. Most typical reason for this I've found (after checking that the column doesn't contain any logically false values, like non-digit characters or double comma values) is when your varchar values use comma for decimal pointer, as opposed to period.
For instance, if you run the following:
DECLARE #T VARCHAR(256)
SET #T = '5,6'
SELECT #T, CAST(#T AS DEC(32,2))
You will get an error.
Instead:
DECLARE #T VARCHAR(256)
SET #T = '5,6'
-- Let's change the comma to a period
SELECT #T = REPLACE(#T,',','.')
SELECT #T, CAST(#T AS DEC(32,2)) -- Now it works!
Should be easy enough to look if your column has these cases, and run the appropriate update before your ALTER COLUMN, if this is the cause.
You could also just use a similar idea and make a regex search on the column for all values that don't match digit / digit+'.'+digit criteria, but i suck with regex so someone else can help with that. :)
Also, the american system uses weird separators like the number '123100.5', which would appear as '123,100.5', so in those cases you might want to just replace the commas with empty strings and try then?

Complex Find and Replace in SQL

I have a list of ProductName key/value pairs, like so:
CREATE TABLE [dbo].[ProductNames]
(
[Key] [nvarchar](100) NULL,
[Value] [nvarchar](100) NULL
)
Containing values like this:
'Pepsi', 'Pepsi®'
'Coke', 'Coke®'
I need to find and replace values in a text field that DO NOT ALREADY contain the registered trademark for a product...and replace it with the full trademark string.
FOR EXAMPLE:
1. if 'Pepsi' is alone....it becomes 'Pepsi®'
2. if 'Pepsi®' already exists...do nothing
UPDATE:
The registered trademark is only one example of something to be replaced. There could be other multi-character replacements as well. As such, something more complex is probably needed. For example, I would probably detect 'Pepsi', then truncate 'Pepsi' from the VALUE portion of the key/value row...AND THEN...try to detect if that truncated value already exists in the string. If not, then go ahead and replace the value (something like that).
Perhaps this solution will help you. It will update records where value does not end with ®, appending it at the end.
update ProductNames
set Value = Value + '®'
where Value not like '%®'
update ProductNames set
Value = CASE WHEN CHARINDEX('®',value,1) = 0 then value+'®' else value end
I can't think of an easy way for this to work. Iterate through each special keyword and check the beginning, middle and end cases for these words, assuming you require white space to separate each keyword. Break down the operation into a few steps, as required by business logic. Expensive yes, but it's a one time thing, I assume. Better yet, run this routine once when a row is added/modified.

How do I do a case sensitive comparison in sql?

Just started a tutorial in SQL for beginners. I'm doing some exercises now and I would like to know how to change the title.
If you look at here: you'll see that I have made firstname, lastname, title, age and salary. And I wrote the letters in small letter.
How can I change it to capital letter?
http://tinypic.com/r/amtpgm/3
I tried using this:
update mytablename
set firstname = 'Firstname'
where firstname = 'firstname'
But I later realized that this one will not work.
Thanks
====
additional question:
I also notice that if I wrote with spaces, then its not recognized. It's the first part only which will be displayed. Do you know why is it doing? Thanks
create table myemployees_tr0214
(First Name varchar(20),
Last Name varchar(20),
Title char(5),
Age number(3),
Salary number(6,10));
==========
thank you for all your inputs.
I've tried this one in renaming the "Firstname" to "Fname" and it didn't work. Did I miss something?
alter table myemployees_tr0214
rename column Firstname to Fname;
This should update all the firstnames in the table to an uppercase first letter:
UPDATE mytablename SET firstname = CONCAT(UCASE(MID(firstname,1,1)),MID(firstname,2));
Hope this helps you :)
First, unless you really want to change the names of the fields, don't. It's not really all that important if all you're doing is learning SQL. Note that if you want to learn the syntax for doing so, then of course it would be a worthwhile exercise, but other than that, I'd let it be.
Your edited question mentions using spaces in names. This is not allowed. The rules for what constitutes a "SQL Identifier", be it the name of a table, column, constraint, etc. has some strict rules, and simplified they are that you should only use letters, underscores, and digits, except that you can't start with a digit.
Now, why the online website that you're using to learn SQL through doesn't complain when you add those spaces, that I don't know, and to me that makes it a little suspect. It doesn't sound as though it actually uses a known database engine, as just the presence of those spaces there + the extra words would make any normal database engine complain about bad syntax.
In order to fix it, either add underscores instead of spaces, or contract and use camelCasing, like this: FirstName, LastName
Ah, there are two ways to read this question. The first is based on reading the sample UPDATE you posted. This will fail because SQL by default doesn't do a case sensitive comparison on strings.
The second piece of code implies what you wanted was to ALTER TABLE and change the name of the column from a column name with one casing to another. In MS-SQL, you can't do that without dropping the whole table and re-creating it, in other dialects of SQL there will be version specific DDL syntax.
And finally, in MS-Access, if a column name has a space, you wrap it in double quotes, e.g. "My Column" in SQL wrap it in [My Column]
update mytablename set firstname = 'Firstname'; where firstname = 'firstname';
This will update the values of the firstname column. What you are trying to do is change the name of the firstname column. How to do this depends on the database you're using (which you haven't mentioned).
If it's MS Access or SQL server, you can open the table in the UI and use the designer to change the column name.
Otherwise you can use the SQL ALTER TABLE statement, as described here.
For MS SQL Server...
You'd use [ and ] to delimit identifiers:
create table myemployees_tr0214 (
[First Name] varchar(20), --here
[Last Name] varchar(20), --here
Title char(5),
Age number(3),
Salary number(6,10)
);
If you want to change the column name from "firstname" to "Firstname", you could use sp_rename in MS SQL Server.
If you want to change the first letter of the data in the "firstname" column, other posters have offered solutions and here's another for a single name.
update mytablename
set firstname = 'Firstname'
where firstname COLLATE Latin1_general_Bin = 'firstname' COLLATE Latin1_general_Bin
If you're using mysql see: http://dev.mysql.com/doc/refman/5.0/en/charset-binary-op.html
The BINARY operator casts the string
following it to a binary string. This
is an easy way to force a comparison
to be done byte by byte rather than
character by character. BINARY also
causes trailing spaces to be
significant
mysql> SELECT 'a' = 'A';
-> 1
mysql> SELECT BINARY 'a' = 'A';
-> 0
mysql> SELECT 'a' = 'a ';
-> 1
mysql> SELECT BINARY 'a' = 'a ';
-> 0
you'll need some kind of unique row indentifier like id so you can do
update mytablename set firstname = 'Firstname' where id = 1
now what can be used as a unique row indentifier is a huge debate of natural vs surrogate keys. use what you think is best for your example but i'm a supporter of surogate keys since every natural key has the possibility to change.
This may need to be optimize for more but it will allow you to even update mulitple first name MSSQL version
select 'mary ellen' as firstname into #test
insert into #test select 'Nathan'
select
Case when patindex('% %',firstname) >0 then
upper(left(firstname,1)) --first letter
+ rtrim(substring(firstname,2,patindex('% %',firstname)-1)) --whole firstname
+ ' ' -- space
+ Upper(substring(firstname,patindex('% %',firstname)+1,1)) --first letter last name
+ rtrim(substring(firstname,patindex('% %',firstname)+2, len(firstname)))
else
upper(left(firstname,1)) + substring(firstname,2,len(firstname))
end as firstname
from #test
update #test
set firstname = Case when patindex('% %',firstname) >0 then
upper(left(firstname,1)) --first letter
+ rtrim(substring(firstname,2,patindex('% %',firstname)-1)) --whole firstname
+ ' ' -- space
+ Upper(substring(firstname,patindex('% %',firstname)+1,1)) --first letter last name
+ rtrim(substring(firstname,patindex('% %',firstname)+2, len(firstname)))
else
upper(left(firstname,1)) + substring(firstname,2,len(firstname))
end

MySQL - Set default value for field as a string concatenation function

I have a table that looks a bit like this actors(forename, surname, stage_name);
I want to update stage_name to have a default value of
forename." ".surname
So that
insert into actors(forename, surname) values ('Stack', 'Overflow');
would produce the record
'Stack' 'Overflow' 'Stack Overflow'
Is this possible?
Thanks :)
MySQL does not support computed columns or expressions in the DEFAULT option of a column definition.
You can do this in a trigger (MySQL 5.0 or greater required):
CREATE TRIGGER format_stage_name
BEFORE INSERT ON actors
FOR EACH ROW
BEGIN
SET NEW.stage_name = CONCAT(NEW.forename, ' ', NEW.surname);
END
You may also want to create a similar trigger BEFORE UPDATE.
Watch out for NULL in forename and surname, because concat of a NULL with any other string produces a NULL. Use COALESCE() on each column or on the concatenated string as appropriate.
edit: The following example sets stage_name only if it's NULL. Otherwise you can specify the stage_name in your INSERT statement, and it'll be preserved.
CREATE TRIGGER format_stage_name
BEFORE INSERT ON actors
FOR EACH ROW
BEGIN
IF (NEW.stage_name IS NULL) THEN
SET NEW.stage_name = CONCAT(NEW.forename, ' ', NEW.surname);
END IF;
END
According to 10.1.4. Data Type Default Values no, you can't do that. You can only use a constant or CURRENT_TIMESTAMP.
OTOH if you're pretty up-to-date, you could probably use a trigger to accomplish the same thing.
My first thought is if you have the two values in other fields what is the compelling need for redundantly storing them in a third field? It flies in the face of normalization and efficiency.
If you simply want to store the concatenated value then you can simply create a view (or IMSNHO even better a stored procedure) that concatenates the values into a pseudo actor field and perform your reads from the view/sproc instead of the table directly.
If you absolutely must store the concatenated value you could handle this in two ways:
1) Use a stored procedure to do your inserts instead of straight SQL. This way you can receive the values and construct a value for the field you wish to populate then build the insert statement including a concatenated value for the actors field.
2) So I don't draw too many flames, treat this suggestion with kid gloves. Use only as a last resort. You could hack this behavior by adding a trigger to build the value if it is left null. Generally, triggers are not good. They add unseen cost and interactions to fairly simple interactions. You can, though, use the CREATE TRIGGER to update the actors field after a record is inserted or updated. Here is the reference page.
As of MySQL 8.0.13, you can use DEFAULT clause for a column which can be a literal constant or an expression.
If you want to use an expression then, simply enclose the required expression within parentheses.
(concat(forename," ",surname))
There are two ways to accomplish what you are trying to do as per my knowledge:
(important: consider backing up your table first before running below queries)
1- Drop the column "stage_name" all together and create a new one with DEFAULT constraint.
ALTER TABLE actors ADD COLUMN stage_name VARCHAR(20) DEFAULT (concat(forename," ",surname))
2- This will update newer entries in the column "stage_name" but not the old ones.
ALTER TABLE actors alter stage_name set DEFAULT (concat(forename," ",surname));
After that, if you need to update the previous values in the column "stage_name" then simply run:
UPDATE actors SET stage_name=(concat(forename," ",surname));
I believe this should solve your problem.