0

[I wasn't sure if this is the proper place to post SQL questions.] I have a public dataset of pharmaceutical prices. Any given drug gets a new price on some unpredictable day, and then the price remains that price until the next price change.

E.g.

drug            date          new_price
acetaminophen   2020-01-09    0.25
oxycontin       2020-01-10    1.40
valaxirin       2020-02-10    2.34
oranicin        2020-02-11    1.54
acetaminophen   2020-02-12    1.47

I have to do a variety of analytics e.g. "what was the price of acetaminophen on 2020-02-01?" Well that would of course be 0.25, but I need a way to figure that out in SQL. I have a variety of more complex queries, e.g. "list the ten cheapest drugs on a given date". So a solution I think needs to be generalized.

I realize that one possible solution would be to run a job that populates the database with prices for every day of the year, but I prefer not to solve the problem that way.

1
  • I finally figured out a way to google for this question and it appears that the use of last_value() may be the trick, however I'm still researching.
    – David
    Commented Sep 29, 2020 at 18:41

2 Answers 2

1

You will need to use a correlated subquery to get the price as of a given date:

SELECT
  price.drug
 ,price.date
 ,price.new_price
FROM
  DrugPrice price
WHERE
  price.Drug = 'acetaminophen'
    AND price.new_price =
      (
        SELECT
          MAX(date)
        FROM
          DrugPrice
        WHERE
          Drug = price.Drug
            AND date <= '2020-02-01'
      )

You will need a unique B-tree index on (Drug,Date) for that query to return consistent results in a reasonable amount of time.

For the ten cheapest type of query you can do something along the lines of:

SELECT
  drug
 ,date
 ,new_price
 ,price_order
FROM
  (
    SELECT
      price.drug
     ,price.date
     ,price.new_price
     ,ROW_NUMBER() OVER (PARTITION BY 1 ORDER BY price.new_price, price.drug) AS price_order
    FROM
      DrugPrice price
    WHERE
      price.Drug = 'acetaminophen'
        AND price.new_price =
          (
            SELECT
              MAX(date)
            FROM
              DrugPrice
            WHERE
              Drug = price.Drug
                AND date <= '2020-02-01'
          )
  ) x
WHERE
  price_order <= 10

This is going to require scanning the whole table, then ordering the rows, which can be expensive. If you can keep the underlying table small then this might not take too long and there may be no need to go further. But if the requirement is to return that data right away, you can create a table like this and run an update to check for changed records each day:

CREATE TABLE PriceSummary
(
  RankTypeCd   CHAR(1)      NOT NULL  --Ascending/descending/other metrics probably make this a FK to a reference table
 ,DrugRank     TINYINT      NOT NULL  --Don't need to rank everything, just maybe top 100
 ,AsOfDt       DATE         NOT NULL
 ,Drug         <whatever>   NOT NULL
 ,CONSTRAINT FK_PriceSummary_Summarizes_Drug FOREIGN KEY (Drug)  REFERENCES <Drug Table> (Drug)
 ,CONSTRAINT PK_PriceSummary PRIMARY KEY (RankTypeCd, DrunkRank, AsOfDt)
)

Then if you wanted to get the least expensive drugs (let's call that RankTypeCd = 'A' for ascending) you could:

SELECT
  PriceSummary.Drug
 ,PriceSummary.DrugRank
FROM
  PriceSummary PriceSummary
WHERE
  PriceSummary.RankTypeCd = 'A'
    AND PriceSummary.DrugRank <= 10
    AND PriceSummary.AsOfDt =
      (
        SELECT
          MAX(AsOfDt)
        FROM
          PriceSummary
        WHERE
          RankTypeCd = PriceSummary.RankTypeCd
            AND DrugRank = PriceSummary.DrugRank
            AND AsOfDt <= '2020-02-01'
      )
2
  • I'll try to clean this up provide a little more explanation later when I'm not at work.
    – user212533
    Commented Sep 29, 2020 at 20:19
  • Thank you! Appreciate all the effort.
    – David
    Commented Sep 30, 2020 at 17:38
1

This is basic sql For your second query there is only 1 drug at the given day

for the rest enter Postgre and what you are searching for ijn plain woirds and you usually get a result

The secnd query wrks like this. A subquery grabs with a window functions the last update date for everys drug.

When the originbal table is joined with the drug name and date changed you get the latest price Which will be sorted by price and grab only the 10 with the lowest prices Schema (PostgreSQL v12)

CREATE TABLE table1 (
  "drug" VARCHAR(13),
  "date" Date,
  "new_price" FLOAT
);

INSERT INTO table1
  ("drug", "date", "new_price")
VALUES
  ('acetaminophen', '2020-01-09', '0.25'),
  ('oxycontin', '2020-01-10', '1.40'),
  ('valaxirin', '2020-02-10', '2.34'),
  ('oranicin', '2020-02-11', '1.54'),
  ('acetaminophen', '2020-02-12', '1.47');

Query #1

SELECT
     "new_price"
FROM table1
WHERE "drug" = 'acetaminophen'
AND "date"  < '2020-02-01'
ORDER BY "date" DESC
LIMIT 1;

| new_price |
| --------- |
| 0.25      |

Query #2

SELECT
    t1."drug", 
    "new_price"
FROM table1 t1
INNER JOIN (SELECT 
            "drug", 
            MAX(date) OVER (PARTITION BY "drug"  ORDER BY "date")     AS max_date
        FROM table1
        WHERE 
    "date"  <= '2020-02-10') t2 ON t1."drug" = t2."drug" AND t1."date" = t2.max_date
ORDER BY "new_price" ASC
LIMIT 10;

| drug          | new_price |
| ------------- | --------- |
| acetaminophen | 0.25      |
| oxycontin     | 1.4       |
| valaxirin     | 2.34      |

Query #3

SELECT 
            "drug", 
            MAX(date) OVER (PARTITION BY "drug"  ORDER BY "date")     AS max_date
        FROM table1
        WHERE 
    "date"  <= '2020-02-10'
;

| drug          | max_date                 |
| ------------- | ------------------------ |
| acetaminophen | 2020-01-09T00:00:00.000Z |
| oxycontin     | 2020-01-10T00:00:00.000Z |
| valaxirin     | 2020-02-10T00:00:00.000Z |

View on DB Fiddle

4
  • You're missing DESC in query 1's ORDER BY, which will make it return the first price for acetaminophen, not the price as of 2020-02-01. Query 2 doesn't do what the question asks, it only includes items that have a price update on that date. There should be 3 rows (acetaminophen,0.25 / oxycontin,1.40 / valaxirin,2.34) returned from query 2; as written there is only one.
    – AdamKG
    Commented Sep 29, 2020 at 19:45
  • Thanks nbk - I believe Query 1 solves that problem, which I see now is easier than I had envisioned. For Query 2, I agree with Adam: every drug has a price on every day, that's the crux of my problem, your query is just looking at drugs that had a price change on that very date.
    – David
    Commented Sep 29, 2020 at 19:48
  • Here's an updated fiddle that fixes both issues: db-fiddle.com/f/6A7WEuEyxTigZWqo2iiarD/0
    – AdamKG
    Commented Sep 29, 2020 at 19:51
  • i updated my answer so that you get all the new prices from every drug that was inserted into the table
    – nbk
    Commented Sep 29, 2020 at 20:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.