AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / dba / Perguntas / 179482
Accepted
Jon49
Jon49
Asked: 2017-07-11 07:51:53 +0800 CST2017-07-11 07:51:53 +0800 CST 2017-07-11 07:51:53 +0800 CST

Reutilização de código de classificação para banco de dados multilíngue

  • 772

Estou tentando classificar um resultado de uma tabela multilíngue. Eu gostaria que o algoritmo de classificação viesse de uma função, mas ele adiciona um impacto de 8% ao desempenho, pelo menos. Então, eu não tenho certeza de como fazer isso. Portanto, para a classificação estou usando este método descrito em um artigo sobre como classificar tabelas multilíngues conforme mostrado abaixo:

select UnicodeData,Collation
from (
    select
        ML.UnicodeData,
        ML.Collation,
        RN =
            CASE
                when Collation = 'he-IL' then ROW_NUMBER() OVER (order by unicodedata Collate Hebrew_CI_AS                  )
                when Collation = 'en-US' then ROW_NUMBER() OVER (order by unicodedata Collate SQL_Latin1_General_CP1_CI_AS  )
                when Collation = 'kn-IN' then ROW_NUMBER() OVER (order by unicodedata Collate Indic_General_100_CI_AS       )
                when Collation = 'hi-IN' then ROW_NUMBER() OVER (order by unicodedata Collate Indic_General_100_CI_AS       )
                when Collation = 'ar-EG' then ROW_NUMBER() OVER (order by unicodedata Collate Arabic_CI_AS                  )
                when Collation = 'cs'    then ROW_NUMBER() OVER (order by unicodedata Collate Czech_CI_AS                   )
            END
    from MultipleLanguages ML
) T
order by RN

Exceto que eu abstraí o collationcódigo em sua própria função, assim:

CREATE FUNCTION [utils].[OrderByLanguage]
( @LanguageID tinyint
, @IDName utils.ID_Name READONLY
) RETURNS TABLE AS RETURN
SELECT
      t.ID
    , CASE @LanguageID
        WHEN 1 THEN ROW_NUMBER() OVER (ORDER BY t.[Name]) -- en
        WHEN 3 THEN ROW_NUMBER() OVER (ORDER BY t.[Name]) -- en-ca
        WHEN 6 THEN ROW_NUMBER() OVER (ORDER BY t.[Name]) -- 'en-nz'
        WHEN 5 THEN ROW_NUMBER() OVER (ORDER BY t.[Name]) -- 'en-za'
        WHEN 2 THEN ROW_NUMBER() OVER (ORDER BY t.[Name] COLLATE Modern_Spanish_CI_AI) -- es
        WHEN 4 THEN ROW_NUMBER() OVER (ORDER BY t.[Name] COLLATE French_CI_AI) -- 'fr-ca'
    END RowNumber
FROM @IDName t

Mas então, quando eu chamo essa função, tenho que fazer essa chamada dupla estranha na função com valor de tabela.

CREATE FUNCTION api.GetTable
( @LanguageCode VARCHAR(10)
) RETURNS NVARCHAR(MAX)
AS BEGIN

    DECLARE
          @Result NVARCHAR(MAX)
        , @LangID tinyint
    DECLARE @Sort utils.ID_Name

    SET @LangID = api_utils.GetLanguageID(@LanguageCode)

    INSERT INTO @Sort (ID, [Name])
    SELECT
          t.ID
        , t.title
    FROM api_utils.GetTable(@LangID) t

    SET @Result = (
        SELECT
            CONVERT(VARCHAR(10), t.ID) id,
            t.category,
            t.[system],
            t.title,
            JSON_QUERY(utils.ToRawJsonArray((
                SELECT x.[Description]
                FROM api_utils.GetKeywords(t.ID, @LangID) x
                ORDER BY x.[Description]
                FOR JSON AUTO), 'Description')
            ) keywords
        FROM api_utils.GetTable(@LangID) t
        ORDER BY (SELECT s.RowNumber
                  FROM utils.OrderByLanguage(@LangID, @Sort) s
                  WHERE s.ID = t.ID)
        FOR JSON AUTO, ROOT('titles')
        )

    RETURN @Result

