AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

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

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / dba / Perguntas / 338587
Accepted
John K. N.
John K. N.
Asked: 2024-04-12 20:31:19 +0800 CST2024-04-12 20:31:19 +0800 CST 2024-04-12 20:31:19 +0800 CST

Por que o SQL Server exige LEFT JOIN para recuperar um status que não existe?

  • 772

Recentemente, consultei nossa ferramenta interna de inventário de banco de dados para obter uma lista de servidores, instâncias e bancos de dados e adicionei o status correspondente a cada servidor, instância e banco de dados.

Diagrama de Relacionamento

Server ˂-- 1 : n --˃ Instance ˂-- 1 : n --˃ Database
   ˄                    ˄                      ˄
   |                    |                      |
   |                  1 : 1                    |
   |                    |                      |
   |                    ˅                      |
   +-- 1 : 1 --˃     Status        ˂-- 1 : 1 --+

Leia como:
...um servidor pode ter múltiplas instâncias
...uma instância pode ter vários bancos de dados
...um servidor, uma instância e um banco de dados podem ter um status

Configurar

Tabela de status

CREATE TABLE [Status]
(
  StatusID int,
  StatusName char(20),
  );

Dados de status

INSERT INTO [Status] (StatusID, StatusName)
VALUES
(1,'Productive'),
(2,'Prod ACC'),
(3,'Prod APP'),
(4,'Test'),
(5,'Test ACC'),
(6,'Test APP'),
(7,'OFFLINE'),
(8,'Reserved'),
(9,'Decommisioned');

Tabela de servidores

CREATE TABLE [Server]
(
  ServerID int,
  ServerName char(20),
  ServerStatusID int
  );

Dados do servidor

INSERT INTO [Server] (ServerID, ServerName, ServerStatusID)
VALUES
(1,'FirstServer',1),
(2,'SecondServer',2),
(3,'ThirdServer',5),
(4,'FourthServer',8),
(5,'FifthServer',8);

Tabela de Instâncias

CREATE TABLE [Instance]
(
  InstanceID int,
  ServerID int,
  InstanceName char(30),
  InstanceStatusID int
  );

Dados da instância

INSERT INTO [Instance] 
  (InstanceID, ServerID, InstanceName, InstanceStatusID)
VALUES
(1,1,'GENERAL',1),
(2,1,'TAXES',1),
(3,2,'GENERAL',9),
(4,2,'SOCIAL',2),
(5,3,'GENERAL',5),
(6,3,'FBI',8),
(7,5,'COMINGSOON',8);

Tabela de banco de dados

CREATE TABLE [Database]
(
  DatabaseID int,
  InstanceID int,
  DatabaseName char(30),
  DatabaseStatusID int
  );

Dados do banco de dados

INSERT INTO [Database]
(DatabaseID, InstanceID, DatabaseName, DatabaseStatusID)
VALUES
(1,1,'master',1),
(2,1,'model',1),
(3,1,'msdb',1),
(4,1,'UserDB1',1),
(5,2,'master',1),
(6,2,'model',1),
(7,2,'msdb',1),
(8,2,'TaxesDB',1),
(9,4,'master',2),
(10,4,'model',2),
(11,4,'msdb',2),
(12,4,'HealthCareDB',2),
(13,5,'master',5),
(14,5,'model',5),
(15,5,'msdb',5),
(16,5,'GeneralUserDB',5),
(17,6,'master',8),
(18,6,'model',8),
(19,6,'msdb',8),
(20,6,'CriminalDB',8);

Instrução SELECT sem tabela de status envolvida

A instrução SELECT inicial envolvia simplesmente juntar as três tabelas: servidor, instância, banco de dados e era a seguinte:

-- Simple SELECT to get all information on Servers, Instances and Databases
-- The status of the server, instance or database is not returned
SELECT 
  ServerName, 
  InstanceName,
  DatabaseName 
  FROM [Server] as srv
    LEFT JOIN [Instance] as ins
      ON srv.ServerID = ins.ServerID
    LEFT JOIN [Database] as dbs
      ON ins.InstanceID = dbs.InstanceID;

Resultados de 1. Declaração

POR FAVOR, OBSERVE ISSO...

  • existe um servidor sem instância e banco de dados
  • há uma instância sem banco de dados
