Home > Software design >  Identifying users with a downward trend SQL
Identifying users with a downward trend SQL

Time:10-21

Trying to identify a list of customers who's quantity decreases from their previous purchase.

In this example we see that with each new purchase Mary's quantity decreases over time. However, while Bob shows a decline, he would not yield in the results because on 9/19 he purchased 8 quantities which is greater than his previous purchase of 5.

I'm trying to figure out a query for this for the life of me I can't seem to get it together

Customer    PurchaseDate    Quantity
Bob         9/1/2021        10
Bob         9/10/2021       6
Bob         9/18/2021       5
Bob         9/19/2021       8
Mary        9/1/2021        10
Mary        9/10/2021       6
Mary        9/18/2021       5
Mary        9/19/2021       3
Frank       9/1/2021        5
Lucus       9/1/2021        5
Lucus       9/10/2021       6
Lucus       9/18/2021       10

End results should be

Customer
Mary

CodePudding user response:

This is a bit tricky, and to find results that are steadily increasing or decreasing you would probably want to use the MATCH_RECOGNIZE clause, which MySQL doesn't (yet) support. This way you can define a pattern whereby each qty is less than than the previous value. Additionally, you could probably do this with a recursive cte, but that would be outside of my abilities.

Here is what I came up with, with the caveat that it only compares the first and last values:

WITH
    tbl (customer, purchasedate, quantity) AS (
SELECT * FROM VALUES 
    ('Bob',         '9/1/2021',        10),
    ('Bob',         '9/10/2021',       6),
    ('Bob',         '9/18/2021',       5),
    ('Bob',         '9/19/2021',       8),
    ('Mary',        '9/1/2021',        10),
    ('Mary',        '9/10/2021',       6),
    ('Mary',        '9/18/2021',       5),
    ('Mary',        '9/19/2021',       3),
    ('Frank',       '9/1/2021',        5),
    ('Lucus',       '9/1/2021',        5),
    ('Lucus',       '9/10/2021',       6),
    ('Lucus',       '9/18/2021',       10)
)

SELECT
    DISTINCT customer
FROM
    tbl
QUALIFY
      FIRST_VALUE(quantity) OVER (partition BY customer ORDER BY purchasedate)
    > LAST_VALUE(quantity)  OVER (PARTITION BY customer ORDER BY purchasedate)

Which gives:

CUSTOMER
Bob
Mary

Or, to get strictly decreasing with a known max, you can chain them all together which gets pretty ugly:

WITH
    tbl (customer, purchasedate, quantity) AS (
SELECT * FROM VALUES 
    ('Bob',         '9/1/2021',        10),
    ('Bob',         '9/10/2021',       6),
    ('Bob',         '9/18/2021',       5),
    ('Bob',         '9/19/2021',       8),
    ('Mary',        '9/1/2021',        10),
    ('Mary',        '9/10/2021',       6),
    ('Mary',        '9/18/2021',       5),
    ('Mary',        '9/19/2021',       3),
    ('Frank',       '9/1/2021',        5),
    ('Lucus',       '9/1/2021',        5),
    ('Lucus',       '9/10/2021',       6),
    ('Lucus',       '9/18/2021',       10)
)

SELECT
    DISTINCT customer
FROM
    tbl
    qualify 
        (NTH_VALUE(quantity, 1) OVER (partition BY customer ORDER BY purchasedate) >= NTH_VALUE(quantity, 2) OVER (partition BY customer ORDER BY purchasedate))
        and ((NTH_VALUE(quantity, 2) OVER (partition BY customer ORDER BY purchasedate) >= NTH_VALUE(quantity, 3) OVER (partition BY customer ORDER BY purchasedate)) or (NTH_VALUE(quantity, 3) OVER (partition BY customer ORDER BY purchasedate) is null))
        and ((NTH_VALUE(quantity,3) OVER (partition BY customer ORDER BY purchasedate) >= NTH_VALUE(quantity, 4) OVER (partition BY customer ORDER BY purchasedate)) or (NTH_VALUE(quantity, 4) OVER (partition BY customer ORDER BY purchasedate) is null))

Which gives:

CUSTOMER
Mary

Though for an unknown amount I would think match_recognize would be the best solution (or you could add in some recursion or a custom function).

CodePudding user response:

SELECT Customer
FROM ( SELECT CASE WHEN Customer = @customer AND Quantity > @quantity
                   THEN 1
                   ELSE 0 
                   END AS increase_detected,
              @customer := Customer Customer,
              PurchaseDate,
              @quantity := Quantity Quantity
       FROM test
       CROSS JOIN ( SELECT @customer := NULL, @quantity := NULL ) init_variables
       ORDER BY Customer, PurchaseDate
     ) subquery
GROUP BY Customer
HAVING NOT SUM(increase_detected);

https://dbfiddle.uk/?rdbms=mysql_5.6&fiddle=68b75b0df7fe4b383896e78db0caa569

  • Related