Saltar a contenido

pgAdmin Cheatsheet

< < > > > > > > > "Clase de inscripción" pgAdmin es la plataforma de administración y desarrollo de código abierto más popular y rico en características para PostgreSQL. Proporciona una interfaz web para gestionar bases de datos PostgreSQL con herramientas integrales para la administración de bases de datos, el desarrollo de consultas y la vigilancia. ▪/p] ■/div titulada

########################################################################################################################################################################################################################################################## Copiar todos los comandos
########################################################################################################################################################################################################################################################## Generar PDF seleccionado/button

■/div titulada ■/div titulada

Cuadro de contenidos

Instalación

Instalación Ubuntu/Debian

# Install pgAdmin 4 from official repository
curl https://www.pgadmin.org/static/packages_pgadmin_org.pub | sudo apt-key add
sudo sh -c 'echo "deb https://ftp.postgresql.org/pub/pgadmin/pgadmin4/apt/$(lsb_release -cs) pgadmin4 main" > /etc/apt/sources.list.d/pgadmin4.list'

# Update package list
sudo apt update

# Install pgAdmin 4 (web mode)
sudo apt install pgadmin4-web

# Install pgAdmin 4 (desktop mode)
sudo apt install pgadmin4-desktop

# Configure web mode
sudo /usr/pgadmin4/bin/setup-web.sh

# The setup script will:
# - Create a virtual environment
# - Configure Apache/Nginx
# - Set up initial user account
# - Configure SSL (optional)

# Access pgAdmin web interface
# http://your-server-ip/pgadmin4
# or
# https://your-server-ip/pgadmin4 (if SSL configured)

CentOS/RHEL Instalación

# Install PostgreSQL repository
sudo yum install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm

# Install pgAdmin 4
sudo yum install pgadmin4-web

# Configure web mode
sudo /usr/pgadmin4/bin/setup-web.sh

# For CentOS 8/RHEL 8, use dnf
sudo dnf install pgadmin4-web

Docker Instalación

# Run pgAdmin 4 with Docker
docker run -p 80:80 \
    -e 'PGADMIN_DEFAULT_EMAIL=admin@example.com' \
    -e 'PGADMIN_DEFAULT_PASSWORD=password' \
    -d dpage/pgadmin4

# Run with persistent storage
docker run -p 80:80 \
    -e 'PGADMIN_DEFAULT_EMAIL=admin@example.com' \
    -e 'PGADMIN_DEFAULT_PASSWORD=password' \
    -v pgadmin-data:/var/lib/pgadmin \
    -d dpage/pgadmin4

# Docker Compose setup
cat > docker-compose.yml << EOF
version: '3.8'
services:
  postgres:
    image: postgres:13
    container_name: postgres
    environment:
      POSTGRES_DB: testdb
      POSTGRES_USER: testuser
      POSTGRES_PASSWORD: testpass
    ports:
      - "5432:5432"
    volumes:
      - postgres-data:/var/lib/postgresql/data

  pgadmin:
    image: dpage/pgadmin4
    container_name: pgadmin
    environment:
      PGADMIN_DEFAULT_EMAIL: admin@example.com
      PGADMIN_DEFAULT_PASSWORD: password
      PGADMIN_CONFIG_SERVER_MODE: 'False'
    ports:
      - "8080:80"
    volumes:
      - pgadmin-data:/var/lib/pgadmin
    depends_on:
      - postgres

volumes:
  postgres-data:
  pgadmin-data:
EOF

docker-compose up -d

# Access pgAdmin at http://localhost:8080

Instalación de Windows

# Download pgAdmin 4 installer from https://www.pgadmin.org/download/
# Run the installer as administrator

# Alternative: Install via Chocolatey
choco install pgadmin4

# Alternative: Install via winget
winget install PostgreSQL.pgAdmin

# pgAdmin will be available in Start Menu
# Default installation path: C:\Program Files\pgAdmin 4\

macOS Instalación

# Install via Homebrew
brew install --cask pgadmin4

# Alternative: Download from official website
# https://www.pgadmin.org/download/pgadmin-4-macos/

# pgAdmin will be available in Applications folder

Configuración

Configuración inicial

# Web mode initial configuration
sudo /usr/pgadmin4/bin/setup-web.sh

# During setup, you'll be prompted for:
# - Email address (admin user)
# - Password
# - Apache/Nginx configuration
# - SSL certificate setup (optional)

# Configuration files location:
# /usr/pgadmin4/web/config_local.py (local configuration)
# /usr/pgadmin4/web/config.py (default configuration)

Archivo de configuración

# config_local.py - Local configuration overrides

# Server mode (True for web, False for desktop)
SERVER_MODE = True

# Debug mode (disable in production)
DEBUG = False

# Data directory
DATA_DIR = '/var/lib/pgadmin'

# Log file location
LOG_FILE = '/var/log/pgadmin4/pgadmin4.log'

# Database configuration
SQLITE_PATH = '/var/lib/pgadmin/pgadmin4.db'

# Session configuration
SESSION_DB_PATH = '/var/lib/pgadmin/sessions'
SESSION_COOKIE_NAME = 'pga4_session'
SESSION_COOKIE_DOMAIN = None
SESSION_COOKIE_SECURE = False
SESSION_COOKIE_HTTPONLY = True
SESSION_EXPIRATION_TIME = 1  # hours

# Security
SECRET_KEY = 'your-secret-key-here'
SECURITY_PASSWORD_SALT = 'your-password-salt-here'

# CSRF protection
WTF_CSRF_ENABLED = True
WTF_CSRF_TIME_LIMIT = None

# Mail server configuration (for password reset)
MAIL_SERVER = 'localhost'
MAIL_PORT = 587
MAIL_USE_SSL = False
MAIL_USE_TLS = True
MAIL_USERNAME = 'your-email@example.com'
MAIL_PASSWORD = 'your-email-password'
MAIL_DEFAULT_SENDER = 'pgadmin@example.com'

# LDAP authentication
AUTHENTICATION_SOURCES = ['internal', 'ldap']
LDAP_SERVER_URI = 'ldap://ldap.example.com:389'
LDAP_BASE_DN = 'dc=example,dc=com'
LDAP_USERNAME_ATTRIBUTE = 'uid'
LDAP_SEARCH_BASE_DN = 'ou=users,dc=example,dc=com'
LDAP_SEARCH_FILTER = '(objectClass=person)'

# Logging configuration
CONSOLE_LOG_LEVEL = 'WARNING'
FILE_LOG_LEVEL = 'INFO'

# Query tool configuration
DEFAULT_QUERY_TOOL_TAB_SIZE = 4
QUERY_HISTORY_MAX_COUNT = 20

# Browser configuration
DEFAULT_BINARY_PATHS = {
    'pg_dump': '/usr/bin/pg_dump',
    'pg_restore': '/usr/bin/pg_restore',
    'psql': '/usr/bin/psql'
}

Configuración de Apache

# /etc/apache2/sites-available/pgadmin4.conf

<VirtualHost *:80>
    ServerName pgadmin.example.com
    DocumentRoot /usr/pgadmin4/web

    WSGIDaemonProcess pgadmin processes=1 threads=25 python-home=/usr/pgadmin4/venv
    WSGIProcessGroup pgadmin
    WSGIScriptAlias / /usr/pgadmin4/web/pgAdmin4.wsgi

    <Directory /usr/pgadmin4/web>
        WSGIProcessGroup pgadmin
        WSGIApplicationGroup %{GLOBAL}
        Require all granted
    </Directory>

    # Static files
    Alias /static /usr/pgadmin4/web/static
    <Directory /usr/pgadmin4/web/static>
        Require all granted
    </Directory>

    # Logging
    ErrorLog ${APACHE_LOG_DIR}/pgadmin4_error.log
    CustomLog ${APACHE_LOG_DIR}/pgadmin4_access.log combined
