Precisamos fazer alguns relatórios sobre valores que geralmente são sequências mistas de números e letras que precisam ser classificadas 'naturalmente'. Coisas como, por exemplo, "P7B18" ou "P12B3". @ As strings serão principalmente seqüências de letras e números alternados. O número desses segmentos e o comprimento de cada um podem variar.
Gostaríamos que as partes numéricas delas fossem classificadas em ordem numérica. Obviamente, se eu apenas manipular esses valores de string diretamente com ORDER BY
, "P12B3" virá antes de "P7B18", pois "P1" é anterior a "P7", mas eu gostaria do contrário, pois "P7" precede naturalmente "P12".
Eu também gostaria de poder fazer comparações de intervalos, por exemplo, @bin < 'P13S6'
ou algo assim. Não preciso lidar com ponto flutuante ou números negativos; estes serão estritamente números inteiros não negativos com os quais estamos lidando. O comprimento da string e o número de segmentos podem ser arbitrários, sem limites superiores fixos.
No nosso caso, o invólucro de cordas não é importante, embora, se houver uma maneira de fazer isso de maneira compatível com agrupamentos, outros possam achar isso útil. A parte mais feia de tudo isso é que eu gostaria de poder fazer pedidos e filtragem de intervalo na WHERE
cláusula.
Se eu estivesse fazendo isso em C #, seria uma tarefa bem simples: faça uma análise para separar o alfa do numérico, implemente IComparable e pronto. O SQL Server, é claro, não parece oferecer nenhuma funcionalidade semelhante, pelo menos até onde eu saiba.
Alguém conhece algum bom truque para fazer isso funcionar? Existe alguma capacidade pouco divulgada de criar tipos personalizados de CLR que implementam IComparable e têm esse comportamento conforme o esperado? Também não sou contra o Stupid XML Tricks (veja também: concatenação de listas) e também tenho funções de wrapper de correspondência / extração / substituição de regex CLR disponíveis no servidor.
Edição: Como um exemplo um pouco mais detalhado, eu gostaria que os dados se comportassem algo assim.
SELECT bin FROM bins ORDER BY bin
bin
--------------------
M7R16L
P8RF6JJ
P16B5
PR7S19
PR7S19L
S2F3
S12F0
ou seja, divida as strings em tokens de todas as letras ou todos os números e classifique-os alfabeticamente ou numericamente, respectivamente, com os tokens mais à esquerda sendo o termo de classificação mais significativo. Como mencionei, pedaço de bolo no .NET se você implementar o IComparable, mas não sei como (ou se) você pode fazer esse tipo de coisa no SQL Server. Certamente não é algo que me deparei em 10 anos ou mais trabalhando com ele.
P7B12
poderia tornar-seP 07 B 12
, em seguida, (via ASCII)80 07 65 12
, então80076512
Respostas:
Deseja um meio eficiente e sensível de classificar números em strings como números reais? Considere votar na minha sugestão do Microsoft Connect: Suporte "classificação natural" / DIGITSASNUMBERS como uma opção de agrupamento
Não há um meio fácil e interno de fazer isso, mas aqui está uma possibilidade:
Normalize as strings reformatando-as em segmentos de comprimento fixo:
VARCHAR(50) COLLATE Latin1_General_100_BIN2
. O comprimento máximo de 50 pode precisar ser ajustado com base no número máximo de segmentos e em seus possíveis comprimentos máximos.AFTER [or FOR] INSERT, UPDATE
gatilho, para garantir a configuração correta do valor de todos os registros, mesmo aqueles vindo através de consultas ad hoc etc. É claro que esse UDF escalar também pode ser tratado via SQLCLR, mas precisaria ser testado para determinar qual era realmente mais eficiente. **UPPER()
função ao resultado final de todos os segmentos (para que seja necessário apenas uma vez e não por segmento). Isso permitirá uma classificação adequada, considerando o agrupamento binário da coluna de classificação.AFTER INSERT, UPDATE
gatilho na tabela que chama o UDF para definir a coluna de classificação. Para melhorar o desempenho, use aUPDATE()
função para determinar se esta coluna código é mesmo naSET
cláusula daUPDATE
declaração (simplesmenteRETURN
se falso), e depois juntar osINSERTED
eDELETED
pseudo-tabelas na coluna código para apenas as linhas de processos que têm alterações no valor de código . Certifique-se de especificarCOLLATE Latin1_General_100_BIN2
a condição JOIN para garantir a precisão na determinação de uma alteração.Exemplo:
Nesta abordagem, você pode classificar via:
E você pode fazer a filtragem de alcance via:
ou:
O filtro
ORDER BY
e oWHERE
filtro devem usar o agrupamento binário definidoSortColumn
devido à Precedência do agrupamento .As comparações de igualdade ainda seriam feitas na coluna de valor original.
Outros pensamentos:
Use um SQLCLR UDT. Isso pode funcionar, embora não esteja claro se ele apresenta um ganho líquido em comparação com a abordagem descrita acima.
Sim, um SQLCLR UDT pode ter seus operadores de comparação substituídos por algoritmos customizados. Isso lida com situações em que o valor está sendo comparado a outro valor que já é do mesmo tipo personalizado ou que precisa ser convertido implicitamente. Isso deve lidar com o filtro de faixa em uma
WHERE
condição.Com relação à classificação da UDT como um tipo de coluna regular (não uma coluna calculada), isso só é possível se a UDT for "ordenada por byte". Ser "ordenado por byte" significa que a representação binária da UDT (que pode ser definida na UDT) é classificada naturalmente na ordem apropriada. Supondo que a representação binária seja tratada de maneira semelhante à abordagem descrita acima para a coluna VARCHAR (50) que possui segmentos de comprimento fixo preenchidos, que se qualificariam. Ou, se não fosse fácil garantir que a representação binária fosse ordenada naturalmente da maneira correta, você poderia expor um método ou propriedade da UDT que produza um valor que seria ordenado corretamente e, em seguida, criar uma
PERSISTED
coluna computada nessa método ou propriedade. O método precisa ser determinístico e marcado comoIsDeterministic = true
.Os benefícios dessa abordagem são:
Parse
método da UDT capte oP7B18
valor e o converta, você poderá simplesmente inserir os valores naturalmente comoP7B18
. E com o método implícito de conversão definido na UDT, a condição WHERE também permitiria usar simplesmente P7B18`.As conseqüências dessa abordagem são:
PERSISTED
coluna computada em uma propriedade ou método da UDT, você obterá a representação retornada pela propriedade ou método. Se você quiser oP7B18
valor original , precisará chamar um método ou propriedade da UDT codificada para retornar essa representação. Como você precisa substituir oToString
método de qualquer maneira, esse é um bom candidato para fornecer isso.Não está claro (pelo menos para mim, agora que não testei esta parte) o quão fácil / difícil seria fazer alterações na representação binária. Alterar a representação classificada e armazenada pode exigir a remoção e a inclusão novamente do campo. Além disso, a eliminação do Assembly que contém a UDT falharia se usada de qualquer maneira, portanto, você deve garantir que não haja mais nada no Assembly além deste UDT. Você pode
ALTER ASSEMBLY
substituir a definição, mas existem algumas restrições nisso.Por outro lado, o
VARCHAR()
campo são dados desconectados do algoritmo, portanto, seria necessário apenas atualizar a coluna. E se houver dezenas de milhões de linhas (ou mais), isso poderá ser feito em uma abordagem em lotes.Implemente a biblioteca ICU que realmente permite fazer essa classificação alfanumérica. Embora altamente funcional, a biblioteca vem apenas em duas linguagens: C / C ++ e Java. O que significa que você pode precisar fazer alguns ajustes para que funcione no Visual C ++, ou existe a chance de que o código Java possa ser convertido para MSIL usando o IKVM . Existem um ou dois projetos paralelos do .NET vinculados nesse site que fornecem uma interface COM que pode ser acessada no código gerenciado, mas acredito que eles não foram atualizados há algum tempo e ainda não os tentei. A melhor aposta aqui seria lidar com isso na camada de aplicativos com o objetivo de gerar chaves de classificação. As chaves de classificação seriam salvas em uma nova coluna de classificação.
Esta pode não ser a abordagem mais prática. No entanto, ainda é muito legal que essa capacidade exista. Forneci uma explicação mais detalhada de um exemplo disso na seguinte resposta:
Existe um agrupamento para classificar as seguintes cadeias na seguinte ordem 1,2,3,6,10,10A, 10B, 11?
Mas o padrão tratado nessa questão é um pouco mais simples. Para um exemplo que mostra que o tipo de padrão tratado nesta pergunta também funciona, vá para a seguinte página:
Demonstração de agrupamento na UTI
Em "Configurações", defina a opção "numérico" como "ativado" e todos os outros devem ser definidos como "padrão". Em seguida, à direita do botão "classificar", desmarque a opção "Pontos fortes" e marque a opção "Chaves de classificação". Em seguida, substitua a lista de itens na área de texto "Entrada" pela seguinte lista:
Clique no botão "classificar". A área de texto "Saída" deve exibir o seguinte:
Observe que as chaves de classificação são estruturadas em vários campos, separados por vírgulas. Cada campo precisa ser classificado de forma independente, para que apresente outro pequeno problema a ser resolvido se for necessário implementá-lo no SQL Server.
** Se houver alguma preocupação com o desempenho em relação ao uso de funções definidas pelo usuário, observe que as abordagens propostas fazem um uso mínimo delas. De fato, o principal motivo para armazenar o valor normalizado foi evitar chamar um UDF por cada linha de cada consulta. Na abordagem primária, o UDF é usado para definir o valor de
SortColumn
, e isso é feito somente noINSERT
eUPDATE
através do gatilho. Selecionar valores é muito mais comum do que inserir e atualizar, e algum valor nunca é atualizado. Por cadaSELECT
consulta que usa oSortColumn
filtro para um intervalo naWHERE
cláusula, o UDF é necessário apenas uma vez por cada um dos valores range_start e range_end para obter os valores normalizados; o UDF não é chamado por linha.Com relação à UDT, o uso é realmente o mesmo que com a UDF escalar. Ou seja, inserir e atualizar chamaria o método de normalização uma vez por cada linha para definir o valor. Em seguida, o método de normalização seria chamado uma vez por consulta por cada range_start e range_value em um filtro de intervalo, mas não por linha.
Um ponto a favor de lidar com a normalização inteiramente em um SQLCLR UDF é que, dado que não está fazendo qualquer acesso a dados e é determinista, se ele é marcado como
IsDeterministic = true
, então ele pode participar em planos paralelos (que pode ajudar oINSERT
eUPDATE
operações), enquanto um O T-SQL UDF impedirá que um plano paralelo seja usado.fonte