-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsql_practice.html
More file actions
2617 lines (2335 loc) · 177 KB
/
Copy pathsql_practice.html
File metadata and controls
2617 lines (2335 loc) · 177 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Quinton Jackson | SQL Interview Prep</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.10.3/sql-wasm.js"></script>
<style>
*{margin:0;padding:0;box-sizing:border-box}
:root{
--bg:#0c0d12;--surface:#14161e;--surface2:#1c1f2b;--surface3:#252838;
--border:#2a2d3e;--border-light:#363a50;
--text:#e8ecf4;--text-dim:#7a8194;--text-muted:#505468;
--accent:#8b5cf6;--accent-light:#a78bfa;--accent-bg:#8b5cf620;
--green:#10b981;--green-bg:#10b98118;--green-light:#34d399;
--red:#ef4444;--red-bg:#ef444418;
--yellow:#f59e0b;--yellow-bg:#f59e0b18;
--blue:#3b82f6;--blue-bg:#3b82f618;--blue-light:#60a5fa;
--cyan:#06b6d4;--cyan-bg:#06b6d418;
--pink:#ec4899;--pink-bg:#ec489918;
}
body{font-family:'Segoe UI',system-ui,-apple-system,sans-serif;background:var(--bg);color:var(--text);display:flex;height:100vh;overflow:hidden}
/* Sidebar */
.sidebar{width:270px;min-width:270px;background:var(--surface);border-right:1px solid var(--border);display:flex;flex-direction:column;overflow-y:auto}
.sidebar-header{padding:20px;border-bottom:1px solid var(--border)}
.sidebar-header .logo{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:3px;color:var(--accent-light);margin-bottom:2px}
.sidebar-header h1{font-size:17px;font-weight:700;color:var(--text)}
.sidebar-header .subtitle{font-size:11px;color:var(--text-dim);margin-top:2px}
.progress-container{padding:12px 20px;border-bottom:1px solid var(--border)}
.progress-stats{display:flex;justify-content:space-between;font-size:11px;color:var(--text-dim);margin-bottom:6px}
.progress-bar{height:4px;background:var(--surface3);border-radius:2px;overflow:hidden}
.progress-fill{height:100%;background:linear-gradient(90deg,var(--accent),var(--green));border-radius:2px;transition:width .5s;width:0%}
.streak-display{padding:8px 20px;border-bottom:1px solid var(--border);font-size:11px;color:var(--yellow);display:flex;align-items:center;gap:6px}
.nav-section{padding:6px 0;flex:1}
.nav-group-label{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:1.5px;color:var(--text-muted);padding:12px 20px 4px}
.nav-item{display:flex;align-items:center;gap:10px;padding:8px 20px;cursor:pointer;transition:all .15s;font-size:12.5px;color:var(--text-dim);position:relative}
.nav-item:hover{background:var(--surface2);color:var(--text)}
.nav-item.active{background:var(--accent-bg);color:var(--accent-light)}
.nav-item .ni{width:22px;height:22px;border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;background:var(--surface3);flex-shrink:0;transition:all .15s}
.nav-item.active .ni{background:var(--accent);color:#fff}
.nav-item.completed .ni{background:var(--green);color:#fff}
.nav-badge{margin-left:auto;font-size:9px;padding:2px 6px;border-radius:8px;background:var(--surface3);color:var(--text-muted)}
.nav-item.completed .nav-badge{background:var(--green-bg);color:var(--green)}
/* Main */
.main{flex:1;overflow-y:auto;scroll-behavior:smooth}
.content{max-width:920px;margin:0 auto;padding:32px 44px 120px}
.section{display:none}.section.active{display:block}
.section-header{margin-bottom:28px}
.section-num{font-size:11px;font-weight:600;color:var(--accent-light);text-transform:uppercase;letter-spacing:2px;margin-bottom:6px}
.section-title{font-size:28px;font-weight:700;margin-bottom:6px;line-height:1.2}
.section-desc{font-size:14px;color:var(--text-dim);line-height:1.6}
/* Cards */
.card{background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:22px;margin-bottom:16px}
.card h3{font-size:15px;margin-bottom:14px;display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.tag{font-size:9px;padding:2px 8px;border-radius:8px;font-weight:700;text-transform:uppercase;letter-spacing:.5px}
.tag-core{background:var(--blue-bg);color:var(--blue-light)}
.tag-syntax{background:var(--accent-bg);color:var(--accent-light)}
.tag-visual{background:var(--green-bg);color:var(--green-light)}
.tag-gotcha{background:var(--red-bg);color:var(--red)}
.tag-tip{background:var(--yellow-bg);color:var(--yellow)}
.tag-think{background:var(--cyan-bg);color:var(--cyan)}
.tag-new{background:var(--pink-bg);color:var(--pink)}
.card-text{color:var(--text-dim);font-size:13.5px;line-height:1.7}
.card-text strong{color:var(--text)}
.card-text code{background:var(--surface3);padding:1px 6px;border-radius:4px;font-family:'Cascadia Code','Fira Code',Consolas,monospace;font-size:12px;color:var(--accent-light)}
/* Code blocks */
pre{background:#0a0b10;border:1px solid var(--border);border-radius:8px;padding:14px 16px;overflow-x:auto;margin:10px 0;font-size:12.5px;line-height:1.65}
code{font-family:'Cascadia Code','Fira Code',Consolas,monospace}
.kw{color:#c084fc}.fn{color:#60a5fa}.str{color:#34d399}.num{color:#f59e0b}.cm{color:#3e4254}.op{color:#f472b6}.tbl{color:#06b6d4}
/* Tables */
.sql-table{width:100%;border-collapse:collapse;margin:10px 0;font-size:12px}
.sql-table th{background:var(--accent-bg);color:var(--accent-light);padding:7px 10px;text-align:left;font-weight:600;border:1px solid var(--border);font-size:11px}
.sql-table td{padding:6px 10px;border:1px solid var(--border);color:var(--text-dim);font-family:'Cascadia Code',Consolas,monospace;font-size:11.5px}
.sql-table tr:hover td{background:var(--surface2);color:var(--text)}
.sql-table caption{font-size:12px;font-weight:700;color:var(--text);padding:6px 0;text-align:left}
.table-label{font-size:11px;font-weight:700;color:var(--cyan);margin-bottom:4px;text-transform:uppercase;letter-spacing:1px}
/* Compare boxes */
.compare{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin:10px 0}
.compare-box{background:#0a0b10;border:1px solid var(--border);border-radius:8px;padding:14px}
.compare-box h4{font-size:10px;color:var(--text-dim);margin-bottom:8px;text-transform:uppercase;letter-spacing:1px}
.compare-box.good{border-color:var(--green)40}
.compare-box.good h4{color:var(--green)}
.compare-box.bad{border-color:var(--red)40}
.compare-box.bad h4{color:var(--red)}
.mono{font-family:'Cascadia Code',Consolas,monospace;font-size:11.5px;line-height:1.7;color:var(--text-dim)}
/* Cheat grid */
.cheat-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin:10px 0}
.cheat-item{background:var(--surface2);border:1px solid var(--border);border-radius:8px;padding:10px 14px}
.cheat-op{font-family:'Cascadia Code',monospace;font-size:13px;font-weight:600;color:var(--accent-light);margin-bottom:2px}
.cheat-desc{font-size:11px;color:var(--text-dim)}
.cheat-ex{font-family:'Cascadia Code',monospace;font-size:10.5px;color:var(--text-muted);margin-top:3px}
/* Step-by-step teaching blocks */
.step-through{margin:12px 0}
.step-item{display:flex;gap:12px;padding:10px 0;border-bottom:1px solid var(--border)}
.step-item:last-child{border-bottom:none}
.step-num{width:28px;height:28px;border-radius:50%;background:var(--accent-bg);color:var(--accent-light);display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;flex-shrink:0}
.step-content{flex:1;font-size:13px;color:var(--text-dim);line-height:1.6}
.step-content strong{color:var(--text)}
.step-content code{background:var(--surface3);padding:1px 5px;border-radius:3px;font-size:11.5px;color:var(--accent-light)}
/* Exec order */
.exec-order{display:flex;gap:6px;flex-wrap:wrap;margin:12px 0}
.exec-step{background:var(--surface2);border:1px solid var(--border);border-radius:8px;padding:8px 10px;text-align:center;flex:1;min-width:75px}
.exec-step .es-num{font-size:16px;font-weight:700;color:var(--accent-light)}
.exec-step .es-kw{font-size:11px;font-weight:700;color:var(--text);font-family:'Cascadia Code',monospace}
.exec-step .es-label{font-size:9px;color:var(--text-dim);margin-top:1px}
.exec-arrow{display:flex;align-items:center;color:var(--text-muted);font-size:14px}
/* ER Diagram */
.er-diagram{background:#0a0b10;border:1px solid var(--border);border-radius:10px;padding:20px;margin:12px 0;overflow-x:auto}
.er-tables{display:flex;gap:16px;flex-wrap:wrap;justify-content:center}
.er-table{background:var(--surface);border:1px solid var(--border);border-radius:8px;min-width:140px;overflow:hidden}
.er-table-name{background:var(--accent-bg);color:var(--accent-light);padding:6px 12px;font-size:11px;font-weight:700;text-align:center;text-transform:uppercase;letter-spacing:1px}
.er-col{padding:4px 12px;font-size:11px;color:var(--text-dim);font-family:'Cascadia Code',monospace;border-top:1px solid var(--border)}
.er-col.pk{color:var(--yellow);font-weight:600}
.er-col.fk{color:var(--cyan)}
/* Join diagram cards */
.join-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:10px;margin:12px 0}
.join-card{background:#0a0b10;border:1px solid var(--border);border-radius:8px;padding:14px;text-align:center}
.join-card h4{font-size:12px;color:var(--accent-light);margin-bottom:8px}
.join-card .jc-visual{font-size:28px;margin:8px 0}
.join-card p{font-size:10.5px;color:var(--text-dim);line-height:1.4}
/* Problems */
.problem{background:var(--surface);border:1px solid var(--border);border-radius:10px;margin-bottom:14px;overflow:hidden;transition:border-color .3s}
.problem.correct{border-color:var(--green)}
.problem.wrong{border-color:var(--red)}
.problem-header{padding:14px 18px;display:flex;align-items:center;gap:10px;border-bottom:1px solid var(--border)}
.p-num{background:var(--accent-bg);color:var(--accent-light);font-size:10px;font-weight:700;padding:3px 8px;border-radius:5px}
.p-diff{font-size:9px;padding:2px 7px;border-radius:8px;font-weight:700;text-transform:uppercase}
.diff-easy{background:var(--green-bg);color:var(--green)}.diff-medium{background:var(--yellow-bg);color:var(--yellow)}.diff-hard{background:var(--red-bg);color:var(--red)}
.p-text{font-size:13px;flex:1;line-height:1.5}
.p-scenario{font-size:11.5px;color:var(--text-dim);font-style:italic;padding:0 18px;margin-top:-4px;padding-bottom:10px;border-bottom:1px solid var(--border)}
.problem-body{padding:14px 18px}
.hint-box{font-size:12px;color:var(--text-dim);margin-bottom:10px;display:none;padding:8px 12px;background:var(--yellow-bg);border:1px solid var(--yellow)25;border-radius:6px}
.hint-box.visible{display:block}
.expected-box{font-size:11px;color:var(--text-dim);margin-bottom:10px;padding:8px 12px;background:var(--surface2);border:1px solid var(--border);border-radius:6px;display:none}
.expected-box.visible{display:block}
.expected-box strong{color:var(--cyan);font-size:10px;text-transform:uppercase;letter-spacing:.5px}
.sql-editor{width:100%;min-height:70px;background:#0a0b10;border:1px solid var(--border);border-radius:8px;padding:12px;color:var(--text);font-family:'Cascadia Code','Fira Code',Consolas,monospace;font-size:12.5px;line-height:1.6;resize:vertical;outline:none;transition:border-color .2s}
.sql-editor:focus{border-color:var(--accent)}
.sql-editor::placeholder{color:var(--text-muted)}
.p-actions{display:flex;gap:6px;margin-top:10px;align-items:center;flex-wrap:wrap}
.btn{padding:7px 14px;border-radius:7px;border:none;font-size:12px;font-weight:600;cursor:pointer;transition:all .15s;font-family:inherit}
.btn-primary{background:var(--accent);color:#fff}.btn-primary:hover{background:#7c3aed}
.btn-secondary{background:var(--surface2);color:var(--text-dim);border:1px solid var(--border)}.btn-secondary:hover{background:var(--border);color:var(--text)}
.btn-hint{background:transparent;color:var(--yellow);border:1px solid var(--yellow)40}.btn-hint:hover{background:var(--yellow-bg)}
.btn-answer{background:transparent;color:var(--green);border:1px solid var(--green)40}.btn-answer:hover{background:var(--green-bg)}
.btn-expected{background:transparent;color:var(--cyan);border:1px solid var(--cyan)40}.btn-expected:hover{background:var(--cyan-bg)}
.feedback-box{margin-top:10px;padding:10px 14px;border-radius:7px;font-size:12.5px;line-height:1.6;display:none}
.feedback-box.visible{display:block}
.fb-correct{background:var(--green-bg);border:1px solid var(--green)30;color:var(--green-light)}
.fb-wrong{background:var(--red-bg);border:1px solid var(--red)30;color:#fca5a5}
.answer-box{margin-top:10px;padding:10px 14px;border-radius:7px;background:var(--blue-bg);border:1px solid var(--blue)30;display:none;font-size:12.5px}
.answer-box.visible{display:block}
.answer-box .ab-label{font-size:10px;font-weight:700;color:var(--blue-light);text-transform:uppercase;letter-spacing:.5px;margin-bottom:6px}
/* Nav buttons */
.section-nav{display:flex;justify-content:space-between;margin-top:36px;padding-top:20px;border-top:1px solid var(--border)}
.btn-nav{padding:10px 20px;border-radius:8px;font-size:13px;font-weight:600;display:flex;align-items:center;gap:6px}
/* Result preview tables */
.result-table{width:100%;border-collapse:collapse;margin:8px 0;font-size:11px}
.result-table th{background:var(--green-bg);color:var(--green);padding:5px 8px;text-align:left;font-weight:600;border:1px solid var(--border)}
.result-table td{padding:5px 8px;border:1px solid var(--border);color:var(--text-dim);font-family:'Cascadia Code',Consolas,monospace}
/* Scrollbar */
::-webkit-scrollbar{width:5px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}
/* Animations */
.flash-g{animation:flashG .5s}
.flash-r{animation:flashR .5s}
@keyframes flashG{0%,100%{background:var(--surface)}50%{background:var(--green-bg)}}
@keyframes flashR{0%,100%{background:var(--surface)}50%{background:var(--red-bg)}}
/* Responsive */
@media(max-width:768px){
.sidebar{display:none}
.content{padding:16px}
.section-title{font-size:22px}
.cheat-grid,.compare{grid-template-columns:1fr}
.mobile-menu{display:flex!important}
}
.mobile-menu{display:none;position:fixed;bottom:0;left:0;right:0;background:var(--surface);border-top:1px solid var(--border);padding:6px;z-index:100;gap:3px;overflow-x:auto}
.mobile-menu button{flex-shrink:0;padding:6px 10px;background:var(--surface2);border:1px solid var(--border);color:var(--text-dim);border-radius:5px;font-size:10px;cursor:pointer}
.mobile-menu button.active{background:var(--accent);color:#fff;border-color:var(--accent)}
/* Run results */
.run-result{margin-top:10px;max-height:300px;overflow:auto;border:1px solid var(--border);border-radius:8px;display:none}
.run-result.visible{display:block}
.run-result table{width:100%;border-collapse:collapse;font-size:11px}
.run-result th{background:var(--cyan-bg);color:var(--cyan);padding:6px 10px;text-align:left;font-weight:600;border:1px solid var(--border);position:sticky;top:0;font-size:10px;text-transform:uppercase;letter-spacing:.5px}
.run-result td{padding:5px 10px;border:1px solid var(--border);color:var(--text-dim);font-family:'Cascadia Code',Consolas,monospace;font-size:11px}
.run-result tr:hover td{background:var(--surface2);color:var(--text)}
.run-result .run-info{padding:6px 12px;font-size:10px;color:var(--text-muted);border-top:1px solid var(--border);background:var(--surface)}
.run-error{padding:12px;color:var(--red);font-size:12px;background:var(--red-bg);border-radius:8px;margin-top:8px;font-family:'Cascadia Code',monospace;white-space:pre-wrap}
.btn-run{background:var(--cyan);color:#000;font-weight:700}.btn-run:hover{background:#22d3ee}
.btn-run:disabled{opacity:.5;cursor:not-allowed}
/* Sandbox */
.sandbox-editor{width:100%;min-height:150px;background:#0a0b10;border:1px solid var(--border);border-radius:8px;padding:14px;color:var(--text);font-family:'Cascadia Code','Fira Code',Consolas,monospace;font-size:13px;line-height:1.6;resize:vertical;outline:none}
.sandbox-editor:focus{border-color:var(--cyan)}
.sandbox-editor::placeholder{color:var(--text-muted)}
.sandbox-result{margin-top:12px;max-height:400px;overflow:auto}
.sandbox-tables{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:12px}
.sandbox-tables button{padding:5px 12px;border-radius:6px;border:1px solid var(--border);background:var(--surface2);color:var(--text-dim);font-size:11px;cursor:pointer;font-family:'Cascadia Code',monospace}
.sandbox-tables button:hover{background:var(--border);color:var(--text)}
/* Timer */
.timer-bar{display:none;padding:8px 20px;border-bottom:1px solid var(--border);font-size:12px;align-items:center;gap:8px;color:var(--text-dim)}
.timer-bar.active{display:flex}
.timer-time{font-family:'Cascadia Code',monospace;font-weight:700;font-size:14px;color:var(--yellow)}
.timer-bar .btn{padding:4px 10px;font-size:10px}
/* Loading overlay */
.db-loading{position:fixed;inset:0;background:var(--bg);display:flex;align-items:center;justify-content:center;flex-direction:column;gap:12px;z-index:9999;transition:opacity .5s}
.db-loading.done{opacity:0;pointer-events:none}
.db-loading h2{font-size:18px;color:var(--accent-light)}
.db-loading p{font-size:13px;color:var(--text-dim)}
.spinner{width:36px;height:36px;border:3px solid var(--border);border-top-color:var(--accent);border-radius:50%;animation:spin 1s linear infinite}
@keyframes spin{to{transform:rotate(360deg)}}
/* NULL badge */
.null-val{color:var(--text-muted);font-style:italic;font-size:10px}
</style>
</head>
<body>
<div class="db-loading" id="db-loading">
<div class="spinner"></div>
<h2>Loading SQL Engine...</h2>
<p>Setting up your practice database, Quinton</p>
</div>
<nav class="sidebar">
<div class="sidebar-header">
<div class="logo">SQL Prep</div>
<h1>Quinton Jackson</h1>
<div class="subtitle">Interview Ready • Live SQL Engine</div>
</div>
<div class="progress-container">
<div class="progress-stats"><span>Progress</span><span id="prog-text">0 / 0</span></div>
<div class="progress-bar"><div class="progress-fill" id="prog-fill"></div></div>
</div>
<div class="streak-display" id="streak-display" style="display:none">Streak: <strong id="streak-count">0</strong> correct in a row</div>
<div class="timer-bar" id="timer-bar">
Timer: <span class="timer-time" id="timer-time">00:00</span>
<button class="btn btn-secondary" onclick="stopTimer()">Stop</button>
<button class="btn btn-secondary" onclick="resetTimer()">Reset</button>
</div>
<div class="nav-section" id="nav-items"></div>
</nav>
<div class="mobile-menu" id="mobile-nav"></div>
<div class="main" id="main-scroll">
<div class="content" id="content"></div>
</div>
<script>
// ====================== UTILITIES ======================
function norm(s){return s.toLowerCase().replace(/\s+/g,' ').replace(/[;'"`]/g,'').trim()}
function esc(s){return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')}
let completed=JSON.parse(localStorage.getItem('qj_done')||'{}');
let streak=parseInt(localStorage.getItem('qj_streak')||'0');
// ====================== ALL SECTIONS ======================
const S=[
// ──────────────── WELCOME ────────────────
{
id:"welcome",title:"Welcome",icon:"Q",group:"Start",problems:[],
content:`
<div class="section-header">
<div class="section-num">Let's Go</div>
<div class="section-title">What's up, Quinton</div>
<div class="section-desc">You've got an interview coming up and SQL is rusty. This app is built specifically for you - work through it top to bottom and you'll be solid. Every concept has visuals, every problem has hints and answers.</div>
</div>
<div class="card">
<h3>Your Game Plan</h3>
<div class="step-through">
<div class="step-item"><div class="step-num">1</div><div class="step-content"><strong>Read the concept</strong> - each section teaches one idea with visuals and analogies</div></div>
<div class="step-item"><div class="step-num">2</div><div class="step-content"><strong>Study the examples</strong> - see the SQL, see the result, understand why</div></div>
<div class="step-item"><div class="step-num">3</div><div class="step-content"><strong>Solve the problems</strong> - write SQL and click <strong>"Run Query"</strong> to execute it live and see real results</div></div>
<div class="step-item"><div class="step-num">4</div><div class="step-content"><strong>Click "Check Answer"</strong> to validate your query structure. Use "Hint" or "Show Answer" if stuck</div></div>
<div class="step-item"><div class="step-num">5</div><div class="step-content"><strong>Use the Sandbox</strong> (in the sidebar) to experiment freely with any query you want</div></div>
<div class="step-item"><div class="step-num">6</div><div class="step-content"><strong>Start the Timer</strong> in the Sandbox to simulate interview pressure (aim for under 15 mins per hard problem)</div></div>
</div>
</div>
<div class="card">
<h3>The Database You're Working With <span class="tag tag-core">5 TABLES</span></h3>
<div class="card-text" style="margin-bottom:12px">This is a <strong>company database</strong> - employees, departments, customers, products, and orders. These tables connect to each other (that's where JOINs come in). Here's the map:</div>
<div class="er-diagram">
<div class="er-tables">
<div class="er-table">
<div class="er-table-name">employees</div>
<div class="er-col pk">id (PK)</div>
<div class="er-col">name</div>
<div class="er-col fk">dept_id (FK)</div>
<div class="er-col">salary</div>
<div class="er-col">hire_date</div>
<div class="er-col fk">manager_id (FK)</div>
</div>
<div class="er-table">
<div class="er-table-name">departments</div>
<div class="er-col pk">id (PK)</div>
<div class="er-col">name</div>
<div class="er-col">budget</div>
<div class="er-col">location</div>
</div>
<div class="er-table">
<div class="er-table-name">customers</div>
<div class="er-col pk">id (PK)</div>
<div class="er-col">name</div>
<div class="er-col">email</div>
<div class="er-col">city</div>
<div class="er-col">signup_date</div>
</div>
<div class="er-table">
<div class="er-table-name">products</div>
<div class="er-col pk">id (PK)</div>
<div class="er-col">name</div>
<div class="er-col">category</div>
<div class="er-col">price</div>
<div class="er-col">stock</div>
</div>
<div class="er-table">
<div class="er-table-name">orders</div>
<div class="er-col pk">id (PK)</div>
<div class="er-col fk">customer_id (FK)</div>
<div class="er-col fk">product_id (FK)</div>
<div class="er-col fk">employee_id (FK)</div>
<div class="er-col">quantity</div>
<div class="er-col">order_date</div>
<div class="er-col">status</div>
</div>
</div>
<div style="text-align:center;margin-top:12px;font-size:10px;color:var(--text-muted)">
PK = Primary Key (unique ID) • FK = Foreign Key (links to another table's PK)
</div>
</div>
</div>
<div class="card">
<h3>employees <span class="tag tag-visual">8 ROWS</span></h3>
<table class="sql-table">
<tr><th>id</th><th>name</th><th>dept_id</th><th>salary</th><th>hire_date</th><th>manager_id</th></tr>
<tr><td>1</td><td>Alice</td><td>1</td><td>60000</td><td>2020-01-15</td><td>NULL</td></tr>
<tr><td>2</td><td>Bob</td><td>2</td><td>85000</td><td>2019-06-01</td><td>4</td></tr>
<tr><td>3</td><td>Charlie</td><td>1</td><td>55000</td><td>2021-03-20</td><td>1</td></tr>
<tr><td>4</td><td>Diana</td><td>2</td><td>92000</td><td>2018-11-10</td><td>NULL</td></tr>
<tr><td>5</td><td>Eve</td><td>3</td><td>50000</td><td>2022-07-01</td><td>7</td></tr>
<tr><td>6</td><td>Frank</td><td>2</td><td>78000</td><td>2020-09-15</td><td>4</td></tr>
<tr><td>7</td><td>Grace</td><td>3</td><td>54000</td><td>2021-01-10</td><td>NULL</td></tr>
<tr><td>8</td><td>Hank</td><td>1</td><td>62000</td><td>2019-04-22</td><td>1</td></tr>
</table>
</div>
<div class="card">
<h3>departments <span class="tag tag-visual">4 ROWS</span></h3>
<table class="sql-table">
<tr><th>id</th><th>name</th><th>budget</th><th>location</th></tr>
<tr><td>1</td><td>Sales</td><td>200000</td><td>New York</td></tr>
<tr><td>2</td><td>Engineering</td><td>500000</td><td>San Francisco</td></tr>
<tr><td>3</td><td>Marketing</td><td>150000</td><td>New York</td></tr>
<tr><td>4</td><td>HR</td><td>100000</td><td>Chicago</td></tr>
</table>
</div>
<div class="card">
<h3>customers <span class="tag tag-visual">6 ROWS</span></h3>
<table class="sql-table">
<tr><th>id</th><th>name</th><th>email</th><th>city</th><th>signup_date</th></tr>
<tr><td>1</td><td>Acme Corp</td><td>buy@acme.com</td><td>New York</td><td>2022-01-10</td></tr>
<tr><td>2</td><td>Globex</td><td>info@globex.com</td><td>Chicago</td><td>2022-03-15</td></tr>
<tr><td>3</td><td>Initech</td><td>orders@initech.com</td><td>Austin</td><td>2021-11-20</td></tr>
<tr><td>4</td><td>Umbrella</td><td>sales@umbrella.com</td><td>San Francisco</td><td>2023-02-01</td></tr>
<tr><td>5</td><td>Stark Ind</td><td>tony@stark.com</td><td>New York</td><td>2021-06-30</td></tr>
<tr><td>6</td><td>Wayne Ent</td><td>bruce@wayne.com</td><td>Chicago</td><td>2023-05-14</td></tr>
</table>
</div>
<div class="card">
<h3>products <span class="tag tag-visual">6 ROWS</span></h3>
<table class="sql-table">
<tr><th>id</th><th>name</th><th>category</th><th>price</th><th>stock</th></tr>
<tr><td>1</td><td>Widget A</td><td>Gadgets</td><td>25.00</td><td>500</td></tr>
<tr><td>2</td><td>Widget B</td><td>Gadgets</td><td>45.00</td><td>200</td></tr>
<tr><td>3</td><td>Gizmo X</td><td>Tools</td><td>150.00</td><td>75</td></tr>
<tr><td>4</td><td>Gizmo Y</td><td>Tools</td><td>89.99</td><td>120</td></tr>
<tr><td>5</td><td>Doohickey</td><td>Accessories</td><td>12.50</td><td>1000</td></tr>
<tr><td>6</td><td>Thingamajig</td><td>Accessories</td><td>34.99</td><td>0</td></tr>
</table>
</div>
<div class="card">
<h3>orders <span class="tag tag-visual">10 ROWS</span></h3>
<table class="sql-table">
<tr><th>id</th><th>customer_id</th><th>product_id</th><th>employee_id</th><th>quantity</th><th>order_date</th><th>status</th></tr>
<tr><td>1</td><td>1</td><td>1</td><td>1</td><td>100</td><td>2023-01-15</td><td>completed</td></tr>
<tr><td>2</td><td>1</td><td>3</td><td>1</td><td>10</td><td>2023-02-20</td><td>completed</td></tr>
<tr><td>3</td><td>2</td><td>2</td><td>3</td><td>50</td><td>2023-01-30</td><td>completed</td></tr>
<tr><td>4</td><td>3</td><td>5</td><td>8</td><td>200</td><td>2023-03-10</td><td>completed</td></tr>
<tr><td>5</td><td>5</td><td>1</td><td>3</td><td>75</td><td>2023-04-05</td><td>shipped</td></tr>
<tr><td>6</td><td>4</td><td>4</td><td>1</td><td>30</td><td>2023-05-01</td><td>shipped</td></tr>
<tr><td>7</td><td>2</td><td>3</td><td>8</td><td>5</td><td>2023-06-12</td><td>pending</td></tr>
<tr><td>8</td><td>5</td><td>2</td><td>3</td><td>25</td><td>2023-07-20</td><td>pending</td></tr>
<tr><td>9</td><td>3</td><td>6</td><td>1</td><td>150</td><td>2023-08-01</td><td>cancelled</td></tr>
<tr><td>10</td><td>6</td><td>1</td><td>8</td><td>60</td><td>2023-09-15</td><td>completed</td></tr>
</table>
</div>
<div class="card">
<h3>SQL Execution Order <span class="tag tag-gotcha">MEMORIZE THIS</span></h3>
<div class="card-text" style="margin-bottom:10px">SQL doesn't run in the order you write it. Interviewers <strong>will</strong> test this:</div>
<div class="exec-order">
<div class="exec-step"><div class="es-num">1</div><div class="es-kw">FROM</div><div class="es-label">Pick table</div></div>
<div class="exec-arrow">→</div>
<div class="exec-step"><div class="es-num">2</div><div class="es-kw">JOIN</div><div class="es-label">Combine</div></div>
<div class="exec-arrow">→</div>
<div class="exec-step"><div class="es-num">3</div><div class="es-kw">WHERE</div><div class="es-label">Filter rows</div></div>
<div class="exec-arrow">→</div>
<div class="exec-step"><div class="es-num">4</div><div class="es-kw">GROUP BY</div><div class="es-label">Group</div></div>
<div class="exec-arrow">→</div>
<div class="exec-step"><div class="es-num">5</div><div class="es-kw">HAVING</div><div class="es-label">Filter groups</div></div>
<div class="exec-arrow">→</div>
<div class="exec-step"><div class="es-num">6</div><div class="es-kw">SELECT</div><div class="es-label">Pick cols</div></div>
<div class="exec-arrow">→</div>
<div class="exec-step"><div class="es-num">7</div><div class="es-kw">ORDER BY</div><div class="es-label">Sort</div></div>
<div class="exec-arrow">→</div>
<div class="exec-step"><div class="es-num">8</div><div class="es-kw">LIMIT</div><div class="es-label">Cap rows</div></div>
</div>
</div>
`},
// ──────────────── SELECT & FROM ────────────────
{
id:"select",title:"SELECT & FROM",icon:"1",group:"Fundamentals",
content:`
<div class="section-header">
<div class="section-num">Section 1</div>
<div class="section-title">SELECT & FROM</div>
<div class="section-desc">Every SQL query starts here. SELECT picks what columns, FROM picks what table.</div>
</div>
<div class="card">
<h3>Think of it Like This <span class="tag tag-think">ANALOGY</span></h3>
<div class="card-text">
Imagine a spreadsheet with tabs. <strong>FROM</strong> is choosing which tab to open. <strong>SELECT</strong> is choosing which columns to look at.<br><br>
<code>SELECT name, salary FROM employees</code> = "Open the employees tab, show me just the name and salary columns"<br><br>
<code>SELECT *</code> means "show me ALL columns" - quick for exploring, but in real work always list specific columns.
</div>
</div>
<div class="card">
<h3>Syntax <span class="tag tag-syntax">SYNTAX</span></h3>
<pre><code><span class="cm">-- Get everything</span>
<span class="kw">SELECT</span> * <span class="kw">FROM</span> <span class="tbl">employees</span>;
<span class="cm">-- Get specific columns</span>
<span class="kw">SELECT</span> name, salary <span class="kw">FROM</span> <span class="tbl">employees</span>;
<span class="cm">-- Rename columns in the output (aliases)</span>
<span class="kw">SELECT</span> name <span class="kw">AS</span> employee_name,
salary <span class="kw">AS</span> annual_pay
<span class="kw">FROM</span> <span class="tbl">employees</span>;
<span class="cm">-- Math in SELECT</span>
<span class="kw">SELECT</span> name,
salary,
salary / <span class="num">12</span> <span class="kw">AS</span> monthly_pay
<span class="kw">FROM</span> <span class="tbl">employees</span>;</code></pre>
</div>
<div class="card">
<h3>Result Walkthrough <span class="tag tag-visual">VISUAL</span></h3>
<div class="card-text">Running <code>SELECT name, salary, salary / 12 AS monthly_pay FROM employees</code>:</div>
<table class="result-table">
<tr><th>name</th><th>salary</th><th>monthly_pay</th></tr>
<tr><td>Alice</td><td>60000</td><td>5000</td></tr>
<tr><td>Bob</td><td>85000</td><td>7083</td></tr>
<tr><td>Charlie</td><td>55000</td><td>4583</td></tr>
<tr><td>...</td><td>...</td><td>...</td></tr>
</table>
<div class="card-text" style="margin-top:8px">See how <code>salary / 12</code> created a new calculated column? You can do math right in SELECT.</div>
</div>
`,
problems:[
{id:"1.1",text:"Get all columns from the orders table.",difficulty:"easy",
hint:"Use SELECT * to get all columns.",
answer:"SELECT * FROM orders;",
check:s=>{const n=norm(s);return n.includes("select")&&n.includes("*")&&n.includes("from")&&n.includes("orders")}},
{id:"1.2",text:"Get just the name and city of all customers.",difficulty:"easy",
hint:"List the columns you want: SELECT col1, col2 FROM table",
answer:"SELECT name, city FROM customers;",
check:s=>{const n=norm(s);return n.includes("select")&&n.includes("name")&&n.includes("city")&&n.includes("from")&&n.includes("customers")&&!n.includes("*")}},
{id:"1.3",text:"Get the name, category, and stock of all products.",difficulty:"easy",
hint:"SELECT col1, col2, col3 FROM table",
answer:"SELECT name, category, stock FROM products;",
check:s=>{const n=norm(s);return n.includes("select")&&n.includes("name")&&n.includes("category")&&n.includes("stock")&&n.includes("from")&&n.includes("products")}},
{id:"1.4",text:"Show each product's name and a column called total_value showing price * stock.",difficulty:"easy",
hint:"Use math in SELECT: price * stock AS total_value",
answer:"SELECT name, price * stock AS total_value FROM products;",
check:s=>{const n=norm(s);return n.includes("name")&&n.includes("price")&&n.includes("stock")&&n.includes("*")&&n.includes("from")&&n.includes("products")}},
{id:"1.5",text:"Show each employee's name, salary, and a column called weekly_pay showing salary / 52.",difficulty:"medium",
hint:"SELECT name, salary, salary / 52 AS weekly_pay FROM employees",
answer:"SELECT name, salary, salary / 52 AS weekly_pay FROM employees;",
check:s=>{const n=norm(s);return n.includes("name")&&n.includes("salary")&&n.includes("52")&&n.includes("/")&&n.includes("employees")}},
]},
// ──────────────── WHERE ────────────────
{
id:"where",title:"WHERE",icon:"2",group:"Fundamentals",
content:`
<div class="section-header">
<div class="section-num">Section 2</div>
<div class="section-title">WHERE - Filtering Rows</div>
<div class="section-desc">WHERE is a filter. Only rows matching your condition get through. Think of a bouncer at a club.</div>
</div>
<div class="card">
<h3>How Filtering Works <span class="tag tag-visual">VISUAL</span></h3>
<div class="compare">
<div class="compare-box">
<h4>All 8 Employees</h4>
<div class="mono">Alice 60k Sales<br>Bob 85k Eng<br>Charlie 55k Sales<br>Diana 92k Eng<br>Eve 50k Mkt<br>Frank 78k Eng<br>Grace 54k Mkt<br>Hank 62k Sales</div>
</div>
<div class="compare-box good">
<h4>WHERE salary > 70000</h4>
<div class="mono" style="color:var(--green)">Bob 85k Eng<br>Diana 92k Eng<br>Frank 78k Eng</div>
<div style="font-size:10px;color:var(--text-muted);margin-top:8px">5 rows filtered out, 3 remain</div>
</div>
</div>
</div>
<div class="card">
<h3>All WHERE Operators <span class="tag tag-syntax">CHEAT SHEET</span></h3>
<div class="cheat-grid">
<div class="cheat-item"><div class="cheat-op">=</div><div class="cheat-desc">Equals</div><div class="cheat-ex">WHERE city = 'Chicago'</div></div>
<div class="cheat-item"><div class="cheat-op">!= or <></div><div class="cheat-desc">Not equal</div><div class="cheat-ex">WHERE status != 'cancelled'</div></div>
<div class="cheat-item"><div class="cheat-op">> < >= <=</div><div class="cheat-desc">Comparisons</div><div class="cheat-ex">WHERE price >= 50.00</div></div>
<div class="cheat-item"><div class="cheat-op">AND / OR</div><div class="cheat-desc">Combine conditions</div><div class="cheat-ex">WHERE a > 5 AND b = 'x'</div></div>
<div class="cheat-item"><div class="cheat-op">IN (...)</div><div class="cheat-desc">Match any in list</div><div class="cheat-ex">WHERE city IN ('NY','LA')</div></div>
<div class="cheat-item"><div class="cheat-op">NOT IN (...)</div><div class="cheat-desc">Exclude a list</div><div class="cheat-ex">WHERE status NOT IN ('x')</div></div>
<div class="cheat-item"><div class="cheat-op">BETWEEN x AND y</div><div class="cheat-desc">Range (inclusive)</div><div class="cheat-ex">WHERE price BETWEEN 10 AND 50</div></div>
<div class="cheat-item"><div class="cheat-op">LIKE 'A%'</div><div class="cheat-desc">Pattern match</div><div class="cheat-ex">% = any chars, _ = one char</div></div>
<div class="cheat-item"><div class="cheat-op">IS NULL</div><div class="cheat-desc">Check for empty</div><div class="cheat-ex">WHERE manager_id IS NULL</div></div>
<div class="cheat-item"><div class="cheat-op">IS NOT NULL</div><div class="cheat-desc">Check not empty</div><div class="cheat-ex">WHERE email IS NOT NULL</div></div>
</div>
</div>
<div class="card">
<h3>Real Examples <span class="tag tag-syntax">SYNTAX</span></h3>
<pre><code><span class="cm">-- Products that cost more than $50</span>
<span class="kw">SELECT</span> name, price <span class="kw">FROM</span> <span class="tbl">products</span>
<span class="kw">WHERE</span> price <span class="op">></span> <span class="num">50</span>;
<span class="cm">-- Completed orders from 2023</span>
<span class="kw">SELECT</span> * <span class="kw">FROM</span> <span class="tbl">orders</span>
<span class="kw">WHERE</span> status <span class="op">=</span> <span class="str">'completed'</span>
<span class="kw">AND</span> order_date <span class="op">>=</span> <span class="str">'2023-01-01'</span>;
<span class="cm">-- Customers in New York or Chicago</span>
<span class="kw">SELECT</span> name, city <span class="kw">FROM</span> <span class="tbl">customers</span>
<span class="kw">WHERE</span> city <span class="kw">IN</span> (<span class="str">'New York'</span>, <span class="str">'Chicago'</span>);
<span class="cm">-- Employees with no manager (top of org chart)</span>
<span class="kw">SELECT</span> name <span class="kw">FROM</span> <span class="tbl">employees</span>
<span class="kw">WHERE</span> manager_id <span class="kw">IS NULL</span>;</code></pre>
</div>
<div class="card">
<h3>NULL Trap <span class="tag tag-gotcha">GOTCHA</span></h3>
<div class="compare">
<div class="compare-box bad"><h4>WRONG</h4><div class="mono" style="color:var(--red)">WHERE manager_id = NULL</div><div style="font-size:10px;color:var(--text-muted);margin-top:4px">This returns NOTHING. NULL isn't a value, it's the absence of one.</div></div>
<div class="compare-box good"><h4>CORRECT</h4><div class="mono" style="color:var(--green)">WHERE manager_id IS NULL</div><div style="font-size:10px;color:var(--text-muted);margin-top:4px">Always use IS NULL / IS NOT NULL</div></div>
</div>
</div>
`,
problems:[
{id:"2.1",text:"Get all employees in department 2 (dept_id = 2).",difficulty:"easy",
hint:"WHERE dept_id = 2",
answer:"SELECT * FROM employees WHERE dept_id = 2;",
check:s=>{const n=norm(s);return n.includes("from")&&n.includes("employees")&&n.includes("where")&&n.includes("dept_id")&&n.includes("2")}},
{id:"2.2",text:"Get the names of products that cost less than 30.",difficulty:"easy",
hint:"Use WHERE price < 30.",
answer:"SELECT name FROM products WHERE price < 30;",
check:s=>{const n=norm(s);return n.includes("name")&&n.includes("products")&&n.includes("where")&&n.includes("price")&&n.includes("30")}},
{id:"2.3",text:"Get all orders with status 'shipped'.",difficulty:"easy",
hint:"WHERE status = 'shipped'",
answer:"SELECT * FROM orders WHERE status = 'shipped';",
check:s=>{const n=norm(s);return n.includes("orders")&&n.includes("where")&&n.includes("status")&&n.includes("shipped")}},
{id:"2.4",text:"Find employees with no manager (manager_id IS NULL).",difficulty:"easy",
hint:"Use IS NULL, not = NULL!",
answer:"SELECT * FROM employees WHERE manager_id IS NULL;",
check:s=>{const n=norm(s);return n.includes("employees")&&n.includes("where")&&n.includes("manager_id")&&n.includes("is null")}},
{id:"2.5",text:"Find employees who earn between 60000 and 90000 (inclusive).",difficulty:"medium",
hint:"Use BETWEEN or two comparisons with AND.",
answer:"SELECT * FROM employees WHERE salary BETWEEN 60000 AND 90000;",
check:s=>{const n=norm(s);return n.includes("employees")&&n.includes("where")&&n.includes("salary")&&(n.includes("between")||(n.includes(">=")&&n.includes("<=")))}},
{id:"2.6",text:"Find products in the 'Gadgets' category OR with price over 100.",difficulty:"medium",
hint:"Combine with OR.",
answer:"SELECT * FROM products WHERE category = 'Gadgets' OR price > 100;",
check:s=>{const n=norm(s);return n.includes("products")&&n.includes("where")&&n.includes("or")&&(n.includes("gadgets")||n.includes("100"))}},
{id:"2.7",text:"Find all orders placed in 2023 that are NOT 'pending'.",difficulty:"medium",
hint:"Combine date range with status != 'pending' or NOT IN.",
answer:"SELECT * FROM orders\nWHERE order_date >= '2023-01-01' AND order_date < '2024-01-01'\nAND status != 'pending';",
check:s=>{const n=norm(s);return n.includes("orders")&&n.includes("where")&&n.includes("order_date")&&n.includes("2023")&&(n.includes("!=")|| n.includes("<>")|| n.includes("not"))&&n.includes("pending")}},
]},
// ──────────────── STRING FUNCTIONS & LIKE ────────────────
{
id:"strings",title:"String Functions",icon:"S",group:"Fundamentals",
content:`
<div class="section-header">
<div class="section-num">Section 2B</div>
<div class="section-title">String Functions & LIKE</div>
<div class="section-desc">Text manipulation is everywhere in real SQL work. LIKE is for pattern matching, and string functions let you clean and transform text data.</div>
</div>
<div class="card">
<h3>LIKE Pattern Matching <span class="tag tag-core">MUST KNOW</span></h3>
<div class="card-text">
LIKE is SQL's search tool for text. Two wildcards:<br><br>
<strong>%</strong> = any number of characters (including zero)<br>
<strong>_</strong> = exactly one character<br><br>
Think of <code>%</code> as "anything goes here" and <code>_</code> as "one character goes here."
</div>
<div class="cheat-grid">
<div class="cheat-item"><div class="cheat-op">LIKE 'A%'</div><div class="cheat-desc">Starts with A</div><div class="cheat-ex">Alice, Acme Corp</div></div>
<div class="cheat-item"><div class="cheat-op">LIKE '%Corp'</div><div class="cheat-desc">Ends with Corp</div><div class="cheat-ex">Acme Corp</div></div>
<div class="cheat-item"><div class="cheat-op">LIKE '%an%'</div><div class="cheat-desc">Contains "an"</div><div class="cheat-ex">Frank, Hank</div></div>
<div class="cheat-item"><div class="cheat-op">LIKE '_____'</div><div class="cheat-desc">Exactly 5 chars</div><div class="cheat-ex">Alice, Grace</div></div>
<div class="cheat-item"><div class="cheat-op">LIKE 'W%e'</div><div class="cheat-desc">Starts W, ends e</div><div class="cheat-ex">Combine start + end</div></div>
<div class="cheat-item"><div class="cheat-op">NOT LIKE '%x%'</div><div class="cheat-desc">Does NOT contain x</div><div class="cheat-ex">Exclude matches</div></div>
</div>
</div>
<div class="card">
<h3>Essential String Functions <span class="tag tag-syntax">TOOLKIT</span></h3>
<pre><code><span class="cm">-- Convert to upper/lower case</span>
<span class="kw">SELECT</span> <span class="fn">UPPER</span>(name), <span class="fn">LOWER</span>(email)
<span class="kw">FROM</span> <span class="tbl">customers</span>;
<span class="cm">-- Get string length</span>
<span class="kw">SELECT</span> name, <span class="fn">LENGTH</span>(name) <span class="kw">AS</span> name_len
<span class="kw">FROM</span> <span class="tbl">employees</span>;
<span class="cm">-- Extract part of a string (SUBSTR)</span>
<span class="cm">-- SUBSTR(string, start_position, length)</span>
<span class="kw">SELECT</span> <span class="fn">SUBSTR</span>(email, <span class="num">1</span>, <span class="num">3</span>) <span class="kw">AS</span> first_3
<span class="kw">FROM</span> <span class="tbl">customers</span>;
<span class="cm">-- Replace text within a string</span>
<span class="kw">SELECT</span> <span class="fn">REPLACE</span>(city, <span class="str">'New York'</span>, <span class="str">'NYC'</span>)
<span class="kw">FROM</span> <span class="tbl">customers</span>;
<span class="cm">-- Concatenation (SQLite uses ||)</span>
<span class="kw">SELECT</span> name <span class="op">||</span> <span class="str">' - '</span> <span class="op">||</span> city <span class="kw">AS</span> label
<span class="kw">FROM</span> <span class="tbl">customers</span>;
<span class="cm">-- Trim whitespace</span>
<span class="kw">SELECT</span> <span class="fn">TRIM</span>(<span class="str">' hello '</span>); <span class="cm">-- 'hello'</span>
<span class="kw">SELECT</span> <span class="fn">LTRIM</span>(<span class="str">' hello'</span>); <span class="cm">-- 'hello'</span>
<span class="kw">SELECT</span> <span class="fn">RTRIM</span>(<span class="str">'hello '</span>); <span class="cm">-- 'hello'</span></code></pre>
</div>
<div class="card">
<h3>Result Walkthrough <span class="tag tag-visual">VISUAL</span></h3>
<div class="card-text">Running <code>SELECT name, UPPER(name) AS upper_name, LENGTH(name) AS len FROM employees</code>:</div>
<table class="result-table">
<tr><th>name</th><th>upper_name</th><th>len</th></tr>
<tr><td>Alice</td><td>ALICE</td><td>5</td></tr>
<tr><td>Bob</td><td>BOB</td><td>3</td></tr>
<tr><td>Charlie</td><td>CHARLIE</td><td>7</td></tr>
<tr><td>...</td><td>...</td><td>...</td></tr>
</table>
</div>
<div class="card">
<h3>Case-Insensitive Search <span class="tag tag-tip">INTERVIEW TIP</span></h3>
<div class="card-text">
By default, LIKE in SQLite is case-insensitive for ASCII. But in PostgreSQL, it's case-sensitive.<br><br>
<strong>Safe approach for all databases:</strong> force both sides to the same case:<br>
<code>WHERE UPPER(name) LIKE UPPER('%search%')</code><br><br>
Or use <code>ILIKE</code> in PostgreSQL (case-insensitive LIKE).
</div>
</div>
<div class="card">
<h3>Concatenation Across Databases <span class="tag tag-gotcha">GOTCHA</span></h3>
<div class="compare">
<div class="compare-box"><h4>SQLite / PostgreSQL</h4><div class="mono">name || ' - ' || city</div><div style="font-size:10px;color:var(--text-muted);margin-top:4px">Use the || operator</div></div>
<div class="compare-box good"><h4>MySQL / SQL Server</h4><div class="mono" style="color:var(--green)">CONCAT(name, ' - ', city)</div><div style="font-size:10px;color:var(--text-muted);margin-top:4px">Use the CONCAT function</div></div>
</div>
</div>
`,
problems:[
{id:"S.1",text:"Find all customers whose name contains the letter 'a' (use LIKE).",difficulty:"easy",
hint:"WHERE name LIKE '%a%'",
answer:"SELECT * FROM customers WHERE name LIKE '%a%';",
check:s=>{const n=norm(s);return n.includes("customers")&&n.includes("where")&&n.includes("name")&&n.includes("like")&&n.includes("%a%")}},
{id:"S.2",text:"Find all employees whose name starts with 'B'.",difficulty:"easy",
hint:"WHERE name LIKE 'B%'",
answer:"SELECT * FROM employees WHERE name LIKE 'B%';",
check:s=>{const n=norm(s);return n.includes("employees")&&n.includes("where")&&n.includes("name")&&n.includes("like")&&n.includes("b%")}},
{id:"S.3",text:"Show each product's name in lowercase and its category in uppercase.",difficulty:"easy",
hint:"SELECT LOWER(name), UPPER(category) FROM products",
answer:"SELECT LOWER(name) AS lower_name, UPPER(category) AS upper_cat FROM products;",
check:s=>{const n=norm(s);return n.includes("lower")&&n.includes("upper")&&n.includes("products")&&n.includes("name")&&n.includes("category")}},
{id:"S.4",text:"Find all customers whose city ends with the letter 'o'.",difficulty:"easy",
hint:"WHERE city LIKE '%o'",
answer:"SELECT * FROM customers WHERE city LIKE '%o';",
check:s=>{const n=norm(s);return n.includes("customers")&&n.includes("where")&&n.includes("city")&&n.includes("like")&&n.includes("%o")}},
{id:"S.5",text:"Create a 'summary' column combining product name and category: 'Name (Category)'. Use string concatenation (||).",difficulty:"medium",
hint:"Use || to concatenate: name || ' (' || category || ')'",
answer:"SELECT name || ' (' || category || ')' AS summary FROM products;",
check:s=>{const n=norm(s);return n.includes("||")&&n.includes("name")&&n.includes("category")&&n.includes("products")}},
{id:"S.6",text:"Find employees whose names have exactly 3 characters.",difficulty:"medium",
hint:"Use LENGTH(name) = 3 or LIKE '___' (three underscores).",
answer:"SELECT * FROM employees WHERE LENGTH(name) = 3;",
check:s=>{const n=norm(s);return n.includes("employees")&&n.includes("where")&&((n.includes("length")&&n.includes("3"))||(n.includes("like")&&n.includes("___")))}},
{id:"S.7",text:"Replace 'New York' with 'NYC' in the city column for all customers. Show name and the modified city.",difficulty:"hard",
hint:"Use REPLACE(city, 'New York', 'NYC')",
answer:"SELECT name, REPLACE(city, 'New York', 'NYC') AS city FROM customers;",
check:s=>{const n=norm(s);return n.includes("replace")&&n.includes("city")&&n.includes("new york")&&n.includes("nyc")&&n.includes("customers")}},
]},
// ──────────────── ORDER BY, DISTINCT, LIMIT ────────────────
{
id:"order",title:"ORDER BY & DISTINCT",icon:"3",group:"Fundamentals",
content:`
<div class="section-header">
<div class="section-num">Section 3</div>
<div class="section-title">ORDER BY, DISTINCT & LIMIT</div>
<div class="section-desc">Sorting, removing duplicates, and capping results.</div>
</div>
<div class="card">
<h3>ORDER BY <span class="tag tag-core">CORE</span></h3>
<div class="card-text">
<strong>ASC</strong> = ascending (A-Z, 1-100, oldest first) - <strong>this is the default</strong><br>
<strong>DESC</strong> = descending (Z-A, 100-1, newest first)<br><br>
You can sort by multiple columns: <code>ORDER BY dept ASC, salary DESC</code> = sort by dept alphabetically, then within each dept sort salary highest first.
</div>
<pre><code><span class="cm">-- Highest paid first</span>
<span class="kw">SELECT</span> name, salary <span class="kw">FROM</span> <span class="tbl">employees</span>
<span class="kw">ORDER BY</span> salary <span class="kw">DESC</span>;
<span class="cm">-- Most expensive products, limit to top 3</span>
<span class="kw">SELECT</span> name, price <span class="kw">FROM</span> <span class="tbl">products</span>
<span class="kw">ORDER BY</span> price <span class="kw">DESC</span>
<span class="kw">LIMIT</span> <span class="num">3</span>;
<span class="cm">-- Most recent orders first</span>
<span class="kw">SELECT</span> * <span class="kw">FROM</span> <span class="tbl">orders</span>
<span class="kw">ORDER BY</span> order_date <span class="kw">DESC</span>;</code></pre>
</div>
<div class="card">
<h3>DISTINCT <span class="tag tag-core">CORE</span></h3>
<div class="compare">
<div class="compare-box"><h4>SELECT city FROM customers</h4><div class="mono">New York<br>Chicago<br>Austin<br>San Francisco<br>New York<br>Chicago</div><div style="font-size:10px;color:var(--text-muted);margin-top:4px">6 rows, with dupes</div></div>
<div class="compare-box good"><h4>SELECT DISTINCT city FROM customers</h4><div class="mono" style="color:var(--green)">New York<br>Chicago<br>Austin<br>San Francisco</div><div style="font-size:10px;color:var(--text-muted);margin-top:4px">4 rows, dupes removed</div></div>
</div>
</div>
`,
problems:[
{id:"3.1",text:"Get all products sorted by price, cheapest first.",difficulty:"easy",
hint:"ORDER BY price ASC (ASC is default)",
answer:"SELECT * FROM products ORDER BY price ASC;",
check:s=>{const n=norm(s);return n.includes("products")&&n.includes("order by")&&n.includes("price")}},
{id:"3.2",text:"Get all unique statuses from the orders table.",difficulty:"easy",
hint:"SELECT DISTINCT status ...",
answer:"SELECT DISTINCT status FROM orders;",
check:s=>{const n=norm(s);return n.includes("distinct")&&n.includes("status")&&n.includes("orders")}},
{id:"3.3",text:"Get the 5 most recent orders (show id, order_date, status).",difficulty:"easy",
hint:"ORDER BY order_date DESC LIMIT 5",
answer:"SELECT id, order_date, status FROM orders ORDER BY order_date DESC LIMIT 5;",
check:s=>{const n=norm(s);return n.includes("orders")&&n.includes("order_date")&&n.includes("order by")&&n.includes("desc")&&n.includes("limit")&&n.includes("5")}},
{id:"3.4",text:"Get all unique cities from the customers table, sorted Z to A.",difficulty:"easy",
hint:"DISTINCT + ORDER BY ... DESC",
answer:"SELECT DISTINCT city FROM customers ORDER BY city DESC;",
check:s=>{const n=norm(s);return n.includes("distinct")&&n.includes("city")&&n.includes("customers")&&n.includes("order by")&&n.includes("desc")}},
{id:"3.5",text:"Get the 2 most expensive products (name and price).",difficulty:"medium",
hint:"ORDER BY price DESC LIMIT 2",
answer:"SELECT name, price FROM products ORDER BY price DESC LIMIT 2;",
check:s=>{const n=norm(s);return n.includes("name")&&n.includes("price")&&n.includes("products")&&n.includes("order by")&&n.includes("desc")&&n.includes("limit")&&n.includes("2")}},
{id:"3.6",text:"Get all unique product categories, sorted alphabetically.",difficulty:"easy",
hint:"SELECT DISTINCT + ORDER BY",
answer:"SELECT DISTINCT category FROM products ORDER BY category;",
check:s=>{const n=norm(s);return n.includes("distinct")&&n.includes("category")&&n.includes("products")&&n.includes("order by")}},
]},
// ──────────────── SQL EXECUTION ORDER ────────────────
{
id:"execorder",title:"Execution Order",icon:"EO",group:"Fundamentals",
content:`
<div class="section-header">
<div class="section-num">Concept Deep Dive</div>
<div class="section-title">SQL Execution Order</div>
<div class="section-desc">SQL doesn't run top to bottom. Understanding the real execution order prevents 90% of beginner mistakes and is a common interview question.</div>
</div>
<div class="card">
<h3>Write Order vs Run Order <span class="tag tag-core">KEY CONCEPT</span></h3>
<div class="compare">
<div class="compare-box">
<h4>How You Write It</h4>
<div class="mono">1. SELECT<br>2. FROM<br>3. JOIN<br>4. WHERE<br>5. GROUP BY<br>6. HAVING<br>7. ORDER BY<br>8. LIMIT</div>
</div>
<div class="compare-box good">
<h4>How SQL Executes It</h4>
<div class="mono" style="color:var(--green)">1. FROM / JOIN<br>2. WHERE<br>3. GROUP BY<br>4. HAVING<br>5. SELECT<br>6. DISTINCT<br>7. ORDER BY<br>8. LIMIT</div>
</div>
</div>
</div>
<div class="card">
<h3>Step-by-Step Execution <span class="tag tag-visual">VISUAL</span></h3>
<div class="exec-order">
<div class="exec-step"><div class="es-num">1</div><div class="es-kw">FROM</div><div class="es-label">Pick tables</div></div>
<div class="exec-arrow">→</div>
<div class="exec-step"><div class="es-num">2</div><div class="es-kw">JOIN</div><div class="es-label">Combine</div></div>
<div class="exec-arrow">→</div>
<div class="exec-step"><div class="es-num">3</div><div class="es-kw">WHERE</div><div class="es-label">Filter rows</div></div>
<div class="exec-arrow">→</div>
<div class="exec-step"><div class="es-num">4</div><div class="es-kw">GROUP BY</div><div class="es-label">Bucket</div></div>
<div class="exec-arrow">→</div>
<div class="exec-step"><div class="es-num">5</div><div class="es-kw">HAVING</div><div class="es-label">Filter groups</div></div>
<div class="exec-arrow">→</div>
<div class="exec-step"><div class="es-num">6</div><div class="es-kw">SELECT</div><div class="es-label">Pick cols</div></div>
<div class="exec-arrow">→</div>
<div class="exec-step"><div class="es-num">7</div><div class="es-kw">ORDER BY</div><div class="es-label">Sort</div></div>
<div class="exec-arrow">→</div>
<div class="exec-step"><div class="es-num">8</div><div class="es-kw">LIMIT</div><div class="es-label">Cap</div></div>
</div>
</div>
<div class="card">
<h3>Why This Matters <span class="tag tag-gotcha">GOTCHAS EXPLAINED</span></h3>
<div class="step-through">
<div class="step-item"><div class="step-num" style="background:var(--red-bg);color:var(--red)">!</div><div class="step-content"><strong>Can't use SELECT alias in WHERE</strong><br><code>SELECT salary * 12 AS annual ... WHERE annual > 700000</code><br>Fails because WHERE runs BEFORE SELECT. Use: <code>WHERE salary * 12 > 700000</code></div></div>
<div class="step-item"><div class="step-num" style="background:var(--red-bg);color:var(--red)">!</div><div class="step-content"><strong>Can't use aggregate in WHERE</strong><br><code>WHERE COUNT(*) > 5</code> fails because WHERE runs before GROUP BY. Use <code>HAVING COUNT(*) > 5</code>.</div></div>
<div class="step-item"><div class="step-num" style="background:var(--green-bg);color:var(--green)">✓</div><div class="step-content"><strong>CAN use SELECT alias in ORDER BY</strong><br><code>SELECT salary * 12 AS annual ... ORDER BY annual DESC</code> works because ORDER BY runs AFTER SELECT.</div></div>
<div class="step-item"><div class="step-num" style="background:var(--green-bg);color:var(--green)">✓</div><div class="step-content"><strong>WHERE filters before grouping = better performance</strong><br>Filter with WHERE (before grouping) when possible, not HAVING (after grouping).</div></div>
</div>
</div>
<div class="card">
<h3>Trace Through Example <span class="tag tag-think">THINK</span></h3>
<pre><code><span class="kw">SELECT</span> d.name, <span class="fn">AVG</span>(e.salary) <span class="kw">AS</span> avg_sal
<span class="kw">FROM</span> <span class="tbl">employees</span> e
<span class="kw">JOIN</span> <span class="tbl">departments</span> d <span class="kw">ON</span> e.dept_id <span class="op">=</span> d.id
<span class="kw">WHERE</span> e.hire_date <span class="op">></span> <span class="str">'2019-01-01'</span>
<span class="kw">GROUP BY</span> d.name
<span class="kw">HAVING</span> <span class="fn">AVG</span>(e.salary) <span class="op">></span> <span class="num">55000</span>
<span class="kw">ORDER BY</span> avg_sal <span class="kw">DESC</span>;</code></pre>
<div class="step-through">
<div class="step-item"><div class="step-num">1</div><div class="step-content"><strong>FROM employees JOIN departments</strong> → combine into 8 matched rows</div></div>
<div class="step-item"><div class="step-num">2</div><div class="step-content"><strong>WHERE hire_date > '2019-01-01'</strong> → filter out Diana (2018). 7 rows remain</div></div>
<div class="step-item"><div class="step-num">3</div><div class="step-content"><strong>GROUP BY d.name</strong> → bucket into Sales(3), Engineering(2), Marketing(2)</div></div>
<div class="step-item"><div class="step-num">4</div><div class="step-content"><strong>HAVING AVG(salary) > 55000</strong> → Marketing avg 52k eliminated. 2 groups remain</div></div>
<div class="step-item"><div class="step-num">5</div><div class="step-content"><strong>SELECT d.name, AVG(salary)</strong> → pick columns for output</div></div>
<div class="step-item"><div class="step-num">6</div><div class="step-content"><strong>ORDER BY avg_sal DESC</strong> → sort by average salary descending</div></div>
</div>
</div>
`,
problems:[
{id:"EO.1",text:"Fix this bug: SELECT salary / 12 AS monthly FROM employees WHERE monthly > 5000;",difficulty:"easy",
hint:"WHERE can't see SELECT aliases. Replace 'monthly' in WHERE with the actual expression.",
answer:"SELECT salary / 12 AS monthly FROM employees WHERE salary / 12 > 5000;",
check:s=>{const n=norm(s);return n.includes("salary")&&n.includes("12")&&n.includes("where")&&!n.includes("where monthly")}},
{id:"EO.2",text:"Fix this bug: SELECT status, SUM(quantity) AS total FROM orders WHERE SUM(quantity) > 100 GROUP BY status;",difficulty:"easy",
hint:"Aggregate functions can't go in WHERE. Move the condition to HAVING.",
answer:"SELECT status, SUM(quantity) AS total FROM orders GROUP BY status HAVING SUM(quantity) > 100;",
check:s=>{const n=norm(s);return n.includes("group by")&&n.includes("having")&&n.includes("sum")&&!n.includes("where sum")}},
{id:"EO.3",text:"Write a query: JOIN employees with departments, filter to hired after 2019, group by dept name, only depts with avg salary > 60000, sorted by avg salary desc.",difficulty:"hard",
hint:"Follow execution order: FROM/JOIN, WHERE (hire_date), GROUP BY, HAVING (avg), SELECT, ORDER BY",
answer:"SELECT d.name, AVG(e.salary) AS avg_sal\\nFROM employees e\\nJOIN departments d ON e.dept_id = d.id\\nWHERE e.hire_date > '2019-01-01'\\nGROUP BY d.name\\nHAVING AVG(e.salary) > 60000\\nORDER BY avg_sal DESC;",
check:s=>{const n=norm(s);return n.includes("join")&&n.includes("departments")&&n.includes("where")&&n.includes("hire_date")&&n.includes("2019")&&n.includes("group by")&&n.includes("having")&&n.includes("avg")&&n.includes("60000")&&n.includes("order by")&&n.includes("desc")}},
]},
// ──────────────── AGGREGATES ────────────────
{
id:"agg",title:"Aggregates",icon:"4",group:"Intermediate",
content:`
<div class="section-header">
<div class="section-num">Section 4</div>
<div class="section-title">Aggregate Functions</div>
<div class="section-desc">COUNT, SUM, AVG, MIN, MAX - functions that crunch multiple rows into one answer.</div>
</div>
<div class="card">
<h3>The Big 5 <span class="tag tag-core">MEMORIZE</span></h3>
<div class="cheat-grid">
<div class="cheat-item"><div class="cheat-op">COUNT(*)</div><div class="cheat-desc">How many rows?</div><div class="cheat-ex">8 employees total</div></div>
<div class="cheat-item"><div class="cheat-op">SUM(col)</div><div class="cheat-desc">Add up all values</div><div class="cheat-ex">SUM(salary) = 536,000</div></div>
<div class="cheat-item"><div class="cheat-op">AVG(col)</div><div class="cheat-desc">Average</div><div class="cheat-ex">AVG(salary) = 67,000</div></div>
<div class="cheat-item"><div class="cheat-op">MIN(col)</div><div class="cheat-desc">Smallest</div><div class="cheat-ex">MIN(salary) = 50,000</div></div>
<div class="cheat-item"><div class="cheat-op">MAX(col)</div><div class="cheat-desc">Largest</div><div class="cheat-ex">MAX(price) = 150.00</div></div>
<div class="cheat-item"><div class="cheat-op">COUNT(col)</div><div class="cheat-desc">Non-NULL count</div><div class="cheat-ex">COUNT(manager_id) = 5</div></div>
</div>
</div>
<div class="card">
<h3>COUNT(*) vs COUNT(column) <span class="tag tag-gotcha">GOTCHA</span></h3>
<div class="compare">
<div class="compare-box"><h4>COUNT(*) = 8</h4><div class="mono">Counts ALL rows, even if some values are NULL</div></div>
<div class="compare-box"><h4>COUNT(manager_id) = 5</h4><div class="mono">Only counts rows where manager_id is NOT NULL<br>(3 employees have NULL manager_id)</div></div>
</div>
</div>
<div class="card">
<h3>Examples <span class="tag tag-syntax">SYNTAX</span></h3>
<pre><code><span class="cm">-- How many orders are completed?</span>
<span class="kw">SELECT</span> <span class="fn">COUNT</span>(*) <span class="kw">AS</span> completed_orders
<span class="kw">FROM</span> <span class="tbl">orders</span>
<span class="kw">WHERE</span> status <span class="op">=</span> <span class="str">'completed'</span>;
<span class="cm">-- Total revenue potential (price * stock for all products)</span>
<span class="kw">SELECT</span> <span class="fn">SUM</span>(price * stock) <span class="kw">AS</span> total_inventory_value
<span class="kw">FROM</span> <span class="tbl">products</span>;
<span class="cm">-- Multiple aggregates at once</span>
<span class="kw">SELECT</span>
<span class="fn">COUNT</span>(*) <span class="kw">AS</span> total_products,
<span class="fn">AVG</span>(price) <span class="kw">AS</span> avg_price,
<span class="fn">MAX</span>(price) <span class="kw">AS</span> most_expensive,
<span class="fn">SUM</span>(stock) <span class="kw">AS</span> total_stock
<span class="kw">FROM</span> <span class="tbl">products</span>;</code></pre>
</div>
`,
problems:[
{id:"4.1",text:"How many orders are there in total?",difficulty:"easy",
hint:"SELECT COUNT(*) FROM orders",
answer:"SELECT COUNT(*) FROM orders;",
check:s=>{const n=norm(s);return n.includes("count")&&n.includes("from")&&n.includes("orders")}},
{id:"4.2",text:"What is the highest salary in the employees table?",difficulty:"easy",
hint:"SELECT MAX(salary) FROM employees",
answer:"SELECT MAX(salary) FROM employees;",
check:s=>{const n=norm(s);return n.includes("max")&&n.includes("salary")&&n.includes("employees")}},
{id:"4.3",text:"What is the total salary paid across all employees?",difficulty:"easy",
hint:"SUM(salary) from employees",
answer:"SELECT SUM(salary) FROM employees;",
check:s=>{const n=norm(s);return n.includes("sum")&&n.includes("salary")&&n.includes("employees")}},
{id:"4.4",text:"What is the average quantity per order?",difficulty:"easy",
hint:"AVG(quantity) from orders",
answer:"SELECT AVG(quantity) FROM orders;",
check:s=>{const n=norm(s);return n.includes("avg")&&n.includes("quantity")&&n.includes("orders")}},
{id:"4.5",text:"How many products are in the 'Gadgets' category?",difficulty:"easy",
hint:"COUNT with WHERE",
answer:"SELECT COUNT(*) FROM products WHERE category = 'Gadgets';",
check:s=>{const n=norm(s);return n.includes("count")&&n.includes("products")&&n.includes("where")&&n.includes("category")&&n.includes("gadgets")}},
{id:"4.6",text:"Show the total number of products, the average stock, and the most expensive price all in one query.",difficulty:"medium",
hint:"SELECT COUNT(*), AVG(stock), MAX(price) FROM products",
answer:"SELECT COUNT(*) AS total_products, AVG(stock) AS avg_stock, MAX(price) AS max_price FROM products;",
check:s=>{const n=norm(s);return n.includes("count")&&n.includes("avg")&&n.includes("stock")&&n.includes("max")&&n.includes("price")&&n.includes("products")}},
{id:"4.7",text:"What is the lowest salary among employees who have a manager (manager_id IS NOT NULL)?",difficulty:"medium",