How can I take the sum of only the max values? - sql-server

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

Related

Get multiple rows from a subquery SQL

Basically I have 2 Tables, the first with the raw material amount (QT) for each serial number and the second one with how much raw material was spent (Qt_Added) on batch's production. Like this:
Table 1
+----------+------------+-----+
| Code_Raw | Serial_Raw | Qt |
+----------+------------+-----+
| 1 | 1 | 100 |
| 1 | 2 | 150 |
| 2 | 1 | 80 |
| 1 | 3 | 100 |
+----------+------------+-----+
And Table 2
+------------+----------+------------+----------+--+
| Code_Batch | Code_Raw | Serial_Raw | Qt_Added | |
+------------+----------+------------+----------+--+
| 1 | 1 | 1 | 80 | |
| 2 | 1 | 1 | 10 | |
| 3 | 1 | 2 | 150 | |
| 4 | 1 | 3 | 80 | |
+------------+----------+------------+----------+--+
I tried to do a query for a specific Code_Raw, show me how much left for each serial number, But worked only when there's a single serial_raw.
My query:
select *
from
(select
Serial_Raw,
(Select QT From Table_1 where Code_Raw = 1) - Sum(qt_added) as Total_Remaining
from
Table_2
where
Cod_Raw = 1
group by
Serial_Raw) e
where
Total_Remaining > 0
but it throws this error
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression
And I expect :
Serial_Raw Total_Remaining
-------------------------------
1 10
3 20
Is there a struct problem or another way to do this?
I'm using SQL Server 2014
Thanks guys
Try this:
DECLARE #tbl1 TABLE
( CodeRaw INT,
Serial_Raw INT,
Qty INT)
DECLARE #tbl2 TABLE
(
CodeBatch INT,
CodeRaw INT,
Serial_Raw INT,
QtyAdded INT)
INSERT INTO #tbl1 VALUES(1,1,100)
INSERT INTO #tbl1 VALUES(1,2,150)
INSERT INTO #tbl1 VALUES(2,1,80)
INSERT INTO #tbl1 VALUES(1,3,100)
INSERT INTO #tbl2 VALUES(1,1,1,80)
INSERT INTO #tbl2 VALUES(2,1,1,10)
INSERT INTO #tbl2 VALUES(3,1,2,150)
INSERT INTO #tbl2 VALUES(4,1,3,80)
--Inner table has the summary of the Quantity added with columns CodeRaw and SerialRaw. Outer table make join with inner table and just substruct with the Qty and Sum of Qty Added.
SELECT t2.Serial_Raw, t1.Qty - t2.QtyAdded AS Total_Remaining FROM #tbl1 t1
INNER JOIN (SELECT CodeRaw, Serial_Raw , SUM(QtyAdded) QtyAdded FROM #tbl2
GROUP BY CodeRaw, Serial_Raw) AS t2 ON t2.CodeRaw = t1.CodeRaw AND t1.Serial_Raw = t2.Serial_Raw
WHERE t1.Qty - t2.QtyAdded > 0
If I understood you right, this might be what you are after
declare #tbl1 table (CodeRaw INT, Serial_Raw INT, Qty INT)
declare #tbl2 table (CodeBatch INT, CodeRaw INT, Serial_Raw INT, QtyAdded INT)
insert into #tbl1 values (1,1,100), (1,2,150), (2,1,80), (1,3,100)
insert into #tbl2 values (1,1,1,80), (2,1,1,10), (3,1,2,150), (4,1,3,80)
select t2.Serial_Raw,
t3.Qty - sum(t2.QtyAdded) as Total_Remaining
from #tbl2 t2
inner join ( select t1.Serial_Raw,
t1.CodeRaw,
sum(t1.Qty) as Qty
from #tbl1 t1
group by t1.Serial_Raw, t1.CodeRaw
) t3
on t2.Serial_Raw = t3.Serial_Raw
and t2.CodeRaw = t3.CodeRaw
group by t2.Serial_Raw, t3.Qty
So in t2 we get all distinct Serial_Raw values, and sum their QtyAdded from the first table.
In t3 we get all Qty values from the 2nd table.
All we need to do is join them together and subtract
The result of this query is
Serial_Raw Total_Remaining
---------- ---------------
1 10
2 0
3 20

