getting difference between two invoices by ranking and subtracting one from the other - sql

Trying to grab difference in invoices
Attempted using cte's for ranks 1 and 2, but they have a subquery in them and cant be done!
the second query looks the same, but with rank=2.
select *
from (
SELECT i.id, i.subtotal/100 as subtotal, i.created_at, i.paid_at
,RANK() OVER (PARTITION BY i.subscription_id ORDER BY i.created_at DESC) AS Rank
From Invoices i
) as r
where r.rank = 1
order by r.created_at desc;

Following the path that you are on (using row_number()/rank()), you can use conditional aggregation. Assuming you want the difference of the subtotal, then:
select sum(case when seqnum = 1 then subtotal
else - subtotal
end) as difference
from (select i.*, i.subtotal/100 as subtotal,
row_number() over (partition by i.subscription_id order by i.created_at desc) as seqnum
from Invoices i
) i
where seqnum in (1, 2)
order by r.created_at desc;

Related

SQL get top 3 values / bottom 3 values with group by and sum

I am working on a restaurant management system. There I have two tables
order_details(orderId,dishId,createdAt)
dishes(id,name,imageUrl)
My customer wants to see a report top 3 selling items / least selling 3 items by the month
For the moment I did something like this
SELECT
*
FROM
(SELECT
SUM(qty) AS qty,
order_details.dishId,
MONTHNAME(order_details.createdAt) AS mon,
dishes.name,
dishes.imageUrl
FROM
rms.order_details
INNER JOIN dishes ON order_details.dishId = dishes.id
GROUP BY order_details.dishId , MONTHNAME(order_details.createdAt)) t
ORDER BY t.qty
This gives me all the dishes sold count order by qty.
I have to manually filter max 3 records and reject the rest. There should be a SQL way of doing this. How do I do this in SQL?
You would use row_number() for this purpose. You don't specify the database you are using, so I am guessing at the appropriate date functions. I also assume that you mean a month within a year, so you need to take the year into account as well:
SELECT ym.*
FROM (SELECT YEAR(od.CreatedAt) as yyyy,
MONTH(od.createdAt) as mm,
SUM(qty) AS qty,
od.dishId, d.name, d.imageUrl,
ROW_NUMBER() OVER (PARTITION BY YEAR(od.CreatedAt), MONTH(od.createdAt) ORDER BY SUM(qty) DESC) as seqnum_desc,
ROW_NUMBER() OVER (PARTITION BY YEAR(od.CreatedAt), MONTH(od.createdAt) ORDER BY SUM(qty) DESC) as seqnum_asc
FROM rms.order_details od INNER JOIN
dishes d
ON od.dishId = d.id
GROUP BY YEAR(od.CreatedAt), MONTH(od.CreatedAt), od.dishId
) ym
WHERE seqnum_asc <= 3 OR
seqnum_desc <= 3;
Using the above info i used i combination of group by, order by and limit
as shown below. I hope this is what you are looking for
SELECT
t.qty,
t.dishId,
t.month,
d.name,
d.mageUrl
from
(
SELECT
od.dishId,
count(od.dishId) AS 'qty',
date_format(od.createdAt,'%Y-%m') as 'month'
FROM
rms.order_details od
group by date_format(od.createdAt,'%Y-%m'),od.dishId
order by qty desc
limit 3) t
join rms.dishes d on (t.dishId = d.id)

How to get the validity date range of a price from individual daily prices in SQL

