T-SQL kullanarak bir alt dizenin son oluşumunun dizinini bulun


128

SQL kullanarak bir dizenin son oluşumunun dizinini bulmanın basit bir yolu var mı? Şu anda SQL Server 2000 kullanıyorum. Temelde .NET System.String.LastIndexOfyönteminin sağladığı işlevselliğe ihtiyacım var . Biraz googling bunu ortaya çıkardı - Son Dizini Alma Fonksiyonu - ancak bir "metin" sütun ifadesi bu çalışmaz. Başka yerlerde bulunan diğer çözümler, yalnızca aradığınız metin 1 karakter uzunluğunda olduğu sürece işe yarar.

Muhtemelen bir fonksiyon pişirmem gerekecek. Bunu yaparsam, buraya göndereceğim, böylece sizler bakabilir ve belki yararlanabilirsiniz.

Yanıtlar:


33

Metin veri türü için küçük işlevler listesiyle sınırlandırılırsınız .

Tek önerebileceğim, başlamak PATINDEX, ancak DATALENGTH-1, DATALENGTH-2, DATALENGTH-3bir sonuç elde edene veya sıfıra ulaşana kadar geriye doğru çalışın (DATALENGTH-DATALENGTH)

Bu gerçekten SQL Server 2000başa çıkamayacak bir şey .

Diğer yanıtları düzenleyin : REVERSE, SQL Server 2000'de metin verileriyle kullanılabilen işlevler listesinde değil


1
Evet, oldukça garip. Bu basit olmalı gibi görünüyor, ancak öyle değil!
Raj

... bu nedenle SQL 2005'te normal işlevlere izin vermek için varchar (max) var
gbn

1
Ah! bu yüzden "varchar (max)" bir SQL 2005 şeydir, bu da SQL 2000'de denediğimde neden çalışmadığını açıklıyor.
Raj

DATALENGTH UZUNLUK işe yarasa da benim için doğru sonucu üretemiyor.
Tequila

@Tequila ve diğerleri: DATALENGTHkarakter değil bayt sayısını döndürür. Bu nedenle, dizeler DATALENGTHiçin bir dizedeki karakter sayısını 2 x döndürür NVARCHAR. LENancak, sondaki beyaz boşlukları çıkararak karakter sayısını döndürür . Ben asla kullanmak DATALENGTHsonlarındaki boşluk çok büyük değilse, karakter uzunluğu hesaplaması için ve benim veri türleri onlar olsun, tutarlı olduğundan emin olduğum VARCHARveyaNVARCHAR
rbsdca

175

Direkt yol? Hayır, ama tersini kullandım. Kelimenin tam anlamıyla.

Önceki rutinlerde, belirli bir dizenin son oluşumunu bulmak için, REVERSE () işlevini kullandım, ardından CHARINDEX ve ardından orijinal sırayı geri yüklemek için REVERSE izledim. Örneğin:

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

alt klasörlerde ne kadar derinlemesine iç içe geçmiş olursa olsun, gerçek veritabanı dosya adlarının "fiziksel adlarından" nasıl çıkarılacağını gösterir. Bu yalnızca bir karakter (ters eğik çizgi) arar, ancak daha uzun arama dizeleri için bunun üzerine inşa edebilirsiniz.

Tek dezavantajı, bunun TEXT veri türlerinde ne kadar iyi çalışacağını bilmiyorum. Birkaç yıldır SQL 2005 kullanıyorum ve artık TEXT ile çalışma konusunda bilgim yok - ancak üzerinde SOL ve SAĞ kullanabileceğinizi hatırlıyorum.

Philip


1
Üzgünüm - 2000 ile çalışırken geri dönmediğimden oldukça eminim ve şu anda herhangi bir SQL 2000 kurulumuna erişimim yok.
Philip Kelley

Parlak! Bu soruna bu şekilde saldırmayı asla düşünmezdim!
Jared

4
Güzel bir! Kendi ihtiyaçlarım için değişiklik yaptım: email.Substring (0, email.lastIndexOf ('@')) == SELECT LEFT (email, LEN (email) -CHARINDEX ('@', REVERSE (email)))
Fredrik Johansson

1
Bunun gibi zekice şeyler, programlamanın bu kadar eğlenceli olmasının nedenidir!
Chris

orijinal üzerinde fazladan bir ters yerine neden sol yerine sağı kullanmayalım
Phil

108

En basit yol ...

REVERSE(SUBSTRING(REVERSE([field]),0,CHARINDEX('[expr]',REVERSE([field]))))

3
+1 Çünkü hiçbir eşleşme bulunmazsa 'SOL veya ALTSTRING işlevine geçersiz uzunluk parametresi geçirildi' gibi hata
YANMAZ

12
Eğer [expr]1 sembolden daha uzunsa, onu da tersine çevirmelisin!
Andrius Naruševičius

60

Sqlserver 2005 veya üstünü kullanıyorsanız, REVERSEişlevi birçok kez kullanmak performansa zarar verir, aşağıdaki kod daha etkilidir.

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

1
Geriye dönüp bakıldığında açık görünebilir, ancak tek bir karakter yerine bir dize arıyorsanız, yapmanız gereken: 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

