Skip to content

alokshandilya/sql50-leetcode

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

16 Commits
Β 
Β 

Repository files navigation

Top SQL 50 Study Plan (LeetCode)

This repository contains solutions to the Top SQL 50 Study Plan from LeetCode using PostgreSQL. Each problem is linked to its corresponding LeetCode page, and solutions are provided in PostgreSQL syntax.

Order of Execution

Select

SELECT
    product_id
FROM
    Products
WHERE
    low_fats = 'Y'
    AND
    recyclable = 'Y';
SELECT
    name
FROM
    Customer
WHERE
    referee_id <> 2
    OR
    referee_id IS NULL;

NOTE: The <>, != operator is used to compare two values and return TRUE if they are not equal, and FALSE otherwise.

SELECT
    NULL <> 2
        AS result;

-- output: NULL
SELECT
    name,
    population,
    area
FROM
    World
WHERE
    area >= 3000000
    OR
    population >= 25000000;
SELECT
    DISTINCT
        author_id AS id
FROM
    Views
WHERE
    author_id = viewer_id
ORDER BY
    id;  -- or 1 (1st selected column) author_id/id)
SELECT
    tweet_id
FROM
    Tweets
WHERE
    LENGTH(content) > 15;

PostgreSQL string functions docs
The LENGTH() function returns the length of a string in characters.

 length (text) β†’ integer
     Returns the number of characters in the string.
     length('jose') β†’ 4

Basic Joins

JOINs are used to combine rows from two or more tables based on a related column between them.

SELECT
    e.name,
    u.unique_id
FROM
    Employees e
LEFT JOIN
    EmployeeUNI u
        ON e.id = u.id;
SELECT
    p.product_name,
    s.year,
    s.price
FROM
    Sales s
NATURAL JOIN
    Product p
SELECT
    v.customer_id,
    COUNT(*)
        AS count_no_trans
FROM
    Visits v
LEFT JOIN
    Transactions t
        ON t.visit_id = v.visit_id
WHERE
    t.transaction_id
        IS NULL
GROUP BY
    v.customer_id;
SELECT
    w2.id
FROM
    Weather w1
INNER JOIN
    Weather w2
    ON w2.temperature > w1.temperature
WHERE
    w2.recordDate::date - w1.recordDate::date = 1
SELECT
    a2.machine_id,
    ROUND(AVG(a2.timestamp - a1.timestamp)::numeric, 3)  -- or, decimal
        AS processing_time
FROM
    Activity a1
INNER JOIN
    Activity a2
    ON a2.machine_id = a1.machine_id
WHERE
    a2.activity_type = 'end'
    AND a1.activity_type = 'start'
GROUP BY
    a2.machine_id

The :: operator is used to cast a value to a specific data type.

SELECT
    e.name,
    b.bonus
FROM
    Employee e
LEFT JOIN
    Bonus b
        ON b.empId = e.empId
WHERE
    b.bonus < 1000
    OR
    b.bonus IS NULL;
SELECT
    st.student_id,
    st.student_name,
    su.subject_name,
    COUNT(e.subject_name)
        AS attended_exams
FROM
    Students st
CROSS JOIN
    Subjects su
LEFT JOIN
    Examinations e
    ON
        e.student_id = st.student_id
        AND
        e.subject_name = su.subject_name
GROUP BY
    st.student_id,
    st.student_name,
    su.subject_name
ORDER BY
    st.student_id,
    su.subject_name;
-- using subquery
SELECT
    name
FROM
    Employee
WHERE
    id IN (
        SELECT
            managerId
        FROM
            Employee
        WHERE
            managerId
                IS NOT NULL
        GROUP BY
            managerId
        HAVING
            COUNT(*) >= 5
    )
-- using join
SELECT
    e1.name
FROM
    Employee e1
JOIN
    Employee e2
    ON
        e2.managerId = e1.id
WHERE
    e2.managerId
        IS NOT NULL
GROUP BY
    e1.name, e1.id  -- allow multiple name
HAVING
    COUNT(*) >= 5;
SELECT
    s.user_id,
    ROUND(
        COUNT(*) FILTER (WHERE c.action='confirmed')::decimal / COUNT(*),
        2
    ) AS confirmation_rate
FROM
    Signups s
LEFT JOIN
    Confirmations c
    ON
        c.user_id = s.user_id
GROUP BY
    s.user_id

Basic Aggregate Functions

SELECT
    *
FROM
    Cinema
WHERE
    id % 2 = 1
    AND
    description <> 'boring'
ORDER BY
    rating DESC;
SELECT
    p.product_id,
    COALESCE(
        ROUND(
            SUM(u.units * p.price)::decimal / SUM(u.units), 2
        ), 0
    ) AS average_price
FROM
    Prices p
