Artigo
· Maio 2, 2023 26min de leitura

Tutorial para Desenvolvedor nível Médio/Senior : Solução de Consulta Geral

Resumo

O que é Query

Query é um método para encontrar dados que atendem às condições e apresentar os resultados como um conjunto de dados.

Tipo de Query

  • SQL Query,Usando %SQLQuery e SQL SELECT.
  • Custom Query,Usando a classe %Query e lógica personalizada para gerar resultados.

Observação: antes de falar sobre a solução de Query geral, vamos primeiro entender os fundamentos da Query para compreender os princípios da implementação. Se você já conhece o uso básico da Query, pule esta seção e vá direto para "Desafios".

Fundamentos da Query

Fundamentos do SQL Query

Query QueryPersonByName(name As %String = "") As %SQLQuery(COMPILEMODE = "IMMEDIATE", CONTAINID = 1, ROWSPEC = "id:%Integer:ID,MT_Name:%String:name,age:%String,no:%String", SELECTMODE = "RUNTIME") [ SqlName = QueryPersonByName, SqlProc ]
{
    SELECT top 10 ID, MT_Age, MT_Name, MT_No
    FROM M_T.Person
    WHERE (MT_Name %STARTSWITH :name)
    ORDER BY id
}

Descrição:

  • Query - Declara a palavra-chave do método Query.

  • QueryPersonByName - Declara o método Query.

  • name As %String = "" - Declara a palavra-chave do método Query.

  • %SQLQuery - O tipo da Query é %SQLQuery.

    • %SQLQuery é uma subclasse de %Query e usa uma forma simples de Query que permite a gravação de declarações Select SQL diretamente no corpo do método.
  • COMPILEMODE - parâmetro de %SQLQuery que indica o método de compilação.

    • IMMEDIATE - faz a compilação imediata, ao verificar se a declaração SQL atual está correta.
    • DYNAMIC - faz a compilação dinâmica, quando a declaração SQL é compilada no tempo de execução.
  • CONTAINID - Para definir o número da coluna colocada como o ID retornado.

    • 1 - Retorna a coluna do ID.
    • 0 - Não retorna.
  • SELECTMODE - Indica o método de exibição.

    • RUNTIME - Nenhum
    • ODBC - Mostra os dados como ODBC.
    • DISPLAY - Mostra os dados como um display.
    • LOGICAL - Mostra os dados de maneira lógica.
  • ROWSPEC - Fornece o nome da coluna de dados, o tipo de dado e a descrição. Uma lista de nomes de variáveis e tipos de dados separados por aspas e vírgulas. Confira o formato a seguir.

    • java ROWSPEC = "id:%Integer:ID,age:%String,MT_Name:%String:name,no:%String"

    • id - Indica o nome da coluna de dados.

    • %Integer - Indica o tipo de dado.
    • ID - A descrição dos dados.
  • SqlProc - Indica que o método pode ser chamado como um procedimento armazenado.

  • SqlName - O nome do procedimento a ser chamado.

    • Método de chamada não declarado - call M.Query_QueryPersonByName()
    • Método de chamada não declarado - call M.QueryPersonByName()

image

  • Método de chamada não declarado - d ##class(%ResultSet).RunQuery(className, queryName, arg...)
USER>d ##class(%ResultSet).RunQuery("M.Query", "QueryPersonByName")

ID:age:name:no:
1:21:yaoxin:314629:
2:29:yx:685381:
3:18:Umansky,Josephine Q.:419268:
4:27:Pape,Ted F.:241661:
5:25:Russell,Howard T.:873214:
6:30:Xenia,Ashley U.:420471:
7:24:Rotterman,Martin O.:578867:
8:18:Drabek,Hannah X.:662167:
9:19:Eno,Mark U.:913628:
11:18:Tsatsulin,Dan Z.:920134:

Fundamentos da Query personalizada

Ao usar uma Query personalizada, geralmente seguimos um modelo fixo. Os seguintes métodos de classe são definidos na mesma classe.

  • QueryName - Especifica a %Query no tipo de método Query.
  • QueryNameExecute— Esse método principalmente grava a lógica de negócio para buscar os dados e obter o conjunto de dados.
  • QueryNameFetch — Esse método itera o conjunto de dados.
  • QueryNameClose — Esse método exclui dados ou objetos temporários.

**Observação: o exemplo a seguir mostra um modelo de Query personalizada que é um dos casos mais usados, e não um fixo. **


Definição de QueryName

Query QueryPersonByAge(pAge As %String = "", count As %Integer = "10") As %Query(ROWSPEC = "id:%Integer:ID,MT_Name:%String:name,age:%String,no:%String")
{
}

Define o tipo de Query chamado QueryPersonByAge e especificado como %Query. E deixa o corpo da definição da consulta em branco.


Definição de QueryNameExecute

ClassMethod QueryPersonByAgeExecute(ByRef qHandle As %Binary, pAge As %String = "", count As %Integer = "10") As %Status
{
    s pid = $i(^IRISTemp) // comment1
    s qHandle = $lb(0, pid, 0) // comment2
    s index = 1 // comment 3

    /* Business Logic comment4 */ 
    s id = ""
    for {
        s id = $o(^M.T.PersonD(id))
        q:(id = "")
        q:(id > count)
        s data = ^M.T.PersonD(id)
        s i = 1
        s name = $lg(data, $i(i))
        s age = $lg(data, $i(i))
        continue:(age < pAge)
        s no = $lg(data, $i(i))
        d output
    }   

    q $$$OK

output
    s ^IRISTemp(pid, index) = $lb(id, age, name, no) // comment 6 
    s index = index + 1 // comment 7
}

