3

I have a Sales table with the following data:

| SalesId | CustomerId | Amount |  
|---------|------------|--------|  
| 1       | 1          | 100    |  
| 2       | 2          | 75     |  
| 3       | 1          | 30     |  
| 4       | 3          | 49     |  
| 5       | 1          | 93     |  

I would like to insert a column into this table that tells us the number of times the customer has made a purchase. So it'll be like:

| SalesId | CustomerId | Amount | SalesNum |  
|---------|------------|--------|----------|  
| 1       | 1          | 100    | 1        |  
| 2       | 2          | 75     | 1        |  
| 3       | 1          | 30     | 2        |  
| 4       | 3          | 49     | 1        |  
| 5       | 1          | 93     | 3        |  

So I can see that in salesId = 5, that is the 3rd transaction for customerId = 1. How can I write such a query to insert / update such column? I am on MS SQL but I am also interested in the MYSQL solution should I need to do this there in the future.

Thank you.

ps. Apology for the table formatting. Couldn't figure out how to format it nicely.

0

3 Answers 3

2

You need ROW_NUMBER() to assign a sequence number. I'd strongly advise against storing this value though, since you will need to recalculate it with every update, instead, you may be best off creating a view if you need it regularly:

CREATE VIEW dbo.SalesWithRank
AS
    SELECT  SalesID,
            CustomerID,
            Amount,
            SalesNum = ROW_NUMBER() OVER(PARTITION BY CustomerID ORDER BY SalesID)
    FROM    Sales;
GO

SQL Server Example on SQL Fiddle

ROW_NUMBER() will not assign duplicates in the same group, e.g. if you were assigning the rows based on Amount and you have two sales for the same customer that are both 100, they will not have the same SalesNum, in the absence of any other ordering criteria in your ROW_NUMBER() function they will be randomly sorted. If you want Sales with the same amount to have the same SalesNum, then you need to use either RANK or DENSE_RANK. DENSE_RANK will have no gaps in the sequence, e.g 1, 1, 2, 2, 3, whereas RANK will start at the corresponding position, e.g. 1, 1, 3, 3, 5.

If you must do this as an update then you can use:

WITH CTE AS
(   SELECT  SalesID,
            CustomerID,
            Amount,
            SalesNum,
            NewSalesNum = ROW_NUMBER() OVER(PARTITION BY CustomerID ORDER BY SalesID)
    FROM    Sales
)
UPDATE  CTE
SET     SalesNum = NewSalesNum;

SQL Server Update Example on SQL Fiddle

MySQL Does not have ranking functions, so you need to use local variables to achieve a rank by keeping track of the value from the previous row. This is not allowed in views so you would just need to repeat this logic wherever you needed the row number:

SELECT  s.SalesID,
        s.Amount,
        @r:= CASE WHEN @c = s.CustomerID THEN @r + 1 ELSE 1 END AS SalesNum,
        @c:= CustomerID AS CustomerID
FROM    Sales AS s
        CROSS JOIN (SELECT @c:= 0, @r:= 0) AS var
ORDER BY s.CustomerID, s.SalesID;

The order by is critical here, which means in order to order the results without affecting the ranking you need to use a subquery:

SELECT  SalesID,
        Amount,
        CustomerID,
        SalesNum
FROM    (   SELECT  s.SalesID,
                    s.Amount,
                    @r:= CASE WHEN @c = s.CustomerID THEN @r + 1 ELSE 1 END AS SalesNum,
                    @c:= CustomerID AS CustomerID
            FROM    Sales AS s
                    CROSS JOIN (SELECT @c:= 0, @r:= 0) AS var
            ORDER BY s.CustomerID, s.SalesID
        ) AS s
ORDER BY s.SalesID;

MySQL Example on SQL Fiddle

Again, I would recommend against storing the value, but if you must in MySQL you would use:

UPDATE  Sales
        INNER JOIN
        (   SELECT  s.SalesID,
                    @r:= CASE WHEN @c = s.CustomerID THEN @r + 1 ELSE 1 END AS NewSalesNum,
                    @c:= CustomerID AS CustomerID
            FROM    Sales AS s
                    CROSS JOIN (SELECT @c:= 0, @r:= 0) AS var
            ORDER BY s.CustomerID, s.SalesID
        ) AS s2
            ON Sales.SalesID = s2.SalesID
SET     SalesNum = s2.NewSalesNum;

MySQL Update Example on SQL Fiddle

Sign up to request clarification or add additional context in comments.

Comments

2

Using Subquery,

   Select *, (Select count(customerid) 
               from ##tmp t
               where t.salesid <= s.salesid
               and      t.customerid = s.customerid)
from ##tmp s

1 Comment

+1 Slower, but should run on both SQL Server and MySQL.
1

Try this -

SELECT SalesId, CustomerId, Amount, 
SalesNum = ROW_NUMBER() OVER (PARTITION BY CustomerId ORDER BY SalesId) 
FROM YOURTABLE

1 Comment

To quote the question; "I am on MS SQL but I am also interested in the MYSQL solution should I need to do this there in the future."

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.