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

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
)

Related

Select substring with dashes in SQL

I have this column called package_type and it contains strings like this:
TP-CYS01-01-2700-W-003
TP-CYS01-01-2700-W-004
TP-CYS01-02-2700-W-003
TP-CYS01-02-2700-W-001
I need to count the package_type but grouped by the package_no which is CYS01-01, CYS01-02.
What I have done is this:
select
substring(substring(package_type, '-', 3), '-', -2) as package_no,
count(distinct package_type)
from
project_june
where
progress = bill_of_quantity and event_date = '2020-06-12'
group by
substring(substring(package_type, '-', 3), '-', -2) as package_no
I get this error:
Invalid input syntax for integer: "-"
I expect the result would be like:
package_no count
------------------
CYS01-01 2
CYS01-02 2
How am I suppose to write this query?
Thank you.
The error message indicates that you are running Postgres. That database has powerful string functions - you can just use split_part():
select split_part(package_no, '-', 2) as package_no, count(distinct package_type) as cnt
from project_june
where progress = bill_of_quantity and event_date = date '2020-06-12'
group by split_part(package_no, '-', 2)
Notes:
Presumably, event_date is of a date-like datatype, so it should be compared against a literal date rather than a string.
I am suspicious about condition progress = bill_of_quantity; is bill_of_quantity an actual column in your table? If it is meant to be a literal string instead, then it should be surrounded with single quotes
Your strings seem to have a fixed format -- and you want 8 characters starting at the 4th position. That suggests that you can use:
select substring(package_type, 4, 8), as package_no,
count(distinct package_type)
from project_june
where progress = bill_of_quantity and
event_date = '2020-06-12'
group by substring(package_type, 4, 8);
There are no doubt other ways to write such a query. However, string functions are often database-specific and your question doesn't specify what database you are using.

Date is not displaying correct with substr & like query

I am trying to get this out out,
but i am experiencing that the substr i am using is incorrect ,
For an example , all my columns are displaying
hdfs://asdasda/asdas/fdsfdsfd/received_files/asdasd_20191231_11122333_123456789_CO.dat
some of which has more character so in order for me to get the exact date in the column is inconsistent if i am using subsring
some will return 20191230
but some will return _2020123
How do we tackle this problem ?
i am trying to display only data , this is using sql language or hue ,
when i input my script in ,
select SUBSTR(input_file_name, LENGTH(input_file_name) - 44, 9) from th_ingestion_status limit 100
i feel my script for Like and substr statement is incorrect
I you want the first sequence of 8 digits surrounded by underscores, use regexp_extract():
select regexp_extract(filename, '_([0-9]{8})_', 1)
If you need this after the last /, then:
select regexp_extract(filename, '_([0-9]{8})_[^/]*$', 1)
Please use below query, also please mention the database you are using, so that can provide relevant query
substr(column_name, instr(column_name, '_', 1, 2) +1, 6)
Oracle Test Case:
select 'hdfs://asdasda/asdas/fdsfdsfd/received_files/asdasd_20191231_11122333_123456789_CO.dat', substr('hdfs://asdasda/asdas/fdsfdsfd/received_files/asdasd_20191231_11122333_123456789_CO.dat', instr('hdfs://asdasda/asdas/fdsfdsfd/received_files/asdasd_20191231_11122333_123456789_CO.dat', '_', 1, 2) +1, 6)
from dual;

SQL QUERY to bring last letter in a string to first letter position using SQL Server

I have a column called Supervisor from a table JobData in a SQL Server database. In this Supervisor column the records are of the format below.
DANNYL
ADITYAG
SAMMYS
BOBBYJ
I want to convert these records to lower case and bring the last letter to first letter. For example, DANNYL should be changed to the format ldanny and this format should be applied to all the remaining records.
Can anyone help me out with a SQL query for this?
You can use the following solution using LEFT and RIGHT to get the parts of the name. By using LOWER you can convert the upper case characters to lower case:
SELECT LOWER(RIGHT(Supervisor, 1) + LEFT(Supervisor, LEN(Supervisor) - 1))
FROM JobData
WHERE LTRIM(RTRIM(Supervisor)) <> ''
-- or using ABS on the length - 1 so the WHERE isn't needed.
SELECT LOWER(RIGHT(Supervisor, 1) + LEFT(Supervisor, ABS(LEN(Supervisor) - 1)))
FROM JobData
Since it looks like the column Supervisor contains empty values you can also use the following solution without calculation and not failing on the empty values:
SELECT LOWER(RIGHT(Supervisor, 1) + REVERSE(SUBSTRING(REVERSE(Supervisor), 2, LEN(Supervisor))))
FROM JobData
... and another possibility using STUFF:
SELECT LOWER(LEFT(STUFF(Supervisor, 1, 0, RIGHT(Supervisor, 1)), LEN(Supervisor)))
FROM JobData
demo on dbfiddle.uk
there is probably a better way do to that , but here is my proposition.
SELECT lower(left(right('DANYL',1)+'DANYL',len('DANYL')))
Using SUBSTRING you can get the expected result:
SELECT LOWER(CONCAT(SUBSTRING(Supervisor, LEN(Supervisor), 1), SUBSTRING(Supervisor, 0, LEN(Supervisor))))
FROM JobData
Demo with the given sample data:
DECLARE #JobData TABLE (Supervisor VARCHAR(100));
INSERT INTO #JobData (Supervisor) VALUES
('DANNYL'), ('ADITYAG'), ('SAMMYS'), ('BOBBYJ');
SELECT LOWER(CONCAT(SUBSTRING(Supervisor, LEN(Supervisor), 1), SUBSTRING(Supervisor, 0, LEN(Supervisor)))) AS Supervisor
FROM #JobData
Output:
ldanny
gaditya
ssammy
jbobby
Like this? :
SELECT
LOWER(CONCAT(SUBSTRING([Supervisor], LEN([Supervisor]), 1),SUBSTRING([Supervisor], 1, ABS(LEN([Supervisor])-1))))
FROM TABLE

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

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.

Insert into sql Max +1 only numbers (prevent characters)

I'm using this code
(SELECT (MAX(CODE) +1 WHERE ISNUMERIC([code]) = 1)
I want to max +1 only my numbers of my column preventing characters characters.
NOTE: THIS QUESTION WAS TAGGED MYSQL WHEN THIS ANSWER WAS POSTED.
You can use substring_index() to split the values and then re-unite them:
(SELECT CONCAT(SUBSTRING_INDEX(MAX(Code), '-', 1), '-',
SUBSTRING_INDEX(MAX(CODE), '-', -1) + 1
)
FROM . . .
WHERE code LIKE '%NEW-1%'
)
This assumes that the wildcards do not have hyphens in them, and that the values after the "1" are all numbers.
Also, this doesn't pad the number is zeroes, but that is a good idea for such codes -- it ensures that they are always the same length and that they sort correctly.
The MAX() function accepts expressions, not just column names:
SELECT MAX(CASE ISNUMERIC(code) WHEN 1 THEN code END)+1 as next_code
FROM (
SELECT '15' AS code
UNION ALL SELECT ' 98 ' AS code
UNION ALL SELECT 'New-45' AS code
) foo
WHERE ISNUMERIC(code)=1;
16
(Link is to SQL Server 2005, docs for SQL Server 2000 are apparently no longer on line, but MAX() belongs to SQL standard anyway.)