How to extract value from middle of substring in SQL? - sql

I have column called "CustomerId" with value "1_Nissan_028" and "2_Ferrari_035".
I would like to extract "Nissan" or "Ferrari" as "CustomerName".
CustomerName is located in middle of CustomerId and lenght varies.
Following SQL query return values like "Nissan_" or "Ferrar".
How to write SQL statement?
SELECT cast(
SUBSTRING(
CustomerId,
6,
charindex('_', CustomerId)
) as nvarchar(32)
) as CustomerName
FROM [sales].[CustomerSales]

Assuming that the value is always the 2nd delimited value, you can use STRING_SPLIT and its ordinal column to achieve this:
SELECT SS.value AS CustomerName
FROM sales.CustomerSales CS
CROSS APPLY STRING_SPLIT(CS.CustomerId,'_',1) SS
WHERE SS.ordinal = 2;

An alternative quirky way of doing this would be to use translate
with customersales as (
select '1_Nissan_028' CustomerId union select '2_Ferrari_035'
)
select customerId,
Replace(Translate(customerId, '0123456789','__________'),'_','') CustomerName
from customersales;

Related

How to make select statement return rows have comma separated from field?

I work on SQL server 2012 I face issue:I can't make select statement
return only rows have comma separated from field ValueHaveComma ?
I need to do
select * from #seachvaluesHaveComma where ValueHaveComma contain comma
my sample as below
create table #seachvaluesHaveComma
(
ID INT IDENTITY(1,1),
ValueHaveComma nvarchar(100)
)
insert into #seachvaluesHaveComma(ValueHaveComma)
values
('1,2'),
('3.5,5.4'),
('a,b,c'),
('A')
Expected result as :
ID ValueHaveComma
1 1,2
2 3.5,5.4
3 a,b,c
Try this query. this is simple query to get value having comma. WE need to use Like operator because you mentioned sql server.
select * from #seachvaluesHaveComma where ValueHaveComma like '%,%'
You can get the results in two ways,
CHARINDEX
select * from #seachvaluesHaveComma where CHARINDEX(ValueHaveComma, ',') > 0;
LIKE
select * from #seachvaluesHaveComma where ValueHaveComma like '%,%';

Append values from 2 different columns in SQL

I have the following table
I need to get the following output as "SVGFRAMXPOSLSVG" from the 2 columns.
Is it possible to get this appended values from 2 columns
Please try this.
SELECT STUFF((
SELECT '' + DEPART_AIRPORT_CODE + ARRIVE_AIRPORT_CODE
FROM #tblName
FOR XML PATH('')
), 1, 0, '')
For Example:-
Declare #tbl Table(
id INT ,
DEPART_AIRPORT_CODE Varchar(50),
ARRIVE_AIRPORT_CODE Varchar(50),
value varchar(50)
)
INSERT INTO #tbl VALUES(1,'g1','g2',NULL)
INSERT INTO #tbl VALUES(2,'g2','g3',NULL)
INSERT INTO #tbl VALUES(3,'g3','g1',NULL)
SELECT STUFF((
SELECT '' + DEPART_AIRPORT_CODE + ARRIVE_AIRPORT_CODE
FROM #tbl
FOR XML PATH('')
), 1, 0, '')
Summary
Use Analytic functions and listagg to get the job done.
Detail
Create two lists of code_id and code values. Match the code_id values for the same airport codes (passengers depart from the same airport they just arrived at). Using lag and lead to grab values from other rows. NULLs will exist for code_id at the start and end of the itinerary. Default the first NULL to 0, and the last NULL to be the previous code_id plus 1. A list of codes will be produced, with a matching index. Merge the lists together and remove duplicates by using a union. Finally use listagg with no delimiter to aggregate the rows onto a string value.
with codes as
(
select
nvl(lag(t1.id) over (order by t1.id),0) as code_id,
t1.depart_airport_code as code
from table1 t1
union
select
nvl(lead(t1.id) over (order by t1.id)-1,lag(t1.id) over (order by t1.id)+1) as code_id,
t1.arrive_airport_code as code
from table1 t1
)
select
listagg(c.code,'') WITHIN GROUP (ORDER BY c.code_id) as result
from codes c;
Note: This solution does rely on an integer id field being available. Otherwise the analytic functions wouldn't have a column to sort by. If id doesn't exist, then you would need to manufacture one based on another column, such as a timestamp or another identifier that ensures the rows are in the correct order.
Use row_number() over (order by myorderedidentifier) as id in a subquery or view to achieve this. Don't use rownum. It could give you unpredictable results. Without an ORDER BY clause, there is no guarantee that the same query will return the same results each time.
Output
| RESULT |
|-----------------|
| SVGFRAMXPOSLSVG |

Max and substring function SQL Server

