How to ORDER BY Alphanumeric values in SQL specific columns - sql

create table Employee(id int, Registration_no varchar(50),Name varchar(50))
insert into #Employee values(1,'DLW/TTC/19/3','RAMESH')
insert into #Employee values(2,'DLW/TTC/19/2','RAJEEV')
insert into #Employee values(3,'DLW/TTC/19/1','RUPAK')
insert into #Employee values(4,'DLW/TTC/19/4','RAMLAAL')
insert into #Employee values(5,'DLW/TTC/19/8','RITESH')
insert into #Employee values(6,'DLW/TTC/19/6','HRITIK')
insert into #Employee values(7,'DLW/TTC/19/9','ROSHAN')
insert into #Employee values(8,'DLW/TTC/19/7','RUPALI')
insert into #Employee values(9,'DLW/TTC/19/5','SHRISTI')
insert into #Employee values(10,'DLW/TTC/19/10','ROSHNI')
select * from Employee
Hello I have the table given above.
Actually am facing problem while am trying to order this table's column (Registration_no)
So kindly help me to ORDER it according to its (Registration_no) column
Its not matter that how the other columns are arranged. I just want my Registration_no column to be arranged in the specific order like this
Registration_no
DLW/TTC/19/1
DLW/TTC/19/2
DLW/TTC/19/3
DLW/TTC/19/4
DLW/TTC/19/5
DLW/TTC/19/6
DLW/TTC/19/7
DLW/TTC/19/8
DLW/TTC/19/9
DLW/TTC/19/10

This will sort by the digits to the right of the last / in the Registration_No string. I'm only including the SortColumn in the result set so you can see the values. You can omit it from your query.
SELECT
e.*,
CAST(RIGHT(e.Registration_no,CHARINDEX('/',REVERSE(e.Registration_no))-1) AS INTEGER) AS SortColumn
FROM #Employee AS e
ORDER BY
CAST(RIGHT(e.Registration_no,CHARINDEX('/',REVERSE(e.Registration_no))-1) AS INTEGER)
Results:
+----+-----------------+---------+------------+
| id | Registration_no | Name | SortColumn |
+----+-----------------+---------+------------+
| 3 | DLW/TTC/19/1 | RUPAK | 1 |
| 2 | DLW/TTC/19/2 | RAJEEV | 2 |
| 1 | DLW/TTC/19/3 | RAMESH | 3 |
| 4 | DLW/TTC/19/4 | RAMLAAL | 4 |
| 9 | DLW/TTC/19/5 | SHRISTI | 5 |
| 6 | DLW/TTC/19/6 | HRITIK | 6 |
| 8 | DLW/TTC/19/7 | RUPALI | 7 |
| 5 | DLW/TTC/19/8 | RITESH | 8 |
| 7 | DLW/TTC/19/9 | ROSHAN | 9 |
| 10 | DLW/TTC/19/10 | ROSHNI | 10 |
+----+-----------------+---------+------------+
The SortColumn functions first REVERSE the string, then use CHARINDEX to find the position from the end of the string of the last occurrence of /, then take that number -1 from the right side of the original column (-1 to exclude the / itself).

You can use reverse function together with charindex function
SELECT e.*, cast( reverse(substring(reverse(Registration_no),1,
charindex('/',reverse(Registration_no),1) -1 ) ) as int ) as nr
FROM employee e
ORDER BY nr;
Demo
The main principle is extracting the pieces and converting them to a numerical value, such as integer, in the tail part of the string values. It's easier to operate from the beginning with substring function to this extraction provided reverse function is used to make read the string reversely. And in this case we need to determine the position of the first delimiter(/) by contribution of charindex function. All those functions exist since the version 2008.

If the pattern at the end of Registration_no is always like /X or /XX then:
select * from Employee
order by
case left(right(Registration_no, 2), 1)
when '/' then
left(Registration_no, len(Registration_no) - 1) + '0' + right(Registration_no, 1)
else Registration_no
end
See the demo.

Although I dont like the way the number is extracted, this is how it can be done
select cast(substring(registration_no, charindex('/', registration_no, len(registration_no) -3) + 1, 3) as int),
* from Employee
order by 1
I would suggest you order it on the frontend using regex assuming that you are wanting to order it for display purpose.