How to generate an External ID based ID's that has negative value on price

I have this Data set
InvoiceID CDamount companyname
1 2500 NASA
1 -2500 NASA
2 1600 Airjet
3 5000 Boeing
4 -600 EXEarth
5 8000 SpaceX
5 -8000 SpaceX
I want to be able to get that as shown below:
External ID CDamount companyname
1 2500 NASA
1-C -2500 NASA
2 1600 Airjet
3 5000 Boeing
4 -600 EXEarth
5 8000 SpaceX
5-C -8000 SpaceX
I cannot use CASE WHEN CDamount < 0 THEN InvoiceID + '-' + 'C' ELSE InvoiceID END AS "External ID" because some of other companies have negative amount as well that do not fall under this category.
I was wondering how can I say IF InvoiceID is Duplicated AND CDAmount is Negative then Create a new External ID?
Is this something possible?
Below you can create the sample data
Create Table #Incident (
InvoiceID int,
CDamount int,
Companyname Nvarchar(255))
insert into #Incident Values (1,2500,'NASA')
insert into #Incident Values (1,-2500,'NASA')
insert into #Incident Values (2,1600,'Airjet')
insert into #Incident Values (3, 5000, 'Boeing')
insert into #Incident Values (4, -600, 'ExEarth')
insert into #Incident Values (5,8000,'SpaceX')
insert into #Incident Values (5, -8000, 'SpaceX')
Here is What I used but as I mentioned since ID number 4 has negative value as well I get "-C" for it which I do not want to.
Select CASE WHEN T1.CDamount < 0
THEN CAST(T1.InvoiceID AS nvarchar (255)) + '-' + 'C'
ELSE CAST(T1.InvoiceID AS nvarchar (255))
END AS ExternalID,
T1.Companyname
from #Incident AS T1
So I got this based on my knowledge of SQL and that works for my case.
Not sure if it is an smart way to go with but can be a good start for someone who is struggling with a Scenario like this:
;With CTE1 AS (
SELECT Count(*) AS Duplicate, T1.InvoiceID
From #Incident AS T1
Group by T1.InvoiceID
),
Main AS (
Select CASE WHEN T1.CDamount < 0 AND T2.Duplicate > 1
THEN CAST(T1.InvoiceID AS nvarchar (255)) + '-' + 'C'
ELSE CAST(T1.InvoiceID AS nvarchar (255))
END AS ExternalID,
T1.InvoiceID AS count,
T1.CDamount,
T1.Companyname
from #Incident AS T1
Join CTE1 AS T2 ON T1.InvoiceID = T2.InvoiceID
)
SELECT * FROM Main
Alternative solution without CTE, using ROW_NUMBER() function.
SELECT
CASE WHEN CDAmount < 0 AND RowID > 1
THEN InvoiceID + '-C'
ELSE InvoiceID
END AS ExternalID
, CDAmount
, CompanyName
FROM
(
SELECT
CAST(InvoiceID AS NVARCHAR(255)) AS InvoiceID
, CDAmount
, CompanyName
, ROW_NUMBER() OVER (PARTITION BY InvoiceID ORDER BY CompanyName) AS RowID
FROM
#Incident
) AS SourceTable
The trick is using ROW_NUMBER() function to generate a sequence which resets when InvoiceID changes. Here's the subquery and its result. Use CASE statement when CDAmount is negative and RowID greater than 1.
SELECT
CAST(InvoiceID AS NVARCHAR(255)) AS InvoiceID
, CDAmount
, CompanyName
, ROW_NUMBER() OVER (PARTITION BY InvoiceID ORDER BY CompanyName) AS RowID
FROM
#Incident
Subquery result:
+-----------+----------+-------------+-------+
| InvoiceID | CDAmount | CompanyName | RowID |
+-----------+----------+-------------+-------+
| 1 | 2500 | NASA | 1 |
| 1 | -2500 | NASA | 2 |
| 2 | 1600 | Airjet | 1 |
| 3 | 5000 | Boeing | 1 |
| 4 | -600 | ExEarth | 1 |
| 5 | 8000 | SpaceX | 1 |
| 5 | -8000 | SpaceX | 2 |
+-----------+----------+-------------+-------+