O método QueryNameExecute() fornece toda a lógica de negócio necessária. O nome do método precisa ser QueryNameExecute(), em que QueryName é o nome da Query definida.

其中:

  • qHandle - Usado para a comunicação com outros métodos que implementam essa consulta. qHandle pode ser de qualquer tipo. O padrão é %Binary.
  • pAge As %String = "", count As %Integer = "10" são os parâmetros de entrada para a Query, que podem ser usados como condições para a lógica de negócio.
  • Comment 1,s pid = $i(^IRISTemp) - obtém pid.
  • Comment 2,s qHandle = $lb(0, pid, 0) - O primeiro elemento 0 no array indica o início do loop, o segundo elemento pid é usado para obter os dados de ^IRISTemp, e o terceiro elemento 0 é usado para percorrer o nó inicial ^IRISTemp.
  • Código de lógica de negócio - é a principal lógica de implementação para buscar o conjunto de dados.
  • Comment 3 e comment 7,adiciona nós de índice a ^IRISTemp.
  • Comment 6,s ^IRISTemp(pid, index) = $lb(id, name, age, no) - atribuindo valores a ^IRISTemp para a travessia subsequente.
    • Aqui o formato de dados está na forma de %Library.List, para que o método Fetch não precise converter o tipo. Caso contrário, o método Fetch ainda precisaria converter os dados para o formato de lista interna.

Definição de QueryNameFetch

ClassMethod QueryPersonByAgeFetch(ByRef qHandle As %Binary, ByRef row As %List, ByRef end As %Integer = 0) As %Status [ PlaceAfter = QueryPersonByAgeExecute ]
{
    s end = $li(qHandle, 1) // comment1
    s pid = $li(qHandle, 2)
    s index = $li(qHandle, 3)
    s index = $o(^IRISTemp(pid, index)) // comment2
    if index = "" {  // comment3
        s end = 1
        s row = ""
    } else { 
        s row = ^IRISTemp(pid, index)
    }
    s qHandle = $lb(end, pid, index) // comment4
    q $$$OK
}

O método QueryNameFetch() precisa retornar uma única linha de dados em %Library. O nome do método precisa ser QueryNameFetch, em que QueryName é o nome da Query definida.

Em que:

  • qHandle - Usado para a comunicação com outros métodos que implementam essa consulta. O valor deve ser o definido por Execute.
  • row - indica o valor da linha de dados que é retornada do tipo %Library.List ou a string vazia, se nenhum dado for retornado.
  • end - precisa ser 1 quando a última linha de dados é alcançada. Se 1 não estiver especificado, ele ficará em loop infinito.
  • PlaceAfter - a palavra-chave do método PlaceAfter controla a ordem desse método no código gerado. Isso significa que o método QueryPersonByAgeFetch é gerado após a geração do método QueryPersonByAgeExecute.
  • Comment 1, linhas 1~3, analisa os valores do array qHandle para obter end, pid, index.
  • Comment 2,s index = $o(^IRISTemp(pid, index)) inicia a travessia de acordo com o pid e index analisado.
  • Comment 3, a travessia de ^IRISTemp(pid, index), cada linha pertence ao valor da fila, se "index" estiver vazio, precisa ser atribuído a 1 "end".
  • Comment 4,s qHandle = $lb(end, pid, index) buscará o end, recopiará o index do qHandle para a próxima linha de dados se preparar.

Observação: O método Fetch é executado várias vezes e itera o maior número possível de linhas de dados. Os métodos Execute e Close são executados uma vez.


Definição de QueryNameClose

ClassMethod QueryPersonByAgeClose(ByRef qHandle As %Binary) As %Status [ PlaceAfter = QueryPersonByAgeExecute ]
{
    s pid = $li(qHandle, 2) // comment1
    k ^IRISTemp(pid) // comment2
    q $$$OK
}

O método QueryNameClose() é encerrado após a conclusão da busca de dados ao remover e limpar os dados ou objetos temporários. O nome do método precisa ser QueryNameClose(), em que QueryName é o nome da Query definida.

  • qHandle - Usado para a comunicação com outros métodos que implementam essa consulta.
  • Comment 1,para obter o pid salvo por qHandle.
  • Comment 2,para limpar o ^IRISTemp gerado temporariamente.

Chame uma Query personalizada

USER> d ##class(%ResultSet).RunQuery("M.Query", "QueryPersonByAge","20")

ID:name:age:no:
1:yaoxin:21:314629:
2:yx:29:685381:
4:Pape,Ted F.:27:241661:
5:Russell,Howard T.:25:873214:
6:Xenia,Ashley U.:30:420471:
7:Rotterman,Martin O.:24:578867:
  • A consulta aqui é para todas as pessoas com mais de 20 anos de idade e com ID menor que 10.

Desafios

Os 2 exemplos básicos acima de Query são provavelmente as duas maneiras mais frequentemente usadas no momento.