How about this.
select *
from Employee
order by LEFT(Registration_no,PATINDEX('%[0-9]%',Registration_no)-1)-- alpha sort
, CONVERT(INT,REPLACE(SUBSTRING(Registration_no,PATINDEX('%[0-9]%',Registration_no),PATINDEX('%[0-9]%',Registration_no)),'/', '')) -- number sort

The first thing you'll notice as you try to order by the Registration_no is that it will be ordered alphabetically due to the nature of the string contents of the column of type varchar. So the correct way to do it is to convert the last two parts of the reg no into numbers and use them in an order by clause. (in this case we can use only the last part since the 2nd last is always 19)
Doing a bit of search I found this function 'Parsename' which has a usage in replication scenarios as it splits the SQL object schema name into its forming parts, so we can use it here (as long as the reg no parts doesn't exceed 4 parts "the SQL objects maximum parts")
So this will work in that case in SQL Server (T-SQL):
SELECT *
FROM Employee
order by cast(Parsename(replace(Registration_no,'/','.'),1) as int)
More info here
Thanks

Try this query.
create table #Employee(id int, Registration_no varchar(50),Name varchar(50))
insert into #Employee values(1,'DLW/TTC/19/3','RAMESH')
insert into #Employee values(2,'DLW/TTC/19/2','RAJEEV')
insert into #Employee values(3,'DLW/TTC/19/1','RUPAK')
insert into #Employee values(4,'DLW/TTC/19/4','RAMLAAL')
insert into #Employee values(5,'DLW/TTC/19/8','RITESH')
insert into #Employee values(6,'DLW/TTC/19/6','HRITIK')
insert into #Employee values(7,'DLW/TTC/19/9','ROSHAN')
insert into #Employee values(8,'DLW/TTC/19/7','RUPALI')
insert into #Employee values(9,'DLW/TTC/19/5','SHRISTI')
insert into #Employee values(10,'DLW/TTC/19/10','ROSHNI')
select * from #Employee
order by Registration_no
id Registration_no Name
----------- -------------------------------------------------- --------------------------------------------------
3 DLW/TTC/19/1 RUPAK
10 DLW/TTC/19/10 ROSHNI
2 DLW/TTC/19/2 RAJEEV
1 DLW/TTC/19/3 RAMESH
4 DLW/TTC/19/4 RAMLAAL
9 DLW/TTC/19/5 SHRISTI
6 DLW/TTC/19/6 HRITIK
8 DLW/TTC/19/7 RUPALI
5 DLW/TTC/19/8 RITESH
7 DLW/TTC/19/9 ROSHAN

Related

SQL Server - Ordering Combined Number Strings Prior To Column Insert

I have 2 string columns (thousands of rows) with ordered numbers in each string (there can be zero to ten numbers in each string). Example:
+------------------+------------+
| ColString1 | ColString2 |
+------------------+------------+
| 1;3;5;12; | 4;6' |
+------------------+------------+
| 1;5;10 | 2;26; |
+------------------+------------+
| 4;7; | 3; |
+------------------+------------+
The end result is to combine these 2 columns, sort the numbers in
ascending order and then put each number into individual columns (smallest, 2nd smallest etc).
e.g. Colstring1 is 1;3;5;12; and ColString2 is 4;6; needs to return 1;3;4;5;6;12; which I then use xml to allocated into columns.
Everthing works fine using xml apart from the step to order the numbers (i.e I'm getting 1;3;5;12;4;6; when I combine the strings i.e. not in ascending order).
I've tried put them into a JSON array first to order, thinking I could do a top[1] etc but that did not work.
Any help on how to combine the 2 columns and order them before inserting into columns:
Steps so far:
Example data:
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, ColString1 VARCHAR(50), ColString2 VARCHAR(50));
INSERT INTO #tbl (ColString1, ColString2)
VALUES
('1;3;5;12;', '4;6;'),
('1;5;10;', '2;26;'),
('14;', '3;8;');
XML Approach (Combines strings and puts into columns but not in the correct order):
;WITH Split_Numbers (xmlname)
AS
(
SELECT
CONVERT(XML,'<Names><name>'
+ REPLACE ( LEFT(ColString1+ColString2,LEN(ColString1+ColString2) - 1),';', '</name><name>') + '</name></Names>') AS xmlname
FROM #tbl
)
SELECT
xmlname.value('/Names[1]/name[1]','int') AS Number1,
xmlname.value('/Names[1]/name[2]','int') AS Number2,
xmlname.value('/Names[1]/name[3]','int') AS Number3,
xmlname.value('/Names[1]/name[4]','int') AS Number4,
xmlname.value('/Names[1]/name[5]','int') AS Number5
--etc for additional columns
FROM Split_Numbers
Current Output: numbers not in correct order,
+---------+---------+---------+---------+---------+
| Number1 | Number2 | Number3 | Number4 | Number5 |
+---------+---------+---------+---------+---------+
| 1 | 3 | 5 | 12 | 4 |
| 1 | 5 | 10 | 2 | 26 |
| 14 | 3 | 8 | NULL | NULL |
+---------+---------+---------+---------+---------+
Desired Output: numbers in ascending order.
+---------+---------+---------+---------+---------+
| Number1 | Number2 | Number3 | Number4 | Number5 |
+---------+---------+---------+---------+---------+
| 1 | 3 | 4 | 5 | 6 |
| 1 | 2 | 5 | 10 | 26 |
| 3 | 8 | 14 | NULL | NULL |
+---------+---------+---------+---------+---------+
JSON Approach: combines the columns into a JSON array but I still can't order correctly when in JSON format.
REPLACE ( CONCAT('[', LEFT(ColString1+ColString2,LEN(ColString1+ColString2) - 1), ']') ,';',',')
Any help will be greatly appreciated whether there is a way to order the xml or JSON string prior to entry. Happy to consider an alternative way if there is an easier solution.
You can use string_agg() and string_split():
select t.*, newstring
from t cross apply
(select string_agg(value, ',') order by (value) as newstring
from (select s1.value
from unnest(colstring1, ',') s1
union all
select s2.value
from unnest(colstring2, ',') s2
) s
) s;
That said, you should probably put your effort into fixing the data model. Storing numbers in strings is bad. Storing multiple values in a string is bad, bad. If the numbers are foreign references to other tables, that is bad, bad, bad, bad, bad.
While waiting for a DDL and sample data population, etc., here is a conceptual example for you. It is using XQuery and its FLWOR expression.
CTE does most of the heavy lifting:
Concatenates both columns values into one string. CONCAT() function protects against NULL values.
Converts it into XML data type.
Sorts XML elements by converting their values to int data type in the FLWOR expression.
Filters out XML elements with no legit values.
The rest is trivial.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, col1 VARCHAR(100), col2 VARCHAR(100));
INSERT INTO #tbl (col1, col2)
VALUES
('1;3;5;12;', '4;6;'),
('1;5;10;', '2;26;');
-- DDL and sample data population, end
DECLARE #separator CHAR(1) = ';';
;WITH rs AS
(
SELECT *
, CAST('<root><r><![CDATA[' +
REPLACE(CONCAT(col1, col2), #separator, ']]></r><r><![CDATA[') +
']]></r></root>' AS XML).query('<root>
{
for $x in /root/r[text()]
order by xs:int($x)
return $x
}
</root>') AS sortedXML
FROM #tbl
)
SELECT ID
, c.value('(r[1]/text())[1]','INT') AS Number1
, c.value('(r[2]/text())[1]','INT') AS Number2
, c.value('(r[3]/text())[1]','INT') AS Number3
-- continue with the rest of the columns
FROM rs CROSS APPLY sortedXML.nodes('/root') AS t(c);
Output
+----+---------+---------+---------+
| ID | Number1 | Number2 | Number3 |
+----+---------+---------+---------+
| 1 | 1 | 3 | 4 |
| 2 | 1 | 2 | 5 |
+----+---------+---------+---------+

Use input and output stored procedure to list multiple values

Hello I have written a stored procedure which has input dept id and output as varchar. I have to list all the records having dept id passed through input. Can anyone help me with it. I am able to print only single value but i want all the values having dept id passed in input.
You can pass the departmentIDs are comma separated list and then split them and use them as given below.
STRING_SPLIT function is available from SQL Server 2016 onwards.
DECLARE #dept table (deptId int, deptName varchar(30))
INSERT INTO #dept
values (1,'1'),(2,'2'),(3,'3'),(4,'4'),(5,'5');
DECLARE #DeptidList VARCHAR(4000) = '1,3,4'
SELECT * FROM #dept
WHERE deptID IN
(
select value from string_split(#DeptidList,',')
)
+--------+----------+
| deptId | deptName |
+--------+----------+
| 1 | 1 |
| 3 | 3 |
| 4 | 4 |
+--------+----------+

Get rows where value is not a substring in another row

I'm writing recursive sql against a table that contains circular references.
No problem! I read that you can build a unique path to prevent infinite loops. Now I need to filter the list down to only the last record in the chain. I must be doing something wrong though. -edit I'm adding more records to this sample to make it more clear why just selecting the longest record doesn't work.
This is an example table:
create table strings (id int, string varchar(200));
insert into strings values (1, '1');
insert into strings values (2, '1,2');
insert into strings values (3, '1,2,3');
insert into strings values (4, '1,2,3,4');
insert into strings values (5, '5');
And my query:
select * from strings str1 where not exists
(
select * from strings str2
where str2.id <> str1.id
and str1.string || '%' like str2.string
)
I'd expect to only get the last records
| id | string |
|----|---------|
| 4 | 1,2,3,4 |
| 5 | 5 |
Instead I get them all
| id | string |
|----|---------|
| 1 | 1 |
| 2 | 1,2 |
| 3 | 1,2,3 |
| 4 | 1,2,3,4 |
| 5 | 5 |
Link to sql fiddle: http://sqlfiddle.com/#!15/7a974/1
My problem was all around the 'LIKE' comparison.
select * from strings str1
where not exists
(
select
*
from
strings str2
where
str2.id <> str1.id
and str2.string like str1.string || '%'
)

Get previous and next row from rows selected with (WHERE) conditions

For example I have this statement:
my name is Joseph and my father's name is Brian
This statement is splitted by word, like this table:
------------------------------
| ID | word |
------------------------------
| 1 | my |
| 2 | name |
| 3 | is |
| 4 | Joseph |
| 5 | and |
| 6 | my |
| 7 | father's |
| 8 | name |
| 9 | is |
| 10 | Brian |
------------------------------
I want to get previous and next word of each word
For example I want to get previous and next word of "name":
--------------------------
| my | name | is |
--------------------------
| father's | name | is |
--------------------------
How could I get this result?
you didn't specify your DBMS, so the following is ANSI SQL:
select prev_word, word, next_word
from (
select id,
lag(word) over (order by id) as prev_word,
word,
lead(word) over (order by id) as next_word
from words
) as t
where word = 'name';
SQLFiddle: http://sqlfiddle.com/#!12/7639e/1
Why did no-body give the simple answer?
SELECT LAG(word) OVER ( ORDER BY ID ) AS PreviousWord ,
word ,
LEAD(word) OVER ( ORDER BY ID ) AS NextWord
FROM words;
Without subqueries:
SELECT a.word
FROM my_table AS a
JOIN my_table AS b
ON b.word = 'name' AND abs(a.id - b.id) <= 1
ORDER BY a.id
Use Join to get the expected result for SQL Server 2005 plus.
create table words (id integer, word varchar(20));
insert into words
values
(1 ,'my'),
(2 ,'name'),
(3 ,'is'),
(4 ,'joseph'),
(5 ,'and'),
(6 ,'my'),
(7 ,'father'),
(8 ,'name'),
(9 ,'is'),
(10,'brian');
SELECT A.Id , C.word AS PrevName ,
A.word AS CurName ,
B.word AS NxtName
FROM words AS A
LEFT JOIN words AS B ON A.Id = B.Id - 1
LEFT JOIN words AS C ON A.Id = C.Id + 1
WHERE A.Word = 'name'
Result:
Fiddler Demo
Try this
SELECT *
FROM tablename a
WHERE ID IN(SELECT ID - 1
FROM tablename
WHERE word = 'name') -- will fetch previous rows of word `name`
OR ID IN(SELECT ID + 1
FROM tablename
WHERE word = 'name') -- will fetch next rows of word `name`
OR word = 'name' -- to fetch the rows where word = `name`
Here's a different approach, if you want the selects to be fast. It takes a bit of preparation work.
Create a new column (e.g. "phrase") in the database that will contain the words
you want. (i.e. the previous, the current and next).
Write a trigger that on insert appends the new word to the previous
row's phrase and prepends the previous row's word to the new row's word and fills
phrase.
If the individual words can change, you'll need a trigger on update to keep the phrase in sync.
Then just select the phrase. You get much better speed, but at the cost of extra storage and slower insert and harder maintainability. Obviously you have to update the phrase column for the existing records, but you have the SQL to do that in the other answers.

Selecting a record based on integer being in an array field

I have a database of houses. Within the houses mssql database record is a field called areaID. A house could be in multiple areas so an entry could be as follows in the database:
+---------+----------------------+-----------+-------------+-------+
| HouseID | AreaID | HouseType | Description | Title |
+---------+----------------------+-----------+-------------+-------+
| 21 | 17, 32, 53 | B | data | data |
+---------+----------------------+-----------+-------------+-------+
| 23 | 23, 73 | B | data | data |
+---------+----------------------+-----------+-------------+-------+
| 24 | 53, 12, 153, 72, 153 | B | data | data |
+---------+----------------------+-----------+-------------+-------+
| 23 | 23, 53 | B | data | data |
+---------+----------------------+-----------+-------------+-------+
If I open a page that called for houses only in area 53 how would I search for it. I know in MySQL you can use find_in_SET but I am using Microsoft SQL Server 2005.
If your formatting is EXACTLY
N1, N2 (e.g.) one comma and space between each N
Then use this WHERE clause
WHERE ', ' + AreaID + ',' LIKE '%, 53,%'
The addition of the prefix and suffix makes every number, anywhere in the list, consistently wrapped by comma-space and suffixed by comma. Otherwise, you may get false positives with 53 appearing in part of another number.
Note
A LIKE expression will be anything but fast, since it will always scan the entire table.
You should consider normalizing the data into two tables:
Tables become
House
+---------+----------------------+----------+
| HouseID | HouseType | Description | Title |
+---------+----------------------+----------+
| 21 | B | data | data |
| 23 | B | data | data |
| 24 | B | data | data |
| 23 | B | data | data |
+---------+----------------------+----------+
HouseArea
+---------+-------
| HouseID | AreaID
+---------+-------
| 21 | 17
| 21 | 32
| 21 | 53
| 23 | 23
| 23 | 73
..etc
Then you can use
select * from house h
where exists (
select *
from housearea a
where h.houseid=a.houseid and a.areaid=53)
2 options, change the id's of AreaId so that you can use the & operator OR create a table that links the House and Area's....
What datatype is AreaID?
If it's a text field you could something like
WHERE (
AreaID LIKE '53,%' -- Covers: multi number seq w/ 53 at beginning
OR AreaID LIKE '% 53,%' -- Covers: multi number seq w/ 53 in middle
OR AreaID LIKE '% 53' -- Covers: multi number seq w/ 53 at end
OR AreaID = '53' -- Covers: single number seq w/ only 53
)
Note: I haven't used SQL-Server in some time, so I'm not sure about the operators. PostgreSQL has a regex function, which would be better at condensing that WHERE statement. Also, I'm not sure if the above example would include numbers like 253 or 531; it shouldn't but you still need to verify.
Furthermore, there are a bunch of functions that iterate through arrays, so storing it as an array vs text might be better. Finally, this might be a good example to use a stored procedure, so you can call your homebrewed function instead of cluttering your SQL.
Use a Split function to convert comma-separated values into rows.
CREATE TABLE Areas (AreaID int PRIMARY KEY);
CREATE TABLE Houses (HouseID int PRIMARY KEY, AreaIDList varchar(max));
GO
INSERT INTO Areas VALUES (84);
INSERT INTO Areas VALUES (24);
INSERT INTO Areas VALUES (66);
INSERT INTO Houses VALUES (1, '84,24,66');
INSERT INTO Houses VALUES (2, '24');
GO
CREATE FUNCTION dbo.Split (#values varchar(512)) RETURNS table
AS
RETURN
WITH Items (Num, Start, [Stop]) AS (
SELECT 1, 1, CHARINDEX(',', #values)
UNION ALL
SELECT Num + 1, [Stop] + 1, CHARINDEX(',', #values, [Stop] + 1)
FROM Items
WHERE [Stop] > 0
)
SELECT Num, SUBSTRING(#values, Start,
CASE WHEN [Stop] > 0 THEN [Stop] - Start ELSE LEN(#values) END) Value
FROM Items;
GO
CREATE VIEW dbo.HouseAreas
AS
SELECT h.HouseID, s.Num HouseAreaNum,
CASE WHEN s.Value NOT LIKE '%[^0-9]%'
THEN CAST(s.Value AS int)
END AreaID
FROM Houses h
CROSS APPLY dbo.Split(h.AreaIDList) s
GO
SELECT DISTINCT h.HouseID, ha.AreaID
FROM Houses h
INNER JOIN HouseAreas ha ON ha.HouseID = h.HouseID
WHERE ha.AreaID = 24