├── .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:
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 |
22 | 12.1.2306.113
23 | 12.1.2402.105
24 |
25 |
26 |
27 |
28 |
29 |
30 |
44 |
45 |
46 | Visualização de Relações de Tabelas
47 |
48 |
49 |
50 |
51 |
52 | Consulta SQL Gerada
53 |
96 |
97 |
98 |
99 |
100 |
101 |
102 | Consulta SQL copiada com sucesso!
103 |
104 |
105 | ➜
106 |
107 | ☀️
108 | ❮❯
115 |
116 |
117 | ⚠️
118 |
119 |
120 | ✖
121 |
122 | Possíveis Relações para Tabelas Disconexas
123 |
124 | ATENÇÂO! Nem todas as seguintes ligações podem fazer sentido. Use
125 | extrema cautela!
126 |
127 |
128 | Os 🟢 circulos verdes são as tabelas
129 | selecionadas conectadas.
130 |
131 |
132 | Os 🔴 circulos vermelhos são as
133 | tabelas selecionadas disconexas.
134 |
135 |
136 | As 🟨 caixas amarelas são tabelas que
137 | relacionam as conectadas e as disconexas.
138 |
139 |
140 |
141 |
144 |
145 |
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 |
--------------------------------------------------------------------------------