← Back to OnTime Dataset

PuppyGraph Implementation

Deployment and Query logic for PuppyGraph Community 0.106a.

Schema Diagram and Graph Statistics

OnTime PuppyGraph Schema

Node Statistics

Node Label Count
Airport 379

Edge Statistics

Relationship Count
FLIGHT 17,412,876

Data Loading

Overview

The data ingestion pipeline has two stages. Initially, raw flight datasets are stored in Hadoop Distributed File System (HDFS). We then utilize Spark to perform schema enforcement and type casting, persisting the structured data as Delta Lake tables registered within a Hive Metastore. PuppyGraph then interfaces directly with this Metastore via a schema.json file, enabling a zero-ETL graph overlay on the existing data lake.

Metastore Import

First we create a Spark session using its Python API. This stage handles the transformation of monthly CSV partitions into a unified, high-performance relational structure.

python
from pyspark.sql import SparkSession

spark = (
    SparkSession.builder
        .appName("ot_puppy")
        .master("spark://master:7077")
        
        # EXECUTORS
        .config("spark.executor.instances", "14")
        .config("spark.executor.cores", "8")
        .config("spark.executor.memory", "30g")
        .config("spark.driver.memory", "10g")
        .config("spark.cores.max", "112")
        
        # Memory management
        .config("spark.memory.fraction", "0.6")
        .config("spark.memory.storageFraction", "0.5")
        
        # Parallelism
        .config("spark.default.parallelism", "112")
        .config("spark.sql.shuffle.partitions", "112")
        
        # Delta Lake
        .config("spark.jars.packages", "io.delta:delta-core_2.12:2.4.0")
        .config("spark.sql.extensions", "io.delta.sql.DeltaSparkSessionExtension")
        .config("spark.sql.catalog.spark_catalog", "org.apache.spark.sql.delta.catalog.DeltaCatalog")
        
        # HDFS - CORRECTED WAREHOUSE PATH
        .config("spark.hadoop.fs.defaultFS", "hdfs://master:9000")
        .config("spark.sql.warehouse.dir", "hdfs://master:9000/spark-warehouse")        
        # Hive Metastore
        .config("spark.sql.catalogImplementation", "hive")
        .config("hive.metastore.uris", "thrift://master:9083")
        
        .getOrCreate()
)

Next, we create the database and table, and load data from HDFS into the Hive Metastore. We used a big dataframe to store all our CSV files.

python
from pyspark.sql.functions import col, lit
from pyspark.sql.types import ShortType, ByteType, IntegerType

years = [2020, 2021, 2022]
combined_df = None

for year in years:
    year_path = f"hdfs:///ontime20-22/{year}_*.csv"
    print(f"Processing Year: {year} from {year_path}")
    
    temp_df = spark.read.format("csv") \
        .option("header", "true") \
        .option("inferSchema", "true") \
        .load(year_path)
    
    processed_df = temp_df.select(
        col("Year").cast(ShortType()),
        col("Quarter").cast(ByteType()),
        col("Month").cast(ByteType()),
        col("DayofMonth").cast(ByteType()),
        col("DayOfWeek").cast(ByteType()),
        col("FlightDate").cast("date"),
        col("Reporting_Airline"),
        col("DOT_ID_Reporting_Airline").cast(IntegerType()),
        col("IATA_CODE_Reporting_Airline"),
        col("Tail_Number"),
        col("Flight_Number_Reporting_Airline").cast("string"),
        col("OriginAirportID").cast(IntegerType()),
        col("OriginAirportSeqID").cast(IntegerType()),
        col("OriginCityMarketID").cast(IntegerType()),
        col("Origin"),
        col("OriginCityName"),
        col("OriginState"),
        col("OriginStateFips").cast("string"),
        col("OriginStateName"),
        col("OriginWac").cast(IntegerType()),
        col("DestAirportID").cast(IntegerType()),
        col("DestAirportSeqID").cast(IntegerType()),
        col("DestCityMarketID").cast(IntegerType()),
        col("Dest"),
        col("DestCityName"),
        col("DestState"),
        col("DestStateFips").cast("string"),
        col("DestStateName"),
        col("DestWac").cast(IntegerType()),
        col("CRSDepTime").cast(IntegerType()),
        col("DepTime").cast(IntegerType()),
        col("DepDelay").cast(IntegerType()),
        col("DepDelayMinutes").cast(IntegerType()),
        col("DepDel15").cast(IntegerType()),
        col("DepartureDelayGroups").cast("string"),
        col("DepTimeBlk"),
        col("TaxiOut").cast(IntegerType()),
        col("WheelsOff").cast("string"),
        col("WheelsOn").cast("string"),
        col("TaxiIn").cast(IntegerType()),
        col("CRSArrTime").cast(IntegerType()),
        col("ArrTime").cast(IntegerType()),
        col("ArrDelay").cast(IntegerType()),
        col("ArrDelayMinutes").cast(IntegerType()),
        col("ArrDel15").cast(IntegerType()),
        col("ArrivalDelayGroups"),
        col("ArrTimeBlk"),
        col("Cancelled").cast(ByteType()),
        col("CancellationCode"),
        col("Diverted").cast(ByteType()),
        col("CRSElapsedTime").cast(IntegerType()),
        col("ActualElapsedTime").cast(IntegerType()),
        col("AirTime").cast(IntegerType()),
        col("Flights").cast(IntegerType()),
        col("Distance").cast(IntegerType()),
        col("DistanceGroup").cast(ByteType()),
        col("CarrierDelay").cast(IntegerType()),
        col("WeatherDelay").cast(IntegerType()),
        col("NASDelay").cast(IntegerType()),
        col("SecurityDelay").cast(IntegerType()),
        col("LateAircraftDelay").cast(IntegerType())
    )
    
    if combined_df is None:
        combined_df = processed_df
    else:
        combined_df = combined_df.union(processed_df)

combined_df.write.format("delta") \
    .mode("overwrite") \
    .partitionBy("Year") \
    .option("overwriteSchema", "true") \
    .saveAsTable("ontime_202x")

print("Delta table 'ontime_202x' created successfully.")

And then we create a database and put our big table into this database.

python
from pyspark.sql import DataFrame

spark._jsparkSession.sql("CREATE DATABASE IF NOT EXISTS ontime20_22")

# Verify it was created
db_list = DataFrame(spark._jsparkSession.sql("SHOW DATABASES"), spark)
print(db_list.show())

target_table = "ontime20_22.ontime_202x"
combined_df.write.format("delta") \
    .mode("overwrite") \
    .partitionBy("Year") \
    .option("overwriteSchema", "true") \
    .saveAsTable(target_table)
print(f"Table successfully saved to {target_table}")

PuppyGraph Import

The graph topology is defined through a comprehensive schema.json mapping. This configuration designates the relational tables as vertex and edge sources, defining the unique identifiers and rich attribute sets required for graph-traversal operations without duplicating the underlying physical storage.

json
{
  "catalogs": [
    {
      "name": "test_202x",
      "type": "deltalake",
      "metastore": {
        "type": "HMS",
        "hiveMetastoreUrl": "thrift://master:9083"
      },
      "storage": {
        "type": "FromMetaStore"
      }
    }
  ],
  "graph": {
    "vertices": [
      {
        "label": "airport",
        "manyToOne": {
          "sources": [
            {
              "source": {
                "catalog": "test_202x",
                "schema": "ontime20_22",
                "table": "ontime_202x"
              },
              "id": {
                "fields": [
                  {
                    "type": "INT",
                    "field": "OriginAirportID",
                    "alias": "puppy_id0"
                  }
                ]
              }
            },
            {
              "source": {
                "catalog": "test_202x",
                "schema": "ontime20_22",
                "table": "ontime_202x"
              },
              "id": {
                "fields": [
                  {
                    "type": "INT",
                    "field": "DestAirportID",
                    "alias": "puppy_id0"
                  }
                ]
              }
            }
          ]
        }
      }
    ],
    "edges": [
      {
        "label": "flight",
        "fromVertex": "airport",
        "toVertex": "airport",
        "tableSource": {
          "catalog": "test_202x",
          "schema": "ontime20_22",
          "table": "ontime_202x"
        },
        "id": {
          "fields": [
            {"type": "DATE","field": "FlightDate","alias": "puppy_id_FlightDate"},
            {"type": "STRING","field": "IATA_CODE_Reporting_Airline","alias": "puppy_id_IATA_CODE_Reporting_Airline"},
            {"type": "STRING","field": "Flight_Number_Reporting_Airline","alias": "puppy_id_Flight_Number_Reporting_Airline"},
            {"type": "INT","field": "OriginAirportID","alias": "puppy_id_OriginAirportID"},
            {"type": "INT","field": "DestAirportID","alias": "puppy_id_DestAirportID"}
          ]
        },
        "fromId": {
          "fields": [
            {"type": "INT", "field": "OriginAirportID", "alias": "puppy_from_OriginAirportID"}
          ]
        },
        "toId": {
          "fields": [
            { "type": "INT", "field": "DestAirportID", "alias": "puppy_to_DestAirportID"
            }
          ]
        },
        "attributes": [
          {"type": "SHORT", "field": "Year", "alias": "Year"},
          {"type": "BYTE", "field": "Quarter", "alias": "Quarter"},
          {"type": "BYTE", "field": "Month", "alias": "Month"},
          {"type": "BYTE", "field": "DayofMonth", "alias": "DayofMonth"},
          {"type": "BYTE", "field": "DayOfWeek", "alias": "DayOfWeek"},
          {"type": "DATE", "field": "FlightDate", "alias": "FlightDate"},
          {"type": "STRING", "field": "Reporting_Airline", "alias": "Reporting_Airline"},
          {"type": "INT", "field": "DOT_ID_Reporting_Airline", "alias": "DOT_ID_Reporting_Airline"},
          {"type": "STRING", "field": "IATA_CODE_Reporting_Airline", "alias": "IATA_CODE_Reporting_Airline"},
          {"type": "STRING", "field": "Tail_Number", "alias": "Tail_Number"},
          {"type": "STRING", "field": "Flight_Number_Reporting_Airline", "alias": "Flight_Number_Reporting_Airline"},
          {"type": "INT", "field": "OriginAirportID", "alias": "OriginAirportID"},
          {"type": "INT", "field": "OriginAirportSeqID", "alias": "OriginAirportSeqID"},
          {"type": "INT", "field": "OriginCityMarketID", "alias": "OriginCityMarketID"},
          {"type": "STRING", "field": "Origin", "alias": "Origin"},
          {"type": "STRING", "field": "OriginCityName", "alias": "OriginCityName"},
          {"type": "STRING", "field": "OriginCityName", "alias": "OriginCityName"},
          {"type": "STRING", "field": "OriginState", "alias": "OriginState"},
          {"type": "STRING", "field": "OriginStateFips", "alias": "OriginStateFips"},
          {"type": "STRING", "field": "OriginStateName", "alias": "OriginStateName"},
          {"type": "INT", "field": "OriginWac", "alias": "OriginWac"},
          {"type": "INT", "field": "DestAirportID", "alias": "DestAirportID"},
          {"type": "INT", "field": "DestAirportSeqID", "alias": "DestAirportSeqID"},
          {"type": "INT", "field": "DestCityMarketID", "alias": "DestCityMarketID"},
          {"type": "STRING", "field": "Dest", "alias": "Dest"},
          {"type": "STRING", "field": "DestCityName", "alias": "DestCityName"},
          {"type": "STRING", "field": "DestState", "alias": "DestState"},
          {"type": "STRING", "field": "DestStateFips", "alias": "DestStateFips"},
          {"type": "STRING", "field": "DestStateName", "alias": "DestStateName"},
          {"type": "INT", "field": "DestWac", "alias": "DestWac"},
          {"type": "INT", "field": "CRSDepTime", "alias": "CRSDepTime"},
          {"type": "INT", "field": "DepTime", "alias": "DepTime"},
          {"type": "INT", "field": "DepDelay", "alias": "DepDelay"},
          {"type": "INT", "field": "DepDelayMinutes", "alias": "DepDelayMinutes"},
          {"type": "INT", "field": "DepDel15", "alias": "DepDel15"},
          {"type": "STRING", "field": "DepartureDelayGroups", "alias": "DepartureDelayGroups"},
          {"type": "STRING", "field": "DepTimeBlk", "alias": "DepTimeBlk"},
          {"type": "INT", "field": "TaxiOut", "alias": "TaxiOut"},
          {"type": "STRING", "field": "WheelsOff", "alias": "WheelsOff"},
          {"type": "STRING", "field": "WheelsOn", "alias": "WheelsOn"},
          {"type": "INT", "field": "TaxiIn", "alias": "TaxiIn"},
          {"type": "INT", "field": "CRSArrTime", "alias": "CRSArrTime"},
          {"type": "INT", "field": "ArrTime", "alias": "ArrTime"},
          {"type": "INT", "field": "ArrDelay", "alias": "ArrDelay"},
          {"type": "INT", "field": "ArrDelayMinutes", "alias": "ArrDelayMinutes"},
          {"type": "INT", "field": "ArrDel15", "alias": "ArrDel15"},
          {"type": "INT", "field": "ArrivalDelayGroups", "alias": "ArrivalDelayGroups"},
          {"type": "STRING", "field": "ArrTimeBlk", "alias": "ArrTimeBlk"},
          {"type": "BYTE", "field": "Cancelled", "alias": "Cancelled"},
          {"type": "STRING", "field": "CancellationCode", "alias": "CancellationCode"},
          {"type": "BYTE", "field": "Diverted", "alias": "Diverted"},
          {"type": "INT", "field": "CRSElapsedTime", "alias": "CRSElapsedTime"},
          {"type": "INT", "field": "ActualElapsedTime", "alias": "ActualElapsedTime"},
          {"type": "INT", "field": "AirTime", "alias": "AirTime"},
          {"type": "INT", "field": "Flights", "alias": "Flights"},
          {"type": "INT", "field": "Distance", "alias": "Distance"},
          {"type": "BYTE", "field": "DistanceGroup", "alias": "DistanceGroup"},
          {"type": "INT", "field": "CarrierDelay", "alias": "CarrierDelay"},
          {"type": "INT", "field": "WeatherDelay", "alias": "WeatherDelay"},
          {"type": "INT", "field": "NASDelay", "alias": "NASDelay"},
          {"type": "INT", "field": "SecurityDelay", "alias": "SecurityDelay"},
          {"type": "INT", "field": "LateAircraftDelay", "alias": "LateAircraftDelay"}
        ]
      }
    ]
  }
}

Queries

Select a query below to view its logic and description.

1. Neighborhood
2. Path
3. Pattern
4. Aggregation
5. Analytics
6. Hybrid
7. Others
No queries in this category.

Q1. Direct Destinations from an Airport

Subcategory: T1.1 One-Hop Neighbors

This query lists airports directly reachable from a selected origin airport by one outgoing FLIGHT relationship during a selected year.

openCypher
-- Q1. Direct Destinations from an Airport
MATCH (:airport)-[f:flight]->(:airport)
WHERE f.Origin = 'IND'
  AND f.Year = 2020
RETURN DISTINCT f.DestAirportID AS airport_id,
       f.Dest AS code,
       f.DestCityName AS city,
       f.DestState AS state
ORDER BY code;

Equivalent query in GQL standard:

gql
MATCH (origin:Airport {airport_id: $originAirportId})-[f:FLIGHT]->(dest:Airport)
WHERE f.Year = $year
RETURN DISTINCT dest.airport_id AS airport_id, dest.code AS code, dest.city AS city, dest.state AS state
ORDER BY code
LIMIT $limit;

Q2. Airports Reachable within K Hops

Subcategory: T1.2 k-Hop Neighborhood

This query returns airports reachable from a selected airport within three directed flight legs.

openCypher
-- Q2. Airports Reachable within K Hops
MATCH (origin:airport)
WHERE elementId(origin) = 'airport[12339]'
MATCH p = (origin)-[flights:flight*1..3]->(:airport)
WHERE all(flight IN flights WHERE flight.Year = 2020)
RETURN DISTINCT
       last(flights).DestAirportID AS airport_id,
       last(flights).Dest AS code,
       last(flights).DestCityName AS city,
       length(p) AS hop_count
ORDER BY hop_count, code;

Equivalent query in GQL standard:

gql
MATCH p = (:Airport {airport_id: $originAirportId})-[f:FLIGHT]->{1,3}(dest:Airport)
WHERE all(flight IN f WHERE flight.year = $Year)
RETURN DISTINCT
    dest.airport_id AS airport_id,
    dest.code AS code,
    dest.city AS city,
    length(p) AS hop_count
ORDER BY hop_count, code
LIMIT $limit;

Q3. Airports Exactly Three Flights Away

Subcategory: T1.3 Exact kth-Hop Neighbors

This query finds airports exactly three outgoing flight legs away from a selected origin airport.

openCypher
-- Q3. Airports Exactly Three Flights Away
MATCH (origin:airport)
WHERE elementId(origin) = 'airport[12339]'
MATCH p = (origin)-[flights:flight*3]->(:airport)
WHERE all(flight IN flights WHERE flight.Year = 2020)
RETURN DISTINCT last(flights).DestAirportID AS airport_id,
       last(flights).Dest AS code,
       last(flights).DestCityName AS city,
       length(p) AS hop_count
ORDER BY code;

Equivalent query in GQL standard:

gql
MATCH p = (:Airport {airport_id: $originAirportId})-[f:FLIGHT]->{3,3}(dest:Airport)
WHERE all(flight IN f WHERE flight.year = $Year)
RETURN DISTINCT dest.airport_id AS airport_id, dest.code AS code, dest.city AS city, length(p) AS hop_count
ORDER BY code
LIMIT $limit;

Q4. Delayed One-Hop Destinations

Subcategory: T1.4 Neighborhood with Property Filter

This query filters one-hop destinations by departure delay on the FLIGHT relationship.

openCypher
-- Q4. Delayed One-Hop Destinations
MATCH (:airport)-[f:flight]->(:airport)
WHERE f.Origin = 'IND'
  AND f.Year = 2020
  AND f.DepDelayMinutes >= 120
RETURN f.DestAirportID AS airport_id,
       f.Dest AS code,
       f.FlightDate AS flight_date,
       f.IATA_CODE_Reporting_Airline AS airline,
       f.DepDelayMinutes AS departure_delay
ORDER BY departure_delay DESC;

Equivalent query in GQL standard:

gql
MATCH (origin:Airport {airport_id: $originAirportId})-[f:FLIGHT]->(dest:Airport)
WHERE f.Year = $year
  AND f.DepDelayMinutes >= $minDelayMinutes
RETURN dest.airport_id AS airport_id, dest.code AS code, f.FlightDate AS flight_date,
       f.IATA_CODE_Reporting_Airline AS airline, f.DepDelayMinutes AS departure_delay
ORDER BY departure_delay DESC
LIMIT $limit;

Q5. Airports by State and Code Prefix

Subcategory: T2.1 Node Filtering (Single Node Paths)

This query filters Airport nodes by imported node properties without traversing relationships.

openCypher
-- Q5. Airports by State and Code Prefix
MATCH (:airport)-[f:flight]->(:airport)
WHERE f.OriginState = 'CA'
  AND f.Origin STARTS WITH 'S'
RETURN DISTINCT f.OriginAirportID AS airport_id,
       f.Origin AS code,
       f.OriginCityName AS city,
       f.OriginStateName AS state_name
ORDER BY code;

Equivalent query in GQL standard:

gql
MATCH (airport:Airport)
WHERE airport.state = $state
  AND airport.code STARTS WITH $codePrefix
RETURN airport.airport_id AS airport_id, airport.code AS code, airport.city AS city, airport.stateName AS state_name
ORDER BY code
LIMIT $limit;

Q6. Long Non-Cancelled Flights

Subcategory: T2.2 Edge Filtering

This query filters FLIGHT relationships by relationship properties loaded from the OnTime CSV files.

openCypher
-- Q6. Long Non-Cancelled Flights
MATCH (:airport)-[f:flight]->(:airport)
WHERE f.Year = 2020
  AND f.Cancelled = 0
  AND f.Distance >= 2000
RETURN f.Origin AS origin,
       f.Dest AS dest,
       f.FlightDate AS flight_date,
       f.IATA_CODE_Reporting_Airline AS airline,
       f.Distance AS distance
ORDER BY distance DESC;

Equivalent query in GQL standard:

gql
MATCH (origin:Airport)-[f:FLIGHT]->(dest:Airport)
WHERE f.Year = $year
  AND f.Cancelled = 0
  AND f.Distance >= $minDistance
RETURN origin.code AS origin, dest.code AS dest, f.FlightDate AS flight_date,
       f.IATA_CODE_Reporting_Airline AS airline, f.Distance AS distance
ORDER BY distance DESC
LIMIT $limit;

Q7. Reachability between Two Airports

Subcategory: T2.3 Reachability between Two Nodes

This query checks whether a destination airport is reachable from a source airport within three directed flight legs.

openCypher
-- Q7. Reachability between Two Airports
MATCH (source:airport)
WHERE elementId(source) = 'airport[12339]'
MATCH (target:airport)
WHERE elementId(target) = 'airport[12478]'
MATCH p = (source)-[:flight*1..3]->(target)
RETURN count(p) > 0 AS reachable
LIMIT 1;

Equivalent query in GQL standard:

gql
MATCH p = ANY (:Airport {airport_id: $sourceAirportId})-[:FLIGHT]->{1,3}(:Airport {airport_id: $targetAirportId})
RETURN true AS reachable;

Q8. Shortest Airport Route

Subcategory: T2.4 Shortest Path

This query finds the shortest directed airport-to-airport route up to four flight legs.

openCypher
-- Q8. Shortest Airport Route
MATCH (source:airport)
WHERE elementId(source) = 'airport[12339]'
MATCH (target:airport)
WHERE elementId(target) = 'airport[12478]'
MATCH p = shortestPath((source)-[:flight*1..4]->(target))
RETURN p, length(p) AS hop_count;

Equivalent query in GQL standard:

gql
MATCH p = SHORTEST (:Airport {airport_id: $sourceAirportId})-[:FLIGHT]->{1,4}(:Airport {airport_id: $targetAirportId})
RETURN p, length(p) AS hop_count;

Q9. Simple Routes up to Three Legs

Subcategory: T2.5 All Simple Paths up to Length k

This query enumerates simple directed routes between two airports up to three flight legs.

openCypher
-- Q9. Simple Routes up to Three Legs
MATCH (source:airport)
WHERE elementId(source) = 'airport[12339]'
MATCH (target:airport)
WHERE elementId(target) = 'airport[12478]'
MATCH p = (source)-[:flight*1..3]->(target)
WHERE all(n IN nodes(p) WHERE single(m IN nodes(p) WHERE m = n))
RETURN p, length(p) AS hop_count
ORDER BY hop_count;

Equivalent query in GQL standard:

gql
MATCH p = ACYCLIC (:Airport {airport_id: $sourceAirportId})-[:FLIGHT]->{1,3}(:Airport {airport_id: $targetAirportId})
RETURN p, length(p) AS hop_count
ORDER BY hop_count
LIMIT $limit;

Q10. Top Weighted Routes by Distance

Subcategory: T2.6 top-K Weighted Paths

This query ranks bounded airport routes by cumulative flight distance.

openCypher
-- Q10. Top Weighted Routes by Distance
MATCH (source:airport)
WHERE elementId(source) = 'airport[12339]'
MATCH (target:airport)
WHERE elementId(target) = 'airport[12478]'
MATCH p = (source)-[:flight*1..3]->(target)
WHERE all(n IN nodes(p) WHERE single(m IN nodes(p) WHERE m = n))
WITH p, reduce(total = 0, f IN relationships(p) |
               total + coalesce(f.Distance, 0)) AS total_distance
RETURN p, length(p) AS hop_count, total_distance
ORDER BY total_distance ASC;

Equivalent query in GQL standard:

gql
MATCH p = ACYCLIC (:Airport {airport_id: $sourceAirportId})-[:FLIGHT]->{1,3}(:Airport {airport_id: $targetAirportId})
WITH p, reduce(total = 0, f IN relationships(p) | total + coalesce(f.Distance, 0)) AS total_distance
RETURN p, length(p) AS hop_count, total_distance
ORDER BY total_distance ASC
LIMIT $limit;

Q11. Directed Triangle of Airports

Subcategory: T3.1 Triangle Detection

This query detects directed three-airport cycles in the flight graph.

openCypher
-- Q11. Directed Triangle of Airports
MATCH (a:airport)-[ab:flight]->(b:airport)-[bc:flight]->(c:airport)-[:flight]->(a)
WHERE ab.OriginAirportID < ab.DestAirportID
  AND ab.DestAirportID < bc.DestAirportID
RETURN ab.Origin AS airport_a,
       ab.Dest AS airport_b,
       bc.Dest AS airport_c;

Equivalent query in GQL standard:

gql
MATCH (a:Airport)-[:FLIGHT]->(b:Airport)-[:FLIGHT]->(c:Airport)-[:FLIGHT]->(a)
WHERE a.airport_id < b.airport_id AND b.airport_id < c.airport_id
RETURN a.code AS airport_a, b.code AS airport_b, c.code AS airport_c
LIMIT $limit;

Q12. Four-Airport Cycle

Subcategory: T3.2 Square Detection

This query detects directed four-airport cycles in the flight graph.

openCypher
-- Q12. Four-Airport Cycle
MATCH p = (a:airport)-[ab:flight]->(b:airport)-[bc:flight]->(c:airport)-[cd:flight]->(d:airport)-[:flight]->(a)
WHERE elementId(a) <> elementId(b)
  AND elementId(a) <> elementId(c)
  AND elementId(a) <> elementId(d)
  AND elementId(b) <> elementId(c)
  AND elementId(b) <> elementId(d)
  AND elementId(c) <> elementId(d)
RETURN ab.Origin AS airport_a,
       ab.Dest AS airport_b,
       bc.Dest AS airport_c,
       cd.Dest AS airport_d;

Equivalent query in GQL standard:

gql
MATCH (a:Airport)-[:FLIGHT]->(b:Airport)-[:FLIGHT]->(c:Airport)-[:FLIGHT]->(d:Airport)-[:FLIGHT]->(a)
WHERE a <> b AND a <> c AND a <> d AND b <> c AND b <> d AND c <> d
RETURN a.code AS airport_a, b.code AS airport_b, c.code AS airport_c, d.code AS airport_d
LIMIT $limit;

Q13. Hub Airports by Distinct Destinations

Subcategory: T3.3 Star Pattern Query

This query identifies hub airports with many distinct outgoing destination neighbors.

openCypher
-- Q13. Hub Airports by Distinct Destinations
MATCH (center:airport)-[f:flight]->(dest:airport)
WHERE f.Year = 2020
WITH center,
     f.OriginAirportID AS airport_id,
     f.Origin AS code,
     f.OriginCityName AS city,
     count(DISTINCT dest) AS destination_count
WHERE destination_count >= 190
RETURN airport_id, code, city, destination_count
ORDER BY destination_count DESC;

Equivalent query in GQL standard:

gql
MATCH (center:Airport)-[f:FLIGHT]->(dest:Airport)
WHERE f.Year = $year
WITH center, count(DISTINCT dest) AS destination_count
WHERE destination_count >= $minDegree
RETURN center.airport_id AS airport_id, center.code AS code, center.city AS city, destination_count
ORDER BY destination_count DESC
LIMIT $limit;

Q14. Four-Airport Clique

Subcategory: T3.4 4-Clique

This query finds sets of four airports where every pair has at least one flight relationship in either direction.

openCypher
-- Q14. Four-Airport Clique
MATCH (a:airport)-[:flight]-(b:airport),
      (a)-[:flight]-(c:airport),
      (a)-[:flight]-(d:airport),
      (b)-[:flight]-(c),
      (b)-[:flight]-(d),
      (c)-[:flight]-(d)
WHERE elementId(a) < elementId(b)
  AND elementId(b) < elementId(c)
  AND elementId(c) < elementId(d)
RETURN elementId(a) AS airport_a,
       elementId(b) AS airport_b,
       elementId(c) AS airport_c,
       elementId(d) AS airport_d;

Equivalent query in GQL standard:

gql
MATCH (a:Airport)-[:FLIGHT]-(b:Airport),
      (a)-[:FLIGHT]-(c:Airport),
      (a)-[:FLIGHT]-(d:Airport),
      (b)-[:FLIGHT]-(c),
      (b)-[:FLIGHT]-(d),
      (c)-[:FLIGHT]-(d)
WHERE a.airport_id < b.airport_id AND b.airport_id < c.airport_id AND c.airport_id < d.airport_id
RETURN a.code AS airport_a, b.code AS airport_b, c.code AS airport_c, d.code AS airport_d
LIMIT $limit;

Q15. Two-Hop Airport Connections

Subcategory: T3.5 Two-hop Join Pattern

This query finds two-leg connections through an intermediate airport for a selected source airport.

openCypher
-- Q15. Two-Hop Airport Connections
MATCH (source:airport)-[first:flight]->(:airport)-[second:flight]->(dest:airport)
WHERE elementId(source) = 'airport[12339]'
  AND first.Year = 2020
  AND second.Year = 2020
  AND elementId(source) <> elementId(dest)
RETURN first.Dest AS connection_airport,
       second.Dest AS destination,
       count(*) AS connection_count
ORDER BY connection_count DESC;

Equivalent query in GQL standard:

gql
MATCH (source:Airport {airport_id: $sourceAirportId})-[first:FLIGHT]->(middle:Airport)-[second:FLIGHT]->(dest:Airport)
WHERE first.Year = $year
  AND second.Year = $year
  AND source <> dest
RETURN middle.code AS connection_airport, dest.code AS destination, count(*) AS connection_count
ORDER BY connection_count DESC
LIMIT $limit;

Q16. Three-Hop Line Pattern

Subcategory: T3.6 Line Pattern

This query matches a four-airport line connected by three consecutive FLIGHT relationships.

openCypher
-- Q16. Three-Hop Line Pattern
MATCH p = (a:airport)-[ab:flight]->(b:airport)-[bc:flight]->(c:airport)-[cd:flight]->(d:airport)
WHERE elementId(a) <> elementId(b)
  AND elementId(a) <> elementId(c)
  AND elementId(a) <> elementId(d)
  AND elementId(b) <> elementId(c)
  AND elementId(b) <> elementId(d)
  AND elementId(c) <> elementId(d)
RETURN ab.Origin AS airport_a,
       ab.Dest AS airport_b,
       bc.Dest AS airport_c,
       cd.Dest AS airport_d,
       p;

Equivalent query in GQL standard:

gql
MATCH p = (a:Airport)-[:FLIGHT]->(b:Airport)-[:FLIGHT]->(c:Airport)-[:FLIGHT]->(d:Airport)
WHERE a <> b AND a <> c AND a <> d AND b <> c AND b <> d AND c <> d
RETURN a.code AS airport_a, b.code AS airport_b, c.code AS airport_c, d.code AS airport_d, p
LIMIT $limit;

Q17. Airport Degree Summary

Subcategory: T4.1 Ungrouped Node Degree Statistics by Label

This query computes global statistics over each airport's distinct outgoing destination count.

openCypher
-- Q17. Airport Degree Summary
MATCH (airport:airport)
OPTIONAL MATCH (airport)-[:flight]->(dest:airport)
WITH airport, count(DISTINCT dest) AS out_degree
RETURN count(airport) AS airport_count,
       avg(out_degree) AS avg_out_degree,
       min(out_degree) AS min_out_degree,
       max(out_degree) AS max_out_degree;

Equivalent query in GQL standard:

gql
MATCH (airport:Airport)
OPTIONAL MATCH (airport)-[:FLIGHT]->(dest:Airport)
WITH airport, count(DISTINCT dest) AS out_degree
RETURN count(airport) AS airport_count,
       avg(out_degree) AS avg_out_degree,
       min(out_degree) AS min_out_degree,
       max(out_degree) AS max_out_degree;

Q18. State-Level Degree Summary

Subcategory: T4.2 Grouped Node Degree Statistics by Label

This query groups airport degree statistics by the airport state loaded on Airport nodes.

openCypher
-- Q18. State-Level Degree Summary
MATCH (airport:airport)
OPTIONAL MATCH (airport)-[f:flight]->(dest:airport)
WITH airport, f.OriginState AS state, count(DISTINCT dest) AS out_degree
RETURN state,
       count(airport) AS airport_count,
       avg(out_degree) AS avg_out_degree,
       min(out_degree) AS min_out_degree,
       max(out_degree) AS max_out_degree
ORDER BY avg_out_degree DESC;

Equivalent query in GQL standard:

gql
MATCH (airport:Airport)
OPTIONAL MATCH (airport)-[:FLIGHT]->(dest:Airport)
WITH airport, count(DISTINCT dest) AS out_degree
RETURN airport.state AS state,
       count(airport) AS airport_count,
       avg(out_degree) AS avg_out_degree,
       min(out_degree) AS min_out_degree,
       max(out_degree) AS max_out_degree
ORDER BY avg_out_degree DESC
LIMIT $limit;

Q19. Flight Edge Distance Summary

Subcategory: T4.3 Ungrouped Edge Count and Property Statistics

This query summarizes numeric properties over all matching FLIGHT relationships.

openCypher
-- Q19. Flight Edge Distance Summary
MATCH (:airport)-[f:flight]->(:airport)
WHERE f.Year = 2020
RETURN count(f) AS flight_count,
       avg(f.Distance) AS avg_distance,
       min(f.Distance) AS min_distance,
       max(f.Distance) AS max_distance,
       avg(f.ArrDelayMinutes) AS avg_arrival_delay;

Equivalent query in GQL standard:

gql
MATCH (:Airport)-[f:FLIGHT]->(:Airport)
WHERE f.Year = $year
RETURN count(f) AS flight_count,
       avg(f.Distance) AS avg_distance,
       min(f.Distance) AS min_distance,
       max(f.Distance) AS max_distance,
       avg(f.ArrDelayMinutes) AS avg_arrival_delay;

Q20. State Pair Flight Statistics

Subcategory: T4.4 Grouped Edge Count and Property Statistics

This query groups FLIGHT relationship statistics by origin and destination state.

openCypher
-- Q20. State Pair Flight Statistics
MATCH (:airport)-[f:flight]->(:airport)
WHERE f.Year = 2020
RETURN f.OriginState AS origin_state,
       f.DestState AS dest_state,
       count(f) AS flight_count,
       avg(f.Distance) AS avg_distance,
       avg(f.ArrDelayMinutes) AS avg_arrival_delay
ORDER BY flight_count DESC;

Equivalent query in GQL standard:

gql
MATCH (origin:Airport)-[f:FLIGHT]->(dest:Airport)
WHERE f.Year = $year
RETURN origin.state AS origin_state,
       dest.state AS dest_state,
       count(f) AS flight_count,
       avg(f.Distance) AS avg_distance,
       avg(f.ArrDelayMinutes) AS avg_arrival_delay
ORDER BY flight_count DESC
LIMIT $limit;

Q21. Route Counts by Path Length

Subcategory: T4.5 Path Counts by Length

This query counts bounded paths from a selected airport grouped by path length.

openCypher
-- Q21. Route Counts by Path Length
MATCH (source:airport)
WHERE elementId(source) = 'airport[12339]'
MATCH p = (source)-[:flight*1..3]->(target:airport)
RETURN length(p) AS path_length,
       count(p) AS path_count,
       count(DISTINCT target) AS distinct_target_count
ORDER BY path_length;

Equivalent query in GQL standard:

gql
MATCH p = (:Airport {airport_id: $sourceAirportId})-[:FLIGHT]->{1,3}(target:Airport)
RETURN length(p) AS path_length,
       count(p) AS path_count,
       count(DISTINCT target) AS distinct_target_count
ORDER BY path_length;

Q22. Route Counts by Length and Target State

Subcategory: T4.6 Grouped Path Counts by Length and Endpoint Type

This query extends path-length counts by also grouping endpoints by destination airport state.

openCypher
-- Q22. Route Counts by Length and Target State
MATCH (source:airport)
WHERE elementId(source) = 'airport[12339]'
MATCH p = (source)-[flights:flight*1..3]->(target:airport)
RETURN length(p) AS path_length,
       last(flights).DestState AS target_state,
       count(p) AS path_count,
       count(DISTINCT target) AS distinct_target_count
ORDER BY path_length, path_count DESC;

Equivalent query in GQL standard:

gql
MATCH p = (:Airport {airport_id: $sourceAirportId})-[:FLIGHT]->{1,3}(target:Airport)
RETURN length(p) AS path_length,
       target.state AS target_state,
       count(p) AS path_count,
       count(DISTINCT target) AS distinct_target_count
ORDER BY path_length, path_count DESC
LIMIT $limit;

Q23. Airports Serving Two Destination States

Subcategory: T4.7 Union and Intersection

This query finds origin airports that serve both of two selected destination states.

openCypher
-- Q23. Airports Serving Two Destination States
MATCH (origin:airport)-[to_a:flight]->(:airport)
MATCH (origin:airport)-[to_b:flight]->(:airport)
WHERE to_a.DestState = 'IN'
  AND to_b.DestState = 'IL'
RETURN DISTINCT to_a.OriginAirportID AS airport_id,
       to_a.Origin AS code,
       to_a.OriginCityName AS city,
       to_a.OriginState AS state
ORDER BY code;

Equivalent query in GQL standard:

gql
MATCH (origin:Airport)-[:FLIGHT]->(destA:Airport {state: $stateA})
MATCH (origin)-[:FLIGHT]->(destB:Airport {state: $stateB})
RETURN DISTINCT origin.airport_id AS airport_id, origin.code AS code, origin.city AS city, origin.state AS state
ORDER BY code
LIMIT $limit;

Q24. Top Airports by Degree Centrality

Subcategory: T5.1 Degree Centrality

This query ranks airports by the number of distinct airports they connect to in either direction.

openCypher
-- Q24. Top Airports by Degree Centrality
MATCH (airport:airport)-[:flight]-(neighbor:airport)
WITH airport, count(DISTINCT neighbor) AS degree
MATCH (airport)-[meta:flight]->(:airport)
RETURN DISTINCT meta.OriginAirportID AS airport_id,
       meta.Origin AS code,
       meta.OriginCityName AS city,
       degree
ORDER BY degree DESC;

Q25. Airport PageRank

Subcategory: T5.2 PageRank

This query runs PageRank on the airport flight graph using Neo4j Graph Data Science.

openCypher
-- Q25. Airport PageRank
CALL algo.paral.pagerank({
  labels: ['airport'],
  relationshipTypes: ['flight']
}) YIELD id, score
RETURN id AS airport_id, score
ORDER BY score DESC;
Note: Cannot be expressed in standard GQL because Graph Data Science procedures are not part of standard GQL.

Q26. Weakly Connected Airport Components

Subcategory: T5.3 Connected Components / Weakly Connected Components

This query finds weakly connected components after treating flight direction as undirected.

openCypher
-- Q26. Weakly Connected Airport Components
CALL algo.wcc({
  labels: ['airport'],
  relationshipTypes: ['flight']
}) YIELD id, componentId
RETURN componentId, count(*) AS component_size
ORDER BY component_size DESC;
Note: Cannot be expressed in standard GQL because Graph Data Science procedures are not part of standard GQL.

Q27. Same-day Connections

Subcategory: T2.2 Edge Filtering; T4.3 Ungrouped Edge Count and Property Statistics

This query finds same-day two-leg flight connections from LAX to LGA through an intermediate airport, keeps only connections with at least a scheduled one-hour layover, and returns the monthly count for the selected year.

openCypher
-- Q27. Same-day Connections
MATCH p = (a:airport)-[r1:flight]->(:airport)-[r2:flight]->(c:airport)
WHERE r1.FlightDate = r2.FlightDate
  AND r1.CRSArrTime + 100 <= r2.CRSDepTime
  AND elementId(a) = 'airport[12892]'
  AND elementId(c) = 'airport[12953]'
  AND r1.Year = 2020
WITH r1.Month AS month, count(p) AS path_count
RETURN month, path_count
ORDER BY month;

Equivalent query in GQL standard:

gql
MATCH p = (a:Airport)-[r1:FLIGHT]->(b:Airport)-[r2:FLIGHT]->(c:Airport)
WHERE r1.FlightDate = r2.FlightDate
  AND r1.CRSArrTime + 100 <= r2.CRSDepTime
  AND a.airport_id = 12892
  AND c.airport_id = 12953
  AND r1.Year = $year
WITH r1.Month AS month, count(p) AS path_count
RETURN month, path_count
ORDER BY month;

Q28. Missed Connecting Flights

Subcategory: T2.2 Edge Filtering; T4.3 Ungrouped Edge Count and Property Statistics

This query finds scheduled two-leg LAX-to-LGA connections through an intermediate airport, filters to cases where the first actual arrival is at or after the second actual departure, and returns the monthly missed-connection count for the selected year.

openCypher
-- Q28. Missed Connecting Flights
MATCH p = (a:airport)-[r1:flight]->(:airport)-[r2:flight]->(c:airport)
WHERE r1.FlightDate = r2.FlightDate
  AND r1.CRSArrTime + 100 <= r2.CRSDepTime
  AND r1.ArrTime >= r2.DepTime
  AND elementId(a) = 'airport[12892]'
  AND elementId(c) = 'airport[12953]'
  AND r1.Year = 2020
WITH r1.Month AS month, count(p) AS path_count
RETURN month, path_count
ORDER BY month;

Equivalent query in GQL standard:

gql
MATCH p = (a:Airport)-[r1:FLIGHT]->(b:Airport)-[r2:FLIGHT]->(c:Airport)
WHERE r1.FlightDate = r2.FlightDate
  AND r1.CRSArrTime + 100 <= r2.CRSDepTime
  AND r1.ArrTime >= r2.DepTime
  AND a.airport_id = 12892
  AND c.airport_id = 12953
  AND r1.Year = $year
WITH r1.Month AS month, count(p) AS path_count
RETURN month, path_count
ORDER BY month;

Q29. Top 10 Aircraft Turnarounds

Subcategory: T3.5 Two-hop Join Pattern; T4.3 Ungrouped Edge Count and Property Statistics

This query finds airports where the same aircraft arrives and then departs on the same date, counts distinct tail numbers that form valid scheduled turnarounds, and returns the top 10 airports for the selected date.

openCypher
-- Q29. Top 10 Aircraft Turnarounds
MATCH (:airport)-[r1:flight]->(n:airport)-[r2:flight]->(:airport)
WHERE r1.FlightDate = date('2020-12-25')
  AND r1.FlightDate = r2.FlightDate
  AND r1.Tail_Number = r2.Tail_Number
  AND r1.Tail_Number IS NOT NULL
  AND r1.Tail_Number <> ''
  AND r1.CRSArrTime < r2.CRSDepTime
WITH n, count(DISTINCT r1.Tail_Number) AS cnt
RETURN n, cnt
ORDER BY cnt DESC
LIMIT 10;

Equivalent query in GQL standard:

gql
MATCH (:Airport)-[r1:FLIGHT]->(n:Airport)-[r2:FLIGHT]->(:Airport)
WHERE r1.FlightDate = date($date)
  AND r1.FlightDate = r2.FlightDate
  AND r1.Tail_Number = r2.Tail_Number
  AND r1.Tail_Number IS NOT NULL
  AND r1.Tail_Number <> ""
  AND r1.CRSArrTime < r2.CRSDepTime
WITH n, count(DISTINCT r1.Tail_Number) AS cnt
RETURN n, cnt
ORDER BY cnt DESC
LIMIT 10;

Q30. Three-Leg Flight Sequences

Subcategory: T2.2 Edge Filtering; T4.3 Ungrouped Edge Count and Property Statistics

This query follows three consecutive flight legs on the same date for the same aircraft, enforces scheduled arrival-before-departure ordering between legs, and counts distinct tail numbers that complete the sequence.

openCypher
-- Q30. Three-Leg Flight Sequences
MATCH (:airport)-[r1:flight]->(:airport)-[r2:flight]->(:airport)-[r3:flight]->(:airport)
WHERE r1.FlightDate = date('2020-12-25')
  AND r1.FlightDate = r2.FlightDate
  AND r1.FlightDate = r3.FlightDate
  AND r1.Tail_Number = r2.Tail_Number
  AND r1.Tail_Number = r3.Tail_Number
  AND r1.Tail_Number IS NOT NULL
  AND r1.Tail_Number <> ''
  AND r1.CRSArrTime < r2.CRSDepTime
  AND r2.CRSArrTime < r3.CRSDepTime
RETURN count(DISTINCT r1.Tail_Number) AS cnt;

Equivalent query in GQL standard:

gql
MATCH (:Airport)-[r1:FLIGHT]->(:Airport)-[r2:FLIGHT]->(:Airport)-[r3:FLIGHT]->(:Airport)
WHERE r1.FlightDate = date($date)
  AND r1.FlightDate = r2.FlightDate
  AND r1.FlightDate = r3.FlightDate
  AND r1.Tail_Number = r2.Tail_Number
  AND r1.Tail_Number = r3.Tail_Number
  AND r1.Tail_Number IS NOT NULL
  AND r1.Tail_Number <> ""
  AND r1.CRSArrTime < r2.CRSDepTime
  AND r2.CRSArrTime < r3.CRSDepTime
RETURN count(DISTINCT r1.Tail_Number) AS cnt;