I have some prices for the month of January.
Date,Price
1,100
2,100
3,115
4,120
5,120
6,100
7,100
8,120
9,120
10,120
Now, the o/p I need is a non-overlapping date range for each price.
price,from,To
100,1,2
115,3,3
120,4,5
100,6,7
120,8,10
I need to do this using SQL only.
For now, if I simply group by and take min and max dates, I get the below, which is an overlapping range:
price,from,to
100,1,7
115,3,3
120,4,10
This is a gaps-and-islands problem. The simplest solution is the difference of row numbers:
select price, min(date), max(date)
from (select t.*,
row_number() over (order by date) as seqnum,
row_number() over (partition by price, order by date) as seqnum2
from t
) t
group by price, (seqnum - seqnum2)
order by min(date);
Why this works is a little hard to explain. But if you look at the results of the subquery, you will see how the adjacent rows are identified by the difference in the two values.
SELECT Lag.price,Lag.[date] AS [From], MIN(Lead.[date]-Lag.[date])+Lag.[date] AS [to]
FROM
(
SELECT [date],[Price]
FROM
(
SELECT [date],[Price],LAG(Price) OVER (ORDER BY DATE,Price) AS LagID FROM #table1 A
)B
WHERE CASE WHEN Price <> ISNULL(LagID,1) THEN 1 ELSE 0 END = 1
)Lag
JOIN
(
SELECT [date],[Price]
FROM
(
SELECT [date],Price,LEAD(Price) OVER (ORDER BY DATE,Price) AS LeadID FROM [#table1] A
)B
WHERE CASE WHEN Price <> ISNULL(LeadID,1) THEN 1 ELSE 0 END = 1
)Lead
ON Lag.[Price] = Lead.[Price]
WHERE Lead.[date]-Lag.[date] >= 0
GROUP BY Lag.[date],Lag.[price]
ORDER BY Lag.[date]
Another method using ROWS UNBOUNDED PRECEDING
SELECT price, MIN([date]) AS [from], [end_date] AS [To]
FROM
(
SELECT *, MIN([abc]) OVER (ORDER BY DATE DESC ROWS UNBOUNDED PRECEDING ) end_date
FROM
(
SELECT *, CASE WHEN price = next_price THEN NULL ELSE DATE END AS abc
FROM
(
SELECT a.* , b.[date] AS next_date, b.price AS next_price
FROM #table1 a
LEFT JOIN #table1 b
ON a.[date] = b.[date]-1
)AA
)BB
)CC
GROUP BY price, end_date

Incremental count of duplicates

The following query displays duplicates in a table with the qty alias showing the total count, eg if there are five duplicates then all five will have the same qty = 5.
select s.*, t.*
from [Migrate].[dbo].[Table1] s
join (
select [date] as d1, [product] as h1, count(*) as qty
from [Migrate].[dbo].[Table1]
group by [date], [product]
having count(*) > 1
) t on s.[date] = t.[d1] and s.[product] = t.[h1]
ORDER BY s.[product], s.[date], s.[id]
Is it possible to amend the count(*) as qty to show an incremental count so that five duplicates would display 1,2,3,4,5?
The answer to your question is row_number(). How you use it is rather unclear, because you provide no guidance, such as sample data or desired results. Hence this answer is rather general:
select s.*, t.*,
row_number() over (partition by s.product order by s.date) as seqnum
from [Migrate].[dbo].[Table1] s join
(select [date] as d1, [product] as h1, count(*) as qty
from [Migrate].[dbo].[Table1]
group by [date], [product]
having count(*) > 1
) t
on s.[date] = t.[d1] and s.[product] = t.[h1]
order by s.[product], s.[date], s.[id];
The speculation is that the duplicates are by product. This enumerates them by date. Some combination of the partition by and group by is almost certainly what you need.

Oracle SQL, calculating next order qty based on order history

I am using the following script to get the order history of a particular manufacturing order;
select ds.status, ds.catnr, ds.part_no, ds.print_type, ds.nr_discs, ds.qty, ds.ship_date
from
(select 'Open Order' status, gb.catnr, gb.part_no, decode(gb.tec_criteria,'XX','SCREEN','OF','OFFSET','PI','OFFSET','MC','OFFSET') print_type, sp.nrunits nr_discs, sum(gb.or_menge_fd) qty, min(trunc(gb.shd_date)) ship_date
from gps_beweg gb, oes_customer oc, scm_packtyp sp
where gb.part_no = 'A0101628358-VV92-1900'
and gb.uebergabe_oes = '1'
and gb.pwerk_disc = 'W'
and gb.cunr = oc.cunr
and gb.packtyp = sp.packtyp
group by gb.cunr, oc.name, gb.part_no, sp.nrunits, gb.tec_criteria, gb.catnr, gb.prodtyp, gb.packtyp
UNION ALL
select unique 'Shipped Order' status,
null catnr, null part_no, null print_type, null nr_discs,
(select sum(ds1.planqty) from oes_delsegview ds1 where ds.ordnr = ds1.ordnr and ds.catnr = ds1.catnr and ds.prodtyp = ds1.prodtyp and ds.packtyp = ds1.packtyp) qty,
(select trunc(max(ds1.gps_planshpdate)) from oes_delsegview ds1 where ds.ordnr = ds1.ordnr and ds.catnr = ds1.catnr and ds.prodtyp = ds1.prodtyp and ds.packtyp = ds1.packtyp) ship_date
from part_description pd1, oes_delsegview ds
where pd1.part_no =
(select max(gb.part_no)
from gps_beweg gb
where gb.part_no = 'A0101628358-VV92-1900'
and gb.uebergabe_oes = '1'
and gb.pwerk_disc = 'W')
and pd1.catnr = ds.catnr
and pd1.prodtyp = ds.prodtyp
and pd1.packtyp = ds.packtyp
and ds.ord_o_status in ('7','9')
order by status, ship_date desc) ds
where rownum <=5
The result for this script looks like this...
I would like to use the data in the QTY and SHIP_DATE column to predict the next qty and date. I can do this in Excel using the TREND function. Is there a way of doing this in SQL? Will it be in line with the REGR_SLOPE function (I can't seem to get my head around how this works!?!).
As mentioned, as far as I know Oracle's SQL has no built-in trend functions to help you here. What you could do, though, is to to play around with analytic functions and come up with some algorithm.
ship_date - LAG(ship_date) OVER (ORDER BY ship_date) gives you the days between last and current order for instance. You'd have to weight these values, however, say multiply them with ROW_NUMBER() OVER (ORDER BY ship_date). Then divide to get the value to add to MAX(ship_date).
Here is the according query. A little hard to read and understand, but still an option in my opinion. The query retrieves all rows from yours plus a trend date and quantity for each row. So you can see what would have been forcast at some time and what shipment really followed. The last row gives you the current forecast.
select
status, catnr, part_no, print_type, nr_discs, qty, ship_date,
round(qty + sum(qty_diff_weighted) over (order by rn) /
(sum(rn) over (order by rn) - 1)) as trend_qty,
round(ship_date + sum(date_diff_weighted) over (order by rn) /
(sum(rn) over (order by rn) - 1)) as trend_date
from
(
select
status, catnr, part_no, print_type, nr_discs, qty, ship_date,
row_number() over (order by ship_date) *
(qty - lag(qty) over (order by ship_date)) as qty_diff_weighted,
row_number() over (order by ship_date) *
(ship_date - lag(ship_date) over (order by ship_date)) as date_diff_weighted,
row_number() over (order by ship_date) as rn
from (your query)
)
order by ship_date;
Result:
STATUS CATNR ... QTY SHIP_DATE TREND_QTY TREND_DATE
Shipped Order 500 06.06.2014
Shipped Order 500 17.11.2014 500 30.04.2015
Shipped Order 300 21.09.2015 180 28.05.2016
Shipped Order 300 16.08.2016 233 29.05.2017
Open Order PPD168 300 24.03.2017 257 11.12.2017
This shows the technique. You may come up with a completely different algorithm that suits you better, of course.

oracle window functions

Could someone help me out with this query:
SELECT SUM(summa), name,
TO_CHAR(invoice_date, 'YYYY/mm')
OVER (PARTITON EXTRACT(MONTH FROM i.invoice_date, c.name)
FROM invoice i, customer c
WHERE i.customer_id = c.id
AND months_between(sysdate, invoice_date) = 3
AND rownum < 11 GROUP BY invoice_date, name
ORDER BY SUM(SUMMA) DESC;
Supposed to get the first ten rows from last three months, grouped by month and ordered by sum.
Thanks.
First, use proper explicit join syntax. Second, you need row_number():
SELECT t.*
FROM (SELECT SUM(summa) as sumsumma, name,
TO_CHAR(invoice_date, 'YYYY/mm') as yyyymm,
ROW_NUMBER() OVER (PARTITION BY TO_CHAR(invoice_date, 'YYYY/mm')
ORDER BY SUM(summa) DESC
) as seqnum
FROM invoice i JOIN
customer c
ON i.customer_id = c.id
WHERE months_between(sysdate, invoice_date) = 3
GROUP BY invoice_date, name
) t
WHERE seqnum <= 10
ORDER BY sumsumma DESC;