SQL Query construct

I have three tables. I want to get data from all those tables and put it in a virtual table. i am using SQL Server 2012.
Sorry if my format or tags are wrong because I m getting error Stack overflow requires external javascrip from another source domain, which is blocked of failed to load.
Booking Table
BookingId | date
======================
2 | 7/1/2017 (MM/dd/yyyy)
3 | 7/1/2017
BookingCost Table
Id | bookinId | Cost
==========================
1 | 2 | 2000
2 | 3 | 4000
Expense Table
Id | ExpenseCost | Date
======================
1 | 1400 | 7/2/2017 (MM/dd/yyyy)
2 | 1422 | 7/1/2017
3 | 4000 | 6/3/2017
I want to get Monthly result like following Table.
Date | Expense | Bookings
===================================
jan/2017 | 0 | 0
feb/2017 | 0 | 0
. | . | .
. | . | .
. | . | .
jun/2017 | 4000 | 0
jul/2017 | 2822 | 6000
. | . | .
. | . | .
. | . | .
How is something like this (assuming your dates are DATE types and not VARCHAR - otherwise you could convert them).
SELECT COALESCE(EXPENSE.MONTH, BOOKINGS.MONTH) [Date], EXPENSE.Cost Expense, BOOKINGS.Cost Bookings
FROM (
SELECT DATEADD(DD,1-DAY([date]),[date]) MONTH, SUM(Cost) Cost
FROM Booking
INNER JOIN BookingCost
ON Booking.BookingID = BookingCost.BookingID
GROUP BY DATEADD(DD,1-DAY([date]),[date])
) BOOKINGS
FULL JOIN (
SELECT DATEADD(DD,1-DAY([date]),[date]) MONTH, SUM(ExpenseCost) Cost
FROM Expense
GROUP BY DATEADD(DD,1-DAY([date]),[date])
) EXPENSE
ON EXPENSE.MONTH = BOOKINGS.MONTH
ORDER BY 1
To also get the 0 counts, you could left join the totals to a tally table which has all the months for the year.
The sql is using FORMAT to transform the Date
For example:
;WITH MONTHS AS
(
select
[Year], [Month],
format(datefromparts([Year],[Month],1),'MMM/yyyy') as [MonthYear]
from (values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)) m([Month])
cross join (values (2017)) y([Year])
)
select
m.[MonthYear] as [Date],
coalesce(e.TotalExpense,0) as Expense,
coalesce(bc.TotalCost,0) as Bookings
from MONTHS m
left join (
select
datepart(year,[Date]) as [Year],
datepart(month,[Date]) as [Month],
sum(ExpenseCost) as TotalExpense
from Expense
where datepart(year,[Date]) in (select distinct [Year] from MONTHS)
group by datepart(year,[Date]), datepart(month,[Date])
)e on (e.[Year] = m.[Year] and e.[Month] = m.[Month])
left join (
select
datepart(year,b.[date]) as [Year],
datepart(month,b.[date]) as [Month],
sum(c.Cost) as TotalCost
from Booking b
join BookingCost c on c.BookingId = b.BookingId
where datepart(year,b.[date]) in (select distinct [Year] from MONTHS)
group by datepart(year,b.[date]), datepart(month,b.[date])
) bc
on (bc.[Year] = m.[Year] and bc.[Month] = m.[Month])
order by m.[Year], m.[Month];
Test data I used
declare #Booking table (BookingId int, [date] date);
insert into #Booking (BookingId,[date]) values (2,'2017-07-01'),(3,'2017-07-01');
declare #BookingCost table (Id int, BookingId int, Cost int);
insert into #BookingCost (Id, BookingId, Cost) values (1,2,2000),(2,3,4000);
declare #Expense table (Id int, ExpenseCost int, [Date] date);
insert into #Expense (Id, ExpenseCost, [Date]) values
(1,1400,'2017-07-02'),(2,1422,'2017-07-01'),(3,4000,'2017-06-03');

SQL Server: how to create sequence number column

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

Efficient way to update column with arithmetic sequence of numbers after delete operation

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

Resources