Oracle SQL - group by char cast - sql

Using the a char-cast in a group-by clause results something unexpected:
select cast(col as char(2)) from (
select 'Abc' as col from dual
union all
select 'Abc' as col from dual
) group by cast(col as char(10));
The result is 'Abc ' (10 characters long).
Intuitively, I would have expected Oracle to return one of the following:
An error: 'not a group-by expression', as the group-by clause is another than the selection clause
A result of length 2 'Ab'.
Replacing cast(col as char(2)) with cast(col as char(3)), Oracle returns an error 'not a group-by expression'. This, again is a very strange behavior.
How can this be explained? What's the reason behind it?
I'm using Oracle SQL 11g.

As was mentioned above, I think there is a misunderstanding going on. o.O
I can't explain why it's doing this, but here's the pattern for the type of query you have:
If you generalize it a bit like this, where [A] and [B] are integers, and [STRING] is whatever text you want:
select cast(col as char([A])) from (
select '[STRING]' as col from dual
union all
select '[STRING]' as col from dual
) group by cast(col as char([B]));
it looks like this always fails if one of the two conditions below is true (there may be others):
( LENGTH([STRING]) < [B] OR LENGTH([STRING] > [B]) and [A] = LENGTH([STRING])
( LENGTH([STRING]) = [B] AND [A] <> LENGTH([STRING]) )
Otherwise, it'll return a row.
But if you take your example that runs and use it in a CREATE TABLE statement, it's going to fail as it sets up the column width to be the 2 and can't fit the 3 character string coming in.
To add to the oddity, if you append something at the start and the end of the string like this:
select '\*'||cast(col as char([A]))||'\*' from (
select '[STRING]' as col from dual
union all
select '[STRING]' as col from dual
) group by cast(col as char([B]));
This will only work if [A] >= [B], otherwise it fails on ORA-01489: result of string concatenation is too long.
Curious...

Related

How to extract this specific data from a particular column in SQL Server?

I have column with below data:
Change
18 MCO-005329
A ECO-12239
0 ECO-25126
X1 ECO-05963
NA MCO-003778
C ECO-08399
MCO-003759
ECO-00643217
NULL
I want to extract the output like below:
MCO-005329
ECO-12239
ECO-25126
ECO-05963
MCO-003778
ECO-08399
MCO-003759
ECO-00643217
I have implemented the code like below:
select DISTINCT change,
case when change like 'MCO%' THEN change when change like 'ECO-%' THEN change
when change like '%MCO-%' then LTRIM(RTRIM(SUBSTRING(change,10,19) ))
when change like '%ECO-%' then LTRIM(RTRIM(SUBSTRING(change,10,19) ))
else '' end x from table
You can parse out the values from your requirements using SPLIT_STRING, outer apply, and a simple where clause without relying on hard coding any specific string length or position values, its dynamic.
SELECT D2.*
FROM
(
select '18 MCO-005329'
union select 'A ECO-12239'
union select '0 ECO-25126'
union select 'X1 ECO-05963'
union select 'NA MCO-003778'
union select 'C ECO-08399'
union select 'MCO-003759'
union select 'ECO-00643217'
union select NULL
) T(Change)
outer apply
(
select value
from
string_split(Change, ' ') d
) d2
where d2.value like '%-%' or d2.value is null
If you dont want nulls then smiply remove or d2.value is null
https://learn.microsoft.com/en-us/sql/t-sql/queries/from-transact-sql?view=sql-server-ver15
https://learn.microsoft.com/en-us/sql/t-sql/functions/string-split-transact-sql?view=sql-server-ver15
You could use CHARINDEX() and RIGHT() as
SELECT *, RIGHT(Change, CHARINDEX('-', REVERSE(Change)) + 3)
FROM
(
VALUES
('18 MCO-005329'),
('A ECO-12239'),
('0 ECO-25126'),
('X1 ECO-05963'),
('NA MCO-003778'),
('C ECO-08399'),
('MCO-003759'),
('ECO-00643217'), ('hhh kkk-k'),
(NULL)
) T(Change)

sql repeat regex pattern unlimited times

I need to select where column contains numbers only and ends with a hyphen
I'm running SQL Server Management Studio v17.9.1
I have tried:
select * from [table] where [column] like '[0-9]*-'
select * from [table] where [column] like '[0-9]{1,}-'
select * from [table] where [column] like '[0-9]{1,2}-'
none of these work. The expression ([0-9]*-) works in any regex tester I've run it against, SQL just doesn't like it, nor the other variations I've found searching.
You can filter where any but the last character are not numbers and the last is a dash. DATALENGTH/2 assumes NVARCHAR type. If you're using VARCHAR, just use DATALENGTH
SELECT
*
FROM
[table]
WHERE
[column] like '%-'
AND
LEFT([column], (datalength([column])/2)-1) NOT LIKE '%[^0-9]%'
SQL Server does not support regular expressions -- just very limited extensions to like functionality.
One method is:
where column like '%-' and
column not like '%[^0-9]%-'
You can use left() and right() functions as below :
with [table]([column]) as
(
select '1234-' union all
select '{123]' union all
select '1234' union all
select '/1234-' union all
select 'test-' union all
select '1test-' union all
select '700-'
)
select *
from [table]
where left([column],len([column])-1) not like '%[^0-9]%'
and right([column],1)='-';
column
------
1234-
700-
Demo

Why this sql will cause type conversion error?

WITH tb_testl AS (
SELECT 1 AS id ,'hehe' AS value
UNION ALL
SELECT 1 AS id, '1' AS value
UNION ALL
SELECT 2 AS id, '2' AS value
UNION ALL
SELECT 2 AS id, '2' AS value
), tb_test2 AS (
SELECT CONVERT(INT , value) AS value FROM tb_testl WHERE id = 2
)
SELECT * FROM tb_test2 WHERE value = 2;
this sql will cause error
Conversion failed when converting the varchar value 'hehe' to data
type int.
but the table tb_test2 dosen't have the value 'hehe' which is in the anthor table tb_test1. And I found that this sql will work well if I don't append the statement WHERE value = 2; .I've tried ISNUMBERIC function but it didn't work.
version:mssql2008 R2
With respect to the why this occurs:
There is a Logical Processing Order, which describes the order in which clauses are evaluated. The order is:
FROM
ON
JOIN
WHERE
GROUP BY
WITH CUBE or WITH ROLLUP
HAVING
SELECT
DISTINCT
ORDER BY
TOP
You can also see the processing order when you SET SHOWPLAN_ALL ON. For this query, the processing is as follows:
Constant scan - this is the FROM clause, which consists of hard coded values, hence the constants.
Filter - this is the WHERE clause. While it looks like there are two where clauses (WHERE id = 2 and WHERE value = 2). SQL Server sees this differently, it considers a single WHERE clause: WHERE CONVERT(INT , value) = 2 AND id = 2.
Compute scaler. This is the CONVERT function in the select.
Because both WHERE clauses are executed simultaneously, the hehe value is not filtered out of the CONVERT scope.
Effectively, the query is simplified to something like:
SELECT CONVERT(INT, tb_testl.value) AS Cvalue
FROM (
SELECT 1 AS id
, 'hehe' AS value
UNION ALL
SELECT 1 AS id
, '1' AS value
UNION ALL
SELECT 2 AS id
, '2' AS value
UNION ALL
SELECT 2 AS id
, '2' AS value
) tb_testl
WHERE CONVERT(INT, tb_testl.value) = 2
AND tb_testl.id = 2
Which should clarify why the error occurs.
With SQL, you cannot read code in the same way as imperative languages like C. Lines of SQL code are not necessarily (mostly not at all, in fact) executed in the same order it is written in. In this case, it's an error to think the inner where is executed before the outer where.
SQL Server does not guarantee the order of processing of statements (with one exception below). That is, there is no guarantee that WHERE filtering happens before the SELECT. Or that one CTE is evaluated before another. This is considered an advantage because it allows SQL Server to rearrange the processing to optimize performance (although I consider the issue that you are seeing a bug).
Obviously, the problem is in this part of the code:
tb_test2 AS (
SELECT CONVERT(INT, value) AS value
FROM tb_testl
WHERE id = 2
)
(Well, actually, it is where tb_test2 is referenced.)
What is happening is that SQL Server pushes the CONVERT() to where the values are being read, so the conversion is attempted before the WHERE clause is processed. Hence, the error.
In SQL Server 2012+, you can easily solve this using TRY_CNVERT():
tb_test2 AS (
SELECT TRY_CONVERT(INT, value) AS value
FROM tb_testl
WHERE id = 2
)
However, that doesn't work in SQL Server 2008. You can use the fact that CASE does have some guarantees on the order of processing:
tb_test2 AS (
SELECT (CASE WHEN value NOT LIKE '%[^0-9]%' THEN CONVERT(INT, value)
END) AS value
FROM tb_testl
WHERE id = 2
)
error caused by this part of statement
), tb_test2 AS (
SELECT CONVERT(INT , value) AS value FROM tb_testl WHERE id = 2
value has type of varchar and 'hehe' value cannot be converted to integer
WITH tb_testl AS (
SELECT 1 AS id ,'hehe' AS value
UPDATE: sql try convert all value(s) to integer in you statement. to avoid error rewrite statement as
WITH tb_testl AS (
SELECT 1 AS id ,'hehe' AS value
UNION ALL SELECT 1 AS id, '1' AS value
UNION ALL SELECT 2 AS id, '2' AS value
UNION ALL SELECT 2 AS id, '2' AS value
), tb_test2 AS (
SELECT value AS value FROM tb_testl WHERE id = 2
),
tb_test3 AS (
SELECT cast(value as int) AS value FROM tb_test2
)
SELECT * FROM tb_test3

Why does this SQL query fail

The query below is failing unexpectedly with an arithmatic overflow error.
select IsNull(t2.val, 5005)
from(
SELECT 336.6 as val UNION ALL
SELECT NULL
) as t2
"Arithmetic overflow error converting int to data type numeric."
Strangely if the query is modified to remove the NULL and replace it with the same value as in the null coalesce (5005), it runs without issue
select IsNull(t2.val, 5005)
from(
SELECT 336.6 as val UNION ALL
SELECT 5005
) as t2
Also, omitting the SELECT NULL line entirely allows the query to run without issue
select IsNull(t2.val, 5005)
from(
SELECT 336.6 as val
) as t2
If the coalesce value in the IsNull function is changed to an integer which is small enough to convert to the decimal in the subquery without widening, the query runs
select IsNull(t2.val, 500)
from(
SELECT 336.6 as val UNION ALL
SELECT NULL
) as t2
Tested this in both SQL Server 2005 and SQL Server 2008.
Ordinarily combining integers with decimals is seamless and SQL Server will convert both the integer and the decimal into a decimal type large enough to accommodate both. But for some reason running a query where the cast occurrs from both the UNION and the IsNull, causes the cast to fail.
Does anyone know why this is?
Try doing this
select * into t2
from(
SELECT 336.6 as val
UNION ALL
SELECT NULL
) as x
If you now look at the columns, you see a numeric with numeric precision of 4 and scale of 1
select * from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME='T2'
SQL made that decision based on the smallest numeric precision to hold 336.6. Now, when you ask it to convert the NULL to 5005, you are saying, convert any NULL values to a number too big to fit in a numeric with the precision of 4 and a scale of 1. The error message indicates that 5005 won't fit in Numeric(4,1)
This will work because the table will now generate a larger numeric field, since SQL needs to accommodate 5005. Create the table using the new contents of T2 from below, and the field type should go to Numeric(5,1) allowing the 5005 to fit.
select IsNull(t2.val, 5005)
from(
SELECT 336.6 as val UNION ALL
SELECT 5005
) as t2
When you run the statement without a NULL in your inner query, SQL never evaluates the 5005, so it doesn't reach a condition where it needs to put 5005 into a numeric(4,1) field.
select IsNull(t2.val, 5005)
from(
SELECT 336.6 as val
) as t2
I think the problem is that when SQL Server resolves the union, it decides on a decimal type that is only large enough to fit 333.6 (which is decimal(4,1)). Trying to put 5005 into that results in an overflow.
You can get around that specifying the precision of decimal yourself:
select IsNull(t2.val, 5005)
from(
SELECT CONVERT(DECIMAL(5,1), 336.6) as val UNION ALL
SELECT NULL
) as t2
I believe its because your 336.6 value is being inferred to be of data type NUMERIC. If you want to be more specific, then explicity cast it to DECIMAL;
SELECT IsNull(t2.val, CAST(5005 AS DECIMAL))
FROM (
SELECT CAST(336.6 AS DECIMAL) AS val
UNION ALL
SELECT NULL
) AS t2

Get SQL Substring After a Certain Character but before a Different Character

I have some key values that I want to parse out of my SQL Server table. Here are some examples of these key values:
R50470B50469
B17699C88C68AM
R22818B17565C32G16SU
B1444
What I am wanting to get out of the string, is all the numbers that occur after the character 'B' but before any other letter character if it exists such as 'C'. How can I do this in SQL?
WITH VALS(Val) AS
(
SELECT 'R50470B50469' UNION ALL
SELECT 'R22818B17565C32G16SU' UNION ALL
SELECT 'R22818B17565C32G16SU' UNION ALL
SELECT 'B1444'
)
SELECT SUBSTRING(Tail,0,PATINDEX('%[AC-Z]%', Tail))
FROM VALS
CROSS APPLY
(SELECT RIGHT(Val, LEN(Val) - CHARINDEX('B', Val)) + 'X') T(Tail)
WHERE Val LIKE '%B%'