Order by with regular expression - sql

there a column which i want to sort
C_NUMBER
---------
1718-SI-1
1718-SI-2
1718-SI-10
1718-SI-13
1718-SI-5
1718-SI-6
1718-SI-11
and this is the query where i am bringing my data in one table and applying order by but it is not working.
MYTABLE order by MYTABLE.C_NUMBER asc, patindex('%0-9]%',MYTABLE.C_NUMBER),len(MYTABLE.C_NUMBER)

I think you don't want to be ordering by the C_NUMBER first, just your patindex section. Try this;
order by patindex('%0-9]%',MYTABLE.C_NUMBER),len(MYTABLE.C_NUMBER)

Try this
;With cte(C_NUMBER)
AS
(
SELECT '1718-SI-1' UNION ALL
SELECT '1718-SI-2' UNION ALL
SELECT '1718-SI-10' UNION ALL
SELECT '1718-SI-13' UNION ALL
SELECT '1718-SI-5' UNION ALL
SELECT '1718-SI-6' UNION ALL
SELECT '1718-SI-11'
)
SELECT * FROM cte
Order by CAST(REVERSE(SUBSTRING(REVERSE(C_NUMBER),0, CHARINDEX('-',REVERSE(C_NUMBER)))) AS INT)
Result
C_NUMBER
----------
1718-SI-1
1718-SI-2
1718-SI-5
1718-SI-6
1718-SI-10
1718-SI-11
1718-SI-13

This code will cut your string in three parts and use these values - typesafe!! - in the ORDER BY clause separately. This will sort differen 1718-SI or 1719-BI or whatever you might have.
DECLARE #mockup TABLE(ID INT IDENTITY, C_NUMBER VARCHAR(100));
INSERT INTO #mockup VALUES
('1718-SI-1')
,('1718-SI-2')
,('1718-SI-10')
,('1718-SI-13')
,('1718-SI-5')
,('1718-SI-6')
,('1718-SI-11');
SELECT * FROM #mockup;
SELECT m.*
FROM #mockup AS m
CROSS APPLY(SELECT CAST('<x>' + REPLACE(m.C_NUMBER,'-','</x><x>') + '</x>' AS XML)) AS Parted(AsXML)
ORDER BY AsXML.value('/x[1]/text()[1]','int')
,AsXML.value('/x[2]/text()[1]','nvarchar(max)')
,AsXML.value('/x[3]/text()[1]','int')
HINT: Your design breaks 1.NF
Store the three parts in three different typed columns, apply indexes and create the output format on-the-fly. You should not store more than one value in a cell...

Related

Trying to filter on a union of 3 queries

I have a union of 3 queries that summarizes like this...
Select param1 As 'example1' And .... Where...
Union All
Select param1 As 'example2' And .... Where...
Union All
Select param1 As 'example3' And .... Where...
Is there any way to wrap this in a Select and create an optional parameter that filters on example1/example2/example3?
Any help would be greatly appreciated. Thanks!
Sorry everyone...when I was brainstorming in my head, I had my query wrong and I'm not very good at sql anyway. But what I want to do was filter on the created column of AccountStatus as an optional parameter. Is there some way to capture all this and then add an optional parameter to filter on the created column?
Select 'Red Account' As AccountStatus And .... Where OverdueDays >= 30
Union All
Select 'Yellow Account' As AccountStatus And .... Where 10 < OverdueDays < 30
Union All
Select 'Green Account' As AccountStatus And .... Where OverdueDays <= 10
Is there any way to wrap this in a Select and create an optional parameter that filters on example1/example2/example3?
Assuming that I understood your need then you can use common table expression (CTE).
In the following document you can read more about the option of using CTE:
https://learn.microsoft.com/en-us/sql/t-sql/queries/with-common-table-expression-transact-sql?view=sql-server-ver15
In general, you wrap your code into a CTE (in the following example I will name my CTE MyCTE but you can use any name), and then in the outside query you can add the filter which you want.
Do not confuse between CTE and a variable or temporary table. A CTE is not a physical entity but logically for the sake pf the query and the server will parse it as inline code.
For example:
;With MyCTE as ( <here comes the code you want to wrap> )
SELECT <choose columns to select>
FROM MyCTE
<here you can add order by or filetring with where>
In your case it might look like:
;With MyCTE as (
-- when you use UNION then the name of the columns in the result is configured by the names in the first query, so you do not need all the "as..." for the other queries.
-- not clear why you use "And" in the name of the columns you select. If you need more than one column then you should use comma "," between the columns
Select param1 As [example1], column2, column3... Where... <this filter only this specific query>
Union All
Select param1, column2, column3.... Where...
Union All
Select param1, column2, column3.... Where...
)
SELECT param1, column2, column3....
FROM MyCTE -- we select from the logical CTE as it was a table
WHERE <here you can add condition to filter the result of the UNION>
Yes it is possible. And here is where you should make the effort provide consumable information that you and everyone else can use as a basis for a solution. Below is the code in this fiddle which you can play with.
-- "parameters"
--declare #p1 varchar(20) = null;
declare #p1 varchar(20) = 'waggle';
-- some crap data
declare #x table (id int, trandate date);
insert #x (id, trandate) values
(1, '20190101'), (2, '20190425'),
(4, '20200311'), (5, '20200630'), (11, '20200801'),
(12, '20210101'), (13, '20210710');
with crap_union as (
select 'wiggle' as [when], id, trandate
from #x where year(trandate) = 2019
union all
select 'waggle' as [when], id, trandate
from #x where year(trandate) = 2020
union all
select 'waddle' as [when], id, trandate
from #x where year(trandate) = 2021
)
select * from crap_union
where [when] = #p1 or #p1 is null
order by [when], id, trandate
;
Same idea as generically expressed by Ronen. You add some sort of identifier to each query in the UNION. That allows you to "see" which row comes from which query. You stuff that UNION into a CTE or a derived table (same logical effect) and then select from that CTE (or derived table) with the desired filter.
You might consider reading Erland's discussion on dynamic search conditions to understand the performance considerations of this approach. Bookmark Erland's website since it has much useful information.

