Sum all rows where column is different in SQL? - sql

I have a simple table.
The relevant fields are:Return Value, and Return Number
So this table shows me all items that were returned, what return number this return is, and what was the value of all items in this return.
So an example table can look something like this
Line # | Item Number | Quantity Returned | Return Value | Return Number | Cust Order #
1 789 1 $40 123 456
1 780 1 $40 123 456
1 780 1 $20 124 456
I just want it to sum up all return values by different return numbers. So for example, there are two rows with return number 123 and one row with return number 124. So it should take one of the 123 and sum it to 124, giving my $60
I've tried
SUM((rh.Total_Value-rh.Freight_Charges)) OVER (PARTITION BY rh.Customer_Purchase_Order_Number) as Total_Returned_Value
SUM((rh.Total_Value-rh.Freight_Charges)) OVER (PARTITION BY rh.Return_Number) as Total_Returned_Value
SUM((rh.Total_Value-rh.Freight_Charges)) OVER (PARTITION BY rh.Return_Number Order by rh.Customer_Purchase_Order_Number) as Total_Returned_Value
SUM((rh.Total_Value-rh.Freight_Charges)) OVER (PARTITION BY rh.Customer_Purchase_Order_Number Order by rh.Return_Number) as Total_Returned_Value
None of these seem to work and I feel that I don't have a great grasp on order by and partition by
This is my full code
select rh.Return_Number,
rd.Odet_Line_Number, rd.Item_Number, rd.Color_Code, rd.Quantity_Returned,
(rh.Total_Value-rh.Freight_Charges)as Returned_Value, rh.Remarks,
SUM((rh.Total_Value-rh.Freight_Charges)) OVER (PARTITION BY /*rh.Return_Number Order by*/ rh.Customer_Purchase_Order_Number) as Total_Returned_Value
from
[JMNYC-AMTDB].[AMTPLUS].[dbo].Returns_Header rh (nolock)
LEFT JOIN
[JMNYC-AMTDB].[AMTPLUS].[dbo].Returns_Detail rd (nolock) on rd.Return_Number = Rh.Return_number
WHERE rh.Customer_Purchase_Order_Number = #Shopify

You probably got multiple detail rows per header resulting in duplicate header data. If you want to sum by unique return number do the calculation on the header first in a CTE and join the result to the detail, like this
with rh as
( select -- assuming the rh.Return_Number is unique
rh.Return_Number,
(rh.Total_Value-rh.Freight_Charges)as Returned_Value,
rh.Remarks,
SUM((rh.Total_Value-rh.Freight_Charges))
OVER (PARTITION BY rh.Customer_Purchase_Order_Number) as Total_Returned_Value
-- don't know if this is the PARTITION you want, maybe none
from
[JMNYC-AMTDB].[AMTPLUS].[dbo].Returns_Header rh (nolock)
)
select rh.Return_Number,
rd.Odet_Line_Number, rd.Item_Number, rd.Color_Code, rd.Quantity_Returned,
rh.Returned_Value, rh.Remarks,
rh.Total_Returned_Value
from
rh
LEFT JOIN
[JMNYC-AMTDB].[AMTPLUS].[dbo].Returns_Detail rd (nolock) on rd.Return_Number = Rh.Return_number
WHERE rh.Customer_Purchase_Order_Number = #Shopify

Related

How to write a query that returns rows that do not have a specific value in a dynamic list of future dates?

I am working on a problem trying to accurately identify customers that have left a business service permanently. Currently, there is an incorrect definition of these customers defined as 'churners' in operation within the business.
'Churners' may re-enter the platform in the following months, and I don't want to include those specific accounts in my result set.
These accounts appear similar to the following table:
ID
Month_End_Date
Activity_Flag
123
31/07/22
Customer
123
30/06/22
Customer
123
31/05/22
Customer
123
30/04/22
Customer
123
31/03/22
Customer
123
28/02/22
Order
123
31/01/22
Churn
Whereas an actual 'churner' should appear as the following:
ID
Month_End_Date
Activity_Flag
321
31/07/22
x
321
30/06/22
x
321
31/05/22
x
321
30/04/22
x
321
31/03/22
x
321
28/02/22
x
321
31/01/22
Churn
And in some cases, these customers DO NOT populate any further rows (unique by Month_End_Date) within the dataset:
ID
Month_End_Date
Activity_Flag
321
31/01/22
Churn
So my question is, how can I write an SQL query that will show me the accounts that meet the parameter of Activity_Flag = 'Churn' and DO NOT HAVE any further Activity_Flags within the table itself?
Hope this is enough information,
I have tried to filter these applicable customers within the dataset by understanding unique monthly churners and their following behaviour (activity_type) but to no avail.
You can use the Row_Number function with the Partition By clause and Order by Month_End_Date descending to get only records where the last record for the given ID has an Activity_Flag of Churn:
select x.ID
from
(
select ID, Activity_Flag,
Row_Number() Over (Partition By ID
Order By Month_End_Date desc) as rn
from yourtable
) x
where x.rn = 1 and x.Activity_Flag = 'Churn'

