SQL Ordering by a substring with a condition - sql

I have data that looks like this:
[Model Number]
[Model Number]*1
[Model Number]*4
[Model Number]*10
[Model Number]*13
And when I select them I would liek to order them by the number after the " * ", I have it almost working but I dont know how to deal with the case where I dont have a " * ". Here is the last part of my query:
ORDER BY
CAST(SUBSTRING(COL_NAME, CHARINDEX('*', COL_NAME) + 1, LEN(COL_NAME)) AS INT) DESC
Thanks for the help

A couple of solutions that should get you what you're after:
ORDER BY TRY_CONVERT(int,RIGHT(COL_NAME, NULLIF(CHARINDEX('*',REVERSE(COL_NAME)),0) -1)) DESC;
ORDER BY TRY_CONVERT(int, STUFF(COL_NAME, 1, NULLIF(CHARINDEX('*', COL_NAME),0),'')) DESC;
The NULLIF resolves the issue if there is no '*', as CHARINDEX will return 0. Then you don't end up passing an invalid (negative) value to the RIGHT function, as NULL - 1 = NULL.
My personal preference would be using STUFF, as REVERSE is quite an expensive function.

You are almost there, try this
declare #t table (col_name varchar(128));
insert into #t (col_name)
values
( '[Model Number]')
, ('[Model Number]*1')
, ('[Model Number]*4')
, ('[Model Number]*10')
, ('[Model Number]*13');
select *
from #t
order by case charindex('*', col_name) when 0 then 0 else cast(substring(col_name, charindex('*', col_name)+1, 10) as int) end desc

Related

String_Split on delimiter '.' SQL Server

I have an issue when parsing out a particular field of data, and I'm at a block on how to solve it, so I'm hoping I can gain some insight on how to solve it.
I have a field being brought [ItemCategory] that contains instances like...
Instance: TennisShoes.Laces
Instance: HikingBoot-Dr.Marten.Laces
(I cannot change the delimiter from '.' to '|' as I don't control the source)
the code being used to separate the instances is as follows:
SELECT
[Program] = LTRIM(RTRIM(LEFT(c.[ItemCategory], CHARINDEX('.',c.[ItemCategory] + '.') - 1)))
,[Category] = LTRIM(RTRIM(RIGHT(c.[ItemCategory],LEN(c.[ItemCategory]) - CHARINDEX('.',c.[ItemCategory]))))
So my issue when the DHikingBoot-Dr.Marten.Laces instance passes through the code it becomes.
[Program] = HikingBoot-Dr
[Category] = Marten.Laces
How would I make it to ignore the first '.' and delimit on the second '.', while still maintaining correctness for the first instance.
Thank you for your time.. any advice is helpful.
Give this one a try for grabbing the end.
RIGHT(c.[ItemCategory], CHARINDEX(REVERSE('.'), REVERSE(c.[ItemCategory])) -1)
I would suggest revisiting how you are storing this data, if you can, as it is flawed and will continue to give you challenges.
But that aside, this solution assumes "Category" will not include a period and the data will always end with .category
A few tweaks to what you had started, we'll use REVERSE() to basically determine the length of "Category" when using LEFT(). Then when we do "Program" we subtract that from the total length when using the RIGHT()
DECLARE #testdata TABLE
(
[sampledata] NVARCHAR(100)
);
INSERT INTO #testdata (
[sampledata]
)
VALUES ( N'TennisShoes.Laces' )
, ( 'HikingBoot-Dr.Marten.Laces' );
SELECT LEFT([sampledata], LEN([sampledata]) - CHARINDEX('.', REVERSE([sampledata]))) AS [Program]
,RIGHT([sampledata], CHARINDEX('.', REVERSE([sampledata])) -1) AS [Category]
FROM #testdata;
You can also use SUBTRING() along with REVERSE()
For category, reverse the data, find the first period, parse the
value and reverse it back.
For Program, reverse the data, go 1 past the first period to the end
and reverse it back.
DECLARE #testdata TABLE
(
[sampledata] NVARCHAR(100)
);
INSERT INTO #testdata (
[sampledata]
)
VALUES ( N'TennisShoes.Laces' )
, ( 'HikingBoot-Dr.Marten.Laces' );
SELECT REVERSE(SUBSTRING(REVERSE([sampledata]), CHARINDEX('.', REVERSE([sampledata])) + 1, LEN([sampledata]))) AS [Program]
, REVERSE(SUBSTRING(REVERSE([sampledata]), 1, CHARINDEX('.', REVERSE([sampledata])) - 1)) AS [Category]
FROM #testdata;
Both giving you results of:
Program Category
--------------------- ----------
TennisShoes Laces
HikingBoot-Dr.Marten Laces
If you need to select only last part after the ., then you can reverse the string, find charindex and do left and right with that position:
with s as (
select 'TennisShoes.Laces' as inst union
select 'HikingBoot-Dr.Marten.Laces' union
select 'Test'
)
, pos as (
select
s.*,
charindex('.', reverse(inst)) as pos
from s
)
select
ltrim(rtrim(left(inst, len(inst) - pos))) as program,
ltrim(rtrim(right(inst, nullif(pos - 1, -1)))) as category
from pos
program | category
:------------------- | :-------
HikingBoot-Dr.Marten | Laces
TennisShoes | Laces
Test | null
db<>fiddle

