2010-02-24 15 views
6

Potrzebuję znaleźć najwyższą wartość z bazy danych, która spełnia pewną konwencję formatowania. Konkretnie chciałbym znaleźć najwyższą wartość, która wygląda jakT-SQL IsNumeric() i Linq-to-SQL

EU999999 ('9' jest dowolna cyfra)

select max (COL) powróci coś jak 'EUZ ...' dla wystąpienie, które chcę wykluczyć. Poniższe zapytanie wykonuje sztuczkę, ale nie mogę tego zrobić przez Linq-SQL. Wydaje się, że nie ma tłumaczenia dla funkcji isnumeric() w SQL Server.

select max(col) from table where col like 'EU%' 
    and 1=isnumeric(replace(col, 'EU', '')) 

Napisanie funkcji bazy danych, procedury składowanej lub cokolwiek innego o takim charakterze jest daleko na liście moich korzystnych rozwiązań, ponieważ tabela ta ma zasadnicze znaczenie dla mojej aplikacji i nie można łatwo zastąpić obiekt tabeli z czegoś innego .

Jakie jest następne najlepsze rozwiązanie?

Odpowiedz

10

Chociaż ISNUMERIC brakuje, zawsze można spróbować prawie równoważne NOT LIKE '%[^0-9]%, to znaczy, nie ma non-cyfrowy w ciągu, lub alternatywnie, ciąg jest pusta lub składa się wyłącznie z cyfr:

from x in table 
where SqlMethods.Like(x.col, 'EU[0-9]%') // starts with EU and at least one digit 
    && !SqlMethods.Like(x.col, '__%[^0-9]%') // and no non-digits 
select x; 

Oczywiście, jeśli wiesz, że liczba cyfr jest stała, to można uprościć do

from x in table 
where SqlMethods.Like(x.col, 'EU[0-9][0-9][0-9][0-9][0-9][0-9]') 
select x; 
+0

Podobają mi się twoje sugestie i zaakceptuję je, ale mój prefiks (i długość) nie jest stały, niestety, więc musiałbym generować wyrażenie dynamicznie. To mi się nie podoba i myślę o sposobach zmiany schematu, abym mógł obejść to. – cdonner

+0

Jedną z zalet używania 'LIKE' bez symbolu wieloznacznego na froncie jest to, że jest bardzo przyjazny dla indeksu (można dokonać sprawdzenia zakresu względem indeksu). – Ruben

+0

Bardzo sprytna alternatywa dla IsNumeric w wyrażeniach o zmiennej długości. Szukaj nigdzie liczby w łańcuchu. Dzięki za wskazówkę! – BlueMonkMN

1

Jak powiedział, nie jest przetłumaczony na IsNumeric z LINQ to SQL. Jest kilka opcji, już napisałeś funkcję bazy danych i zapisaną procedurę w dół. Chciałbym dodać jeszcze dwa.

Opcja 1: Można to zrobić poprzez zmieszanie LINQ to SQL z LINQ to Objects, ale kiedy masz dużą bazę danych, nie należy spodziewać się doskonałą wydajność:

var cols = (from c in db.Table where c.StartsWith("EU") select c).ToList(); 
var stripped = from c in cols select int.Parse(c.Replace("EU", "")); 
var max = stripped.Max(); 

Opcja 2: zmienić Schemat bazy danych :-)

+0

nie jest opcją - o wiele za dużo danych w tej tabeli. – cdonner

+0

nadal, +1 za sugestię zmiany mojego schematu. – cdonner

10

Można użyć funkcji ISNUMERIC, dodając metodę do klasy częściowej dla DataContext. Byłoby podobne do używania UDF.

W swojej częściowej klasy DataContext za dodać to:

partial class MyDataContext 
{ 
    [Function(Name = "ISNUMERIC", IsComposable = true)] 
    public int IsNumeric(string input) 
    { 
     throw new NotImplementedException(); // this won't get called 
    } 
} 

Wtedy twój kod będzie używać go w ten sposób:

var query = dc.TableName 
       .Select(p => new { p.Col, ReplacedText = p.Col.Replace("EU", "") }) 
       .Where(p => SqlMethods.Like(p.Col, "EU%") 
         && dc.IsNumeric(p.ReplacedText) == 1) 
       .OrderByDescending(p => p.ReplacedText) 
       .First() 
       .Col; 

Console.WriteLine(query); 

Albo można wykorzystać maks:

var query = dc.TableName 
       .Select(p => new { p.Col, ReplacedText = p.Col.Replace("EU", "") }) 
       .Where(p => SqlMethods.Like(p.Col, "EU%") 
        && dc.IsNumeric(p.ReplacedText) == 1); 

var result = query.Where(p => p.ReplacedText == query.Max(p => p.ReplacedText)) 
        .First() 
        .Col; 

Console.WriteLine("Max: {0}, Result: {1}", max, result); 

W zależności od ostatecznego celu, możliwe jest zatrzymanie się na zmiennej max i dodanie jej z " EU "tekst, aby uniknąć drugiego zapytania, które dostaje nazwę kolumny.

EDIT: jak wspomniano w komentarzach, wada tej metody jest to, że odbywa się na zamawianie zamiast tekstu wartościami liczbowymi i nie ma jeszcze tłumaczenie Int32.Parse na SQL.

+0

+1. Jestem zaskoczony, że to możliwe. Nie zapomnij o zamawianiu * OrderByDescending (p => p.ReplacedText) * jest oparta na tekście (ReplacedText to ciąg/varchar), więc może nie dać właściwego wyniku. Przed sortowaniem musi być rzutowany na liczbę całkowitą. – Steven

+0

@Thanks thanks, to miłe obejście prostych funkcji. Masz rację co do zamówienia. Niestety nie ma na to żadnego rozwiązania i 'Int32.Parse' nie ma tłumaczenia na SQL, więc w przypadku numerycznego sortowania najlepszy jest UDF/SPROC. –

+0

To samo, jest to jedna z najlepszych rzeczy, jakie widziałem od jakiegoś czasu. – cdonner

0

Moja sugestia to powrót do SQL in-line i użycie metody DataContext.ExecuteQuery(). Możesz użyć zapytania SQL, które wysłałeś na początku.

Oto, co zrobiłem w podobnych sytuacjach. Nie idealne, przyznane, ze względu na brak sprawdzania typu i ewentualne błędy składniowe, ale po prostu upewnij się, że jest uwzględnione w testach jednostkowych. Nie każde możliwe zapytanie jest objęte składnią Linq, stąd istnienie ExecuteQuery w pierwszej kolejności.