Nome do servidor Nome da instância Nome do banco de dados
Primeiro Servidor EM GERAL mestre
Primeiro Servidor EM GERAL modelo
Primeiro Servidor EM GERAL msdb
Primeiro Servidor EM GERAL UsuárioDB1
Primeiro Servidor IMPOSTOS mestre
Primeiro Servidor IMPOSTOS modelo
Primeiro Servidor IMPOSTOS msdb
Primeiro Servidor IMPOSTOS ImpostosDB
Segundo Servidor EM GERAL nulo
Segundo Servidor SOCIAL mestre
Segundo Servidor SOCIAL modelo
Segundo Servidor SOCIAL msdb
Segundo Servidor SOCIAL HealthCareDB
Terceiro Servidor EM GERAL mestre
Terceiro Servidor EM GERAL modelo
Terceiro Servidor EM GERAL msdb
Terceiro Servidor EM GERAL GeralUserDB
Terceiro Servidor FBI mestre
Terceiro Servidor FBI modelo
Terceiro Servidor FBI msdb
Terceiro Servidor FBI CriminalDB
Quarto Servidor nulo nulo
Quinto Servidor EM BREVE nulo

Instrução SELECT envolvendo tabela de status

Na próxima instrução decido adicionar o status a cada elemento (servidor, instância, banco de dados) e JOINeditar cada tabela com a Statustabela da seguinte forma:

-- Advanced SELECT to get all information on Servers, Instances and Databases 
-- including their status
SELECT 
  ServerName, 
  srvst.StatusName,
  InstanceName,
  insst.StatusName,
  DatabaseName,
  dbsst.StatusName
  FROM [Server] as srv
    JOIN [Status] as srvst
      ON srv.ServerStatusID = srvst.StatusID
    LEFT JOIN [Instance] as ins
      ON srv.ServerID = ins.ServerID
    JOIN [Status] as insst
      ON ins.InstanceStatusID = insst.StatusID
    LEFT JOIN [Database] as dbs
      ON ins.InstanceID = dbs.InstanceID
    JOIN [Status] as dbsst
      ON dbs.DatabaseStatusID = dbsst.StatusID
  ;

Resultados de 2. Declaração

Para minha surpresa o servidor sem instância e banco de dados e o servidor com instância mas sem banco de dados não estavam mais listados:

Nome do servidor StatusNome Nome da instância StatusNome Nome do banco de dados StatusNome
Primeiro Servidor Produtivo EM GERAL Produtivo mestre Produtivo
Primeiro Servidor Produtivo EM GERAL Produtivo modelo Produtivo
Primeiro Servidor Produtivo EM GERAL Produtivo msdb Produtivo
Primeiro Servidor Produtivo EM GERAL Produtivo UsuárioDB1 Produtivo
Primeiro Servidor Produtivo IMPOSTOS Produtivo mestre Produtivo
Primeiro Servidor Produtivo IMPOSTOS Produtivo modelo Produtivo
Primeiro Servidor Produtivo IMPOSTOS Produtivo msdb Produtivo
Primeiro Servidor Produtivo IMPOSTOS Produtivo ImpostosDB Produtivo
Segundo Servidor ACC de produção SOCIAL ACC de produção mestre ACC de produção
Segundo Servidor ACC de produção SOCIAL ACC de produção modelo ACC de produção
Segundo Servidor ACC de produção SOCIAL ACC de produção msdb ACC de produção
Segundo Servidor ACC de produção SOCIAL ACC de produção HealthCareDB ACC de produção
Terceiro Servidor Teste ACC EM GERAL Teste ACC mestre Teste ACC
Terceiro Servidor Teste ACC EM GERAL Teste ACC modelo Teste ACC
Terceiro Servidor Teste ACC EM GERAL Teste ACC msdb Teste ACC
Terceiro Servidor Teste ACC EM GERAL Teste ACC GeralUserDB Teste ACC
Terceiro Servidor Teste ACC FBI Reservado mestre Reservado
Terceiro Servidor Teste ACC FBI Reservado modelo Reservado
Terceiro Servidor Teste ACC FBI Reservado msdb Reservado
Terceiro Servidor Teste ACC FBI Reservado CriminalDB Reservado

Descobertas / Solução

Depois de verificar várias opções com uma abordagem de tentativa e erro, descobri que JOINon the Statustable teve que ser alterado para a LEFT JOINpara permitir que a instrução exibisse o servidor sem uma instância ou um banco de dados e exibisse a instância sem um banco de dados :