END

Então, você pode ver que eu tenho que chamar a função api_utils.GetTableduas vezes. Até onde eu sei, a única outra maneira de abstrair a classificação de agrupamento seria colocar o algoritmo de classificação real e, em seguida, ter um script que pesquise toda a base de código e adicione outro idioma de agrupamento sempre que precisar adicionar outro idioma. Existe alguma outra maneira de fazer isso? O que os outros fizeram? Qual seria a melhor prática? O desempenho nisso não é absolutamente crítico, mas é bom mantê-lo enxuto para que não demore muito, pois já é uma chamada intensiva.

Desde já, obrigado!

Atualizar

Respondendo às perguntas de @srutzky nos comentários:

1) Quantos dados são retornados por api_utils.GetTable?

Há cerca de 150 registros retornados da tabela.

2) Por que chamar api_utils.GetTable duas vezes quando na primeira vez o resultado é despejado em @Sort?

A @Sorttabela é uma tabela definida pelo usuário com otimização de memória ( UDT). Como estou passando uma tabela para a utils.OrderByLanguagefunção, ela precisa ser um arquivo UDT. O que significa que preciso obter os dados da função inline api_utils.GetTableduas vezes. Não tenho certeza se isso causou um problema de desempenho ao chamar api_utils.GetTableduas vezes. Talvez SQL Serverseja inteligente o suficiente para armazenar em cache o resultado? Testando novamente o INSERTcusto da consulta é de 38%. Portanto, uma parte bastante significativa do custo da consulta.

Não seria mais rápido adicionar as colunas de categoria e sistema a @Sort e puxá-las de volta na primeira chamada e depois usar @Sort na cláusula FROM?

Como o UDTé genérico para uso em todos os diferentes procedimentos que chamam a função utils.OrderByLanguage, seria muito difícil generalizar para um número desconhecido de colunas que os diferentes procedimentos usariam.

3) Isso tem que ser uma função ou pode ser um procedimento armazenado?

Você está falando api_utils.GetTable? Eu preferiria que api_utils.GetTablepermanecesse uma função, pois facilita o uso e o teste. Onde eu chamo api_utils.GetTableé um Stored Procedure.

Se você está falando utils.OrderByLanguage, eu não me importo se seria um arquivo stored procedure. Eu não tenho certeza de como isso ajudaria embora. Então, se for, por favor me avise!

Atualização para resposta aceita

Descobri que adicionar um índice não fazia diferença no desempenho. Eu também imaginei que poderia colocar a sortcoluna na #sorttabela original, pois ela deve ser a mesma de qualquer maneira. Isso reduz o número de avisos no meu projeto SSDT. Então eu apenas faço um alterna coluna assim:

ALTER TABLE #AlterSort ALTER COLUMN [sort] nvarchar(max) COLLATE SQL_Latin1_General_CP1_CI_AS
sql-server t-sql
  • 2 2 respostas
  • 297 Views