LEFT JOIN
    UnitsSold u
    ON u.product_id = p.product_id
    AND
    u.purchase_date BETWEEN p.start_date AND p.end_date
GROUP BY
    p.product_id
SELECT
    p.project_id,
    ROUND(
        AVG(e.experience_years), 2
    ) AS average_years
FROM
    Project p
NATURAL JOIN
    Employee e
GROUP BY
    p.project_id
SELECT
    r.contest_id,
    ROUND(
        COUNT(r.user_id)::decimal / (SELECT COUNT(*) FROM Users) * 100.0,
        2
    ) AS percentage
FROM
    Users u
NATURAL JOIN
    Register r
GROUP BY
    r.contest_id
ORDER BY
    percentage DESC,
    contest_id
SELECT
    query_name,
    ROUND(
        AVG(rating::decimal / position), 2
    ) AS quality,
    ROUND(
        (COUNT(*) FILTER(WHERE rating < 3))::decimal * 100 / COUNT(*), 2
    ) AS poor_query_percentage
FROM
    Queries
GROUP BY
    query_name
SELECT
    to_char(trans_date, 'YYYY-MM') AS month,
    country,
    COUNT(*) AS trans_count,
    COUNT(*) FILTER(WHERE state='approved') AS approved_count,
    COALESCE(
        SUM(amount), 0
    ) AS trans_total_amount,
    COALESCE(
        SUM(amount) FILTER(WHERE state='approved'), 0
    ) AS approved_total_amount
FROM
    Transactions
GROUP BY
    month,
    country

1. to_char Function

  • to format dates, timestamps, and intervals into human-readable strings. It's incredibly versatile, allowing you to customize the output precisely.
to_char(expression, format_string)
  • expression: The date, timestamp, or interval value you want to format.
  • format_string: A template string specifying how the output should look. This is where you control the format.

Format String Elements (Common Examples):

  • YYYY: Four-digit year (e.g., 2023)
  • YY: Two-digit year (e.g., 23)
  • MM: Two-digit month (e.g., 01 for January, 12 for December)
  • Month: Full month name (e.g., January)
  • Mon: Abbreviated month name (e.g., Jan)
  • DD: Two-digit day of the month (e.g., 01, 31)
  • Day: Full day of the week (e.g., Monday)
  • Dy: Abbreviated day of the week (e.g., Mon)
  • HH: Hour (24-hour clock)
  • HH12: Hour (12-hour clock)
  • MI: Minutes
  • SS: Seconds
  • MS: Milliseconds
  • US: Microseconds
  • TZ: Time zone abbreviation (e.g., IST, PST)
  • CC: Century

Examples of to_char:

SELECT to_char(now(), 'YYYY-MM-DD HH24:MI:SS');  -- Current date and time
SELECT to_char(date '2024-03-15', 'Month DD, YYYY'); -- March 15, 2024
SELECT to_char(timestamp '2023-10-27 10:30:00', 'Day, Mon DD YYYY HH12:MI AM'); -- Friday, Oct 27 2023 10:30 AM
SELECT to_char(interval '2 years 3 months', 'YY years MM months'); -- 02 years 03 months

2. date_part Function

The date_part function extracts a specific component (like year, month, day, hour, etc.) from a date, timestamp, or interval.

date_part('field', source)
  • field: The part of the date/time you want to extract (e.g., 'year', 'month', 'day', 'hour', 'minute', 'second', 'dow' (day of week), 'doy' (day of year), 'week', 'quarter').
  • source: The date, timestamp, or interval value.

Examples of date_part:

SELECT date_part('year', date '2024-03-15');  -- 2024
SELECT date_part('month', timestamp '2023-10-27 10:30:00'); -- 10
SELECT date_part('day', date '2024-03-15');   -- 15
SELECT date_part('dow', date '2024-03-15');  -- 5 (Friday, where Sunday = 0)
SELECT date_part('hour', timestamp '2023-10-27 10:30:00'); -- 10
SELECT date_part('week', date '2024-03-15'); -- 11 (week number of the year)
SELECT date_part('quarter', date '2024-05-15'); -- 2

Key Differences and When to Use Which:

  • to_char: Use this when you need to format a date/time/interval into a specific string representation. You have fine-grained control over the output format. Think of it as converting a date/time into a textual representation.
  • date_part: Use this when you need to extract a specific component (a number) from a date/time/interval. It gives you a numeric value representing that part. Think of it as getting a numerical value representing a date/time part.
WITH first_order_cte AS (
    SELECT
        customer_id,
        MIN(order_date) AS first_order
    FROM
        Delivery
    GROUP BY
        customer_id
),

immediate_order_cte AS (
    SELECT
        d.customer_id,
        MIN(d.customer_pref_delivery_date) AS immediate_delivery
    FROM
        Delivery d
    JOIN
        first_order_cte fo
        ON fo.customer_id = d.customer_id
    GROUP BY
        d.customer_id
)

SELECT
    ROUND(
        COUNT(*) FILTER(WHERE f.first_order = i.immediate_delivery)::decimal * 100
        /
        COUNT(f.customer_id), 2
    ) AS immediate_percentage
FROM
    first_order_cte f
JOIN
    immediate_order_cte i
    ON
        i.customer_id = f.customer_id
SELECT
    ROUND(
        AVG((order_date = customer_pref_delivery_date)::int) * 100,
        2
    ) AS immediate_percentage
FROM
    Delivery
WHERE (customer_id, order_date) IN (
    SELECT
        customer_id,
        MIN(order_date)
    FROM
        Delivery
    GROUP BY
        customer_id
)

Common Table Expressions (CTEs):

  • CTEs are temporary result sets that you can reference within a SELECT, INSERT, UPDATE, or DELETE statement.
  • They help break down complex queries into smaller, more manageable parts.

Syntax:

WITH cte_name (column1, column2, ...) AS (
    subquery
)
SELECT columns
FROM cte_name
WHERE condition;
  • cte_name: The name of the Common Table Expression.
  • (column1, column2, ...): An optional list of column names for the CTE.
  • subquery: The subquery that defines the CTE.
  • SELECT columns: The main query that references the CTE.
  • WHERE condition: An optional condition to filter the results.
-- Step 1: Find the first login date for each player

WITH FirstLogin AS (
    SELECT
        player_id,
        MIN(event_date)
            AS first_login
    FROM
        Activity
    GROUP BY
        player_id
),

-- Step 2: Check if the player logged in the day after their first login

NextDayLogin AS (
    SELECT
        a.player_id
    FROM
        FirstLogin f
    JOIN
        Activity a
        ON
            a.player_id = f.player_id
        AND
            a.event_date = f.first_login + INTERVAL '1 DAY'
)

-- Step 3: Calculate the fraction of players who logged in the next day

SELECT
    ROUND(
        COUNT(*) FILTER(WHERE f.player_id = n.player_id)::decimal / COUNT(*),
        2
    ) AS fraction
FROM
    FirstLogin f
LEFT JOIN
    NextDayLogin n
    ON
        f.player_id = n.player_id

In PostgreSQL, the INTERVAL data type is used to store and manipulate periods of time. It's an extremely useful feature when you need to perform date and time calculations.

Here's what you should know about PostgreSQL intervals:

Basic Syntax

INTERVAL '1 day'
INTERVAL '2 hours 30 minutes'
INTERVAL '1 year 2 months 3 days'

Common Interval Units

PostgreSQL supports these interval units:

  • year, month, week, day
  • hour, minute, second, millisecond, microsecond
  • Abbreviations like yr, mon, h, min, sec are also recognized

Date/Time Arithmetic

You can use intervals for date calculations:

-- Add 3 days to a date
SELECT date '2025-03-03' + INTERVAL '3 days';

-- Subtract 1 month from a timestamp
SELECT now() - INTERVAL '1 month';

-- Calculate time difference
SELECT age(timestamp '2025-03-03', timestamp '2025-01-01');

Extracting Parts

You can extract specific parts from an interval:

SELECT EXTRACT(day FROM INTERVAL '1 year 2 months 3 days 4 hours');
-- Returns: 3

Interval Constants

PostgreSQL allows shorthand notation:

'1 day'::interval
'2 hours'::interval

Practical Example

-- Find all records created in the last 7 days
SELECT * FROM records
WHERE created_at > now() - INTERVAL '7 days';

Functions

PostgreSQL offers functions for interval manipulation:

-- Justify hours (convert hours to days when possible)
SELECT justify_hours(INTERVAL '30 hours'); -- Returns '1 day 6 hours'

-- Justify days (convert days to months when possible)
SELECT justify_days(INTERVAL '40 days'); -- Returns '1 month 10 days'

Intervals are particularly valuable in reporting, data analysis, and applications that need to track time differences or schedule recurring events.

Sorting and Grouping

SELECT
    teacher_id,
    COUNT(DISTINCT subject_id)
        AS cnt
FROM
    Teacher
GROUP BY
    teacher_id
SELECT
    activity_date
        AS day,
    COUNT(DISTINCT user_id)
        AS active_users
FROM
    Activity
WHERE
    activity_date
        BETWEEN
            '2019-07-27'::date - INTERVAL '29 days'
            AND
            '2019-07-27'::date
GROUP BY
    activity_date

Contributing

If you'd like to contribute, feel free to fork this repository and submit a pull request with your solutions or improvements. Make sure to follow the same format for consistency.

About

SQL 50 leetcode | solutions | postgres

Topics

Resources

Stars

Watchers

Forks