-- Advanced SELECT to get all information on Servers, Instances and Databases 
-- including their status
SELECT 
  ServerName, 
  srvst.StatusName,
  InstanceName,
  insst.StatusName,
  DatabaseName,
  dbsst.StatusName
  FROM [Server] as srv
    LEFT JOIN [Status] as srvst
      ON srv.ServerStatusID = srvst.StatusID
    LEFT JOIN [Instance] as ins
      ON srv.ServerID = ins.ServerID
    LEFT JOIN [Status] as insst
      ON ins.InstanceStatusID = insst.StatusID
    LEFT JOIN [Database] as dbs
      ON ins.InstanceID = dbs.InstanceID
    LEFT JOIN [Status] as dbsst
      ON dbs.DatabaseStatusID = dbsst.StatusID;

Resultados de 3. Declaração

Nome do servidor StatusNome Nome da instância StatusNome Nome do banco de dados StatusNome
Primeiro Servidor Produtivo EM GERAL Produtivo mestre Produtivo
Primeiro Servidor Produtivo EM GERAL Produtivo modelo Produtivo
Primeiro Servidor Produtivo EM GERAL Produtivo msdb Produtivo
Primeiro Servidor Produtivo EM GERAL Produtivo UsuárioDB1 Produtivo
Primeiro Servidor Produtivo IMPOSTOS Produtivo mestre Produtivo
Primeiro Servidor Produtivo IMPOSTOS Produtivo modelo Produtivo
Primeiro Servidor Produtivo IMPOSTOS Produtivo msdb Produtivo
Primeiro Servidor Produtivo IMPOSTOS Produtivo ImpostosDB Produtivo
Segundo Servidor ACC de produção EM GERAL Desativado nulo nulo
Segundo Servidor ACC de produção SOCIAL ACC de produção mestre ACC de produção
Segundo Servidor ACC de produção SOCIAL ACC de produção modelo ACC de produção
Segundo Servidor ACC de produção SOCIAL ACC de produção msdb ACC de produção
Segundo Servidor ACC de produção SOCIAL ACC de produção HealthCareDB ACC de produção
Terceiro Servidor Teste ACC EM GERAL Teste ACC mestre Teste ACC
Terceiro Servidor Teste ACC EM GERAL Teste ACC modelo Teste ACC
Terceiro Servidor Teste ACC EM GERAL Teste ACC msdb Teste ACC
Terceiro Servidor Teste ACC EM GERAL Teste ACC GeralUserDB Teste ACC
Terceiro Servidor Teste ACC FBI Reservado mestre Reservado
Terceiro Servidor Teste ACC FBI Reservado modelo Reservado
Terceiro Servidor Teste ACC FBI Reservado msdb Reservado
Terceiro Servidor Teste ACC FBI Reservado CriminalDB Reservado
Quarto Servidor Reservado nulo nulo nulo nulo
Quinto Servidor Reservado EM BREVE Reservado nulo nulo

Material de referência

Aqui está um link para o db<>fiddle para reproduzir minhas descobertas.

Pergunta

Por que o SQL Server exige um LEFT JOINna Statustabela para itens filhos que não existem e para que a consulta exiba esses itens?

sql-server
  • 3 3 respostas
  • 431 Views

