Big Data

Hive architecture and HiveQL

This lecture puts a SQL layer over Hadoop with Hive. It covers architecture, data types, file formats, and HiveQL for defining, loading, and querying tables. After this page you can trace a query through the Driver and Metastore. You can also write DDL, DML, GROUP BY and JOIN queries, and compute sample query outputs by hand.

Why Hive existsSearch on YouTube

Hadoop and HDFS could store huge datasets. The catch: the way to ask a question of that data was a hand-written MapReduce job in Java. A task as small as "count rows by category" needed a full Mapper class, a Reducer class, a Driver, and job config code.

That put analysis out of reach for analysts who knew SQL but not distributed Java. Companies like Facebook needed a bridge. Hive is that bridge: SQL-literate people query petabyte-scale data in HDFS without writing MapReduce first.

The same question, two ways ("total sales by region"):

SELECT region, SUM(amount) AS total
FROM sales.orders
GROUP BY region;

Three lines of HiveQL replace 100+ lines of Java. Anyone who knows SQL can read and change it at once.

Where Hive came fromSearch on YouTube

Facebook built Hive out of need. Its data grew fast. It rose from tens of gigabytes a day in 2006 to about 1 terabyte a day in 2007. Within a few more years it hit roughly 15 terabytes a day. The data sat in Hadoop. The analysts knew SQL, not Java.

YearEvent
2007Development begins inside Facebook, led by Joydeep Sen Sarma and Ashish Thusoo
2008Facebook releases Hive to open source
2010Hive becomes a top-level Apache project ("Apache Hive")
TodayStandard SQL layer for Hadoop warehouses; releases are on the 4.x line

Hive architectureSearch on YouTube

A query passes through fixed layers. Each layer has one job.

Hive Client (top layer). This is how a user or program connects:

  • Thrift Server. Hive is built on Apache Thrift, a cross-language RPC framework. Clients in Python, PHP, or C++ connect, not only Java clients.
  • JDBC Driver. Java applications connect through the standard java.sql interface, like any relational database.
  • ODBC Driver. BI and reporting tools (Tableau, Power BI) connect through the ODBC standard.

Hive Services. These entry points receive the query:

  • Hive Web UI. A browser interface for queries without a terminal.
  • CLI. The command-line shell. It is the Hive equivalent of the Pig Grunt shell.
  • HiveServer2. Listens for remote JDBC, ODBC, and Thrift connections. It serves many users at once. This makes Hive a shared warehouse, not a single-user tool.

Hive Driver. Every query ends up here, whatever client sent it. The Driver manages the full query lifecycle. It receives the HiveQL statement. It parses, compiles, and optimizes it into an execution plan. It talks to the Metastore first, because it must know table schemas, column types, and partition locations before it compiles.

Metastore. The catalog, not the data. It stores table names, column names and types, partition info, and the HDFS paths of each table's files.

Execution engine and HDFS. The compiled plan runs here. In the classic design this box is MapReduce; in a modern deployment it is often Tez or Spark. The role stays the same: the engine that crunches the data. HDFS holds the real files. The engine reads from HDFS and writes back to it.

Client: CLI / Web UI / JDBC Driver: parse, compile, plan Engine: MapReduce / Tez / Spark HDFS data results back Metastore
Query path: client to Driver, Driver checks the Metastore, engine reads HDFS, results return.

The slide lists the run as numbered steps:

  1. Send the query from the CLI, Web UI, or a remote client to the driver.
  2. Get the plan: the driver opens a session handle and passes the query to the compiler.
  3. Get metadata: the compiler asks the Metastore for table and column details.
  4. Send metadata: the Metastore answers the compiler.
  5. Send plan: the compiler returns the execution plan to the driver.
  6. Execute plan: the driver sends the plan to the execution engine, which runs the job and touches HDFS files.
  7. Fetch results: the driver fetches results from the execution engine.
  8. Send results: the driver sends results to the user interface.

MetastoreSearch on YouTube

The Metastore is a central repository. It stores structure info for every table and Hive partition in the warehouse. That means column names and types used to read and write data, plus the HDFS files where the data lives. It never stores the data itself.

One shared catalog serves many tools. Other engines can read the same table definitions.

Key characteristicsSearch on YouTube

#TraitMeaning
1SQL-like interfaceHiveQL looks like SQL, so SQL users start fast
2Built on Hadoop / HDFSInherits distributed storage and fault tolerance
3Schema-on-readStructure applies at query time, not load time, so ingestion stays flexible
4Extensible with UDFsJava, Python, and other languages cover what SQL lacks
5Pluggable enginesCompiles to MapReduce, Tez, or Spark; Tez is recommended in Hive 4.x
6Centralized MetastoreOne shared catalog of tables, columns, and partitions
7Batch analytics at scaleBuilt for large scans and aggregations, not row-level OLTP
8Open source Apache projectFree, widely adopted, large ecosystem

Schema-on-read has one sharp edge: LOAD DATA never validates column types. Bad rows surface at query time.

Primitive data typesSearch on YouTube

GroupTypeSizeExample
NumericTINYINT1-byte signed integer5
NumericSMALLINT2-byte signed integer320
NumericINT4-byte signed integer100000
NumericBIGINT8-byte signed integer100000000000
NumericFLOAT / DOUBLEsingle / double float3.14
NumericDECIMAL(p,s)fixed precision and scale1234.56
Date/TimeDATEcalendar date'2026-08-18'
Date/TimeTIMESTAMPdate plus time'2026-08-18 14:30:00'
Date/TimeINTERVALtime spanINTERVAL '3' DAY
String/MiscSTRINGunbounded text'hive'
String/MiscVARCHAR(n)variable lengthVARCHAR(20)
String/MiscCHAR(n)fixed lengthCHAR(10)
String/MiscBOOLEANtrue / falseTRUE
String/MiscBINARYraw bytes0x48 69

Complex data typesSearch on YouTube

One column can hold nested data. This fits JSON and log sources that are not flat.

TypeIdeaExample
ARRAY<STRING>Ordered values of one type, indexed from zero['red','blue']
MAP<STRING,INT>Unordered key-value pairs; keys are primitive{'a':1,'b':2}
STRUCT<name:STRING,age:INT>Named fields, each with its own type, like a row in a cell{'name':'Anu','age':21}
UNIONTYPE<INT,STRING>One of several types at a time; rarely used{0:42} or {1:'x'}

Access syntax mirrors Pig: tags[0] for an ARRAY, attributes['color'] for a MAP, dimensions.length for a STRUCT with dot notation.

Worked example. One row holds every type. The slide creates this table and inserts one full row.

CREATE TABLE sales.product_catalog (
  product_id INT, sku SMALLINT, total_sold BIGINT,
  unit_price FLOAT, revenue DOUBLE, margin_pct DECIMAL(5,2),
  launch_date DATE, last_updated TIMESTAMP,
  product_name STRING, category_code VARCHAR(10),
  warehouse_zone CHAR(3), is_active BOOLEAN,
  tags ARRAY<STRING>,
  attributes MAP<STRING,STRING>,
  dimensions STRUCT<length:FLOAT,width:FLOAT,height:FLOAT>)
ROW FORMAT DELIMITED FIELDS TERMINATED BY ','
COLLECTION ITEMS TERMINATED BY '|'
MAP KEYS TERMINATED BY ':'
STORED AS TEXTFILE;
INSERT INTO sales.product_catalog VALUES (
  501, 12, 3400, 799.99, 2719966.00, 18.50,
  '2025-01-15', '2026-08-20 10:15:00',
  'Wireless Mouse', 'ELEC', 'A12', true,
  array('electronics','accessory'),
  map('color','black','brand','TechCo'),
  named_struct('length',10.5,'width',6.2,'height',3.1));

Query into the nested fields.

SELECT product_name,
  tags[0] AS first_tag,
  attributes['color'] AS color,
  dimensions.length AS length_cm
FROM sales.product_catalog;

Output (one row):

Wireless Mouse  electronics  black  10.5

tags[0] picks the first array item (zero-indexed). attributes['color'] looks up the map key. dimensions.length reaches into the struct.

File formatsSearch on YouTube

STORED AS in CREATE TABLE controls the physical layout. The choice changes speed, size, and features such as ACID.

FormatLayoutNote
TEXTFILEPlain delimited text, one record per lineDefault; readable, no compression, slowest scans
SEQUENCEFILERow-basedHadoop native binary key-value; splittable, compressible, not columnar
RCFILEColumnar (legacy)Early Hive columnar format, superseded by ORC
ORCColumnar, recommendedHive native; per-column stats, strong compression, base for ACID tables
PARQUETColumnar, interoperableShared with Spark, Trino, Presto, Iceberg; best cross-engine pick
AVRORow-based, schema evolutionJSON schema travels with the data; evolves cleanly
format choice=f(readers,updates,schema change)\text{format choice} = f(\text{readers}, \text{updates}, \text{schema change})

Hive-only analytics with repeated filtering: STORED AS ORC. Tables needing UPDATE / DELETE: ORC plus ('transactional'='true'), required for ACID. Tables also read by Spark, Trino, or Presto: STORED AS PARQUET. Schemas that change over time, or streaming ingestion: STORED AS AVRO. Raw landing or one-time staging data: TEXTFILE is fine, then convert to ORC or Parquet before serious querying.

CREATE TABLE orders (order_id INT, customer_id INT, amount DOUBLE)
STORED AS ORC
TBLPROPERTIES ('transactional'='true');

Databases in HiveSearch on YouTube

DATABASE and SCHEMA are interchangeable. CREATE SCHEMA and CREATE DATABASE do the same thing.

CREATE DATABASE  →  USE  →  CREATE TABLE  →  DESCRIBE / ALTER  →  DROP (CASCADE)\texttt{CREATE DATABASE} \;\to\; \texttt{USE} \;\to\; \texttt{CREATE TABLE} \;\to\; \texttt{DESCRIBE / ALTER} \;\to\; \texttt{DROP (CASCADE)}
CREATE DATABASE IF NOT EXISTS sales
  COMMENT 'Sales warehouse'
  WITH DBPROPERTIES ('owner'='analytics-team');
SHOW DATABASES;
USE sales;
DESCRIBE DATABASE EXTENDED sales;
ALTER DATABASE sales SET DBPROPERTIES
  ('reviewed'='2026-08');
DROP DATABASE IF EXISTS sales CASCADE;

Three rules from the slides:

  • DROP DATABASE fails on a non-empty database unless CASCADE is added. CASCADE drops every table inside too.
  • A database LOCATION change affects new tables created after the change. It never moves existing data.
  • Every table belongs to exactly one database. default is a database named default.

Worked example. Build the sales database the way a project starts.

CREATE DATABASE IF NOT EXISTS sales
  COMMENT 'Sales analytics warehouse'
  WITH DBPROPERTIES ('owner'='cse412-batch2026');
SHOW DATABASES;
default
sales
USE sales;
DESCRIBE DATABASE EXTENDED sales;
sales  hdfs://localhost:9000/user/hive/warehouse/sales.db
cse412-student  USER  {owner=cse412-batch2026}

The path is the warehouse directory for the database. The properties map holds the owner label.

ALTER DATABASE sales SET DBPROPERTIES ('reviewed'='2026-08-24');

Run DESCRIBE DATABASE EXTENDED sales; again. The map now holds both owner and reviewed.

HiveQL categoriesSearch on YouTube

HiveQL looks like SQL. Hive is a warehouse over files, not a transactional database. That shapes each category:

CategoryStatementsExample
DDL (definition)CREATE, ALTER, DROP, TRUNCATECREATE TABLE orders (...);
DML (manipulation)LOAD, INSERT, UPDATE, DELETEINSERT INTO TABLE orders ...;
DQL (queries)SELECT, JOIN, GROUP BY, sortingSELECT * FROM orders;
Viewsplain and materialized viewsCREATE VIEW big_orders AS ...;
What replaced indexesstatistics and design toolsANALYZE TABLE ... COMPUTE STATISTICS;
Designpartitioning, bucketing, format choicePARTITIONED BY (order_date STRING);

Creating and changing tablesSearch on YouTube

CREATE TABLE IF NOT EXISTS sales.orders (
    order_id INT, customer_id INT, amount DOUBLE, order_date DATE
)
ROW FORMAT DELIMITED FIELDS TERMINATED BY ','
STORED AS ORC;
ALTER TABLE sales.orders ADD COLUMNS (region STRING);
ALTER TABLE sales.orders RENAME TO sales.customer_orders;
TRUNCATE TABLE sales.customer_orders;

Managed (also called internal) table. Hive owns the data lifecycle. DROP TABLE deletes both the schema and the files.

External table. Created with EXTERNAL and a LOCATION. DROP TABLE removes the metadata. The data stays on disk.

TRUNCATE TABLE. Removes all rows, keeps the definition. Works on managed tables.

managed table schema + files DROP deletes both external table schema only files stay in LOCATION gone gone schema gone files stay
DROP on a managed table deletes schema and files. DROP on an external table deletes the schema; the files stay.

Worked example. Load the sample orders into a managed table. Then show the external-table difference.

Sample files used through the rest of this page:

customers.csv (customer_id, name, since):

1,Rajiv,2021
2,Omer,2022
3,Rajesh,2020
4,Divya,2023

orders.csv (order_id, customer_id, amount, order_date):

101,1,450.00,2026-08-10
102,2,0.00,2026-08-10
103,1,220.50,2026-08-11
104,3,180.00,2026-08-11
105,4,999.99,2026-08-12

Order 102 is cancelled (amount = 0). It stays in the file so query examples have something to filter.

CREATE TABLE IF NOT EXISTS sales.orders (
  order_id INT, customer_id INT, amount DOUBLE, order_date STRING)
ROW FORMAT DELIMITED FIELDS TERMINATED BY ','
STORED AS TEXTFILE;
LOAD DATA LOCAL INPATH '/home/student/orders.csv'
  INTO TABLE sales.orders;
Loading data to table sales.orders
Table sales.orders stats: [numFiles=1, numRows=0, totalSize=157]
SELECT * FROM sales.orders;
101  1  450.0   2026-08-10
102  2  0.0     2026-08-10
103  1  220.5   2026-08-11
104  3  180.0   2026-08-11
105  4  999.99  2026-08-12

Add a column the file lacks. Existing rows return NULL for it.

ALTER TABLE sales.orders ADD COLUMNS (region STRING);
SELECT order_id, region FROM sales.orders LIMIT 2;
101  NULL
102  NULL

External table over a file Hive must not own.

CREATE EXTERNAL TABLE sales.orders_ext (
  order_id INT, customer_id INT, amount DOUBLE, order_date STRING)
ROW FORMAT DELIMITED FIELDS TERMINATED BY ','
LOCATION '/user/student/external/orders';
DROP TABLE sales.orders_ext;

The metadata is gone. The file at /user/student/external/orders still sits on HDFS untouched. Had sales.orders (managed) been dropped instead, both its definition and its loaded file would vanish.

Partitioning and bucketingSearch on YouTube

PARTITIONED BY splits a table into separate directories by column value. Queries that filter on the Hive partition column skip whole directories. Use columns with low-to-moderate cardinality that queries filter on often (date, region, country). Too many tiny partitions strain the Metastore.

CLUSTERED BY hashes rows into a fixed number of files by column value, within a Hive partition or across the table. Use it for high-cardinality join or sampling keys. It enables bucket map joins and TABLESAMPLE.

This lecture gives the short form. Partitioning, bucketing and joins gives the full treatment with the bucket-number rule and the join types.

Worked example. Partition the orders by day, then read one day.

CREATE TABLE sales.orders_partitioned (
  order_id INT, customer_id INT, amount DOUBLE)
PARTITIONED BY (order_date STRING)
STORED AS ORC;
INSERT OVERWRITE TABLE sales.orders_partitioned
  PARTITION (order_date='2026-08-10')
  SELECT order_id, customer_id, amount FROM sales.orders
  WHERE order_date='2026-08-10';

Repeat for '2026-08-11' and '2026-08-12' the same way.

SHOW PARTITIONS sales.orders_partitioned;
order_date=2026-08-10
order_date=2026-08-11
order_date=2026-08-12
SELECT * FROM sales.orders_partitioned
WHERE order_date = '2026-08-11';
103  1  220.5  2026-08-11
104  3  180.0  2026-08-11

This query reads the order_date=2026-08-11 directory. The 2026-08-10 and 2026-08-12 directories are never touched. That skipping is partition pruning.

Bucket the same data by customer_id into 4 files.

CREATE TABLE sales.orders_bucketed (
  order_id INT, customer_id INT, amount DOUBLE, order_date STRING)
CLUSTERED BY (customer_id) INTO 4 BUCKETS
STORED AS ORC;
INSERT OVERWRITE TABLE sales.orders_bucketed
  SELECT order_id, customer_id, amount, order_date FROM sales.orders;

For INT keys the hash is the value itself, so bucket =customer_id mod 4= \text{customer\_id} \bmod 4:

1 mod 4=1(101,103 land here)2 mod 4=2(102 lands here)3 mod 4=3(104 lands here)4 mod 4=0(105 lands here)\begin{aligned} 1 &\bmod 4 = \mathbf{1} \quad (101, 103 \text{ land here})\\ 2 &\bmod 4 = \mathbf{2} \quad (102 \text{ lands here})\\ 3 &\bmod 4 = \mathbf{3} \quad (104 \text{ lands here})\\ 4 &\bmod 4 = \mathbf{0} \quad (105 \text{ lands here}) \end{aligned}

Rows with the same customer_id always land in the same file. That shared placement is what makes bucket joins fast.

Loading and inserting dataSearch on YouTube

LOAD DATA moves or copies files as-is. No MapReduce or Tez job runs. INSERT ... SELECT always runs a query, so it can filter, join, or transform on the way in.

LOAD DATA LOCAL INPATH '/home/user/orders.csv'
  INTO TABLE sales.orders;
INSERT INTO TABLE sales.orders
  SELECT * FROM sales.orders_staging WHERE amount > 0;
INSERT OVERWRITE TABLE sales.orders PARTITION (order_date='2026-08-18')
  SELECT order_id, customer_id, amount FROM sales.orders_staging;
EXPORT TABLE sales.orders TO 'hdfs:///backup/orders';
IMPORT TABLE sales.orders FROM 'hdfs:///backup/orders';

INSERT INTO appends rows. INSERT OVERWRITE replaces a table (or Hive partition) fully. EXPORT / IMPORT move a table, schema plus data, between clusters.

LOCAL means the path is on the client machine (/home/user/orders.csv). Without LOCAL, the path is already on HDFS. LOAD DATA without OVERWRITE appends; with OVERWRITE it replaces.

ACID tables with UPDATE and DELETESearch on YouTube

UPDATE and DELETE work on ACID (transactional) tables. An ordinary table does not support them; the statement fails. The ACID recipe: STORED AS ORC plus TBLPROPERTIES ('transactional'='true'), with CLUSTERED BY into buckets on older Hive versions.

CREATE TABLE sales.accounts (id INT, balance DOUBLE)
CLUSTERED BY (id) INTO 8 BUCKETS
STORED AS ORC
TBLPROPERTIES ('transactional'='true');
UPDATE sales.accounts SET balance = balance - 100 WHERE id = 42;
DELETE FROM sales.accounts WHERE balance <= 0;

Worked example. Insert an order, then move money on an ACID table.

INSERT INTO TABLE sales.orders (order_id, customer_id, amount, order_date)
  VALUES (106, 2, 375.25, '2026-08-13');
SELECT * FROM sales.orders WHERE order_id = 106;
106  2  375.25  2026-08-13  NULL

The trailing NULL is the region column added earlier. Watch out: the slide omits the column list. The list is required here because the table now has five columns and the insert supplies four.

CREATE TABLE sales.accounts (
  customer_id INT, balance DOUBLE)
CLUSTERED BY (customer_id) INTO 4 BUCKETS
STORED AS ORC
TBLPROPERTIES ('transactional'='true');
INSERT INTO sales.accounts VALUES
  (1,1000.00), (2,500.00), (3,750.00), (4,220.00);
UPDATE sales.accounts SET balance = balance - 100
  WHERE customer_id = 1;
SELECT * FROM sales.accounts WHERE customer_id = 1;
1  900.0

Balance math: 1000.00−100=900.01000.00 - 100 = \mathbf{900.0}.

UPDATE on an ordinary table fails.

UPDATE sales.orders SET amount = 0 WHERE order_id = 106;
FAILED: SemanticException [Error 10294]: Attempt to do update or delete
using transaction manager that does not support these operations.

sales.orders was never created with transactional=true. The failure is the rule, shown as a real error.

QueriesSearch on YouTube

Standard shape: WHERE filters rows, GROUP BY aggregates, HAVING filters the aggregates, ORDER BY sorts, LIMIT caps output.

SELECT customer_id, SUM(amount) AS total
FROM sales.orders
WHERE order_date >= '2026-01-01'
GROUP BY customer_id
HAVING SUM(amount) > 1000
ORDER BY total DESC
LIMIT 10;

Four ways to order rows:

ClauseScopeExample
ORDER BYOne global order; slow on huge resultsORDER BY total DESC;
SORT BYSorted within each reduce task, not globalSORT BY amount;
DISTRIBUTE BYSends matching rows to the same reduce task, unsortedDISTRIBUTE BY customer_id;
CLUSTER BYShorthand for DISTRIBUTE BY x SORT BY xCLUSTER BY customer_id;

Join types: INNER, LEFT / RIGHT / FULL OUTER, LEFT SEMI (an EXISTS-style join), and CROSS.

O-S-D-C: One Sorts, Distribute Collects

ORDER BY gives one global order. SORT BY sorts within each reduce task. DISTRIBUTE BY collects matching rows onto one reduce task. CLUSTER BY combines both: distribute plus sort.

Worked example. Total spend per customer, biggest first (orders 101-106 on file).

SELECT customer_id, SUM(amount) AS total_spent
FROM sales.orders
WHERE amount > 0
GROUP BY customer_id
ORDER BY total_spent DESC;

Step 1, filter (WHERE amount > 0): order 102 drops out (cancelled, amount 0). Five rows stay: 101, 103, 104, 105, 106.

Step 2, group and sum:

customer 1: 450.00+220.50=670.5customer 2: 375.25=375.25customer 3: 180.00=180.0customer 4: 999.99=999.99\begin{aligned} \text{customer } 1&:\ 450.00 + 220.50 = \mathbf{670.5}\\ \text{customer } 2&:\ 375.25 = \mathbf{375.25}\\ \text{customer } 3&:\ 180.00 = \mathbf{180.0}\\ \text{customer } 4&:\ 999.99 = \mathbf{999.99} \end{aligned}

Step 3, sort descending:

4  999.99
1  670.5
2  375.25
3  180.0

Worked example. Bring in names with a JOIN, then filter with HAVING.

SELECT o.order_id, c.name, o.amount
FROM sales.orders o
JOIN sales.customers c ON o.customer_id = c.customer_id
WHERE o.amount > 0
ORDER BY o.order_id;

Match each order to its customer row; drop order 102 (amount = 0):

101  Rajiv   450.0
103  Rajiv   220.5
104  Rajesh  180.0
105  Divya   999.99
106  Omer    375.25

HAVING on six rows. Orders 101-106 are on file. Per-customer totals become:

customer 1: 450.00+220.50=670.5customer 2: 0.00+375.25=375.25customer 3: 180.00=180.0customer 4: 999.99=999.99\begin{aligned} \text{customer } 1&:\ 450.00 + 220.50 = 670.5\\ \text{customer } 2&:\ 0.00 + 375.25 = 375.25\\ \text{customer } 3&:\ 180.00 = 180.0\\ \text{customer } 4&:\ 999.99 = 999.99 \end{aligned}
SELECT customer_id, SUM(amount) AS total
FROM sales.orders
GROUP BY customer_id
HAVING SUM(amount) > 500
ORDER BY total DESC
LIMIT 2;
4  999.99
1  670.5

WHERE would fail here: it cannot see SUM(amount). HAVING filters after aggregation. LIMIT 2 keeps the top two.

Cross-join size. With 4 customers and the first 5 orders, CROSS JOIN (every pair) returns 4×5=204 \times 5 = \mathbf{20} rows.

Views and materialized viewsSearch on YouTube

A plain view is a saved query, not a copy of data. Each query re-runs the SELECT on current data. Plain views are read-only. They simplify complex queries and hide columns from some users.

CREATE VIEW sales.big_orders AS
  SELECT order_id, customer_id, amount
  FROM sales.orders
  WHERE amount > 1000;
DROP VIEW IF EXISTS sales.big_orders;

A materialized view stores its result physically (Hive 3.0 and later). The optimizer can rewrite a matching base-table query to use it: same answer, far less work. Stale data is the cost: inserts into the base table stay invisible until REBUILD.

CREATE MATERIALIZED VIEW sales.daily_totals AS
  SELECT order_date, SUM(amount) AS total
  FROM sales.orders GROUP BY order_date;
ALTER MATERIALIZED VIEW sales.daily_totals
  REBUILD;
plain view no stored data reruns SELECT each time materialized view stores result table fast reads, goes stale REBUILD refreshes after base-table inserts
Plain view: fresh on every read, reruns the query. Materialized view: fast reads, stale until REBUILD.

Worked example. Save the big-orders filter, then watch a materialized view go stale.

CREATE VIEW sales.big_orders AS
  SELECT order_id, customer_id, amount
  FROM sales.orders
  WHERE amount > 400;
SELECT * FROM sales.big_orders;

Orders on file are 101-106. Filter amount > 400:

101: 450.0>400  ⇒  kept105: 999.99>400  ⇒  kept103: 220.5, 104: 180.0, 106: 375.25  ⇒  dropped\begin{aligned} 101&:\ 450.0 \gt 400 \;\Rightarrow\; \text{kept}\\ 105&:\ 999.99 \gt 400 \;\Rightarrow\; \text{kept}\\ 103&:\ 220.5,\ 104:\ 180.0,\ 106:\ 375.25 \;\Rightarrow\; \text{dropped} \end{aligned}
101  1  450.0
105  4  999.99

Materialized daily totals.

CREATE MATERIALIZED VIEW sales.daily_totals AS
  SELECT order_date, SUM(amount) AS total
  FROM sales.orders
  GROUP BY order_date;
SELECT * FROM sales.daily_totals;

Day sums over orders 101-106:

2026-08-10: 450.00+0.00=450.02026-08-11: 220.50+180.00=400.52026-08-12: 999.99=999.992026-08-13: 375.25=375.25\begin{aligned} \text{2026-08-10}&:\ 450.00 + 0.00 = \mathbf{450.0}\\ \text{2026-08-11}&:\ 220.50 + 180.00 = \mathbf{400.5}\\ \text{2026-08-12}&:\ 999.99 = \mathbf{999.99}\\ \text{2026-08-13}&:\ 375.25 = \mathbf{375.25} \end{aligned}
2026-08-10  450.0
2026-08-11  400.5
2026-08-12  999.99
2026-08-13  375.25

Insert a row, then read again before and after REBUILD.

INSERT INTO TABLE sales.orders (order_id, customer_id, amount, order_date)
  VALUES (107, 3, 60.00, '2026-08-13');
SELECT * FROM sales.daily_totals WHERE order_date = '2026-08-13';
2026-08-13  375.25

Still the old total. The new 60.00 order is invisible.

ALTER MATERIALIZED VIEW sales.daily_totals REBUILD;
SELECT * FROM sales.daily_totals WHERE order_date = '2026-08-13';
2026-08-13  435.25

Rebuild math: 375.25+60.00=435.25375.25 + 60.00 = \mathbf{435.25}. The plain view sales.big_orders needs no rebuild: it would show order 107 at once if 107 passed its filter (it does not: 60.00<40060.00 \lt 400).

What replaced indexesSearch on YouTube

Hive had CREATE INDEX before 3.0. It was removed in Hive 3.0 (tracked as HIVE-18448) for limited benefit versus maintenance cost. Current Hive has no CREATE INDEX at all. Four tools do the job now:

ReplacementHow it helpsExample
Materialized viewsPre-compute expensive results, with automatic rewriteCREATE MATERIALIZED VIEW v AS SELECT ...;
Columnar formatsPer-column min/max stats skip blocks that cannot matchSTORED AS ORC;
Partitioning and bucketingPrune directories or files before readingPARTITIONED BY (order_date STRING);
StatisticsANALYZE TABLE feeds the optimizer row counts and value rangesANALYZE TABLE orders COMPUTE STATISTICS;
ANALYZE TABLE orders COMPUTE STATISTICS;

Schema design principlesSearch on YouTube

  1. Schema-on-read, not schema-on-write. Hive applies structure at query time. LOAD DATA never validates column types.
  2. Pick Hive partition columns with care. Low-to-moderate cardinality, filtered often. PARTITIONED BY (order_date STRING), not customer_id.
  3. Use bucketing for high-cardinality keys. Enables bucket map joins that partitioning cannot. CLUSTERED BY (customer_id) INTO 16 BUCKETS.
  4. Default to ORC or Parquet. Keep TEXTFILE for raw landing data. Convert with STORED AS ORC; before serious querying.
  5. Avoid the small-files problem. Many tiny files slow the NameNode and planning. INSERT OVERWRITE compacts after streaming ingestion.
  6. Keep statistics current. Feeds the optimizer real numbers, not guesses. ANALYZE TABLE orders COMPUTE STATISTICS FOR COLUMNS;

Do not partition by a high-cardinality column such as customer_id. Each value becomes a directory, and thousands of tiny partitions strain the Metastore. Partition by date or region; bucket by the high-cardinality key instead.

Quick self-testSearch on YouTube

1. Why did Facebook build Hive, and what does its main query show?

Hadoop needed hand-written MapReduce in Java for every question. Most analysts knew SQL, not Java. Hive lets them query HDFS data with SQL-like statements. The slide demo replaces 100+ lines of Java with a three-line GROUP BY query.

2. Name the four layers a query passes through, in order. What sits underneath them?

Four layers: Client (Thrift / JDBC / ODBC), Services (Web UI, CLI, HiveServer2), Driver, Metastore. The execution engine (MapReduce, Tez, or Spark) and HDFS sit underneath. The Driver checks the Metastore before compiling the plan.

3. What does the Metastore store, and what does it never store?

It stores metadata: table names, column names and types, Hive partition info, and HDFS file locations. It never stores the data itself. The data lives in HDFS files.

4. Query the sample orders: total spend per customer with amount greater than 0, biggest first. Give all four rows.

Filter drops order 102. Sums: customer 1=450.00+220.50=670.51 = 450.00 + 220.50 = 670.5; customer 2=375.252 = 375.25; customer 3=180.03 = 180.0; customer 4=999.994 = 999.99. Descending: 4 999.99, 1 670.5, 2 375.25, 3 180.0.

5. A table is CLUSTERED BY (customer_id) INTO 4 BUCKETS. Which bucket holds customer_id 3 and 4? (INT hash equals the value.)

Bucket == value  mod 4\bmod 4. Customer 33: 3 mod 4=33 \bmod 4 = \mathbf{3}. Customer 44: 4 mod 4=04 \bmod 4 = \mathbf{0}. Rows with id 1 always land in bucket 1 together.

6. Differentiate a managed table from an external table on DROP.

DROP TABLE on a managed (internal) table deletes the schema and the files. On an external table it deletes the metadata; the files at LOCATION stay on disk.

7. Daily totals show 2026-08-13 as 375.25. Then order 107 (60.00, same day) is inserted. What does the materialized view show before and after REBUILD?

Before: still 375.25 (stale). After REBUILD: 375.25+60.00=435.25375.25 + 60.00 = \mathbf{435.25}. A plain view would show the new row at once, with no rebuild step.

8. Differentiate ORDER BY, SORT BY, DISTRIBUTE BY, and CLUSTER BY.

ORDER BY gives one global order. SORT BY sorts within each reduce task. DISTRIBUTE BY sends matching rows to one reduce task without sorting. CLUSTER BY is both: distribute plus sort on the same key.

9. Why does UPDATE fail on sales.orders but work on sales.accounts?

UPDATE needs an ACID table: STORED AS ORC with transactional=true. sales.accounts has both, so the update runs. sales.orders lacks them, so Hive raises error 10294.

10. Name two replacements for Hive indexes (removed in 3.0) and the JIRA id.

Any two: materialized views, ORC/Parquet column stats, partitioning and bucketing, ANALYZE TABLE ... COMPUTE STATISTICS. Removal tracked as HIVE-18448.

  • Hive gives SQL-literate users access to HDFS data without hand-written MapReduce. Facebook built it (2007), open-sourced it (2008); Apache top-level since 2010, now 4.x.
  • Query path: Client (Thrift/JDBC/ODBC) to Services (Web UI, CLI, HiveServer2) to Driver; Driver checks the Metastore, compiles a plan; the engine (MapReduce/Tez/Spark) reads HDFS.
  • Metastore holds metadata (schemas, partitions, file paths), never data.
  • Traits: SQL-like, Hadoop-based, schema-on-read, UDF-extensible, pluggable engines (Tez recommended in 4.x), batch analytics, not OLTP.
  • Primitive types: TINYINT to BIGINT, FLOAT/DOUBLE, DECIMAL(p,s), DATE, TIMESTAMP, INTERVAL, STRING, VARCHAR(n), CHAR(n), BOOLEAN, BINARY.
  • Complex types: ARRAY (tags[0]), MAP (attributes['color']), STRUCT (dimensions.length), rarely used UNIONTYPE.
  • Formats: ORC for Hive-only and ACID tables, Parquet for cross-engine reads, Avro for evolving schemas, TEXTFILE for raw landing data.
  • DATABASE = SCHEMA; DROP DATABASE needs CASCADE when non-empty; LOCATION changes affect new tables.
  • Managed (internal) tables: DROP deletes schema and files. External tables: DROP deletes metadata; files stay. TRUNCATE keeps schema, clears rows, managed tables.
  • Hive partition columns: low cardinality, often filtered. Bucket keys: high cardinality, for joins and sampling.
  • LOAD DATA copies files with no job. INSERT ... SELECT runs a query. INSERT OVERWRITE replaces. EXPORT/IMPORT move schema plus data.
  • UPDATE/DELETE need ACID tables: STORED AS ORC with transactional=true; ordinary tables raise error 10294.
  • Query shape: WHERE, GROUP BY, HAVING, ORDER BY, LIMIT. Four orderings: ORDER BY global, SORT BY per reduce task, DISTRIBUTE BY collects, CLUSTER BY is both. Joins: inner, outer, left semi, cross.
  • Plain views rerun each time and stay fresh. Materialized views store results, read fast, go stale until REBUILD; the optimizer can auto-rewrite queries to use them.
  • Indexes were removed in Hive 3.0 (HIVE-18448). Replacements: materialized views, columnar stats, partitioning/bucketing, ANALYZE TABLE ... COMPUTE STATISTICS.
  • Design rules: schema-on-read, date/region partitions, bucket high-cardinality keys, ORC/Parquet default, compact small files, current statistics.