I need some help in returning the max value for each OrderID.
Example I have a table that have OrderID and there are like: A04-01, B17-10, C12-01, etc.... I'm trying to do a Select where it shows me the max number for each letter. Example: A04-01 is the lowest, and A17-01 is the highest for the letter A. All this was built in Oracle Procedure but we getting rid of Oracle, so I need to re-create it in Microsoft SQL Server. Thank You.
Declare #YourTable table (OrderID varchar(25))
Insert into #YourTable values
('A04-01')
,('A17-10')
,('B17-10')
,('C12-01')
Select Letter = left(OrderID,1)
,MinVal = min(OrderID)
,MaxVal = max(OrderID)
From #YourTableYourTable
Group By left(OrderID,1)
Returns
Letter MinVal MaxVal
A A04-01 A17-10
B B17-10 B17-10
C C12-01 C12-01
If the OrderID is what exactly you mentioned, try this:
WITH ABC
AS
(
Select OrderID, ROW_NUMBER()OVER(Partition by OrderID Order by OrderID desc) as Row_Indicator
From table
)
Select * From ABC
Where ABC.Row_Indicator = 1

sql column which contains a comma separate values

How can I combine multiple rows into one row and have a column which contains a comma separate values?
Example: Originally my SQLresult would return the following using a simple select script like
select order_no, item_no, item_description
from orders...
order_no item_no item_description
1234 5 toaster
1234 6 hair dryer
Instead I would like to return the results into the below (having the item_description listed in the same order as the item_nos?
order_no item_nos item_descriptions
1234 5, 6 toaster, hair dryer
And could I return results like this?
order_no item_nos_descriptions
1234 5 - toaster, 6 - hair dryer
By the way I'm using SQL 2008...
For SQL Server 2005 and up, here's the way that I usually do it without using Recursive CTE
DECLARE #T TABLE
(
order_no int,
item_no int,
item_description nvarchar(50)
)
INSERT INTO #T VALUES (1234, 5, 'toaster')
INSERT INTO #T VALUES (1234, 6, 'hair dryer')
SELECT order_no,
STUFF(
(
SELECT ', ' + CAST(item_no AS VARCHAR) AS [text()]
FROM #T As MyItem
WHERE MyItem.order_no = MyTable.order_no
FOR XML PATH('')
), 1, 2, '' ) AS item_nos,
STUFF(
(
SELECT ', ' + CAST(item_no AS VARCHAR) AS [text()]
FROM #T As MyItem
WHERE MyItem.order_no = MyTable.order_no
FOR XML PATH('')
), 1, 2, '' ) AS item_descriptions
FROM #T AS MyTable
GROUP BY order_no
This yields:
Result Set (1 item)
order_no | item_nos | item_descriptions |
1234 | 5, 6 | 5, 6
The STUFF removes the last ', ' from the string.
The other way to do this is with recursive CTE, but I think the above will do...
Check out the group_concat function (docs).
select
order_no,
group_concat(item_no ORDER BY item_nos ASC SEPARATOR ', ') as item_nos,
group_concat(item_description ORDER BY item_no ASC SEPARATOR ', ')
as item_descriptions
from orders
group by order_no
will give something like this:
order_no item_nos item_descriptions
1234 5, 6 toaster, hair dryer
For the second form you requested, it would look something like this:
select
order_no,
group_concat( concat(item_no,' - ',item_description
ORDER BY item_no ASC SEPARATOR ', ')
as item_nos_descriptions
from orders
group by order_no
I think you should heed #pst if you can.
That said, most relational databases have a function to do exactly this. In MySQL it's group_concat. In Oracle it's wm_concat. In PostgreSQL it's string_agg. Notice it's rather unstandardized.
To use it, you would do something like this:
SELECT order_no, string_agg(item_description, ',')
FROM orders
INNER JOIN line_items ON line_item.order_id = order.id
GROUP BY order_no;
Note that not all databases have a way to get from CSV back to rows. This is something I know PostgreSQL can do. I would expect Oracle to be able to do it, but haven't checked, and I'm fairly sure that MySQL cannot, but could be mistaken.
Try GROUP_CONCAT if you are using mySQL:
SELECT order_no,
GROUP_CONCAT(item_no ORDER BY item_no ASC SEPARATOR ','),
GROUP_CONCAT(item_description ORDER BY item_no ASC SEPARATOR ',')
FROM Orders
GROUP BY order_no
In this way you can keep the original normalized DB schema and still get the following result:
order_no item_nos item_descriptions
1234 5, 6 toaster, hair dryer
For MySQL you could do this:
SELECT order_no,
GROUP_CONCAT(CONCAT(item_no,' - ',item_description) ORDER BY item_no ASC SEPARATOR ', ')
FROM Orders
GROUP BY order_no

Is it possible to concatenate column values into a string using CTE?

Say I have the following table:
id|myId|Name
-------------
1 | 3 |Bob
2 | 3 |Chet
3 | 3 |Dave
4 | 4 |Jim
5 | 4 |Jose
-------------
Is it possible to use a recursive CTE to generate the following output:
3 | Bob, Chet, Date
4 | Jim, Jose
I've played around with it a bit but haven't been able to get it working. Would I do better using a different technique?
I do not recommend this, but I managed to work it out.
Table:
CREATE TABLE [dbo].[names](
[id] [int] NULL,
[myId] [int] NULL,
[name] [char](25) NULL
) ON [PRIMARY]
Data:
INSERT INTO names values (1,3,'Bob')
INSERT INTO names values 2,3,'Chet')
INSERT INTO names values 3,3,'Dave')
INSERT INTO names values 4,4,'Jim')
INSERT INTO names values 5,4,'Jose')
INSERT INTO names values 6,5,'Nick')
Query:
WITH CTE (id, myId, Name, NameCount)
AS (SELECT id,
myId,
Cast(Name AS VARCHAR(225)) Name,
1 NameCount
FROM (SELECT Row_number() OVER (PARTITION BY myId ORDER BY myId) AS id,
myId,
Name
FROM names) e
WHERE id = 1
UNION ALL
SELECT e1.id,
e1.myId,
Cast(Rtrim(CTE.Name) + ',' + e1.Name AS VARCHAR(225)) AS Name,
CTE.NameCount + 1 NameCount
FROM CTE
INNER JOIN (SELECT Row_number() OVER (PARTITION BY myId ORDER BY myId) AS id,
myId,
Name
FROM names) e1
ON e1.id = CTE.id + 1
AND e1.myId = CTE.myId)
SELECT myID,
Name
FROM (SELECT myID,
Name,
(Row_number() OVER (PARTITION BY myId ORDER BY namecount DESC)) AS id
FROM CTE) AS p
WHERE id = 1
As requested, here is the XML method:
SELECT myId,
STUFF((SELECT ',' + rtrim(convert(char(50),Name))
FROM namestable b
WHERE a.myId = b.myId
FOR XML PATH('')),1,1,'') Names
FROM namestable a
GROUP BY myId
A CTE is just a glorified derived table with some extra features (like recursion). The question is, can you use recursion to do this? Probably, but it's using a screwdriver to pound in a nail. The nice part about doing the XML path (seen in the first answer) is it will combine grouping the MyId column with string concatenation.
How would you concatenate a list of strings using a CTE? I don't think that's its purpose.
A CTE is just a temporarily-created relation (tables and views are both relations) which only exists for the "life" of the current query.
I've played with the CTE names and the field names. I really don't like reusing fields names like id in multiple places; I tend to think those get confusing. And since the only use for names.id is as a ORDER BY in the first ROW_NUMBER() statement, I don't reuse it going forward.
WITH namesNumbered as (
select myId, Name,
ROW_NUMBER() OVER (
PARTITION BY myId
ORDER BY id
) as nameNum
FROM names
)
, namesJoined(myId, Name, nameCount) as (
SELECT myId,
Cast(Name AS VARCHAR(225)),
1
FROM namesNumbered nn1
WHERE nameNum = 1
UNION ALL
SELECT nn2.myId,
Cast(
Rtrim(nc.Name) + ',' + nn2.Name
AS VARCHAR(225)
),
nn.nameNum
FROM namesJoined nj
INNER JOIN namesNumbered nn2 ON nn2.myId = nj.myId
and nn2.nameNum = nj.nameCount + 1
)
SELECT myId, Name
FROM (
SELECT myID, Name,
ROW_NUMBER() OVER (
PARTITION BY myId
ORDER BY nameCount DESC
) AS finalSort
FROM namesJoined
) AS tmp
WHERE finalSort = 1
The first CTE, namesNumbered, returns two fields we care about and a sorting value; we can't just use names.id for this because we need, for each myId value, to have values of 1, 2, .... names.id will have 1, 2 ... for myId = 1 but it will have a higher starting value for subsequent myId values.
The second CTE, namesJoined, has to have the field names specified in the CTE signature because it will be recursive. The base case (part before UNION ALL) gives us records where nameNum = 1. We have to CAST() the Name field because it will grow with subsequent passes; we need to ensure that we CAST() it large enough to handle any of the outputs; we can always TRIM() it later, if needed. We don't have to specify aliases for the fields because the CTE signature provides those. The recursive case (after the UNION ALL) joins the current CTE with the prior one, ensuring that subsequent passes use ever-higher nameNum values. We need to TRIM() the prior iterations of Name, then add the comma and the new Name. The result will be, implicitly, CAST()ed to a larger field.
The final query grabs only the fields we care about (myId, Name) and, within the subquery, pointedly re-sorts the records so that the highest namesJoined.nameCount value will get a 1 as the finalSort value. Then, we tell the WHERE clause to only give us this one record (for each myId value).
Yes, I aliased the subquery as tmp, which is about as generic as you can get. Most SQL engines require that you give a subquery an alias, even if it's the only relation visible at that point.