</VirtualHost>

# Enable SSL (recommended)
<VirtualHost *:443>
    ServerName pgadmin.example.com
    DocumentRoot /usr/pgadmin4/web

    SSLEngine on
    SSLCertificateFile /path/to/certificate.crt
    SSLCertificateKeyFile /path/to/private.key

    WSGIDaemonProcess pgadmin-ssl processes=1 threads=25 python-home=/usr/pgadmin4/venv
    WSGIProcessGroup pgadmin-ssl
    WSGIScriptAlias / /usr/pgadmin4/web/pgAdmin4.wsgi

    <Directory /usr/pgadmin4/web>
        WSGIProcessGroup pgadmin-ssl
        WSGIApplicationGroup %{GLOBAL}
        Require all granted
    </Directory>

    Alias /static /usr/pgadmin4/web/static
    <Directory /usr/pgadmin4/web/static>
        Require all granted
    </Directory>
</VirtualHost>

Configuración Nginx

# /etc/nginx/sites-available/pgadmin4

server {
    listen 80;
    server_name pgadmin.example.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name pgadmin.example.com;

    ssl_certificate /path/to/certificate.crt;
    ssl_certificate_key /path/to/private.key;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;

    location / {
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_pass http://unix:/tmp/pgadmin4.sock;
    }

    location /static/ {
        alias /usr/pgadmin4/web/static/;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
}

Gestión de servidores

Agregar PostgreSQL Servidores

-- Via pgAdmin Interface:
-- 1. Right-click "Servers" in browser tree
-- 2. Select "Create" > "Server..."
-- 3. Fill in connection details:

-- General Tab:
-- Name: My PostgreSQL Server
-- Server group: Servers (default)
-- Comments: Production database server

-- Connection Tab:
-- Host name/address: localhost (or IP address)
-- Port: 5432
-- Maintenance database: postgres
-- Username: postgres
-- Password: your_password
-- Save password: Yes (optional)

-- SSL Tab:
-- SSL mode: Prefer (or Require for production)
-- Client certificate: /path/to/client.crt
-- Client key: /path/to/client.key
-- Root certificate: /path/to/ca.crt

-- Advanced Tab:
-- DB restriction: database1,database2 (optional)
-- Host address: (leave blank usually)
-- Service: (for Windows service connections)

Ejemplos de conexión de servidor

# Programmatic server registration (servers.json)
{
    "Servers": {
        "1": {
            "Name": "Local PostgreSQL",
            "Group": "Servers",
            "Host": "localhost",
            "Port": 5432,
            "MaintenanceDB": "postgres",
            "Username": "postgres",
            "SSLMode": "prefer",
            "Comment": "Local development server"
        },
        "2": {
            "Name": "Production Server",
            "Group": "Production",
            "Host": "prod.example.com",
            "Port": 5432,
            "MaintenanceDB": "postgres",
            "Username": "admin",
            "SSLMode": "require",
            "SSLCert": "/path/to/client.crt",
            "SSLKey": "/path/to/client.key",
            "SSLRootCert": "/path/to/ca.crt",
            "Comment": "Production database server"
        }
    }
}

Solución de problemas

# Test PostgreSQL connection from command line
psql -h localhost -p 5432 -U postgres -d postgres

# Check PostgreSQL service status
sudo systemctl status postgresql

# Check PostgreSQL configuration
sudo -u postgres psql -c "SHOW config_file;"
sudo -u postgres psql -c "SHOW hba_file;"

# Common connection issues:

# 1. PostgreSQL not running
sudo systemctl start postgresql

# 2. Wrong port
sudo -u postgres psql -c "SHOW port;"

# 3. Authentication failure - check pg_hba.conf
sudo vim /etc/postgresql/13/main/pg_hba.conf

# Example pg_hba.conf entries:
# local   all             postgres                                peer
# local   all             all                                     md5
# host    all             all             127.0.0.1/32            md5
# host    all             all             ::1/128                 md5

# 4. Connection not allowed - check postgresql.conf
sudo vim /etc/postgresql/13/main/postgresql.conf

# Set listen_addresses:
# listen_addresses = 'localhost'  # for local only
# listen_addresses = '*'          # for all addresses

# Restart PostgreSQL after configuration changes
sudo systemctl restart postgresql

Operaciones de base de datos

Creación de bases de datos

-- Via SQL in Query Tool:
CREATE DATABASE myapp
    WITH 
    OWNER = myuser
    ENCODING = 'UTF8'
    LC_COLLATE = 'en_US.UTF-8'
    LC_CTYPE = 'en_US.UTF-8'
    TABLESPACE = pg_default
    CONNECTION LIMIT = -1
    TEMPLATE = template0;

-- Create database with specific settings
CREATE DATABASE analytics
    WITH 
    OWNER = analyst
    ENCODING = 'UTF8'
    LC_COLLATE = 'C'
    LC_CTYPE = 'C'
    TABLESPACE = pg_default
    CONNECTION LIMIT = 50
    TEMPLATE = template0;

-- Via pgAdmin Interface:
-- 1. Right-click server name
-- 2. Select "Create" > "Database..."
-- 3. Fill in database details:
--    - Database: myapp
--    - Owner: myuser
--    - Encoding: UTF8
--    - Template: template0
--    - Collation: en_US.UTF-8
--    - Character type: en_US.UTF-8
--    - Connection limit: -1 (unlimited)

Gestión de bases de datos

-- List all databases
SELECT datname, datowner, encoding, datcollate, datctype 
FROM pg_database;

-- Show database size
SELECT 
    datname as database_name,
    pg_size_pretty(pg_database_size(datname)) as size
FROM pg_database
ORDER BY pg_database_size(datname) DESC;

-- Show database connections
SELECT 
    datname,
    numbackends as active_connections,
    datconnlimit as connection_limit
FROM pg_stat_database;

-- Rename database
ALTER DATABASE old_name RENAME TO new_name;

-- Change database owner
ALTER DATABASE myapp OWNER TO new_owner;

-- Set database connection limit
ALTER DATABASE myapp CONNECTION LIMIT 100;

-- Drop database
DROP DATABASE IF EXISTS myapp;

-- Terminate connections to database before dropping
SELECT pg_terminate_backend(pg_stat_activity.pid)
FROM pg_stat_activity
WHERE pg_stat_activity.datname = 'myapp'
  AND pid <> pg_backend_pid();

Operaciones de esquema

-- Create schema
CREATE SCHEMA sales AUTHORIZATION sales_user;

-- Create schema with specific privileges
CREATE SCHEMA reporting;
GRANT USAGE ON SCHEMA reporting TO analyst;
GRANT CREATE ON SCHEMA reporting TO analyst;

-- List schemas
SELECT schema_name, schema_owner 
FROM information_schema.schemata
WHERE schema_name NOT IN ('information_schema', 'pg_catalog', 'pg_toast');

-- Show schema privileges
SELECT 
    schemaname,
    schemaowner,
    schemaacl
FROM pg_namespace n
JOIN pg_user u ON n.nspowner = u.usesysid
WHERE nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast');

-- Drop schema
DROP SCHEMA IF EXISTS old_schema CASCADE;

-- Rename schema
ALTER SCHEMA old_name RENAME TO new_name;

-- Change schema owner
ALTER SCHEMA sales OWNER TO new_owner;

Herramienta de consultas

Operaciones básicas de consulta

-- Query Tool Features:
-- 1. Syntax highlighting
-- 2. Auto-completion
-- 3. Query execution
-- 4. Result export
-- 5. Query history
-- 6. Explain plans
-- 7. Query profiling

-- Basic SELECT queries
SELECT * FROM users;

SELECT id, username, email, created_at 
FROM users 
WHERE created_at > '2023-01-01'
ORDER BY created_at DESC
LIMIT 10;

-- Complex queries with JOINs
SELECT 
    u.username,
    u.email,
    COUNT(p.id) as post_count,
    MAX(p.created_at) as last_post
FROM users u
LEFT JOIN posts p ON u.id = p.user_id
WHERE u.active = true
GROUP BY u.id, u.username, u.email
HAVING COUNT(p.id) > 0
ORDER BY post_count DESC;

-- Window functions
SELECT 
    username,
    email,
    created_at,
    ROW_NUMBER() OVER (ORDER BY created_at) as user_number,
    RANK() OVER (ORDER BY created_at) as user_rank,
    LAG(created_at) OVER (ORDER BY created_at) as prev_user_date
FROM users;

-- Common Table Expressions (CTEs)
WITH active_users AS (
    SELECT user_id, COUNT(*) as activity_count
    FROM user_activities
    WHERE activity_date > CURRENT_DATE - INTERVAL '30 days'
    GROUP BY user_id
    HAVING COUNT(*) > 10
),
user_stats AS (
    SELECT 
        u.id,
        u.username,
        au.activity_count,
        CASE 
            WHEN au.activity_count > 100 THEN 'Very Active'
            WHEN au.activity_count > 50 THEN 'Active'
            ELSE 'Moderate'
        END as activity_level
    FROM users u
    JOIN active_users au ON u.id = au.user_id
)
SELECT * FROM user_stats ORDER BY activity_count DESC;

Ejecución y análisis de consultas

-- Explain query execution plan
EXPLAIN SELECT * FROM users WHERE email = 'john@example.com';

-- Explain with detailed analysis
EXPLAIN (ANALYZE, BUFFERS, VERBOSE) 
SELECT u.*, COUNT(p.id) as post_count
FROM users u
LEFT JOIN posts p ON u.id = p.user_id
GROUP BY u.id;

-- Show query execution time
\timing on
SELECT COUNT(*) FROM large_table;
\timing off

-- Analyze table statistics
ANALYZE users;

-- Show table statistics
SELECT 
    schemaname,
    tablename,
    n_tup_ins as inserts,
    n_tup_upd as updates,
    n_tup_del as deletes,
    n_live_tup as live_tuples,
    n_dead_tup as dead_tuples,
    last_vacuum,
    last_autovacuum,
    last_analyze,
    last_autoanalyze
FROM pg_stat_user_tables
ORDER BY n_live_tup DESC;

Características de la herramienta de consulta

-- Query Tool Shortcuts:
-- F5 or Ctrl+Enter: Execute query
-- F7: Explain query
-- Shift+F7: Explain analyze
-- Ctrl+Shift+C: Comment/uncomment
-- Ctrl+A: Select all
-- Ctrl+/: Toggle comment
-- Ctrl+Space: Auto-complete
-- Ctrl+Shift+F: Format SQL

-- Query history access:
-- 1. Click "History" tab in Query Tool
-- 2. View previously executed queries
-- 3. Re-execute or edit historical queries

-- Export results:
-- 1. Execute query
-- 2. Click "Download" button in results
-- 3. Choose format: CSV, Excel, JSON, etc.

-- Save queries:
-- 1. Write query in editor
-- 2. Click "Save" button or Ctrl+S
-- 3. Provide filename and location
-- 4. Access saved queries from "Files" tab

-- Query profiling:
-- 1. Enable auto-explain in postgresql.conf:
-- shared_preload_libraries = 'auto_explain'
-- auto_explain.log_min_duration = 1000
-- auto_explain.log_analyze = on
-- auto_explain.log_buffers = on

-- 2. Check PostgreSQL logs for execution plans

Características de la consulta avanzada

-- Parameterized queries (using pgAdmin variables)
-- Define variable: $$user_id$$ = 123
SELECT * FROM users WHERE id = $$user_id$$;

-- Multiple result sets
SELECT 'User Count' as metric, COUNT(*) as value FROM users
UNION ALL
SELECT 'Post Count' as metric, COUNT(*) as value FROM posts
UNION ALL
SELECT 'Active Users' as metric, COUNT(*) as value FROM users WHERE last_login > CURRENT_DATE - INTERVAL '7 days';

-- JSON operations
SELECT 
    id,
    username,
    profile->>'first_name' as first_name,
    profile->>'last_name' as last_name,
    profile->'preferences'->>'theme' as theme
FROM users
WHERE profile ? 'preferences';

-- Array operations
SELECT 
    id,
    username,
    tags,
    array_length(tags, 1) as tag_count,
    'postgresql' = ANY(tags) as has_postgresql_tag
FROM posts
WHERE tags IS NOT NULL;

-- Full-text search
SELECT 
    id,
    title,
    content,
    ts_rank(to_tsvector('english', title || ' ' || content), 
            plainto_tsquery('english', 'postgresql database')) as rank
FROM articles
WHERE to_tsvector('english', title || ' ' || content) @@ 
      plainto_tsquery('english', 'postgresql database')
ORDER BY rank DESC;

Gestión de esquemas

Operaciones

-- Create table with various column types
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    description TEXT,
    price DECIMAL(10,2) NOT NULL CHECK (price > 0),
    category_id INTEGER REFERENCES categories(id),
    tags TEXT[],
    metadata JSONB,
    is_active BOOLEAN DEFAULT true,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

-- Add constraints
ALTER TABLE products 
ADD CONSTRAINT uk_products_name UNIQUE (name);

ALTER TABLE products 
ADD CONSTRAINT chk_products_price CHECK (price BETWEEN 0.01 AND 999999.99);

-- Create indexes
CREATE INDEX idx_products_category ON products(category_id);
CREATE INDEX idx_products_active ON products(is_active) WHERE is_active = true;
CREATE INDEX idx_products_tags ON products USING GIN(tags);
CREATE INDEX idx_products_metadata ON products USING GIN(metadata);

-- Full-text search index
CREATE INDEX idx_products_search ON products USING GIN(to_tsvector('english', name || ' ' || COALESCE(description, '')));

-- Partial index
CREATE INDEX idx_products_expensive ON products(price) WHERE price > 1000;

-- Composite index
CREATE INDEX idx_products_category_price ON products(category_id, price);

View Management

-- Create simple view
CREATE VIEW active_products AS
SELECT id, name, price, category_id
FROM products
WHERE is_active = true;

-- Create complex view with JOINs
CREATE VIEW product_summary AS
SELECT 
    p.id,
    p.name,
    p.price,
    c.name as category_name,
    COUNT(r.id) as review_count,
    AVG(r.rating) as avg_rating
FROM products p
LEFT JOIN categories c ON p.category_id = c.id
LEFT JOIN reviews r ON p.id = r.product_id
WHERE p.is_active = true
GROUP BY p.id, p.name, p.price, c.name;

-- Create materialized view
CREATE MATERIALIZED VIEW monthly_sales AS
SELECT 
    DATE_TRUNC('month', order_date) as month,
    COUNT(*) as order_count,
    SUM(total_amount) as total_sales,
    AVG(total_amount) as avg_order_value
FROM orders
WHERE order_date >= CURRENT_DATE - INTERVAL '2 years'
GROUP BY DATE_TRUNC('month', order_date)
ORDER BY month;

-- Refresh materialized view
REFRESH MATERIALIZED VIEW monthly_sales;

-- Create updatable view
CREATE VIEW user_profiles AS
SELECT id, username, email, first_name, last_name
FROM users
WHERE is_active = true;

-- Make view updatable with rules
CREATE OR REPLACE RULE user_profiles_update AS
ON UPDATE TO user_profiles
DO INSTEAD
UPDATE users SET
    username = NEW.username,
    email = NEW.email,
    first_name = NEW.first_name,
    last_name = NEW.last_name,
    updated_at = CURRENT_TIMESTAMP
WHERE id = OLD.id;

Función y gestión de procedimientos

-- Create function
CREATE OR REPLACE FUNCTION calculate_age(birth_date DATE)
RETURNS INTEGER AS $$
BEGIN
    RETURN EXTRACT(YEAR FROM AGE(birth_date));
END;
$$ LANGUAGE plpgsql;

-- Create function with multiple parameters
CREATE OR REPLACE FUNCTION get_user_stats(user_id INTEGER)
RETURNS TABLE(
    total_posts INTEGER,
    total_comments INTEGER,
    avg_post_length NUMERIC,
    last_activity TIMESTAMP
) AS $$
BEGIN
    RETURN QUERY
    SELECT 
        (SELECT COUNT(*)::INTEGER FROM posts WHERE posts.user_id = get_user_stats.user_id),
        (SELECT COUNT(*)::INTEGER FROM comments WHERE comments.user_id = get_user_stats.user_id),
        (SELECT AVG(LENGTH(content))::NUMERIC FROM posts WHERE posts.user_id = get_user_stats.user_id),
        (SELECT MAX(GREATEST(
            COALESCE((SELECT MAX(created_at) FROM posts WHERE posts.user_id = get_user_stats.user_id), '1970-01-01'::TIMESTAMP),
            COALESCE((SELECT MAX(created_at) FROM comments WHERE comments.user_id = get_user_stats.user_id), '1970-01-01'::TIMESTAMP)
        )));
END;
$$ LANGUAGE plpgsql;

-- Create stored procedure (PostgreSQL 11+)
CREATE OR REPLACE PROCEDURE update_product_prices(
    category_name TEXT,
    price_increase_percent DECIMAL
)
LANGUAGE plpgsql
AS $$
DECLARE
    affected_count INTEGER;
BEGIN
    UPDATE products 
    SET price = price * (1 + price_increase_percent / 100),
        updated_at = CURRENT_TIMESTAMP
    FROM categories c
    WHERE products.category_id = c.id 
    AND c.name = category_name;

    GET DIAGNOSTICS affected_count = ROW_COUNT;

    RAISE NOTICE 'Updated % products in category %', affected_count, category_name;

    COMMIT;
END;
$$;

-- Create trigger function
CREATE OR REPLACE FUNCTION update_modified_column()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = CURRENT_TIMESTAMP;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- Create trigger
CREATE TRIGGER update_products_modtime
    BEFORE UPDATE ON products
    FOR EACH ROW
    EXECUTE FUNCTION update_modified_column();

Sequence Management

-- Create sequence
CREATE SEQUENCE order_number_seq
    START WITH 1000
    INCREMENT BY 1
    MINVALUE 1000
    MAXVALUE 999999999
    CACHE 1;

-- Use sequence in table
CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    order_number INTEGER DEFAULT nextval('order_number_seq'),
    customer_id INTEGER NOT NULL,
    order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    total_amount DECIMAL(10,2)
);

