Select all table entries which have a fully capitalized string in a specific column? - sql

I have a database table with a few thousand entries. A part of the entries (~20%) have been entered with a fully capitalized strings in the 'name' column.
Example:
id | name
---------
1 | THOMAS GOLDENBERG
2 | Henry Samuel
3 | GIL DOFT
4 | HARRY CRAFT
5 | Susan Etwall
6 | Carl Cooper
How would an SQL query look like that selects all entries with a fully capitalized string in the name column? (i.e. in the example: those with the ID 1,3,4)

In MySQL it would be:
SELECT id FROM table WHERE name = UPPER(name);
I think this would work the same way in SQL Server, DB2 and Postgres.

What database system?
In theory you can do a simple SELECT ... WHERE name = UPPER(name); but that does not always work. Depending on the collation of your data, you may found that all records satisfy this condition because the comparison used may be case insensitive.
You need to ensure you compare using a case sensitive collation, and the correct answer depends on the database platform you use. For example, using SQL Server syntax:
SELECT ... WHERE Name COLLATE Latin1_General_100_CS_AS = UPPER(Name);
This also works in MySQL with the condition that you use a collation name valid on MySQL.

select * from your_table where name = upper(name)

Here's a MySql function to convert uppercase to title case:
example:
update your_table set name = tcase(name) where name = upper(name);
function:
CREATE FUNCTION `tcase`(str text) RETURNS text CHARSET latin1
DETERMINISTIC
BEGIN
DECLARE result TEXT default '';
DECLARE space INT default 0;
DECLARE last_space INT default 0;
IF (str IS NULL) THEN
RETURN NULL;
END IF;
IF (char_length(str) = 0) THEN
RETURN '';
END IF;
SET result = upper(left(str,1));
SET space = locate(' ', str);
WHILE space > 0 DO
SET result = CONCAT(result, SUBSTRING(str, last_space+2, space-last_space-1));
SET result = CONCAT(result, UPPER(SUBSTRING(str, space+1, 1)));
SET last_space = space;
SET space = locate(' ', str, space+2);
END WHILE;
SET result = CONCAT(result, SUBSTRING(str, last_space+2));
RETURN result;
END $$
DELIMITER ;

Related

Is there a way to find default values that is a combination of the same number like 000/000/0 or 11111 or 99999?

I want to find values in the SQL database that is a combination of the same number such as 0000 or 000/000/0 or 11111 or 99999 etc.
Is there a way to find these values without hardcoding?
What I am currently doing is:
select * from XXXX where value = '000/000/0'
A simple solution is to remove all instances of first character of the string, and check if the result is an empty string:
select *
from t
where replace(replace(str, '/', ''), substring(str, 1, 1), '') = ''
Try this on :
SELECT *
FROM XXXX
WHERE value IN ('000/000/0',11111,99999,0000)
If you need fill column values with application or other third-party. you can use stored procedure like below:
CREATE PROC dbo.usp_ListOfNumbers #NumberValues Nvarchar(200)
as
BEGIN
SELECT *
FROM XXXX
WHERE value = #NumberValues
END
for call just use
EXEC dbo.usp_ListOfNumbers #NumberValues = '000/000/0'

create sql view from comma separated values