2 respostas

  • Voted
  1. Best Answer
    Solomon Rutzky
    2017-07-14T22:24:15+08:002017-07-14T22:24:15+08:00

    Olhando para o que você tem até agora, é bom que [utils].[OrderByLanguage]seja uma função com valor de tabela em linha (ITVF), mas ainda parece ser uma subconsulta correlacionada onde, para cada linha api_utils.GetTable(@LangID), passa em todas as linhas api_utils.GetTable(@LangID)para classificá-la (ou seja, a ORDER BYcláusula.

    Aplicar agrupamentos em tempo de execução pode ser bastante caro, pois é necessário gerar as chaves de classificação para esses valores naquele momento. Para obter o melhor desempenho, a criação de um índice gerará as chaves de classificação antecipadamente e até as colocará na ordem correta. Mas lidar com várias localidades é realmente complicado. Criar uma cópia da coluna de origem para cada localidade pode exigir uma enorme quantidade de espaço adicional em disco (e E/S), dependendo do tamanho das strings, de quantas localidades / agrupamentos são necessários e quantas linhas haverá os próximos 3 - 5 anos. Felizmente, você pode criar colunas computadas não persistentes para serem essas cópias (que não ocupam espaço) e indexá-las (o que ocupa espaço). Embora isso provavelmente não seja viável se houvesse 10 localidades, uma coluna de base de pelo menosNVARCHAR(200), e 1 milhão (ou mais) de linhas, para sua situação deve funcionar bem. Com essa abordagem, a parte dinâmica estará na escolha de qual coluna selecionar (alcançável via SQL dinâmico ou IFinstruções, dependendo da situação). Mas, como você pode ver no exemplo a seguir (ative "Incluir Plano de Execução Real"), ambas as consultas filtradas (últimas 2 consultas) obtêm Buscas de Índice nos índices pretendidos e retornam os resultados esperados:

    Você pode ver uma demonstração ao vivo disso em dbfiddle.uk .

    SET NOCOUNT ON;
    -- DROP TABLE #T;
    CREATE TABLE #T
    (
      [ID] INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
      [Col1] VARCHAR(10) COLLATE Latin1_General_100_CI_AS,
      [Col2] AS ([Col1] COLLATE Latin1_General_100_CS_AS)
    );
    
    CREATE INDEX [IX_#T_Col1] ON [#T] ([Col1]);
    CREATE INDEX [IX_#T_Col2] ON [#T] ([Col2]);
    
    INSERT INTO #T ([Col1]) VALUES ('a'), ('A');
    
    SELECT * FROM #T; -- 2 rows
    
    SELECT [ID] FROM #T WHERE [Col1] = 'A'; -- 2 rows
    
    SELECT [ID] FROM #T WHERE [Col2] = 'A'; -- 1 row
    

    No entanto, dado o código para api.GetTableisso, pode não ser a melhor abordagem. Se você quiser manter a estrutura atual (tanto quanto possível), poderá fazer o seguinte:

    1. Converter api.GetTableem um procedimento armazenado
    2. Use um OUTPUTparâmetro para não precisar lidar com um conjunto de resultados
    3. @Sortdeve ser uma tabela temporária, não uma variável de tabela
    4. Crie a #Sorttabela temporária com todas as colunas necessárias, exceto /[Name] ) title: ID, categorye [system].
    5. Em uma série de IFdeclarações, adicione a [title]coluna, mas com o Collation adequado:

      IF (@LanguageID = 2)
      BEGIN
        ALTER TABLE #Sort ADD [title] NVARCHAR(2000) COLLATE Modern_Spanish_CI_AI
      END;
      
      IF (@LanguageID = 4)
      BEGIN
        ALTER TABLE #Sort ADD [title] NVARCHAR(2000) COLLATE French_CI_AI
      END;
      
      ...
      
    6. Altere FROM api_utils.GetTable(@LangID) tna @Result=consulta principal para ser:FROM #Sort t
    7. Altere ORDER BY (SELECT s.RowNumber...na @Result=consulta principal para ser:ORDER BY t.[title]

    Isso exigirá a reaplicação do Collation em cada execução, mas:

    1. não requer nenhuma alteração nas tabelas base
    2. ele irá reutilizar os dados retornados por api.GetTable(sem chamada dupla)
    3. ele usará estatísticas na [Name]coluna
    4. não será uma subconsulta correlacionada (woo hoo!)
    5. ele oferece a opção de criar um índice na [Name]coluna depois que ela for preenchida.

    Reutilização de código

    Ao mudar para procedimentos armazenados e tabelas temporárias, podemos realmente manter o objetivo de reutilização de código. O código para adicionar a coluna de classificação à tabela temporária local pode ser abstraído para outro procedimento armazenado. Embora a criação de uma tabela temporária em uma chamada de subprocedimento não ajude, pois essa tabela temporária desaparecerá quando a chamada de subprocedimento terminar, as alterações feitas em uma tabela temporária que existe antes da chamada de subprocedimento sobreviverão à conclusão dessa chamada . Por exemplo:

    Configurar

    CREATE PROCEDURE #AddSortColumn
    (
      @LanguageID TINYINT
    )
    AS
    SET NOCOUNT ON;
    
    DECLARE @IsColumnAdded BIT = 0;
    
    IF (@LanguageID = 2)
    BEGIN
      ALTER TABLE #Sort ADD [title] NVARCHAR(2000) COLLATE Modern_Spanish_CI_AI;
      SET @IsColumnAdded = 1;
    END;
    
    IF (@LanguageID = 4)
    BEGIN
      ALTER TABLE #Sort ADD [title] NVARCHAR(2000) COLLATE French_CI_AI;
      SET @IsColumnAdded = 1;
    END;
    
    IF (@IsColumnAdded = 0)
    BEGIN
      RAISERROR(N'Invalid @LanguageID: %d', 16, 1, @LanguageID);
      RETURN;
    END;
    GO
    

    Teste

    CREATE TABLE #Sort (ID INT);
    
    EXEC tempdb.dbo.sp_help '#Sort'; -- only 1 column
    
    EXEC #AddSortColumn @LanguageID = 4;
    
    EXEC tempdb.dbo.sp_help '#Sort'; -- now there are 2 columns
    

    Ao colocar os agrupamentos específicos apenas neste procedimento armazenado, você deve ter apenas um local para atualizar ao adicionar um novo idioma ao suporte.

    Juntando tudo

    Com todos os itens acima em mente, acabamos com o seguinte procedimento armazenado contendo uma única chamada para api_utils.GetTable(), sem subconsulta correlacionada e api_utils.AddSortColumn (que você precisa criar) pode ser usado em outros procedimentos armazenados e em outras tabelas base:

    CREATE PROCEDURE api.GetTableAsJSON
    (
      @LanguageCode VARCHAR(10),
      @JSONifiedTable NVARCHAR(MAX) OUTPUT
    )
    AS
    SET NOCOUNT ON;
    
        DECLARE @LangID TINYINT;
    
        CREATE TABLE #Sort
        (
          [ID] INT NOT NULL PRIMARY KEY,
          [category] ...,
          [system] ...
        );
    
        SET @LangID = api_utils.GetLanguageID(@LanguageCode)
    
        EXEC api_utils.AddSortColumn @LanguageID = @LangID; -- add [Title] to #Sort
    
        INSERT INTO #Sort ([ID], [category], [system], [Title])
          SELECT t.[ID], t.[category], t.[system], t.[title]
          FROM   api_utils.GetTable(@LangID) t
    
        -- Optional index; try with and without to see which is better.
        --CREATE INDEX [IX_#Sort_Title] ON #Sort ([Title]);
    
        SET @JSONifiedTable = (
            SELECT
                CONVERT(VARCHAR(10), t.ID) AS [id],
                t.[category],
                t.[system],
                t.title,
                JSON_QUERY(utils.ToRawJsonArray((
                    SELECT x.[Description]
                    FROM api_utils.GetKeywords(t.[ID], @LangID) x
                    ORDER BY x.[Description]
                    FOR JSON AUTO), 'Description')
                ) AS [keywords]
            FROM #Sort t
            ORDER BY t.[Title]
            FOR JSON AUTO, ROOT('titles')
            );
    
        RETURN;
    END;
    
    • 2
  2. Jon49
    2017-07-15T15:41:08+08:002017-07-15T15:41:08+08:00

    Outra abordagem é ter apenas um gerador de código. Isso é se você precisar de desempenho máximo, pois é apenas cerca de 14% mais rápido que a solução marcada como a resposta para meus parâmetros fornecidos (dados 14% somados rapidamente quando você o tem em vários lugares e em várias partes do mesmo código) .

    # Place the following in your code then run this script and it will update for you
    # without the `#` comment symbols. Replace `title` with whatever it field it should
    # be sorting by.
    
    #-- Start-Collation-Sort {sort-by:title}
    #
    #-- End-Collation-Sort
    
    $text = @'
    -- Start-Collation-Sort {sort-by:$1,table:$2}
    -- This is generated code. Use ./CollationSortGenerator.ps1 to change
    -- this code and all the other files which contain this code.
      CASE @LangID
        WHEN 1 THEN ROW_NUMBER() OVER (ORDER BY $2.$1) -- en
        WHEN 3 THEN ROW_NUMBER() OVER (ORDER BY $2.$1) -- en-ca
        WHEN 6 THEN ROW_NUMBER() OVER (ORDER BY $2.$1) -- 'en-nz'
        WHEN 5 THEN ROW_NUMBER() OVER (ORDER BY $2.$1) -- 'en-za'
        WHEN 2 THEN ROW_NUMBER() OVER (ORDER BY $2.$1 COLLATE Modern_Spanish_CI_AI) -- es
        WHEN 4 THEN ROW_NUMBER() OVER (ORDER BY $2.$1 COLLATE French_CI_AI) -- 'fr-ca'
      END
    -- `End-Collation-Sort
    '@
    
    $regex =
    @'
    (?m)^\s*-- Start-Collation-Sort {sort-by:(.+),table:(.+)}
    [^`]*
    -- `End-Collation-Sort
    '@
    
    Get-ChildItem -Path *.sql -recurse |
        Where-Object { (Select-String "-- Start-Collation-Sort"  $_ -Quiet -CaseSensitive -SimpleMatch) } |
        ForEach-Object {
            Write-Output ("Updating file ... " + ($_ | Resolve-Path -Relative ))
    
            (Get-Content $_ -Raw) -replace $regex, $text |
            Set-Content $_
        }
    
    • 1

relate perguntas

  • SQL Server - Como as páginas de dados são armazenadas ao usar um índice clusterizado

  • Preciso de índices separados para cada tipo de consulta ou um índice de várias colunas funcionará?

  • Quando devo usar uma restrição exclusiva em vez de um índice exclusivo?

  • Quais são as principais causas de deadlocks e podem ser evitadas?

  • Como determinar se um Índice é necessário ou necessário

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    conectar ao servidor PostgreSQL: FATAL: nenhuma entrada pg_hba.conf para o host

    • 12 respostas
  • Marko Smith

    Como fazer a saída do sqlplus aparecer em uma linha?

    • 3 respostas
  • Marko Smith

    Selecione qual tem data máxima ou data mais recente

    • 3 respostas
  • Marko Smith

    Como faço para listar todos os esquemas no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Listar todas as colunas de uma tabela especificada

    • 5 respostas
  • Marko Smith

    Como usar o sqlplus para se conectar a um banco de dados Oracle localizado em outro host sem modificar meu próprio tnsnames.ora

    • 4 respostas
  • Marko Smith

    Como você mysqldump tabela (s) específica (s)?

    • 4 respostas
  • Marko Smith

    Listar os privilégios do banco de dados usando o psql

    • 10 respostas
  • Marko Smith

    Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Como faço para listar todos os bancos de dados e tabelas usando o psql?

    • 7 respostas
  • Martin Hope
    Jin conectar ao servidor PostgreSQL: FATAL: nenhuma entrada pg_hba.conf para o host 2014-12-02 02:54:58 +0800 CST
  • Martin Hope
    Stéphane Como faço para listar todos os esquemas no PostgreSQL? 2013-04-16 11:19:16 +0800 CST
  • Martin Hope
    Mike Walsh Por que o log de transações continua crescendo ou fica sem espaço? 2012-12-05 18:11:22 +0800 CST
  • Martin Hope
    Stephane Rolland Listar todas as colunas de uma tabela especificada 2012-08-14 04:44:44 +0800 CST
  • Martin Hope
    haxney O MySQL pode realizar consultas razoavelmente em bilhões de linhas? 2012-07-03 11:36:13 +0800 CST
  • Martin Hope
    qazwsx Como posso monitorar o andamento de uma importação de um arquivo .sql grande? 2012-05-03 08:54:41 +0800 CST
  • Martin Hope
    markdorison Como você mysqldump tabela (s) específica (s)? 2011-12-17 12:39:37 +0800 CST
  • Martin Hope
    Jonas Como posso cronometrar consultas SQL usando psql? 2011-06-04 02:22:54 +0800 CST
  • Martin Hope
    Jonas Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL? 2011-05-28 00:33:05 +0800 CST
  • Martin Hope
    Jonas Como faço para listar todos os bancos de dados e tabelas usando o psql? 2011-02-18 00:45:49 +0800 CST

Hot tag

sql-server mysql postgresql sql-server-2014 sql-server-2016 oracle sql-server-2008 database-design query-performance sql-server-2017

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve