Oracle SQL Toolkit (Cheatsheet)

Context: FIT2094_MOC, FIT3003_MOC · the ONE pre-lab/pre-exam re-read — every clause, predicate and function the two units teach, in one place · depth lives in the linked pattern notes Read protocol: scan anatomy → scan tables → attempt all three practice items from a blank editor → follow links only where you failed.

Quick Revision

  • 🎯 Objective: assemble any query from the fixed skeleton ➔ SELECT → FROM(+JOIN) → WHERE → GROUP BY → HAVING → ORDER BY.
  • ⚡ Key Constraint: logical execution order ≠ writing order: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY — explains why aliases work in ORDER BY but not GROUP BY/WHERE.
  • ⚠ Unit divergence: FIT2094 bans implicit joins; FIT3003 lectures and labs use them. Everything else is shared.

🗺️ Task Index — “I need to…”

I need to…Reach forSection
copy a lecturer’s table into my accountcreate table t as select * from dtaniar.t (rows only, no PK/FK)DDL
insert several rows in one statementinsert all into … into … select * from dualDML
compare or insert a dateto_date('01-Mar-2021','DD-MON-YYYY')Row Functions
get a year / month out of a dateto_char(d,'YYYY') · extract(year from d)Row Functions · Advanced
show something instead of NULLnvl(col, x) — same type both sidesRow Functions
filter groups, not rowshavingAggregates
keep unmatched rowsleft outer join · old-style a.k = b.k(+)Joins · W5–6
find rows with no partnernot in (subquery) ⛔ a NULL in the subquery returns nothingSubqueries · W3
per-group maximum rowwhere (g, x) in (select g, max(x) … group by g)Subqueries
if / else inside a querycase when … then … end · decode(a, b, c, d)Advanced
build a dimensionselect distinct … — band derived attributes with alter add updateW2
build a factselect keys, agg … group by keys from operational tables or a TempFactW2
count two populationsleft outer join count(col)W2
keep only the latest row per entityrank() over (partition by … order by … desc) W2
find duplicate keysgroup by k having count(*) > 1 on both join sidesW3
weight factor / group list for a bridge1.0/count(*) · listagg(c, '_') within group (order by c)W4
key a dimensioncreate sequence .nextvalW5–6
fill an FK from a dimensioncorrelated update … set fk = (select … where …)W5–6
build a pivoted factAllDimensions product (+) nvl(…, 0)W5–6
merge facts of the same shapeunionW7 · W8
merge sources owning different measureszero-padded union outer sumW8
top-Ninline view rank() … <= N (or rownum after an inner order by)W7 · W9
subtotals and a grand totalgroup by cube / rollup decode(grouping(c), 1, 'All', c)W9
rank with ties handledrank / dense_rank / row_numberW9
running total / moving averagesum(sum(m)) over (… rows unbounded preceding) · avg(sum(m)) over (… rows k-1 preceding)W9
put the grand mean on every rowavg(x) over ()W10
slope and interceptmeans → slope → intercept, three layersW10
auto-fill an ID on insertbefore insert … for each row select seq.nextval into :new.ID from dualW11
keep a fact count livefind-or-insert trigger: select count(*) into IsFound …W11

🧩 Statement Anatomy (execution order)

SELECT   dt_code, AVG(drone_flight_time) AS avg_flight     -- 5. project + alias
FROM     drone.drone                                        -- 1. source (+ JOINs)
WHERE    to_char(drone_pur_date,'yyyy') = '2021'            -- 2. filter ROWS (pre-group)
GROUP BY dt_code                                            -- 3. form groups (no aliases here!)
HAVING   AVG(drone_flight_time) > 50                        -- 4. filter GROUPS (may use aggregates)
ORDER BY dt_code;                                           -- 6. sort (aliases OK, NULLS LAST OK)

🏗 DDL — Build & Inspect the Schema

(➔ DDL Table Creation · Altering and Dropping Tables · Oracle Data Types)

