Sort string as number in sql server - sql

I have a column that contains data like this. dashes indicate multi copies of the same invoice and these have to be sorted in ascending order
790711
790109-1
790109-11
790109-2
i have to sort it in increasing order by this number but since this is a varchar field it sorts in alphabetical order like this
790109-1
790109-11
790109-2
790711
in order to fix this i tried replacing the -(dash) with empty and then casting it as a number and then sorting on that
select cast(replace(invoiceid,'-','') as decimal) as invoiceSort...............order by invoiceSort asc
while this is better and sorts like this
invoiceSort
790711 (790711) <-----this is wrong now as it should come later than 790109
790109-1 (7901091)
790109-2 (7901092)
790109-11 (79010911)
Someone suggested to me to split invoice id on the - (dash ) and order by on the 2 split parts
like=====> order by split1 asc,split2 asc (790109,1)
which would work i think but how would i split the column.
The various split functions on the internet are those that return a table while in this case i would be requiring a scalar function.
Are there any other approaches that can be used? The data is shown in grid view and grid view doesn't support sorting on 2 columns by default ( i can implement it though :) ) so if any simpler approaches are there i would be very nice.
EDIT : thanks for all the answers. While every answer is correct i have chosen the answer which allowed me to incorporate these columns in the GridView Sorting with minimum re factoring of the sql queries.

Judicious use of REVERSE, CHARINDEX, and SUBSTRING, can get us what we want. I have used hopefully-explanatory columns names in my code below to illustrate what's going on.
Set up sample data:
DECLARE #Invoice TABLE (
InvoiceNumber nvarchar(10)
);
INSERT #Invoice VALUES
('790711')
,('790709-1')
,('790709-11')
,('790709-21')
,('790709-212')
,('790709-2')
SELECT * FROM #Invoice
Sample data:
InvoiceNumber
-------------
790711
790709-1
790709-11
790709-21
790709-212
790709-2
And here's the code. I have a nagging feeling the final expressions could be simplified.
SELECT
InvoiceNumber
,REVERSE(InvoiceNumber)
AS Reversed
,CHARINDEX('-',REVERSE(InvoiceNumber))
AS HyphenIndexWithinReversed
,SUBSTRING(REVERSE(InvoiceNumber),1+CHARINDEX('-',REVERSE(InvoiceNumber)),LEN(InvoiceNumber))
AS ReversedWithoutAffix
,SUBSTRING(InvoiceNumber,1+LEN(SUBSTRING(REVERSE(InvoiceNumber),1+CHARINDEX('-',REVERSE(InvoiceNumber)),LEN(InvoiceNumber))),LEN(InvoiceNumber))
AS AffixIncludingHyphen
,SUBSTRING(InvoiceNumber,2+LEN(SUBSTRING(REVERSE(InvoiceNumber),1+CHARINDEX('-',REVERSE(InvoiceNumber)),LEN(InvoiceNumber))),LEN(InvoiceNumber))
AS AffixExcludingHyphen
,CAST(
SUBSTRING(InvoiceNumber,2+LEN(SUBSTRING(REVERSE(InvoiceNumber),1+CHARINDEX('-',REVERSE(InvoiceNumber)),LEN(InvoiceNumber))),LEN(InvoiceNumber))
AS int)
AS AffixAsInt
,REVERSE(SUBSTRING(REVERSE(InvoiceNumber),1+CHARINDEX('-',REVERSE(InvoiceNumber)),LEN(InvoiceNumber)))
AS WithoutAffix
FROM #Invoice
ORDER BY
-- WithoutAffix
REVERSE(SUBSTRING(REVERSE(InvoiceNumber),1+CHARINDEX('-',REVERSE(InvoiceNumber)),LEN(InvoiceNumber)))
-- AffixAsInt
,CAST(
SUBSTRING(InvoiceNumber,2+LEN(SUBSTRING(REVERSE(InvoiceNumber),1+CHARINDEX('-',REVERSE(InvoiceNumber)),LEN(InvoiceNumber))),LEN(InvoiceNumber))
AS int)
Output:
InvoiceNumber Reversed HyphenIndexWithinReversed ReversedWithoutAffix AffixIncludingHyphen AffixExcludingHyphen AffixAsInt WithoutAffix
------------- ---------- ------------------------- -------------------- -------------------- -------------------- ----------- ------------
790709-1 1-907097 2 907097 -1 1 1 790709
790709-2 2-907097 2 907097 -2 2 2 790709
790709-11 11-907097 3 907097 -11 11 11 790709
790709-21 12-907097 3 907097 -21 21 21 790709
790709-212 212-907097 4 907097 -212 212 212 790709
790711 117097 0 117097 0 790711
Note that all you actually need is the ORDER BY clause, the rest is just to show my working, which goes like this:
Reverse the string, find the hyphen, get the substring after the hyphen, reverse that part: This is the number without any affix
The length of (the number without any affix) tells us how many characters to drop from the start in order to get the affix including the hyphen. Drop an additional character to get just the numeric part, and convert this to int. Fortunately we get a break from SQL Server in that this conversion gives zero for an empty string.
Finally, having got these two pieces, we simple ORDER BY (the number without any affix) and then by (the numeric value of the affix). This is the final order we seek.
The code would be more concise if SQL Server allowed us to say SUBSTRING(value, start) to get the string starting at that point, but it doesn't, so we have to say SUBSTRING(value, start, LEN(value)) a lot.

Try this one -
Query:
DECLARE #Invoice TABLE (InvoiceNumber VARCHAR(10))
INSERT #Invoice
VALUES
('790711')
, ('790709-1')
, ('790709-21')
, ('790709-11')
, ('790709-211')
, ('790709-2')
;WITH cte AS
(
SELECT
InvoiceNumber
, lenght = LEN(InvoiceNumber)
, delimeter = CHARINDEX('-', InvoiceNumber)
FROM #Invoice
)
SELECT InvoiceNumber
FROM cte
CROSS JOIN (
SELECT repl = MAX(lenght - delimeter)
FROM cte
WHERE delimeter != 0
) mx
ORDER BY
SUBSTRING(InvoiceNumber, 1, ISNULL(NULLIF(delimeter - 1, -1), lenght))
, RIGHT(REPLICATE('0', repl) + SUBSTRING(InvoiceNumber, delimeter + 1, lenght), repl)
Output:
InvoiceNumber
-------------
790709-1
790709-2
790709-11
790709-21
790709-211
790711

Try this
SELECT invoiceid FROM Invoice
ORDER BY
CASE WHEN PatIndex('%[-]%',invoiceid) > 0
THEN LEFT(invoiceid,PatIndex('%[-]%',invoiceid)-1)
ELSE invoiceid END * 1
,CASE WHEN PatIndex('%[-]%',REVERSE(invoiceid)) > 0
THEN RIGHT(invoiceid,PatIndex('%[-]%',REVERSE(invoiceid))-1)
ELSE NULL END * 1
SQLFiddle Demo
Above query uses two case statements
Sorts first part of Invoiceid 790109-1 (eg: 790709)
Sorts second part of Invoiceid after splitting with '-' 790109-1 (eg: 1)
For detailed understanding check the below SQLfiddle
SQLFiddle Detailed Demo
OR use 'CHARINDEX'
SELECT invoiceid FROM Invoice
ORDER BY
CASE WHEN CHARINDEX('-', invoiceid) > 0
THEN LEFT(invoiceid, CHARINDEX('-', invoiceid)-1)
ELSE invoiceid END * 1
,CASE WHEN CHARINDEX('-', REVERSE(invoiceid)) > 0
THEN RIGHT(invoiceid, CHARINDEX('-', REVERSE(invoiceid))-1)
ELSE NULL END * 1

Order by each part separately is the simplest and reliable way to go, why look for other approaches? Take a look at this simple query.
select *
from Invoice
order by Convert(int, SUBSTRING(invoiceid, 0, CHARINDEX('-',invoiceid+'-'))) asc,
Convert(int, SUBSTRING(invoiceid, CHARINDEX('-',invoiceid)+1, LEN(invoiceid)-CHARINDEX('-',invoiceid))) asc