-- Alter sequence
ALTER SEQUENCE order_number_seq RESTART WITH 5000;
ALTER SEQUENCE order_number_seq INCREMENT BY 5;

-- Show sequence information
SELECT 
    schemaname,
    sequencename,
    start_value,
    min_value,
    max_value,
    increment_by,
    cycle,
    cache_size,
    last_value
FROM pg_sequences;

-- Reset sequence to match table data
SELECT setval('order_number_seq', (SELECT MAX(order_number) FROM orders));

Gestión de usuarios

Crear usuarios y roles

-- Create user
CREATE USER john_doe WITH PASSWORD 'secure_password';

-- Create user with specific attributes
CREATE USER admin_user WITH 
    PASSWORD 'admin_password'
    CREATEDB
    CREATEROLE
    LOGIN
    VALID UNTIL '2024-12-31';

-- Create role (group)
CREATE ROLE developers;
CREATE ROLE analysts;
CREATE ROLE managers;

-- Create role with inheritance
CREATE ROLE app_users INHERIT;

-- Grant role to user
GRANT developers TO john_doe;
GRANT analysts TO jane_smith;

-- Create user and assign to role
CREATE USER developer1 WITH PASSWORD 'dev_password';
GRANT developers TO developer1;

Gestión de los privilegios

-- Database privileges
GRANT CONNECT ON DATABASE myapp TO developers;
GRANT CREATE ON DATABASE myapp TO developers;

-- Schema privileges
GRANT USAGE ON SCHEMA public TO developers;
GRANT CREATE ON SCHEMA public TO developers;

-- Table privileges
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO developers;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO analysts;

-- Grant privileges on future tables
ALTER DEFAULT PRIVILEGES IN SCHEMA public 
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO developers;

-- Sequence privileges
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO developers;

-- Function privileges
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO developers;

-- Column-level privileges
GRANT SELECT (id, username, email) ON users TO analysts;
GRANT UPDATE (email, last_login) ON users TO app_users;

-- Revoke privileges
REVOKE DELETE ON users FROM developers;
REVOKE ALL PRIVILEGES ON DATABASE myapp FROM old_user;

Row Level Security (RLS)

-- Enable RLS on table
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;

-- Create policy for users to see only their own posts
CREATE POLICY user_posts_policy ON posts
    FOR ALL
    TO app_users
    USING (user_id = current_setting('app.current_user_id')::INTEGER);

-- Create policy for managers to see all posts
CREATE POLICY manager_posts_policy ON posts
    FOR ALL
    TO managers
    USING (true);

-- Create policy for different operations
CREATE POLICY posts_select_policy ON posts
    FOR SELECT
    TO public
    USING (status = 'published' OR user_id = current_setting('app.current_user_id')::INTEGER);

CREATE POLICY posts_insert_policy ON posts
    FOR INSERT
    TO app_users
    WITH CHECK (user_id = current_setting('app.current_user_id')::INTEGER);

CREATE POLICY posts_update_policy ON posts
    FOR UPDATE
    TO app_users
    USING (user_id = current_setting('app.current_user_id')::INTEGER)
    WITH CHECK (user_id = current_setting('app.current_user_id')::INTEGER);

-- Set application context
SELECT set_config('app.current_user_id', '123', true);

-- Disable RLS for superuser
ALTER TABLE posts FORCE ROW LEVEL SECURITY;

Gestión del usuario mediante pgAdmin

User Management via pgAdmin Interface:

Creating Users:
1. Expand server in browser tree
2. Right-click "Login/Group Roles"
3. Select "Create" > "Login/Group Role..."
4. General tab:
   - Name: username
   - Comments: User description
5. Definition tab:
   - Password: user password
   - Password expiration: optional date
6. Privileges tab:
   - Can login: Yes
   - Superuser: No (usually)
   - Create roles: No (usually)
   - Create databases: No (usually)
   - Inherit rights from parent roles: Yes
   - Can initiate streaming replication: No (usually)
7. Membership tab:
   - Add user to existing roles
8. Parameters tab:
   - Set user-specific parameters
9. Security tab:
   - Set security labels

Managing Privileges:
1. Right-click database/schema/table
2. Select "Properties"
3. Go to "Security" tab
4. Click "+" to add new privilege
5. Select grantee (user/role)
6. Select privileges to grant
7. Set grant options if needed

Respaldo y restauración

Base de datos

-- Via pgAdmin Interface:
-- 1. Right-click database
-- 2. Select "Backup..."
-- 3. Configure backup options:

-- General Tab:
-- Filename: /path/to/backup.sql
-- Format: Custom, Tar, Plain (SQL), Directory
-- Compression: 0-9 (for custom format)
-- Encoding: UTF8