ToolMicro-syntaxJob / gotcha
createCREATE TABLE car (carID NUMBER(5) NOT NULL, …, PRIMARY KEY (carID), FOREIGN KEY (carID) REFERENCES car(carID));FIT3003 declares PK/FK inline; FIT2094 requires named constraints via ALTER
typesVARCHAR2(30) · CHAR(2) · NUMBER(4) · NUMBER(8,2) · DATECHAR is blank-padded fixed width; NUMBER(p,s) = precision, scale
DATE defaultsformat DD-MON-YYYY; range 1/1/4712 BC – 12/31/4712 ADno time given ⟹ 12:00:00 A.M.; no date given ⟹ first day of current month
copy a tableCREATE TABLE car AS SELECT * FROM dtaniar.car;copies rows only — PK/FK are NOT copied ➔ Populating Tables from Queries (INSERT-SELECT, CTAS)
list my tablesSELECT * FROM TAB;returns TNAME / TABTYPE / CLUSTERID — the data-dictionary view of your account
show structureDESCRIBE car; / DESC car;column, nullability, type — use after every ALTER to confirm
add columnALTER TABLE student ADD (Suburb VARCHAR2(40));new column is NULL in all existing rows
retype columnALTER TABLE car MODIFY (transmission CHAR(30));one verb per statement — cannot ADD and DROP in one ALTER
drop columnALTER TABLE car DROP COLUMN transmission; / DROP (CiTTy)data destroyed, DDL auto-commits ⟹ irreversible
drop tableDROP TABLE car;fails while another table holds an FK to it — drop the child (carsales) first

✏️ DML & Transactions

(➔ DML INSERT (Oracle) · DML UPDATE and DELETE (Oracle) · Database Transaction)

ToolMicro-syntaxJob / gotcha
insert all colsINSERT INTO car VALUES (1,'Holden','Cruze',2015,'Black',25780);positional, every column in table order
insert some colsINSERT INTO car (carID, make) VALUES (16,'Audi');column list mandatory for partial inserts; all NOT NULL columns must appear
insert a dateINSERT INTO carsales VALUES (1,4,TO_DATE('04/Feb/2015','DD/MON/YYYY'),25780,824.96);a bare '04-FEB-2015' is a string, not a DATE
insert manyINSERT ALL INTO car VALUES (…) INTO car VALUES (…) SELECT * FROM DUAL;one statement, all-or-nothing; use single inserts when rows depend on each other
updateUPDATE car SET colour='Grey' WHERE carID=5;one table at a time; several columns OK; omitting WHERE updates every row
deleteDELETE FROM car WHERE carID=5;omitting WHERE deletes every row; deleting a referenced PK raises ORA-02292: integrity constraint violated - child record found
commitCOMMIT;until then changes sit in the local buffer only — commit regularly and exit the client properly
DUALSELECT TO_CHAR(SYSDATE,'DD-MON-YYYY HH24:MI') FROM DUAL;Oracle’s one-row dummy table for evaluating an expression with no real source

🔎 Predicates (WHERE) — SQL SELECT and WHERE

PredicateMicro-syntaxGotcha
comparisonprice > 2000, <><> is not-equal
rangeprice BETWEEN 3000 AND 5300inclusive both ends ≡ >=3000 AND <=5300
setemp_no IN (3, 8) · colour NOT IN ('Black','White')negate: “not 3 nor 8” = <>3 AND <>8 — OR is always-true trap
patternname LIKE 'D%' / '_JI%'% any run, _ one char
null testrent_in_dt IS NULL / IS NOT NULL= NULL is always UNKNOWN — never matches
date rangesalesdate BETWEEN TO_DATE('01-JAN-2014','DD-MON-YYYY') AND TO_DATE('31-DEC-2015','DD-MON-YYYY')alternative: compare TO_CHAR(salesdate,'YYYYMMDD') > '20140101' as sortable strings
logicNOT → AND → OR precedence3-valued logic: NULL = UNKNOWN; only TRUE rows returned — bracket everything

🛠 Row Functions & Output Shaping

ToolMicro-syntaxJob / gotcha
NVLNVL(col, 'Still out')replace NULL; types must match ➔ wrap date first: NVL(TO_CHAR(dt,'dd-Mon-yyyy'),'Still out')
TO_CHARTO_CHAR(dt,'dd-Mon-yyyy'), TO_CHAR(n,'$9,999')date/number → display string; also extracts parts: TO_CHAR(dt,'yyyy')
TO_DATETO_DATE('01-Mar-2021','dd-Mon-yyyy')string → date for comparing/inserting — never compare raw strings
format masksDD-MON-YYYY · MM/DD/YYYY · HH:MI AM · MONTH DAY, YYYY · DD-MON-YYYY HH24:MIthe same masks serve TO_DATE (in) and TO_CHAR (out)
concatcust_fname || ' ' || cust_lnameOracle concatenation is ||
case-blindUPPER(manuf_name) = UPPER('DJI...')normalise both sides — string values are case-sensitive, identifiers are not
DISTINCTSELECT DISTINCT make, yearde-dupes at record level, not per attribute — make may still repeat
aliasAS avg_flight · (purchasedPrice+stampDuty) AS TotalPriceusable in ORDER BY only (see execution order); also names computed columns
sortORDER BY t DESC, id · NULLS LAST/FIRSTmandatory whenever >1 row possible — tuples have no order
conditionalCASE/DECODE➔ SQL Conditional Expressions (CASE, DECODE)

📦 Aggregates & Grouping — SQL Aggregate Functions and GROUP BY

  • Five aggregates ➔ COUNT / SUM / AVG / MIN / MAX; COUNT(*) counts rows incl. NULLs, COUNT(col) non-null only, COUNT(DISTINCT col) distinct non-null only.
  • GROUP BY rule ➔ every non-aggregate SELECT column MUST appear in GROUP BY; column aliases are illegal in GROUP BY (not yet computed) — repeat the expression: GROUP BY to_char(ct_date_start,'yyyy').
  • WHERE vs HAVING ➔ WHERE filters rows before grouping; HAVING filters groups after, and may contain aggregates: HAVING COUNT(DISTINCT model) > 1.
  • Aggregate over a join ➔ join condition goes in WHERE/ON and runs first; group on the surviving columns.

🔗 Joins — SQL Joins (ANSI) · SQL Self Join and Outer Join

FormSyntaxWhen
JOIN … ONFROM a JOIN b ON a.id = b.idthe general form — default choice, always works
JOIN … USINGJOIN b USING (manuf_id)identical column names both sides
NATURAL JOINa NATURAL JOIN ball common columns match — ⚠ no common column ⇒ Cartesian product
self joinFROM emp e1 JOIN emp e2 ON e1.mgrno = e2.empnorecursive FK (employee→manager); distinct aliases required
outer joinLEFT / RIGHT / FULL OUTER JOIN … ON …keep unmatched rows (INNER drops them)
implicit / old-styleFROM customer ct, carsales cs, car c WHERE ct.customerID = cs.customerID AND c.carID = cs.carIDbanned in FIT2094 (marked wrong) · ✅ standard in FIT3003 — tables need conditions

Missing join condition = PRODUCT. FIT3003 warns that a comma-list FROM with no matching WHERE condition returns the Cartesian product, exhausts your Oracle quota, and locks your account. Count your ANDs before running.

🪆 Subqueries — SQL Subquery (Nested SELECT) · SQL Subquery Approaches (Nested, Correlated, Inline)

  • Single value ➔ compare with =, <, >: WHERE price < (SELECT AVG(price) FROM …).
  • List of values ➔ IN / NOT IN: WHERE carID NOT IN (SELECT carID FROM carsales) — the anti-join “which cars are unsold” shape.
  • ANY / ALL ➔ > ANY (beats at least one ➔ nearly all rows), > ALL (beats every one ➔ few rows) — classic MCQ discriminator.
  • Multi-column pairs ➔ WHERE (dt_code, price) IN (SELECT dt_code, MAX(price) … GROUP BY dt_code) — per-group max pattern.
  • In DML ➔ subquery sets the target set: UPDATE … SET cost = cost*1.2 WHERE manuf_id = (SELECT …); see Populating Tables from Queries (INSERT-SELECT, CTAS).

➕ Advanced SQL (FIT2094 Topic 10)

ToolMicro-syntaxJob / gotcha
CASECASE WHEN cond THEN 'r' ELSE 'd' END / CASE col WHEN v THEN …if/else in SELECT; searched form allows ranges
DECODEDECODE(emp_type,'F','Full','C','Casual')equality-only legacy of CASE — ➔ SQL Conditional Expressions (CASE, DECODE)
set opsq1 UNION / UNION ALL / INTERSECT / MINUS q2union-compatible (same cols + types); one final ORDER BY; names from q1 — ➔ SQL Set Operators
subquery placementnested (once) · correlated (per-row) · inline view (FROM (SELECT …) alias) · scalar-in-SELECT➔ SQL Subquery Approaches (Nested, Correlated, Inline)
populate tableINSERT INTO t (SELECT …) · CREATE TABLE t AS (SELECT …)CTAS copies data, NOT constraints — re-add PK/FK after
viewCREATE OR REPLACE VIEW v AS SELECT … · DROP VIEW vvirtual table (stored query); banned in FIT2094 Assignment 2 — use a subquery
date part / formatEXTRACT(YEAR FROM d) (number) · TO_CHAR(d,'yyyy') (string)filter/group by a date part
text alignLPAD/RPAD(s,n,'*') · LTRIM/TRIM(s)monospace-only bar charts

🏭 Warehouse ETL Clauses (FIT3003 W2)

(➔ Building Dimension Tables · Building Fact Tables · Fact Measure Aggregation Rules)

ToolMicro-syntaxJob / gotcha
dimension by copycreate table AgentDim as select * from Agent;route 1 of 3; rows only, no PK/FK
dimension by projectioncreate table CourseDim as select CourseCode, CourseName from Course;route 2 — drop columns no analysis question needs
dimension by de-dupcreate table CountryDim as select distinct Country from Student;mandatory distinct when the source is a transaction table
dimension by joinselect distinct C.CountryCode, C.CountryName, T.TestPrice from Test_Venue T, Country C where …route 2 across two operational tables — drags the lookup’s descriptive columns in beside the code; distinct stays mandatory
dimension by handcreate table TimeDim (Quarter number(1), Description varchar2(20)); + insert into … values (1,'Jan-Mar');route 3 — members are business knowledge, membership is fixed and known
manufactured keyCountry || City as LocationID · set QuarterID = Year || Quarterbuilds an ID the operational DB never stored
time keyto_char(DownloadDate,'YYYYMM') as TimeID · 'MM' · 'YYYY' · 'Month''Month' yields the name; masks are the dimension’s attributes
staging tablecreate table TempFact as select … from a, b where …;the joined, unaggregated row set; grain preserved for one later group by
add derived columnalter table TempFact add (Quarter number(1)); then update … set Quarter = 1 where …the chapter’s banding idiom — one update per band, not CASE
null-band catch-allupdate TempFact set Quarter = '4' where Quarter is null;last band by exclusion instead of a range test
fact by aggregationcreate table SalesFact as select Quarter, BranchID, sum(TotalPrice) as Total_Sales from TempFact group by Quarter, BranchID;group by list the fact’s composite PK; build from operational/temp tables, never from the dimensions
two populationsfrom Opening O left outer join Placement P on O.OpenNo = P.OpenNo + count(OpenNo) / count(CandNo)outer join keeps unmatched rows; count(col) skips the NULLs — count(*) would equalise both measures
latest-per-entityrank() over (partition by E.EmpNo order by D.GraduationDate desc) as Rank in an inline view, then where T.Rank = 1pre-processes the operational side; without it the join multiplies rows per entity
never storeavg(x) as a fact measureaverage of averages ≠ average — store Total_x and Number_of_y, recover with sum(Total_x)/sum(Number_of_y)

🧹 Data Exploration & Cleaning Clauses (FIT3003 W3)

(➔ Data Exploration (Warehouse Validation) · Data Cleaning (Dirty Data) · Multi-Role Facts)

ToolMicro-syntaxJob / gotcha
baseline countselect count(*) from dw.uselog;run on every source table before building anything — the prediction all later numbers are judged against
duplicate PK probeselect PK, count(*) from t group by PK having count(*) > 1;the standard dirty-data detector; empty result that key is clean
duplicate row probegroup by log_time, log_date, student_id, act having count(*) > 1when there is no single-column PK, group on the full composite
orphan FK probeselect * from t1 where FK not in (select PK from t2);anti-join; a NULL inside the subquery makes not in return nothing at all
formatted sampleselect log_date, to_char(log_time,'HH24:MI'), student_id from t order by …;order by puts duplicates adjacent so they are visible in a big table
de-dup at the joincreate table tempfact2 as select distinct U.…, S.… from … where …;kills join fan-out and true source duplicates in one clause; only removes rows identical across the projected columns
de-dup a copycreate table student_clean as select distinct * from dw.student;when the defect survives projection; CTAS brings no PK/FK
contradiction checkselect * from contract where starttime > endtime;inconsistent-value type — two attributes disagreeing
domain checkselect * from charter where char_distance < 0;incorrect-value type — outside the legal range
null checkselect * from major where major_name is null;= NULL never matches; only IS NULL
simplest repairdelete from t where <<the same predicate>>;always select first — the count is the evidence the repair worked
merge two role factsselect … sum(x) from ( select * from f1 union select * from f2 ) group by emp_num;union alone leaves two rows per member; the outer group by is what merges — legal only for individually-owned measures
label vs keygroup by F.CourseCode, D1.CourseNamekeep the key in the grouping list; grouping on the description alone merges members that share it

❄️ Snowflake & Bridge Clauses (FIT3003 W4)

(➔ Bridge Tables · Building Bridge Table Schemas · Dimension Hierarchies)

