Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>SQL Query Converter</title> | |
| <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/prism.min.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-sql.min.js"></script> | |
| <link href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism-tomorrow.min.css" rel="stylesheet" /> | |
| <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Space+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet"> | |
| <style> | |
| :root { | |
| --bg-primary: #0a0e17; | |
| --bg-secondary: #111827; | |
| --bg-tertiary: #1a2234; | |
| --bg-card: #141c2e; | |
| --text-primary: #e2e8f0; | |
| --text-secondary: #94a3b8; | |
| --text-muted: #64748b; | |
| --accent: #22d3ee; | |
| --accent-secondary: #a78bfa; | |
| --accent-glow: rgba(34, 211, 238, 0.3); | |
| --border: #1e293b; | |
| --success: #10b981; | |
| --warning: #f59e0b; | |
| --code-bg: #0d1117; | |
| } | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: 'Space Grotesk', sans-serif; | |
| background: var(--bg-primary); | |
| color: var(--text-primary); | |
| min-height: 100vh; | |
| overflow-x: hidden; | |
| } | |
| /* Animated background */ | |
| .bg-grid { | |
| position: fixed; | |
| inset: 0; | |
| background-image: | |
| linear-gradient(rgba(34, 211, 238, 0.03) 1px, transparent 1px), | |
| linear-gradient(90deg, rgba(34, 211, 238, 0.03) 1px, transparent 1px); | |
| background-size: 50px 50px; | |
| z-index: -2; | |
| } | |
| .bg-glow { | |
| position: fixed; | |
| width: 600px; | |
| height: 600px; | |
| border-radius: 50%; | |
| filter: blur(150px); | |
| opacity: 0.15; | |
| z-index: -1; | |
| animation: float 20s ease-in-out infinite; | |
| } | |
| .bg-glow-1 { | |
| top: -200px; | |
| left: -200px; | |
| background: var(--accent); | |
| } | |
| .bg-glow-2 { | |
| bottom: -200px; | |
| right: -200px; | |
| background: var(--accent-secondary); | |
| animation-delay: -10s; | |
| } | |
| @keyframes float { | |
| 0%, 100% { transform: translate(0, 0) scale(1); } | |
| 50% { transform: translate(50px, 50px) scale(1.1); } | |
| } | |
| /* Header */ | |
| .header { | |
| padding: 1.5rem 2rem; | |
| border-bottom: 1px solid var(--border); | |
| background: rgba(10, 14, 23, 0.8); | |
| backdrop-filter: blur(20px); | |
| position: sticky; | |
| top: 0; | |
| z-index: 100; | |
| } | |
| .header-content { | |
| max-width: 1400px; | |
| margin: 0 auto; | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| gap: 1rem; | |
| flex-wrap: wrap; | |
| } | |
| .logo { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.75rem; | |
| } | |
| .logo-icon { | |
| width: 40px; | |
| height: 40px; | |
| background: linear-gradient(135deg, var(--accent), var(--accent-secondary)); | |
| border-radius: 10px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-weight: 700; | |
| font-size: 1.25rem; | |
| } | |
| .logo-text { | |
| font-size: 1.5rem; | |
| font-weight: 700; | |
| background: linear-gradient(135deg, var(--text-primary), var(--accent)); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| background-clip: text; | |
| } | |
| .built-with { | |
| font-size: 0.875rem; | |
| color: var(--text-muted); | |
| text-decoration: none; | |
| padding: 0.5rem 1rem; | |
| border: 1px solid var(--border); | |
| border-radius: 8px; | |
| transition: all 0.3s ease; | |
| } | |
| .built-with:hover { | |
| border-color: var(--accent); | |
| color: var(--accent); | |
| background: rgba(34, 211, 238, 0.1); | |
| } | |
| /* Main container */ | |
| .container { | |
| max-width: 1400px; | |
| margin: 0 auto; | |
| padding: 2rem; | |
| } | |
| /* Title section */ | |
| .title-section { | |
| text-align: center; | |
| margin-bottom: 3rem; | |
| animation: fadeInUp 0.8s ease-out; | |
| } | |
| @keyframes fadeInUp { | |
| from { | |
| opacity: 0; | |
| transform: translateY(30px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| } | |
| .title-section h1 { | |
| font-size: clamp(1.75rem, 4vw, 2.5rem); | |
| font-weight: 700; | |
| margin-bottom: 0.75rem; | |
| background: linear-gradient(135deg, var(--text-primary), var(--accent)); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| background-clip: text; | |
| } | |
| .title-section p { | |
| color: var(--text-secondary); | |
| font-size: 1rem; | |
| max-width: 600px; | |
| margin: 0 auto; | |
| } | |
| /* Tabs */ | |
| .tabs { | |
| display: flex; | |
| gap: 0.5rem; | |
| margin-bottom: 1.5rem; | |
| border-bottom: 1px solid var(--border); | |
| padding-bottom: 0.5rem; | |
| animation: fadeInUp 0.8s ease-out 0.1s both; | |
| } | |
| .tab-btn { | |
| padding: 0.75rem 1.5rem; | |
| background: transparent; | |
| border: 1px solid transparent; | |
| border-radius: 8px 8px 0 0; | |
| color: var(--text-secondary); | |
| font-family: inherit; | |
| font-size: 0.9rem; | |
| font-weight: 500; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| position: relative; | |
| } | |
| .tab-btn:hover { | |
| color: var(--text-primary); | |
| background: var(--bg-tertiary); | |
| } | |
| .tab-btn.active { | |
| color: var(--accent); | |
| background: var(--bg-card); | |
| border-color: var(--border); | |
| border-bottom-color: var(--bg-card); | |
| } | |
| .tab-btn.active::after { | |
| content: ''; | |
| position: absolute; | |
| bottom: -1px; | |
| left: 0; | |
| right: 0; | |
| height: 2px; | |
| background: var(--accent); | |
| } | |
| /* Code panels */ | |
| .code-panel { | |
| display: none; | |
| animation: fadeInUp 0.5s ease-out; | |
| } | |
| .code-panel.active { | |
| display: block; | |
| } | |
| .code-card { | |
| background: var(--bg-card); | |
| border: 1px solid var(--border); | |
| border-radius: 12px; | |
| overflow: hidden; | |
| } | |
| .code-header { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| padding: 1rem 1.5rem; | |
| background: var(--bg-tertiary); | |
| border-bottom: 1px solid var(--border); | |
| } | |
| .code-header-left { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.75rem; | |
| } | |
| .code-dots { | |
| display: flex; | |
| gap: 6px; | |
| } | |
| .code-dot { | |
| width: 12px; | |
| height: 12px; | |
| border-radius: 50%; | |
| } | |
| .code-dot.red { background: #ff5f57; } | |
| .code-dot.yellow { background: #febc2e; } | |
| .code-dot.green { background: #28c840; } | |
| .code-title { | |
| font-size: 0.875rem; | |
| color: var(--text-secondary); | |
| font-weight: 500; | |
| } | |
| .copy-btn { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| padding: 0.5rem 1rem; | |
| background: var(--bg-secondary); | |
| border: 1px solid var(--border); | |
| border-radius: 6px; | |
| color: var(--text-secondary); | |
| font-family: inherit; | |
| font-size: 0.8rem; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| } | |
| .copy-btn:hover { | |
| border-color: var(--accent); | |
| color: var(--accent); | |
| } | |
| .copy-btn.copied { | |
| border-color: var(--success); | |
| color: var(--success); | |
| } | |
| .code-body { | |
| padding: 1.5rem; | |
| overflow-x: auto; | |
| max-height: 600px; | |
| overflow-y: auto; | |
| } | |
| .code-body pre { | |
| margin: 0; | |
| font-family: 'JetBrains Mono', monospace; | |
| font-size: 0.85rem; | |
| line-height: 1.7; | |
| background: transparent ; | |
| } | |
| .code-body code { | |
| font-family: 'JetBrains Mono', monospace; | |
| background: transparent ; | |
| } | |
| /* Info cards */ | |
| .info-section { | |
| margin-top: 2rem; | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); | |
| gap: 1.5rem; | |
| animation: fadeInUp 0.8s ease-out 0.3s both; | |
| } | |
| .info-card { | |
| background: var(--bg-card); | |
| border: 1px solid var(--border); | |
| border-radius: 12px; | |
| padding: 1.5rem; | |
| transition: all 0.3s ease; | |
| } | |
| .info-card:hover { | |
| border-color: var(--accent); | |
| transform: translateY(-2px); | |
| box-shadow: 0 10px 40px rgba(34, 211, 238, 0.1); | |
| } | |
| .info-card h3 { | |
| font-size: 1rem; | |
| font-weight: 600; | |
| margin-bottom: 1rem; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| .info-card h3 svg { | |
| width: 20px; | |
| height: 20px; | |
| color: var(--accent); | |
| } | |
| .info-card p { | |
| color: var(--text-secondary); | |
| font-size: 0.9rem; | |
| line-height: 1.6; | |
| } | |
| .info-card ul { | |
| list-style: none; | |
| color: var(--text-secondary); | |
| font-size: 0.9rem; | |
| line-height: 1.8; | |
| } | |
| .info-card ul li { | |
| display: flex; | |
| align-items: flex-start; | |
| gap: 0.5rem; | |
| } | |
| .info-card ul li::before { | |
| content: '→'; | |
| color: var(--accent); | |
| flex-shrink: 0; | |
| } | |
| /* Changes highlight */ | |
| .change-badge { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 0.25rem; | |
| padding: 0.25rem 0.5rem; | |
| background: rgba(34, 211, 238, 0.1); | |
| border: 1px solid rgba(34, 211, 238, 0.3); | |
| border-radius: 4px; | |
| font-size: 0.75rem; | |
| color: var(--accent); | |
| margin-left: 0.5rem; | |
| } | |
| /* Responsive */ | |
| @media (max-width: 768px) { | |
| .header { | |
| padding: 1rem; | |
| } | |
| .header-content { | |
| flex-direction: column; | |
| align-items: flex-start; | |
| } | |
| .container { | |
| padding: 1rem; | |
| } | |
| .tabs { | |
| overflow-x: auto; | |
| padding-bottom: 0; | |
| } | |
| .tab-btn { | |
| padding: 0.5rem 1rem; | |
| font-size: 0.8rem; | |
| white-space: nowrap; | |
| } | |
| .code-body { | |
| padding: 1rem; | |
| } | |
| .code-body pre { | |
| font-size: 0.75rem; | |
| } | |
| .info-section { | |
| grid-template-columns: 1fr; | |
| } | |
| } | |
| /* Scrollbar */ | |
| ::-webkit-scrollbar { | |
| width: 8px; | |
| height: 8px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| background: var(--bg-secondary); | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| background: var(--border); | |
| border-radius: 4px; | |
| } | |
| ::-webkit-scrollbar-thumb:hover { | |
| background: var(--text-muted); | |
| } | |
| /* Focus states */ | |
| button:focus-visible, | |
| a:focus-visible { | |
| outline: 2px solid var(--accent); | |
| outline-offset: 2px; | |
| } | |
| /* Reduced motion */ | |
| @media (prefers-reduced-motion: reduce) { | |
| *, *::before, *::after { | |
| animation-duration: 0.01ms ; | |
| animation-iteration-count: 1 ; | |
| transition-duration: 0.01ms ; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="bg-grid"></div> | |
| <div class="bg-glow bg-glow-1"></div> | |
| <div class="bg-glow bg-glow-2"></div> | |
| <header class="header"> | |
| <div class="header-content"> | |
| <div class="logo"> | |
| <div class="logo-icon">S</div> | |
| <span class="logo-text">SQL Converter</span> | |
| </div> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" class="built-with" target="_blank" rel="noopener noreferrer"> | |
| Built with anycoder | |
| </a> | |
| </div> | |
| </header> | |
| <main class="container"> | |
| <section class="title-section"> | |
| <h1>JOIN to WHERE Clause Converter</h1> | |
| <p>Transform your SQL queries from explicit JOIN syntax to comma-separated table syntax with WHERE clause conditions</p> | |
| </section> | |
| <div class="tabs"> | |
| <button class="tab-btn active" data-tab="original">Original SQL</button> | |
| <button class="tab-btn" data-tab="converted">Converted SQL <span class="change-badge">Updated</span></button> | |
| <button class="tab-btn" data-tab="comparison">Side by Side</button> | |
| </div> | |
| <div class="code-panel active" id="original"> | |
| <div class="code-card"> | |
| <div class="code-header"> | |
| <div class="code-header-left"> | |
| <div class="code-dots"> | |
| <span class="code-dot red"></span> | |
| <span class="code-dot yellow"></span> | |
| <span class="code-dot green"></span> | |
| </div> | |
| <span class="code-title">original_query.sql</span> | |
| </div> | |
| <button class="copy-btn" data-target="original-code"> | |
| <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect> | |
| <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path> | |
| </svg> | |
| Copy | |
| </button> | |
| </div> | |
| <div class="code-body"> | |
| <pre><code id="original-code" class="language-sql">WITH schema_ai_fields AS ( | |
| -- Get all metadata fields from schemas where assignee = 'ai' | |
| SELECT | |
| ms.id as schema_id, | |
| ms.metadata as schema_metadata, | |
| jsonb_object_keys(ms.metadata) as metadata_key | |
| FROM "MediaSchema" ms | |
| WHERE ms.metadata IS NOT NULL | |
| AND ms.metadata != '{}'::jsonb | |
| ), | |
| ai_fields_expanded AS ( | |
| -- Expand to get details of each AI-assigned field | |
| SELECT | |
| saf.schema_id, | |
| saf.metadata_key, | |
| saf.schema_metadata -> saf.metadata_key as metadata_struct | |
| FROM schema_ai_fields saf | |
| WHERE (saf.schema_metadata -> saf.metadata_key ->> 'assignee') = 'ai' | |
| ), | |
| media_with_data AS ( | |
| -- Get all media with their latest MediaData | |
| SELECT | |
| m.id as media_id, | |
| m.schema_id, | |
| m.file_data, | |
| m.file_mime_type, | |
| md.id as media_data_id, | |
| md.metadata as media_metadata | |
| FROM "Media" m | |
| LEFT JOIN "MediaData" md ON md.media_id = m.id | |
| WHERE m.status != 'approved' | |
| ), | |
| items_needing_processing AS ( | |
| -- Find items where metadata doesn't exist or has status 'init' | |
| SELECT | |
| mwd.media_id, | |
| mwd.media_data_id, | |
| mwd.schema_id, | |
| mwd.file_data, | |
| mwd.file_mime_type, | |
| afe.metadata_key, | |
| afe.metadata_struct, | |
| CASE | |
| WHEN mwd.media_metadata IS NULL THEN NULL | |
| WHEN mwd.media_metadata -> afe.metadata_key IS NULL THEN NULL | |
| ELSE mwd.media_metadata -> afe.metadata_key | |
| END as metadata_value | |
| FROM media_with_data mwd | |
| INNER JOIN ai_fields_expanded afe ON mwd.schema_id = afe.schema_id | |
| WHERE | |
| -- Either metadata is null, or the specific key doesn't exist, or status is 'init' | |
| mwd.media_metadata IS NULL | |
| OR mwd.media_metadata -> afe.metadata_key IS NULL | |
| OR (mwd.media_metadata -> afe.metadata_key ->> 'status') = 'init' | |
| ) | |
| -- Final selection excluding items that already have InstructJobs | |
| SELECT | |
| inp.media_id, | |
| inp.media_data_id, | |
| inp.schema_id, | |
| inp.file_data, | |
| inp.file_mime_type, | |
| inp.metadata_key, | |
| inp.metadata_struct, | |
| inp.metadata_value | |
| FROM items_needing_processing inp | |
| WHERE NOT EXISTS ( | |
| SELECT 1 FROM "InstructJob" ij | |
| WHERE ij.media_id = inp.media_id | |
| AND ij.key_type = 'metadata' | |
| AND ij.key_id = inp.metadata_key | |
| ) | |
| ORDER BY inp.media_id, inp.metadata_key | |
| LIMIT $1;</code></pre> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="code-panel" id="converted"> | |
| <div class="code-card"> | |
| <div class="code-header"> | |
| <div class="code-header-left"> | |
| <div class="code-dots"> | |
| <span class="code-dot red"></span> | |
| <span class="code-dot yellow"></span> | |
| <span class="code-dot green"></span> | |
| </div> | |
| <span class="code-title">converted_query.sql</span> | |
| </div> | |
| <button class="copy-btn" data-target="converted-code"> | |
| <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect> | |
| <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path> | |
| </svg> | |
| Copy | |
| </button> | |
| </div> | |
| <div class="code-body"> | |
| <pre><code id="converted-code" class="language-sql">WITH schema_ai_fields AS ( | |
| -- Get all metadata fields from schemas where assignee = 'ai' | |
| SELECT | |
| ms.id as schema_id, | |
| ms.metadata as schema_metadata, | |
| jsonb_object_keys(ms.metadata) as metadata_key | |
| FROM "MediaSchema" ms | |
| WHERE ms.metadata IS NOT NULL | |
| AND ms.metadata != '{}'::jsonb | |
| ), | |
| ai_fields_expanded AS ( | |
| -- Expand to get details of each AI-assigned field | |
| SELECT | |
| saf.schema_id, | |
| saf.metadata_key, | |
| saf.schema_metadata -> saf.metadata_key as metadata_struct | |
| FROM schema_ai_fields saf | |
| WHERE (saf.schema_metadata -> saf.metadata_key ->> 'assignee') = 'ai' | |
| ), | |
| media_with_data AS ( | |
| -- Get all media with their latest MediaData | |
| -- Note: LEFT JOIN converted using (+) Oracle-style syntax | |
| -- For standard SQL, this becomes an INNER JOIN when using comma syntax | |
| SELECT | |
| m.id as media_id, | |
| m.schema_id, | |
| m.file_data, | |
| m.file_mime_type, | |
| md.id as media_data_id, | |
| md.metadata as media_metadata | |
| FROM "Media" m, "MediaData" md | |
| WHERE md.media_id(+) = m.id | |
| AND m.status != 'approved' | |
| ), | |
| items_needing_processing AS ( | |
| -- Find items where metadata doesn't exist or has status 'init' | |
| SELECT | |
| mwd.media_id, | |
| mwd.media_data_id, | |
| mwd.schema_id, | |
| mwd.file_data, | |
| mwd.file_mime_type, | |
| afe.metadata_key, | |
| afe.metadata_struct, | |
| CASE | |
| WHEN mwd.media_metadata IS NULL THEN NULL | |
| WHEN mwd.media_metadata -> afe.metadata_key IS NULL THEN NULL | |
| ELSE mwd.media_metadata -> afe.metadata_key | |
| END as metadata_value | |
| FROM media_with_data mwd, ai_fields_expanded afe | |
| WHERE mwd.schema_id = afe.schema_id | |
| AND ( | |
| -- Either metadata is null, or the specific key doesn't exist, or status is 'init' | |
| mwd.media_metadata IS NULL | |
| OR mwd.media_metadata -> afe.metadata_key IS NULL | |
| OR (mwd.media_metadata -> afe.metadata_key ->> 'status') = 'init' | |
| ) | |
| ) | |
| -- Final selection excluding items that already have InstructJobs | |
| SELECT | |
| inp.media_id, | |
| inp.media_data_id, | |
| inp.schema_id, | |
| inp.file_data, | |
| inp.file_mime_type, | |
| inp.metadata_key, | |
| inp.metadata_struct, | |
| inp.metadata_value | |
| FROM items_needing_processing inp | |
| WHERE NOT EXISTS ( | |
| SELECT 1 FROM "InstructJob" ij | |
| WHERE ij.media_id = inp.media_id | |
| AND ij.key_type = 'metadata' | |
| AND ij.key_id = inp.metadata_key | |
| ) | |
| ORDER BY inp.media_id, inp.metadata_key | |
| LIMIT $1;</code></pre> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="code-panel" id="comparison"> | |
| <div class="comparison-grid" style="display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem;"> | |
| <div class="code-card"> | |
| <div class="code-header"> | |
| <div class="code-header-left"> | |
| <span class="code-title" style="color: var(--warning);">Original (JOIN Syntax)</span> | |
| </div> | |
| </div> | |
| <div class="code-body" style="max-height: 500px;"> | |
| <pre><code class="language-sql">-- media_with_data CTE | |
| FROM "Media" m | |
| LEFT JOIN "MediaData" md ON md.media_id = m.id | |
| -- items_needing_processing CTE | |
| FROM media_with_data mwd | |
| INNER JOIN ai_fields_expanded afe | |
| ON mwd.schema_id = afe.schema_id</code></pre> | |
| </div> | |
| </div> | |
| <div class="code-card"> | |
| <div class="code-header"> | |
| <div class="code-header-left"> | |
| <span class="code-title" style="color: var(--success);">Converted (WHERE Syntax)</span> | |
| </div> | |
| </div> | |
| <div class="code-body" style="max-height: 500px;"> | |
| <pre><code class="language-sql">-- media_with_data CTE | |
| FROM "Media" m, "MediaData" md | |
| WHERE md.media_id(+) = m.id | |
| -- items_needing_processing CTE | |
| FROM media_with_data mwd, ai_fields_expanded afe | |
| WHERE mwd.schema_id = afe.schema_id</code></pre> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <section class="info-section"> | |
| <div class="info-card"> | |
| <h3> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <circle cx="12" cy="12" r="10"></circle> | |
| <line x1="12" y1="16" x2="12" y2="12"></line> | |
| <line x1="12" y1="8" x2="12.01" y2="8"></line> | |
| </svg> | |
| Conversion Notes | |
| </h3> | |
| <ul> | |
| <li>INNER JOIN becomes comma-separated tables with WHERE condition</li> | |
| <li>LEFT JOIN uses Oracle-style (+) syntax for outer join</li> | |
| <li>Join conditions move from ON clause to WHERE clause</li> | |
| <li>CTEs remain unchanged in structure</li> | |
| </ul> | |
| </div> | |
| <div class="info-card"> | |
| <h3> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path> | |
| </svg> | |
| Important Considerations | |
| </h3> | |
| <p>The (+) syntax is Oracle-specific. For PostgreSQL, you would need to keep the LEFT JOIN or use a different approach. Standard SQL comma syntax only supports INNER JOIN behavior.</p> | |
| </div> | |
| <div class="info-card"> | |
| <h3> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <polyline points="16 18 22 12 16 6"></polyline> | |
| <polyline points="8 6 2 12 8 18"></polyline> | |
| </svg> | |
| Key Changes Made | |
| </h3> | |
| <ul> | |
| <li><code>LEFT JOIN ... ON</code> → <code>, ... WHERE ...(+)</code></li> | |
| <li><code>INNER JOIN ... ON</code> → <code>, ... WHERE ...</code></li> | |
| <li>Combined join conditions with existing WHERE filters</li> | |
| </ul> | |
| </div> | |
| </section> | |
| </main> | |
| <script> | |
| // Tab switching | |
| const tabBtns = document.querySelectorAll('.tab-btn'); | |
| const panels = document.querySelectorAll('.code-panel'); | |
| tabBtns.forEach(btn => { | |
| btn.addEventListener('click', () => { | |
| const tabId = btn.dataset.tab; | |
| tabBtns.forEach(b => b.classList.remove('active')); | |
| panels.forEach(p => p.classList.remove('active')); | |
| btn.classList.add('active'); | |
| document.getElementById(tabId).classList.add('active'); | |
| }); | |
| }); | |
| // Copy functionality | |
| const copyBtns = document.querySelectorAll('.copy-btn'); | |
| copyBtns.forEach(btn => { | |
| btn.addEventListener('click', async () => { | |
| const targetId = btn.dataset.target; | |
| const codeElement = document.getElementById(targetId); | |
| const text = codeElement.textContent; | |
| try { | |
| await navigator.clipboard.writeText(text); | |
| btn.classList.add('copied'); | |
| btn.innerHTML = ` | |
| <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <polyline points="20 6 9 17 4 12"></polyline> | |
| </svg> | |
| Copied! | |
| `; | |
| setTimeout(() => { | |
| btn.classList.remove('copied'); | |
| btn.innerHTML = ` | |
| <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect> | |
| <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path> | |
| </svg> | |
| Copy | |
| `; | |
| }, 2000); | |
| } catch (err) { | |
| console.error('Failed to copy:', err); | |
| } | |
| }); | |
| }); | |
| // Re-highlight code after DOM is ready | |
| if (typeof Prism !== 'undefined') { | |
| Prism.highlightAll(); | |
| } | |
| // Intersection Observer for scroll animations | |
| const observerOptions = { | |
| threshold: 0.1, | |
| rootMargin: '0px 0px -50px 0px' | |
| }; | |
| const observer = new IntersectionObserver((entries) => { | |
| entries.forEach(entry => { | |
| if (entry.isIntersecting) { | |
| entry.target.style.opacity = '1'; | |
| entry.target.style.transform = 'translateY(0)'; | |
| } | |
| }); | |
| }, observerOptions); | |
| document.querySelectorAll('.info-card').forEach(card => { | |
| card.style.opacity = '0'; | |
| card.style.transform = 'translateY(20px)'; | |
| card.style.transition = 'all 0.6s ease-out'; | |
| observer.observe(card); | |
| }); | |
| </script> | |
| </body> | |
| </html> |