-- Dump Options Tab:
-- Sections:
-- - Pre-data (schema)
-- - Data
-- - Post-data (indexes, triggers, etc.)

-- Type of objects:
-- - Only data
-- - Only schema
-- - Data and schema

-- Don't save:
-- - Owner
-- - Privilege
-- - Tablespace
-- - Unlogged table data

-- Queries:
-- - Use Column Inserts
-- - Use Insert Commands
-- - Include CREATE DATABASE statement

-- Disable:
-- - Trigger (during restore)
-- - $ quoting (use standard SQL strings)

-- Miscellaneous:
-- - Verbose messages
-- - Force double quotes on identifiers
-- - Use SET SESSION AUTHORIZATION

Línea de comandos Backup

# Full database backup
pg_dump -h localhost -U postgres -d myapp -f myapp_backup.sql

# Custom format backup (recommended)
pg_dump -h localhost -U postgres -d myapp -Fc -f myapp_backup.dump

# Compressed backup
pg_dump -h localhost -U postgres -d myapp -Fc -Z 9 -f myapp_backup.dump

# Schema only backup
pg_dump -h localhost -U postgres -d myapp -s -f myapp_schema.sql

# Data only backup
pg_dump -h localhost -U postgres -d myapp -a -f myapp_data.sql

# Specific tables backup
pg_dump -h localhost -U postgres -d myapp -t users -t posts -f tables_backup.sql

# Exclude specific tables
pg_dump -h localhost -U postgres -d myapp -T logs -T temp_data -f myapp_no_logs.sql

# Directory format backup (parallel)
pg_dump -h localhost -U postgres -d myapp -Fd -j 4 -f myapp_backup_dir

# All databases backup
pg_dumpall -h localhost -U postgres -f all_databases.sql

# Roles and tablespaces only
pg_dumpall -h localhost -U postgres -r -f roles.sql
pg_dumpall -h localhost -U postgres -t -f tablespaces.sql

Restaurar la base de datos

# Restore from SQL file
psql -h localhost -U postgres -d myapp -f myapp_backup.sql

# Restore from custom format
pg_restore -h localhost -U postgres -d myapp myapp_backup.dump

# Restore with parallel jobs
pg_restore -h localhost -U postgres -d myapp -j 4 myapp_backup.dump

# Restore specific tables
pg_restore -h localhost -U postgres -d myapp -t users -t posts myapp_backup.dump

# Restore schema only
pg_restore -h localhost -U postgres -d myapp -s myapp_backup.dump

# Restore data only
pg_restore -h localhost -U postgres -d myapp -a myapp_backup.dump

# Restore to different database
pg_restore -h localhost -U postgres -d newapp myapp_backup.dump

# Restore with clean (drop existing objects)
pg_restore -h localhost -U postgres -d myapp -c myapp_backup.dump

# Restore with create database
pg_restore -h localhost -U postgres -C -d postgres myapp_backup.dump

# List contents of backup file
pg_restore -l myapp_backup.dump

# Restore specific items using list file
pg_restore -h localhost -U postgres -d myapp -L restore_list.txt myapp_backup.dump

Automated Backup Script

#!/bin/bash
# PostgreSQL backup script

# Configuration
DB_HOST="localhost"
DB_PORT="5432"
DB_USER="postgres"
BACKUP_DIR="/backup/postgresql"
DATE=$(date +%Y%m%d_%H%M%S)
RETENTION_DAYS=30

# Create backup directory
mkdir -p $BACKUP_DIR

# Function to backup single database
backup_database() {
    local db_name=$1
    local backup_file="$BACKUP_DIR/${db_name}_${DATE}.dump"

    echo "Backing up database: $db_name"
    pg_dump -h $DB_HOST -p $DB_PORT -U $DB_USER -Fc -Z 9 -d $db_name -f $backup_file

    if [ $? -eq 0 ]; then
        echo "Backup successful: $backup_file"

        # Calculate file size
        size=$(du -h $backup_file | cut -f1)
        echo "Backup size: $size"
    else
        echo "Backup failed for database: $db_name"
        return 1
    fi
}

# Get list of databases (excluding system databases)
databases=$(psql -h $DB_HOST -p $DB_PORT -U $DB_USER -t -c "SELECT datname FROM pg_database WHERE NOT datistemplate AND datname NOT IN ('postgres', 'template0', 'template1');")

# Backup each database
for db in $databases; do
    backup_database $db
done

# Backup global objects (roles, tablespaces)
echo "Backing up global objects..."
pg_dumpall -h $DB_HOST -p $DB_PORT -U $DB_USER -g -f "$BACKUP_DIR/globals_${DATE}.sql"

# Remove old backups
echo "Cleaning up old backups..."
find $BACKUP_DIR -name "*.dump" -mtime +$RETENTION_DAYS -delete
find $BACKUP_DIR -name "*.sql" -mtime +$RETENTION_DAYS -delete

echo "Backup process completed at $(date)"

# Log backup completion
echo "$(date): PostgreSQL backup completed" >> /var/log/postgresql_backup.log

Supervisión

Supervisión de la actividad del servidor

-- Current connections
SELECT 
    pid,
    usename,
    application_name,
    client_addr,
    client_port,
    backend_start,
    state,
    query_start,
    LEFT(query, 50) as query_preview
FROM pg_stat_activity
WHERE state = 'active'
ORDER BY query_start;

-- Long running queries
SELECT 
    pid,
    usename,
    query_start,
    now() - query_start as duration,
    state,
    LEFT(query, 100) as query_preview
FROM pg_stat_activity
WHERE state = 'active'
AND now() - query_start > interval '5 minutes'
ORDER BY duration DESC;

-- Blocking queries
SELECT 
    blocked_locks.pid AS blocked_pid,
    blocked_activity.usename AS blocked_user,
    blocking_locks.pid AS blocking_pid,
    blocking_activity.usename AS blocking_user,
    blocked_activity.query AS blocked_statement,
    blocking_activity.query AS blocking_statement
FROM pg_catalog.pg_locks blocked_locks
JOIN pg_catalog.pg_stat_activity blocked_activity ON blocked_activity.pid = blocked_locks.pid
JOIN pg_catalog.pg_locks blocking_locks ON blocking_locks.locktype = blocked_locks.locktype
    AND blocking_locks.DATABASE IS NOT DISTINCT FROM blocked_locks.DATABASE
    AND blocking_locks.relation IS NOT DISTINCT FROM blocked_locks.relation
    AND blocking_locks.page IS NOT DISTINCT FROM blocked_locks.page
    AND blocking_locks.tuple IS NOT DISTINCT FROM blocked_locks.tuple
    AND blocking_locks.virtualxid IS NOT DISTINCT FROM blocked_locks.virtualxid
    AND blocking_locks.transactionid IS NOT DISTINCT FROM blocked_locks.transactionid
    AND blocking_locks.classid IS NOT DISTINCT FROM blocked_locks.classid
    AND blocking_locks.objid IS NOT DISTINCT FROM blocked_locks.objid
    AND blocking_locks.objsubid IS NOT DISTINCT FROM blocked_locks.objsubid
    AND blocking_locks.pid != blocked_locks.pid
JOIN pg_catalog.pg_stat_activity blocking_activity ON blocking_activity.pid = blocking_locks.pid
WHERE NOT blocked_locks.GRANTED;

-- Database statistics
SELECT 
    datname,
    numbackends as connections,
    xact_commit as commits,
    xact_rollback as rollbacks,
    blks_read,
    blks_hit,
    round((blks_hit::float / (blks_hit + blks_read)) * 100, 2) as cache_hit_ratio,
    tup_returned,
    tup_fetched,
    tup_inserted,
    tup_updated,
    tup_deleted