3 respostas

  • Voted
  1. Best Answer
    Charlieface
    2024-04-13T01:03:36+08:002024-04-13T01:03:36+08:00

    Você precisa aninhar suas junções. Caso contrário, o que está acontecendo é que ele espera que cada cláusula de junção individual retorne um resultado, mas não poderá se a anterior não retornar nada.

    Essentially, you want the server to take the result of the inner-join of Instance and Status, and left-join all of that back to Server.

    SELECT 
      ServerName, 
      srvst.StatusName,
      InstanceName,
      insst.StatusName,
      DatabaseName,
      dbsst.StatusName
    FROM Server as srv
      JOIN Status as srvst
        ON srv.ServerStatusID = srvst.StatusID
      LEFT JOIN (
          Instance as ins
          JOIN Status as insst
            ON ins.InstanceStatusID = insst.StatusID
        )
        ON srv.ServerID = ins.ServerID
      LEFT JOIN (
          Database as dbs
          JOIN Status as dbsst
            ON dbs.DatabaseStatusID = dbsst.StatusID
        )
        ON ins.InstanceID = dbs.InstanceID;
    

    In SQL Server, the parenthesis are not essential, they are purely for readability. The key is putting JOIN table2 ON ... inside another join, so it comes out to LEFT JOIN table1 JOIN table2 ON ... ON ... ie the ON clauses are nested.

    This is the exact equivalent of using derived subqueries:

    SELECT 
      ServerName, 
      srvst.StatusName,
      InstanceName,
      ins.StatusName,
      DatabaseName,
      dbs.StatusName
    FROM Server as srv
      JOIN Status as srvst
        ON srv.ServerStatusID = srvst.StatusID
      LEFT JOIN (
          SELECT ins.*, insst.StatusName
          FROM Instance as ins
          JOIN Status as insst
            ON ins.InstanceStatusID = insst.StatusID
        ) ins
        ON srv.ServerID = ins.ServerID
      LEFT JOIN (
          SELECT dbs.*, dbsst.StatusName
          Database as dbs
          JOIN Status as dbsst
            ON dbs.DatabaseStatusID = dbsst.StatusID
        ) dbs
        ON ins.InstanceID = dbs.InstanceID;
    
    • 6
  2. Shivam Kumar
    2024-04-12T23:20:40+08:002024-04-12T23:20:40+08:00

    Porque a junção com a tabela de status para os itens filhos está acontecendo com base em ins.InstanceStatusID, dbs.DatabaseStatusID que acaba sendo NULL quando as junções anteriores ocorrem em algumas linhas.

    Quando você faz uma junção interna posteriormente, ele mostra as linhas correspondentes em ambas as tabelas e para obter também as linhas com valores nulos, você terá que usar a junção à esquerda.

    Para visualizar o mesmo:

    SELECT 
      ServerName, 
      srvst.StatusName,
      InstanceName,
    
      DatabaseName,
    
      ins.InstanceStatusID,
      dbs.DatabaseStatusID 
      
      FROM [Server] as srv
        LEFT JOIN [Status] as srvst
          ON srv.ServerStatusID = srvst.StatusID
        LEFT JOIN [Instance] as ins
          ON srv.ServerID = ins.ServerID
        LEFT JOIN [Database] as dbs
          ON ins.InstanceID = dbs.InstanceID 
    

    enter image description here

    SELECT TMP.* ,    insst.StatusName,
      dbsst.StatusName FROM (SELECT 
      ServerName, 
      srvst.StatusName,
      InstanceName,
    
      DatabaseName,
    
      ins.InstanceStatusID,
      dbs.DatabaseStatusID 
      
      FROM [Server] as srv
        LEFT JOIN [Status] as srvst
          ON srv.ServerStatusID = srvst.StatusID
        LEFT JOIN [Instance] as ins
          ON srv.ServerID = ins.ServerID
        LEFT JOIN [Database] as dbs
          ON ins.InstanceID = dbs.InstanceID ) AS TMP
    
        /* Here if left join is not used values which are null are skipped and only values which are in both tables are returned */
    
        JOIN [Status] as insst
          ON TMP.InstanceStatusID = insst.StatusID 
        JOIN [Status] as dbsst
          ON TMP.DatabaseStatusID = dbsst.StatusID ;
    

    enter image description here

    Que depois de usar a junção à esquerda extrai todos os registros do conjunto de resultados esquerdo e os valores correspondentes de ambas as tabelas unidas ao conjunto de resultados

    SELECT TMP.* ,    insst.StatusName,
      dbsst.StatusName FROM (SELECT 
      ServerName, 
      srvst.StatusName,
      InstanceName,
    
      DatabaseName,
    
      ins.InstanceStatusID,
      dbs.DatabaseStatusID 
      
      FROM [Server] as srv
        LEFT JOIN [Status] as srvst
          ON srv.ServerStatusID = srvst.StatusID
        LEFT JOIN [Instance] as ins
          ON srv.ServerID = ins.ServerID
        LEFT JOIN [Database] as dbs
          ON ins.InstanceID = dbs.InstanceID ) AS TMP
        
        LEFT JOIN [Status] as insst
          ON TMP.InstanceStatusID = insst.StatusID 
        LEFT JOIN [Status] as dbsst
          ON TMP.DatabaseStatusID = dbsst.StatusID ;
    

    enter image description here

    • 2
  3. Paul White
    2024-04-15T16:01:06+08:002024-04-15T16:01:06+08:00

    Why does SQL Server require a LEFT JOIN on the Status table for child items that do not exist and for the query to display these items?

    It doesn't, in general. It is just one of the ways to express your requirement that produces the results you are after.

    Your query tries to (inner) join from a null-extended row (created by an outer join) to the Status table using a join predicate that looks for an equality match on StatusID values.

    This predicate does not return true because StatusID is null on one side, there is no matching null in the Status table, and the equality predicate would reject null matches anyway. Since the join predicate fails and you have specified an inner join, no result row is produced.

    For clarity, the inner join to Status could produce a result with a different join predicate.

    For example, there could be a null StatusID in the Status table (something your schema allows) and the join predicate could use IS NOT DISTINCT FROM or an equivalent test where nulls match. You could also use COALESCE or ISNULL in the join predicate to select a particular status (as a default) without adding any new status rows.

    There are many possibilities; the point is you could write a join predicate that would produce a match. The one you have does not.

    It is possible you thought SQL Server would skip the join to Status for any rows previously null-extended by an outer join, but things don't work that way. An outer join is not an 'optional' join with subsequent short-circuiting. Perhaps some people have a mental image of them functioning that way.

    Making any following joins 'outer' as a way to fix the output is close to using 'magic'—a recipe that works but isn't understood.

    Why does it work here? Because, with a left outer join, the null-extended row is preserved despite there being no match in Status according to the join predicate. The missing status columns are populated with null, as usual.

    An alternative solution

    People do seem to find left outer joins more intuitive to work with, but there's no particular reason to prefer them otherwise. You don't have to start at the top of the hierarchy and 'join down' to rows that may or may not be present.

    Let's see what happens with your schema if we instead start at the bottom of the hierarchy (the [Database] table) and work up. The first join to Status is straightforward:

    SELECT
        DBS.DatabaseName,
        DBSST.StatusName
    FROM dbo.[Database] AS DBS
    JOIN dbo.[Status] AS DBSST
        ON DBSST.StatusID = DBS.DatabaseStatusID;
    

    Moving up the hierarchy, we now need to add rows from the Instance table. We can't use a left outer join or inner join because there may be an instance without a database. The natural solution is to use a right join, with the associated inner join to Status:

    SELECT
        INS.InstanceName,
        INSST.StatusName,
        DBS.DatabaseName,
        DBSST.StatusName
    FROM dbo.[Database] AS DBS
    JOIN dbo.[Status] AS DBSST
        ON DBSST.StatusID = DBS.DatabaseStatusID
    RIGHT JOIN dbo.Instance AS INS
        ON INS.InstanceID = DBS.InstanceID
    JOIN dbo.[Status] AS INSST
        ON INSST.StatusID = INS.InstanceStatusID;
    

    There is no need for 'nested' joins here. We quite naturally add in any instances without a database, adding null-extended database rows as necessary. All instances are accounted for, so the inner join to Status is perfectly correct.

    We follow the same approach to add in rows from the Server table, along with its status:

    SELECT
        SRV.ServerName,
        SVS.StatusName,
        INS.InstanceName,
        INSST.StatusName,
        DBS.DatabaseName,
        DBSST.StatusName
    FROM dbo.[Database] AS DBS
    JOIN dbo.[Status] AS DBSST
        ON DBSST.StatusID = DBS.DatabaseStatusID
    RIGHT JOIN dbo.Instance AS INS
        ON INS.InstanceID = DBS.InstanceID
    JOIN dbo.[Status] AS INSST
        ON INSST.StatusID = INS.InstanceStatusID
    RIGHT JOIN dbo.[Server] AS SRV
        ON SRV.ServerID = INS.ServerID
    JOIN dbo.[Status] AS SVS
        ON SVS.StatusID = SRV.ServerStatusID;
    

    This produces the results you want, without invoking any magic.

    There are any number of ways to write a correct query specification for your requirement. I show a RIGHT JOIN alternative mostly because people seem somewhat scared by right outer joins or consider them redundant.

    • 2

relate perguntas

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

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

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

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

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

Sidebar

Stats

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

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

    • 12 respostas
  • Marko Smith

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

    • 3 respostas
  • Marko Smith

    Selecione qual tem data máxima ou data mais recente

    • 3 respostas
  • Marko Smith

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

    • 4 respostas
  • Marko Smith

    Listar todas as colunas de uma tabela especificada

    • 5 respostas
  • Marko Smith

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

    • 4 respostas
  • Marko Smith

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

    • 4 respostas
  • Marko Smith

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

    • 10 respostas
  • Marko Smith

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

    • 4 respostas
  • Marko Smith

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

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

Hot tag

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

Explore

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

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve