├── .gitignore ├── LICENSE ├── README.md ├── dados ├── GDIC_TOTVS_RM_2306_113.XLSX ├── GDIC_TOTVS_RM_2306_113.pkl ├── GDIC_TOTVS_RM_2402_105.XLSX ├── GDIC_TOTVS_RM_2402_105.pkl ├── GLINKSREL_TOTVS_RM_2306_113.XLSX ├── GLINKSREL_TOTVS_RM_2306_113.pkl ├── GLINKSREL_TOTVS_RM_2402_105.XLSX ├── GLINKSREL_TOTVS_RM_2402_105.pkl ├── relacoes_2306_113.json ├── relacoes_2402_105.json ├── relacoes_unicas_2306_113.pkl ├── relacoes_unicas_2402_105.pkl ├── tabelas_2306_113.json └── tabelas_2402_105.json ├── index.html ├── notebook.ipynb ├── requirements.txt ├── to_json.ipynb └── web ├── favicon.ico ├── favicon.png ├── graphology.js ├── graphology.min.js ├── sql.js └── style.css /.gitignore: -------------------------------------------------------------------------------- 1 | consultas_geradas/ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Geração de Consultas SQL a partir de Tabelas e Relacionamentos do TOTVS RM 2 | 3 | ## [Versão online](https://vitorgt.github.io/TOTVS-RM-SQL) 4 | 5 | ## Introdução 6 | 7 | Bem-vindo ao projeto de geração de consultas SQL a partir de tabelas e relacionamentos do sistema TOTVS RM! Este script em Python foi desenvolvido para simplificar a criação de consultas complexas, oferecendo uma abordagem intuitiva e visual para compreender as intricadas relações entre as tabelas do TOTVS RM. 8 | 9 | Ao contrário da abordagem convencional, onde muitas vezes criamos joins baseados em suposições, dicas e truques, este script utiliza as informações fornecidas pelos desenvolvedores do TOTVS RM. Ele automaticamente cria joins com base nas ligações declaradas entre as tabelas, proporcionando uma forma mais confiável e precisa de construir consultas SQL. 10 | 11 | Continue explorando para entender como o script organiza as relações, visualiza graficamente as conexões entre tabelas, e oferece funcionalidades para otimizar a construção de consultas SQL em ambientes que utilizam o TOTVS RM. 12 | 13 | ## Visualização de Relacionamentos 14 | 15 | Através da integração da biblioteca `networkx`, o script oferece uma poderosa ferramenta de visualização de relacionamentos entre tabelas do TOTVS RM. Ao transformar as relações em um grafo, proporciona uma representação gráfica clara e intuitiva das conexões, facilitando assim a compreensão das complexas relações entre entidades. 16 | 17 | ## Verificação de Conexões Faltantes 18 | 19 | O script oferece a identificação de conexões ausentes entre as tabelas desejadas do TOTVS RM. Esta verificação não apenas destaca a existência de tabelas desconectadas, mas também fornece alternativas de caminhos para estabelecer as conexões necessárias. Essa abordagem simplifica a integração de tabelas, oferecendo opções para preencher lacunas nas relações e garantindo uma visão abrangente e coesa do sistema. 20 | 21 | ## Salvando Consultas SQL 22 | 23 | Ao final, o script salva a consulta SQL gerada em um arquivo na pasta `consultas_geradas`. 24 | 25 | ## Executando no Google Colab 26 | 27 | 1. Faça o [download desse repositório](https://github.com/vitorgt/TOTVS-RM-SQL/zipball/master/) no seu computador. 28 | 29 | 2. Clique no botão: Open In Colab 30 | 31 | 3. Copie a pasta `dados` para o ambiente do Colab. 32 | 33 | 4. Ajuste as tabelas desejadas e execute todas as células. 34 | 35 | ## Executando localmente 36 | 37 | 1. Faça o [download desse repositório](https://github.com/vitorgt/TOTVS-RM-SQL/zipball/master/) no seu computador. 38 | 39 | 2. Atente-se aos pré-requisitos abaixo. 40 | 41 | 3. Ajuste as tabelas desejadas e execute todas as células. 42 | 43 | ### Bibliotecas necessárias 44 | 45 | O script utiliza as seguintes bibliotecas Python: 46 | 47 | - `networkx`: Para manipulação e visualização de grafos. 48 | - `numpy`: Para operações numéricas e manipulação de arrays. 49 | - `pandas`: Para manipulação de dados tabulares. 50 | - `notebook`: É o motor de execução do script. 51 | 52 | Se ainda não tiver as bibliotecas necessárias instaladas, você pode instalá-las a partir do arquivo `requirements.txt`. 53 | 54 | #### Instalando as bibliotecas utilizando `pip` 55 | 56 | ```bash 57 | pip install -r requirements.txt 58 | ``` 59 | 60 | #### Instalando as bibliotecas utilizando `conda` 61 | 62 | ```bash 63 | conda install --file requirements.txt 64 | ``` 65 | 66 | ### Dados 67 | 68 | Antes de executar o script, é fundamental gerar as planilhas `GDIC.XLSX` e `GLINKSREL.XLSX` no seu sistema atual, utilizando as seguintes consultas SQL, para que o script gere consultas coerentes com a versão do seu sistema: 69 | 70 | ```sql 71 | SELECT TABELA, 72 | COLUNA, 73 | DESCRICAO 74 | FROM GDIC (NOLOCK) /* Lista tabelas do sistema, seus campos e suas descrições */ 75 | ``` 76 | 77 | ```sql 78 | SELECT MASTERTABLE, 79 | CHILDTABLE, 80 | MASTERFIELD, 81 | CHILDFIELD 82 | FROM GLINKSREL (NOLOCK) /* Lista relacionamentos entre as tabelas do sistema */ 83 | ``` 84 | 85 | ## Avisos 86 | 87 | - Utilize o script com cautela e revise as consultas geradas, especialmente quando houver mais de uma alternativa de ligação entre tabelas. 88 | - Nem sempre todas as ligações fazem sentido em todos os contextos. 89 | - Avalie cuidadosamente as opções apresentadas para garantir que correspondam às necessidades específicas da sua seleção. 90 | 91 | Sinta-se à vontade para explorar e adaptar o script de acordo com suas necessidades. 92 | -------------------------------------------------------------------------------- /dados/GDIC_TOTVS_RM_2306_113.XLSX: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitorgt/TOTVS-RM-SQL/07fa70e1846ca865520425d51caa4b7dfa37da58/dados/GDIC_TOTVS_RM_2306_113.XLSX -------------------------------------------------------------------------------- /dados/GDIC_TOTVS_RM_2306_113.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitorgt/TOTVS-RM-SQL/07fa70e1846ca865520425d51caa4b7dfa37da58/dados/GDIC_TOTVS_RM_2306_113.pkl -------------------------------------------------------------------------------- /dados/GDIC_TOTVS_RM_2402_105.XLSX: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitorgt/TOTVS-RM-SQL/07fa70e1846ca865520425d51caa4b7dfa37da58/dados/GDIC_TOTVS_RM_2402_105.XLSX -------------------------------------------------------------------------------- /dados/GDIC_TOTVS_RM_2402_105.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitorgt/TOTVS-RM-SQL/07fa70e1846ca865520425d51caa4b7dfa37da58/dados/GDIC_TOTVS_RM_2402_105.pkl -------------------------------------------------------------------------------- /dados/GLINKSREL_TOTVS_RM_2306_113.XLSX: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitorgt/TOTVS-RM-SQL/07fa70e1846ca865520425d51caa4b7dfa37da58/dados/GLINKSREL_TOTVS_RM_2306_113.XLSX -------------------------------------------------------------------------------- /dados/GLINKSREL_TOTVS_RM_2306_113.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitorgt/TOTVS-RM-SQL/07fa70e1846ca865520425d51caa4b7dfa37da58/dados/GLINKSREL_TOTVS_RM_2306_113.pkl -------------------------------------------------------------------------------- /dados/GLINKSREL_TOTVS_RM_2402_105.XLSX: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitorgt/TOTVS-RM-SQL/07fa70e1846ca865520425d51caa4b7dfa37da58/dados/GLINKSREL_TOTVS_RM_2402_105.XLSX -------------------------------------------------------------------------------- /dados/GLINKSREL_TOTVS_RM_2402_105.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitorgt/TOTVS-RM-SQL/07fa70e1846ca865520425d51caa4b7dfa37da58/dados/GLINKSREL_TOTVS_RM_2402_105.pkl -------------------------------------------------------------------------------- /dados/relacoes_unicas_2306_113.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitorgt/TOTVS-RM-SQL/07fa70e1846ca865520425d51caa4b7dfa37da58/dados/relacoes_unicas_2306_113.pkl -------------------------------------------------------------------------------- /dados/relacoes_unicas_2402_105.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitorgt/TOTVS-RM-SQL/07fa70e1846ca865520425d51caa4b7dfa37da58/dados/relacoes_unicas_2402_105.pkl -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Gerador de Consulta SQL - TOTVS RM 8 | 11 | 12 | 15 | 16 | 17 | 18 |
19 |

20 | Gerador de Consulta SQL - TOTVS RM 21 | 25 |

26 |
27 | 28 |
29 |
30 |
31 |
32 |

Selecione Tabelas

33 | 34 |
35 | 40 |
    41 | 42 |
43 |
44 | 45 |
46 |

Visualização de Relações de Tabelas

47 |
48 |
49 |
50 | 51 |
52 |

Consulta SQL Gerada

53 |
54 |
55 | 56 | 57 |
58 | 59 |
60 | 64 | 68 | 72 | 76 |
77 | 78 |
79 | 82 | 86 | 90 | 94 |
95 |
96 | 97 |
98 |
99 |
100 | 101 |
102 | Consulta SQL copiada com sucesso! 103 |
104 | 107 | 108 | ❮❯ 115 | 116 | 119 | 146 | 147 | 148 | 151 | 154 | 157 | 160 | 161 | 162 | 163 | 164 | -------------------------------------------------------------------------------- /notebook.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "Esse código é um script em Python que gera consultas SQL baseado em tabelas e relações do TOTVS RM.\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": 1, 13 | "metadata": {}, 14 | "outputs": [], 15 | "source": [ 16 | "# Importações\n", 17 | "\n", 18 | "import os.path\n", 19 | "from datetime import datetime\n", 20 | "from itertools import islice\n", 21 | "\n", 22 | "# Possivelmente tem que instalar\n", 23 | "# pip install networkx numpy pandas\n", 24 | "import networkx as nx # Para manipulação e visualização de grafos\n", 25 | "import numpy as np # Para operações numéricas e manipulação de arrays\n", 26 | "import pandas as pd # Para manipulação de dados tabulares\n" 27 | ] 28 | }, 29 | { 30 | "cell_type": "code", 31 | "execution_count": 2, 32 | "metadata": {}, 33 | "outputs": [], 34 | "source": [ 35 | "# Definição de cores ANSI para formatação de saída no console\n", 36 | "\n", 37 | "\n", 38 | "class cores:\n", 39 | " END = \"\\033[0m\"\n", 40 | " BOLD = \"\\033[1m\"\n", 41 | " UNDERLINE = \"\\033[4m\"\n", 42 | " STRIKETHROUGH = \"\\033[9m\"\n", 43 | " BLACK_BG = \"\\033[40m\"\n", 44 | " BLACK_FG = \"\\033[30m\"\n", 45 | " RED_BG = \"\\033[41m\"\n", 46 | " RED_FG = \"\\033[91m\"\n", 47 | " GREEN_BG = \"\\033[42m\"\n", 48 | " GREEN_FG = \"\\033[92m\"\n", 49 | " YELLOW_FG = \"\\033[93m\"\n", 50 | " YELLOW_BG = \"\\033[43m\"\n", 51 | " BLUE_FG = \"\\033[94m\"\n", 52 | " BLUE_BG = \"\\033[44m\"\n", 53 | " PURPLE_FG = \"\\033[95m\"\n", 54 | " PURPLE_BG = \"\\033[45m\"\n", 55 | " WHITE_FG = \"\\033[97m\"\n", 56 | " WHITE_BG = \"\\033[47m\"\n" 57 | ] 58 | }, 59 | { 60 | "cell_type": "markdown", 61 | "metadata": {}, 62 | "source": [ 63 | "As planilhas `GDIC.XLSX` e `GLINKSREL.XLSX` devem ser geradas no seu sistema atual com as seguintes SQL\n", 64 | "\n", 65 | "```sql\n", 66 | "SELECT TABELA,\n", 67 | " COLUNA,\n", 68 | " DESCRICAO\n", 69 | "FROM GDIC (NOLOCK) /* Lista tabelas do sistema, seus campos e suas descrições */\n", 70 | "```\n", 71 | "\n", 72 | "```sql\n", 73 | "SELECT MASTERTABLE,\n", 74 | " CHILDTABLE,\n", 75 | " MASTERFIELD,\n", 76 | " CHILDFIELD\n", 77 | "FROM GLINKSREL (NOLOCK) /* Lista relacionamentos entre tabelas */\n", 78 | "```\n" 79 | ] 80 | }, 81 | { 82 | "cell_type": "code", 83 | "execution_count": 3, 84 | "metadata": {}, 85 | "outputs": [], 86 | "source": [ 87 | "# Leitura de Tabelas e Relacionamentos\n", 88 | "\n", 89 | "\n", 90 | "def le_arquivo_excel(caminho):\n", 91 | " # Procura pelo arquvio excel\n", 92 | " if not os.path.isfile(caminho):\n", 93 | " raise Exception(\"Arquivo não existe.\")\n", 94 | "\n", 95 | " caminho_pickle = os.path.splitext(caminho)[0] + \".pkl\"\n", 96 | "\n", 97 | " # Procura pelo arquivo pickle (excel ja processado pelo pandas)\n", 98 | " if os.path.isfile(caminho_pickle):\n", 99 | " return pd.read_pickle(caminho_pickle)\n", 100 | " else:\n", 101 | " # Se não encontrou\n", 102 | " # Processa o excel com o pandas\n", 103 | " df = pd.read_excel(io=caminho).dropna().astype(str)\n", 104 | "\n", 105 | " # Converte os valores das seguintes colunas em maiusculas\n", 106 | " for coluna in [\n", 107 | " \"TABELA\",\n", 108 | " \"COLUNA\",\n", 109 | " \"MASTERTABLE\",\n", 110 | " \"CHILDTABLE\",\n", 111 | " \"MASTERFIELD\",\n", 112 | " \"CHILDFIELD\",\n", 113 | " ]:\n", 114 | " try:\n", 115 | " df[coluna] = df[coluna].str.upper()\n", 116 | " except KeyError:\n", 117 | " pass\n", 118 | "\n", 119 | " # Substitui ';' por ',' e apaga caracteres invalidos nas seguintes colunas\n", 120 | " for coluna in [\"MASTERFIELD\", \"CHILDFIELD\"]:\n", 121 | " try:\n", 122 | " df[coluna] = df[coluna].str.replace(\";\", \",\")\n", 123 | " df[coluna] = df[coluna].str.replace(\n", 124 | " r\"[^0-9A-Z,_]\", \"\", regex=True\n", 125 | " )\n", 126 | " except KeyError:\n", 127 | " pass\n", 128 | "\n", 129 | " # Salva como arquivo pickle\n", 130 | " df.to_pickle(caminho_pickle)\n", 131 | " return df\n", 132 | "\n", 133 | "\n", 134 | "versao_rm = \"2402_105\"\n", 135 | "\n", 136 | "tabelas = le_arquivo_excel(\n", 137 | " os.path.join(os.getcwd(), \"dados\", f\"GDIC_TOTVS_RM_{versao_rm}.XLSX\")\n", 138 | ")\n", 139 | "relacoes = le_arquivo_excel(\n", 140 | " os.path.join(os.getcwd(), \"dados\", f\"GLINKSREL_TOTVS_RM_{versao_rm}.XLSX\")\n", 141 | ")\n" 142 | ] 143 | }, 144 | { 145 | "cell_type": "markdown", 146 | "metadata": {}, 147 | "source": [ 148 | "#### Organização de Relacionamentos\n", 149 | "\n", 150 | "No contexto do TOTVS RM, a tabela `GLINKSREL` desempenha um papel crucial ao armazenar informações sobre relacionamentos entre tabelas, considerando tanto a relação de ida quanto a de volta. Para ilustrar, suponha que a tabela `SALUNO` se relacione com a tabela `PPESSOA` por meio das chaves `CODPESSOA` e `CODIGO`, respectivamente. Na tabela `GLINKSREL`, essas relações seriam representadas da seguinte maneira:\n", 151 | "\n", 152 | "| `MASTERTABLE` | `CHILDTABLE` | `MASTERFIELD` | `CHILDFIELD` |\n", 153 | "| :-----------: | :----------: | :-----------: | :----------: |\n", 154 | "| `PPESSOA` | `SALUNO` | `CODIGO` | `CODPESSOA` |\n", 155 | "| `SALUNO` | `PPESSOA` | `CODPESSOA` | `CODIGO` |\n", 156 | "\n", 157 | "A função `unifica_relacoes()` é que organiza essas tabelas de relacionamento em ordem alfabética e elimina duplicatas.\n", 158 | "\n", 159 | "| `A` | `B` | `LIGACOES` |\n", 160 | "| :-------: | :------: | :---------------------: |\n", 161 | "| `PPESSOA` | `SALUNO` | (`CODIGO`, `CODPESSOA`) |\n" 162 | ] 163 | }, 164 | { 165 | "cell_type": "code", 166 | "execution_count": 4, 167 | "metadata": {}, 168 | "outputs": [], 169 | "source": [ 170 | "def unifica_relacoes():\n", 171 | " # Procura pelo arquivo pickle (ja processado pelo pandas)\n", 172 | " caminho_pickle = os.path.join(\n", 173 | " os.getcwd(), \"dados\", f\"relacoes_unicas_{versao_rm}.pkl\"\n", 174 | " )\n", 175 | " if os.path.isfile(caminho_pickle):\n", 176 | " return pd.read_pickle(caminho_pickle)\n", 177 | "\n", 178 | " # Ordena a linha e cria um dataframe\n", 179 | " relacoes_unicas = pd.DataFrame(np.sort(relacoes.iloc[:, :2]))\n", 180 | " # Remove duplicados\n", 181 | " relacoes_unicas = relacoes_unicas.drop_duplicates(ignore_index=True)\n", 182 | "\n", 183 | " # Cria nova coluna com um conjunto vazio\n", 184 | " relacoes_unicas[\"LIGACOES\"] = [set() for _ in range(len(relacoes_unicas))]\n", 185 | "\n", 186 | " for [a, b, s] in relacoes_unicas.values:\n", 187 | " # Filtra as relacoes com infos de chaves extrangeiras\n", 188 | " A = relacoes.loc[\n", 189 | " (relacoes[\"MASTERTABLE\"] == a) & (relacoes[\"CHILDTABLE\"] == b),\n", 190 | " [\"MASTERFIELD\", \"CHILDFIELD\"],\n", 191 | " ]\n", 192 | " B = relacoes.loc[\n", 193 | " (relacoes[\"MASTERTABLE\"] == b) & (relacoes[\"CHILDTABLE\"] == a),\n", 194 | " [\"CHILDFIELD\", \"MASTERFIELD\"],\n", 195 | " ]\n", 196 | "\n", 197 | " # Salva as relacoes encontradas no conjunto\n", 198 | " for [a_chaves, b_chaves] in A.values:\n", 199 | " s.add((a_chaves, b_chaves))\n", 200 | " for [a_chaves, b_chaves] in B.values:\n", 201 | " s.add((a_chaves, b_chaves))\n", 202 | "\n", 203 | " # Salva num arquivo pickle para nao ter que recalcular\n", 204 | " relacoes_unicas.to_pickle(caminho_pickle)\n", 205 | "\n", 206 | " return relacoes_unicas\n", 207 | "\n", 208 | "\n", 209 | "relacoes = unifica_relacoes()\n" 210 | ] 211 | }, 212 | { 213 | "cell_type": "code", 214 | "execution_count": 5, 215 | "metadata": {}, 216 | "outputs": [ 217 | { 218 | "name": "stdout", 219 | "output_type": "stream", 220 | "text": [ 221 | "8114 tabelas definidas.\n", 222 | "128432 colunas de tabelas definidas.\n", 223 | "16532 relações definidas entre tabelas.\n" 224 | ] 225 | } 226 | ], 227 | "source": [ 228 | "print(f\"{tabelas.iloc[:,0].drop_duplicates().shape[0]} tabelas definidas.\")\n", 229 | "print(f\"{tabelas.shape[0]} colunas de tabelas definidas.\")\n", 230 | "print(f\"{relacoes['LIGACOES'].map(len).sum()}\", end=\"\")\n", 231 | "print(\" relações definidas entre tabelas.\")\n" 232 | ] 233 | }, 234 | { 235 | "cell_type": "code", 236 | "execution_count": 6, 237 | "metadata": {}, 238 | "outputs": [ 239 | { 240 | "name": "stdout", 241 | "output_type": "stream", 242 | "text": [ 243 | "O grafo gerado tem subgrafos disconexos do principal.\n", 244 | "Subgrafos disconexos foram abandonados.\n" 245 | ] 246 | } 247 | ], 248 | "source": [ 249 | "def gera_grafo_relacoes(relacionamentos, abandona_disconexos=True):\n", 250 | " # Cria grafo a partir de uma tabela de relações\n", 251 | " grafo = nx.from_pandas_edgelist(relacionamentos, source=0, target=1)\n", 252 | "\n", 253 | " if not nx.is_connected(grafo):\n", 254 | " print(\"O grafo gerado tem subgrafos disconexos do principal.\")\n", 255 | " if abandona_disconexos:\n", 256 | " grafo = grafo.subgraph(\n", 257 | " sorted(nx.connected_components(grafo), key=len)[-1]\n", 258 | " )\n", 259 | " print(\"Subgrafos disconexos foram abandonados.\")\n", 260 | " else:\n", 261 | " s = f\"Subgrafos disconexos {cores.BOLD}não{cores.END} foram\"\n", 262 | " s += \" abandonados.\"\n", 263 | " print(s)\n", 264 | "\n", 265 | " return grafo\n", 266 | "\n", 267 | "\n", 268 | "grafo_relacoes = gera_grafo_relacoes(relacoes)\n", 269 | "# grafo_relacoes = gera_grafo_relacoes(relacoes, abandona_disconexos=False)\n" 270 | ] 271 | }, 272 | { 273 | "cell_type": "code", 274 | "execution_count": 7, 275 | "metadata": {}, 276 | "outputs": [], 277 | "source": [ 278 | "# Lista tabelas desejadas no select\n", 279 | "\n", 280 | "tabelas_desejadas = [\n", 281 | " # \"DTIPOBAIRRO\"\n", 282 | " # \"DTIPORUA\",\n", 283 | " # \"DTRBMUNICIPIOPRD\",\n", 284 | " # \"EPROFISS\",\n", 285 | " # \"FBOLETO\",\n", 286 | " \"FCFO\",\n", 287 | " # \"FCONTA\",\n", 288 | " # \"FCONVENIO\",\n", 289 | " # \"FCXA\",\n", 290 | " # \"FLAN\",\n", 291 | " # \"FLANBOLETO\",\n", 292 | " # \"FLANRATCCU\",\n", 293 | " # \"FTDO\",\n", 294 | " # \"GBANCO\",\n", 295 | " # \"GCCUSTO\",\n", 296 | " # \"GCOLIGADA\",\n", 297 | " # \"GFILIAL\",\n", 298 | " # \"GIMAGEM\",\n", 299 | " # \"GJOBX\",\n", 300 | " # \"GJOBXEXECUCAO\"\n", 301 | " # \"GMUNICIPIO\",\n", 302 | " # \"GPERFIL\",\n", 303 | " # \"GUSRPERFIL\",\n", 304 | " # \"GUSUARIO\",\n", 305 | " # \"NUSUARIOLOCALIDADE\",\n", 306 | " # \"PCODESTCIVIL\",\n", 307 | " # \"PCODNACAO\",\n", 308 | " \"PPESSOA\",\n", 309 | " \"SALUNO\",\n", 310 | " # \"SBOLSA\",\n", 311 | " # \"SBOLSAALUNO\",\n", 312 | " # \"SCONCEITO\",\n", 313 | " # \"SCONTRATO\",\n", 314 | " # \"SCURSO\",\n", 315 | " # \"SDISCGRADE\",\n", 316 | " # \"SDISCIPLINA\",\n", 317 | " # \"SETAPAS\",\n", 318 | " # \"SFILIAL\",\n", 319 | " # \"SFREQUENCIA\",\n", 320 | " # \"SGRUPOCONCEITO\",\n", 321 | " # \"SHABILITACAO\",\n", 322 | " # \"SHABILITACAOALUNO\",\n", 323 | " # \"SHABILITACAOFILIAL\",\n", 324 | " # \"SHORARIO\",\n", 325 | " # \"SHORARIOPROFESSOR\",\n", 326 | " # \"SHORARIOTURMA\",\n", 327 | " # \"SINSTITUICAO\",\n", 328 | " # \"SJUSTIFICATIVAFALTA\",\n", 329 | " # \"SLAN\",\n", 330 | " # \"SLOGPLETIVO\",\n", 331 | " # \"SMATRICPL\",\n", 332 | " # \"SMATRICULA\",\n", 333 | " # \"SMODETAPAPLETIVO\",\n", 334 | " # \"SMODPROVAPLETIVO\",\n", 335 | " # \"SMOTIVOALTMAT\",\n", 336 | " # \"SNOTAETAPA\",\n", 337 | " # \"SNOTAETAPACOMENTARIO\",\n", 338 | " # \"SNOTAS\",\n", 339 | " # \"SNOTASCOMENTARIO\",\n", 340 | " # \"SPARCELA\",\n", 341 | " # \"SPARCPLANO\",\n", 342 | " # \"SPLANOAULA\",\n", 343 | " # \"SPLANOPGTO\",\n", 344 | " # \"SPLETIVO\",\n", 345 | " # \"SPROFESSOR\",\n", 346 | " # \"SPROFESSORCOMPL\",\n", 347 | " # \"SPROFESSORTURMA\",\n", 348 | " # \"SPROVAS\",\n", 349 | " # \"SSERVICO\",\n", 350 | " # \"SSTATUS\",\n", 351 | " # \"STIPOMATRICULA\",\n", 352 | " # \"STURMA\",\n", 353 | " # \"STURMADISC\",\n", 354 | " # \"STURMADISCCOMPL\",\n", 355 | " # \"STURNO\",\n", 356 | " # \"SUSUARIOFILIAL\",\n", 357 | " # \"TMOV\",\n", 358 | " # \"TNFEMUNICIPAL\",\n", 359 | " # \"TNFEMUNICIPALHIST\",\n", 360 | " # \"TPRDCODFISCAL\",\n", 361 | " # \"TPRDFISCAL\",\n", 362 | " # \"TPRODUTO\",\n", 363 | " # \"TTBORCAMENTO\",\n", 364 | " # \"TTRBPRD\",\n", 365 | " # \"VFILIACAO\",\n", 366 | "]\n", 367 | "\n", 368 | "if len(tabelas_desejadas) == 0:\n", 369 | " raise ValueError(\"A lista tabelas_desejadas não pode estar vazia.\")\n" 370 | ] 371 | }, 372 | { 373 | "cell_type": "code", 374 | "execution_count": 8, 375 | "metadata": {}, 376 | "outputs": [ 377 | { 378 | "data": { 379 | "image/png": "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6FACAACn1rFjR5nNZvn6+iokJETDhw/X9evXjS7LrdChBAAALsFiseiDDz7Q2LFjVa5cOcXExMjf39/ostwCHUoAAOASPDw8NGzYMB06dEienp6qUaOG3nvvPVksFqNLc3kESgAA4FIqVaqkAwcOaMiQIRo9erQaNmyokydPGl2WSyNQAgAAl5MvXz7Nnj1b//znP3X06FH5+flp1apVRpflsgiUAADAZTVo0ECJiYlq1qyZunXrpq5duyopKcnoslwON+UAAAC3sGrVKr3++usqWLCgoqKi1KBBA6NLchkESgAA4DZOnjypsLAw7dq1SyNGjNC0adOUL1++R17n8vnTOmrepRvXLyufdwGVq1xPBYo8lwMVOwcCJQAAcCsWi0Vz5szR+PHj5ePjo5iYGFWqVOmB7zu8f4sWfjRRW1MP66cCqbKa/jxmskplLnupmZevBrSZKt+arXLwGzgeAiUAAHBLCQkJ6tatm44ePaqZM2dqyJAh8vC49/aS44m71X9ZW+14+ry80qRUz/uveft4cFIRLeqzWaX9Xs3Bb+A4CJQAAMBtXbt2TePGjdPcuXPVsGFDLV++XC+++OKd40vnhmnw2SilemQfJO/mlSZ5WaR5z/ZU+NAVOVC5YyFQAgAAt7djxw716tVL165d08KFC9WxY0dNnxqsCZYvJask0wOXuNet903zaKTIiTvsXLFjIVACAABIOn/+vPr3768NGzaoZ2gZRVX6yW5rL306TH2HLLfbeo6G51ACAACnsXz5cplMpjs/3t7eKl++vAYNGqTff/9dkvTvf/870zl58uRRmTJl1LNnT/30058h8cSJE5nOK1q0qDZs2CBJitrwk7QnwwdbJMVLWiJppqR3JH0gaZOkX+4qMknSR5LmSpoqabbU770VGjKg1z3fx2q1auXKlXr11VdVuHBhPfHEE6pcubKmTJmiK1eu3Pf3cOHCBXl7e8tkMunIkSOP8BvMGV5GFwAAAPCopkyZotKlS+v69evau3evFixYoK1bt+q77767c86QIUMUGBiolJQUxcbGavHixfrss89kNpv1/PPP3zmvS5cuatas2Z1/z4oeoMPeV2R5IcMHfi7poKSXJVVWekvuD0k/Snpa0l9unfeH0kOnl6SqkgpLuizpV2n+0hX6YOHyO0umpaWpa9euWrdunerWrau3335bTzzxhPbs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", 380 | "text/plain": [ 381 | "
" 382 | ] 383 | }, 384 | "metadata": {}, 385 | "output_type": "display_data" 386 | } 387 | ], 388 | "source": [ 389 | "def exibe_conexao_tabelas(\n", 390 | " tabelas_selecionadas=None,\n", 391 | " tabelas_opcionais=None,\n", 392 | " layout=None,\n", 393 | " remove_auto_referencias=False,\n", 394 | "):\n", 395 | " if tabelas_selecionadas is None:\n", 396 | " tabelas_selecionadas = tabelas_desejadas.copy()\n", 397 | "\n", 398 | " if tabelas_opcionais is not None:\n", 399 | " tabelas_selecionadas.extend(tabelas_opcionais)\n", 400 | "\n", 401 | " grafo_selecionado = nx.Graph(grafo_relacoes.subgraph(tabelas_selecionadas))\n", 402 | "\n", 403 | " if remove_auto_referencias:\n", 404 | " grafo_selecionado.remove_edges_from(\n", 405 | " nx.selfloop_edges(grafo_selecionado)\n", 406 | " )\n", 407 | "\n", 408 | " if nx.is_empty(grafo_selecionado):\n", 409 | " if len(tabelas_desejadas) == 0:\n", 410 | " raise ValueError(\"A lista tabelas_desejadas não pode estar vazia.\")\n", 411 | " raise ValueError(\"Não foi possível encontrar as tabelas_desejadas.\")\n", 412 | "\n", 413 | " elementos_conectados = list(\n", 414 | " sorted(nx.connected_components(grafo_selecionado), key=len)[-1]\n", 415 | " )\n", 416 | "\n", 417 | " layouts = {\n", 418 | " \"elastico\": nx.spring_layout,\n", 419 | " \"circular\": nx.circular_layout,\n", 420 | " \"kamada_kawai\": nx.kamada_kawai_layout,\n", 421 | " \"aleatorio\": nx.random_layout,\n", 422 | " \"planar\": nx.planar_layout, # Pode dar erro se o grafo nao for planar\n", 423 | " \"espiral\": nx.spiral_layout,\n", 424 | " }\n", 425 | " pos = layouts.get(layout, nx.kamada_kawai_layout)(grafo_selecionado)\n", 426 | "\n", 427 | " # Pinta de vermelho todas as tabelas selecionadas\n", 428 | " nx.draw(grafo_selecionado, pos=pos, with_labels=True, node_color=\"r\")\n", 429 | "\n", 430 | " # Pinta de verde os elementos conectados\n", 431 | " nx.draw_networkx_nodes(\n", 432 | " grafo_selecionado,\n", 433 | " pos=pos,\n", 434 | " nodelist=elementos_conectados,\n", 435 | " node_color=\"g\",\n", 436 | " )\n", 437 | "\n", 438 | " if len(list(grafo_selecionado)) != len(elementos_conectados):\n", 439 | " mensagem_alerta = f\"{cores.YELLOW_FG}Alerta: Existem tabelas d\"\n", 440 | " mensagem_alerta += f\"isconexas marcadas em {cores.RED_BG}verme\"\n", 441 | " mensagem_alerta += f\"lho{cores.END}{cores.YELLOW_FG} que serã\"\n", 442 | " mensagem_alerta += f\"o ignoradas!{cores.END}\\nVocê pode inclui\"\n", 443 | " mensagem_alerta += f\"r tabelas na lista de desejadas para compl\"\n", 444 | " mensagem_alerta += f\"etar a ligação\"\n", 445 | " print(mensagem_alerta)\n", 446 | "\n", 447 | " if tabelas_opcionais is not None:\n", 448 | " # Pinta de amarelo os elementos para fazer novas conexões\n", 449 | " nx.draw_networkx_nodes(\n", 450 | " grafo_selecionado,\n", 451 | " pos=pos,\n", 452 | " nodelist=tabelas_opcionais,\n", 453 | " node_color=\"y\",\n", 454 | " )\n", 455 | " mensagem_alerta = f\"\\nAs tabelas marcadas em {cores.YELLOW_BG}\"\n", 456 | " mensagem_alerta += f\"{cores.BLACK_FG}amarelo{cores.END} não e\"\n", 457 | " mensagem_alerta += f\"stão atualmente na seleção e serão ignorad\"\n", 458 | " mensagem_alerta += f\"as!\\nVocê pode inclui-las na lista de dese\"\n", 459 | " mensagem_alerta += f\"jadas para completar a ligação\"\n", 460 | " print(mensagem_alerta)\n", 461 | "\n", 462 | "\n", 463 | "exibe_conexao_tabelas()\n" 464 | ] 465 | }, 466 | { 467 | "cell_type": "code", 468 | "execution_count": 9, 469 | "metadata": {}, 470 | "outputs": [ 471 | { 472 | "name": "stdout", 473 | "output_type": "stream", 474 | "text": [ 475 | "Todos os nós estão conectados!\n" 476 | ] 477 | } 478 | ], 479 | "source": [ 480 | "def conexoes_possiveis_faltantes(tabelas_selecionadas=tabelas_desejadas):\n", 481 | " def k_menores_caminhos(grafo, origem, destino, k=3):\n", 482 | " try:\n", 483 | " return list(\n", 484 | " islice(nx.shortest_simple_paths(grafo, origem, destino), k)\n", 485 | " )\n", 486 | " except nx.NetworkXNoPath:\n", 487 | " print(f\"Não existe caminho possível entre {destino} e {origem}.\")\n", 488 | " return []\n", 489 | "\n", 490 | " grafo_selecionado = grafo_relacoes.subgraph(tabelas_selecionadas)\n", 491 | "\n", 492 | " elementos_conectados = grafo_selecionado.subgraph(\n", 493 | " list(sorted(nx.connected_components(grafo_selecionado), key=len)[-1])\n", 494 | " )\n", 495 | "\n", 496 | " elementos_desconectados = set(grafo_selecionado).difference(\n", 497 | " set(elementos_conectados)\n", 498 | " )\n", 499 | "\n", 500 | " if not elementos_desconectados:\n", 501 | " print(\"Todos os nós estão conectados!\")\n", 502 | " return\n", 503 | "\n", 504 | " mensagem_alerta = f\"{cores.YELLOW_FG}Nem todas as seguintes ligaçõe\"\n", 505 | " mensagem_alerta += f\"s podem fazer sentido para o seu contexto.\\nUs\"\n", 506 | " mensagem_alerta += f\"e extrema cautela!{cores.END}\\n\\nLicações poss\"\n", 507 | " mensagem_alerta += f\"iveis para atingir as tabelas disconexas:\"\n", 508 | " print(mensagem_alerta)\n", 509 | "\n", 510 | " elementos_conexao = set()\n", 511 | " for ec in elementos_conectados:\n", 512 | " for ed in elementos_desconectados:\n", 513 | " for caminho in k_menores_caminhos(grafo_relacoes, ec, ed):\n", 514 | " print(\" - \" + \" <-> \".join(caminho))\n", 515 | " for tabela in caminho[1:-1]:\n", 516 | " elementos_conexao.add(tabela)\n", 517 | "\n", 518 | " exibe_conexao_tabelas(\n", 519 | " tabelas_opcionais=elementos_conexao, remove_auto_referencias=True\n", 520 | " )\n", 521 | "\n", 522 | "\n", 523 | "conexoes_possiveis_faltantes()\n" 524 | ] 525 | }, 526 | { 527 | "cell_type": "code", 528 | "execution_count": 10, 529 | "metadata": {}, 530 | "outputs": [], 531 | "source": [ 532 | "def descricao(tabela, coluna=\"#\"):\n", 533 | " \"\"\"Retorna a descrição de uma tabela ou coluna.\"\"\"\n", 534 | " try:\n", 535 | " return tabelas.loc[\n", 536 | " (tabelas[\"TABELA\"] == tabela) & (tabelas[\"COLUNA\"] == coluna),\n", 537 | " [\"DESCRICAO\"],\n", 538 | " ].iloc[0, 0]\n", 539 | " except IndexError:\n", 540 | " return \"Descrição não encontrada\"\n" 541 | ] 542 | }, 543 | { 544 | "cell_type": "code", 545 | "execution_count": 11, 546 | "metadata": {}, 547 | "outputs": [ 548 | { 549 | "name": "stdout", 550 | "output_type": "stream", 551 | "text": [ 552 | "SFREQUENCIA (Freqüência do Aluno) tem relacionamento com as seguintes tabelas:\n", 553 | " - SHORARIOTURMA: Horários da Turma Disciplina\n", 554 | " - SJUSTIFICATIVAFALTA: Justificativas de faltas\n", 555 | " - SMATRICULA: Matrícula do Aluno em cada Disciplina\n" 556 | ] 557 | }, 558 | { 559 | "data": { 560 | "image/png": "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i9M333yjd955x7rt0Ucf1b///W99+eWX+ve//22ofwAgUAIwpGXLllq5cqWSk5O1Z88ehYeH65133lFgYKB2795tDVNeXl52YdGRDz74QLVr19alS5e0aNEibdu2TcWLO055pUqVyrTP9CCWHiwl6dr1K/YNk//+WjLL6d3kaD3y8pJqWP7YqHU1PVQn8/dbpkwZjR49Wm+88YZ+/fVXh5f1S5cubTP/nFi2bJlSUlLUtGlTHTlyxLq9devWMpvNBEoAhnHJG0CuKFasmFq2bKm33npLH374oVJSUhQWFnbb/bRq1UqdO3fWo48+qjVr1qhhw4YaNGiQrlxxEASzUKZMGfn4+Gjv3r3WbcU97rZv+D9JpXXzV+z0r/a3PlqkKMtfxx2O48CoUaNUtmxZTZkyxeH+unXr6vDhw0pOTna4PzvS75Vs166datWqZX398MMP2r59u/74448c9w0AEoESQB5IX/bn9OnThvpxd3fX9OnTFRMTo/fffz9HffTq1UvHjh3TDz/8IElqUKmjbYMTki5KapBhW9m/v8Y66DBZlvstyzjYl4HdOE6kn6VcvXq1fv31V7v9vXr1UmJiolasWJGt/m517NgxRUZGasSIEQoLC7N5LVu2TMWKFVNISEiO+gaAdARKADm2efNmpaWl2W2PiIiQJNWpU8fwGJ06dVKrVq307rvvWqvq3I6xY8eqZMmSGj58uOLi4lS2ZEX5lvj79GKCpHWy3DeZsRpiNVnW/9kp6cYtHe76e1st52NWLnmXypasmO05jh49WmXLltWbb75pt+/5559XpUqV9NJLL+nwYftH0c+ePWt3b2pG6Wcnx48fr8DAQJtX//791bFjR572BmAY91ACyLHg4GAlJCSob9++qlu3rpKTkxUZGally5bJ399fw4YNy5Vxxo0bp8cee0yLFy/W888/b91+6dIlLVmyxOEx6Que16xZU59//rkGDhyo+++/X08//bSqnq2omEMnlfarpERJgbJd1PxuSR0lbZJl4fM6ku6S9Jek/bLcJ1nbwaCnJdMeya9SRZt51ahRQw888IDT91emTBmNGjXK4WXvcuXKKTw8XN27d1eTJk1sKuX88ssvCg0NzbRvs9msJk2aWJ/uvlXv3r0VHBysX375Rc2aNXPaDwBkxpTm6PQCAGTD119/rbCwMEVGRurkyZNKTk5W1apV1a1bN7366qvWxc39/f3VsGFDrVu3zmlfixcv1rBhw7Rjxw7rJfN0N27cUO3algQXHR0td3d3derUSVu3bnXa363/a9u/f7+mT5+uTZs26ezZs7px44blV+rnJDlbg32vpJ9lucfyhiyhs6Gk9rL9dfyCpHlOp6IhQ4Zo8eLFkixnXGNjY+3W7rx48aL8/f116dIlzZo1S2PHjrXZf/r0ac2aNUtfffWV/vzzT7m5ualevXrq16+fRowYodKlS9v1/8svv6h58+Z67bXXHJ79lKQTJ07I399fL774oubOnev8TQBAJgiUAFxSzSF36+jnV6VGkvrlTp/uklp6l9f2f8XlTocAUEhwDyUAl7R2RoTcu8hyFvLb3OnT3U1a1Dc8dzoDgEKEQAnAJdWr9KBmTHxSmiwp6+Uxs+Wt9kNUr9KDudMZABQiXPIG4NLGremi2b8aP0U5rlkXzey1IRdmBACFD2coAbi0Wb03alaHJ1XMzXIP5O1wl1TMTZr94BDCJACXxhlKAJB08PQ2PRXeV1HnzstdUmombdP3t/Eur0V9w7nMDcDlESgBIIOdJ9ZofuRr2nzyoE4mpNjtr1zyLgVUrqdR7aapedWe+TBDACh4CJQA4MTFhDP6avtnevWNSZo65S31eGDYbVXAAQBXwT2UAOBE2ZIVVa9cVx3/UapXrithEgCcIFACAADAEAIlAAAADCFQAgAAwBACJQAAAAwhUAIAAMAQAiUAAAAMIVACAADAEAIlAAAADCFQAgAAwBACJQAAAAwhUAIAAMAQAiUAAAAMIVACAADAEAIlAAAADCFQAgAAwBACJQAAAAwhUAIAAMAQAiUAAAAMIVACAADAEAIlAAAADCFQAgAAwBACJQAAAAwhUAIAAMAQAiUAAAAMIVACAADAEAIlAAAADCFQAgAAwBACJQAAAAwhUAIAAMAQAiUAAAAMIVACAADAEAIlAAAADCFQAgAAwBACJQAAAAwhUAIAAMAQAiUAAAAMIVACAADAEAIlAAAADCFQAgAAwBACJQAAAAwhUAIAAMAQAiUAAAAMIVACAADAEAIlAAAADCFQAgAAwBACJQAAAAwhUAIAAMAQAiUAAAAMIVACAADAEAIlAAAADCFQAgAAwBACJQAAAAwhUAIAAMAQAiUAAAAMIVACAADAEAIlAAAADCFQAgAAwBACJQAAAAwhUAIAAMAQAiUAAAAMIVACAADAEAIlAAAADCFQAgAAwBACJQAAAAwhUAIAAMAQAiUAAAAMIVACAADAEAIlAAAADCFQAgAAwBACJQAAAAwhUAIAAMAQAiUAAAAMIVACAADAEAIlAAAADCFQAgAAwBACJQAAAAwhUAIAAMAQAiUAAAAMIVACAADAEAIlAAAADCFQAgAAwBACJQAAAAwhUAIAAMAQAiUAAAAMIVACAADAEAIlAAAADCFQAgAAwBACJQAAAAwhUAIAAMAQAiUAAAAMIVACAADAEAIlAAAADCFQAgAAwBACJQAAAAwhUAIAAMAQAiUAAAAMIVACAADAEAIlAAAADCFQAgAAwBACJQAAAAwhUAIAAMAQAiUAAAAMIVACAADAEAIlAAAADCFQAgAAwBACJQAAAAwhUAIAAMAQAiUAAAAMIVACAADAEAIlAAAADCFQAgAAwBACJQAAAAwhUAIAAMAQAiUAAAAMIVACAADAEAIlAAAADCFQAgAAwBACJQAAAAwhUAIAAMAQAiUAAAAMIVACAADAEAIlAAAADCFQAgAAwBACJQAAAAwhUAIAAMAQAiUAAAAMIVACAADAEAIlAAAADCFQAgAAwBACJQAAAAwhUAIAAMAQAiUAAAAMIVACAADAEAIlAAAADCFQAgAAwBACJQAAAAwhUAIAAMAQAiUAAAAMIVACAADAEAIlAAAADCFQAgAAwBACJQAAAAwhUAIAAMAQAiUAAAAMIVACAADAEAIlAAAADCFQAgAAwBACJQAAAAwhUAIAAMAQAiUAAAAMIVACAADAEAIlAAAADCFQAgAAwBACJQAAAAwhUAIAAMAQAiUAAAAMIVACAADAEAIlAAAADCFQAgAAwBACJQAAAAwhUAIAAMAQAiUAAAAMIVACAADAEAIlAAAADCFQAgAAwBACJQAAAAwhUAIAAMAQAiUAAAAMIVACAADAEAIlAAAADCFQAgAAwBACJQAAAAwhUAIAAMAQAiUAAAAMIVACAADAEAIlAAAADCFQAgAAwBACJQAAAAwhUAIAAMAQAiUAAAAMIVACAADAEAIlAAAADCFQAgAAwBACJQAAAAwhUAIAAMAQAiUAAAAMIVACAADAEAIlAAAADCFQAgAAwBACJQAAAAwhUAIAAMAQAiUAAAAMIVACAADAEAIlAAAADCFQAgAAwBACJQAAAAwhUAIAAMAQAiUAAAAMIVACAADAEAIlAAAADCFQAgAAwBACJQAAAAwhUAIAAMAQAiUAAAAMIVACAADAEAIlAAAADCFQAgAAwBACJQAAAAwhUAIAAMAQAiUAAAAMIVACAADAEAIlAAAADCFQAgAAwBACJQAAAAwhUAIAAMAQAiUAAAAMIVACAADAEAIlAAAADCFQAgAAwBACJQAAAAwhUAIAAMAQAiUAAAAMIVACAADAEAIlAAAADCFQAgAAwBACJQA4cf36FaWmRqtePSk1NVrXr1/J7ykBQIFkSktLS8vvSQBAQXH16gHFxHykuLgIJSX9ISnj/yJN8vSsrgoVusvH53mVKlU/v6YJAAUKgRIAJCUmHtPhw8N14cJGSR6SrmfS2rK/XLkuql37Y5UoUe3OTBIACigCJQCXFxOzQEeOBOvGjevKPEjeykNubh6qWfM9+fg8k1fTA4ACj0AJwKWdODFNx469arifatWmys/vlVyYEQAUPgRKAC4rJmaBDh9+Ntf6q1NngSpVejrX+gOAwoKnvAFo3759CgwMlJ+fnzw9PeXr66suXbrovffes7bx9/dXz549HR6/ZcsWmUwmLV++3G7fb7/9psGDB8vX11fFixeXj4+PHn/8cf322292bRcvXiyTyWR9eXh4yNfXV0OHDtWpU6eczv+///2vTCaTWrdu7bRNxn5NJpNKl75H3bs/p+3b7dt+/bUUECBFR9vv27dPeu01qV8/6eGHpaAgac4c6X//k37/fYQSE4/p+PHjduM5ex0/ftz6vnfu3Olw7j179pS/v38W76e0OnbsqK+++irTz/WHH36w25+WlqYqVarIZDI5/Te+ePGiPD09ZTKZdPDgQYdtALguj/yeAID8FRkZqYCAAFWtWlXPPvusKlasqL/++ktRUVGaN2+egoODc9z3ypUrNXDgQJUvX15PP/20qlWrpuPHj2vhwoVavny5li5dqr59+9od9+abb6patWpKSkpSVFSUFi9erB9++EH79++Xp6enXXuz2Sx/f3/9/PPPOnLkiGrWrOlwPl26dNGTTz6ptLQ0/fTTFC1bdlSvvCLNmCG1apWd9yO9/75UqZLUt69Uvrz055/SV19JmzdLM2akqEyZ4apZM1xffPGFzbFz5szRyZMn9c4779hs9/b2znpgJzK+nxMnTujDDz9Ur169tH79enXt2tWuvaenp0JC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", 561 | "text/plain": [ 562 | "
" 563 | ] 564 | }, 565 | "metadata": {}, 566 | "output_type": "display_data" 567 | } 568 | ], 569 | "source": [ 570 | "def exibe_vizinhos_tabela(central, layout=None, grafo=grafo_relacoes):\n", 571 | " if central not in grafo:\n", 572 | " raise ValueError(f\"A tabela {central} não foi encontrada.\")\n", 573 | "\n", 574 | " grafo_ego = nx.ego_graph(grafo, central)\n", 575 | "\n", 576 | " print(f\"{central} ({descricao(central)})\", end=\"\")\n", 577 | " print(\" tem relacionamento com as seguintes tabelas:\")\n", 578 | "\n", 579 | " vizinhos = np.sort(grafo_ego)\n", 580 | " if central not in nx.nodes_with_selfloops(grafo_ego):\n", 581 | " vizinhos = np.delete(vizinhos, vizinhos == central)\n", 582 | " [print(f\" - {v}: {descricao(v)}\") for v in vizinhos]\n", 583 | "\n", 584 | " grau = dict(grafo_ego.degree()).get(central)\n", 585 | " if grau > 60:\n", 586 | " raise ResourceWarning(f\"Não é possivel visualizar {grau} elementos.\")\n", 587 | "\n", 588 | " layouts = {\n", 589 | " \"elastico\": nx.spring_layout,\n", 590 | " \"circular\": nx.circular_layout,\n", 591 | " \"kamada_kawai\": nx.kamada_kawai_layout,\n", 592 | " \"aleatorio\": nx.random_layout,\n", 593 | " \"planar\": nx.planar_layout, # Pode dar erro se o grafo nao for planar\n", 594 | " \"espiral\": nx.spiral_layout,\n", 595 | " }\n", 596 | " pos = layouts.get(layout, nx.kamada_kawai_layout)(grafo_ego)\n", 597 | "\n", 598 | " nx.draw(grafo_ego, pos=pos, with_labels=True, node_color=\"y\")\n", 599 | " nx.draw_networkx_nodes(\n", 600 | " grafo_ego, pos=pos, nodelist=[central], node_color=\"g\"\n", 601 | " )\n", 602 | "\n", 603 | "\n", 604 | "exibe_vizinhos_tabela(\"SFREQUENCIA\")\n" 605 | ] 606 | }, 607 | { 608 | "cell_type": "code", 609 | "execution_count": 12, 610 | "metadata": {}, 611 | "outputs": [ 612 | { 613 | "name": "stdout", 614 | "output_type": "stream", 615 | "text": [ 616 | "0: SALUNO\n", 617 | "1: FCFO\n", 618 | "2: PPESSOA\n" 619 | ] 620 | } 621 | ], 622 | "source": [ 623 | "def ordena_por_dependencia(tabelas_selecionadas=tabelas_desejadas, raiz=None):\n", 624 | " grafo_selecionado = grafo_relacoes.subgraph(tabelas_selecionadas)\n", 625 | "\n", 626 | " # Se não foi especificada uma raiz, define como a tabela de maior grau\n", 627 | " if raiz is None:\n", 628 | " sem_selfloops = grafo_selecionado.copy()\n", 629 | " sem_selfloops.remove_edges_from(nx.selfloop_edges(sem_selfloops))\n", 630 | " graus = dict(sem_selfloops.degree())\n", 631 | " raiz = max(graus, key=graus.get)\n", 632 | "\n", 633 | " if raiz not in grafo_selecionado:\n", 634 | " raise ValueError(\n", 635 | " f\"A tabela {raiz} não foi encontrada na lista de seleção.\"\n", 636 | " )\n", 637 | "\n", 638 | " # Obtém os sucessores de cada nó no grafo a partir da raiz usando DFS\n", 639 | " dependencias = nx.dfs_successors(grafo_selecionado, source=raiz)\n", 640 | "\n", 641 | " ordenado = []\n", 642 | " fila = [[raiz]]\n", 643 | "\n", 644 | " # Percorre os nós do grafo em largura (BFS)\n", 645 | " while fila:\n", 646 | " sucessores = fila.pop(0)\n", 647 | " for elemento in sucessores:\n", 648 | " ordenado.append(elemento)\n", 649 | " if elemento in dependencias:\n", 650 | " # Se o nó tem sucessores, adiciona esses sucessores à fila\n", 651 | " fila.append(dependencias[elemento])\n", 652 | "\n", 653 | " max_len = len(str(len(ordenado)))\n", 654 | " for i, tabela in enumerate(ordenado):\n", 655 | " print(f\"{str(i).rjust(max_len, '0')}: {tabela}\")\n", 656 | "\n", 657 | " return ordenado\n", 658 | "\n", 659 | "\n", 660 | "# tabelas_desejadas = ordena_por_dependencia(raiz=tabelas_desejadas[0])\n", 661 | "tabelas_desejadas = ordena_por_dependencia()\n" 662 | ] 663 | }, 664 | { 665 | "cell_type": "code", 666 | "execution_count": 13, 667 | "metadata": {}, 668 | "outputs": [ 669 | { 670 | "name": "stdout", 671 | "output_type": "stream", 672 | "text": [ 673 | "SELECT\n", 674 | "\t/* Aluno */\n", 675 | "\tSALUNO.ANOINGRESSO AS SALUNO_ANOINGRESSO, /* Ano de Ingresso */\n", 676 | "\tSALUNO.ANOTACOES AS SALUNO_ANOTACOES, /* Anortações */\n", 677 | "\tSALUNO.CODAREA AS SALUNO_CODAREA, /* Código da Área */\n", 678 | "\tSALUNO.CODCARREIRA AS SALUNO_CODCARREIRA, /* Código da Carreira */\n", 679 | "\tSALUNO.CODCFO AS SALUNO_CODCFO, /* Código do Cliente/Fornecedor */\n", 680 | "\tSALUNO.CODCOLCFO AS SALUNO_CODCOLCFO, /* Código da Coligada do Cliente/Fornecedor */\n", 681 | "\tSALUNO.CODCOLIGADA AS SALUNO_CODCOLIGADA, /* Código da Coligada */\n", 682 | "\tSALUNO.CODCURSOHIST AS SALUNO_CODCURSOHIST, /* Código do Curso do Histórico */\n", 683 | "\tSALUNO.CODIDIOMA AS SALUNO_CODIDIOMA, /* Código do Idioma */\n", 684 | "\tSALUNO.CODINSTDESTINO AS SALUNO_CODINSTDESTINO, /* Código da Instituição de Destino */\n", 685 | "\tSALUNO.CODINSTORIGEM AS SALUNO_CODINSTORIGEM, /* Código da Instituição de Origem */\n", 686 | "\tSALUNO.CODPARENTCFO AS SALUNO_CODPARENTCFO, /* Código do Parentesco do Resp. Fin. */\n", 687 | "\tSALUNO.CODPARENTRACA AS SALUNO_CODPARENTRACA, /* Código do Parentesco do Resp. Acadêmico */\n", 688 | "\tSALUNO.CODPESSOA AS SALUNO_CODPESSOA, /* Código da Pessoa */\n", 689 | "\tSALUNO.CODPESSOARACA AS SALUNO_CODPESSOARACA, /* Código do Resp. Acadêmico */\n", 690 | "\tSALUNO.CODSERIEHIST AS SALUNO_CODSERIEHIST, /* Código da Série do Histórico */\n", 691 | "\tSALUNO.CODSISTEC AS SALUNO_CODSISTEC, /* Número do registro do diploma do aluno de EB no SISTEC */\n", 692 | "\tSALUNO.CODTIPOALUNO AS SALUNO_CODTIPOALUNO, /* Código do Tipo de Aluno */\n", 693 | "\tSALUNO.CODTIPOCURSO AS SALUNO_CODTIPOCURSO, /* Código do Nível de Ensino */\n", 694 | "\tSALUNO.DADOSACADEMICOSCFO AS SALUNO_DADOSACADEMICOSCFO, /* Habilita visualização dos dados acadêmicos no portal do aluno pelo responsável financeiro */\n", 695 | "\tSALUNO.IDENTIFICADOR2 AS SALUNO_IDENTIFICADOR2, /* Identificador 2 */\n", 696 | "\tSALUNO.IDENTIFICADOR3 AS SALUNO_IDENTIFICADOR3, /* Identificador 3 */\n", 697 | "\tSALUNO.OBSHIST AS SALUNO_OBSHIST, /* Observações do Histórico */\n", 698 | "\tSALUNO.PRONATEC AS SALUNO_PRONATEC, /* Se o curso do aluno de EB é PRONATEC */\n", 699 | "\tSALUNO.RA AS SALUNO_RA, /* Registro Acadêmico */\n", 700 | "\tSALUNO.RACAD AS SALUNO_RACAD, /* Registro acadêcimo Sec. Educação */\n", 701 | "\tSALUNO.UFRACAD AS SALUNO_UFRACAD, /* UF Registro acadêcimo Sec. Educação */\n", 702 | "\n", 703 | "\t/* Clientes/Fornecedores */\n", 704 | "\tFCFO.AGRUPCOB AS FCFO_AGRUPCOB, /* Agrupar Cobrança (Classis) */\n", 705 | "\tFCFO.APLICFORMULA AS FCFO_APLICFORMULA, /* Código da Aplicação da Fórmula */\n", 706 | "\tFCFO.APOSENTADOOUPENSIONISTA AS FCFO_APOSENTADOOUPENSIONISTA, /* Identifica o aposentado ou pensionista */\n", 707 | "\tFCFO.ATIVO AS FCFO_ATIVO, /* Ativo */\n", 708 | "\tFCFO.BAIRRO AS FCFO_BAIRRO, /* Bairro */\n", 709 | "\tFCFO.BAIRROENTREGA AS FCFO_BAIRROENTREGA, /* Bairro de Entrega */\n", 710 | "\tFCFO.BAIRROPGTO AS FCFO_BAIRROPGTO, /* Bairro de Pagamento */\n", 711 | "\tFCFO.CAIXAPOSTAL AS FCFO_CAIXAPOSTAL, /* Caixa Postal */\n", 712 | "\tFCFO.CAIXAPOSTALENTREGA AS FCFO_CAIXAPOSTALENTREGA, /* Caixa Postal de Entrega */\n", 713 | "\tFCFO.CAIXAPOSTALPAGAMENTO AS FCFO_CAIXAPOSTALPAGAMENTO, /* Caixa Postal de Pagamento */\n", 714 | "\tFCFO.CALCULAAVP AS FCFO_CALCULAAVP, /* Calcula AVP */\n", 715 | "\tFCFO.CAMPOALFAOP1 AS FCFO_CAMPOALFAOP1, /* Campo Alfa 1 */\n", 716 | "\tFCFO.CAMPOALFAOP2 AS FCFO_CAMPOALFAOP2, /* Campo Alfa 2 */\n", 717 | "\tFCFO.CAMPOALFAOP3 AS FCFO_CAMPOALFAOP3, /* Campo Alfa 3 */\n", 718 | "\tFCFO.CAMPOLIVRE AS FCFO_CAMPOLIVRE, /* Campo Livre */\n", 719 | "\tFCFO.CATEGORIAAUTONOMO AS FCFO_CATEGORIAAUTONOMO, /* Categoria do Autônomo */\n", 720 | "\tFCFO.CBOAUTONOMO AS FCFO_CBOAUTONOMO, /* Cód.Brasileiro de Ocupação do Autônomo */\n", 721 | "\tFCFO.CEI AS FCFO_CEI, /* CEI */\n", 722 | "\tFCFO.CEP AS FCFO_CEP, /* Cep */\n", 723 | "\tFCFO.CEPCAIXAPOSTAL AS FCFO_CEPCAIXAPOSTAL, /* CEP Caixa Postal */\n", 724 | "\tFCFO.CEPENTREGA AS FCFO_CEPENTREGA, /* Cep de Entrega */\n", 725 | "\tFCFO.CEPPGTO AS FCFO_CEPPGTO, /* Cep de Pagamento */\n", 726 | "\tFCFO.CFOIMOB AS FCFO_CFOIMOB, /* Bloqueado */\n", 727 | "\tFCFO.CGCCFO AS FCFO_CGCCFO, /* Cnpj */\n", 728 | "\tFCFO.CHAPA AS FCFO_CHAPA, /* Chapa do Funcionário */\n", 729 | "\tFCFO.CIAUTONOMO AS FCFO_CIAUTONOMO, /* Núm.Contribuinte Individual do Autônomo */\n", 730 | "\tFCFO.CIDADE AS FCFO_CIDADE, /* Cidade */\n", 731 | "\tFCFO.CIDADEENTREGA AS FCFO_CIDADEENTREGA, /* Cidade de Entrega */\n", 732 | "\tFCFO.CIDADEPGTO AS FCFO_CIDADEPGTO, /* Cidade de Pagamento */\n", 733 | "\tFCFO.CIDENTIDADE AS FCFO_CIDENTIDADE, /* Cédula de Identidade */\n", 734 | "\tFCFO.CI_ORGAO AS FCFO_CI_ORGAO, /* Órgão Emissor Ci */\n", 735 | "\tFCFO.CI_UF AS FCFO_CI_UF, /* Estado Emissor Ci */\n", 736 | "\tFCFO.CNAEPREP AS FCFO_CNAEPREP, /* CNAE Preponderante */\n", 737 | "\tFCFO.CNPJRURAL AS FCFO_CNPJRURAL, /* CNPJ para produtor rural */\n", 738 | "\tFCFO.CODCARGO AS FCFO_CODCARGO, /* Cargo */\n", 739 | "\tFCFO.CODCATEGORIAESOCIAL AS FCFO_CODCATEGORIAESOCIAL, /* Categoria e-Social */\n", 740 | "\tFCFO.CODCFO AS FCFO_CODCFO, /* Código do Cliente/Fornecedor */\n", 741 | "\tFCFO.CODCFOCOLINTEGRACAO AS FCFO_CODCFOCOLINTEGRACAO, /* Coligada do Fornecedor do Cliente */\n", 742 | "\tFCFO.CODCFOINTEGRACAO AS FCFO_CODCFOINTEGRACAO, /* Código do Fornecedor do Cliente */\n", 743 | "\tFCFO.CODCOLCFOFISCAL AS FCFO_CODCOLCFOFISCAL, /* Coligada da classificação fiscal */\n", 744 | "\tFCFO.CODCOLCHAVESESTRANG AS FCFO_CODCOLCHAVESESTRANG, /* Coligada de Tabelas Auxiliares */\n", 745 | "\tFCFO.CODCOLCONTAGER AS FCFO_CODCOLCONTAGER, /* Coligada da Conta Gerencial */\n", 746 | "\tFCFO.CODCOLCXA AS FCFO_CODCOLCXA, /* Coligada da conta/caixa */\n", 747 | "\tFCFO.CODCOLFORMULA AS FCFO_CODCOLFORMULA, /* Código Coligada da Fórmula */\n", 748 | "\tFCFO.CODCOLIGADA AS FCFO_CODCOLIGADA, /* Coligada */\n", 749 | "\tFCFO.CODCOLIGADAFILIALOBRA AS FCFO_CODCOLIGADAFILIALOBRA, /* Coligada da Filial da Obra */\n", 750 | "\tFCFO.CODCOLTCF AS FCFO_CODCOLTCF, /* Coligada do Tipo de Cli/For */\n", 751 | "\tFCFO.CODCONTAGER AS FCFO_CODCONTAGER, /* Código da Conta Gerencial */\n", 752 | "\tFCFO.CODCXA AS FCFO_CODCXA, /* Código da conta/caixa */\n", 753 | "\tFCFO.CODETD AS FCFO_CODETD, /* Estado */\n", 754 | "\tFCFO.CODETDENTREGA AS FCFO_CODETDENTREGA, /* Estado de Entrega */\n", 755 | "\tFCFO.CODETDPGTO AS FCFO_CODETDPGTO, /* Estado de Pagamento */\n", 756 | "\tFCFO.CODEXTERNO AS FCFO_CODEXTERNO, /* código externo de integração com protheus */\n", 757 | "\tFCFO.CODFILIALINTEGRACAO AS FCFO_CODFILIALINTEGRACAO, /* código de filial de integração */\n", 758 | "\tFCFO.CODFILIALOBRA AS FCFO_CODFILIALOBRA, /* Código da Filial da Obra */\n", 759 | "\tFCFO.CODFINALIDADE AS FCFO_CODFINALIDADE, /* Código da Finalidade do D.O.C. */\n", 760 | "\tFCFO.CODIGOCAEPF AS FCFO_CODIGOCAEPF, /* Código no Cadastro de Atividade Econômica da Pessoa Física */\n", 761 | "\tFCFO.CODIGOINSS AS FCFO_CODIGOINSS, /* Código de receita do INSS */\n", 762 | "\tFCFO.CODLOJA AS FCFO_CODLOJA, /* código da loja de integração com protheus */\n", 763 | "\tFCFO.CODMUNICIPIO AS FCFO_CODMUNICIPIO, /* Código do Município */\n", 764 | "\tFCFO.CODMUNICIPIOENTREGA AS FCFO_CODMUNICIPIOENTREGA, /* Código do Municipio de Entrega */\n", 765 | "\tFCFO.CODMUNICIPIOPGTO AS FCFO_CODMUNICIPIOPGTO, /* Código do Municipio de Pagamento */\n", 766 | "\tFCFO.CODPAGTOGPS AS FCFO_CODPAGTOGPS, /* Código de Pagamento Gps */\n", 767 | "\tFCFO.CODPROF AS FCFO_CODPROF, /* Profissão (Classis) */\n", 768 | "\tFCFO.CODRECEITA AS FCFO_CODRECEITA, /* Código da receita */\n", 769 | "\tFCFO.CODTCF AS FCFO_CODTCF, /* Tipo de Cliente/Fornecedor */\n", 770 | "\tFCFO.CODTRA AS FCFO_CODTRA, /* Transportadora */\n", 771 | "\tFCFO.CODUSUARIOACESSO AS FCFO_CODUSUARIOACESSO, /* Usuário para Acesso em Portal */\n", 772 | "\tFCFO.CODVINCULO AS FCFO_CODVINCULO, /* Vínculo Empregatício */\n", 773 | "\tFCFO.COMPLEMENTO AS FCFO_COMPLEMENTO, /* Complemento */\n", 774 | "\tFCFO.COMPLEMENTOPGTO AS FCFO_COMPLEMENTOPGTO, /* Complemento de Pagamento */\n", 775 | "\tFCFO.COMPLEMENTREGA AS FCFO_COMPLEMENTREGA, /* Complemento de Entrega */\n", 776 | "\tFCFO.CONSIDERAFILIALOBRA AS FCFO_CONSIDERAFILIALOBRA, /* Considera Dados da Filial */\n", 777 | "\tFCFO.CONTATO AS FCFO_CONTATO, /* Contato */\n", 778 | "\tFCFO.CONTATOENTREGA AS FCFO_CONTATOENTREGA, /* Contato de Entrega */\n", 779 | "\tFCFO.CONTATOPGTO AS FCFO_CONTATOPGTO, /* Contato de Pagamento */\n", 780 | "\tFCFO.CONTEVENTOCONTAB AS FCFO_CONTEVENTOCONTAB, /* Evento Contábil */\n", 781 | "\tFCFO.CONTRIBUINTE AS FCFO_CONTRIBUINTE, /* Contribuinte */\n", 782 | "\tFCFO.CONTRIBUINTEISS AS FCFO_CONTRIBUINTEISS, /* Ident. Cli./Forn. Contribuinte ISS */\n", 783 | "\tFCFO.DATACRIACAO AS FCFO_DATACRIACAO, /* Data de Criação */\n", 784 | "\tFCFO.DATAOP1 AS FCFO_DATAOP1, /* Data Opcional 1 */\n", 785 | "\tFCFO.DATAOP2 AS FCFO_DATAOP2, /* Data Opcional 2 */\n", 786 | "\tFCFO.DATAOP3 AS FCFO_DATAOP3, /* Data Opcional 3 */\n", 787 | "\tFCFO.DATAULTALTERACAO AS FCFO_DATAULTALTERACAO, /* Data da Última Alteração */\n", 788 | "\tFCFO.DATAULTMOVIMENTO AS FCFO_DATAULTMOVIMENTO, /* Data do Último Movimento */\n", 789 | "\tFCFO.DIGVERIFICDEBAUTOMATICO AS FCFO_DIGVERIFICDEBAUTOMATICO, /* Dígito verificador do identificador de débito automático */\n", 790 | "\tFCFO.DOCUMENTOESTRANGEIRO AS FCFO_DOCUMENTOESTRANGEIRO, /* Documentos Estrangeiros */\n", 791 | "\tFCFO.DTINICATIVIDADES AS FCFO_DTINICATIVIDADES, /* Data de Início das Atividades */\n", 792 | "\tFCFO.DTNASCIMENTO AS FCFO_DTNASCIMENTO, /* Data de nascimento */\n", 793 | "\tFCFO.EMAIL AS FCFO_EMAIL, /* E-Mail */\n", 794 | "\tFCFO.EMAILENTREGA AS FCFO_EMAILENTREGA, /* E-Mail do Endereço de Entrega */\n", 795 | "\tFCFO.EMAILFISCAL AS FCFO_EMAILFISCAL, /* E-mail para envio de dados fiscais. */\n", 796 | "\tFCFO.EMAILPGTO AS FCFO_EMAILPGTO, /* E-Mail do Endereço de Pagamento */\n", 797 | "\tFCFO.EMPRESA AS FCFO_EMPRESA, /* Empresa que a pessoa trabalha */\n", 798 | "\tFCFO.ENDCOBC AS FCFO_ENDCOBC, /* Endereço de Cobrança (Classis) */\n", 799 | "\tFCFO.ENTIDADEEXECUTORAPAA AS FCFO_ENTIDADEEXECUTORAPAA, /* Entidade é executora do Programa de Aquisição de Alimentos – PAA (Sim ou Não) */\n", 800 | "\tFCFO.ESTADOCIVIL AS FCFO_ESTADOCIVIL, /* Estado Civil do Cli/For */\n", 801 | "\tFCFO.FAP AS FCFO_FAP, /* Fator Acidentário de Prevenção (FAP) */\n", 802 | "\tFCFO.FAX AS FCFO_FAX, /* Fax */\n", 803 | "\tFCFO.FAXDEDICADO AS FCFO_FAXDEDICADO, /* Fax Dedicado */\n", 804 | "\tFCFO.FAXENTREGA AS FCFO_FAXENTREGA, /* Fax do Endereço de Entrega */\n", 805 | "\tFCFO.FAXPGTO AS FCFO_FAXPGTO, /* Fax do Endereço de Pagamento */\n", 806 | "\tFCFO.FILIALFINANCEIRA AS FCFO_FILIALFINANCEIRA, /* Filial Financeira (Sistémica) */\n", 807 | "\tFCFO.FORMAPAGAMENTO AS FCFO_FORMAPAGAMENTO, /* Forma de Pagamento */\n", 808 | "\tFCFO.FORMATRIBUTACAO AS FCFO_FORMATRIBUTACAO, /* Forma de Tributação */\n", 809 | "\tFCFO.FORMULAVALDEDUCAOVARIAVEL AS FCFO_FORMULAVALDEDUCAOVARIAVEL, /* Fórmula do Valor Decução Variável */\n", 810 | "\tFCFO.IDCFO AS FCFO_IDCFO, /* ID do Cliente/Fornecedor */\n", 811 | "\tFCFO.IDCFOFISCAL AS FCFO_IDCFOFISCAL, /* Referência da Classificação Fiscal */\n", 812 | "\tFCFO.IDENTPORCNPJ AS FCFO_IDENTPORCNPJ, /* Empresa Identificada por Cnpj/Cei */\n", 813 | "\tFCFO.IDINTEGRACAO AS FCFO_IDINTEGRACAO, /* Identificador de Integração */\n", 814 | "\tFCFO.IDNATRENDIMENTO AS FCFO_IDNATRENDIMENTO, /* IDENTIFICADOR DA NATUREZA DE RENDIMENTOS PARA IRRF */\n", 815 | "\tFCFO.IDPAIS AS FCFO_IDPAIS, /* Identificador do país */\n", 816 | "\tFCFO.IDPAISENTREGA AS FCFO_IDPAISENTREGA, /* Identificador do país de entrega */\n", 817 | "\tFCFO.IDPAISPGTO AS FCFO_IDPAISPGTO, /* Identificador do país de pagamento */\n", 818 | "\tFCFO.INDNATRET AS FCFO_INDNATRET, /* Indicador de Natureza da Retenção na Fonte */\n", 819 | "\tFCFO.INOVAR_AUTO AS FCFO_INOVAR_AUTO, /* Campo para a geração da rotina Inovar Auto, implementado no Aplicativos Gestão Fiscal */\n", 820 | "\tFCFO.INSCRESTADUAL AS FCFO_INSCRESTADUAL, /* Inscrição Estadual */\n", 821 | "\tFCFO.INSCRESTADUALST AS FCFO_INSCRESTADUALST, /* Inscr. Estadual ST do Fornecedor em MG */\n", 822 | "\tFCFO.INSCRMUNICIPAL AS FCFO_INSCRMUNICIPAL, /* Inscrição Municipal */\n", 823 | "\tFCFO.ISENTOTRIBUTOS AS FCFO_ISENTOTRIBUTOS, /* Isento tributos federais */\n", 824 | "\tFCFO.LIMITECREDITO AS FCFO_LIMITECREDITO, /* Limite de Crédito */\n", 825 | "\tFCFO.LOCALIDADE AS FCFO_LOCALIDADE, /* Localidade */\n", 826 | "\tFCFO.LOCALIDADEENTREGA AS FCFO_LOCALIDADEENTREGA, /* Localidade de Entrega */\n", 827 | "\tFCFO.LOCALIDADEPGTO AS FCFO_LOCALIDADEPGTO, /* Localidade de Pagamento */\n", 828 | "\tFCFO.NACIONALIDADE AS FCFO_NACIONALIDADE, /* Nacionalidade do Cliente/Fornecedor */\n", 829 | "\tFCFO.NIF AS FCFO_NIF, /* Número de Identificação Fiscal */\n", 830 | "\tFCFO.NIT AS FCFO_NIT, /* Inscrição NIT */\n", 831 | "\tFCFO.NOME AS FCFO_NOME, /* Nome */\n", 832 | "\tFCFO.NOMEFANTASIA AS FCFO_NOMEFANTASIA, /* Nome Fantasia */\n", 833 | "\tFCFO.NUMDEPENDENTES AS FCFO_NUMDEPENDENTES, /* Número de Dependentes */\n", 834 | "\tFCFO.NUMDIASATRASO AS FCFO_NUMDIASATRASO, /* Nº Dias p/Controle de Clientes em Atraso */\n", 835 | "\tFCFO.NUMERO AS FCFO_NUMERO, /* Número */\n", 836 | "\tFCFO.NUMEROENTREGA AS FCFO_NUMEROENTREGA, /* Número de Entrega */\n", 837 | "\tFCFO.NUMEROPGTO AS FCFO_NUMEROPGTO, /* Número de Pagamento */\n", 838 | "\tFCFO.NUMFUNCIONARIOS AS FCFO_NUMFUNCIONARIOS, /* Número de Funcionários */\n", 839 | "\tFCFO.OBRAPROPRIA AS FCFO_OBRAPROPRIA, /* Obra Própria */\n", 840 | "\tFCFO.OPTANTEPELOSIMPLES AS FCFO_OPTANTEPELOSIMPLES, /* Optante pelo simples */\n", 841 | "\tFCFO.ORGAOPUBLICO AS FCFO_ORGAOPUBLICO, /* Orgão Público */\n", 842 | "\tFCFO.PAGREC AS FCFO_PAGREC, /* Cliente ou Fornecedor */\n", 843 | "\tFCFO.PAIS AS FCFO_PAIS, /* País */\n", 844 | "\tFCFO.PAISENTREGA AS FCFO_PAISENTREGA, /* País de Entrega */\n", 845 | "\tFCFO.PAISPAGTO AS FCFO_PAISPAGTO, /* País de Pagamento */\n", 846 | "\tFCFO.PATRIMONIO AS FCFO_PATRIMONIO, /* Patrimônio */\n", 847 | "\tFCFO.PERCENTACIDTRAB AS FCFO_PERCENTACIDTRAB, /* Alíquota CNAE Preponderante */\n", 848 | "\tFCFO.PESSOAFISOUJUR AS FCFO_PESSOAFISOUJUR, /* Pessoa Física ou Jurídica */\n", 849 | "\tFCFO.PORTE AS FCFO_PORTE, /* Porte da Empresa */\n", 850 | "\tFCFO.PRODUTORRURAL AS FCFO_PRODUTORRURAL, /* Produtor Rural */\n", 851 | "\tFCFO.RAMOATIV AS FCFO_RAMOATIV, /* Ramo de Atividade */\n", 852 | "\tFCFO.REGIMEISS AS FCFO_REGIMEISS, /* Regime de ISS */\n", 853 | "\tFCFO.RETENCAOISS AS FCFO_RETENCAOISS, /* Cli/For responsável pela retenção de ISS */\n", 854 | "\tFCFO.RUA AS FCFO_RUA, /* Rua */\n", 855 | "\tFCFO.RUAENTREGA AS FCFO_RUAENTREGA, /* Rua de Entrega */\n", 856 | "\tFCFO.RUAPGTO AS FCFO_RUAPGTO, /* Rua de Pagamento */\n", 857 | "\tFCFO.SATISFACAO AS FCFO_SATISFACAO, /* Nível de Satisfação do Cliente */\n", 858 | "\tFCFO.SIMBMOEDAINDEX AS FCFO_SIMBMOEDAINDEX, /* Moeda para Indexar */\n", 859 | "\tFCFO.SITUACAONIF AS FCFO_SITUACAONIF, /* Situação do NIF */\n", 860 | "\tFCFO.SOCIOCOOPERADO AS FCFO_SOCIOCOOPERADO, /* Sócio Cooperado */\n", 861 | "\tFCFO.STATUSCOTACAO AS FCFO_STATUSCOTACAO, /* Status de Cotação */\n", 862 | "\tFCFO.SUFRAMA AS FCFO_SUFRAMA, /* Inscrição no SUFRAMA */\n", 863 | "\tFCFO.TELEFONE AS FCFO_TELEFONE, /* Telefone */\n", 864 | "\tFCFO.TELEFONECOMERCIAL AS FCFO_TELEFONECOMERCIAL, /* Telefone Comercial */\n", 865 | "\tFCFO.TELEFONEENTREGA AS FCFO_TELEFONEENTREGA, /* Telefone de Entrega */\n", 866 | "\tFCFO.TELEFONEPGTO AS FCFO_TELEFONEPGTO, /* Telefone de Pagamento */\n", 867 | "\tFCFO.TELEX AS FCFO_TELEX, /* Celular */\n", 868 | "\tFCFO.TIPOBAIRRO AS FCFO_TIPOBAIRRO, /* Tipo de Bairro */\n", 869 | "\tFCFO.TIPOBAIRROENTREGA AS FCFO_TIPOBAIRROENTREGA, /* Tipo de Bairro de entrega */\n", 870 | "\tFCFO.TIPOBAIRROPGTO AS FCFO_TIPOBAIRROPGTO, /* Tipo de Bairro de pagamento */\n", 871 | "\tFCFO.TIPOCLIENTE AS FCFO_TIPOCLIENTE, /* Tipo de Cliente Fornecimento Energia Elétrica e Comunicação */\n", 872 | "\tFCFO.TIPOCONTRIBUINTEINSS AS FCFO_TIPOCONTRIBUINTEINSS, /* Tipo de Contribuinte do INSS */\n", 873 | "\tFCFO.TIPOCONTROLEPONTO AS FCFO_TIPOCONTROLEPONTO, /* Registro de Ponto */\n", 874 | "\tFCFO.TIPODOC AS FCFO_TIPODOC, /* Tipo do D.O.C. */\n", 875 | "\tFCFO.TIPOINSCRCNAB AS FCFO_TIPOINSCRCNAB, /* Tipo de Inscrição Cnab */\n", 876 | "\tFCFO.TIPOOPCOMBUSTIVEL AS FCFO_TIPOOPCOMBUSTIVEL, /* Tipo de operação com combustível */\n", 877 | "\tFCFO.TIPORENDIMENTO AS FCFO_TIPORENDIMENTO, /* Tipo de Rendimento */\n", 878 | "\tFCFO.TIPORUA AS FCFO_TIPORUA, /* Tipo de rua */\n", 879 | "\tFCFO.TIPORUAENTREGA AS FCFO_TIPORUAENTREGA, /* Tipo de rua de entrega */\n", 880 | "\tFCFO.TIPORUAPGTO AS FCFO_TIPORUAPGTO, /* Tipo de rua de pagamento */\n", 881 | "\tFCFO.TOMADORFOLHA AS FCFO_TOMADORFOLHA, /* Indica que o tomador pode ser utilizado como prestrados de serviços no FOP */\n", 882 | "\tFCFO.TPLOTACAO_OLD AS FCFO_TPLOTACAO_OLD, /* Tipo de Lotação eSocial */\n", 883 | "\tFCFO.TPTOMADOR AS FCFO_TPTOMADOR, /* Tipo de Tomador (Default) */\n", 884 | "\tFCFO.ULTIMODOCUMENTO AS FCFO_ULTIMODOCUMENTO, /* Último Documento do Cli/For */\n", 885 | "\tFCFO.USARCUMULATRETENCAOPAGAR AS FCFO_USARCUMULATRETENCAOPAGAR, /* Usar Cumulatividade de Retenções (Lei 10.925) */\n", 886 | "\tFCFO.USUARIOALTERACAO AS FCFO_USUARIOALTERACAO, /* Usuário que realizou a última alteração */\n", 887 | "\tFCFO.USUARIOCRIACAO AS FCFO_USUARIOCRIACAO, /* Código do usuário */\n", 888 | "\tFCFO.VALFRETE AS FCFO_VALFRETE, /* Valor do Frete por Fornecedor */\n", 889 | "\tFCFO.VALOROP1 AS FCFO_VALOROP1, /* Valor Opcional 1 */\n", 890 | "\tFCFO.VALOROP2 AS FCFO_VALOROP2, /* Valor Opcional 2 */\n", 891 | "\tFCFO.VALOROP3 AS FCFO_VALOROP3, /* Valor Opcional 3 */\n", 892 | "\tFCFO.VALORULTIMOLAN AS FCFO_VALORULTIMOLAN, /* Valor do Último Lançamento */\n", 893 | "\tFCFO.VROUTRASDEDUCOESIRRF AS FCFO_VROUTRASDEDUCOESIRRF, /* Val.de outras deduções para cálc.de IRRF */\n", 894 | "\n", 895 | "\t/* Pessoas */\n", 896 | "\tPPESSOA.AJUSTATAMANHOFOTO AS PPESSOA_AJUSTATAMANHOFOTO, /* Ajusta tamanho da foto? */\n", 897 | "\tPPESSOA.ALUNO AS PPESSOA_ALUNO, /* É aluno? */\n", 898 | "\tPPESSOA.ANO1EMPREGO AS PPESSOA_ANO1EMPREGO, /* Ano do Primeiro Emprego PPE */\n", 899 | "\tPPESSOA.APELIDO AS PPESSOA_APELIDO, /* Apelido */\n", 900 | "\tPPESSOA.BAIRRO AS PPESSOA_BAIRRO, /* Bairro */\n", 901 | "\tPPESSOA.BRPDH AS PPESSOA_BRPDH, /* BR-PDH */\n", 902 | "\tPPESSOA.CANDIDATO AS PPESSOA_CANDIDATO, /* É um candidato? */\n", 903 | "\tPPESSOA.CARTEIRATRAB AS PPESSOA_CARTEIRATRAB, /* Nº da Carteira de Trabalho */\n", 904 | "\tPPESSOA.CARTIDENTIDADE AS PPESSOA_CARTIDENTIDADE, /* Nº da Carteira de Identidade */\n", 905 | "\tPPESSOA.CARTMODELO19 AS PPESSOA_CARTMODELO19, /* Carta Modelo 19 */\n", 906 | "\tPPESSOA.CARTMOTORISTA AS PPESSOA_CARTMOTORISTA, /* Nº da Carteira de Motorista */\n", 907 | "\tPPESSOA.CATEGMILITAR AS PPESSOA_CATEGMILITAR, /* Categoria Militar */\n", 908 | "\tPPESSOA.CEP AS PPESSOA_CEP, /* Cep */\n", 909 | "\tPPESSOA.CERTIFRESERV AS PPESSOA_CERTIFRESERV, /* Nº do Certificado de Reservista */\n", 910 | "\tPPESSOA.CIDADE AS PPESSOA_CIDADE, /* Cidade */\n", 911 | "\tPPESSOA.CODCLASSIFTRABESTRANG AS PPESSOA_CODCLASSIFTRABESTRANG, /* Classificação do Trabalhador Estrangeiro */\n", 912 | "\tPPESSOA.CODIGO AS PPESSOA_CODIGO, /* Identificador da Pessoa */\n", 913 | "\tPPESSOA.CODMEMOOBS AS PPESSOA_CODMEMOOBS, /* Cod. Observações para o PPP */\n", 914 | "\tPPESSOA.CODMUNICIPIO AS PPESSOA_CODMUNICIPIO, /* Município (PT - Concelho) */\n", 915 | "\tPPESSOA.CODNATURALIDADE AS PPESSOA_CODNATURALIDADE, /* Código do Municipio Naturalidade */\n", 916 | "\tPPESSOA.CODOCUPACAO AS PPESSOA_CODOCUPACAO, /* Código da Ocupação */\n", 917 | "\tPPESSOA.CODPROFISSAO AS PPESSOA_CODPROFISSAO, /* Código da Profissão */\n", 918 | "\tPPESSOA.CODTIPOBAIRRO AS PPESSOA_CODTIPOBAIRRO, /* Tipo do Bairro */\n", 919 | "\tPPESSOA.CODTIPORUA AS PPESSOA_CODTIPORUA, /* Tipo da Rua */\n", 920 | "\tPPESSOA.CODUSUARIO AS PPESSOA_CODUSUARIO, /* Código do Usuário */\n", 921 | "\tPPESSOA.COMPLEMENTO AS PPESSOA_COMPLEMENTO, /* Complemento */\n", 922 | "\tPPESSOA.CONJUGEBRASIL AS PPESSOA_CONJUGEBRASIL, /* Cônjuge no Brasil */\n", 923 | "\tPPESSOA.CONJUGE_SGI AS PPESSOA_CONJUGE_SGI, /* Fiador no Totvs Incorporação */\n", 924 | "\tPPESSOA.CORRACA AS PPESSOA_CORRACA, /* Cor / Raça */\n", 925 | "\tPPESSOA.CPF AS PPESSOA_CPF, /* CPF */\n", 926 | "\tPPESSOA.CSM AS PPESSOA_CSM, /* Circunscrição do Serviço Militar */\n", 927 | "\tPPESSOA.DATAAPROVACAOCURR AS PPESSOA_DATAAPROVACAOCURR, /* Data da aprovação do currículo */\n", 928 | "\tPPESSOA.DATACHEGADA AS PPESSOA_DATACHEGADA, /* Data de Chegada ao Brasil */\n", 929 | "\tPPESSOA.DATANATURALIZACAO AS PPESSOA_DATANATURALIZACAO, /* Data de naturalização do estrangeiro no brasil */\n", 930 | "\tPPESSOA.DATAOBITO AS PPESSOA_DATAOBITO, /* Data do Óbito */\n", 931 | "\tPPESSOA.DATAPRIMEIRACNH AS PPESSOA_DATAPRIMEIRACNH, /* Data da Primeira Carteira de Motorista */\n", 932 | "\tPPESSOA.DEFICIENTEAUDITIVO AS PPESSOA_DEFICIENTEAUDITIVO, /* Deficiente Auditivo */\n", 933 | "\tPPESSOA.DEFICIENTEFALA AS PPESSOA_DEFICIENTEFALA, /* Deficiente da Fala */\n", 934 | "\tPPESSOA.DEFICIENTEFISICO AS PPESSOA_DEFICIENTEFISICO, /* Deficiente Físico */\n", 935 | "\tPPESSOA.DEFICIENTEINTELECTUAL AS PPESSOA_DEFICIENTEINTELECTUAL, /* Deficiente intelectual */\n", 936 | "\tPPESSOA.DEFICIENTEMENTAL AS PPESSOA_DEFICIENTEMENTAL, /* Deficiente Mental */\n", 937 | "\tPPESSOA.DEFICIENTEMOBREDUZIDA AS PPESSOA_DEFICIENTEMOBREDUZIDA, /* Deficiente Mobilidade Reduzida */\n", 938 | "\tPPESSOA.DEFICIENTEOBSERVACAO AS PPESSOA_DEFICIENTEOBSERVACAO, /* Observação deficiência */\n", 939 | "\tPPESSOA.DEFICIENTEVISUAL AS PPESSOA_DEFICIENTEVISUAL, /* Deficiente Visual */\n", 940 | "\tPPESSOA.DTCARTTRAB AS PPESSOA_DTCARTTRAB, /* Data de Emissão da Carteira de Trabalho */\n", 941 | "\tPPESSOA.DTEMISSAOCNH AS PPESSOA_DTEMISSAOCNH, /* Data de emissão do registro único de cadastro */\n", 942 | "\tPPESSOA.DTEMISSAOIDENT AS PPESSOA_DTEMISSAOIDENT, /* Data de Emissão da Identidade */\n", 943 | "\tPPESSOA.DTEMISSAORIC AS PPESSOA_DTEMISSAORIC, /* Data de emissão do registro único de cadastro */\n", 944 | "\tPPESSOA.DTEMISSAORNE AS PPESSOA_DTEMISSAORNE, /* Data de emissão do registro nacional de estrangeiros */\n", 945 | "\tPPESSOA.DTEMISSPASSAPORTE AS PPESSOA_DTEMISSPASSAPORTE, /* Data Emissão Passaporte */\n", 946 | "\tPPESSOA.DTEXPCML AS PPESSOA_DTEXPCML, /* Data de Emissão do Certificado Militar */\n", 947 | "\tPPESSOA.DTNASCIMENTO AS PPESSOA_DTNASCIMENTO, /* Data de Nascimento */\n", 948 | "\tPPESSOA.DTTITELEITOR AS PPESSOA_DTTITELEITOR, /* Data de emissão do Título de Eleitoral */\n", 949 | "\tPPESSOA.DTVALPASSAPORTE AS PPESSOA_DTVALPASSAPORTE, /* Data Validade Passaporte */\n", 950 | "\tPPESSOA.DTVENCCARTTRAB AS PPESSOA_DTVENCCARTTRAB, /* Data de Vencimento da Cart. de Trabalho */\n", 951 | "\tPPESSOA.DTVENCHABILIT AS PPESSOA_DTVENCHABILIT, /* Data de Vencimento da Habilitação */\n", 952 | "\tPPESSOA.DTVENCIDENT AS PPESSOA_DTVENCIDENT, /* Data de Vencimento da Identidade */\n", 953 | "\tPPESSOA.DTVENCIDENTPT AS PPESSOA_DTVENCIDENTPT, /* Data de Vencimento do Documento de Identidade */\n", 954 | "\tPPESSOA.EMAIL AS PPESSOA_EMAIL, /* E-Mail */\n", 955 | "\tPPESSOA.EMAILPESSOAL AS PPESSOA_EMAILPESSOAL, /* E-Mail pessoal */\n", 956 | "\tPPESSOA.EMPRESA AS PPESSOA_EMPRESA, /* Empresa que a pessoa trabalha */\n", 957 | "\tPPESSOA.ESTADO AS PPESSOA_ESTADO, /* Unidade da Federação */\n", 958 | "\tPPESSOA.ESTADOCIVIL AS PPESSOA_ESTADOCIVIL, /* Estado Civil */\n", 959 | "\tPPESSOA.ESTADONATAL AS PPESSOA_ESTADONATAL, /* Estado Natal */\n", 960 | "\tPPESSOA.ESTELEIT AS PPESSOA_ESTELEIT, /* Uf do Título Eleitoral */\n", 961 | "\tPPESSOA.EXFUNCIONARIO AS PPESSOA_EXFUNCIONARIO, /* É um ex-funcionário? */\n", 962 | "\tPPESSOA.EXPED AS PPESSOA_EXPED, /* Órgão Expedidor do Certificado Militar */\n", 963 | "\tPPESSOA.FALECIDO AS PPESSOA_FALECIDO, /* Falecido */\n", 964 | "\tPPESSOA.FAX AS PPESSOA_FAX, /* Fax */\n", 965 | "\tPPESSOA.FIADOR_SGI AS PPESSOA_FIADOR_SGI, /* Fiador no Totvs Incorporação */\n", 966 | "\tPPESSOA.FILHOSBRASIL AS PPESSOA_FILHOSBRASIL, /* Filhos no Brasil */\n", 967 | "\tPPESSOA.FUMANTE AS PPESSOA_FUMANTE, /* Fumante */\n", 968 | "\tPPESSOA.FUNCIONARIO AS PPESSOA_FUNCIONARIO, /* É funcionário? */\n", 969 | "\tPPESSOA.GRAUINSTRUCAO AS PPESSOA_GRAUINSTRUCAO, /* Grau de Instrução */\n", 970 | "\tPPESSOA.IDBIOMETRIA AS PPESSOA_IDBIOMETRIA, /* Identificador da biometria */\n", 971 | "\tPPESSOA.IDIMAGEM AS PPESSOA_IDIMAGEM, /* Identificador da Imagem */\n", 972 | "\tPPESSOA.IDIMAGEMDOC AS PPESSOA_IDIMAGEMDOC, /* ID Imagem do documento */\n", 973 | "\tPPESSOA.IDIMAGEMDOCV AS PPESSOA_IDIMAGEMDOCV, /* ID Imagem do verso do documento */\n", 974 | "\tPPESSOA.IDPAIS AS PPESSOA_IDPAIS, /* Código do País de Endereço */\n", 975 | "\tPPESSOA.INVESTTREINANT AS PPESSOA_INVESTTREINANT, /* Investimento em Treinamentos Anteriores */\n", 976 | "\tPPESSOA.LOCALIDADE AS PPESSOA_LOCALIDADE, /* Localidade */\n", 977 | "\tPPESSOA.MATRICULAOBITO AS PPESSOA_MATRICULAOBITO, /* Matrícula da Certidão de Óbito */\n", 978 | "\tPPESSOA.MUDOUCPF AS PPESSOA_MUDOUCPF, /* MUDOU CPF */\n", 979 | "\tPPESSOA.NACIONALIDADE AS PPESSOA_NACIONALIDADE, /* Nacionalidade */\n", 980 | "\tPPESSOA.NATURALIDADE AS PPESSOA_NATURALIDADE, /* Naturalidade */\n", 981 | "\tPPESSOA.NATURALIZADO AS PPESSOA_NATURALIZADO, /* Naturalizado */\n", 982 | "\tPPESSOA.NIT AS PPESSOA_NIT, /* Tipo de Carteira de Trabalho */\n", 983 | "\tPPESSOA.NOME AS PPESSOA_NOME, /* Nome */\n", 984 | "\tPPESSOA.NOMESOCIAL AS PPESSOA_NOMESOCIAL, /* Nome Social */\n", 985 | "\tPPESSOA.NPASSAPORTE AS PPESSOA_NPASSAPORTE, /* N. Passaporte */\n", 986 | "\tPPESSOA.NRODECRETO AS PPESSOA_NRODECRETO, /* Número do Decreto de Imigração */\n", 987 | "\tPPESSOA.NROFILHOSBRASIL AS PPESSOA_NROFILHOSBRASIL, /* Número de Filhos no Brasil */\n", 988 | "\tPPESSOA.NROREGGERAL AS PPESSOA_NROREGGERAL, /* Número do Registro Geral */\n", 989 | "\tPPESSOA.NUMERO AS PPESSOA_NUMERO, /* Número */\n", 990 | "\tPPESSOA.NUMERORIC AS PPESSOA_NUMERORIC, /* Numero do registro único de cadastro */\n", 991 | "\tPPESSOA.OBSPESSOA AS PPESSOA_OBSPESSOA, /* Observações da pessoa */\n", 992 | "\tPPESSOA.ORGEMISSORCNH AS PPESSOA_ORGEMISSORCNH, /* Orgão emissor da carteira habilitação */\n", 993 | "\tPPESSOA.ORGEMISSORIDENT AS PPESSOA_ORGEMISSORIDENT, /* Órgão Emissor da Identidade */\n", 994 | "\tPPESSOA.ORGEMISSORRIC AS PPESSOA_ORGEMISSORRIC, /* Orgão emissor do registro único de cadastro */\n", 995 | "\tPPESSOA.ORGEMISSORRNE AS PPESSOA_ORGEMISSORRNE, /* Orgão emissor do registro nacional de estrangeiros */\n", 996 | "\tPPESSOA.PAIS AS PPESSOA_PAIS, /* Nome do País */\n", 997 | "\tPPESSOA.PAISORIGEM AS PPESSOA_PAISORIGEM, /* Pais Origem */\n", 998 | "\tPPESSOA.PORTARIANATURALIZACAO AS PPESSOA_PORTARIANATURALIZACAO, /* Portaria de Naturalização */\n", 999 | "\tPPESSOA.PROFESSOR AS PPESSOA_PROFESSOR, /* É professor? */\n", 1000 | "\tPPESSOA.RECURSOACESSIBILIDADE AS PPESSOA_RECURSOACESSIBILIDADE, /* Recursos p/ acessibil.ao local de trab. */\n", 1001 | "\tPPESSOA.RECURSOREALIZACAOTRAB AS PPESSOA_RECURSOREALIZACAOTRAB, /* Recursos para realização do trabalho */\n", 1002 | "\tPPESSOA.REGISTROPRELIMINAR AS PPESSOA_REGISTROPRELIMINAR, /* FLAG QUE INDICA QUE A PESSOA TEM UM REGISTRO PRELIMINAR DE FUNCIONÁRIO */\n", 1003 | "\tPPESSOA.REGPROFISSIONAL AS PPESSOA_REGPROFISSIONAL, /* Registro Profissional */\n", 1004 | "\tPPESSOA.RM AS PPESSOA_RM, /* Região Militar */\n", 1005 | "\tPPESSOA.RUA AS PPESSOA_RUA, /* Rua */\n", 1006 | "\tPPESSOA.SECAOTITELEITOR AS PPESSOA_SECAOTITELEITOR, /* Seção de Votação */\n", 1007 | "\tPPESSOA.SERIECARTTRAB AS PPESSOA_SERIECARTTRAB, /* Série da Carteira de Trabalho */\n", 1008 | "\tPPESSOA.SEXO AS PPESSOA_SEXO, /* Sexo */\n", 1009 | "\tPPESSOA.SITMILITAR AS PPESSOA_SITMILITAR, /* Situação Militar */\n", 1010 | "\tPPESSOA.TAGSCRIPT AS PPESSOA_TAGSCRIPT, /* Campo com tags de busca para os sistemas */\n", 1011 | "\tPPESSOA.TELEFONE1 AS PPESSOA_TELEFONE1, /* Telefone para contato (Opção I) */\n", 1012 | "\tPPESSOA.TELEFONE2 AS PPESSOA_TELEFONE2, /* Telefone para contato (Opção II) */\n", 1013 | "\tPPESSOA.TELEFONE3 AS PPESSOA_TELEFONE3, /* Telefone para contato (Opção III) */\n", 1014 | "\tPPESSOA.TIPOCARTHABILIT AS PPESSOA_TIPOCARTHABILIT, /* Tipo de Carteira de Habilitação */\n", 1015 | "\tPPESSOA.TIPOPRAZORESIDENCIA AS PPESSOA_TIPOPRAZORESIDENCIA, /* Tipo de prazo de residência do trabalhador imigrante */\n", 1016 | "\tPPESSOA.TIPOSANG AS PPESSOA_TIPOSANG, /* Tipo Sanguíneo */\n", 1017 | "\tPPESSOA.TIPOVISTO AS PPESSOA_TIPOVISTO, /* Tipo de Visto */\n", 1018 | "\tPPESSOA.TITULOELEITOR AS PPESSOA_TITULOELEITOR, /* Título de Eleitor */\n", 1019 | "\tPPESSOA.UFCARTIDENT AS PPESSOA_UFCARTIDENT, /* Uf da Carteira de Identidade */\n", 1020 | "\tPPESSOA.UFCARTTRAB AS PPESSOA_UFCARTTRAB, /* Uf da Carteira de Trabalho */\n", 1021 | "\tPPESSOA.UFCNH AS PPESSOA_UFCNH, /* UF de Eemissão da Carteira de Motorista */\n", 1022 | "\tPPESSOA.USUARIOBIBLIOS AS PPESSOA_USUARIOBIBLIOS, /* É um usuário do Biblios? */\n", 1023 | "\tPPESSOA.ZONATITELEITOR AS PPESSOA_ZONATITELEITOR /* Zona de Votação */\n", 1024 | "\n" 1025 | ] 1026 | } 1027 | ], 1028 | "source": [ 1029 | "def compoe_select(tabelas_selecionadas=tabelas_desejadas, descricoes=True):\n", 1030 | " def escreve_colunas_tabela(tabela, ultima_tabela=False):\n", 1031 | " # Obtém as informações sobre as colunas da tabela no DataFrame `tabelas`\n", 1032 | " t = tabelas[tabelas[\"TABELA\"] == tabela]\n", 1033 | "\n", 1034 | " s = \"\"\n", 1035 | " if descricoes:\n", 1036 | " s = f\"\\t/* {descricao(tabela)} */\\n\"\n", 1037 | "\n", 1038 | " # Remove colunas específicas que não são relevantes para a seleção\n", 1039 | " descartar = [\n", 1040 | " \"IDFT\", # Índice full-text, inútil\n", 1041 | " \"#\", # Descrição da tabela\n", 1042 | " \"RECCREATEDBY\", # Registro criado por\n", 1043 | " \"RECCREATEDON\", # Registro criado em\n", 1044 | " \"RECMODIFIEDBY\", # Última modificação do registro por\n", 1045 | " \"RECMODIFIEDON\", # Última modificação do registro em\n", 1046 | " ]\n", 1047 | " t = t.loc[~t[\"COLUNA\"].isin(descartar)].sort_values(by=[\"COLUNA\"])\n", 1048 | "\n", 1049 | " # Determina o maior comprimento das str das colunas para alinhamento\n", 1050 | " max_len = len(f\"\\t{t.iloc[0,0]}.{max(t['COLUNA'], key=len)}\")\n", 1051 | "\n", 1052 | " ultima_coluna = len(t) - 1\n", 1053 | " for i, (_, linha) in enumerate(t.iterrows()):\n", 1054 | " # Gera a cláusula de seleção para cada coluna da tabela\n", 1055 | " s += f\"\\t{linha['TABELA']}.{linha['COLUNA']}\".ljust(max_len, \" \")\n", 1056 | " s += f\" AS {linha['TABELA']}_{linha['COLUNA']}\"\n", 1057 | " s += \"\" if ultima_tabela and i == ultima_coluna else \",\"\n", 1058 | " s += f\" /* {linha['DESCRICAO']} */\\n\" if descricoes else \"\\n\"\n", 1059 | " return s\n", 1060 | "\n", 1061 | " # Combina as cláusulas de seleção para cada tabela em uma string completa\n", 1062 | " s = \"SELECT\\n\"\n", 1063 | " s += \"\\n\".join(map(escreve_colunas_tabela, tabelas_selecionadas[:-1]))\n", 1064 | " s += \"\\n\" + escreve_colunas_tabela(tabelas_selecionadas[-1], True)\n", 1065 | " return s\n", 1066 | "\n", 1067 | "\n", 1068 | "clausula_select = compoe_select()\n", 1069 | "print(clausula_select)\n" 1070 | ] 1071 | }, 1072 | { 1073 | "cell_type": "code", 1074 | "execution_count": 14, 1075 | "metadata": {}, 1076 | "outputs": [ 1077 | { 1078 | "name": "stdout", 1079 | "output_type": "stream", 1080 | "text": [ 1081 | "FROM SALUNO (NOLOCK) /* Aluno */\n", 1082 | "\tLEFT JOIN FCFO (NOLOCK) /* Clientes/Fornecedores */\n", 1083 | "\t\t/* Relações de chaves entre SALUNO e FCFO */\n", 1084 | "\t\t ON SALUNO.CODCOLCFO = FCFO.CODCOLIGADA /* Código da Coligada do Cliente/Fornecedor - Coligada */\n", 1085 | "\t\tAND SALUNO.CODCFO = FCFO.CODCFO /* Código do Cliente/Fornecedor - Código do Cliente/Fornecedor */\n", 1086 | "\t\t/* Relações de chaves entre FCFO e FCFO */\n", 1087 | "\t\t/* Existem 2 relações entre as tabelas FCFO e FCFO */\n", 1088 | "\t\t/* Provavelmente você deve escolher apenas uma */\n", 1089 | "\t\t/* Alternativa 1 */\n", 1090 | "\t\t ON FCFO.CODCFOCOLINTEGRACAO = FCFO.CODCOLIGADA /* Coligada do Fornecedor do Cliente - Coligada */\n", 1091 | "\t\tAND FCFO.CODCFOINTEGRACAO = FCFO.CODCFO /* Código do Fornecedor do Cliente - Código do Cliente/Fornecedor */\n", 1092 | "\t\t/* Alternativa 2 */\n", 1093 | "\t\t ON FCFO.CODCOLIGADA = FCFO.CODCFOCOLINTEGRACAO /* Coligada - Coligada do Fornecedor do Cliente */\n", 1094 | "\t\tAND FCFO.CODCFO = FCFO.CODCFOINTEGRACAO /* Código do Cliente/Fornecedor - Código do Fornecedor do Cliente */\n", 1095 | "\t\t/* Fim alternativas */\n", 1096 | "\tLEFT JOIN PPESSOA (NOLOCK) /* Pessoas */\n", 1097 | "\t\t/* Relações de chaves entre SALUNO e PPESSOA */\n", 1098 | "\t\t/* Existem 2 relações entre as tabelas SALUNO e PPESSOA */\n", 1099 | "\t\t/* Provavelmente você deve escolher apenas uma */\n", 1100 | "\t\t/* Alternativa 1 */\n", 1101 | "\t\t ON SALUNO.CODPESSOA = PPESSOA.CODIGO /* Código da Pessoa - Identificador da Pessoa */\n", 1102 | "\t\t/* Alternativa 2 */\n", 1103 | "\t\t ON SALUNO.CODPESSOARACA = PPESSOA.CODIGO /* Código do Resp. Acadêmico - Identificador da Pessoa */\n", 1104 | "\t\t/* Fim alternativas */\n", 1105 | "\n" 1106 | ] 1107 | } 1108 | ], 1109 | "source": [ 1110 | "def compoe_join(\n", 1111 | " tabelas_selecionadas=tabelas_desejadas, tipo=\"LEFT\", descricoes=True\n", 1112 | "):\n", 1113 | " def escreve_correspondencia_chaves(tabela_origem, tabela_destino):\n", 1114 | " try:\n", 1115 | " # Obtém as ligações entre as tabelas a partir do DataFrame `relacoes`\n", 1116 | " ligacoes = relacoes.loc[\n", 1117 | " (relacoes[0] == min(tabela_origem, tabela_destino))\n", 1118 | " & (relacoes[1] == max(tabela_origem, tabela_destino)),\n", 1119 | " [\"LIGACOES\"],\n", 1120 | " ].iloc[0, 0]\n", 1121 | " except IndexError:\n", 1122 | " # Se não houver ligações definidas, retorna uma string vazia\n", 1123 | " return \"\"\n", 1124 | "\n", 1125 | " s = f\"\\t\\t/* Relações de chaves entre {tabela_origem} e {tabela_destino} */\\n\"\n", 1126 | " if len(ligacoes) > 1:\n", 1127 | " s += f\"\\t\\t/* Existem {len(ligacoes)} relações entre as tab\"\n", 1128 | " s += f\"elas {tabela_origem} e {tabela_destino} */\\n\\t\\t/* \"\n", 1129 | " s += f\"Provavelmente você deve escolher apenas uma */\\n\"\n", 1130 | " for i, (chaves_origem, chaves_destino) in enumerate(ligacoes):\n", 1131 | " # Se existe mais de uma ligação possível entre as tabelas\n", 1132 | " # selecionadas, numera elas\n", 1133 | " if len(ligacoes) > 1:\n", 1134 | " s += f\"\\t\\t/* Alternativa {i + 1} */\\n\"\n", 1135 | "\n", 1136 | " # Garante que as chaves estejam na ordem correta, já que elas foram\n", 1137 | " # ordenadas por ordem alfabetica pela função `unifica_relacoes`\n", 1138 | " if min(tabela_origem, tabela_destino) != tabela_origem:\n", 1139 | " [chaves_origem, chaves_destino] = [\n", 1140 | " chaves_destino,\n", 1141 | " chaves_origem,\n", 1142 | " ]\n", 1143 | "\n", 1144 | " chaves_origem = chaves_origem.split(\",\")\n", 1145 | " chaves_destino = chaves_destino.split(\",\")\n", 1146 | " primeiro = True\n", 1147 | " for chave_origem, chave_destino in zip(\n", 1148 | " chaves_origem, chaves_destino\n", 1149 | " ):\n", 1150 | " s += f\"\\t\\t{' ON ' if primeiro else 'AND '}\"\n", 1151 | " s += f\"{tabela_origem}.{chave_origem} = \"\n", 1152 | " s += f\"{tabela_destino}.{chave_destino}\"\n", 1153 | " if descricoes:\n", 1154 | " s += f\" /* {descricao(tabela_origem, chave_origem)} \"\n", 1155 | " s += f\"- {descricao(tabela_destino, chave_destino)} */\"\n", 1156 | " s += \"\\n\"\n", 1157 | " primeiro = False\n", 1158 | "\n", 1159 | " if len(chaves_origem) != len(chaves_destino):\n", 1160 | " s += \"\\t\\t/* Não há correspondentes para as chaves \"\n", 1161 | " s += f\"estrangeiras abaixo; por favor, avalie */\\n\"\n", 1162 | " for i in range(len(chaves_destino), len(chaves_origem)):\n", 1163 | " s += (\n", 1164 | " f\"\\t\\t/* AND {tabela_origem}.{chaves_origem[i]} = ?? */\"\n", 1165 | " )\n", 1166 | " if descricoes:\n", 1167 | " s += f\" /* {descricao(tabela_origem, chave_origem)} */\"\n", 1168 | " s += \"\\n\"\n", 1169 | " for i in range(len(chaves_origem), len(chaves_destino)):\n", 1170 | " s += f\"\\t\\t/* AND {tabela_destino}.{chaves_destino[i]} = ?? */\"\n", 1171 | " if descricoes:\n", 1172 | " s += (\n", 1173 | " f\" /* {descricao(tabela_destino, chave_destino)} */\"\n", 1174 | " )\n", 1175 | " s += \"\\n\"\n", 1176 | "\n", 1177 | " if len(ligacoes) > 1:\n", 1178 | " s += \"\\t\\t/* Fim alternativas */\\n\"\n", 1179 | "\n", 1180 | " return s\n", 1181 | "\n", 1182 | " visitadas = [tabelas_selecionadas[0]]\n", 1183 | "\n", 1184 | " # Inicializa a string de saída com a cláusula FROM da primeira tabela\n", 1185 | " s = f\"FROM {tabelas_selecionadas[0]} (NOLOCK)\"\n", 1186 | " s += (\n", 1187 | " f\" /* {descricao(tabelas_selecionadas[0])} */\\n\" if descricoes else \"\\n\"\n", 1188 | " )\n", 1189 | "\n", 1190 | " for tabela in tabelas_selecionadas[1:]:\n", 1191 | " # Adiciona a cláusula JOIN para cada tabela subsequente\n", 1192 | " visitadas.append(tabela)\n", 1193 | " s += f\"\\t{tipo} JOIN {tabela} (NOLOCK)\"\n", 1194 | " s += f\" /* {descricao(tabela)} */\\n\" if descricoes else \"\\n\"\n", 1195 | "\n", 1196 | " for visitada in visitadas:\n", 1197 | " # Adiciona as condições de correspondência entre as tabelas\n", 1198 | " s += escreve_correspondencia_chaves(visitada, tabela)\n", 1199 | "\n", 1200 | " return s\n", 1201 | "\n", 1202 | "\n", 1203 | "clausula_join = compoe_join()\n", 1204 | "print(clausula_join)\n" 1205 | ] 1206 | }, 1207 | { 1208 | "cell_type": "code", 1209 | "execution_count": 15, 1210 | "metadata": {}, 1211 | "outputs": [ 1212 | { 1213 | "name": "stdout", 1214 | "output_type": "stream", 1215 | "text": [ 1216 | "Pasta '/mnt/c/Users/vitor/Documents/TOTVS-RM-SQL/consultas_geradas' criada com sucesso.\n", 1217 | "Arquivo '/mnt/c/Users/vitor/Documents/TOTVS-RM-SQL/consultas_geradas/SALUNO-20240223171959.sql' criado com sucesso.\n" 1218 | ] 1219 | } 1220 | ], 1221 | "source": [ 1222 | "def salva_arquivo_sql():\n", 1223 | " # Obtém a data e hora atual no formato AAAAMMDDHHMMSS\n", 1224 | " agora = datetime.now().strftime(\"%Y%m%d%H%M%S\")\n", 1225 | "\n", 1226 | " nome_arquivo = tabelas_desejadas[0] + \"-\" + agora + \".sql\"\n", 1227 | "\n", 1228 | " # Define o caminho para a pasta onde os arquivos serão salvos\n", 1229 | " caminho = os.path.join(os.getcwd(), \"consultas_geradas\")\n", 1230 | "\n", 1231 | " # Verifica se a pasta existe; se não existir, a cria\n", 1232 | " if not os.path.exists(caminho):\n", 1233 | " os.makedirs(caminho)\n", 1234 | " print(f\"Pasta '{caminho}' criada com sucesso.\")\n", 1235 | "\n", 1236 | " caminho = os.path.join(caminho, nome_arquivo)\n", 1237 | "\n", 1238 | " arquivo = open(caminho, \"a\")\n", 1239 | " arquivo.write(clausula_select + clausula_join)\n", 1240 | " arquivo.close()\n", 1241 | "\n", 1242 | " print(f\"Arquivo '{caminho}' criado com sucesso.\")\n", 1243 | "\n", 1244 | "\n", 1245 | "salva_arquivo_sql()\n" 1246 | ] 1247 | } 1248 | ], 1249 | "metadata": { 1250 | "kernelspec": { 1251 | "display_name": "Python 3", 1252 | "language": "python", 1253 | "name": "python3" 1254 | }, 1255 | "language_info": { 1256 | "codemirror_mode": { 1257 | "name": "ipython", 1258 | "version": 3 1259 | }, 1260 | "file_extension": ".py", 1261 | "mimetype": "text/x-python", 1262 | "name": "python", 1263 | "nbconvert_exporter": "python", 1264 | "pygments_lexer": "ipython3", 1265 | "version": "3.8.10" 1266 | } 1267 | }, 1268 | "nbformat": 4, 1269 | "nbformat_minor": 2 1270 | } 1271 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | networkx==3.2.1 2 | notebook==7.0.6 3 | numpy==1.26.3 4 | pandas==2.1.4 5 | -------------------------------------------------------------------------------- /to_json.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "Esse código é um script em Python que gera consultas SQL baseado em tabelas e relações do TOTVS RM.\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": 1, 13 | "metadata": {}, 14 | "outputs": [], 15 | "source": [ 16 | "# Importações\n", 17 | "\n", 18 | "import json\n", 19 | "import os.path\n", 20 | "\n", 21 | "# Possivelmente tem que instalar\n", 22 | "# pip install numpy pandas\n", 23 | "import numpy as np # Para operações numéricas e manipulação de arrays\n", 24 | "import pandas as pd # Para manipulação de dados tabulares\n" 25 | ] 26 | }, 27 | { 28 | "cell_type": "markdown", 29 | "metadata": {}, 30 | "source": [ 31 | "As planilhas `GDIC.XLSX` e `GLINKSREL.XLSX` devem ser geradas no seu sistema atual com as seguintes SQL\n", 32 | "\n", 33 | "```sql\n", 34 | "SELECT TABELA,\n", 35 | " COLUNA,\n", 36 | " DESCRICAO\n", 37 | "FROM GDIC (NOLOCK) /* Lista tabelas do sistema, seus campos e suas descrições */\n", 38 | "```\n", 39 | "\n", 40 | "```sql\n", 41 | "SELECT MASTERTABLE,\n", 42 | " CHILDTABLE,\n", 43 | " MASTERFIELD,\n", 44 | " CHILDFIELD\n", 45 | "FROM GLINKSREL (NOLOCK) /* Lista relacionamentos entre tabelas */\n", 46 | "```\n" 47 | ] 48 | }, 49 | { 50 | "cell_type": "code", 51 | "execution_count": 2, 52 | "metadata": {}, 53 | "outputs": [], 54 | "source": [ 55 | "# Leitura de Tabelas e Relacionamentos\n", 56 | "\n", 57 | "\n", 58 | "def le_arquivo_excel(caminho):\n", 59 | " # Procura pelo arquvio excel\n", 60 | " if not os.path.isfile(caminho):\n", 61 | " raise Exception(\"Arquivo não existe.\")\n", 62 | "\n", 63 | " caminho_pickle = os.path.splitext(caminho)[0] + \".pkl\"\n", 64 | "\n", 65 | " # Procura pelo arquivo pickle (excel ja processado pelo pandas)\n", 66 | " if os.path.isfile(caminho_pickle):\n", 67 | " return pd.read_pickle(caminho_pickle)\n", 68 | " else:\n", 69 | " # Se não encontrou\n", 70 | " # Processa o excel com o pandas\n", 71 | " df = pd.read_excel(io=caminho).dropna().astype(str)\n", 72 | "\n", 73 | " # Converte os valores das seguintes colunas em maiusculas\n", 74 | " for coluna in [\n", 75 | " \"TABELA\",\n", 76 | " \"COLUNA\",\n", 77 | " \"MASTERTABLE\",\n", 78 | " \"CHILDTABLE\",\n", 79 | " \"MASTERFIELD\",\n", 80 | " \"CHILDFIELD\",\n", 81 | " ]:\n", 82 | " try:\n", 83 | " df[coluna] = df[coluna].str.upper()\n", 84 | " except KeyError:\n", 85 | " pass\n", 86 | "\n", 87 | " # Substitui ';' por ',' e apaga caracteres invalidos nas seguintes colunas\n", 88 | " for coluna in [\"MASTERFIELD\", \"CHILDFIELD\"]:\n", 89 | " try:\n", 90 | " df[coluna] = df[coluna].str.replace(\";\", \",\")\n", 91 | " df[coluna] = df[coluna].str.replace(\n", 92 | " r\"[^0-9A-Z,_]\", \"\", regex=True\n", 93 | " )\n", 94 | " except KeyError:\n", 95 | " pass\n", 96 | "\n", 97 | " # Salva como arquivo pickle\n", 98 | " df.to_pickle(caminho_pickle)\n", 99 | " return df\n", 100 | "\n", 101 | "\n", 102 | "versao_rm = \"2402_105\"\n", 103 | "\n", 104 | "tabelas = le_arquivo_excel(\n", 105 | " os.path.join(os.getcwd(), \"dados\", f\"GDIC_TOTVS_RM_{versao_rm}.XLSX\")\n", 106 | ")\n", 107 | "relacoes = le_arquivo_excel(\n", 108 | " os.path.join(os.getcwd(), \"dados\", f\"GLINKSREL_TOTVS_RM_{versao_rm}.XLSX\")\n", 109 | ")\n" 110 | ] 111 | }, 112 | { 113 | "cell_type": "markdown", 114 | "metadata": {}, 115 | "source": [ 116 | "#### Organização de Relacionamentos\n", 117 | "\n", 118 | "No contexto do TOTVS RM, a tabela `GLINKSREL` desempenha um papel crucial ao armazenar informações sobre relacionamentos entre tabelas, considerando tanto a relação de ida quanto a de volta. Para ilustrar, suponha que a tabela `SALUNO` se relacione com a tabela `PPESSOA` por meio das chaves `CODPESSOA` e `CODIGO`, respectivamente. Na tabela `GLINKSREL`, essas relações seriam representadas da seguinte maneira:\n", 119 | "\n", 120 | "| `MASTERTABLE` | `CHILDTABLE` | `MASTERFIELD` | `CHILDFIELD` |\n", 121 | "| :-----------: | :----------: | :-----------: | :----------: |\n", 122 | "| `PPESSOA` | `SALUNO` | `CODIGO` | `CODPESSOA` |\n", 123 | "| `SALUNO` | `PPESSOA` | `CODPESSOA` | `CODIGO` |\n", 124 | "\n", 125 | "A função `unifica_relacoes()` é que organiza essas tabelas de relacionamento em ordem alfabética e elimina duplicatas.\n", 126 | "\n", 127 | "| `A` | `B` | `LIGACOES` |\n", 128 | "| :-------: | :------: | :---------------------: |\n", 129 | "| `PPESSOA` | `SALUNO` | (`CODIGO`, `CODPESSOA`) |\n" 130 | ] 131 | }, 132 | { 133 | "cell_type": "code", 134 | "execution_count": 3, 135 | "metadata": {}, 136 | "outputs": [], 137 | "source": [ 138 | "def unifica_relacoes():\n", 139 | " # Procura pelo arquivo pickle (ja processado pelo pandas)\n", 140 | " caminho_pickle = os.path.join(\n", 141 | " os.getcwd(), \"dados\", f\"relacoes_unicas_{versao_rm}.pkl\"\n", 142 | " )\n", 143 | " if os.path.isfile(caminho_pickle):\n", 144 | " return pd.read_pickle(caminho_pickle)\n", 145 | "\n", 146 | " # Ordena a linha e cria um dataframe\n", 147 | " relacoes_unicas = pd.DataFrame(np.sort(relacoes.iloc[:, :2]))\n", 148 | " # Remove duplicados\n", 149 | " relacoes_unicas = relacoes_unicas.drop_duplicates(ignore_index=True)\n", 150 | "\n", 151 | " # Cria nova coluna com um conjunto vazio\n", 152 | " relacoes_unicas[\"LIGACOES\"] = [set() for _ in range(len(relacoes_unicas))]\n", 153 | "\n", 154 | " for [a, b, s] in relacoes_unicas.values:\n", 155 | " # Filtra as relacoes com infos de chaves extrangeiras\n", 156 | " A = relacoes.loc[\n", 157 | " (relacoes[\"MASTERTABLE\"] == a) & (relacoes[\"CHILDTABLE\"] == b),\n", 158 | " [\"MASTERFIELD\", \"CHILDFIELD\"],\n", 159 | " ]\n", 160 | " B = relacoes.loc[\n", 161 | " (relacoes[\"MASTERTABLE\"] == b) & (relacoes[\"CHILDTABLE\"] == a),\n", 162 | " [\"CHILDFIELD\", \"MASTERFIELD\"],\n", 163 | " ]\n", 164 | "\n", 165 | " # Salva as relacoes encontradas no conjunto\n", 166 | " for [a_chaves, b_chaves] in A.values:\n", 167 | " s.add((a_chaves, b_chaves))\n", 168 | " for [a_chaves, b_chaves] in B.values:\n", 169 | " s.add((a_chaves, b_chaves))\n", 170 | "\n", 171 | " # Salva num arquivo pickle para nao ter que recalcular\n", 172 | " relacoes_unicas.to_pickle(caminho_pickle)\n", 173 | "\n", 174 | " return relacoes_unicas\n", 175 | "\n", 176 | "\n", 177 | "relacoes = unifica_relacoes()\n" 178 | ] 179 | }, 180 | { 181 | "cell_type": "code", 182 | "execution_count": 4, 183 | "metadata": {}, 184 | "outputs": [ 185 | { 186 | "name": "stdout", 187 | "output_type": "stream", 188 | "text": [ 189 | "8114 tabelas definidas.\n", 190 | "128432 colunas de tabelas definidas.\n", 191 | "16532 relações definidas entre tabelas.\n" 192 | ] 193 | } 194 | ], 195 | "source": [ 196 | "print(f\"{tabelas.iloc[:,0].drop_duplicates().shape[0]} tabelas definidas.\")\n", 197 | "print(f\"{tabelas.shape[0]} colunas de tabelas definidas.\")\n", 198 | "print(f\"{relacoes['LIGACOES'].map(len).sum()}\", end=\"\")\n", 199 | "print(\" relações definidas entre tabelas.\")\n" 200 | ] 201 | }, 202 | { 203 | "cell_type": "code", 204 | "execution_count": 5, 205 | "metadata": {}, 206 | "outputs": [], 207 | "source": [ 208 | "tabelas_json = {}\n", 209 | "for tabela, grupo in tabelas.groupby(\"TABELA\"):\n", 210 | " tabelas_json[tabela] = {\n", 211 | " linha[\"COLUNA\"]: linha[\"DESCRICAO\"] for _, linha in grupo.iterrows()\n", 212 | " }\n", 213 | "\n", 214 | "with open(f\"./dados/tabelas_{versao_rm}.json\", \"w\") as arquivo:\n", 215 | " json.dump(tabelas_json, arquivo, separators=(\",\", \":\"))\n" 216 | ] 217 | }, 218 | { 219 | "cell_type": "code", 220 | "execution_count": null, 221 | "metadata": {}, 222 | "outputs": [], 223 | "source": [ 224 | "relacoes[\"LIGACOES\"] = relacoes[\"LIGACOES\"].apply(list)\n", 225 | "\n", 226 | "relacoes_json = []\n", 227 | "for _, linha in relacoes.iterrows():\n", 228 | " relacao = [linha[0], linha[1], linha[\"LIGACOES\"]]\n", 229 | " relacoes_json.append(relacao)\n", 230 | "\n", 231 | "with open(f\"./dados/relacoes_{versao_rm}.json\", \"w\") as arquivo:\n", 232 | " json.dump(relacoes_json, arquivo, separators=(\",\", \":\"))\n" 233 | ] 234 | } 235 | ], 236 | "metadata": { 237 | "kernelspec": { 238 | "display_name": "Python 3", 239 | "language": "python", 240 | "name": "python3" 241 | }, 242 | "language_info": { 243 | "codemirror_mode": { 244 | "name": "ipython", 245 | "version": 3 246 | }, 247 | "file_extension": ".py", 248 | "mimetype": "text/x-python", 249 | "name": "python", 250 | "nbconvert_exporter": "python", 251 | "pygments_lexer": "ipython3", 252 | "version": "3.8.10" 253 | } 254 | }, 255 | "nbformat": 4, 256 | "nbformat_minor": 2 257 | } 258 | -------------------------------------------------------------------------------- /web/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitorgt/TOTVS-RM-SQL/07fa70e1846ca865520425d51caa4b7dfa37da58/web/favicon.ico -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitorgt/TOTVS-RM-SQL/07fa70e1846ca865520425d51caa4b7dfa37da58/web/favicon.png -------------------------------------------------------------------------------- /web/graphology.js: -------------------------------------------------------------------------------- 1 | function isGraph(value) { 2 | return ( 3 | value !== null && 4 | typeof value === "object" && 5 | typeof value.addUndirectedEdgeWithKey === "function" && 6 | typeof value.dropNode === "function" && 7 | typeof value.multi === "boolean" 8 | ) 9 | } 10 | 11 | function copyNode(graph, key, attributes) { 12 | attributes = Object.assign({}, attributes) 13 | return graph.addNode(key, attributes) 14 | } 15 | 16 | function copyEdge(graph, undirected, key, source, target, attributes) { 17 | attributes = Object.assign({}, attributes) 18 | 19 | if (undirected) { 20 | if (key === null || key === undefined) 21 | return graph.addUndirectedEdge(source, target, attributes) 22 | else return graph.addUndirectedEdgeWithKey(key, source, target, attributes) 23 | } else { 24 | if (key === null || key === undefined) 25 | return graph.addDirectedEdge(source, target, attributes) 26 | else return graph.addDirectedEdgeWithKey(key, source, target, attributes) 27 | } 28 | } 29 | 30 | function DFSStack(graph) { 31 | this.graph = graph 32 | this.stack = new Array(graph.order) 33 | this.seen = new Set() 34 | this.size = 0 35 | } 36 | 37 | DFSStack.prototype.hasAlreadySeenEverything = function () { 38 | return this.seen.size === this.graph.order 39 | } 40 | 41 | DFSStack.prototype.countUnseenNodes = function () { 42 | return this.graph.order - this.seen.size 43 | } 44 | 45 | DFSStack.prototype.forEachNodeYetUnseen = function (callback) { 46 | var seen = this.seen 47 | var graph = this.graph 48 | 49 | graph.someNode(function (node, attr) { 50 | // Useful early exit for connected graphs 51 | if (seen.size === graph.order) return true // break 52 | 53 | // Node already seen? 54 | if (seen.has(node)) return false // continue 55 | 56 | var shouldBreak = callback(node, attr) 57 | 58 | if (shouldBreak) return true 59 | 60 | return false 61 | }) 62 | } 63 | 64 | DFSStack.prototype.has = function (node) { 65 | return this.seen.has(node) 66 | } 67 | 68 | DFSStack.prototype.push = function (node) { 69 | var seenSizeBefore = this.seen.size 70 | 71 | this.seen.add(node) 72 | 73 | // If node was already seen 74 | if (seenSizeBefore === this.seen.size) return false 75 | 76 | this.stack[this.size++] = node 77 | 78 | return true 79 | } 80 | 81 | DFSStack.prototype.pushWith = function (node, item) { 82 | var seenSizeBefore = this.seen.size 83 | 84 | this.seen.add(node) 85 | 86 | // If node was already seen 87 | if (seenSizeBefore === this.seen.size) return false 88 | 89 | this.stack[this.size++] = item 90 | 91 | return true 92 | } 93 | 94 | DFSStack.prototype.pop = function () { 95 | if (this.size === 0) return 96 | 97 | return this.stack[--this.size] 98 | } 99 | 100 | /** 101 | * Function returning the largest component of the given graph. 102 | * 103 | * @param {Graph} graph - Target graph. 104 | * @return {array} 105 | */ 106 | function largestConnectedComponent(graph) { 107 | if (!isGraph(graph)) 108 | throw new Error( 109 | "graphology-components: the given graph is not a valid graphology instance.", 110 | ) 111 | 112 | if (!graph.order) return [] 113 | 114 | var stack = new DFSStack(graph) 115 | var push = stack.push.bind(stack) 116 | 117 | var largestComponent = [] 118 | var component 119 | 120 | stack.forEachNodeYetUnseen(function (node) { 121 | component = [] 122 | 123 | stack.push(node) 124 | 125 | var source 126 | 127 | while (stack.size !== 0) { 128 | source = stack.pop() 129 | 130 | component.push(source) 131 | 132 | graph.forEachNeighbor(source, push) 133 | } 134 | 135 | if (component.length > largestComponent.length) largestComponent = component 136 | 137 | // Early exit condition: 138 | // If current largest component's size is larger than the number of 139 | // remaining nodes to visit, we can safely assert we found the 140 | // overall largest component already. 141 | if (largestComponent.length > stack.countUnseenNodes()) return true 142 | 143 | return false 144 | }) 145 | 146 | return largestComponent 147 | } 148 | 149 | /** 150 | * Function returning a subgraph composed of the largest component of the given graph. 151 | * 152 | * @param {Graph} graph - Target graph. 153 | * @return {Graph} 154 | */ 155 | function largestConnectedComponentSubgraph(graph) { 156 | var component = largestConnectedComponent(graph) 157 | 158 | var S = graph.nullCopy() 159 | 160 | component.forEach(function (key) { 161 | copyNode(S, key, graph.getNodeAttributes(key)) 162 | }) 163 | 164 | graph.forEachEdge(function ( 165 | key, 166 | attr, 167 | source, 168 | target, 169 | sourceAttr, 170 | targetAttr, 171 | undirected, 172 | ) { 173 | if (S.hasNode(source)) { 174 | copyEdge(S, undirected, key, source, target, attr) 175 | } 176 | }) 177 | 178 | return S 179 | } 180 | 181 | function subgraph(graph, nodes) { 182 | if (!isGraph(graph)) 183 | throw new Error("graphology-operators/subgraph: invalid graph instance.") 184 | 185 | var S = graph.nullCopy() 186 | 187 | var filterNode = nodes 188 | 189 | if (Array.isArray(nodes)) { 190 | if (nodes.length === 0) return S 191 | 192 | nodes = new Set(nodes) 193 | } 194 | 195 | if (nodes instanceof Set) { 196 | if (nodes.size === 0) return S 197 | 198 | filterNode = function (key) { 199 | return nodes.has(key) 200 | } 201 | 202 | // Ensuring given keys are casted to string 203 | var old = nodes 204 | nodes = new Set() 205 | 206 | old.forEach(function (node) { 207 | nodes.add("" + node) 208 | }) 209 | } 210 | 211 | if (typeof filterNode !== "function") 212 | throw new Error( 213 | "graphology-operators/subgraph: invalid nodes. Expecting an array or a set or a filtering function.", 214 | ) 215 | 216 | if (typeof nodes === "function") { 217 | graph.forEachNode(function (key, attr) { 218 | if (!filterNode(key, attr)) return 219 | 220 | copyNode(S, key, attr) 221 | }) 222 | 223 | // Early termination 224 | if (S.order === 0) return S 225 | } else { 226 | nodes.forEach(function (key) { 227 | if (!graph.hasNode(key)) 228 | throw new Error( 229 | 'graphology-operators/subgraph: the "' + 230 | key + 231 | '" node was not found in the graph.', 232 | ) 233 | 234 | copyNode(S, key, graph.getNodeAttributes(key)) 235 | }) 236 | } 237 | 238 | graph.forEachEdge(function ( 239 | key, 240 | attr, 241 | source, 242 | target, 243 | sourceAttr, 244 | targetAttr, 245 | undirected, 246 | ) { 247 | if (!filterNode(source, sourceAttr)) return 248 | 249 | if (target !== source && !filterNode(target, targetAttr)) return 250 | 251 | copyEdge(S, undirected, key, source, target, attr) 252 | }) 253 | 254 | return S 255 | } 256 | 257 | function StackSet() { 258 | this.set = new Set() 259 | this.stack = [] 260 | } 261 | 262 | StackSet.prototype.has = function (value) { 263 | return this.set.has(value) 264 | } 265 | 266 | // NOTE: we don't check earlier existence because we don't need to 267 | StackSet.prototype.push = function (value) { 268 | this.stack.push(value) 269 | this.set.add(value) 270 | } 271 | 272 | StackSet.prototype.pop = function () { 273 | this.set.delete(this.stack.pop()) 274 | } 275 | 276 | StackSet.prototype.path = function (value) { 277 | return this.stack.concat(value) 278 | } 279 | 280 | StackSet.of = function (value, cycle) { 281 | var set = new StackSet() 282 | 283 | if (!cycle) { 284 | // Normally we add source both to set & stack 285 | set.push(value) 286 | } else { 287 | // But in case of cycle, we only add to stack so that we may reach the 288 | // source again (as it was not already visited) 289 | set.stack.push(value) 290 | } 291 | 292 | return set 293 | } 294 | 295 | function RecordStackSet() { 296 | this.set = new Set() 297 | this.stack = [] 298 | } 299 | 300 | RecordStackSet.prototype.has = function (value) { 301 | return this.set.has(value) 302 | } 303 | 304 | // NOTE: we don't check earlier existence because we don't need to 305 | RecordStackSet.prototype.push = function (record) { 306 | this.stack.push(record) 307 | this.set.add(record[1]) 308 | } 309 | 310 | RecordStackSet.prototype.pop = function () { 311 | this.set.delete(this.stack.pop()[1]) 312 | } 313 | 314 | RecordStackSet.prototype.path = function (record) { 315 | return this.stack 316 | .slice(1) 317 | .map(function (r) { 318 | return r[0] 319 | }) 320 | .concat([record[0]]) 321 | } 322 | 323 | RecordStackSet.of = function (value, cycle) { 324 | var set = new RecordStackSet() 325 | var record = [null, value] 326 | 327 | if (!cycle) { 328 | // Normally we add source both to set & stack 329 | set.push(record) 330 | } else { 331 | // But in case of cycle, we only add to stack so that we may reach the 332 | // source again (as it was not already visited) 333 | set.stack.push(record) 334 | } 335 | 336 | return set 337 | } 338 | 339 | /** 340 | * Function returning all the paths between source & target in the graph. 341 | * 342 | * @param {Graph} graph - Target graph. 343 | * @param {string} source - Source node. 344 | * @param {string} target - Target node. 345 | * @param {options} options - Options: 346 | * @param {number} maxDepth - Max traversal depth (default: infinity). 347 | * @return {array} - The found paths. 348 | */ 349 | function allSimplePaths(graph, source, target, options) { 350 | if (!isGraph(graph)) 351 | throw new Error( 352 | "graphology-simple-path.allSimplePaths: expecting a graphology instance.", 353 | ) 354 | 355 | if (!graph.hasNode(source)) 356 | throw new Error( 357 | 'graphology-simple-path.allSimplePaths: expecting: could not find source node "' + 358 | source + 359 | '" in the graph.', 360 | ) 361 | 362 | if (!graph.hasNode(target)) 363 | throw new Error( 364 | 'graphology-simple-path.allSimplePaths: expecting: could not find target node "' + 365 | target + 366 | '" in the graph.', 367 | ) 368 | 369 | options = options || {} 370 | var maxDepth = 371 | typeof options.maxDepth === "number" ? options.maxDepth : Infinity 372 | 373 | source = "" + source 374 | target = "" + target 375 | 376 | var cycle = source === target 377 | 378 | var stack = [graph.outboundNeighbors(source)] 379 | var visited = StackSet.of(source, cycle) 380 | 381 | var paths = [] 382 | var p 383 | 384 | var children, child 385 | 386 | while (stack.length !== 0) { 387 | children = stack[stack.length - 1] 388 | child = children.pop() 389 | 390 | if (!child) { 391 | stack.pop() 392 | visited.pop() 393 | } else { 394 | if (visited.has(child)) continue 395 | 396 | if (child === target) { 397 | p = visited.path(child) 398 | paths.push(p) 399 | } 400 | 401 | visited.push(child) 402 | 403 | if (!visited.has(target) && stack.length < maxDepth) 404 | stack.push(graph.outboundNeighbors(child)) 405 | else visited.pop() 406 | } 407 | } 408 | 409 | return paths 410 | } 411 | -------------------------------------------------------------------------------- /web/graphology.min.js: -------------------------------------------------------------------------------- 1 | function isGraph(t){return null!==t&&"object"==typeof t&&"function"==typeof t.addUndirectedEdgeWithKey&&"function"==typeof t.dropNode&&"boolean"==typeof t.multi}function copyNode(t,e,o){return o=Object.assign({},o),t.addNode(e,o)}function copyEdge(t,e,o,n,r,i){return(i=Object.assign({},i),e)?null==o?t.addUndirectedEdge(n,r,i):t.addUndirectedEdgeWithKey(o,n,r,i):null==o?t.addDirectedEdge(n,r,i):t.addDirectedEdgeWithKey(o,n,r,i)}function DFSStack(t){this.graph=t,this.stack=Array(t.order),this.seen=new Set,this.size=0}function largestConnectedComponent(t){if(!isGraph(t))throw Error("graphology-components: the given graph is not a valid graphology instance.");if(!t.order)return[];var e,o=new DFSStack(t),n=o.push.bind(o),r=[];return o.forEachNodeYetUnseen(function(i){var s;for(e=[],o.push(i);0!==o.size;)s=o.pop(),e.push(s),t.forEachNeighbor(s,n);return e.length>r.length&&(r=e),r.length>o.countUnseenNodes()}),r}function largestConnectedComponentSubgraph(t){var e=largestConnectedComponent(t),o=t.nullCopy();return e.forEach(function(e){copyNode(o,e,t.getNodeAttributes(e))}),t.forEachEdge(function(t,e,n,r,i,s,a){o.hasNode(n)&©Edge(o,a,t,n,r,e)}),o}function subgraph(t,e){if(!isGraph(t))throw Error("graphology-operators/subgraph: invalid graph instance.");var o=t.nullCopy(),n=e;if(Array.isArray(e)){if(0===e.length)return o;e=new Set(e)}if(e instanceof Set){if(0===e.size)return o;n=function(t){return e.has(t)};var r=e;e=new Set,r.forEach(function(t){e.add(""+t)})}if("function"!=typeof n)throw Error("graphology-operators/subgraph: invalid nodes. Expecting an array or a set or a filtering function.");if("function"==typeof e){if(t.forEachNode(function(t,e){n(t,e)&©Node(o,t,e)}),0===o.order)return o}else e.forEach(function(e){if(!t.hasNode(e))throw Error('graphology-operators/subgraph: the "'+e+'" node was not found in the graph.');copyNode(o,e,t.getNodeAttributes(e))});return t.forEachEdge(function(t,e,r,i,s,a,h){n(r,s)&&(i===r||n(i,a))&©Edge(o,h,t,r,i,e)}),o}function StackSet(){this.set=new Set,this.stack=[]}function RecordStackSet(){this.set=new Set,this.stack=[]}function allSimplePaths(t,e,o,n){if(!isGraph(t))throw Error("graphology-simple-path.allSimplePaths: expecting a graphology instance.");if(!t.hasNode(e))throw Error('graphology-simple-path.allSimplePaths: expecting: could not find source node "'+e+'" in the graph.');if(!t.hasNode(o))throw Error('graphology-simple-path.allSimplePaths: expecting: could not find target node "'+o+'" in the graph.');for(var r,i,s,a="number"==typeof(n=n||{}).maxDepth?n.maxDepth:1/0,h=(e=""+e)==(o=""+o),p=[t.outboundNeighbors(e)],c=StackSet.of(e,h),u=[];0!==p.length;)if(s=(i=p[p.length-1]).pop()){if(c.has(s))continue;s===o&&(r=c.path(s),u.push(r)),c.push(s),!c.has(o)&&p.length resposta.json()) 13 | .then((dados) => { 14 | lerJSONTabelas(dados) 15 | }) 16 | .catch((erro) => { 17 | notificar("Não foi possível carregar os dados das tabelas.") 18 | console.error("Erro ao carregar os dados das tabelas:", erro) 19 | }) 20 | } 21 | requisitaTabelas() 22 | 23 | function requisitaRelacoes(versao = "2402_105") { 24 | caminho = "https://raw.githubusercontent.com/vitorgt/TOTVS-RM-SQL/main/dados/" 25 | fetch(caminho + "relacoes_" + versao + ".json") 26 | .then((resposta) => resposta.json()) 27 | .then((dados) => { 28 | lerJSONRelacoes(dados) 29 | }) 30 | .catch((erro) => { 31 | notificar("Não foi possível carregar os dados das relações.") 32 | console.error("Erro ao carregar os dados das relacoes:", erro) 33 | }) 34 | } 35 | requisitaRelacoes() 36 | 37 | if (document.readyState == "complete") { 38 | DOMpronto() 39 | } else { 40 | document.addEventListener("DOMContentLoaded", DOMpronto) 41 | } 42 | 43 | function DOMpronto() { 44 | document.getElementById("slc-versao").addEventListener("change", () => { 45 | let versao = document.getElementById("slc-versao").value 46 | limparSelecao() 47 | requisitaTabelas(versao) 48 | requisitaRelacoes(versao) 49 | }) 50 | 51 | document 52 | .getElementById("in-busca-tabela") 53 | .addEventListener("input", atualizarListaTabelas) 54 | document 55 | .getElementById("btn-limpar-selecao") 56 | .addEventListener("click", limparSelecao) 57 | 58 | document.getElementById("btn-sql-copiar").addEventListener("click", copiarSQL) 59 | document.getElementById("btn-sql-baixar").addEventListener("click", baixarSQL) 60 | 61 | document 62 | .getElementById("in-descricoes-select") 63 | .addEventListener("change", atualizarSQL) 64 | document 65 | .getElementById("in-descricoes-join") 66 | .addEventListener("change", atualizarSQL) 67 | document 68 | .getElementById("in-colunas-modificacao") 69 | .addEventListener("change", atualizarSQL) 70 | document 71 | .getElementById("in-tabelas-disconexas") 72 | .addEventListener("change", atualizarSQL) 73 | document 74 | .getElementById("frm-tipo-join") 75 | .addEventListener("change", atualizarSQL) 76 | 77 | window.onscroll = exibirOcultarBtnVoltar 78 | document.getElementById("btn-voltar-topo").onclick = voltarAoTopo 79 | 80 | configurarTema() 81 | document 82 | .getElementById("btn-alternar-tema") 83 | .addEventListener("click", alternarTema) 84 | 85 | document 86 | .getElementById("btn-disconexas") 87 | .addEventListener("click", () => fecharDisconexas(true)) 88 | document 89 | .getElementById("btn-disconexas-fechar") 90 | .addEventListener("click", fecharDisconexas) 91 | 92 | document.addEventListener("keydown", (evt) => { 93 | evt = evt || window.event 94 | if ( 95 | ("key" in evt && (evt.key === "Escape" || evt.key === "Esc")) || 96 | ("keyCode" in evt && evt.keyCode === 27) 97 | ) { 98 | fecharDisconexas() 99 | } 100 | }) 101 | 102 | Prism.languages.insertBefore("sql", "keyword", { 103 | table: { 104 | pattern: /\b(?:from|join)\s+\w+\b/i, 105 | inside: { 106 | keyword: Prism.languages.sql.keyword, 107 | }, 108 | greedy: true, 109 | }, 110 | column: { 111 | pattern: /\b\w+\.\w+\b/, 112 | inside: { 113 | punctuation: Prism.languages.sql.punctuation, 114 | }, 115 | greedy: true, 116 | }, 117 | }) 118 | } 119 | 120 | function fecharDisconexas(alternar = false) { 121 | if (alternar) { 122 | document.getElementById("disconexas").classList.toggle("ativo") 123 | document.getElementById("btn-disconexas").classList.toggle("ativo") 124 | document.getElementById("btn-disconexas-fechar").classList.toggle("ativo") 125 | } else { 126 | document.getElementById("disconexas").classList.remove("ativo") 127 | document.getElementById("btn-disconexas").classList.remove("ativo") 128 | document.getElementById("btn-disconexas-fechar").classList.remove("ativo") 129 | } 130 | } 131 | 132 | function notificar(texto, cor = "red", duracao_segundos = 4) { 133 | const notificacao = document.getElementById("not-sql-copia") 134 | notificacao.textContent = texto 135 | // Se tem a cor declarada como var no CSS, usar ela, senao usar o parametro 136 | const cor_ = getComputedStyle(document.documentElement).getPropertyValue(cor) 137 | notificacao.style.backgroundColor = cor_ ? cor_ : cor 138 | 139 | const coresClaras = new Set(["orange", "yellow", "mint", "cyan"]) 140 | const coresEscuras = new Set(["green", "blue", "indigo"]) 141 | if (coresClaras.has(cor)) notificacao.style.color = "black" 142 | else if (coresEscuras.has(cor)) notificacao.style.color = "white" 143 | else 144 | notificacao.style.color = getComputedStyle( 145 | document.documentElement, 146 | ).getPropertyValue("text-color") 147 | 148 | // Mostrar notificação 149 | notificacao.style.display = "block" 150 | // Ocultar notificação após X segundos 151 | setTimeout(() => { 152 | notificacao.style.display = "none" 153 | }, duracao_segundos * 1000) 154 | } 155 | 156 | function copiarSQL() { 157 | if (!clausulaSQL || !clausulaSQL.trim()) { 158 | notificar("Consulta vazia. Por favor, selecione uma tabela.", "yellow") 159 | return 160 | } 161 | navigator.clipboard 162 | .writeText(clausulaSQL) 163 | .then(() => { 164 | notificar("Consulta SQL copiada com sucesso!", "mint", 3) 165 | }) 166 | .catch((erro) => { 167 | notificar("Não foi possível copiar a Consulta SQL.") 168 | console.error("Erro ao copiar SQL:", erro) 169 | }) 170 | } 171 | 172 | function baixarSQL() { 173 | if (!clausulaSQL || !clausulaSQL.trim()) { 174 | notificar("Consulta vazia. Por favor, selecione uma tabela.", "yellow") 175 | return 176 | } 177 | try { 178 | const binario = new Blob([clausulaSQL], { type: "text/sql" }) 179 | const url = URL.createObjectURL(binario) 180 | let agora = new Date() 181 | agora.setTime(agora.getTime() - agora.getTimezoneOffset() * 60 * 1000) 182 | agora = agora.toISOString().replace(/:|T/g, "-").substring(0, 19) 183 | const a = document.createElement("a") 184 | a.href = url 185 | const nome = Array.from(selecoes)[0] ? Array.from(selecoes)[0] : "TOTVS-RM" 186 | a.download = nome + "-" + agora + ".sql" 187 | document.body.appendChild(a) 188 | a.click() 189 | document.body.removeChild(a) 190 | URL.revokeObjectURL(url) 191 | } catch (erro) { 192 | notificar("Não foi possível baixar a Consulta SQL.") 193 | console.error("Erro ao baixar SQL:", erro) 194 | } 195 | } 196 | 197 | function limparSelecao() { 198 | // Desmarcar todas as caixas de seleção 199 | document 200 | .querySelectorAll('#lst-tabelas input[type="checkbox"]') 201 | .forEach((checkbox) => (checkbox.checked = false)) 202 | selecoes.clear() 203 | visualizarGrafo() 204 | atualizarSQL() 205 | verificarTabelasDesconexas() 206 | } 207 | 208 | function exibirOcultarBtnVoltar() { 209 | const btnVoltarTopo = document.getElementById("btn-voltar-topo") 210 | if ( 211 | btnVoltarTopo && 212 | (document.body.scrollTop > 200 || document.documentElement.scrollTop > 200) 213 | ) { 214 | btnVoltarTopo.style.display = "block" 215 | } else { 216 | btnVoltarTopo.style.display = "none" 217 | } 218 | } 219 | 220 | function voltarAoTopo() { 221 | document.body.scrollTop = 0 // Para Safari 222 | document.documentElement.scrollTop = 0 // Para Chrome, Firefox, IE e Opera 223 | } 224 | 225 | function configurarTema() { 226 | let temaSalvo = localStorage.getItem("tema") 227 | if (!temaSalvo) { 228 | temaSalvo = "dark" 229 | localStorage.setItem("tema", temaSalvo) 230 | } 231 | document.documentElement.setAttribute("data-theme", temaSalvo) 232 | document.getElementById("btn-alternar-tema").textContent = 233 | temaSalvo === "light" ? "🌙" : "☀️" 234 | } 235 | 236 | function alternarTema() { 237 | const atual = document.documentElement.getAttribute("data-theme") 238 | const novo = atual === "light" ? "dark" : "light" 239 | localStorage.setItem("tema", novo) 240 | document.documentElement.setAttribute("data-theme", novo) 241 | this.textContent = novo === "light" ? "🌙" : "☀️" 242 | } 243 | 244 | function atualizarListaTabelas() { 245 | if (!tabelas) return 246 | 247 | const filtro = document.getElementById("in-busca-tabela").value || "" 248 | const lstTabelas = document.getElementById("lst-tabelas") 249 | lstTabelas.innerHTML = "" // Limpa a lista atual 250 | 251 | const tabelas_ids_filtrados = Object.keys(tabelas || {}).filter((tabela) => 252 | (tabela + " " + tabelas[tabela]["#"]) 253 | .toLowerCase() 254 | .normalize("NFD") 255 | .replace(/[\u0300-\u036f]/g, "") 256 | .includes(filtro.toLowerCase()), 257 | ) 258 | 259 | tabelas_ids_filtrados.forEach((tabela) => { 260 | const itemLista = document.createElement("li") 261 | const checkbox = document.createElement("input") 262 | const label = document.createElement("label") 263 | 264 | checkbox.type = "checkbox" 265 | checkbox.id = tabela 266 | checkbox.value = tabela 267 | checkbox.checked = selecoes.has(tabela) 268 | checkbox.addEventListener("change", () => { 269 | if (checkbox.checked) selecoes.add(tabela) 270 | else selecoes.delete(tabela) 271 | visualizarGrafo() 272 | atualizarSQL() 273 | verificarTabelasDesconexas() 274 | }) 275 | 276 | label.htmlFor = tabela 277 | label.textContent = tabela + ": " + (tabelas[tabela]["#"] || "") 278 | 279 | itemLista.appendChild(checkbox) 280 | itemLista.appendChild(label) 281 | lstTabelas.appendChild(itemLista) 282 | }) 283 | } 284 | 285 | function lerJSONTabelas(dados) { 286 | tabelas = dados 287 | atualizarListaTabelas() 288 | Object.keys(tabelas).forEach((tabela) => { 289 | nodes.push({ 290 | id: tabela, 291 | label: tabela, 292 | title: tabela, 293 | key: tabela, 294 | group: tabela.substring(0, 2) == "SZ" ? "SZ" : tabela.substring(0, 1), 295 | }) 296 | }) 297 | } 298 | 299 | function lerJSONRelacoes(dados) { 300 | relacoes = dados 301 | relacoes.forEach((relacao) => { 302 | edges.push({ 303 | from: relacao[0], 304 | source: relacao[0], 305 | to: relacao[1], 306 | target: relacao[1], 307 | }) 308 | }) 309 | grafo = criarGrafo() 310 | } 311 | 312 | function visualizarGrafo() { 313 | const nosFiltrados = nodes.filter((node) => selecoes.has(node.id)) 314 | const idsNosFiltrados = new Set(nosFiltrados.map((node) => node.id)) 315 | const arestasFiltradas = edges.filter( 316 | (edge) => idsNosFiltrados.has(edge.from) && idsNosFiltrados.has(edge.to), 317 | ) 318 | 319 | const grafo = document.getElementById("grafo") 320 | const dados = { 321 | nodes: new vis.DataSet(nosFiltrados), 322 | edges: new vis.DataSet(arestasFiltradas), 323 | } 324 | return new vis.Network(grafo, dados, {}) 325 | } 326 | 327 | function criarGrafo() { 328 | if (!edges || edges.length == 0) return null 329 | if (grafo) return grafo 330 | 331 | const grafo_ = new graphology.Graph({ type: "undirected" }) 332 | edges.forEach((edge) => { 333 | grafo_.mergeEdge(edge.source, edge.target) 334 | }) 335 | return grafo_ 336 | } 337 | 338 | function visualizarGrafoDisconexas(conexos, conexao, disconexos) { 339 | const nosColoridos = [ 340 | ...conexos.map((id) => ({ 341 | id, 342 | label: id, 343 | color: "rgb(49, 222, 75)", // verde 344 | })), 345 | ...conexao.map((id) => ({ 346 | id, 347 | label: id, 348 | color: "rgb(255, 169, 20)", // amarelo 349 | shape: "box", 350 | })), 351 | ...disconexos.map((id) => ({ 352 | id, 353 | label: id, 354 | color: "rgb(255, 65, 54)", // vermelho 355 | })), 356 | ] 357 | const idsNosColoridos = new Set(nosColoridos.map((node) => node.id)) 358 | const arestasFiltradas = edges 359 | .filter( 360 | (edge) => idsNosColoridos.has(edge.from) && idsNosColoridos.has(edge.to), 361 | ) 362 | .map((edge) => { 363 | if (conexos.includes(edge.from) || conexos.includes(edge.to)) 364 | return { ...edge, color: "rgb(49, 222, 75)" } // verde 365 | if (disconexos.includes(edge.from) || disconexos.includes(edge.to)) 366 | return { ...edge, color: "rgb(255, 65, 54)" } // vermelho 367 | return { ...edge, color: "rgb(255, 169, 20)" } // amarelo 368 | }) 369 | 370 | const grafo = document.getElementById("grafo-disconexas") 371 | const dados = { 372 | nodes: new vis.DataSet(nosColoridos), 373 | edges: new vis.DataSet(arestasFiltradas), 374 | } 375 | new vis.Network(grafo, dados, {}) 376 | } 377 | 378 | function listarCaminhosConexao(caminhosConexao) { 379 | const lista = document.getElementById("lst-conexoes") 380 | lista.innerHTML = "" 381 | caminhosConexao.forEach((caminho) => { 382 | const item = document.createElement("li") 383 | item.textContent = caminho 384 | lista.appendChild(item) 385 | }) 386 | 387 | const btnMais = document.createElement("button") 388 | btnMais.textContent = "Tentar procurar mais alternativas" 389 | btnMais.id = "btn-disconexas-mais" 390 | btnMais.value = 2 391 | lista.appendChild(btnMais) 392 | 393 | document 394 | .getElementById("btn-disconexas-mais") 395 | .addEventListener("click", () => { 396 | document.getElementById("btn-disconexas-mais").value = 397 | parseInt(document.getElementById("btn-disconexas-mais").value) + 1 398 | verificarTabelasDesconexas() 399 | }) 400 | } 401 | 402 | // Função para encontrar caminhos entre dois nós 403 | function encontrarCaminhos( 404 | origem, 405 | destino, 406 | minCaminhos = 2, 407 | maxProfundidade = 10, 408 | ) { 409 | let caminhos = [] 410 | for ( 411 | let prof = 0; 412 | caminhos.length < minCaminhos && prof <= maxProfundidade; 413 | prof++ 414 | ) 415 | caminhos.push(...allSimplePaths(grafo, origem, destino, { maxDepth: prof })) 416 | return caminhos 417 | } 418 | 419 | function verificarTabelasDesconexas() { 420 | let selecionadas = Array.from(selecoes) 421 | let ausentesGrafo = [] 422 | 423 | selecionadas = selecionadas.filter((t) => { 424 | if (!grafo.hasNode(t)) { 425 | ausentesGrafo.push(t) 426 | return false 427 | } 428 | return true 429 | }) 430 | 431 | const subgrafoSelecionadas = subgraph(grafo, selecionadas) 432 | const maiorSubgrafoConecatado = 433 | largestConnectedComponentSubgraph(subgrafoSelecionadas) 434 | const elementosConectados = Array.from(maiorSubgrafoConecatado.nodes()) 435 | const elementosDesconectados = selecionadas.filter( 436 | (t) => !elementosConectados.includes(t), 437 | ) 438 | 439 | if (selecionadas.length == 0 || elementosDesconectados.length == 0) { 440 | fecharDisconexas() 441 | setTimeout(() => { 442 | document.getElementById("btn-disconexas").style.display = "none" 443 | }, 350) 444 | return 445 | } else { 446 | document.getElementById("btn-disconexas").style.display = "block" 447 | } 448 | 449 | let minCaminhos = 2 450 | if (document.getElementById("btn-disconexas-mais")) { 451 | minCaminhos = document.getElementById("btn-disconexas-mais").value 452 | } 453 | const elementosConexao = new Set() 454 | const caminhosConexao = new Set() 455 | elementosConectados.forEach((ec) => { 456 | elementosDesconectados.forEach((ed) => { 457 | const caminhos = encontrarCaminhos(ec, ed, minCaminhos) 458 | caminhos.forEach((caminho) => { 459 | caminhosConexao.add(caminho.join(" ↔ ")) 460 | caminho.slice(1, -1).forEach((tabela) => elementosConexao.add(tabela)) 461 | }) 462 | }) 463 | }) 464 | 465 | caminhosConexao.add( 466 | ...ausentesGrafo.map( 467 | (t) => `Não foi possível encontrar meios de relacionar a tabela ${t}.`, 468 | ), 469 | ) 470 | 471 | visualizarGrafoDisconexas( 472 | elementosConectados, 473 | Array.from(elementosConexao).filter( 474 | (t) => 475 | !elementosConectados.includes(t) && !elementosDesconectados.includes(t), 476 | ), 477 | elementosDesconectados, 478 | ) 479 | listarCaminhosConexao(caminhosConexao) 480 | } 481 | 482 | function comporSelect(selecionadas, descricoes = true) { 483 | if (!selecionadas || selecionadas.length == 0) return "" 484 | 485 | const incluirCol = document.getElementById("in-colunas-modificacao").checked 486 | 487 | // Descobre o maior comprimento dos nomes das tabelas selecionadas para 488 | // alinhar a formatacao 489 | let preenche = 0 490 | selecionadas.forEach((t) => { 491 | Object.keys(tabelas[t] || {}).map((c) => { 492 | preenche = Math.max(preenche, t.length + c.length + 1) 493 | }) 494 | }) 495 | 496 | let clausula = "SELECT " 497 | selecionadas.forEach((tabela, t) => { 498 | let colunas = tabelas[tabela] 499 | Object.keys(colunas || {}) 500 | .filter( 501 | (coluna) => 502 | !["IDFT", "#"].includes(coluna) && 503 | (incluirCol || 504 | ![ 505 | "RECCREATEDBY", 506 | "RECCREATEDON", 507 | "RECMODIFIEDBY", 508 | "RECMODIFIEDON", 509 | ].includes(coluna)), 510 | ) 511 | .sort() 512 | .forEach((coluna, c, colunas_) => { 513 | clausula += t == 0 && c == 0 ? "" : " " 514 | clausula += `${tabela}.${coluna}`.padEnd(preenche, " ") 515 | clausula += ` AS ${tabela}_${coluna}` 516 | clausula += 517 | t == selecionadas.length - 1 && c == colunas_.length - 1 ? "" : "," 518 | if (descricoes) { 519 | const ultimoAS = clausula.length - clausula.lastIndexOf(" AS ") 520 | clausula += " ".repeat(Math.abs(preenche - ultimoAS + 5)) 521 | clausula += ` /* ${colunas[coluna]} */` 522 | } 523 | clausula += "\n" 524 | }) 525 | }) 526 | return clausula 527 | } 528 | 529 | function correlacionarChaves( 530 | tabelaOrigem, 531 | tabelaDestino, 532 | descricoes = true, 533 | preenche = 0, 534 | ) { 535 | const tabelasOrdenadas = [tabelaOrigem, tabelaDestino].sort() 536 | let relacoes_ = relacoes.filter( 537 | (r) => r[0] === tabelasOrdenadas[0] && r[1] === tabelasOrdenadas[1], 538 | ) 539 | 540 | if ( 541 | relacoes_ === undefined || 542 | relacoes_.length === 0 || 543 | relacoes_[0] === undefined || 544 | relacoes_[0].length === 0 || 545 | relacoes_[0][2] === undefined || 546 | relacoes_[0][2].length === 0 547 | ) 548 | return "" 549 | relacoes_ = relacoes_[0][2] 550 | 551 | let clausula = " ".repeat(preenche) 552 | clausula += `/* Relações de chaves entre ${tabelaOrigem} e ${tabelaDestino} */\n` 553 | 554 | if (relacoes_.length > 1) { 555 | clausula += `${" ".repeat(preenche)}/* Existem ${relacoes_.length} relações` 556 | clausula += ` entre as tabelas ${tabelaOrigem} e ${tabelaDestino} */\n` 557 | clausula += `${" ".repeat(preenche)}/* Provavelmente você deve escolher ` 558 | clausula += `apenas uma */\n` 559 | } 560 | 561 | relacoes_.forEach(([chavesOrigem, chavesDestino], r) => { 562 | if (relacoes_.length > 1) 563 | clausula += `${" ".repeat(preenche)}/* Alternativa ${r} */\n` 564 | 565 | if (tabelasOrdenadas[0] != tabelaOrigem) { 566 | ;[chavesOrigem, chavesDestino] = [chavesDestino, chavesOrigem] 567 | } 568 | 569 | chavesOrigem = chavesOrigem.split(",") 570 | chavesDestino = chavesDestino.split(",") 571 | 572 | chavesOrigem.forEach((chaveOrigem, c) => { 573 | if (c == 0) clausula += " ".repeat(preenche) + "ON " 574 | else clausula += " ".repeat(preenche + 3) + "AND " 575 | 576 | clausula += `${tabelaOrigem}.${chaveOrigem} = ` 577 | clausula += `${tabelaDestino}.${chavesDestino[c]}` 578 | 579 | if (descricoes) { 580 | clausula += ` /* ${tabelas[tabelaOrigem][chaveOrigem]} - ` 581 | clausula += `${tabelas[tabelaDestino][chavesDestino[c]]} */` 582 | } 583 | 584 | clausula += "\n" 585 | }) 586 | }) 587 | if (relacoes_.length > 1) 588 | clausula += " ".repeat(preenche) + "/* Fim alternativas */\n" 589 | return clausula 590 | } 591 | 592 | function comporJoin(selecionadas, tipo = "LEFT ", descricoes = true) { 593 | if (!selecionadas || selecionadas.length == 0) return "" 594 | 595 | let clausula = "/* IMPORTANTE: Por favor, revise os JOINs abaixo com atenção." 596 | clausula += "\n * Esta consulta inclui todas as combinações possíveis de " 597 | clausula += "JOINs entre as tabelas selecionadas.\n * No entanto, algumas " 598 | clausula += "dessas combinações podem não ser adequadas para o que você " 599 | clausula += "precisa.\n * Certifique-se de ajustar ou remover os JOINs que " 600 | clausula += "não se encaixam no seu contexto específico.\n */" 601 | 602 | clausula += `\nFROM ${selecionadas[0]} (NOLOCK)` 603 | if (descricoes) clausula += ` /* ${tabelas[selecionadas[0]]["#"]} */` 604 | clausula += "\n" 605 | 606 | let visitadas = [selecionadas[0]] 607 | let preenche = ` ${tipo}JOIN `.length - 3 608 | 609 | selecionadas.slice(1).forEach((tabela) => { 610 | visitadas.push(tabela) 611 | 612 | clausula += ` ${tipo}JOIN ${tabela} (NOLOCK)` 613 | if (descricoes) clausula += ` /* ${tabelas[tabela]["#"]} */` 614 | clausula += "\n" 615 | 616 | const correlacoes = visitadas 617 | .map((v) => correlacionarChaves(v, tabela, descricoes, preenche)) 618 | .join("") 619 | 620 | if (!correlacoes) { 621 | clausula += " ".repeat(preenche) + "/* Não foi possível encontrar relação" 622 | clausula += " de chaves para juntar essa tabela com as anteriores. */\n" 623 | } else { 624 | clausula += correlacoes 625 | } 626 | }) 627 | return clausula 628 | } 629 | 630 | function atualizarSQL() { 631 | const sql = document.getElementById("sql") 632 | 633 | const selecionadas = Array.from(selecoes) 634 | let elementosConectados = selecionadas 635 | 636 | let disconexas = document.getElementById("in-tabelas-disconexas") 637 | disconexas.disabled = false 638 | if (!disconexas.checked) { 639 | try { 640 | const subgrafoSelecionadas = subgraph(grafo, selecionadas) 641 | const maiorSubgrafoConecatado = 642 | largestConnectedComponentSubgraph(subgrafoSelecionadas) 643 | elementosConectados = Array.from(maiorSubgrafoConecatado.nodes()) 644 | } catch (erro) { 645 | disconexas.checked = true 646 | disconexas.disabled = true 647 | console.error(erro) 648 | console.log("Assumindo tabelas selecionadas.") 649 | } 650 | } 651 | 652 | if (selecionadas.length === 0) { 653 | clausulaSQL = "" 654 | sql.textContent = "" 655 | return "" 656 | } 657 | 658 | const descSelect = document.getElementById("in-descricoes-select").checked 659 | const clausulaSelect = comporSelect(elementosConectados, descSelect) 660 | 661 | const tipoJoin = document.querySelector( 662 | 'input[name="in-tipo-join"]:checked', 663 | ).value 664 | const descJoin = document.getElementById("in-descricoes-join").checked 665 | const clausulaJoin = comporJoin(elementosConectados, tipoJoin, descJoin) 666 | 667 | clausulaSQL = clausulaSelect + clausulaJoin 668 | sql.textContent = clausulaSQL 669 | Prism.highlightElement(sql) 670 | return clausulaSQL 671 | } 672 | -------------------------------------------------------------------------------- /web/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | /* Tema escuro (padrão) */ 3 | --background-color0: rgb(18, 18, 18); 4 | --background-color1: rgb(44, 44, 46); 5 | --text-color: rgb(255, 255, 255); 6 | --red: rgb(255, 65, 54); 7 | --orange: rgb(255, 169, 20); 8 | --yellow: rgb(255, 212, 38); 9 | --green: rgb(49, 222, 75); 10 | --mint: rgb(108, 224, 219); 11 | --cyan: rgb(112, 215, 255); 12 | --blue: rgb(64, 156, 255); 13 | --indigo: rgb(125, 122, 255); 14 | --gray0: rgb(152, 152, 157); 15 | --gray1: rgb(54, 54, 56); 16 | } 17 | 18 | [data-theme="light"] { 19 | /* Tema claro */ 20 | --background-color0: rgb(255, 255, 255); 21 | --background-color1: rgb(229, 229, 234); 22 | --text-color: rgb(0, 0, 0); 23 | --red: rgb(194, 6, 24); 24 | --orange: rgb(245, 139, 0); 25 | --yellow: rgb(245, 194, 0); 26 | --green: rgb(30, 195, 55); 27 | --mint: rgb(0, 189, 180); 28 | --cyan: rgb(65, 175, 220); 29 | --blue: rgb(0, 122, 245); 30 | --indigo: rgb(54, 52, 163); 31 | --gray0: rgb(97, 97, 101); 32 | --gray1: rgb(188, 188, 192); 33 | } 34 | 35 | * { 36 | box-sizing: border-box; 37 | margin: 0; 38 | padding: 0; 39 | } 40 | 41 | body { 42 | background-color: var(--background-color0); 43 | color: var(--text-color); 44 | font-family: Arial, sans-serif; 45 | } 46 | 47 | header { 48 | padding: 20px 50px; 49 | } 50 | 51 | h1, 52 | h2 { 53 | text-align: center; 54 | } 55 | 56 | #secao-tabelas { 57 | display: flex; 58 | flex-direction: column; 59 | margin: 20px; 60 | margin-bottom: 40px; 61 | } 62 | 63 | #secao-grafo, 64 | #secao-selecao { 65 | width: 50%; 66 | height: 400px; 67 | } 68 | 69 | .pilha-h { 70 | display: flex; 71 | flex-direction: row; 72 | justify-content: center; 73 | align-items: center; /* Centraliza verticalmente os itens */ 74 | gap: 20px; 75 | } 76 | 77 | .pilha-v { 78 | display: flex; 79 | flex-direction: column; 80 | gap: 10px; 81 | } 82 | 83 | .in-busca { 84 | width: 95%; 85 | padding: 10px; 86 | font-size: 16px; 87 | background-color: var(--background-color0); 88 | color: var(--text-color); 89 | border-radius: 5px; 90 | border: 1px solid var(--gray1); 91 | outline: 0; 92 | } 93 | 94 | input.in-busca:focus { 95 | outline: none !important; 96 | } 97 | 98 | #lst-tabelas { 99 | list-style: none; 100 | padding: 0; 101 | width: 100%; 102 | height: 353px; 103 | overflow-y: scroll; 104 | } 105 | 106 | #lst-conexoes { 107 | list-style: none; 108 | padding: 0; 109 | margin: 10px; 110 | width: 95%; 111 | height: 35vh; 112 | overflow-y: auto; 113 | } 114 | 115 | #lst-tabelas li, 116 | #lst-conexoes li { 117 | margin: 10px; 118 | } 119 | 120 | #grafo { 121 | width: 100%; 122 | height: 100%; 123 | margin-bottom: 20px; 124 | } 125 | 126 | #secao-sql { 127 | margin: 20px; 128 | } 129 | 130 | /* Sobrescrevendo os estilos padrões do Prism.js */ 131 | pre[class*="language-sql"], 132 | code[class*="language-sql"], 133 | pre, 134 | code, 135 | .language-sql, 136 | .token { 137 | width: 100%; 138 | background: none; 139 | background-color: none; 140 | text-shadow: none; 141 | color: var(--text-color); 142 | overflow-x: auto; 143 | padding: 0; 144 | margin: 0; 145 | } 146 | /* Palavras reservadas */ 147 | .language-sql .token.keyword, 148 | .language-sql .token.operator { 149 | color: var(--mint); 150 | font-weight: bold; 151 | background: none; 152 | background-color: none; 153 | text-shadow: none; 154 | } 155 | /* Comentários */ 156 | .language-sql .token.comment, 157 | .language-sql .token.punctuation { 158 | color: var(--gray0); 159 | font-style: italic; 160 | } 161 | /* Tabelas e Colunas */ 162 | .language-sql .token.column, 163 | .language-sql .token.table { 164 | color: var(--indigo); 165 | font-weight: bold; 166 | } 167 | 168 | .notificacao { 169 | display: none; 170 | position: fixed; 171 | top: 20px; 172 | right: 20px; 173 | padding: 10px; 174 | background-color: var(--green); 175 | color: var(--text-color); 176 | border-radius: 5px; 177 | z-index: 1000; 178 | } 179 | 180 | #btn-voltar-topo { 181 | position: fixed; 182 | bottom: 20px; 183 | right: 20px; 184 | background-color: var(--gray1); 185 | color: var(--text-color); 186 | border: none; 187 | border-radius: 5px; 188 | padding: 7px 10px 10px 10px; 189 | cursor: pointer; 190 | display: none; /* Inicialmente escondido */ 191 | font-size: 20px; 192 | transform: rotate(-90deg); 193 | } 194 | 195 | #btn-alternar-tema { 196 | position: absolute; 197 | top: 20px; 198 | left: 20px; 199 | background-color: var(--gray1); 200 | color: var(--text-color); 201 | border: none; 202 | border-radius: 5px; 203 | padding: 5px; 204 | cursor: pointer; 205 | } 206 | 207 | #btn-github { 208 | position: absolute; 209 | top: 20px; 210 | right: 20px; 211 | padding: 5px 7px; 212 | background-color: var(--gray1); 213 | color: var(--text-color); 214 | text-decoration: none; 215 | border-radius: 5px; 216 | } 217 | 218 | #btn-voltar-topo:hover, 219 | #btn-alternar-tema:hover, 220 | #btn-github:hover { 221 | background-color: var(--gray0); 222 | } 223 | 224 | #btn-disconexas { 225 | position: fixed; 226 | top: 100px; 227 | right: -5px; 228 | background-color: var(--orange); 229 | color: var(--text-color); 230 | border: none; 231 | cursor: pointer; 232 | padding: 10px; 233 | font-size: 24px; 234 | border-radius: 5px; 235 | display: none; 236 | -webkit-transition-duration: 0.3s; 237 | -moz-transition-duration: 0.3s; 238 | -o-transition-duration: 0.3s; 239 | transition-duration: 0.3s; 240 | z-index: 3; 241 | } 242 | 243 | #btn-disconexas.ativo { 244 | right: 49.5%; 245 | } 246 | 247 | #btn-disconexas-fechar { 248 | position: fixed; 249 | top: 10px; 250 | right: -50%; 251 | background-color: #00000000; 252 | color: var(--text-color); 253 | border: none; 254 | cursor: pointer; 255 | font-size: 24px; 256 | -webkit-transition-duration: 0.3s; 257 | -moz-transition-duration: 0.3s; 258 | -o-transition-duration: 0.3s; 259 | transition-duration: 0.3s; 260 | z-index: 7; 261 | } 262 | 263 | #btn-disconexas-fechar.ativo { 264 | right: 30px; 265 | } 266 | 267 | #disconexas { 268 | background: var(--background-color1); 269 | background-color: var(--background-color1); 270 | color: var(--text-color); 271 | position: fixed; 272 | top: 0; 273 | right: -50%; /* Inicialmente oculto */ 274 | width: 50%; 275 | height: 100%; 276 | overflow-y: none; 277 | -webkit-transition-duration: 0.3s; 278 | -moz-transition-duration: 0.3s; 279 | -o-transition-duration: 0.3s; 280 | transition-duration: 0.3s; 281 | z-index: 5; 282 | } 283 | 284 | #disconexas.ativo { 285 | right: 0; 286 | } 287 | 288 | #disconexas section { 289 | margin: 20px; 290 | } 291 | 292 | #disconexas h2 { 293 | padding: 10px 50px; 294 | } 295 | 296 | #disconexas p, 297 | #disconexas p span { 298 | text-align: center; 299 | margin: 0 !important; 300 | } 301 | 302 | .txt-verde { 303 | color: var(--green); 304 | } 305 | 306 | .txt-vermelho { 307 | color: var(--red); 308 | } 309 | 310 | .txt-amarelo { 311 | color: var(--orange); 312 | } 313 | 314 | #grafo-disconexas { 315 | width: 100%; 316 | height: 45vh; 317 | margin-bottom: 10px; 318 | } 319 | 320 | @media (min-width: 768px) { 321 | #secao-tabelas { 322 | flex-direction: row; 323 | } 324 | 325 | #secao-tabelas > div { 326 | flex: 1; 327 | } 328 | } 329 | @media (max-width: 768px) { 330 | #secao-grafo, 331 | #secao-selecao { 332 | width: 100%; 333 | margin-bottom: 70px; 334 | } 335 | 336 | .pilha-h { 337 | flex-direction: column; 338 | gap: 5px; 339 | } 340 | 341 | #disconexas { 342 | width: 100%; 343 | right: -100%; /* Inicialmente oculto */ 344 | } 345 | } 346 | 347 | @media (max-height: 600px) { 348 | #disconexas { 349 | overflow-y: scroll; 350 | } 351 | } 352 | --------------------------------------------------------------------------------