T-sql question:
I need help to build a join from 2 tables, where on one of the tables I have aggregated data (comma separated values).
I have a table - Users where I have 3 columns: UserId, DefaultLanguage and OtherLanguages.
The table looks like this:
UserId | DefaultLanguage | OtherLanguages
---------------------------------------------
1 | en | NULL
2 | en | it, fr
3 | fr | en, it
4 | en | sp
and so on.
I have another table where I have the association between language code (en, fr, ro, it, sp) and language name:
LangCode | LanguageName
-------------------------
en | English
fr | French
it | Italian
sp | Spanish
and so on.
I want to create a view like this:
UserId | DefaultLanguage | OtherLanguages
---------------------------------------------
1 | English | NULL
2 | English | Italian, French
3 | French | English, Italian
4 | English | Spanish
and so on.
In short, I need a view where the language code is replaced by language name.
Any help, please?
Several solutions of course you can recreate all table change the data structure.
1. If all the language are 2 digits:
select t1.UserId, t2.LanguageName,
ISNULL( t3.LanguageName, '') + ISNULL(', '+t4.LanguageName, '') + ISNULL( ', '+t5.LanguageName, '') OtherLanguages
from Table1 t1
inner join Table2 t2 on t1.DefaultLanguage = t2.LangCode
left join Table2 t3 on Left(t1.OtherLanguages,2) = t3.LangCode
left join Table2 t4 on CASE WHEN len(Replace(t1.OtherLanguages, ' ', '')) > 3 THEN
SUBSTRING( Replace(t1.OtherLanguages, ' ', ''), 4, 2) ELSE null END = t4.LangCode
left join Table2 t5 on CASE WHEN len(Replace(t1.OtherLanguages, ' ', '')) > 6 THEN
SUBSTRING( Replace(t1.OtherLanguages, ' ', ''), 7, 2) ELSE null END = t5.LangCode
Use user-define function:
CREATE FUNCTION [dbo].[func_GetLanguageName] (#pLanguageList varchar(max))
RETURNS varchar(max) AS
BEGIN
Declare #aLanguageList varchar(max) = #pLanguageList
Declare #aLangCode varchar(max) = null
Declare #aReturnName varchar(max) = null
WHILE LEN(#aLanguageList) > 0
BEGIN
IF PATINDEX('%,%',#aLanguageList) > 0
BEGIN
SET #aLangCode = RTRIM(LTRIM(SUBSTRING(#aLanguageList, 0, PATINDEX('%,%',#aLanguageList))))
SET #aLanguageList = LTRIM(SUBSTRING(#aLanguageList, LEN(#aLangCode + ',') + 1,LEN(#aLanguageList)))
END
ELSE
BEGIN
SET #aLangCode = #aLanguageList
SET #aLanguageList = NULL
END
Select #aReturnName = ISNULL( #aReturnName + ', ' , '') + LanguageName from Table2 where LangCode=#aLangCode
END
RETURN(#aReturnName)
END
and use select
select UserId, dbo.func_GetLanguageName(DefaultLanguage)DefaultLanguage, dbo.func_GetLanguageName(OtherLanguages) OtherLanguages from table1
Best practice would dictate not to have this type of comma delimited
data in a column...
Since you stated in comments that the schema cannot be changed, the next best thing is a function. This can be used in a select query in-line.
SQL is notoriously slow with string manipulation. Here is an interesting article on the topic. There are many SQL "string split" functions out there. They all generally split a comma delimited string and return a table.
For this specific use-case, you actually need a scalar-valued
function (a function which returns one value) rather than a
table-valued function (one which returns a table of values).
Below is a modified such function, which returns a scalar value in place of the original comma delimited string of language codes.
The comments explain what is happening line by line.
The gist is that you must loop through the input string keeping track of the last comma location, extract each code, lookup the full language from the languages table, and then return the output as a comma-delimited string.
Language codes to languages function:
Create Function [dbo].fn_languageCodeToFull
( #Input Varchar(100) )
Returns Varchar(1000)
As
Begin
-- To address null input, based on the example you provided, we set the output to NULL if there is no input
If #Input = '' Or #Input Is Null
Return Null
Declare
#CodeLength int, -- constant for code length to avoid hardcoded "magic numbers"
#Output varchar(1000), -- will contain the final comma delimited string of full languages
#LastIndex int, -- tracks the location of the input we are searching as we loop over the string
#CurrentCode varchar(2), -- for code readability, we extract each language code to this variable
#CurrentLanguage varchar(50), -- for code readability, we store the full language in this variable
#IndexIncrement int -- constant to increment the search index by 1 at each iteration
-- ensuring the loop moves forward
Set #LastIndex = 0 -- seed the index, so we begin to search at 0 index
Set #CodeLength = 2 -- ISO language codes are always 2 characters in length
Set #Output = '' -- seed with empty string to avoid NULL when concatenating
Set #IndexIncrement = 1 -- again avoiding hardcoded values...
-- We will loop until we have gone to or beyond the length of the input string
While #LastIndex < len(#Input)
Begin
-- Set the index of each comma (charindex is 1-based)
Set #LastIndex = CHARINDEX(',', #Input, #LastIndex)
-- When we get to the last item, CharIndex will return 0 when it does not find a comma.
-- To pull the last item, we will artificially set #LastIndex to be 1 greater than the input string
-- This will allow the code following this line to be unaltered for this scenario
If #LastIndex = 0 set #LastIndex = len(#Input) + 1 -- account for 1-based index of substring
-- Extract the code prior to the current comma that charindex has identified
Set #CurrentCode = substring(#Input, #LastIndex - #CodeLength, #CodeLength)
-- Do a lookup to get the language for the current code
Set #CurrentLanguage = (Select LanguageName From languages Where code = #CurrentCode)
-- Only add comma after first language to ensure no extra comma will be present in Output
If #LastIndex > 3 Set #Output = #Output + ','
-- Here we build the Output string with the language
Set #Output = #Output + #CurrentLanguage
-- Finally, we increment #LastIndex by 1 to avoid loop on first instance of comma
Set #LastIndex = #LastIndex + #IndexIncrement
End
Return #Output
End
Then your view would simply do something like:
Sample view using the function:
Create View vw_UserLanguages
As
Select
UserId,
dbo.fn_languageCodeToFull(DefaultLanguage) as DefaultLanguage,
dbo.fn_languageCodeToFull(OtherLanguages) as OtherLanguages,
From UserLanguageCodes -- you do not provide a name so I made one up
Note that the function will work whether there are commas or not, so there is no need to join the Languages table here as you can just have the function do all the work in this case.
One quick and dirty solution would be to use a nested REPLACE command but that could result in a very complex statement a bit long winded, especially if you have more than five languages.
As an example:
SELECT [UserId],[DefaultLanguage],
CASE
WHEN [OtherLanguages] IS NULL THEN ''
ELSE REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE([OtherLanguages],
'en','English'),
'fr','French'),
'it','Italian'),
'ro','Romulan'), --Probably not the intended language ;-)
'sp','Spanish')
END as [OtherLanguages]
FROM YourTable
Personally, I'd create a scalar function, again using the REPLACE command, but you can then check the number of languages present and add a counter so that you're not doing unnecessary lookups.
SELECT [UserId],[DefaultLanguage],
CASE
WHEN [OtherLanguages] IS NULL THEN ''
WHEN [OtherLanguages] = '' THEN ''
ELSE do_function_name([OtherLanguages])
END as [OtherLanguages]
FROM YourTable
It might not be good practice but there are times when it is more efficient to store multiple values in a single field but accept that when you do, it will slow down the way you handle that data.