FROM pg_stat_database
WHERE datname NOT IN ('template0', 'template1', 'postgres');

Estadísticas del cuadro e índice

-- Table statistics
SELECT 
    schemaname,
    tablename,
    n_live_tup as live_tuples,
    n_dead_tup as dead_tuples,
    n_tup_ins as inserts,
    n_tup_upd as updates,
    n_tup_del as deletes,
    last_vacuum,
    last_autovacuum,
    last_analyze,
    last_autoanalyze,
    vacuum_count,
    autovacuum_count,
    analyze_count,
    autoanalyze_count
FROM pg_stat_user_tables
ORDER BY n_live_tup DESC;

-- Table sizes
SELECT 
    schemaname,
    tablename,
    pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) as total_size,
    pg_size_pretty(pg_relation_size(schemaname||'.'||tablename)) as table_size,
    pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename) - pg_relation_size(schemaname||'.'||tablename)) as index_size
FROM pg_tables
WHERE schemaname NOT IN ('information_schema', 'pg_catalog')
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC;

-- Index usage statistics
SELECT 
    schemaname,
    tablename,
    indexname,
    idx_tup_read,
    idx_tup_fetch,
    idx_scan,
    pg_size_pretty(pg_relation_size(indexrelid)) as index_size
FROM pg_stat_user_indexes
ORDER BY idx_scan DESC;

-- Unused indexes
SELECT 
    schemaname,
    tablename,
    indexname,
    pg_size_pretty(pg_relation_size(indexrelid)) as index_size
FROM pg_stat_user_indexes
WHERE idx_scan = 0
ORDER BY pg_relation_size(indexrelid) DESC;

Supervisión de la ejecución

-- Slow queries (requires pg_stat_statements extension)
-- Enable in postgresql.conf:
-- shared_preload_libraries = 'pg_stat_statements'
-- pg_stat_statements.track = all

CREATE EXTENSION IF NOT EXISTS pg_stat_statements;

-- Top queries by total time
SELECT 
    query,
    calls,
    total_time,
    mean_time,
    rows,
    100.0 * shared_blks_hit / nullif(shared_blks_hit + shared_blks_read, 0) AS hit_percent
FROM pg_stat_statements
ORDER BY total_time DESC
LIMIT 10;

-- Top queries by average time
SELECT 
    query,
    calls,
    total_time,
    mean_time,
    rows
FROM pg_stat_statements
ORDER BY mean_time DESC
LIMIT 10;

-- Reset statistics
SELECT pg_stat_statements_reset();

-- Buffer cache hit ratio
SELECT 
    sum(heap_blks_read) as heap_read,
    sum(heap_blks_hit) as heap_hit,
    round((sum(heap_blks_hit) / (sum(heap_blks_hit) + sum(heap_blks_read))) * 100, 2) as ratio
FROM pg_statio_user_tables;

-- Checkpoint statistics
SELECT 
    checkpoints_timed,
    checkpoints_req,
    checkpoint_write_time,
    checkpoint_sync_time,
    buffers_checkpoint,
    buffers_clean,
    maxwritten_clean,
    buffers_backend,
    buffers_backend_fsync,
    buffers_alloc
FROM pg_stat_bgwriter;

pgAdmin Dashboard

pgAdmin Dashboard Features:

Server Dashboard:
- Server activity (connections, transactions)
- Database statistics
- Session statistics
- Lock statistics
- Prepared transactions
- Configuration parameters

Database Dashboard:
- Database statistics
- Table statistics
- Index statistics
- Schema statistics

Monitoring Tools:
1. Server Activity:
   - View active sessions
   - Kill sessions
   - Monitor locks and blocking queries

2. System Statistics:
   - CPU usage
   - Memory usage
   - Disk I/O
   - Network activity

3. Query Tool:
   - Execute monitoring queries
   - View execution plans
   - Analyze performance

4. Graphs and Charts:
   - Real-time monitoring
   - Historical data
   - Custom dashboards

Accessing Dashboards:
1. Select server/database in browser tree
2. Click "Dashboard" tab
3. View real-time statistics
4. Refresh automatically or manually
5. Export data for analysis

Seguridad

Configuración SSL

-- Check SSL status
SELECT name, setting FROM pg_settings WHERE name LIKE '%ssl%';

-- Show current connections with SSL info
SELECT 
    pid,
    usename,
    application_name,
    client_addr,
    ssl,
    ssl_version,
    ssl_cipher
FROM pg_stat_ssl
JOIN pg_stat_activity USING (pid);

-- Force SSL connections in pg_hba.conf
-- hostssl all all 0.0.0.0/0 md5
-- host all all 0.0.0.0/0 reject

Configuración de autenticación

# pg_hba.conf configuration examples

# Local connections
local   all             postgres                                peer
local   all             all                                     md5

# IPv4 local connections
host    all             all             127.0.0.1/32            md5
host    all             all             192.168.1.0/24          md5

# IPv6 local connections
host    all             all             ::1/128                 md5

# SSL connections only
hostssl all             all             0.0.0.0/0               md5

# LDAP authentication
host    all             all             0.0.0.0/0               ldap ldapserver=ldap.example.com ldapprefix="uid=" ldapsuffix=",ou=users,dc=example,dc=com"

# Certificate authentication
hostssl all             all             0.0.0.0/0               cert

# GSSAPI authentication
host    all             all             0.0.0.0/0               gss

# Reject specific users
host    all             baduser         0.0.0.0/0               reject

# Database-specific rules
host    production      prod_users      192.168.1.0/24          md5
host    development     dev_users       192.168.2.0/24          md5

Auditoría

-- Enable logging in postgresql.conf
-- log_destination = 'stderr,csvlog'
-- logging_collector = on
-- log_directory = 'pg_log'
-- log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log'
-- log_rotation_age = 1d
-- log_rotation_size = 100MB

-- Log connections and disconnections
-- log_connections = on
-- log_disconnections = on

-- Log all statements
-- log_statement = 'all'

-- Log slow queries
-- log_min_duration_statement = 1000  # milliseconds

-- Log lock waits
-- log_lock_waits = on

-- Log checkpoints
-- log_checkpoints = on

-- Install pgaudit extension for detailed auditing
CREATE EXTENSION IF NOT EXISTS pgaudit;

-- Configure pgaudit
-- pgaudit.log = 'all'
-- pgaudit.log_catalog = off
-- pgaudit.log_parameter = on
-- pgaudit.log_relation = on
-- pgaudit.log_statement_once = off

-- Session-level auditing
SET pgaudit.log = 'read,write';
SELECT * FROM sensitive_table;
SET pgaudit.log = '';

-- Object-level auditing
SELECT pgaudit.log_catalog = on;

Encriptación de datos

-- Enable data encryption at rest
-- Requires file system level encryption or
-- PostgreSQL with TDE (Transparent Data Encryption)

-- Column-level encryption using pgcrypto
CREATE EXTENSION IF NOT EXISTS pgcrypto;

-- Encrypt sensitive data
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    username VARCHAR(50) NOT NULL,
    email VARCHAR(100) NOT NULL,
    password_hash TEXT NOT NULL,
    ssn BYTEA  -- encrypted column
);

-- Insert encrypted data
INSERT INTO users (username, email, password_hash, ssn)
VALUES (
    'john_doe',
    'john@example.com',
    crypt('password', gen_salt('bf')),
    pgp_sym_encrypt('123-45-6789', 'encryption_key')
);

-- Query encrypted data
SELECT 
    id,
    username,
    email,
    pgp_sym_decrypt(ssn, 'encryption_key') as ssn_decrypted
FROM users
WHERE username = 'john_doe';