ToolMicro-syntaxJob / gotcha
bridge from associative tablecreate table ProductSupplierBridge as select * from StockSupplier;the source’s m–m table already is the bridge; its composite key is the PK, so no distinct
bridge, history droppedselect distinct SS.ProductNo, S.Name as SupplierName from …collapsing the temporal columns re-introduces duplicates — distinct becomes mandatory
weight factor1.0/count(*) as WeightFactor grouped on the parent’s own columns1/count(*) is integer division → every weight floors to ; store it in the parent dimension, never the bridge
list aggregateLISTAGG(D.StoreID, '_') Within Group (Order By D.StoreID) As StoreGroupListordered-set aggregate; Within Group is mandatory, add Desc inside it to reverse
join through a bridgewhere F.TripId = T.TripId and T.TripId = B.TripId and B.StoreId = S.StoreIdfact → parent dim → bridge → child dim: 4 tables, 3 conditions
join via a list aggregatewhere T.StoreGroupList like '%'||S.StoreID||'%'one join fewer than the bridge path; redundant and cannot use an index
weighted measuresum(Total_delivery_Cost * Weight_Factor) grouped by the bridged dimensionapportions a parent-owned measure to its members — an estimate, not a stored fact
never doputting the far dimension’s key in the fact when the source is m–mthe measure is undividable, so the fact row multiplies once per partner

🔑 Determinant, Pivot & Junk Clauses (FIT3003 W5–W6)

(➔ Determinant Dimensions · Pivoted Fact Tables · Junk Dimensions · Surrogate Key · Slowly Changing Dimensions (SCD))

ToolMicro-syntaxJob / gotcha
sequence surrogatedrop sequence Seq_ID; then create sequence Seq_ID start with 1 increment by 1 maxvalue 99999999 minvalue 1 nocycle;the warehouse way to key a dimension; drop first or a re-run raises ORA-00955
consume the sequenceupdate JunkDim set JunkID = Seq_ID.nextval;one statement keys every row; .nextval fires once per row, .currval never advances
junk dimensioncreate table JunkDim as select distinct Ensuite, Pool, Aspect_Facing, Spa from Property order by …;the Cartesian product of low-cardinality unrelated columns ➔ Junk Dimensions
deliberate Cartesian productcreate table AllDimensions as select … from Dim1, Dim2, Dim3, Dim4;no where on purpose — the only place a product is correct; it is the row grid a pivoted fact must fill
Oracle old-style outer joinwhere A.Year = O.Year(+)(+) marks the side that may be missing; ANSI equivalent is A left outer join O
zero-fill a pivot measurenvl(O.Total_Students_Overall, 0) as Total_Students_Overallthe inner-joined fact has no row for an absent combination; (+) keeps it and nvl makes it
band with CASE in DMLset Band = (case when s >= 30 and s <= 35 then '4.5' … end)W6’s banding idiom — one statement replaces W2’s one-update-per-band; unmatched rows get NULL, not an error
fill an FK from a dimensionupdate TempFact TF set TF.SuburbID = (select S.SuburbID from SuburbDim S where S.Suburb = TF.Suburb);correlated update — the loop-free alternative to hand-typed update … where statements
fill an FK, multi-columnset TF.JunkID = (select J.JunkID from JunkDim J where J.Ensuite = TF.Ensuite and J.Pool = TF.Pool and …)every junk column must appear, or the subquery returns many rows ⟹ ORA-01427
cursor loopdeclare cursor JunkCursor is select * from JunkDim; begin for JunkCursorRec in JunkCursor loop update … end loop; end;PL/SQL route to the same fill; prefer the correlated update unless per-row logic is needed
stack pivot componentscreate table FinalFact as select … from OverallFact union select * from ListeningFact union …;breaks one wide source record into one fact row per component; union-compatible column lists required
join the components backfrom OverallFactNew O, ListeningFactNew L, … where O.Year = L.Year and L.Year = R.Year and …the pivot’s last step: facts need conditions per key column
trim the padded griddelete from F where M1 = 0 and M2 = 0 and …;the pivot’s final step — deletes only rows that are in every measure (); a row zero in one component is the zero the pivot exists to record
one dimension, many rolesfrom PrivateTaxiFact2 F, CarDim D1, CarDim D2, …, CarDim D5 where F.CarNo1 = D1.CarNo and …a shifted dimension re-entering as aliases — the query cost the pivot buys ➔ Pivoted Fact Tables
open-ended SCD rangeEndDate = 'Dec9999' as the current-version sentinelType 2/4 history; find “now” with a range test or CurrentFlag, never max(EndDate)
SCD Type 4 report — price at sale timewhere F.BookID = P.BookID and to_date(F.TimeID,'MonYYYY') >= to_date(P.StartDate,'MonYYYY') and to_date(F.TimeID,'MonYYYY') <= to_date(P.EndDate,'MonYYYY') listagg(P.Price, ';') within group (order by P.Price)range join, not equality · a month overlapping two price periods returns both — listagg keeps one row ($23.00;$45.95) · ⛔ compare MonYYYY only through to_date ➔ Slowly Changing Dimensions (SCD)