Query to list every line item but only list total once

We have a sales report that pulls data from multiple tables and my query shows correct data except orders that have multiple line items, i.e., the Total from the Orders table is listed on every line item row.
How can we list the Order Total only once on the row that has the smallest line item ID (for that order) but still list every line item row? Thanks!
Data Structure:
Orders Table:
Order_ID
Total
Line Items Table:
ID
Order_ID
Line_Item_Price
Line_Item_Qty
Result should be:
Order_ID Total Line_Item_Price Line_item_Qty Line_Item_ID
---------- ------- ----------------- --------------- --------------
10001 200 100 2 32001
10002 150 150 1 32002
10003 210 55 1 32003
10003 30 2 32004
10003 95 1 32005
10004 125 125 1 32006
This should be done in the application not in SQL.
But you can do that using window functions
select o.order_id,
case row_number() over (partition by o.order_id order by line_item_id)
when 1 then o.total
end as total,
li.line_item_price,
li.line_item_qty,
li.line_item_id
from orders o
join line_item li on o.order_id = li.order_id
order by o.order_id, li.line_item_id;
row_number() assigns a unique row number for each line item for every order. When the number is 1 the total is displayed, otherwise it's not.
In a relational database there is no such thing as "the first row" unless you specify an order by - in this case the "first row" is the line item with the smallest line_item_id
Online example: http://rextester.com/TQOIX50171
Unrelated, but: storing the total in the orders table is not a terribly good idea. In a normalized design you shouldn't store information that can easily be derived from existing data.
What this does is get the orders with their items and ranks them. So that the smallest item_id is rank 1 for that order, and the latest is the last rank.
ROW_NUMBER() is the function that gives the index of the rows in the output of the query (row 1 = 1, row 2 = 2). Then we can combine this with OVER (PARTITION BY), which means get the row numbers within a certain window, a partition. In this case we want to number the rows for the windows of Order_IDs. We use ORDER BY alongside to say how we order the rows within the window
When we have this table, we can then write a query on it to show the total only on the rows where the item_rank = 1
WITH rank_items_for_orders AS (
SELECT
Order_ID,
Line_Item_Price,
Line_Item_Qty,
Line_Item_ID,
Total,
ROW_NUMBER()
OVER (PARTITION BY Order_ID, ORDER BY Line_Item_ID ASC)
AS order_the_items_IDs
FROM orders o
LEFT JOIN line_items li ON o.order_id = li.order_id
ORDER BY Order_ID ASC)
SELECT
Order_ID,
Line_Item_Price,
Line_Item_Qty,
Line_Item_ID,
CASE WHEN order_the_items_IDs = 1
THEN Total ELSE NULL END
AS Total
FROM
rank_items_for_orders

Join tables based on dates with check

I have two tables in PostgreSQL:
Demans_for_parts:
demandid partid demanddate quantity
40 125 01.01.17 10
41 125 05.01.17 30
42 123 20.06.17 10
Orders_for_parts:
orderid partid orderdate quantity
1 125 07.01.17 15
54 125 10.06.17 25
14 122 05.01.17 30
Basicly Demans_for_parts says what to buy and Orders_for_parts says what we bought. We can buy parts which do not list on Demans_for_parts.
I need a report which shows me all parts in Demans_for_parts and how many weeks past since the most recent matching row in Orders_for_parts. note quantity field is irrelevent here,
The expected result is (if more than one row per part show the oldes):
partid demanddate weeks_since_recent_order
125 01.01.17 2 (last order is on 10.06.17)
123 20.06.17 Unhandled
I think the tricky part is getting one row per table. But that is easy using distinct on. Then you need to calculate the months. You can use age() for this purpose:
select dp.partid, dp.date,
(extract(year from age(dp.date, op.date))*12 +
extract(month from age(dp.date, op.date))
) as months
from (select distinct on (dp.partid) dp.*
from demans_for_parts dp
order by dp.partid, dp.date desc
) dp left join
(select distinct on (op.partid) op.*
from Orders_for_parts op
order by op.partid, op.date desc
) op
on dp.partid = op.partid;
smth like?
with o as (
select distinct partid, max(orderdate) over (partition by partid)
from Orders_for_parts
)
, p as (
select distinct partid, min(demanddate) over (partition by partid)
from Demans_for_parts
)
select p.partid, min as demanddate, date_part('day',o.max - p.min)/7
from p
left outer join o on (p.partid = o.partid)
;