8

Eski ama yine de geçerli bir soru, bu yüzden burada başkaları tarafından sağlanan bilgilere dayanarak oluşturduğum şey burada.

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

7

Bu benim için çok iyi çalıştı.

REVERSE(SUBSTRING(REVERSE([field]), CHARINDEX(REVERSE('[expr]'), REVERSE([field])) + DATALENGTH('[expr]'), DATALENGTH([field])))

6
REVERSE(SUBSTRING(REVERSE(ap_description),CHARINDEX('.',REVERSE(ap_description)),len(ap_description)))  

benim için daha iyi çalıştı


4

Hmm, bunun eski bir iş parçacığı olduğunu biliyorum, ancak bir tally tablosu bunu SQL2000'de (veya başka bir veritabanında) yapabilir:

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

Bir çetele tablosu yalnızca artan sayılardan oluşan bir tablodur.

substring(@str, _Tally.n, 1) = @delimO zaman sadece o sette en fazla pozisyon almak, her sınırlayıcı pozisyonunu alır.

Tally tabloları harika. Bunları daha önce kullanmadıysanız, SQL Server Central hakkında iyi bir makale var (Ücretsiz kayıt veya sadece Bug Me Not'u kullanın ( http://www.bugmenot.com/view/sqlservercentral.com )).

* DÜZENLEME: n <= LEN(TEXT_FIELD)METİN türünde LEN () kullanamayacağınız için kaldırılmıştır . substring(...) = @delimKalıntılar olduğu sürece sonuç hala doğrudur.


Güzel. Sanırım bu, gbn tarafından kabul edilen cevapla aynı çözüm olsa da; DATALENGTH'den çıkarılan 1, 2, 3 vb. tam sayıları saklamak için bir tablo kullanıyorsunuz ve geriye son karakter yerine ilk karakterden ileriye doğru okuyorsunuz.
Michael Petito

2

Hem dizenizi hem de alt dizenizi ters çevirin, ardından ilk geçtiği yeri arayın.


İyi bir nokta. Şu anda 2000'im yok ve yaptığım zaman yapıp yapamayacağımı hatırlayamıyorum.
AK

2

Diğer cevaplardan bazıları gerçek bir dizge döndürürken, gerçek int dizini bilmeye daha çok ihtiyacım vardı. Ve bunu yapan cevaplar işleri aşırı karmaşık hale getiriyor. Diğer cevaplardan bazılarını ilham kaynağı olarak kullanarak şunları yaptım ...

İlk önce bir fonksiyon oluşturdum:

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

Ardından, sorgunuzda şunları yapabilirsiniz:

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

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

Yukarıdakiler 23 döndürmelidir (':' son dizini)

Umarım bu, biri için işi biraz kolaylaştırmıştır!


2

Bunun birkaç yıllık bir soru olduğunun farkındayım, ama ...

Açık Access 2010, bunu InStrRev()yapmak için kullanabilirsiniz . Bu yardımcı olur umarım.


2

Bu cevap MS SQL Server 2008 kullanıyor (MS SQL Server 2000'e erişimim yok), ancak OP'ye göre onu görme şeklim dikkate alınması gereken 3 durum. Hiçbir yanıt veremediğime göre, burada bunların 3'ü de var:

  1. Belirli bir dizedeki bir arama karakterinin son dizinini döndürür.
  2. Belirli bir dizedeki bir arama alt dizesinin (tek bir karakterden fazla) son dizinini döndürür.
  3. Arama karakteri veya alt dize verilen dizede değilse dönüş 0

Bulduğum işlev 2 parametre alıyor:

@String NVARCHAR(MAX) : Aranacak dize

@FindString NVARCHAR(MAX) : İn son dizinini almak için tek bir karakter veya bir alt dize @String

Bir döner INTya pozitif indeks olduğunu @FindStringbölgesi @Stringveya 0anlam @FindStringdeğil@String

Fonksiyonun ne yaptığına dair bir açıklama:

  1. Başlatır @ReturnValiçin 0olduğunu belirten @FindStringdeğil@String
  2. Çekler indeksi @FindStringin @StringkullanarakCHARINDEX()
  3. Endeksi ise @FindStringin @Stringolduğu 0, @ReturnValolarak bırakılır0
  4. Endeksi ise @FindStringin @Stringolduğu > 0, @FindStringolduğu @Stringo son indeksini hesaplar böylece @FindStringde @StringkullanarakREVERSE()
  5. İade @ReturnValson endeksidir olumlu numarası şeklindeki @FindStringbölgesi @Stringveya 0belirten @FindStringdeğildir@String

İşte oluşturma işlevi komut dosyası (kopyalayıp yapıştırmaya hazır):

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

İşte işlevi uygun şekilde test eden küçük bir parça:

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)

Bunu yalnızca MS SQL Server 2008'de çalıştırdım çünkü başka bir sürüme erişimim yok, ancak baktığım şey en azından 2008+ için iyi olmalı.

Zevk almak.


1

Verimsiz olacağını biliyorum ancak bulduğunuz web sitesinin sağladığı çözümü kullanabilmek için textalanı yayınlamayı varchardüşündünüz mü? Bu çözümün sorun yaratacağını biliyorum, çünkü textalandaki uzunluk sizin alanınızın uzunluğunu aşarsa kaydı potansiyel olarak kısaltabilirsiniz .varchar (bunun çok performanslı olmayacağından bahsetmiyorum bile).

Verileriniz bir textalanın içinde olduğundan (ve SQL Server 2000 kullandığınızdan) seçenekleriniz sınırlıdır.


Evet, sık sık işlenen veriler bir "varchar" içinde tutulabilecek maksimum değeri aştığından "varchar" a çevrim bir seçenek değildir. Yine de cevabınız için teşekkürler!
Raj

1

Bir sözcük dizisindeki son boşluğun dizinini almak istiyorsanız, dizedeki son sözcüğü döndürmek için bu RIGHT (ad, (CHARINDEX ('', REVERSE (ad), 0)) ifadesini kullanabilirsiniz. ad ve / veya ikinci adın baş harflerini içeren bir tam adın soyadını ayrıştırmak istiyorsanız yararlıdır.


1

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

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

Test edilmedi, sıfır indeksi nedeniyle bir eksik olabilir, ancak karakterlerden SUBSTRINGdizinizin @indexOfsonuna kadar kesilirken işlevde çalışır.

SUBSTRING([MyField], 0, @LastIndexOf)


1

Bu kod, alt dize 1'den fazla karakter içerse bile çalışır.

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

0

Bir klasör yolunda ters eğik çizginin n'inci son konumunu bulmam gerekiyordu. İşte benim çözümüm.

/*
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

İşte geçen test vakalarım

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

0

Sınırlayıcının son oluşumundan önceki parçayı almak için (yalnızca kullanım NVARCHARnedeniyle çalışır DATALENGTH):

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

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

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

0

Bu cevap, OP'nin gerekliliklerini karşılar. özellikle, iğnenin tek bir karakterden daha fazla olmasına izin verir ve samanlıkta iğne bulunmadığında hata oluşturmaz. Bana öyle geldi ki, diğer cevapların çoğu (tümü?) Bu uç durumları ele almadı. Bunun ötesinde, yerel MS SQL sunucusu CharIndex işlevi tarafından sağlanan "Başlangıç ​​Konumu" bağımsız değişkenini ekledim. Soldan sağa yerine sağdan sola işlem yapmak dışında CharIndex için spesifikasyonu tam olarak yansıtmaya çalıştım. Örneğin, iğne veya samanlık boşsa null döndürürüm ve iğne samanlıkta bulunmazsa sıfır döndürürüm. Anlayamadığım bir şey, yerleşik işlevle üçüncü parametrenin isteğe bağlı olmasıdır. SQL Server kullanıcı tanımlı işlevleriyle, işlev "EXEC" kullanılarak çağrılmadıkça tüm parametreler çağrıda sağlanmalıdır. . Üçüncü parametrenin parametre listesine dahil edilmesi gerekmekle birlikte, "varsayılan" anahtar kelimesini bir değer vermek zorunda kalmadan onun için bir yer tutucu olarak sağlayabilirsiniz (aşağıdaki örneklere bakın). Bu fonksiyondan üçüncü parametrenin istenmiyorsa kaldırılması, gerekirse eklenmesinden daha kolay olduğu için, buraya bir başlangıç ​​noktası olarak dahil ettim.

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

0

Bu iş parçacığına, tamamen aynı gereksinimi olan ancak aynı zamanda eksik olan farklı bir veritabanı türü için benzer sorunuma bir çözüm ararken rastladım. REVERSE işlevi .

Benim durumumda bu, biraz farklı bir sözdizimi olan bir OpenEdge (Progress) veritabanı içindi . Bu INSTR, çoğu Oracle tipi veritabanının sunduğu işlevi bana sağladı .

Bu yüzden şu kodu buldum:

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

Bununla birlikte, benim özel durumum için ( OpenEdge (İlerleme) veritabanı olarak) bu istenen davranışla sonuçlanmadı çünkü karakteri boş bir karakterle değiştirmek orijinal dizeyle aynı uzunluğu verdi. Bu bana pek mantıklı gelmiyor ancak aşağıdaki kodla sorunu aşabildim:

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

Şimdi anlıyorum ki bu kod, T-SQL için problemi çözmeyecek çünkü INSTRbunu sunan fonksiyonun alternatifi yok .Occurence özelliği .

Sadece kapsamlı olmak gerekirse, bu skaler işlevi oluşturmak için gereken kodu ekleyeceğim, böylece yukarıdaki örneklerde yaptığım gibi kullanılabilecektir.

  -- 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

Açık REVERSEolanı önlemek için, işlev mevcut olduğunda bu skaler işlevi oluşturmanıza gerek yoktur ve aşağıdaki gibi gerekli sonucu elde edebilirsiniz:

SELECT
  LEN(foo.filepath) - CHARINDEX('/', REVERSE(foo.filepath))+1 AS LastIndexOfSlash 
FROM foo
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.