SQL - Reverse Part of Column

I want to reverse part of column. Ex;
SELECT Column FROM Table ORDER BY Column ASC
Output:
Column
------
Kaan001
Kaan002
Kaan003
Turan001
Turan002
If I use DESC instead of ASC;
Column
------
Turan002
Turan001
Kaan003
Kaan002
Kaan001
But I want this;
Column
------
Turan001
Turan002
Kaan001
Kaan002
Kaan003
Is it possible or not? Please Help me... Thank you..
You can use:
order by left(columnname, len(columnname) - 3) desc,
right(columnname, 3)
You are required to use DESC. It will return the result by the mentioned column in the descending order.
SELECT ColumnName FROM TableName ORDER BY ColumnName DESC
If you are not explicitly mentioned the Column's ORDER BY it will consider as ASC (ascending order) by default.
Demo on db<>fiddle
As per your edited question, the following query will work using PATINDEX
SELECT ColumnName
FROM TableName
ORDER BY LEFT(ColumnName, PATINDEX('%[0-9]%', ColumnName) - 1) DESC,
RIGHT(ColumnName, LEN(ColumnName) - PATINDEX('%[0-9]%', ColumnName) + 1) ASC
Updated db<>fiddle demo
You can try the below query.
create table #temp (
id int identity(1,1),
testCol Varchar(20)
)
insert into #temp values ('Turan002'),('Turan001'),('Kaan003'),('Kaan002'),('Kaan001')
SELECT
* from
#temp order by RTRIM(SUBSTRING( testCol , 1 , CHARINDEX( '0' , testCol) - 1)) desc,
RTRIM(SUBSTRING( testCol , CHARINDEX( '0' , testCol ), LEN( testCol) - (CHARINDEX( '0' , testCol) - 1))) asc
drop table #temp
You can find the demo Here.
From my understanding,
select * from (
values
('Kaan001')
,('Kaan002')
,('Kaan003')
,('Turan001')
,('Turan002')
) d (val)
order by left (val, PATINDEX ('%[0-9]%', val) - 1) desc
, right (val, PATINDEX ('%[0-9]%', val)) -- Here, Patindex returns the first appearance of integer.

T-SQL How to convert comma separated string of numbers to integer