Sql query to calculate first occurrence of a sales order not fulfilled by stock

I have two tables:
Sales Orders (SO ) with fields:Part, Due_Date, Qty
Part with fields Part and Stock.
I an trying to write a query that will produce the first occurrence ( by date - SO.Due_Date) that a sales order (SO.Qty) cannot be fulfilled by the stock.
This is easy if there is no stock i.e. Part.Stock=0 or if there is only one sales order for the part (SO.Qty > Part.Stock)
If there are multiple sales orders I only want the first one shown e.g.
Part.Part = Box , Part.Stock = 250
SO.Part | SO.Due_Date | SO.Qty
Box | 26/10/2014 | 100
Box | 27/10/2014 | 100
Box | 28/10/2014 | 100 * Return this row
Box | 29/10/2014 | 100
I think I need a sub query or need to use CTE but I can't work it out unless I use a loop. The tables have thousands of parts and sales orders and I am trying to run this query as quickly as possible.
Many thanks for your help
I assume this is a learning exercise, as no real business would work this way.
Anyway, here is a query to do what you want:
select *
from sales_order as so1
where due_date =
(select min(due_date)
from sales_order as so2
inner join part as p on p.part = so2.part
where so1.part = so2.part
and stock < (
select sum(quantity)
from sales_order as so3
where so3.due_date <= so2.due_date
and so3.part = so2.part
)
)
Which I have put into a working fiddle here: http://sqlfiddle.com/#!2/bd8ab5/1
There are some assumptions such as one order per date, but I believe it answers the question.
A query that uses a self join to calculate the running quantity total for each row and selects the row with the smallest due date having a running total greater than p.stock
select so.part, so.due_date, so.quantity
from sales_order so
join part p on p.part = so.part
join sales_order so2 on so2.part = so.part
and so2.due_date <= so.due_date
where p.part = 'Box'
group by so.part, so.due_date, so.quantity
having sum(so2.quantity) > max(p.stock)
order by so.due_date limit 1

How to increment a value in SQL based on a unique key

Apologies in advance if some of the trigger solutions already cover this but I can't get them to work for my scenario.
I have a table of over 50,000 rows, all of which have an ID, with roughly 5000 distinct ID values. There could be 100 rows with an instrumentID = 1 and 50 with an instrumentID = 2 within the table etc but they will have slightly different column entries. So I could write a
SELECT * from tbl WHERE instrumentID = 1
and have it return 100 rows (I know this is easy stuff but just to be clear)
What I need to do is form an incrementing value for each time a instrument ID is found, so I've tried stuff like this:
IntIndex INT IDENTITY(1,1),
dDateStart DATE,
IntInstrumentID INT,
IntIndex1 AS IntInstrumentID + IntIndex,
at the table create step.
However, I need the IntIndex1 to increment when an instrumentID is found, irrespective of where the record is found in the table so that it effectively would provide a count of the records just by looking at the last IntIndex1 value alone. Rather than what the above does which is increment on all of the rows of the table irrespective of the instrumentID so you would get 5001,4002,4003 etc.
An example would be: for intInstruments 5000 and 4000
intInstrumentID | IntIndex1
--------- ------------------
5000 | 5001
5000 | 5002
4000 | 4001
5000 | 5003
4000 | 4002
The reason I need to do this is because I need to join two tables based on these values (a start and end date for each instrumentID). I have tried GROUP BY etc but this can't work in both tables and the JOIN then doesn't work.
Many thanks
I'm not entirely sure I understand your problem, but if you just need IntIndex1 to join to, could you just join to the following query, rather than trying to actually keep the calculated value in the database:
SELECT *,
intInstrumentID + RANK() OVER(PARTITION BY intInstrumentID ORDER BY dDateStart ASC) AS IntIndex1
FROM tbl
Edit: If I understand your comment correctly (which is not certain!), then presumably, you know that your end date and start date tables have the exact same number of rows, which leads to a one to one mapping between them based on thir respective end dates within instrument id?
If that's the case then maybe this join is what you are looking for:
SELECT SD.intInstrumentID, SD.dDateStart, ED.dEndDate
FROM
(
SELECT intInstrumentID,
dStartDate,
RANK() OVER(PARTITION BY intInstrumentID ORDER BY dDateStart ASC) AS IntIndex1
FROM tblStartDate
) SD
JOIN
(
SELECT intInstrumentID,
dEndDate,
RANK() OVER(PARTITION BY intInstrumentID ORDER BY dEndDate ASC) AS IntIndex1
FROM tblStartDate
) ED
ON SD.intInstrumentID = ED.intInstrumentID
AND SD.IntIndex1 = ED.IntIndex1
If not, please will you post some example data for both tables and the expected results?