-- Verify password
SELECT * FROM users 
WHERE username = 'john_doe' 
AND password_hash = crypt('password', password_hash);

Prácticas óptimas de seguridad

-- 1. Remove default databases and users
DROP DATABASE IF EXISTS template0;
-- Note: Don't actually drop template0, it's needed

-- 2. Change default passwords
ALTER USER postgres PASSWORD 'strong_password_here';

-- 3. Create application-specific users
CREATE USER app_user WITH PASSWORD 'app_password';
GRANT CONNECT ON DATABASE myapp TO app_user;
GRANT USAGE ON SCHEMA public TO app_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app_user;

-- 4. Limit connection attempts
-- In postgresql.conf:
-- max_connections = 100
-- superuser_reserved_connections = 3

-- 5. Enable SSL
-- ssl = on
-- ssl_cert_file = 'server.crt'
-- ssl_key_file = 'server.key'
-- ssl_ca_file = 'ca.crt'

-- 6. Configure firewall rules
-- Allow only necessary IP addresses
-- Block default PostgreSQL port (5432) from internet

-- 7. Regular security updates
-- Keep PostgreSQL and pgAdmin updated
-- Monitor security advisories

-- 8. Backup encryption
-- pg_dump with encryption:
-- pg_dump mydb | gpg --cipher-algo AES256 --compress-algo 1 --symmetric --output mydb.sql.gpg

-- 9. Monitor failed login attempts
-- log_statement = 'all'
-- Check logs for authentication failures

-- 10. Use connection pooling
-- Configure pgBouncer or similar
-- Limit direct database connections

Personalización

pgAdmin Temas y Apariencia

# Custom CSS for pgAdmin
# Create custom.css in pgAdmin web directory

/* Dark theme customization */
.pgadmin-body {
    background-color: #2d3748;
    color: #e2e8f0;
}

.navbar {
    background-color: #1a202c !important;
}

.browser-tree {
    background-color: #2d3748;
    color: #e2e8f0;
}

.sql-editor {
    background-color: #1a202c;
    color: #e2e8f0;
}

/* Query result grid styling */
.slick-header-column {
    background-color: #4a5568;
    color: #e2e8f0;
}

.slick-cell {
    background-color: #2d3748;
    color: #e2e8f0;
    border-color: #4a5568;
}

/* Custom button styling */
.btn-primary {
    background-color: #3182ce;
    border-color: #3182ce;
}

.btn-primary:hover {
    background-color: #2c5aa0;
    border-color: #2c5aa0;
}

Dashboards personalizados

# config_local.py - Custom dashboard configuration

# Custom dashboard widgets
CUSTOM_DASHBOARDS = {
    'server': [
        {
            'name': 'Custom Server Stats',
            'sql': '''
                SELECT 
                    'Active Connections' as metric,
                    count(*) as value
                FROM pg_stat_activity 
                WHERE state = 'active'
                UNION ALL
                SELECT 
                    'Database Size' as metric,
                    pg_size_pretty(sum(pg_database_size(datname))::bigint) as value
                FROM pg_database
                WHERE datistemplate = false
            ''',
            'refresh': 30  # seconds
        }
    ],
    'database': [
        {
            'name': 'Table Statistics',
            'sql': '''
                SELECT 
                    tablename,
                    n_live_tup as "Live Tuples",
                    n_dead_tup as "Dead Tuples",
                    last_vacuum as "Last Vacuum"
                FROM pg_stat_user_tables
                ORDER BY n_live_tup DESC
                LIMIT 10
            ''',
            'refresh': 60
        }
    ]
}

# Custom menu items
CUSTOM_MENU_ITEMS = [
    {
        'name': 'Performance Monitor',
        'url': '/performance',
        'icon': 'fa-chart-line'
    },
    {
        'name': 'Custom Reports',
        'url': '/reports',
        'icon': 'fa-file-alt'
    }
]

Personalización de herramientas de consulta

// custom.js - Query tool enhancements

$(document).ready(function() {
    // Auto-format SQL on Ctrl+Shift+F
    $(document).keydown(function(e) {
        if (e.ctrlKey && e.shiftKey && e.keyCode === 70) {
            formatSQL();
        }
    });

    // Custom SQL snippets
    var sqlSnippets = {
        'sel': 'SELECT * FROM table_name WHERE condition;',
        'ins': 'INSERT INTO table_name (column1, column2) VALUES (value1, value2);',
        'upd': 'UPDATE table_name SET column1 = value1 WHERE condition;',
        'del': 'DELETE FROM table_name WHERE condition;',
        'cte': 'WITH cte_name AS (\n    SELECT column1, column2\n    FROM table_name\n    WHERE condition\n)\nSELECT * FROM cte_name;'
    };

    // Add snippet functionality
    function insertSnippet(snippet) {
        var editor = $('.sql-editor textarea');
        if (editor.length) {
            var cursorPos = editor[0].selectionStart;
            var textBefore = editor.val().substring(0, cursorPos);
            var textAfter = editor.val().substring(cursorPos);
            editor.val(textBefore + sqlSnippets[snippet] + textAfter);
        }
    }

    // Add custom toolbar buttons
    $('.query-tool-toolbar').append(`
        <div class="btn-group" role="group">
            <button type="button" class="btn btn-secondary dropdown-toggle" data-toggle="dropdown">
                Snippets
            </button>
            <div class="dropdown-menu">
                <a class="dropdown-item" href="#" onclick="insertSnippet('sel')">SELECT</a>
                <a class="dropdown-item" href="#" onclick="insertSnippet('ins')">INSERT</a>
                <a class="dropdown-item" href="#" onclick="insertSnippet('upd')">UPDATE</a>
                <a class="dropdown-item" href="#" onclick="insertSnippet('del')">DELETE</a>
                <a class="dropdown-item" href="#" onclick="insertSnippet('cte')">CTE</a>
            </div>
        </div>
    `);
});

function formatSQL() {
    // SQL formatting logic
    var editor = $('.sql-editor textarea');
    if (editor.length) {
        var sql = editor.val();
        // Basic SQL formatting
        sql = sql.replace(/\bSELECT\b/gi, '\nSELECT');
        sql = sql.replace(/\bFROM\b/gi, '\nFROM');
        sql = sql.replace(/\bWHERE\b/gi, '\nWHERE');
        sql = sql.replace(/\bORDER BY\b/gi, '\nORDER BY');
        sql = sql.replace(/\bGROUP BY\b/gi, '\nGROUP BY');
        sql = sql.replace(/\bHAVING\b/gi, '\nHAVING');
        editor.val(sql.trim());
    }
}
```_

## Buenas prácticas

### Prácticas óptimas de rendimiento
```sql
-- 1. Use connection pooling
-- Configure pgBouncer or similar connection pooler
-- Limit concurrent connections to pgAdmin

-- 2. Optimize queries in Query Tool
-- Use EXPLAIN ANALYZE for query optimization
EXPLAIN (ANALYZE, BUFFERS, VERBOSE) 
SELECT * FROM large_table WHERE indexed_column = 'value';

-- 3. Limit result sets
-- Use LIMIT for large queries
SELECT * FROM large_table ORDER BY created_at DESC LIMIT 100;

-- 4. Use appropriate data types
-- Avoid TEXT when VARCHAR(n) is sufficient
-- Use appropriate numeric types (INTEGER vs BIGINT)

-- 5. Index optimization
-- Create indexes for frequently queried columns
CREATE INDEX CONCURRENTLY idx_users_email ON users(email);

-- 6. Regular maintenance
-- Schedule VACUUM and ANALYZE
VACUUM ANALYZE users;

-- 7. Monitor query performance
-- Use pg_stat_statements extension
-- Identify and optimize slow queries