I get the error "Conversion failed when converting the nvarchar value '23,24,3,45,91,' to data type int." The error seems to be occuring on the ON clause. E.ID is an integer field while F.LegalIssue is a varchar field of integers separated by commas. Below is the code with that error.
SELECT F.[FDTitle], E.PrimaryOpID as [FD Primary OP ID], F.County as [FD County], F.Status as [FD Status], F.IssueDate as [FD Date]
FROM [dbo].[tbl_FinalDetMain] F
LEFT OUTER JOIN [dbo].[tbl_lk_Exemptions_FD] E ON E.ID = F.LegalIssue
WHERE F.[FDNbr] = '2013-0041'
I have tried the code below for the on clause, but it only returns one integer value, instead of the entire string of integers.
E.ID = cast(LEFT(F.LegalIssue,PATINDEX('%[^0-9]%',F.LegalIssue)-1) as int)
The result should include five integers delimited by commas.
If LegalIssue contains a string of comma-delimited numbers, then you really want an association table. Lacking that, here is a convenient (but not efficient) way to do the join:
SELECT F.[FDTitle], E.PrimaryOpID as [FD Primary OP ID], F.County as [FD County],
F.Status as [FD Status], F.IssueDate as [FD Date]
FROM [dbo].[tbl_FinalDetMain] F LEFT OUTER JOIN
[dbo].[tbl_lk_Exemptions_FD] E
ON ','+F.LegalIssue+',' like '%,'cast(E.ID as varchar(255))+',%'
WHERE F.[FDNbr] = '2013-0041';
This prepends and postpends the list with commas to avoid conflicts, such as finding "10" in "1,100,1000".
Using xml datatype, you can explode your string to integers like this. Good candidate for a user defined function I would say :-)
declare #test varchar(max)
set #test = '1,2,3,4,5'
select
T2.item.value('(./text())[1]','int')
from
(select convert(xml,'<items><t>'+replace(#test,',','</t><t>')+'</t></items>') as xmldoc)
as xmltable
CROSS APPLY xmltable.xmldoc.nodes('/items/t') as T2(item)
You will either have to normalize the F.LegalIssue into many rows or you would have to use a LIKE
Something like
CAST(E.ID A VARCHAR(50)) = F.LegalIssue
OR F.LegalIssue LIKE CAST(E.ID A VARCHAR(50)) + ',%'
OR '%,' + F.LegalIssue LIKE CAST(E.ID A VARCHAR(50)) + ',%'
OR '%,' + F.LegalIssue LIKE CAST(E.ID A VARCHAR(50))
As you can see, the actual design of the table is the issue. You should rather avoid the design you have currently, and opt for a 1 to many or many to many design.
Here is a DEMO of how to flatten the values using a recursive CTE
SQL Fiddle DEMO
Creating test table and data
CREATE TABLE Tada(
ID INT,
SomeCommaString VARCHAR(50)
)
INSERT INTO Tada Values (1, '10'),(2,'5,6,12,16')
Flattening the table
;WITH Vals AS (
SELECT
ID,
CASE
WHEN CHARINDEX(',',SomeCommaString) = 0
THEN SomeCommaString
WHEN CHARINDEX(',',SomeCommaString) > 0
THEN LEFT(SomeCommaString,CHARINDEX(',',SomeCommaString) - 1)
END Val,
CASE
WHEN CHARINDEX(',',SomeCommaString) > 0
THEN RIGHT(SomeCommaString,LEN(SomeCommaString) - CHARINDEX(',',SomeCommaString))
ELSE NULL
END Remainder
FROM Tada
UNION ALL
SELECT
ID,
CASE
WHEN CHARINDEX(',',Remainder) = 0
THEN Remainder
WHEN CHARINDEX(',',Remainder) > 0
THEN LEFT(Remainder,CHARINDEX(',',Remainder) - 1)
END Val,
CASE
WHEN CHARINDEX(',',Remainder) > 0
THEN RIGHT(Remainder,LEN(Remainder) - CHARINDEX(',',Remainder))
ELSE NULL
END Remainder
FROM Vals
WHERE Remainder IS NOT NULL
)
SELECT ID, Val
FROM Vals

Split string and take last element

I have a table with this values:
Articles/Search/ArtMID/2681/ArticleID/2218/Diet.aspx
OurStory/MeettheFoodieandtheMD.aspx
TheFood/OurMenu.aspx
I want to get this
Diet.aspx
MeettheFoodieandtheMD.aspx
OurMenu.aspx
How can i do this?
The way to do it in SQL :
SELECT SUBSTRING( string , LEN(string) - CHARINDEX('/',REVERSE(string)) + 2 , LEN(string) ) FROM SAMPLE;
JSFiddle here http://sqlfiddle.com/#!3/41ead/11
SELECT REVERSE(LEFT(REVERSE(columnName), CHARINDEX('/', REVERSE(columnName)) - 1))
FROM tableName
SQLFiddle Demo
ORHER SOURCE(s)
REVERSE
LEFT
CHARINDEX
Please try:
select url,(CASE WHEN CHARINDEX('/', url, 1)=0 THEN url ELSE RIGHT(url, CHARINDEX('/', REVERSE(url)) - 1) END)
from(
select 'Articles/Search/ArtMID/2681/ArticleID/2218/Diet.aspx' as url union
select 'OurStory/MeettheFoodieandtheMD.aspx' as url union
select 'MeettheFoodieandtheMD.aspx' as url
)xx
Try this. It's easier.
SELECT RIGHT(string, CHARINDEX('/', REVERSE(string)) -1) FROM TableName
SELECT REVERSE ((
SELECT TOP 1 value FROM STRING_SPLIT(REVERSE('Articles/Search/ArtMID/2681/ArticleID/2218/Diet.aspx'), '/')
)) AS fName
Result: Diet.aspx
Standard STRING_SPLIT does not allow to take last value.
The trick is to reverse the string (REVERSE) before splitting with STRING_SPLIT, get the first value from the end (TOP 1 value) and then the result needs to be reversed again (REVERSE) to restore the original chars sequence.
Here is the common approach, when working with SQL table:
SELECT REVERSE ((
SELECT TOP 1 VALUE FROM STRING_SPLIT(REVERSE(mySearchString), '/')
)) AS myLastValue
FROM myTable
A slightly more compact way of doing this (similar to ktaria's answer but in SQL Server) would be
SELECT TOP 1 REVERSE(value) FROM STRING_SPLIT(REVERSE(fullPath), '/') AS fileName
The equivalent for PostgreSQL:
SELECT reverse(split_part(reverse(column_name), '/', 1));
Please try the code below:
SELECT SUBSTRING( attachment, LEN(attachment)
- CHARINDEX('/', REVERSE(attachment)) + 2, LEN(attachment) ) AS filename
FROM filestable;
more simple and elegant :
select reverse(SPLIT_PART(reverse('Articles/Search/ArtMID/2681/ArticleID/2218/Diet.aspx'), '/',1))
You can try this too
( SELECT TOP(1) value
FROM STRING_SPLIT(#string, '/')
ORDER BY CHARINDEX('/' + value + '/', '/' + #string+ '-') DESC)
I corrected jazzytomato's solution for single character tokens (D) and for single tokens (Diet.aspx)
SELECT SUBSTRING( string , LEN(string) - CHARINDEX('/','/'+REVERSE(string)) + 2 , LEN(string) ) FROM SAMPLE;
The easiest way in MySQL:
SELECT SUBSTRING_INDEX(string, '/', -1) FROM SAMPLE;
I have more simple solve
SELECT SUBSTRING_INDEX(string, 'SUBSTRING_INDEX(string, '/', -1)', 1) FROM SAMPLE;
reverse(SUBSTRING(reverse(yourString),0,CHARINDEX('/',reverse(yourString)))) as stringLastPart
Create Table #temp
(
ID int identity(1,1) not null,
value varchar(100) not null
)
DECLARE #fileName VARCHAR(100);
INSERT INTO #temp(value) SELECT value from STRING_SPLIT('C:\Users\Documents\Datavalidation\Input.csv','\')
SET #fileName=(SELECT TOP 1 value from #temp ORDER BY ID DESC);
SELECT #fileName AS File_Name;
DROP TABLE #temp

Create a delimitted string from a query in DB2

I am trying to create a delimitted string from the results of a query in DB2 on the iSeries (AS/400). I've done this in T-SQL, but can't find a way to do it here.
Here is my code in T-SQL. I'm looking for an equivelant in DB2.
DECLARE #a VARCHAR(1000)
SELECT #a = COALESCE(#a + ', ' + [Description], [Description])
FROM AP.Checkbooks
SELECT #a
If the descriptions in my table look like this:
Desc 1
Desc 2
Desc 3
Then it will return this:
Desc 1, Desc 2, Desc 3
Essentially you're looking for the equivalent of MySQL's GROUP_CONCAT aggregate function in DB2. According to one thread I found, you can mimic this behaviour by going through the XMLAGG function:
create table t1 (num int, color varchar(10));
insert into t1 values (1,'red'), (1,'black'), (2,'red'), (2,'yellow'), (2,'green');
select num,
substr( xmlserialize( xmlagg( xmltext( concat( ', ', color ) ) ) as varchar( 1024 ) ), 3 )
from t1
group by num;
This would return
1 red,black
2 red,yellow,green
(or should, if I'm reading things correctly)
You can do this using common table expressions (CTEs) and recursion.
with
cte1 as
(select description, row_number() over() as row_nbr from checkbooks),
cte2 (list, cnt, cnt_max) AS
(SELECT VARCHAR('', 32000), 0, count(description) FROM cte1
UNION ALL
SELECT
-- No comma before the first description
case when cte2.list = '' THEN RTRIM(CHAR(cte1.description))
else cte2.list || ', ' || RTRIM(CHAR(cte1.description)) end,
cte2.cnt + 1,
cte2.cnt_max
FROM cte1,cte2
WHERE cte1.row_nbr = cte2.cnt + 1 AND cte2.cnt < cte2.cnt_max ),
cte3 as
(select list from cte2
where cte2.cnt = cte2.cnt_max fetch first 1 row only)
select list from cte3;
I'm trying to do this in OLEDB and from what I understand you can't do this because you can't do anything fancy in SQL for OLEDB like declare variables or create a table. So I guess there is no way.
If you are running DB2 9.7 or higher, you can use LISTAGG function. Have a look here:
http://pic.dhe.ibm.com/infocenter/db2luw/v9r7/index.jsp?topic=%2Fcom.ibm.db2.luw.sql.ref.doc%2Fdoc%2Fr0058709.html