Firebird Database Split String on Field

Currently working with a Firebird 1.5 database and attempting to pull the data in the correct format natively with SQL.
Consider the following database:
ID | Full Name
1 Jon Doe
2 Sarah Lee
What I am trying to achieve is a simple split on the full name field (space) within a query.
ID | First Name | Last Name
1 Jon Doe
2 Sarah Lee
The issue faced is Firebird POSITION() was introduced in v2.0. Is there any known workaround to split on a space that anyone has come across?
Much appreciate your assistance!
For Firebird 1.5, a solution is to find a UDF that either combines both functions, or provides the position (I don't use UDFs, so I am not sure if one already exists). If none is available, you might have to write one.
The other solution is to write a stored procedure for this functionality, see for example: Position of substring function in SP
CREATE PROCEDURE Pos (SubStr VARCHAR(100), Str VARCHAR(100))
RETURNS (Pos INTEGER) AS
DECLARE VARIABLE SubStr2 VARCHAR(201); /* 1 + SubStr-lenght + Str-length */
DECLARE VARIABLE Tmp VARCHAR(100);
BEGIN
IF (SubStr IS NULL OR Str IS NULL)
THEN BEGIN Pos = NULL; EXIT; END
SubStr2 = SubStr || '%';
Tmp = '';
Pos = 1;
WHILE (Str NOT LIKE SubStr2 AND Str NOT LIKE Tmp) DO BEGIN
SubStr2 = '_' || SubStr2;
Tmp = Tmp || '_';
Pos = Pos + 1;
END
IF (Str LIKE Tmp) THEN Pos = 0;
END
This example (taken from the link) can be extended to then use SUBSTRING to split on the space.
For searching on a single character like a space, a simpler solution can probably be devised than above stored procedure. For your exact needs you might need to write a selectable stored procedure specifically for this purpose.
However, upgrading your database to Firebird 2.5 will give you much more powerful internal functions that simplify this query (and your life)!
I also wanted to split a full name string to first and last name and I used the following SQL statements in firebird 2.1 Database:
Patients is the table name.
The Name field holds the full name string e.g.: "Jon Doe". The FIRST_NAME field will store the first name and the LAST_NAME field the last name
First get the first name (string part before the first space) and execute a TRIM UPDATE statement to remove any spaces.
UPDATE "Patients" SET "Patients".FIRST_NAME = (SUBSTRING("Patients"."Name" FROM 1 FOR (POSITION(' ' IN "Patients"."Name"))))
UPDATE "Patients" SET "Patients".FIRST_NAME = TRIM(BOTH ' ' FROM "Patients".FIRST_NAME)
Then get the last name (the string after the first space) and execute a TRIM UPDATE statement to remove any spaces
UPDATE "Patients" SET "Patients"."LAST_NAME" = (SUBSTRING("Patients"."Name" FROM (POSITION(' ' IN "Patients"."Name")+1)))
UPDATE "Patients" SET "Patients".LAST_NAME = TRIM(BOTH ' ' FROM "Patients".LAST_NAME)
The result will be:
ID | NAME | FIRST_NAME | LAST_NAME
1 Jon Doe Jon Doe
2 Sarah Lee Sarah Lee
You could use a UDF, but that isn't strictly SQL
you could write a stored procedure to parse and split but thats not strictly SQL either

SQL Insert records in between characters

I would like to ask something, I'm working on generating an excel containing records from table.
Now, I have records in a column ex. 123450000
The customer wants the data to be 12345-0000
So in my select statement, every 5th of the column, there should be a dash in between
If the record has only 5 characters, then no "-" will be placed
I'm using SQL Server database for this.
Thanks :)
SELECT STUFF('12345000',6,7,'-0000');
This function should help;
DELIMITER //
CREATE FUNCTION dashify(#indata VARCHAR(MAX)) RETURNS VARCHAR(MAX) AS
BEGIN
DECLARE #breakat INT = 5
WHILE #breakat < LEN(#indata)
BEGIN
SET #indata = STUFF(#indata, #breakat+1, 0, '-');
SET #breakat = #breakat + 6
END
RETURN #indata;
END;
//
SELECT dbo.dashify('12345678901234') //
> 12345-67890-1234

Why does this MySQL function return null?

Description:
the query actually run have 4 results returned,as can be see from below,
what I did is just concate the items then return,
but unexpectedly,it's null.
I think the code is self-explanatory:
DELIMITER |
DROP FUNCTION IF EXISTS get_idiscussion_ask|
CREATE FUNCTION get_idiscussion_ask(iask_id INT UNSIGNED) RETURNS TEXT DETERMINISTIC
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE body varchar(600);
DECLARE created DATETIME;
DECLARE anonymous TINYINT(1);
DECLARE screen_name varchar(64);
DECLARE result TEXT;
DECLARE cur1 CURSOR FOR SELECT body,created,anonymous,screen_name from idiscussion left join users on idiscussion.uid=users.id where idiscussion.iask_id=iask_id;
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1;
SET result = '';
OPEN cur1;
REPEAT
FETCH cur1 INTO body, created, anonymous, screen_name;
SET result = CONCAT(result,'<comment><body><![CDATA[',body,']]></body>','<replier>',if(screen_name is not null and !anonymous,screen_name,''),'</replier>','<created>',created,'</created></comment>');
UNTIL done END REPEAT;
CLOSE cur1;
RETURN result;
END |
DELIMITER ;
mysql> DELIMITER ;
mysql> select get_idiscussion_ask(1);
+------------------------+
| get_idiscussion_ask(1) |
+------------------------+
| NULL |
+------------------------+
1 row in set (0.01 sec)
mysql> SELECT body,created,anonymous,screen_name from idiscussion left join users on idiscussion.uid=users.id where idiscussion.iask_id=1;
+------+---------------------+-----------+-------------+
| body | created | anonymous | screen_name |
+------+---------------------+-----------+-------------+
| haha | 2009-05-27 04:57:51 | 0 | NULL |
| haha | 2009-05-27 04:57:52 | 0 | NULL |
| haha | 2009-05-27 04:57:52 | 0 | NULL |
| haha | 2009-05-27 04:57:53 | 0 | NULL |
+------+---------------------+-----------+-------------+
4 rows in set (0.00 sec)
For those who don't think the code is self-explanatory:
Why the function returns NULL?
Rename your variables and the input parameter, they're ambiguous.
This query:
SELECT body, created, anonymous, screen_name
FROM idiscussion
LEFT JOIN
users
ON idiscussion.uid = users.id
WHERE idiscussion.iask_id = iask_id
returns your previously declared variables (which are NULL), not the table columns.
Prepend the variable names and the input parameter name with an underscore.
Also you make an extra assignment to result:
FETCH cur1 INTO body, created, anonymous, screen_name;
SET result = CONCAT(result,'<comment><body><![CDATA[',body,']]></body>','<replier>',if(screen_name is not null and !anonymous,screen_name,''),'</replier>','<created>',created,'</created></comment>');
The handler sets done after the FETCH fails, but the result gets assigned nevertheless.
Change your handler to:
DECLARE EXIT HANDLER FOR SQLSTATE '02000' RETURN result;
Finally: in MySQL, this can be done with a single query. There is no need to do it with a function.
SELECT GROUP_CONCAT(CONCAT(result,'<comment><body><![CDATA[',body,']]></body>','<replier>',if(screen_name is not null and !anonymous,screen_name,''),'</replier>','<created>',created,'</created></comment>') SEPARATOR '')
FROM idiscussion
LEFT JOIN
users
ON idiscussion.uid=users.id
WHERE idiscussion.iask_id = #_iask_id
Keep in mind that concatenating any string together with a NULL returns NULL. Try this test:
mysql> SET #s = 'test string';
mysql> SET #s = CONCAT(#s, '<tag>', NULL, '</tag>');
mysql> SELECT #s;
This returns NULL.
So as you loop through your cursor, if the body or created columns are NULL on any row, the result becomes NULL. Then on subsequent iterations of the loop anything concatenated with a NULL result has no effect; it stays NULL.
Try something like this:
REPEAT
FETCH cur1 INTO body, created, anonymous, screen_name;
SET result = CONCAT(result,
'<comment><body><![CDATA[',
COALESCE(body, ''),
']]></body>',
'<replier>',
IF(COALESCE(anonymous, 0) != 0, COALESCE(screen_name, ''), ''),
'</replier>',
'<created>',
COALESCE(created, ''),
'</created></comment>'
);
UNTIL done END REPEAT;
The COALESCE() function is a useful function in standard SQL. It returns its first non-NULL argument.
CONCAT_WS(separator, str1, str2,...)
CONCAT_WS() stands for CONCAT With Separator and is a special form of CONCAT(). The first argument is the separator for the rest of the arguments. The separator is added between the strings to be concatenated: The separator can be a string as can the rest of the arguments. If the separator is NULL, the result is NULL. The function skips any NULL values after the separator argument.
mysql>
SELECT CONCAT_WS(",","First name","Second name","Last Name");
-> 'First name,Second name,Last Name'
mysql>
SELECT CONCAT_WS(",","First name",NULL,"Last Name");
-> 'First name,Last Name'
Before MySQL 4.0.14, CONCAT_WS() skips empty strings as well as NULL values.
I try to be verbose because your question isn't ;)
You're expecting the return value of the function to be non-NULL since you're creating the return value by concatenating only non-NULL strings.
Only if one of the strings was NULL, the whole return value would be NULL. Your demo data only contains NULL values in screen_name, but you respected that case.
But somehow (atm I have no idea how) one of the values must be NULL and the relevant line to look at ist the one with the big CONCAT.
What if you shorten the relevant line for debug reasons to:
SET result = if(screen_name is not null,screen_name,'')
Does it still return NULL?