-- 8. Partition large tables
-- Use table partitioning for very large tables
CREATE TABLE sales (
    id SERIAL,
    sale_date DATE,
    amount DECIMAL(10,2)
) PARTITION BY RANGE (sale_date);

CREATE TABLE sales_2023 PARTITION OF sales
FOR VALUES FROM ('2023-01-01') TO ('2024-01-01');

Prácticas óptimas de seguridad

# pgAdmin security configuration

# 1. Use HTTPS only
FORCE_SSL = True

# 2. Strong session configuration
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'Strict'

# 3. CSRF protection
WTF_CSRF_ENABLED = True

# 4. Content Security Policy
CSP_DEFAULT_SRC = "'self'"
CSP_SCRIPT_SRC = "'self' 'unsafe-inline'"
CSP_STYLE_SRC = "'self' 'unsafe-inline'"

# 5. Hide server information
SERVER_MODE = True
DEBUG = False

# 6. Limit login attempts
MAX_LOGIN_ATTEMPTS = 5
LOGIN_ATTEMPT_TIMEOUT = 300  # 5 minutes

# 7. Regular backups
BACKUP_ENCRYPTION = True
BACKUP_RETENTION_DAYS = 30

# 8. Audit logging
AUDIT_LOG_ENABLED = True
AUDIT_LOG_LEVEL = 'INFO'

# 9. User session timeout
SESSION_EXPIRATION_TIME = 8  # hours

# 10. Database connection security
REQUIRE_SSL_DB_CONNECTIONS = True

Mejores prácticas de mantenimiento

#!/bin/bash
# pgAdmin maintenance script

# 1. Regular updates
echo "Checking for pgAdmin updates..."
apt list --upgradable | grep pgadmin

# 2. Log rotation
echo "Rotating pgAdmin logs..."
logrotate /etc/logrotate.d/pgadmin4

# 3. Database cleanup
echo "Cleaning up pgAdmin database..."
sqlite3 /var/lib/pgadmin/pgadmin4.db "DELETE FROM session WHERE expiry < datetime('now');"

# 4. Backup pgAdmin configuration
echo "Backing up pgAdmin configuration..."
cp /usr/pgadmin4/web/config_local.py /backup/pgadmin/config_local_$(date +%Y%m%d).py

# 5. Monitor disk space
echo "Checking disk space..."
df -h /var/lib/pgadmin
df -h /var/log/pgadmin4

# 6. Check service status
echo "Checking pgAdmin service status..."
systemctl status apache2
systemctl status postgresql

# 7. Verify SSL certificates
echo "Checking SSL certificate expiry..."
openssl x509 -in /etc/ssl/certs/pgadmin.crt -noout -dates

# 8. Performance monitoring
echo "Checking pgAdmin performance..."
ps aux | grep pgadmin
netstat -tulpn | grep :80

# 9. Security audit
echo "Running security checks..."
# Check for unauthorized access attempts
grep "authentication failed" /var/log/pgadmin4/pgadmin4.log | tail -10

# 10. Cleanup temporary files
echo "Cleaning up temporary files..."
find /tmp -name "pgadmin*" -mtime +7 -delete

echo "Maintenance completed at $(date)"

Respaldo y recuperación mejores prácticas

#!/bin/bash
# Comprehensive pgAdmin backup strategy

BACKUP_DIR="/backup/pgadmin"
DATE=$(date +%Y%m%d_%H%M%S)

# 1. Backup pgAdmin configuration
echo "Backing up pgAdmin configuration..."
mkdir -p $BACKUP_DIR/config
cp -r /usr/pgadmin4/web/config* $BACKUP_DIR/config/

# 2. Backup pgAdmin database
echo "Backing up pgAdmin database..."
cp /var/lib/pgadmin/pgadmin4.db $BACKUP_DIR/pgadmin4_$DATE.db

# 3. Backup user preferences and settings
echo "Backing up user data..."
mkdir -p $BACKUP_DIR/userdata
cp -r /var/lib/pgadmin/storage $BACKUP_DIR/userdata/

# 4. Backup SSL certificates
echo "Backing up SSL certificates..."
mkdir -p $BACKUP_DIR/ssl
cp /etc/ssl/certs/pgadmin.* $BACKUP_DIR/ssl/ 2>/dev/null || true

# 5. Export server definitions
echo "Exporting server definitions..."
# This would require custom script to export from pgAdmin database

# 6. Create restore script
cat > $BACKUP_DIR/restore_$DATE.sh << 'EOF'
#!/bin/bash
# pgAdmin restore script

BACKUP_DIR=$(dirname $0)

echo "Restoring pgAdmin configuration..."
cp -r $BACKUP_DIR/config/* /usr/pgadmin4/web/

echo "Restoring pgAdmin database..."
cp $BACKUP_DIR/pgadmin4_*.db /var/lib/pgadmin/pgadmin4.db

echo "Restoring user data..."
cp -r $BACKUP_DIR/userdata/storage /var/lib/pgadmin/

echo "Setting permissions..."
chown -R pgadmin:pgadmin /var/lib/pgadmin
chmod 600 /var/lib/pgadmin/pgadmin4.db

echo "Restarting services..."
systemctl restart apache2

echo "Restore completed!"
EOF

chmod +x $BACKUP_DIR/restore_$DATE.sh

# 7. Compress backup
echo "Compressing backup..."
tar -czf $BACKUP_DIR/pgadmin_backup_$DATE.tar.gz -C $BACKUP_DIR config userdata ssl pgadmin4_$DATE.db restore_$DATE.sh

# 8. Remove old backups
echo "Cleaning up old backups..."
find $BACKUP_DIR -name "pgadmin_backup_*.tar.gz" -mtime +30 -delete

echo "pgAdmin backup completed: $BACKUP_DIR/pgadmin_backup_$DATE.tar.gz"

-...

Resumen

pgAdmin es una plataforma integral de administración y desarrollo de PostgreSQL que ofrece potentes herramientas para la gestión de bases de datos, el desarrollo de consultas y la vigilancia. Esta hoja de trampa cubre todos los aspectos de pgAdmin de la instalación a la administración avanzada.

Key Strengths - Interfaz amplia: Capacidades completas de administración PostgreSQL - Cross-Platform: Disponible en Windows, macOS y Linux - Basado en Internet: Accesible desde cualquier navegador moderno - Rich Query Tool: Editor SQL avanzado con resaltado de sintaxis y autocompleción - Monitoring: Monitorización de servidores y bases de datos en tiempo real - Backup/Restore: Función de copia de seguridad integrada y restauración

Mejores casos de uso: - Administración de bases de datos PostgreSQL - Desarrollo y prueba de consultas SQL - Supervisión de bases de datos y análisis del desempeño - Gestión de usuarios y seguridad - Operaciones de respaldo y restauración - Gestión de bases de datos sobre desarrollo y producción

** Consideraciones importantes:** - La configuración de seguridad es fundamental para los despliegues de producción - El rendimiento puede ser impactado con bases de datos muy grandes - Las actualizaciones regulares son esenciales para la seguridad y las características - Deben aplicarse procedimientos adecuados de copia de seguridad - SSL/TLS debe configurarse para el uso de la producción

Al seguir las prácticas y técnicas descritas en esta hoja de trampa, puede utilizar con eficacia pgAdmin para gestionar bases de datos PostgreSQL manteniendo la seguridad, el rendimiento y la fiabilidad en sus tareas de administración de bases de datos.

" copia de la funciónToClipboard() {} comandos const = document.querySelectorAll('code'); que todos losCommands = '; comandos. paraCada(cmd = confianza allCommands += cmd.textContent + '\n'); navigator.clipboard.writeText(allCommands); alerta ('Todos los comandos copiados a portapapeles!'); }

función generaPDF() { ventana.print(); } ■/script título