✂️ Multi-Fact, Combine & Slice Clauses (FIT3003 W7)

(➔ Multi-Fact Star Schemas · Combining Star Schemas · Slicing a Fact)

ToolMicro-syntaxJob / gotcha
merge facts, same measurescreate table F1 as select * from F1a union select * from F1b;union-compatible facts differing only by time period; identical column lists required
merge facts, same dimensionsfrom F1, F2, F3 where F1.CarNo = F2.CarNo and F1.CarNo = F3.CarNo and …different measures, one subject ➔ facts need conditions per key column
collapse m rows to 1 before a fact joinround(avg(Stars)) as Avg_Stars grouped on the entity keya book with reviews would otherwise multiply the sales rows
keep the unmatched entitywhere B.ISBN = R.ISBN(+) nvl(R.Stars, 0)inner join silently deletes books with no review; ANSI form is left outer join … on
pivot from one dimensionselect CategoryID, 0 as M1, 0 as M2 … from CategoryDim then update F2 set M1 = nvl((select … from F1 where …), 0)the lightweight pivot — grid the surviving dimension, so no AllDimensions product and no all-zero trim
vertical slicecreate table F1 as select CarNo, DriverNo, WeekNo, Total_Kilometers from F;splits measures; each slice must repeat the whole composite key
horizontal slice by rangecreate table F2a as select * from F1 where TimeID < 201001;splits records; column list unchanged, and each star’s TimeDim may be trimmed to its own range
horizontal slice by member… where BookshopID in (select … from <operational> where <type>), key renamed per starthe slicing attribute lives outside the warehouse ➔ reach back to the source; rename the key per star
top-Nselect * from (select …, sum(x) as total … order by total desc) where rownum = 1;Oracle assigns rownum before order by ➔ the sort must live in an inline view or you get an arbitrary row
query a multi-fact starfrom ReviewFact F, CategoryDim C where F.CategoryID = C.CategoryIDpick the fact that owns the measure; the two facts never join to each other, only to the shared dimensions
filter via a shared dimensionand S.StarID = F.StarID and S.StarID = 5join the dimension even when filtering on its key — its description columns are what the report displays
split a dimension to matchcreate table ReligiousBookshopDim as select BookshopID as ReligiousBookshopID, Address, … from BookshopDim where BookshopID in (…);keeps each sliced star self-contained; the same subquery selects the members

🧬 Multi-Input & Granularity Clauses (FIT3003 W8)

(➔ Multi-Input Operational Databases · Levels of Aggregation · Identifying a Level-0 Star Schema)

ToolMicro-syntaxJob / gotcha
address another source DBfrom "OrchestraClub.Concert" CT, "OrchestraClub.Ticket" Tquoted qualified identifier keeps the case and the dot; the same table name in two sources never collides
dimension spanning sourcesselect distinct * from (A union B union C)the outer select distinct is what dedupes across sources; the inner ones only dedupe within each
derive a dimension attribute after the factalter … add (StartDate date) then set StartDate = to_date('01-Mar-'||Year, 'DD-MON-YYYY')|| concatenates the literal to the Year column; a month name needs mask MON — the slide’s 'DD-MM-YYYY' would reject Mar
stamp the source identityalter table ClubTempFact add (ClubID integer); then update ClubTempFact set ClubID = 1;without it the vertical stack cannot tell whose row is whose; do it before the final group by ClubID, Year
vertical stack (rows down)create table F as select * from F1 union select * from F2 union …;legal only when every per-source fact has the same dimensions and same measures
horizontal stack (columns across)per TempFact count(*) as M1, 0 as M2, … → select keys, sum(M1), sum(M2) from (… union …) group by keysthe 0 padding buys union-compatibility; the outer group by sum is what merges four partial rows into one complete one
count two populations per eventcount(*) … group by EventID once per population, then join both into the TempFactseparate counts first, combine second — one query cannot count two different child tables correctly
filter a coded outcome before countingand A.AuctionResultCode = 'SA' inside the TotalSuccessfulAuction TempFactthe success rate is derived at query time; the fact stores numerator and denominator, never the ratio
re-grain a TempFactselect ConcertID, …, HostingCost, sum(Price) as Income from T1 group by ConcertID, …, HostingCostwhen a per-child join repeated the parent’s cost columns, carry them in the group by so they are counted once
drop a degenerate measureLevel-0 count fact ➔ omit the measure entirely, keep only the FK blocklegal only for a count; a sum measure keeps its per-transaction amount at Level-0