No entanto, os desenvolvedores que geralmente escrevem consultas ou relatórios enfrentam vários problemas, como os seguintes:

  1. Sempre que você escreve uma Query, definir o cabeçalho da coluna ROWSPEC é bastante complicado, você consegue especificar o cabeçalho da coluna ROWSPEC sozinho?
  2. Agora quantos métodos retornam valores JSON, como converter métodos JSON em Query rapidamente?
  3. É possível escrever uma Query genérica que só precisa escrever a lógica principal de Execute?
  4. É possível otimizar o modelo atual, por exemplo, substituindo ^IRISTemp por ^||IRISTemp?

Há soluções para todas as perguntas acima. Continue lendo a próxima seção do artigo.

Solução

Se você quiser implementar uma Query geral, também precisa conhecer o método de retorno de chamadas QueryNameGetInfo.

ClassMethod Json2QueryGetInfo(ByRef colinfo As %List, ByRef parminfo As %List, ByRef idinfo As %List, ByRef qHandle As %Binary, extoption As %Integer = 0, extinfo As %List) As %Status
{
    q $$$OK
}

Conforme acima:

  • colinfo - Esse parâmetro é fundamental para definir a seção do cabeçalho da coluna ROWSPEC. Ele contém um elemento de lista para cada coluna declarada em ROWSPEC. A forma é name:exttype:caption.
    • name - nome do cabeçalho da coluna.
    • exttype - tipo de dados.
    • caption - descrição.
  • O tipo colinfo precisa ser %Library.List, e o tipo do cabeçalho de coluna definido também é %Library.List.

Exemplo:

ClassMethod QueryPersonByAgeGetInfo(ByRef colinfo As %List, ByRef parminfo As %List, ByRef idinfo As %List, ByRef %qHandle As %Binary, extoption As %Integer = 0, extinfo As %List) As %Status
{
    s colinfo = $lb($lb("id", "%Integer", "ID"), $lb("age", "%String", ""), $lb("MT_Name", "%String", "name"), $lb("no", "%String", ""))
    s parminfo = ""
    s idinfo = ""
    q $$$OK
}

Descrição: Ordem de execução da Query Execute -> GetInfo -> Fetch(n) -> Close.

As soluções a seguir são descritas separadamente:

  • Gerar uma Query dinamicamente a partir de dados ou métodos Json
  • Geração dinâmica de Query por uma declaração Select Sql
  • Geração dinâmica de Query por Query
  • Compatibilidade com a Query legada e geração de colunas Query por meio de parâmetros
  • Definição de uma Query genérica, só com a implementação do método Execute

Geração dinâmica de uma Query por dados ou métodos Json


Definindo os métodos Json

  • O método Json pode ser definido de maneira arbitrária, este exemplo é apenas para fins de teste. O seguinte método: faça uma consulta na unidade de disco atual do computador para gerar resultados Json.