Plenty of good answers here, but I think this one might be the most compact order by clause that is effective:
SELECT *
FROM Invoice
ORDER BY LEFT(InvoiceId,CHARINDEX('-',InvoiceId+'-'))
,CAST(RIGHT(InvoiceId,CHARINDEX('-',REVERSE(InvoiceId)+'-'))AS INT)DESC
Demo: - SQL Fiddle
Note, I added the '790709' version to my test, since some of the methods listed here aren't treating the no-suffix version as lesser than the with-suffix versions.
If your invoiceID varies in length, before the '-' that is, then you'd need:
SELECT *
FROM Invoice
ORDER BY CAST(LEFT(list,CHARINDEX('-',list+'-')-1)AS INT)
,CAST(RIGHT(InvoiceId,CHARINDEX('-',REVERSE(InvoiceId)+'-'))AS INT)DESC
Demo with varying lengths before the dash: SQL Fiddle

My version:
declare #Len int
select #Len = (select max (len (invoiceid) - charindex ( '-', invoiceid))-1 from MyTable)
select
invoiceid ,
cast (SUBSTRING (invoiceid ,1,charindex ( '-', invoiceid )-1) as int) * POWER (10,#Len) +
cast (right(invoiceid, len (invoiceid) - charindex ( '-', invoiceid) ) as int )
from MyTable
You can implement this as a new column to your table:
ALTER TABLE MyTable ADD COLUMN invoice_numeric_id int null
GO
declare #Len int
select #Len = (select max (len (invoiceid) - charindex ( '-', invoiceid))-1 from MyTable)
UPDATE TABLE MyTable
SET invoice_numeric_id = cast (SUBSTRING (invoiceid ,1,charindex ( '-', invoiceid )-1) as int) * POWER (10,#Len) +
cast (right(invoiceid, len (invoiceid) - charindex ( '-', invoiceid) ) as int )

One way is to split InvoiceId into its parts, and then sort on the parts. Here I use a derived table, but it could be done with a CTE or a temporary table as well.
select InvoiceId, InvoiceId1, InvoiceId2
from
(
select
InvoiceId,
substring(InvoiceId, 0, charindex('-', InvoiceId, 0)) as InvoiceId1,
substring(InvoiceId, charindex('-', InvoiceId, 0)+1, len(InvoiceId)) as InvoiceId2
FROM Invoice
) tmp
order by
cast((case when len(InvoiceId1) > 0 then InvoiceId1 else InvoiceId2 end) as int),
cast((case when len(InvoiceId1) > 0 then InvoiceId2 else '0' end) as int)
In the above, InvoiceId1 and InvoiceId2 are the component parts of InvoiceId. The outer select includes the parts, but only for demonstration purposes - you do not need to do this in your select.
The derived table (the inner select) grabs the InvoiceId as well as the component parts. The way it works is this:
When there is a dash in InvoiceId, InvoiceId1 will contain the first part of the number and InvoiceId2 will contain the second.
When there is not a dash, InvoiceId1 will be empty and InvoiceId2 will contain the entire number.
The second case above (no dash) is not optimal because ideally InvoiceId1 would contain the number and InvoiceId2 would be empty. To make the inner select work optimally would decrease the readability of the select. I chose the non-optimal, more readable, approach since it is good enough to allow for sorting.
This is why the ORDER BY clause tests for the length - it needs to handle the two cases above.
Demo at SQL Fiddle

Break the sort into two sections:
SQL Fiddle
MS SQL Server 2008 Schema Setup:
CREATE TABLE TestData
(
data varchar(20)
)
INSERT TestData
SELECT '790711' as data
UNION
SELECT '790109-1'
UNION
SELECT '790109-11'
UNION
SELECT '790109-2'
Query 1:
SELECT *
FROM TestData
ORDER BY
FLOOR(CAST(REPLACE(data, '-', '.') AS FLOAT)),
CASE WHEN CHARINDEX('-', data) > 0
THEN CAST(RIGHT(data, len(data) - CHARINDEX('-', data)) AS INT)
ELSE 0
END
Results:
| DATA |
-------------
| 790109-1 |
| 790109-2 |
| 790109-11 |
| 790711 |

Try:
select invoiceid ... order by Convert(decimal(18, 2), REPLACE(invoiceid, '-', '.'))

Related

finding distinct customer_id when the part numbers are not one per row but has delimiter "/"

Have a data set similiar to this.
Customer_id PART_N PART_C TXN_ID
B123 268888 7902/7900 159
B123 12839 82900/8900 1278
B869 12839 8203/890025/7902 17890
B290 268888 62820/12839 179018
not sure how to combine PART_N and PART_C and find count(distinct customer_id) for each part the same part could be in PART_N or PART_C like part number 12839
I am interested in getting as following table using teradata
Part COUNT(Distinct Customer id)
268888 2
12839 3
7902 2
7900 1
82900 1
8900 1
8203 1
890025 1
62820 1
if it was just PART_N then it would be straight forward as just one part number is present per row. Unsure how I combine every part number and find how many distinct customer id each one has. If it helps I have all the list of distinct Part numbers in one table say table2.
I cannot not try this code, so see it as pseudocode and sketch of an idea.
SELECT numbers, COUNT(numbers)
FROM
(SELECT
REGEXP_SPLIT_TO_TABLE( -- B
CONCAT(PART_N, '/', PART_C), -- A
'/'
) as numbers
FROM table) s
GROUP BY numbers -- C
A: Concatenation of both columns into one string divided by the delimiter '/'
B: Split string by delimiter
C: Group string parts and count them
http://www.teradatawiki.net/2014/05/regular-expression-functions.html
This is pretty ugly.
First let's split those delimited strings up, using strtok_split_to_table.
create volatile table vt_split as (
select
txn_id,
token as part
from table
(strtok_split_to_table(your_table.txn_id,your_table.part_c,'/')
returns (txn_id integer,tokennum integer,token varchar(10))) t
)
with data
primary index (txn_id)
on commit preserve rows;
That will give you all those split apart, with the appropriate txn_id.
Then we can union that with the part_n values.
create volatile table vt_merged as (
select * from vt_split
UNION ALL
select
txn_id,
cast(part_n as varchar(10)) as part
from
vt_foo)
with data
primary index (txn_id)
on commit preserve rows;
Finally, we can join that back to your original table to get the counts of customer by part.
select
vt_merged.part,
count (distinct yourtable.customer_id)
from
vt_merged
inner join yourtable
on vt_merged.txn_id = yourtable.txn_id
group by 1
This could probably done a little bit cleaner, but it should get you what you're looking for.
This is #S-Man's pseudocode as working query:
WITH cte AS
(
SELECT Customer_id,
Trim(PART_N) ||'/' || PART_C AS all_parts
FROM tab
)
SELECT
part, -- if part should be numeric: Cast(part AS INT)
Count(DISTINCT Customer_id)
FROM TABLE (StrTok_Split_To_Table(cte.Customer_id, cte.all_parts, '/')
RETURNS (Customer_id VARCHAR(10), tokennum INTEGER, part VARCHAR(30))) AS t
GROUP BY 1

Duplicate checking with like operator

I want to check duplicate values in my table with like operator:
select F_Barcode, COUNT(*) as cnt
from T_Assets
group by F_Barcode
having COUNT(*) > 1
This is working fine, but some of my barcode duplicated is like this
4456
00004456
and
45552
00045552
Actually this is the same barcode, but duplicated.
I need to see all of my barcodes duplicated like this? How I can do that?
F_Barcode datatype nvarchar(50).
Values like 0000frdz can't be converted to data type int.
You can cast the type of barcode to integer, then do the count ops.
Just like this:
select F_Barcode,COUNT(*) as cnt From T_Assets
group by CAST(F_Barcode AS UNSIGNED) having COUNT(*) > 1
Check This.
Using Substring:
select substring(F_Barcode, patindex('%[^0]%',F_Barcode), 100),
COUNT(*) as cnt
From T_Assets
group by substring(F_Barcode, patindex('%[^0]%',F_Barcode), 100)
having COUNT(*) > 1
--- Replace 100 with datatype size
Using Cast :
select CAST(F_Barcode as int),COUNT(*) as cnt
From F_Barcode
group by CAST(F_Barcode as int)
having COUNT(*) > 1
fix a length for string.
add O's in the beginning to match desired length.
then collect count group wise.
select RIGHT('000000000'+F_Barcode,9) as F_Barcode, COUNT() as cnt
from T_Assets
group by RIGHT('000000000'+F_Barcode,9)
having COUNT() > 1
Here I have considered string length to 9. You can adjust as per your requirement.
Please take a note that you have to add equal number of 0's to your string length at beginning of you column.
A possible solution for SQL Server 2012 or above is to use TRY_CONVERT to ensure that cast will never fail. Barcodes should contain numbers only, but since you are storing them in a NVARCHAR field
Setup
create table BarcodeData
(
F_Barcode NVARCHAR(50)
)
insert into BarcodeData VALUES ('4456'), ('00004456'), ('45552'), ('00045552'), ('a45552'), ('1234'), ('0'), ('00001'), ('a45552')
GO
Query
;WITH CleanedBarcode AS (
-- CAST is required because INT type is inferred and will generate failure when a non number is met
SELECT ISNULL(CAST(TRY_CONVERT(INT, F_Barcode) AS NVARCHAR(50)), F_Barcode) AS CleanedCode
FROM BarcodeData
)
SELECT *
FROM CleanedBarcode CB
GROUP BY CB.CleanedCode
HAVING COUNT(1) > 1

how to have column output zero when it equals null?

I have a query where i get the average of some ratings
which works correct.
WITH row_avg_table(avg_rating,employee, approveddate) AS
(SELECT
(SELECT AVG(rating)
FROM (
VALUES (CAST(c.rating1 AS float)), (CAST(c.rating2 AS float)), (CAST(c.rating3 AS float)),
(CAST(c.rating4 AS float)), (CAST(c.rating5 AS float)) ) AS v (rating)
WHERE v.rating > 0) avg_rating,
employee,approveddate
FROM CSEReduxResponses c)
SELECT employee,
avg(avg_rating) as average_rating
FROM row_avg_table
where month(approveddate)=2014
AND year(approveddate)=6
GROUP BY employee;
The problem im having is when a rating 1-5 would all be 0.
Right now it gives me 'null' i would like it to show 0 for this special occasion.
for example i have the data below
create table CSEReduxResponses (rating1 int, rating2 int, rating3 int, rating4 int, rating5 int,
approveddate datetime,employee int)
insert into CSEReduxResponses (rating1 , rating2 ,rating3 , rating4 , rating5 ,
approveddate, employee )
values
(5,4,5,1,4,'2014-06-18',1),
(5,4,5,1,0,'2014-06-18',1),
(0,0,0,0,0,'2014-06-19',3);
So for employee=3 average_rating =0
What if you use ISNULL function like below
SELECT ISNULL(AVG(rating),some_default_value)
(OR) CASE condition like below
SELECT AVG(case when rating = 0 then 1 else rating end)
The problem you have is the WHERE v.rating > 0 clause. If the ratings are 0, then this will return no values - and the average of nothing cannot be calculated, and thus returns null. Simply remove this clause, or if you want to filter negative values, change it to WHERE v.rating >= 0.
If you use 0 to store values that you don't want to impact the rating (like it appears that you might be doing from the second of the examples you provide) this won't work - you'll have to instead go through the output and replace null values with 0. You might copy the results from your first query into a temp table/table variable and do replace such as
select employee, rating = isnull(t.rating, 0)
from #temptable t
Or something of the like.
Use
isnull(Rating1, 0)
or
coalesce(Rating1, 0)
If Rating1 is null then it will be replaced with the default value 0. Similartly you can check for other fields. The COALESCE and ISNULL T-SQL functions are used to return the first nonnull expression among the input arguments but there are few differences between those which you can find here
More details on coalesce
There is no advantage in using a CTE for this. You are unpivoting the non-normalized table using cross apply and values. To arrive at the equivalent of the unpivot command you use WHERE ... IS NOT NULL but presumably you are ensuring all 5 values are present to avoid bias in the calculations, so I would suggest this:
SELECT
employee
, AVG(isnull(rating,0)) AS avg_rating
FROM CSEReduxResponses c
CROSS APPLY (
VALUES (CAST(c.rating1 AS float))
, (CAST(c.rating2 AS float))
, (CAST(c.rating3 AS float))
, (CAST(c.rating4 AS float))
, (CAST(c.rating5 AS float))
) AS ca1 (rating)
WHERE approveddate >= '20140601'
AND approveddate < '20140701'
GROUP BY
employee
;
results from your sample data:
| EMPLOYEE | AVG_RATING |
|----------|------------|
| 1 | 3.4 |
| 3 | 0 |
see: http://sqlfiddle.com/#!3/f91d9/2
Also: I would encourage you to NOT to use functions on the data to facilitate a where condition. Avoiding functions on data allows use of indexes. Here all you need is a simple date range (I have used YYYYMMDD as it is the safest format for sql server).

Subquery returns more than one row even with MAX function

I have searched and couldn't find anything that helped me in this case. The DB is MS SQL Server 2008 R2.
I have a complex query that I'm actually using CTE's for, where one of the subqueries should return one row for each column. However, I can't figure out how to do that.
Here's the code.
select MemberFK as LMFK, ContractNr, Contractbegin as LC, ContractEnd as LCE from Contracts as V
WHERE
MainContract=1
AND Description not like '%Mitarbeiter%'
AND Description not like '%Kind%'
AND Description not like '%Teen%'
AND Description not like '%Kid%'
AND Contractbegin =
(select TOP 1 MAX(V1.Contractbegin) from Contracts as V1 WHERE
(V1.Description like '%Gold%'
OR V1.Description like '%Silber%'
OR V1.Description like '%Bronze%'
OR V1.Description like '%Executive%' )
AND V1.ContractEnd>V1.Contractbegin --to flush out some erroneous rows
AND V1.MemberFK =V.MemberFK)
Sample problematic rows:
LMFK ContractNr LC LCE
649 644 2002-10-01 00:00:00 2008-04-30 00:00:00
755 646 2002-11-01 00:00:00 2002-11-01 00:00:00
755 647 2002-11-01 00:00:00 2008-07-31 00:00:00
754 648 2002-11-01 00:00:00 2008-07-31 00:00:00
What I would like to do to get only one row per LMFK is to get the max ContractNr that satisfies the other conditions. As you can see, ContractNr 646 is not valid whereas 647 is. Looks like the V1.ContractEnd>V1.Contractbegin condition isn't working well.
Any help is appreciated!
You should use ContractNr in the where cluse. Your query returns all records matched max Contractbegin
select MemberFK as LMFK, ContractNr, Contractbegin as LC, ContractEnd as LCE from Contracts as V
WHERE
MainContract=1
AND Description not like '%Mitarbeiter%'
AND Description not like '%Kind%'
AND Description not like '%Teen%'
AND Description not like '%Kid%'
AND ContractNr =
(select TOP 1 MAX(V1.ContractNr) from Contracts as V1 WHERE
(V1.Description like '%Gold%'
OR V1.Description like '%Silber%'
OR V1.Description like '%Bronze%'
OR V1.Description like '%Executive%' )
AND V1.ContractEnd>V1.Contractbegin --to flush out some erroneous rows
AND V1.MemberFK =V.MemberFK)
I always like creating a toy table and/or database to show off my ideas. Below is one such snippet.
Here are some comments about your solution versus mine.
1 - When you compare a column to a pattern using a wild card at the beginning, the query optimizer is unable to use any indexes. Therefore, this results in a full table scan.
2 - I always like testing for nulls. The coalesce is a great way to have a null string default to an empty string.
3 - If you use a persisted computed column for your where logic, it is stored in the database when the record is inserted or updated.
4 - A persisted computed column can have a index, therefore, eliminates the table scan with larger tables.
I had to use a query index hint since for small tables, a table scan is faster.
Also, you might want to add member_fk and/or begin_date. IE - more work / testing for a real life example.
5 - Last but not least, use window partitioning and the row_number() function to find the the latest row.
I bundled this into a CTE since you can not reference the calculation in the SELECT part of the statement in the WHERE clause.
There are some good concepts here:
wild card pattern searching equals full table scan
alwaystest/account for nulls
persisted computed columns as an index
for speed
using grouping functions for pick top result
If you have any questions, just holler.
Sincerely
John
-- Drop old table
if object_id('tempdb.dbo.contracts') > 0
drop table tempdb.dbo.contracts;
go
-- Create new table
create table tempdb.dbo.contracts
(
id_num int identity(1,1),
member_fk int,
main_flag bit,
begin_date smalldatetime,
end_date smalldatetime,
description_txt varchar(512),
do_not_use_flag as
(
-- must have these words
(
case
when lower(coalesce(description_txt, '')) like '%gold%' then 0
when lower(coalesce(description_txt, '')) like '%silver%' then 0
when lower(coalesce(description_txt, '')) like '%bronze%' then 0
when lower(coalesce(description_txt, '')) like '%executive%' then 0
else 1
end
)
+
-- must not have these words
(
case
when lower(coalesce(description_txt, '')) like '%mitarbeiter%' then 1
when lower(coalesce(description_txt, '')) like '%kind%' then 1
when lower(coalesce(description_txt, '')) like '%teen%' then 1
when lower(coalesce(description_txt, '')) like '%kid%' then 1
else 0
end
)
+
-- must have begin_date <= end_date
(
case
when begin_date is null then 1
when end_date is null then 0
when begin_date <= end_date then 0
else 1
end
)
+
(
-- toss out non-main records
case
when main_flag = 1 then 0
else 1
end
)
) persisted
);
go
-- add index on id include flag
create nonclustered index ix_contracts
on tempdb.dbo.contracts (do_not_use_flag);
go
-- add data to table
insert into tempdb.dbo.contracts (member_fk, main_flag, begin_date, end_date, description_txt)
values
-- shows up
(1, 1, getdate() - 5, getdate(), 'Silver - good contract for DBA'),
-- main contract <> 1
(1, 0, getdate() - 5, getdate(), 'Gold - good contract for DBA'),
-- no flag = true
(1, 1, getdate() - 5, getdate(), 'Bronze - good contract for Teen'),
-- end < begin
(1, 1, getdate(), getdate()-5, 'Bronze - good contract for DBA'),
(2, 1, getdate() - 5, getdate(), 'Executive - good contract for DBA');
go
-- wait 5 seconds
WAITFOR DELAY '00:00:02';
go
insert into tempdb.dbo.contracts (member_fk, main_flag, begin_date, end_date, description_txt)
values
(2, 1, getdate() - 4, getdate(), 'Executive - good contract for DBA');
go
-- show the raw data
select * from tempdb.dbo.contracts as c
go
-- show the data
;
with cte_contract_by_recent_begin_dte
as
(
select
ROW_NUMBER() OVER (PARTITION BY member_fk ORDER BY begin_date desc) as top_id,
*
from
tempdb.dbo.contracts as c with(index(ix_contracts))
where
c.do_not_use_flag = 0
)
select * from cte_contract_by_recent_begin_dte as cte where cte.top_id = 1

ORDER BY in SQL

I have a column of a type string that contains values in rows like:
1-1
1-5
1-14
1-7
1-3
Now if I use the ORDER BY on that column I get the order as:
1-1
1-14
1-3
1-5
1-7
What would be the proper way to order it as 1-1, 1-3, 1-5,1-7,1-14
Thank you for your time
Assuming your first character may also vary:
order by convert(substr(my_field, 1, locate(my_field, '-') - 1) as int),
convert(substr(my_field, locate(my_field, '-') + 1) as int)
You can rename the "1-1" into "1-01"
The proper way would be to store them as integers in different columns.
Try this:
SELECT * FROM
(
SELECT '1-1' Id
UNION
SELECT '1-5' Id
UNION
SELECT '1-14' Id
UNION
SELECT '1-7' Id
UNION
SELECT '1-3' Id
) a
ORDER BY CAST(REPLACE(Id, '-', '') AS UNSIGNED)
Is there a possibility to split the column in 2 separate columns and concat the two together after sorting them individually?
The problem now is that your column is sorting as strings not as integers.
If there's absolutely impossible to make any changes to the structure, I'd say that Carl Manaster's method is best. That would work slow on large data sets though.
You can also try to add a "sort" column (and index it), then each time a new code is added you can calculate it's value e.g.:
1-5 becomes 1000 + 5 = 1005
1-14 becomes 1000 + 14 = 1014
and save it to that sort column. That will work much faster.
You can also write a simple trigger so that this sort value is calculated automatically.
You can use something like (if the first letter doesn-t metter)
order by CAST(SUBSTRING(field, CHARINDEX(field,'-',0)+1, LEN(field)+1) as int)
Not very pretty though..
SELECT *
FROM (
SELECT '1-1' Id
UNION ALL
SELECT '1-5' Id
UNION ALL
SELECT '1-14' Id
UNION ALL
SELECT '1-7' Id
UNION ALL
SELECT '1-3' Id
UNION ALL
SELECT '10-4' Id
) a
ORDER BY
CAST(SUBSTRING_INDEX(SUBSTRING_INDEX(id, '-', -2), '-', 1) AS UNSIGNED),
CAST(SUBSTRING_INDEX(SUBSTRING_INDEX(id, '-', -1), '-', 1) AS UNSIGNED)