📈 OLAP Clauses (FIT3003 W9)

(➔ OLAP (On-Line Analytical Processing) · OLAP Cube and Rollup · OLAP Ranking and Top-N · OLAP Cumulative and Moving Aggregates)

ToolMicro-syntaxJob / gotcha
distinct countselect count(distinct Year) from TimeDim;unique non-null values; count(Month) counts every row with a month
cubegroup by cube (ProductName, Location)all grouping sets ➔ detail every subtotal grand total (cross-tab); data ➔ rows
rollupgroup by rollup (ProductName, Location) sets, columns dropped right-to-left ➔ order matters; data ➔ rows
partial cube / rollupgroup by ProductName, cube (Location, TimeID) · …, rollup (…)the column outside the parentheses is never rolled up ➔ no grand total
subtotal markergrouping(ProductName) when the column is rolled up in that row (its (null) means “all”), else
label subtotalsdecode(grouping(ProductName), 1, 'All Products', ProductName) as Product_Namedecode(a, b, c, d) if then else ; alias must differ from the raw column you order by
constant column outsidegroup by cube (time_id, c.emp_num), mod_codeLab 9a: a filter-fixed column inside the cube doubles every row () with an identical “All Models” twin; outside it,
rankrank() over (order by sum(Total_Sales) desc) as Sales_Rankties share, then gap (); omit desc and the smallest is
rank raw rowsselect time_year, time_month, rank() over (order by time_year, time_month) as time_rank from dw.time;no group by needed; extra order by keys break ties left to right
dense rankdense_rank() over (order by sum(Total_Sales) desc)ties share, no gap ()
row numberrow_number() over (order by sum(Total_Sales) desc)unique ; ties broken arbitrarily — behaves like rownum
percent rankpercent_rank() over (order by sum(Total_Sales)); top 10% inline view outer >= 0.9 (Lab 9a: of months)
rank per grouprank() over (partition by ProductName order by sum(Total_Sales) desc)numbering restarts in each partition; several over clauses may partition differently in one query
top-N with tiesselect * from (select …, rank() over (…) as Product_Rank … group by …) where Product_Rank <= 2;window alias is invisible in its own where ➔ the inline view is mandatory; keeps tied rows, unlike rownum
cumulativesum(sum(Total_Sales)) over (order by S.TimeID rows unbounded preceding)inner sum the group by total, outer sum runs from the first result row
cumulative per groupsum(sum(Total_Sales)) over (partition by LocationID order by LocationID, S.TimeID rows unbounded preceding)restarts at each location
two running totalsover (partition by year order by model …) · over (partition by model order by year …)each window keeps its own partition; order by the column the total runs along, never the partition key itself
moving averageavg(sum(Total_Sales)) over (order by S.TimeID rows 2 preceding) as Avg_3_Months periods rows k-1 preceding; counts result rows, not months; first rows average fewer
display formatto_char(sum(Total_Sales), '999,999,999')comma-formatted string — display only

📉 Analytics Clauses (FIT3003 W10)

(➔ Linear Regression in SQL · Data Analytics for Data Warehousing)

ToolMicro-syntaxJob / gotcha
means to every rowfrom (select avg(x) as x_bar, avg(y) as y_bar from dataset) av, datasetone-row inline view cross-joined with no where ➔ beside each row; deliberate Cartesian
means by windowselect x, avg(x) over () as x_bar, y, avg(y) over () as y_bar from datasetempty over () whole result as one window ➔ grand mean on every row; the Lab 10 form, no cross join
slopesum((x - x_bar) * (y - y_bar)) / sum((x - x_bar) * (x - x_bar)) as slope, max(x_bar) as x_bar_max; carry x_bar up as max(x_bar) — bare it raises ORA-00937
interceptselect slope, y_bar_max - (slope * x_bar_max) as intercept from (…) in an outer layer — slope is invisible beside its own definition
prediction tablecreate table linear_regression as select x, y, (intercept + (slope * x)) as y_pred from dataset, (…) order by x;the slope/intercept query as a one-row view, cross-joined to every row
period indexrow_number() over (order by S.TimeID) as xTimeID codes are not evenly spaced numbers ( jumps ) ➔ re-index before regressing
decimal columnAge number(4,1) — not numeric(3,0)scale rounds on insert with no error ➔ shifts the fit (Lab 10: )

⚡ PL/SQL & Trigger Clauses (FIT3003 W11)

(➔ PL-SQL Stored Procedures and Functions · Database Triggers (Oracle) · Active Data Warehousing · Active Data Warehouse Evolution)

ToolMicro-syntaxJob / gotcha
blockdeclare v_x number; begin … exception when NO_DATA_FOUND then … end;:= assigns, = compares; a procedure/function puts locals after as/is with no declare
anchored typeDegree Student.DegreeCode%typefollows the column if it is retyped
single-row fetchselect DegreeCode into Degree from Student where StudentNo = :new.StudentNo;exactly one row, or NO_DATA_FOUND
existence testselect count(*) into IsFound from SemesterYearDim where SemYearID = v_ID;never raises — the find-or-insert guard in every active-DW trigger
branchif … then … elsif … then … else … end if;elsif, one word
loopsloop … if c then exit; end if; end loop; · while c loop … end loop;the plain loop exits only where you say
cursor loopcursor c is select …; then for r in c loop … r.col … end loop;one pass per active-set row; r is undeclared
debug printset serveroutput on; then dbms_output.put_line('…');prints nothing without the SQL*Plus setting
procedurecreate or replace procedure P (p1 in number, p2 out number) as … begin … end P;run execute P(…); · remove drop procedure P;
functioncreate or replace function F (…) return varchar2 is … begin … return v; end F;called inside an expression: select department, ClassInfo(department, course) from classes;
dictionaryuser_objects where object_type = 'PROCEDURE' · user_source · user_errors · user_triggersnames are stored UPPERCASE — where name = 'ADDNEWSTUDENT'
row triggercreate or replace trigger T after insert on LabActivities for each row declare … begin … end T;once per affected row; the only level that sees :new / :old
statement triggerthe same header without for each rowonce per statement, even when rows change
context variables:new.col (insert, update) · :old.col (update, delete)colon only on these two, never on a local
event predicatesif inserting … · if updating and :old.deptno != :new.deptno … · if deleting …branch one multi-event trigger
column-scoped eventafter delete or insert or update of deptno, sal on empfires on update only when a listed column is set
auto-IDbefore insert on students for each row begin select student_sequence.nextval into :new.ID from dual; end;writing :new needs before; never add or update
vetoraise_application_error(-20000, 'Cannot delete employee...');inside a before trigger, cancels the triggering operation
view DMLinstead of delete on room_summary for each row begin delete from rooms where building = :old.building; end;makes an aggregate view deletable
self-maintaining DW objectcreate or replace view StudentDim as select …dimension, measure-less fact, upper level that only re-sums — no trigger needed
fact incrementif IsFound <> 0 then update F set Num = Num + 1 where <keys = :new.keys>; else insert into F values (…, 1); end if;update-only loses the first row of every new key combination
fact decrementafter delete on TempFact for each row … set Num = Num - 1 where <keys = :old.keys>expiry: delete from the TempFact, which still holds the date
expiry filterand to_char(LoginDate,'YYYY') >= '2015' in the Level-0 build or viewdecides which source rows may enter the star
derive-then-insertif to_char(:new.LoginDate,'YYYY') >= '2021' then … '06/30' … else … '07/15' … end if;a rule change edits only the TempFact trigger
commit in a triggerslides end every trigger body with commit;🔭 live Oracle raises ORA-04092 — the firing statement’s transaction owns the commit

✍️ Integration Practice

⚠️ Common Mistakes (only traps no table row already states)

  • 💡 Word’s curly quotes ➔ SQL pasted from a .docx lab sheet carries ' instead of '; Oracle rejects it — retype the quote.
  • 💡 select … as X while group by still names the old column ➔ ORA-00979; change the projection and the grouping list together.
  • 💡 Pivoting a fact without AllDimensions ➔ the source fact was built with an inner join, so absent combinations have no row at all; outer-joining the component facts to each other cannot invent them ➔ Pivoted Fact Tables.
  • 💡 Summing a determinant fact without pinning the determinant attribute ➔ where Year = '2017' over a 5-component fact returns for candidates — no error, one tidy row, silently wrong; pin it in the where or split it out in the group by ➔ Determinant Dimensions.
  • 💡 Unqualified shared key in an OLAP query ➔ LocationID / TimeID exist in the fact and its dimension; unqualified they raise ORA-00918 (two Chapter 19 slides do this) ➔ always S.LocationID, S.TimeID.
  • 💡 Row counts on sparse data ➔ a cube/rollup grouping set yields one row per combination that occurs, not the product of distinct counts — Lab 9a’s months pilots gives rows, not ➔ OLAP Cube and Rollup.
  • 💡 Two spellings of one manufactured key ➔ a dimension built on Year || Semester and a trigger looking up Semester || Year never match, so every member is re-inserted under a key no fact row uses (Ch18 slide 68) ➔ Active Data Warehouse Evolution.