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.
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
Using Subquery,
Select *, (Select count(customerid)
from ##tmp t
where t.salesid <= s.salesid
and t.customerid = s.customerid)
from ##tmp s
Try this -
SELECT SalesId, CustomerId, Amount,
SalesNum = ROW_NUMBER() OVER (PARTITION BY CustomerId ORDER BY SalesId)
FROM YOURTABLE
Related
I have an sql table with the below data:
Id department Amount
1 Accounting 10000
2 Catering 5000
3 Cleaning 5000
I want to return the data as below:
Id department Amount
1 Accounting 10000
1 50%
2 Catering 5000
2 25%
3 Cleaning 5000
3 25%
This implies every records return a second record just below it and display the percentage of the total amount. I have tried to use a PIVOT table but still I cannot position
the second row just below the first related one.
Has anyone ever done something similar I need just some guidelines.
create table #T(Id int, Dept varchar(10),Amount int)
insert into #T
values(1,'Accounting',10000),(2,'Catering',5000),(3,'Cleaning',5000)
declare #Totll float = (Select sum(Amount) from #T)
Select *
from #T
union
select Id,Convert(varchar(50), (Amount/#Totll)*100)+'%',0
from #T
order by Id,Amount desc
Use a CTE to calculate the total of the amounts.
Then use UNION ALL for your table and the query which calculates the percentages:
with cte as (select sum(amount) sumamount from tablename)
select id, department, amount
from tablename
union all
select id, concat(100 * amount / (select sumamount from cte), '%'), null
from tablename
order by id, amount desc
See the demo.
Results:
> id | department | amount
> -: | :--------- | -----:
> 1 | Accounting | 10000
> 1 | 50% | null
> 2 | Catering | 5000
> 2 | 25% | null
> 3 | Cleaning | 5000
> 3 | 25% | null
I need to take the max cost of each tracking number (TN) and then sum those values grouped by the OrderNo.
Here's a table:
+----+-----+-------+
|TNo |cost| OrderNo|
+----+-----+-------+
| 1 | 5 | 12 |
| 1 | 4 | 12 |
| 2 | 6 | 12 |
| 2 | 3 | 12 |
| 3 | 3 | 15 |
| 4 | 2 | 15 |
| 4 | 3 | 15 |
+----+-----+-------+
Here's what I want my results to be:
+--------+-----+
| OrderNo| Sum |
+--------+-----+
| 12 | 11 | (6+5)
| 15 | 6 | (3+3)
+--------+-----+
This is what I have so far, but this sums the max but for all instances of the Tracking No. For example, in the above table, for Order# 12, it would sum 5+5+6+6. I only want to sum the max values (5+6).
SELECT ol.OrderNo, SUM(t.maxCost)
FROM (
SELECT
ol.TrackingNumber, MAX(ol.Cost) maxCost
FROM OzLink ol GROUP BY ol.TrackingNumber) t
JOIN OzLink ol ON ol.TrackingNumber=t.TrackingNumber
GROUP BY ol.OrderNo
**Also, I'm new to this work and asking questions on stackoverflow so feedback on how I asked this question would be appreciated!
you could do it like this:
SELECT ol.OrderNo, SUM(ol.maxCost)
FROM (
SELECT
ol.TrackingNumber, MAX(ol.Cost) maxCost, ol.OrderNo
FROM OzLink ol GROUP BY ol.TrackingNumber,ol.OrderNo) ol
GROUP BY ol.OrderNo
You can benefit from cte like below:
CREATE TABLE mytab
(
TNo INT,
Cost INT,
OrderNo INT
)
insert into mytab values (1,5,12)
insert into mytab values (1,4,12)
insert into mytab values (2,6,12)
insert into mytab values (2,3,12)
insert into mytab values (3,3,13)
insert into mytab values (4,2,13)
insert into mytab values (4,3,13)
;with cte (TNo,OrderNo,maxcost) as (
select TNo,OrderNo,Max(Cost) as maxcost
from mytab
group by TNo, OrderNo
)
select OrderNo,SUM(maxcost)
from cte
group by OrderNo
There is a few ways, like the answers below. But you can also use the below query, and create a Row number based on OrderNo and TN and Order by the Cost DESC in the Subquery and then only return the highest cost.
SELECT OrderNo,
SUM(Cost) As Cost
FROM
(
SELECT ROW_NUMBER() OVER(PARTITION BY OrderNo, TN ORDER BY Cost DESC) AS HighestCost,
Cost,
OrderNo,
TN
FROM TableName
) AS Data
WHERE HighestCost = 1
GROUP BY OrderNo
Same as another answer
declare #T TABLE (TNo INT, Cost INT, OrderNo INT);
insert into #T values (1,5,12), (1,4,12), (2,6,12), (2,3,12), (3,3,15), (4,2,15), (4,3,15);
select t.OrderNo, sum(t.cost)
from ( select OrderNo, cost
, ROW_NUMBER() over (partition by TNo, OrderNo order by cost desc) as rn
from #T
) t
where t.rn = 1
group by t.OrderNo;
OrderNo
----------- -----------
12 11
15 6
I have the following table:
OrderID | OldOrderID | Action | EntryDate | Source
1 | NULL | Insert | 2016-01-12| A
1 | NULL | Remove | 2016-01-13| A
2 | NULL | Insert | 2016-01-12| B
3 | NULL | Insert | 2016-01-12| C
4 | 3 | Insert | 2016-01-13| C
4 | NULL | Remove | 2016-01-14| C
I want to query all orders that are currently active orders - they dont have the action remove. Currently I do it with this query :
WITH Active AS
(
SELECT *, rn = ROW_NUMBER()
OVER (PARTITION BY OrderID,Source ORDER BY EntryDate DESC)
FROM Orders
)
SELECT *
FROM Active WHERE [Action] <> 'Remove' AND rn = 1;
The problem is that some orders get child orders (OrderID 3 gets a child OrderID 4) and if a child ever gets the Action Remove the query should also ignore the parent, but with the current query it dosent.
In short the current query gets me this result:
OrderID | OldOrderID | Action | EntryDate | Source
2 | NULL | Insert | 2016-01-12| B
3 | NULL | Insert | 2016-01-12| C
But I need this result:
OrderID | OldOrderID | Action | EntryDate | Source
2 | NULL | Insert | 2016-01-12| B
Is it possible to fix the query to get a result like this?
Try this:
;WITH CTE AS (
SELECT OrderID, OldOrderID, Action, EntryDate, Source,
COUNT(CASE WHEN Action = 'Remove' THEN 1 END)
OVER (PARTITION BY OrderID) AS IsRemoved,
ROW_NUMBER() OVER (PARTITION BY OrderID ORDER BY EntryDate) AS rn
FROM Orders
)
SELECT c1.*
FROM CTE AS c1
LEFT JOIN CTE AS c2 ON c1.OrderID = c2.OldOrderID AND c2.IsRemoved >= 1
WHERE c1.rn = 1 AND c1.IsRemoved = 0 AND c2.IsRemoved IS NULL
The above query uses COUNT() OVER() in order to count the number of occurrences of Action = 'Remove' within each OrderID partition. Hence, a value of IsRemoved that is equal to or greater than 1 identifies a 'removed' order.
I also asked the question on dba stackexchange and got the following answer, which works well.
Can anyone help me with query, I have table
vendorid, agreementid, sales
12001 1004 700
5291 1004 20576
7596 1004 1908
45 103 345
41 103 9087
what is the goal ?
when agreemtneid >1 then show me data when sales is the highest
vendorid agreementid sales
5291 1004 20576
41 103 9087
Any ideas ?
Thx
Well you could try using a CTE and ROW_NUMBER something like
;WITH Vals AS (
SELECT *, ROW_NUMBER() OVER(PARTITION BY AgreementID ORDER BY Sales DESC) RowID
FROM MyTable
WHERE AgreementID > 1
)
SELECT *
FROM Vals
WHERE RowID = 1
This will avoid you returning multiple records with the same sale.
If that was OK you could try something like
SELECT *
FROM MyTable mt INNER JOIN
(
SELECT AgreementID, MAX(Sales) MaxSales
FROM MyTable
WHERE AgreementID > 1
) MaxVals ON mt.AgreementID = MaxVals.AgreementID AND mt.Sales = MaxVals.MaxSales
SELECT TOP 1 WITH TIES *
FROM MyTable
ORDER BY DENSE_RANK() OVER(PARTITION BY agreementid ORDER BY SIGN (SIGN (agreementid - 2) + 1) * sales DESC)
Explanation
We break table MyTable into partitions by agreementid.
For each partition we construct a ranking or its rows.
If agreementid is greater than 1 ranking will be equal to ORDER BY sales DESC.
Otherwise ranking for every single row in partition will be the same: ORDER BY 0 DESC.
See how it looks like:
SELECT *
, SIGN (SIGN (agreementid - 2) + 1) * sales AS x
, DENSE_RANK() OVER(PARTITION BY agreementid ORDER BY SIGN (SIGN (agreementid - 2) + 1) * sales DESC) AS rnk
FROM MyTable
+----------+-------------+-------+-------+-----+
| vendorid | agreementid | sales | x | rnk |
+----------|-------------|-------+-------+-----+
| 0 | 0 | 3 | 0 | 1 |
| -1 | 0 | 7 | 0 | 1 |
| 0 | 1 | 3 | 0 | 1 |
| -1 | 1 | 7 | 0 | 1 |
| 41 | 103 | 9087 | 9087 | 1 |
| 45 | 103 | 345 | 345 | 2 |
| 5291 | 1004 | 20576 | 20576 | 1 |
| 7596 | 1004 | 1908 | 1908 | 2 |
| 12001 | 1004 | 700 | 700 | 3 |
+----------+-------------+-------+-------+-----+
Then using TOP 1 WITH TIES construction we leave only rows where rnk equals 1.
you can try like this.
SELECT TOP 1 sales FROM MyTable WHERE agreemtneid > 1 ORDER BY sales DESC
I really do not know the business logic behind agreement_id > 1. It looks to me you want the max sales (with ties) by agreement id regardless of vendor_id.
First, lets create a simple sample database.
-- Sample table
create table #sales
(
vendor_id int,
agreement_id int,
sales_amt money
);
-- Sample data
insert into #sales values
(12001, 1004, 700),
(5291, 1004, 20576),
(7596, 1004, 1908),
(45, 103, 345),
(41, 103, 9087);
Second, let's solve this problem using a common table expression to get a result set that has each row paired with the max sales by agreement id.
The select statement just applies the business logic to filter the data to get your answer.
-- CTE = max sales for each agreement id
;
with cte_sales as
(
select
vendor_id,
agreement_id,
sales_amt,
max(sales_amt) OVER(PARTITION BY agreement_id) AS max_sales
from
#sales
)
-- Filter by your business logic
select * from cte_sales where sales_amt = max_sales and agreement_id > 1;
The screen shot below shows the exact result you wanted.
I have a PresentationSlide table:
PresentationSlide
PresentationSlideId
PresentationId
Content
Order
and example rows:
+---------------------+----------------+---------+-------+
| PresentationSlideId | PresentationId | Content | Order |
+--------+------------+----------------+---------+-------+
| 123 | 3 | "bla" | 1 |
| 23 | 3 | "bla2" | 2 |
| 22 | 3 | "bla3" | 3 |
| 100 | 3 | "bla4" | 4 |
| 150 | 3 | "bla5" | 5 |
+---------------------+----------------+---------+-------+
I want to maintain arithmetic sequence of numbers (1,2,3,4,...) in the Order column after DELETE operation.
For example, if I delete third row (PresentationSlideId = 22), values in order column will be: (1,2,4,5) I want to update Order this way:
PresentationSlideId = 100: update order from 4 to 3
PresentationSlideId = 150: update order from 5 to 4
How is the most efficient way to do this kind of update? Is any way to do this with using only one UPDATE statement? I could do this using cursor and loop, but it doesn't seems efficient.
1) Order is a very poor name for a column, since it's an SQL Keyword
2) It would be a lot better if you could cope with gaps in the order (and possibly switch to using a float, so you can insert fractional values), because in your current model, every insert, update or delete is potentially going to affect the entire table. This doesn't scale well. Computing an order using ROW_NUMBER() during selects would generally be better.
3)
create table #PresentationSlide (
PresentationSlideID int not null,
PresentationId int not null,
Content varchar(10) not null,
[Order] int not null
)
insert into #PresentationSlide (PresentationSlideId , PresentationId , Content , [Order])
select 123,3,'bla',1 union all
select 23,3,'bla2',2 union all
select 22,3,'bla3',3 union all
select 100,3,'bla4',4 union all
select 150,3,'bla5',5
delete from #PresentationSlide where PresentationSlideId = 22
;With Reorder as (select PresentationSlideId,ROW_NUMBER() OVER (ORDER BY [Order]) as NewOrder from #PresentationSlide)
update ps set [Order] = NewOrder
from #PresentationSlide ps inner join Reorder r on ps.PresentationSlideId = r.PresentationSlideId
select * from #PresentationSlide order by [Order]
drop table #PresentationSlide
;with C as
(
select [Order],
row_number() over(order by [Order]) as rn
from PresentationSlide
)
update C set
[Order] = rn