ClassMethod QueryDrives(fullyQualified = 1, type = "D")
{
    s array = []
    s rs = ##class(%ResultSet).%New()
    s rs.ClassName = "%File"
    s rs.QueryName = "DriveList"
    d rs.Execute(fullyQualified)
    while (rs.Next()) {
        s drive = rs.Get("Drive")
        s drive = $zcvt(drive, "U")
        s obj = {}
        s obj.type = "D"
        continue:(type '= "D")
        s obj.drive = drive
        d array.%Push(obj)
    }
    q array
}

Execute:

USER> w ##class(M.Query).QueryDrives().%ToJSON()
[{"type":"D","drive":"C:\\"},{"type":"D","drive":"D:\\"},{"type":"D","drive":"E:\\"},{"type":"D","drive":"F:\\"},{"type":"D","drive":"G:\\"}]

Defina QueryName

Query Json2Query(className As %String, methodName As %String, arg...) As %Query
{
}

其中:

  • className - nome da classe.
  • methodName - nome do método Json a ser executado.
  • arg... - parâmetros do método a ser executado.

Defina QueryNameExecute

ClassMethod Json2QueryExecute(ByRef qHandle As %Binary, className As %String, methodName As %String, arg...) As %Status
{
    s array = $classmethod(className, methodName, arg...) // comment1
    if ('$isobject(array)) { // comment2
        s array = [].%FromJSON(array)
    }
    q:('array.%IsA("%Library.DynamicArray")) $$$ERROR($$$GeneralError, "Not a Array Object") // comment3
    q:(array.%Size() = 0) $$$ERROR($$$GeneralError, "No data") // comment4
    s qHandle = array // comment5
    q $$$OK
}
  • Comment 1,usando o mecanismo de reflexão para chamar o método desejado e obter o valor de retorno.
  • Comment 2,determina se a string retornada é convertida em objeto Json.
  • Comment 3,determina se a string retornada é convertida em objeto Json.
  • Comment 4,o comprimento do array Json 0 emite uma mensagem de erro.
  • Comment 5,obtém o objeto do array.

Defina QueryNameGetInfo

ClassMethod Json2QueryGetInfo(ByRef colinfo As %List, ByRef parminfo As %List, ByRef idinfo As %List, ByRef qHandle As %Binary, extoption As %Integer = 0, extinfo As %List) As %Status
{
    s colinfo = $lb() // comment1
    s count = 1 
    s obj = qHandle.%GetIterator()
    if obj.%GetNext(.key, .value) { 
        s obj = value.%GetIterator() 
        while obj.%GetNext(.objKey, .objValue) { // comment2
            s $li(colinfo, count) = $lb(objKey) 
            s count = $i(count) 
        }
    }   
    s parminfo = "" // comment3
    s idinfo = "" // comment4
    s qHandle = qHandle.%GetIterator() // comment5
    q $$$OK
}
  • Comment 1,inicializa o array colinfo, atribui obj ao objeto iterador qHandle.%GetIterator().
  • Comment 2,itera o objeto Json para obter Key e atribui um valor a colinfo pelo $li.
  • Comment 3,inicializa parminfo ou o erro é relatado.
  • Comment 4,inicializa idinfo ou o erro é relatado.
  • Comment 5,obtém o objeto iterador

Defina QueryNameFetch

ClassMethod Json2QueryFetch(ByRef qHandle As %Binary, ByRef row As %List, ByRef end As %Integer = 0) As %Status [ PlaceAfter = Json2QueryExecute ]
{
    s iter = qHandle
    q:($g(iter) = "") $$$OK
    if iter.%GetNext(.key, .value) { // comment1
        s row = ""
        s obj = value.%GetIterator()
        while obj.%GetNext(.objKey, .objValue) { // comment2
            if ( $g(row) = "" ) {
                s row = $lb(objValue)
            } else {
                s row = row _ $lb(objValue)
            }
        }
        s end = 0
    } else {
        s row = "" 
        s end = 1 // comment3
    }
    q $$$OK
}
  • Comment 1,obtém a linha de dados Json do iterador atual.
  • Comment 2,itera o objeto Json e concatena o valor com a linha para $lb.
  • Comment 3,se não houver conjunto de dados, define end como 1 para indicar o final da travessia.

Defina QueryNameClose

ClassMethod Json2QueryClose(ByRef qHandle As %Binary) As %Status [ PlaceAfter = Json2QueryFetch ]
{
    s qHandle = "" // comment1
    q $$$OK
}
  • Comment 1,deixa o objeto qHandle em branco.

Observação: na verdade, M tem um mecanismo de reciclagem relacionado e o método Close pode ser usado sem a declaração dele.


Chamando o método Json2Query

USER>d ##class(%ResultSet).RunQuery("M.Query","Json2Query","M.Query","QueryDrives","0","D")

type:drive:
D:D::
D:E::
D:F::
D:G::

USER>d ##class(%ResultSet).RunQuery("M.Query","Json2Query","M.Query","QueryDrives","1","D")

type:drive:
D:D:\:
D:E:\:
D:F:\:
D:G:\:

Geração dinâmica de Query por uma declaração Select Sql


Defina QueryName

Query Sql2Query(sql As %String, mode As %String = 1) As %Query
{
}
  • sql - variável que representa a declaração SQL a ser escrita.
  • mode - mostra o tipo de formato de dados.
    • 0 - formato lógico
    • 1 - formato lógico
    • 2 - formato lógico

Defina QueryNameExecute

ClassMethod Sql2QueryExecute(ByRef qHandle As %Binary, sql As %String, mode As %String = 1) As %Status
{
    s sqlStatement = ##class(%SQL.Statement).%New()
    s sqlStatement.%SelectMode = mode // comment1
    s sqlStatus = sqlStatement.%Prepare(.sql) // comment2
    q:$$$ISERR(sqlStatus) sqlStatus
    s sqlResult = sqlStatement.%Execute() 
    s stateType = sqlStatement.%Metadata.statementType
    q:('stateType = 1 ) $$$ERROR($$$GeneralError, "Not a select statement") // comment3
    s qHandle = {}
    s qHandle.sqlResult = sqlResult // comment4
    s qHandle.sqlStatement = sqlStatement 
    q $$$OK
}
  • Comment 1,define o formato de exibição dos dados de SQL.
  • Comment 2,transmite a declaração de SQL para obter o objeto sqlStatement e sqlResult.
  • Comment 3,transmite a declaração SQL não selecionada, emite uma mensagem de erro.
  • Comment 4,o qHandle transmitido nos dois objetos são sqlResult e sqlStatement.
    • sqlResult é utilizado para percorrer os dados usados.
    • sqlStatement é usado para obter informações do cabeçalho da coluna.

Defina QueryNameGetInfo

ClassMethod Sql2QueryGetInfo(ByRef colinfo As %List, ByRef parminfo As %List, ByRef idinfo As %List, ByRef qHandle As %Binary, extoption As %Integer = 0, extinfo As %List) As %Status
{
    s colinfo = $lb()
    s sqlStatement = qHandle.sqlStatement // comment1
    s count = 1
    s column = ""
    for {
        s column = $o(sqlStatement.%Metadata.columnIndex(column)) 
        q:(column = "")
        s data = sqlStatement.%Metadata.columnIndex(column)
        s $li(colinfo, count) = $lb($lg(data, 2)) // comment2
        s count = $i(count)
    }
    s parminfo = ""
    s idinfo = ""
    q $$$OK
}
  • Comment 1,obtém o objeto sqlStatement por qHandle.
  • Comment 2,dá a lista de colinfo para a atribuição cíclica das informações de cabeçalho da coluna.

Defina QueryNameFetch

ClassMethod Sql2QueryFetch(ByRef qHandle As %Binary, ByRef row As %List, ByRef end As %Integer = 0) As %Status [ PlaceAfter = Sql2QueryExecute ]
{
    s sqlStatement = qHandle.sqlStatement // comment1
    s sqlResult =  qHandle.sqlResult 
    s colCount = sqlResult.%ResultColumnCount // comment2
    if (sqlResult.%Next()) {
        for i = 1 : 1 : colCount{
            s val = sqlResult.%GetData(i)
            if ( $g(row) = "" ) { // comment3
                s row = $lb(val)
            } else {
                s row = row _ $lb(val)
            }
        }
        s end = 0 
    } else {
       s row = ""
       s end = 1
    }
    s qHandle.sqlResult = sqlResult // comment4
    q $$$OK
}
  • Comment 1,obtém o objeto sqlStatement e sqlResult por qHandle.
  • Comment 2,obtém o número de colunas, equivale a obter uma linha de dados do número de itens.
  • Comment 3,itera os dados para atribuir um valor à linha.
  • Comment 4,objeto qHandle.sqlResult, atribui um valor ao objeto atual do loop.

Defina QueryNameClose

Podemos pular isso.

Observação: na verdade, M tem um mecanismo de reciclagem relacionado, e o método Close não deve ser declarado.


Chame o método Sql2Query

USER>d ##class(%ResultSet).RunQuery("M.Query","Sql2Query","select * from M_T.Person", 1)

id:MT_Age:MT_Name:MT_No:
1:21:yaoxin:314629:
2:29:yx:685381:
3:18:Umansky,Josephine Q.:419268:
4:27:Pape,Ted F.:241661:
5:25:Russell,Howard T.:873214:
6:30:Xenia,Ashley U.:420471:
7:24:Rotterman,Martin O.:578867:
8:18:Drabek,Hannah X.:662167:
9:19:Eno,Mark U.:913628:
...
100:24:Nathanson,Jocelyn A.:147578:
USER>d ##class(%ResultSet).RunQuery("M.Query","Sql2Query","select ID,MT_Name from M_T.Person")

id:MT_Name:
1:yaoxin:
2:yx:
3:Umansky,Josephine Q.:
4:Pape,Ted F.:
5:Russell,Howard T.:
6:Xenia,Ashley U.:
7:Rotterman,Martin O.:
...
100:Nathanson,Jocelyn A.:
USER>d ##class(%ResultSet).RunQuery("M.Query","Sql2Query","select top 10 ID as id from M_T.Person")

id:
1:
2:
3:
4:
5:
6:
7:
8:
9:
11:

Chame o método Sql2Query


Defina QueryName

Query Query2Query(className As %String, queryName As %String, arg...) As %Query
{
}
  • className - nome da classe.
  • queryName - nome do método Query a ser executado.
  • arg... - parâmetros do método Query a ser executado.

Defina QueryNameExecute

ClassMethod Query2QueryExecute(ByRef qHandle As %Binary, className As %String, queryName As %String, arg...) As %Status
{
    s sqlStatement = ##class(%SQL.Statement).%New()
    s sqlStatus = sqlStatement.%PrepareClassQuery(className, queryName)
    q:$$$ISERR(sqlStatus) sqlStatus
    s sqlResult = sqlStatement.%Execute() 
    s qHandle = {}
    s qHandle.sqlResult = sqlResult
    s qHandle.sqlStatement = sqlStatement
    q $$$OK
}
  • Semelhante a Sql2Query

Defina QueryNameGetInfo

ClassMethod Query2QueryGetInfo(ByRef colinfo As %List, ByRef parminfo As %List, ByRef idinfo As %List, ByRef qHandle As %Binary, extoption As %Integer = 0, extinfo As %List) As %Status
{
    s colinfo = $lb()
    s sqlStatement = qHandle.sqlStatement
    s count = 1
    s column = ""
    for {
        s column = $o(sqlStatement.%Metadata.columnIndex(column)) 
        q:(column = "")
        s data = sqlStatement.%Metadata.columnIndex(column)
        s $li(colinfo, count) = $lb($lg(data, 2))
        s count = $i(count)
    }
    s parminfo = ""
    s idinfo = ""
    q $$$OK
}
  • Semelhante a Sql2Query

Defina QueryNameFetch

ClassMethod Query2QueryFetch(ByRef qHandle As %Binary, ByRef row As %List, ByRef end As %Integer = 0) As %Status [ PlaceAfter = Query2QueryExecute ]
{
    s sqlStatement = qHandle.sqlStatement
    s sqlResult =  qHandle.sqlResult
    s colCount = sqlResult.%ResultColumnCount
    if (sqlResult.%Next()) {
        for i = 1 : 1 : colCount{
            s val = sqlResult.%GetData(i)
            if ( $g(row) = "" ) {
                s row = $lb(val)
            } else {
                s row = row _ $lb(val)
            }
        }
        s end = 0 
    } else {
       s row = ""
       s end = 1
    }
    s qHandle.sqlResult = sqlResult
    q $$$OK
}
  • Semelhante a Sql2Query

Chame Query2Query

USER>d ##class(%ResultSet).RunQuery("M.Query","Query2Query","M.Query","QueryPersonByName")

age:id:MT_Name:no:
1:21:yaoxin:314629:
2:29:yx:685381:
3:18:Umansky,Josephine Q.:419268:
4:27:Pape,Ted F.:241661:
5:25:Russell,Howard T.:873214:
6:30:Xenia,Ashley U.:420471:
7:24:Rotterman,Martin O.:578867:
8:18:Drabek,Hannah X.:662167:
9:19:Eno,Mark U.:913628:
11:18:Tsatsulin,Dan Z.:920134:

Compatibilidade com a Query tradicional e geração de colunas Query por parâmetro

  • Compatibilidade com a Query tradicional e geração de colunas Query por parâmetro
  • Compatibilidade com a definição de colunas pelo formato de parâmetro sem especificar os parâmetros ROWSPEC
  • Otimização para transformar ^IRISTemp em ^||IRISTemp

Defina M.CommonQuery

Class M.CommonQuery Extends %Query
{

ClassMethod Close(ByRef qHandle As %Binary) As %Status [ CodeMode = generator, PlaceAfter = Execute, ProcedureBlock = 1, ServerOnly = 1 ]
{
    s %code($i(%code))= (" s pid = $li(qHandle, 2)")
    s %code($i(%code))= (" k ^||GlobalTemp(pid)")
    s %code($i(%code))= (" q $$$OK")
    q $$$OK
}

ClassMethod Fetch(ByRef qHandle As %Binary, ByRef row As %List, ByRef end As %Integer = 0) As %Status [ CodeMode = generator, PlaceAfter = Execute, ProcedureBlock = 1, ServerOnly = 1 ]
{
    s %code($i(%code))= (" s end = $li(qHandle, 1)")
    s %code($i(%code))= (" s pid = $li(qHandle, 2)")
    s %code($i(%code))= (" s ind = $li(qHandle, 3)")
    s %code($i(%code))= (" s ind = $o(^||GlobalTemp(pid, ind))")
    s %code($i(%code))= (" if (ind = """") { ")
    s %code($i(%code))= ("  s end = 1")
    s %code($i(%code))= ("  s row = """"")
    s %code($i(%code))= (" } else { ")
    s %code($i(%code))= ("  s row = ^||GlobalTemp(pid, ind)")
    s %code($i(%code))= (" }")
    s %code($i(%code))= (" s qHandle = $lb(end, pid, ind)")
    s %code($i(%code))= (" q $$$OK")
    q $$$OK
}

ClassMethod GetInfo(ByRef colinfo As %List, ByRef parminfo As %List, ByRef idinfo As %List, ByRef qHandle As %Binary, extoption As %Integer = 0, ByRef extinfo As %List) As %Status [ CodeMode = generator, ServerOnly = 1 ]
{
    s %code($i(%code))= (" s colinfo = $lb()")
    s %code($i(%code))= (" s column = $lg(qHandle, 4)")
    s %code($i(%code))= (" if ($lv(column)) {")
    s %code($i(%code))= ("  for i = 1 : 1 : $ll(column) {")
    s %code($i(%code))= ("      s $li(colinfo, i) = $lb(""Column"" _ i )")
    s %code($i(%code))= ("  }   ")
    s %code($i(%code))= (" } else {")
    s %code($i(%code))= ("  s len = $l(column, "","")")
    s %code($i(%code))= ("  for i = 1 : 1 : len {")
    s %code($i(%code))= ("      s $li(colinfo, i) = $lb($p(column, "","", i))")
    s %code($i(%code))= ("  }")
    s %code($i(%code))= (" }")
    s %code($i(%code))= (" s parminfo = """"")
    s %code($i(%code))= (" s idinfo = """"")
    s %code($i(%code))= (" q $$$OK")
    q $$$OK
}

}



Defina QueryName

Query CustomColumnQuery(column As %List) As M.CommonQuery
{
}
  • column - variável que indica a coluna do parâmetro a ser personalizado.
  • M.CommonQuery - tipo de Query personalizada, sem necessidade de escrever métodos GetInfo, Fetch e Close.

Defina QueryNameExecute

QueryNameExecute é compatível com três maneiras de definição dos cabeçalhos de coluna:

  1. O cabeçalho de coluna é transmitido pelo parâmetro column e implementado da seguinte maneira.
ClassMethod CustomColumnQueryExecute(ByRef qHandle As %Binary, column As %List) As %Status
{
    s pid = $i(^||GlobalTemp)
    s qHandle = $lb(0, pid, 0)
    s $li(qHandle, 4) = column // Mode 1 This location is required
    s ind = 1

    s id = ""
    for {
        s id = $o(^M.T.PersonD(id))
        q:(id = "")
        s data = ^M.T.PersonD(id)
        s i = 1
        s name = $lg(data, $i(i))
        s age = $lg(data, $i(i))
        s no = $lg(data, $i(i))
        d output
    }   

    q $$$OK

output
    s data = $lb(id, name)
    s ^||GlobalTemp(pid, ind)=data  
    s ind = ind + 1
}
USER> d ##class(%ResultSet).RunQuery("M.Query","CustomColumnQuery","ID,Name")

ID:Name:
1:yaoxin:
2:yx:
3:Umansky,Josephine Q.:
4:Pape,Ted F.:
5:Russell,Howard T.:
  1. Sem a transmissão do parâmetro column, os cabeçalhos de coluna são gerados automaticamente com base no número de dados de lista, implementado da seguinte maneira.
ClassMethod CustomColumnQueryExecute(ByRef qHandle As %Binary, column As %String = "") As %Status
{
    s pid = $i(^||GlobalTemp)
    s qHandle = $lb(0, pid, 0)
    s ind = 1
    s id = ""
    for {
        s id = $o(^M.T.PersonD(id))
        q:(id = "")
        s data = ^M.T.PersonD(id)
        s i = 1
        s name = $lg(data, $i(i))
        s age = $lg(data, $i(i))
        s no = $lg(data, $i(i))
        s data = $lb(id, name, no)
        q:(id > 5)
        d output
    }   
    s $li(qHandle, 4) = data // Mode 2 This location is required
    q $$$OK

output
    s ^||GlobalTemp(pid, ind)=data  
    s ind = ind + 1
}
USER>d ##class(%ResultSet).RunQuery("M.Query","CustomColumnQuery")

Column1:Column2:Column3:
1:yaoxin:314629:
2:yx:685381:
3:Umansky,Josephine Q.:419268:
4:Pape,Ted F.:241661:
5:Russell,Howard T.:873214:
  1. 3. Sem a transmissão do parâmetro column, personalize as informações do cabeçalho da coluna pelo método Execute, implementado da seguinte maneira.
ClassMethod CustomColumnQueryExecute0(ByRef qHandle As %Binary, column As %String = "") As %Status
{
    s pid = $i(^||GlobalTemp)
    s qHandle = $lb(0, pid, 0)
    s ind = 1

    s id = ""
    for {
        s id = $o(^M.T.PersonD(id))
        q:(id = "")
        s data = ^M.T.PersonD(id)
        s i = 1
        s name = $lg(data, $i(i))
        s age = $lg(data, $i(i))
        s no = $lg(data, $i(i))
        s data = $lb(id, name, no)
        q:(id > 5)
        d output
    }    
    s $li(qHandle, 4) = "id,name,age" // Option 3 This position is required
    q $$$OK 

output
    s ^||GlobalTemp(pid, ind)=data  
    s ind = ind + 1
}
USER>d ##class(%ResultSet).RunQuery("M.Query","CustomColumnQuery")

id:name:age:
1:yaoxin:314629:
2:yx:685381:
3:Umansky,Josephine Q.:419268:
4:Pape,Ted F.:241661:
5:Russell,Howard T.:873214:

Execute CustomColumnQuery

USER>d ##class(%ResultSet).RunQuery("M.Query","CustomColumnQuery","ID,Name")

ID:Name:
1:yaoxin:
2:yx:
3:Umansky,Josephine Q.:
4:Pape,Ted F.:
5:Russell,Howard T.:
6:Xenia,Ashley U.:

Definição de uma Query genérica, só com a implementação do método Execute

Para implementar uma Query geral, você precisa abstrair os métodos e subclasses para substituí-los. Então, primeiro defina a classe mãe.

Defina M.CommonQuery

Class M.BaseQuery Extends %RegisteredObject
{

/// d ##class(%ResultSet).RunQuery("M.BaseQuery","CustomQuery","id,name")
Query CustomQuery(column As %List, arg...) As %Query
{
}

ClassMethod CustomQueryExecute(ByRef qHandle As %Binary, column As %List, arg...) As %Status
{
    s qHandle = $lb(0, 0) // comment1
    s $li(qHandle, 3) = column // comment2
    d ..QueryLogic(arg...) // comment3
    q $$$OK
}

ClassMethod CustomQueryGetInfo(ByRef colinfo As %List, ByRef parminfo As %List, ByRef idinfo As %List, ByRef qHandle As %Binary, extoption As %Integer = 0, ByRef extinfo As %List) As %Status
{
    s colinfo = $lb()
    s column = $lg(qHandle ,3)
    s len = $l(column, ",") 
    for i = 1 : 1 : len {
        s $li(colinfo, i) = $lb($p(column, ",", i)) // comment5
    }
    s parminfo = ""
    s idinfo = ""
    q $$$OK
}

ClassMethod CustomQueryClose(ByRef qHandle As %Binary) As %Status [ PlaceAfter = CustomQueryExecute ]
{
    k %zQueryList // comment7
    q $$$OK
}

ClassMethod CustomQueryFetch(ByRef qHandle As %Binary, ByRef row As %List, ByRef end As %Integer = 0) As %Status [ PlaceAfter = CustomQueryExecute ]
{
    s end = $li(qHandle,1)
    s index = $li(qHandle,2)
    s index = $o(%zQueryList(index))
    if index = "" {  // comment6
        s end = 1
        s row = ""
    } else      { 
        s row = %zQueryList(index)
    }
    s qHandle = $lb(end, index)
    Quit $$$OK
}

ClassMethod QueryLogic(arg...) [ Abstract ]
{
    // comment4
}

}

  • column - indica a variável para personalizar a coluna de parâmetro.
  • arg... - os parâmetros a serem transmitidos.
  • Comment 1,aqui são feitas algumas mudanças, qHandle só registra "end" e "index", porque, se for uma variável global ou uma global de processo particular, só é válida para o processo atual, então pid pode ser omitido.
  • Comment 2,a terceira posição de qHandle será transmitida no nome do cabeçalho da coluna.
  • Comment 3,chama o método de lógica de negócio a ser implementado, esse método é abstrato e precisa de subclasse para a implementação.
  • Comment 4,chama o método de lógica de negócio a ser implementado, esse método é abstrato e precisa de subclasse para a implementação.
  • Comment 5,obtém o cabeçalho da coluna definido dinamicamente.
  • Comment 6,itera as variáveis globais.
  • Comment 7,após a travessia, as variáveis globais serão limpas.

Defina a subclasse M.PersonQuery para herdar de M.BaseQuery e implementar o método QueryLogic

  • Tudo o que precisamos aqui é atribuir um valor à variável global %zQueryList($i(count)). O modelo fixo foi abstraído para a classe mãe.
ClassMethod QueryLogic(arg...)
{
    s pName = arg(1)
    s id = ""
    for {
        s id = $o(^M.T.PersonD(id))
        q:(id = "")
        s data = ^M.T.PersonD(id)
        s i = 1
        s name = $lg(data, $i(i))
        continue:(pName '= "")&&(name '= pName)
        s age = $lg(data, $i(i))
        s no = $lg(data, $i(i))
        s %zQueryList($i(count)) = $lb(id, name, age)
    }
}

Chame o método CustomQuery

USER>d ##class(%ResultSet).RunQuery("M.PersonQuery","CustomQuery","ID,Name,Age", "yaoxin")

ID:Name:Age:
1:yaoxin:21:

Observação: as variáveis globais são usadas aqui como transmissão de dados, então, se os dados forem muito grandes, pode ocorrer vazamento de memória. Basta mudar para global de processo particular. Cabe ao leitor implementá-lo com base nessa lógica.

Observação: assim, uma classe só pode declarar uma Query. Se você quiser declarar mais de uma Query para uma classe, considere mudar para a abordagem de compatibilidade com a Query tradicional.


Gere o Json por Query

ClassMethod Query2Json(className, queryName, arg...)
{
    s array = []
    s rs = ##class(%ResultSet).%New()
    s rs.ClassName = className
    s rs.QueryName = queryName
    d rs.Execute(arg...)

    s array = []
    #; 属性值
    while (rs.Next()) {
        s valStr = ""
        s obj = {}
        for i = 1 : 1 : rs.GetColumnCount(){
            s columnName = rs.GetColumnName(i)
            s val = rs.Data(columnName)
            d obj.%Set(columnName, val)
        }
        d array.%Push(obj)
    }

    q array.%ToJSON()
}

USER>w ##class(Util.JsonUtils).Query2Json("%SYSTEM.License","Summary")
[{"LicenseUnitUse":"当前使用的软件许可单元 ","Local":"1","Distributed":"1"},{"Li           censeUnitUse":"使用的最大软件许可单元数 ","Local":"15","Distributed":"15"},{"Lic            enseUnitUse":"授权的软件许可单元 ","Local":"300","Distributed":"300"},{"LicenseU         nitUse":"当前连接 ","Local":"3","Distributed":"3"},{"LicenseUnitUse":"最大连接数          ","Local":"17","Distributed":"17"}]

Gere o csv por Query

ClassMethod Query2Csv(className, queryName, filePath, arg...)
{
    s file = ##class(%FileCharacterStream).%New()
    s file.Filename = filePath

    s array = []
    s rs = ##class(%ResultSet).%New()
    s rs.ClassName = className
    s rs.QueryName = queryName
    d rs.Execute(arg...)

    #; 列名
    s colStr = ""
    for i = 1 : 1 : rs.GetColumnCount(){
        s columnName = rs.GetColumnName(i)
        s colStr = $s(colStr = "" : columnName, 1 : colStr _ "," _ columnName)
    }
    d file.Write(colStr)

    #; 属性值
    while (rs.Next()) {
        s valStr = ""
        for i = 1 : 1 : rs.GetColumnCount(){
            s columnName = rs.GetColumnName(i)
            s val = rs.Data(columnName)
            s valStr = $s(valStr = "" : val, 1 : valStr _ "," _ val)    
        }
        d file.Write($c(10) _ valStr)
    }

    d file.%Save()
    q $$$OK
}
USER>w ##class(Util.FileUtils).Query2Csv("%SYSTEM.License","Summary","E:\m\CsvFile2.csv")
1

imagem

imagem

Resumo

  • Entender o parâmetro qHandle e o método GetInfo é fundamental para implementar uma Query genérica.
  • O uso de uma Query genérica pode melhorar a eficácia do desenvolvimento.
  • O uso de uma Query genérica pode solucionar o problema de adaptação dos dados.

Acima estão alguns dos meus entendimentos sobre Query. Devido à minha compreensão limitada, comentários e outros contatos são bem-vindos.

Se uma boa ideia tiver o uso banido no futuro só porque alguém pensou nela primeiro, a sociedade inteira precisará fazer muito mais desvios, algo que o espírito do software gratuito sempre expressou.                                                                                                                                - Richard Matthew Stallman

Discussão (0)1
Entre ou crie uma conta para continuar