Generating Lines based on a value from a column in another table

I have the following table:
EventID=00002,DocumentID=0005,EventDesc=ItemsReceived
I have the quantity in another table
DocumentID=0005,Qty=20
I want to generate a result of 20 lines (depending on the quantity) with an auto generated column which will have a sequence of:
ITEM_TAG_001,
ITEM_TAG_002,
ITEM_TAG_003,
ITEM_TAG_004,
..
ITEM_TAG_020
Here's your sql query.
with cte as (
select 1 as ctr, t2.Qty, t1.EventID, t1.DocumentId, t1.EventDesc from tableA t1
inner join tableB t2 on t2.DocumentId = t1.DocumentId
union all
select ctr + 1, Qty, EventID, DocumentId, EventDesc from cte
where ctr <= Qty
)select *, concat('ITEM_TAG_', right('000'+ cast(ctr AS varchar(3)),3)) from cte
option (maxrecursion 0);
Output:
Best is to introduce a numbers table, very handsome in many places...
Something along:
Create some test data:
DECLARE #MockNumbers TABLE(Number BIGINT);
DECLARE #YourTable1 TABLE(DocumentID INT,ItemTag VARCHAR(100),SomeText VARCHAR(100));
DECLARE #YourTable2 TABLE(DocumentID INT, Qty INT);
INSERT INTO #MockNumbers SELECT TOP 100 ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) FROM master..spt_values;
INSERT INTO #YourTable1 VALUES(1,'FirstItem','qty 5'),(2,'SecondItem','qty 7');
INSERT INTO #YourTable2 VALUES(1,5), (2,7);
--The query
SELECT CONCAT(t1.ItemTag,'_',REPLACE(STR(A.Number,3),' ','0'))
FROM #YourTable1 t1
INNER JOIN #YourTable2 t2 ON t1.DocumentID=t2.DocumentID
CROSS APPLY(SELECT Number FROM #MockNumbers WHERE Number BETWEEN 1 AND t2.Qty) A;
The result
FirstItem_001
FirstItem_002
[...]
FirstItem_005
SecondItem_001
SecondItem_002
[...]
SecondItem_007
The idea in short:
We use an INNER JOIN to get the quantity joined to the item.
Now we use APPLY, which is a row-wise action, to bind as many rows to the set, as we need it.
The first item will return with 5 lines, the second with 7. And the trick with STR() and REPLACE() is one way to create a padded number. You might use FORMAT() (v2012+), but this is working rather slowly...
The table #MockNumbers is a declared table variable containing a list of numbers from 1 to 100. This answer provides an example how to create a pyhsical numbers and date table. Any database should have such a table...
If you don't want to create a numbers table, you can search for a tally table or tally on the fly. There are many answers showing approaches how to create a list of running numbers...a

How to select 2 cross split string column in single query

CREATE TABLE #StudentClasses
(
ID INT,
Student VARCHAR(100),
Classes VARCHAR(100),
CCode VARCHAR(30)
)
GO
INSERT INTO #StudentClasses
SELECT 1, 'Mark', 'Maths,Science,English', 'US,UK,AUS'
UNION ALL
SELECT 2, 'John', 'Science,English', 'BE,DE'
UNION ALL
SELECT 3, 'Robert', 'Maths,English', 'CA,IN'
GO
SELECT *
FROM #StudentClasses
GO
SELECT ID, Student, value ,value
FROM #StudentClasses
CROSS APPLY STRING_SPLIT(Classes, ',')
CROSS APPLY STRING_SPLIT(CCode, ',')
This must be put in the very first place: Do not store delimited data! If there is any chance to change your table's design, you should use related side-tables to store data this kind...
Your question is not much better than the one before. Without your expected result any suggestion must be guessing.
What I guess: You want to transform 'Maths,Science,English', 'US,UK,AUS' in a way, that Maths goes along with US, Science along with UK and English matches AUS. Try this
SELECT sc.ID
,sc.Student
,A.[key] AS Position
,A.[value] AS Class
,B.[value] AS CCode
FROM #StudentClasses sc
CROSS APPLY OPENJSON('["' + REPLACE(Classes,',','","') + '"]') A
CROSS APPLY OPENJSON('["' + REPLACE(CCode,',','","') + '"]') B
WHERE A.[key]=B.[key];
You did not tell us your SQL Server's version... But you tagged with Azure. Therefore I assume, that v2016 is okay for you. With a lower version (or a lower compatibility level of the given database) there is no JSON support.
Why JSON at all? This is the best way at the moment to split CSV data and get the fragments together with their position within the array. Regrettfully STRING_SPLIT() does not guarantee to return the expected order. With versions lower than v2016 there are several more or less ugly tricks...
If you need your result side-by-side you should read about conditional aggregation.
use select all or use alias
CREATE TABLE #StudentClasses
(ID INT, Student VARCHAR(100), Classes VARCHAR(100),CCode varchar(30))
INSERT INTO #StudentClasses
SELECT 1, 'Mark', 'Maths,Science,English', 'US,UK,AUS'
UNION ALL
SELECT 2, 'John', 'Science,English', 'BE,DE'
UNION ALL
SELECT 3, 'Robert', 'Maths,English', 'CA,IN'
SELECT *,v1.value as clases,v2.value as codes
FROM #StudentClasses
CROSS APPLY STRING_SPLIT(Classes, ',') v2
CROSS APPLY STRING_SPLIT(CCode,
',') v1

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 |

Select values that don't occur in a table

I'm sure this has been asked somewhere, but I found it difficult to search for.
If I want to get all records where a column value equals one in a list, I'd use the IN operator.
SELECT idSparePart, SparePartName
FROM tabSparePart
WHERE SparePartName IN (
'1234-2043','1237-8026','1238-1036','1238-1039','1223-5172'
)
Suppose this SELECT returns 4 rows although the list has 5 items. How can I select the value that does not occur in the table?
Thanks in advance.
select t.* from (
select '1234-2043' as sparePartName
union select '1237-8026'
union select '1238-1036'
union select '1238-1039'
union select '1223-5172'
) t
where not exists (
select 1 from tabSparePart p WHERE p.SparePartName = t.sparePartName
)
As soon as you mentioned that i have to create a temp table, i remembered my Split-function.
Sorry for answering my own question, but this might be the the best/simplest way for me:
SELECT PartNames.Item
FROM dbo.Split('1234-2043,1237-8026,1238-1036,1238-1039,1223-5172', ',') AS PartNames
LEFT JOIN tabSparePart ON tabSparePart.SparePartName = PartNames.Item
WHERE idSparePart IS NULL
My Split-function:
Help with a sql search query using a comma delimitted parameter
Thank you all anyway.
Update: I misunderstood the question. I guess in that case I would select the values into a temp table, then select the values which are not in that table. Not ideal, I know -- the problem is that you need to get your list of part names to SQL Server somehow (either via IN or putting them in a temp table) but the semantics of IN don't do what you want.
Something like this:
CREATE TABLE tabSparePart
(
SparePartName nvarchar(50)
)
insert into tabSparePart values('1234-2043')
CREATE TABLE #tempSparePartName
(
SparePartName nvarchar(50)
)
insert into #tempSparePartName values('1234-2043')
insert into #tempSparePartName values('1238-1036')
insert into #tempSparePartName values('1237-8026')
select * from #tempSparePartName
where SparePartName not in (select SparePartName from tabSparePart)
With output:
SparePartName
1238-1036
1237-8026
Original (wrong) answer:
You can just use "not in":
SELECT * from tabSparePart WHERE SparePartName NOT in(
'1234-2043','1237-8026','1238-1036','1238-1039','1223-5172'
)
You could try something like this....
declare #test as table
(
items varchar(50)
)
insert into #test
values('1234-2043')
insert into #test
values('1234-2043')
insert into #test
values('1237-8026')
-- the rest of the values --
select * from #test
where items not in (
select theItemId from SparePartName
)
for fun check this out...
http://blogs.microsoft.co.il/blogs/itai/archive/2009/02/01/t-sql-split-function.aspx
It shows you how to take delimited data and return it from a table valued function as separate "rows"... which my make the process of creating the table to select from easier than inserting into a #table or doing a giant select union subquery.