Temukan indeks kemunculan sub-string terakhir menggunakan T-SQL

128

Apakah ada cara mudah untuk menemukan indeks kemunculan terakhir string menggunakan SQL? Saya menggunakan SQL Server 2000 sekarang. Saya pada dasarnya membutuhkan fungsionalitas yang System.String.LastIndexOfdisediakan oleh metode .NET . Sedikit googling mengungkapkan ini - Function To Retrieve Last Index - tetapi itu tidak berfungsi jika Anda memasukkan ekspresi kolom "teks". Solusi lain yang ditemukan di tempat lain hanya berfungsi selama teks yang Anda cari panjangnya 1 karakter.

Saya mungkin harus memasak fungsi. Jika saya melakukannya, saya akan mempostingnya di sini sehingga kalian dapat melihatnya dan mungkin memanfaatkannya.

Raj
sumber

Jawaban:

33

Anda terbatas pada daftar kecil fungsi untuk tipe data teks.

Yang bisa saya sarankan adalah mulai dengan PATINDEX, tetapi bekerja mundur dari DATALENGTH-1, DATALENGTH-2, DATALENGTH-3dll sampai Anda mendapatkan hasil atau berakhir pada nol (DATALENGTH-DATALENGTH)

Ini benar-benar sesuatu yang SQL Server 2000tidak bisa ditangani.

Edit untuk jawaban lain : REVERSE tidak ada dalam daftar fungsi yang dapat digunakan dengan data teks dalam SQL Server 2000

gbn
sumber
1
Ya, ini agak aneh. Sepertinya ini seharusnya sederhana, hanya saja tidak!
Raj
... Inilah sebabnya mengapa SQL 2005 memiliki varchar (maks) untuk memungkinkan fungsi normal
gbn
1
Ah! jadi "varchar (max)" adalah hal SQL 2005, yang menjelaskan mengapa hal itu tidak bekerja ketika saya mencoba di SQL 2000.
Raj
DATALENGTH gagal menghasilkan hasil yang benar untuk saya, meskipun PANJANG berfungsi.
Tequila
@ Equila dan yang lainnya: DATALENGTHmengembalikan jumlah byte bukan karakter. Oleh karena itu, DATALENGTHmengembalikan 2 x jumlah karakter dalam string untuk NVARCHARstring. LEN, namun, mengembalikan jumlah karakter, minus spasi spasi apa pun . Saya tidak pernah menggunakan DATALENGTHuntuk perhitungan panjang karakter kecuali spasi spasi penting dan saya tahu pasti bahwa tipe data saya konsisten, apakah itu VARCHARatauNVARCHAR
rbsdca
175

Cara langsung? Tidak, tapi saya telah menggunakan yang sebaliknya. Secara harfiah.

Dalam rutinitas sebelumnya, untuk menemukan kemunculan terakhir dari string yang diberikan, saya menggunakan fungsi REVERSE (), mengikuti CHARINDEX, diikuti lagi oleh REVERSE untuk mengembalikan urutan asli. Misalnya:

SELECT
   mf.name
  ,mf.physical_name
  ,reverse(left(reverse(physical_name), charindex('\', reverse(physical_name)) -1))
 from sys.master_files mf

memperlihatkan bagaimana cara mengekstrak nama file database aktual dari "nama fisik" mereka, tidak peduli seberapa dalam di subfolder. Ini hanya mencari satu karakter (garis miring terbalik), tetapi Anda dapat membangun ini untuk string pencarian yang lebih lama.

Satu-satunya downside adalah, saya tidak tahu seberapa baik ini akan bekerja pada tipe data TEXT. Saya sudah menggunakan SQL 2005 selama beberapa tahun sekarang, dan saya tidak lagi mahir bekerja dengan TEKS - tetapi saya ingat Anda dapat menggunakan KIRI dan KANAN di atasnya?

Philip

Philip Kelley
sumber
1
Maaf - Saya cukup yakin saya tidak pernah kembali ketika saya bekerja dengan 2000, dan saya saat ini tidak memiliki akses ke instalasi SQL 2000.
Philip Kelley
Cemerlang! Tidak pernah terpikir untuk menyerang masalah ini dengan cara ini!
Jared
4
Yang bagus! Saya memodifikasi untuk kebutuhan saya sendiri: email.Substring (0, email.lastIndexOf ('@')) == SELECT LEFT (email, LEN (email) -CHARINDEX ('@', REVERSE (email)))
Fredrik Johansson
1
Hal-hal pintar seperti ini adalah alasan pemrograman begitu menyenangkan!
Chris
mengapa tidak hanya menggunakan kanan alih-alih pada yang asli dan bukan sebaliknya
Phil
108

Cara paling sederhana adalah ....

REVERSE(SUBSTRING(REVERSE([field]),0,CHARINDEX('[expr]',REVERSE([field]))))
Mptje
sumber
3
+1 Karena BUKAN kesalahan kesalahan seperti 'Parameter panjang tidak valid diteruskan ke fungsi KIRI atau SUBSTRING' jika tidak ada kecocokan yang ditemukan
Xilmiki
12
Jika [expr]simbol Anda lebih dari 1, Anda juga harus membalikkannya!
Andrius Naruševičius
60

Jika Anda menggunakan Sqlserver 2005 atau lebih baru, menggunakan REVERSEfungsi berkali-kali merugikan kinerja, kode di bawah ini lebih efisien.

DECLARE @FilePath VARCHAR(50) = 'My\Super\Long\String\With\Long\Words'
DECLARE @FindChar VARCHAR(1) = '\'

-- Shows text before last slash
SELECT LEFT(@FilePath, LEN(@FilePath) - CHARINDEX(@FindChar,REVERSE(@FilePath))) AS Before
-- Shows text after last slash
SELECT RIGHT(@FilePath, CHARINDEX(@FindChar,REVERSE(@FilePath))-1) AS After
-- Shows the position of the last slash
SELECT LEN(@FilePath) - CHARINDEX(@FindChar,REVERSE(@FilePath)) AS LastOccuredAt
Binoj Antony
sumber
1
Ini mungkin tampak jelas di belakang, tetapi jika Anda mencari string alih-alih satu karakter, Anda harus melakukan: LEN (@FilePath) - CHARINDEX (REVERSE (@FindString), REVERSE (@FilePath))
pkExec
14
DECLARE @FilePath VARCHAR(50) = 'My\Super\Long\String\With\Long\Words'
DECLARE @FindChar VARCHAR(1) = '\'

SELECT LEN(@FilePath) - CHARINDEX(@FindChar,REVERSE(@FilePath)) AS LastOccuredAt
Shivendra
sumber
8

Pertanyaan lama tetapi masih valid, jadi inilah yang saya buat berdasarkan info yang diberikan oleh orang lain di sini.

create function fnLastIndexOf(@text varChar(max),@char varchar(1))
returns int
as
begin
return len(@text) - charindex(@char, reverse(@text)) -1
end
john
sumber
7

Ini bekerja sangat baik untuk saya.

REVERSE(SUBSTRING(REVERSE([field]), CHARINDEX(REVERSE('[expr]'), REVERSE([field])) + DATALENGTH('[expr]'), DATALENGTH([field])))
Karthik DV
sumber
6
REVERSE(SUBSTRING(REVERSE(ap_description),CHARINDEX('.',REVERSE(ap_description)),len(ap_description)))  

bekerja lebih baik untukku

tandai brito
sumber
4

Hmm, saya tahu ini adalah utas lama, tetapi tabel penghitungan bisa melakukan ini di SQL2000 (atau database lain):

DECLARE @str CHAR(21),
        @delim CHAR(1)
 SELECT @str = 'Your-delimited-string',
        @delim = '-'

SELECT
    MAX(n) As 'position'
FROM
    dbo._Tally
WHERE
    substring(@str, _Tally.n, 1) = @delim

Tabel penghitungan hanyalah tabel angka yang bertambah.

Semakin substring(@str, _Tally.n, 1) = @delimmendapat posisi masing-masing pembatas, maka Anda hanya mendapatkan posisi maksimum di set itu.

Tabel penghitungan mengagumkan. Jika Anda belum pernah menggunakannya sebelumnya, ada artikel bagus tentang SQL Server Central (Free reg, atau cukup gunakan Bug Me Not ( http://www.bugmenot.com/view/sqlservercentral.com )).

* EDIT: Dihapus n <= LEN(TEXT_FIELD), karena Anda tidak dapat menggunakan LEN () pada jenis TEXT. Selama substring(...) = @delimsisa - sisa meskipun hasilnya masih benar.

Chris
sumber
Bagus. Saya pikir ini secara efektif merupakan solusi yang sama dengan jawaban yang diterima oleh gbn; Anda hanya menggunakan tabel untuk menyimpan bilangan bulat 1, 2, 3 dll. yang dikurangkan dari DATALENGTH dan membaca dari karakter pertama maju daripada karakter terakhir kembali.
Michael Petito
2

Balikkan string dan substring Anda, lalu cari kejadian pertama.

AK
sumber
Poin yang bagus. Saya tidak memiliki 2000 sekarang, dan saya tidak dapat mengingat jika saya bisa melakukannya ketika saya melakukannya.
AK
2

Beberapa jawaban lain mengembalikan string yang sebenarnya sedangkan saya lebih perlu tahu int indeks yang sebenarnya. Dan jawaban yang melakukan itu tampaknya terlalu rumit. Menggunakan beberapa jawaban lain sebagai inspirasi, saya melakukan yang berikut ...

Pertama, saya membuat fungsi:

CREATE FUNCTION [dbo].[LastIndexOf] (@stringToFind varchar(max), @stringToSearch varchar(max))
RETURNS INT
AS
BEGIN
    RETURN (LEN(@stringToSearch) - CHARINDEX(@stringToFind,REVERSE(@stringToSearch))) + 1
END
GO

Kemudian, dalam kueri Anda, Anda bisa melakukan ini:

declare @stringToSearch varchar(max) = 'SomeText: SomeMoreText: SomeLastText'

select dbo.LastIndexOf(':', @stringToSearch)

Di atas harus mengembalikan 23 (indeks terakhir ':')

Semoga ini membuatnya sedikit lebih mudah bagi seseorang!

Matt Goodwin
sumber
2

Saya menyadari ini adalah pertanyaan lama beberapa tahun, tapi ...

Aktif Access 2010, Anda dapat menggunakannya InStrRev()untuk melakukan ini. Semoga ini membantu.

Dan
sumber
2

Jawaban ini menggunakan MS SQL Server 2008 (saya tidak memiliki akses ke MS SQL Server 2000), tetapi cara saya melihatnya menurut OP adalah 3 situasi yang harus dipertimbangkan. Dari apa yang saya coba tidak ada jawaban di sini mencakup semuanya 3:

  1. Kembalikan indeks terakhir dari karakter pencarian dalam string yang diberikan.
  2. Kembalikan indeks terakhir dari sub-string pencarian (lebih dari sekedar karakter tunggal) dalam string yang diberikan.
  3. Jika karakter pencarian atau sub-string tidak dalam string yang diberikan kembali 0

Fungsi yang saya buat membutuhkan 2 parameter:

@String NVARCHAR(MAX) : String yang akan dicari

@FindString NVARCHAR(MAX) : Baik karakter tunggal atau sub-string untuk mendapatkan indeks terakhir dalam @String

Ini mengembalikan INTyang baik indeks positif @FindStringdalam @Stringatau 0artinya@FindString tidak masuk@String

Berikut adalah penjelasan tentang apa fungsi ini:

  1. Menginisialisasi @ReturnValuntuk 0menunjukkan itu@FindString tidak ada@String
  2. Periksa indeks @FindStringmasuk @Stringdengan menggunakanCHARINDEX()
  3. Jika indeks @FindStringdalam @Stringadalah 0,@ReturnVal dibiarkan sebagai0
  4. Jika indeks @FindStringdalam @Stringadalah > 0, @FindStringdalam @Stringmaka menghitung indeks terakhir @FindStringdalam@String dengan menggunakanREVERSE()
  5. Mengembalikan @ReturnValyang merupakan angka positif yang merupakan indeks terakhir @FindStringdalam @Stringatau 0menunjukkan bahwa @FindStringtidak ada@String

Berikut skrip fungsi buat (siap salin dan tempel):

CREATE FUNCTION [dbo].[fn_LastIndexOf] 
(@String NVARCHAR(MAX)
, @FindString NVARCHAR(MAX))
RETURNS INT
AS 
BEGIN
    DECLARE @ReturnVal INT = 0
    IF CHARINDEX(@FindString,@String) > 0
        SET @ReturnVal = (SELECT LEN(@String) - 
        (CHARINDEX(REVERSE(@FindString),REVERSE(@String)) + 
        LEN(@FindString)) + 2)  
    RETURN @ReturnVal
END

Inilah sedikit yang dengan mudah menguji fungsi:

DECLARE @TestString NVARCHAR(MAX) = 'My_sub2_Super_sub_Long_sub1_String_sub_With_sub_Long_sub_Words_sub2_'
, @TestFindString NVARCHAR(MAX) = 'sub'

SELECT dbo.fn_LastIndexOf(@TestString,@TestFindString)

Saya hanya menjalankan ini di MS SQL Server 2008 karena saya tidak memiliki akses ke versi lain tetapi dari apa yang saya lihat ini harus baik untuk 2008+ setidaknya.

Nikmati.

Gharbad Yang Lemah
sumber
1

Aku tahu bahwa itu akan menjadi tidak efisien tetapi apakah Anda dianggap casting textlapangan untuk varcharsehingga Anda dapat menggunakan solusi yang disediakan oleh situs Anda ditemukan? Saya tahu bahwa solusi ini akan membuat masalah karena Anda berpotensi memotong catatan jika panjang di textlapangan melebihi panjang Andavarchar (belum lagi itu tidak akan sangat performan).

Karena data Anda berada di dalam textbidang (dan Anda menggunakan SQL Server 2000) opsi Anda terbatas.

Andrew Hare
sumber
Ya, casting ke "varchar" bukanlah suatu pilihan karena data yang sedang diproses seringkali melebihi maksimum yang dapat disimpan dalam "varchar". Terima kasih atas jawaban Anda!
Raj
1

Jika Anda ingin mendapatkan indeks spasi terakhir dalam serangkaian kata, Anda dapat menggunakan ungkapan ini KANAN (nama, (CHARINDEX ('', REVERSE (nama), 0)) untuk mengembalikan kata terakhir dalam string. Ini sangat membantu jika Anda ingin menguraikan nama belakang dari nama lengkap yang menyertakan inisial untuk nama depan dan / atau tengah.

Justin Stephens
sumber
1

@indexOf = <whatever characters you are searching for in your string>

@LastIndexOf = LEN([MyField]) - CHARINDEX(@indexOf, REVERSE([MyField]))

Belum diuji, mungkin dimatikan oleh satu karena indeks nol, tetapi berfungsi dengan SUBSTRINGbaik saat memisah dari @indexOfkarakter hingga akhir string Anda

SUBSTRING([MyField], 0, @LastIndexOf)

Dauk
sumber
1

Kode ini berfungsi bahkan jika substring berisi lebih dari 1 karakter.

DECLARE @FilePath VARCHAR(100) = 'My_sub_Super_sub_Long_sub_String_sub_With_sub_Long_sub_Words'
DECLARE @FindSubstring VARCHAR(5) = '_sub_'

-- Shows text before last substing
SELECT LEFT(@FilePath, LEN(@FilePath) - CHARINDEX(REVERSE(@FindSubstring), REVERSE(@FilePath)) - LEN(@FindSubstring) + 1) AS Before
-- Shows text after last substing
SELECT RIGHT(@FilePath, CHARINDEX(REVERSE(@FindSubstring), REVERSE(@FilePath)) -1) AS After
-- Shows the position of the last substing
SELECT LEN(@FilePath) - CHARINDEX(REVERSE(@FindSubstring), REVERSE(@FilePath)) AS LastOccuredAt
Dmitry Kovganov
sumber
0

Saya perlu menemukan posisi terakhir backslash di path folder. Ini solusinya.

/*
http://stackoverflow.com/questions/1024978/find-index-of-last-occurrence-of-a-sub-string-using-t-sql/30904809#30904809
DROP FUNCTION dbo.GetLastIndexOf
*/
CREATE FUNCTION dbo.GetLastIndexOf
(
  @expressionToFind         VARCHAR(MAX)
  ,@expressionToSearch      VARCHAR(8000)
  ,@Occurrence              INT =  1        -- Find the nth last 
)
RETURNS INT
AS
BEGIN

    SELECT  @expressionToSearch = REVERSE(@expressionToSearch)

    DECLARE @LastIndexOf        INT = 0
            ,@IndexOfPartial    INT = -1
            ,@OriginalLength    INT = LEN(@expressionToSearch)
            ,@Iteration         INT = 0

    WHILE (1 = 1)   -- Poor man's do-while
    BEGIN
        SELECT @IndexOfPartial  = CHARINDEX(@expressionToFind, @expressionToSearch)

        IF (@IndexOfPartial = 0) 
        BEGIN
            IF (@Iteration = 0) -- Need to compensate for dropping out early
            BEGIN
                SELECT @LastIndexOf = @OriginalLength  + 1
            END
            BREAK;
        END

        IF (@Occurrence > 0)
        BEGIN
            SELECT @expressionToSearch = SUBSTRING(@expressionToSearch, @IndexOfPartial + 1, LEN(@expressionToSearch) - @IndexOfPartial - 1)
        END

        SELECT  @LastIndexOf = @LastIndexOf + @IndexOfPartial
                ,@Occurrence = @Occurrence - 1
                ,@Iteration = @Iteration + 1

        IF (@Occurrence = 0) BREAK;
    END

    SELECT @LastIndexOf = @OriginalLength - @LastIndexOf + 1 -- Invert due to reverse
    RETURN @LastIndexOf 
END
GO

GRANT EXECUTE ON GetLastIndexOf TO public
GO

Inilah beberapa test case yang lolos

SELECT dbo.GetLastIndexOf('f','123456789\123456789\', 1) as indexOf -- expect 0 (no instances)
SELECT dbo.GetLastIndexOf('\','123456789\123456789\', 1) as indexOf -- expect 20
SELECT dbo.GetLastIndexOf('\','123456789\123456789\', 2) as indexOf -- expect 10
SELECT dbo.GetLastIndexOf('\','1234\6789\123456789\', 3) as indexOf -- expect 5
perintah
sumber
0

Untuk mendapatkan bagian sebelum pemisah terakhir (hanya berfungsi NVARCHARkarena DATALENGTHpenggunaan):

DECLARE @Fullstring NVARCHAR(30) = '12.345.67890.ABC';

DECLARE @Delimiter CHAR(1) = '.';

SELECT SUBSTRING(@Fullstring, 1, DATALENGTH(@Fullstring)/2 - CHARINDEX(@Delimiter, REVERSE(@Fullstring)));
Hans M
sumber
0

Jawaban ini memenuhi persyaratan OP. secara khusus itu memungkinkan jarum menjadi lebih dari satu karakter dan itu tidak menghasilkan kesalahan ketika jarum tidak ditemukan di tumpukan jerami. Tampaknya bagi saya bahwa sebagian besar (semua?) Jawaban lain tidak menangani kasus tepi itu. Di luar itu saya menambahkan argumen "Posisi Awal" yang disediakan oleh fungsi CharIndex MS SQL server asli. Saya mencoba persis mencerminkan spesifikasi untuk CharIndex kecuali untuk memproses kanan ke kiri, bukan kiri ke kanan. misalnya saya mengembalikan nol jika salah satu jarum atau tumpukan jerami adalah nol dan saya mengembalikan nol jika jarum tidak ditemukan di tumpukan jerami. Satu hal yang saya tidak bisa selesaikan adalah bahwa dengan fungsi bawaan parameter ketiga adalah opsional. Dengan fungsi yang ditentukan pengguna SQL Server, semua parameter harus disediakan dalam panggilan kecuali fungsi tersebut disebut menggunakan "EXEC" . Meskipun parameter ketiga harus dimasukkan dalam daftar parameter, Anda dapat memberikan kata kunci "default" sebagai pengganti untuk itu tanpa harus memberikan nilai (lihat contoh di bawah). Karena lebih mudah untuk menghapus parameter ketiga dari fungsi ini jika tidak diinginkan daripada menambahkannya jika diperlukan saya telah memasukkannya di sini sebagai titik awal.

create function dbo.lastCharIndex(
 @needle as varchar(max),
 @haystack as varchar(max),
 @offset as bigint=1
) returns bigint as begin
 declare @position as bigint
 if @needle is null or @haystack is null return null
 set @position=charindex(reverse(@needle),reverse(@haystack),@offset)
 if @position=0 return 0
 return (len(@haystack)-(@position+len(@needle)-1))+1
end
go

select dbo.lastCharIndex('xyz','SQL SERVER 2000 USES ANSI SQL',default) -- returns 0
select dbo.lastCharIndex('SQL','SQL SERVER 2000 USES ANSI SQL',default) -- returns 27
select dbo.lastCharIndex('SQL','SQL SERVER 2000 USES ANSI SQL',1) -- returns 27
select dbo.lastCharIndex('SQL','SQL SERVER 2000 USES ANSI SQL',11) -- returns 1
Ted Cohen
sumber
0

Saya menemukan utas ini saat mencari solusi untuk masalah saya yang serupa yang memiliki persyaratan yang sama persis tetapi untuk jenis database yang berbeda yang juga tidak memiliki REVERSE fungsi.

Dalam kasus saya ini adalah untuk database OpenEdge (Progress) , yang memiliki sintaks yang sedikit berbeda. Ini membuat INSTRfungsi tersedia untuk saya yang ditawarkan sebagian besar tipe database Oracle .

Jadi saya datang dengan kode berikut:

SELECT 
  INSTR(foo.filepath, '/',1, LENGTH(foo.filepath) - LENGTH( REPLACE( foo.filepath, '/',  ''))) AS IndexOfLastSlash 
FROM foo

Namun, untuk situasi spesifik saya (menjadi basis data OpenEdge (Progress) ) ini tidak menghasilkan perilaku yang diinginkan karena mengganti karakter dengan char kosong memberikan panjang yang sama dengan string asli. Ini tidak masuk akal bagi saya tetapi saya dapat mem-bypass masalah dengan kode di bawah ini:

SELECT 
  INSTR(foo.filepath, '/',1, LENGTH( REPLACE( foo.filepath, '/',  'XX')) - LENGTH(foo.filepath))  AS IndexOfLastSlash 
FROM foo

Sekarang saya mengerti bahwa kode ini tidak akan menyelesaikan masalah untuk T-SQL karena tidak ada alternatif untuk INSTRfungsi yang menawarkan Occurenceproperti.

Hanya untuk menyeluruh saya akan menambahkan kode yang diperlukan untuk membuat fungsi skalar ini sehingga dapat digunakan dengan cara yang sama seperti yang saya lakukan pada contoh di atas.

  -- Drop the function if it already exists
  IF OBJECT_ID('INSTR', 'FN') IS NOT NULL
    DROP FUNCTION INSTR
  GO

  -- User-defined function to implement Oracle INSTR in SQL Server
  CREATE FUNCTION INSTR (@str VARCHAR(8000), @substr VARCHAR(255), @start INT, @occurrence INT)
  RETURNS INT
  AS
  BEGIN
    DECLARE @found INT = @occurrence,
            @pos INT = @start;

    WHILE 1=1 
    BEGIN
        -- Find the next occurrence
        SET @pos = CHARINDEX(@substr, @str, @pos);

        -- Nothing found
        IF @pos IS NULL OR @pos = 0
            RETURN @pos;

        -- The required occurrence found
        IF @found = 1
            BREAK;

        -- Prepare to find another one occurrence
        SET @found = @found - 1;
        SET @pos = @pos + 1;
    END

    RETURN @pos;
  END
  GO

Untuk menghindari hal yang jelas, ketika REVERSEfungsi tersedia, Anda tidak perlu membuat fungsi skalar ini dan Anda hanya bisa mendapatkan hasil yang diperlukan seperti ini:

SELECT
  LEN(foo.filepath) - CHARINDEX('/', REVERSE(foo.filepath))+1 AS LastIndexOfSlash 
FROM foo
Lautan
sumber