How to return the correspondant date of a Maximum value - sql

I have a table MyTable like this:
Date Qty Price
2017-07-01 2 5.00
2017-07-08 3 4.00
2017-08-08 1 6.00
If I do a query to get the Maximum total cost like this:
SELECT max(Qty * Price) FROM MyTable
I get 12.00 which is correct.
How can I have the corresponding Date which is 2017-07-08?
In case of several equal Maximum values, I would like to have the smallest (oldest) date.

create table MyTable (date datetime, qty int, price int)
go
insert into MyTable(date,qty,price)
values
('2017-07-01', 2, 5.00),
('2017-07-08', 3, 4.00),
('2017-08-08', 1, 6.00);
with q as
(
select *, row_number() over (order by qty*price desc) rn
from MyTable
)
select date, qty, price
from q
where rn = 1;
outputs
date qty price
----------------------- ----------- -----------
2017-07-08 00:00:00.000 3 4
(1 row(s) affected)

If you need exactly one row with max Qty * Price, oldest day
SELECT TOP(1) *
FROM MyTable
ORDER BY Qty * Price DESC, [Date];
If you need all the rows with the same max Qty * Price value
SELECT TOP(1) WITH TIES *
FROM MyTable
ORDER BY Qty * Price DESC;

Just for the fun of it...
SELECT
[Date] = CAST(SUBSTRING(mv.MaxBin, 9, 8) AS DATE),
Qty = CAST(SUBSTRING(mv.MaxBin, 17, 4) AS INT),
Price = CAST(SUBSTRING(mv.MaxBin, 21, 8) AS MONEY)
FROM (
SELECT
MaxBin = MAX(CAST(td.Qty * td.Price AS BINARY(8)) + CAST(td.Date AS BINARY(8)) + CAST(td.Qty AS BINARY(4)) + CAST(td.Price AS BINARY(8)))
FROM
#TestData td
) mv;
Edit: How and why this works...
As you notes in the opening post, “SELECT max(Qty * Price) FROM MyTable” gets you the correct value but that doesn’t help you get the remaining columns from that row.
So the idea is to do the calculation, and then concatenate the other column values to the calculated results so that we can get all the desired columns using the previous logic [MAX(Qty * Price).
Casting things to fixed length binary values allows us to do just that. We know that Qty * Price = ToalPrice or in terms of data types INT * MONEY = MONEY, which is an 8 byte data type… So I know that I can cast any $ amount as a BINARY(8) and know 1) that I’ll always be able to convert it back to money later, 2) the binary value is EXACTLY 8 characters long and 3) I can sort the binary as I would the original value.
Same goes for Date which is a date data type which is 3 bytes long (Yea, I used 8 instead of 3… I’m used to the DATETIME type… my bad on that one)… and so on, with the other columns.
So, by concatenating the binary values together, I can table the MAX of those values, just like you did with max(Qty * Price) but now I have the entire row of data. Now all that has to be done if break the concatenated binary value back into its constituent parts (which is easy because we know exactly how long each section is) using the SUBSTRING function and the cast them back into their original data types.

Related

How to find an unsorted set of rows in a group by result only inside that particular group?

For exapmle :
enter image description here
Suppose table name is AMAN which is having more than 20 columns and more than 20000 rows
Now after this query:
SELECT id, line_no, date
FROM AMAN
where id in (select id from AMAN group by id);
Result:
We can see the groups with id 10000 and 20000 are having dates sorted.
I only need the id once when the dates corresponding to that are not sorted with respect to the line_no.
In this case I need 30000 as the desired output as the dates are in unsorted order with respect to the line_no.
Can anyone help me frame a similar query?
You don't mention the specific database so I'll provide a generic solution for it. The example runs in PostgreSQL.
You can use the LAG() window function to peek at a value in a previous row according to your desired sorting order.
For example:
select distinct id
from (
select *,
case when lag(d) over(partition by id order by line_no) > d
then 1
else 0
end as unsorted
from aman
) x
where unsorted = 1
Result (see running example at DB Fiddle):
id
-----
30000
SQL Script Data Set:
create table aman (id int, line_no int, d date);
insert into aman (id, line_no, d) values
(10000, 1, date '2019-05-13'),
(10000, 2, date '2020-05-13'),
(10000, 3, date '2021-05-13'),
(20000, 1, date '2015-06-14'),
(20000, 2, date '2017-06-15'),
(30000, 1, date '2024-05-13'),
(30000, 2, date '2020-05-13'),
(30000, 3, date '2022-05-13');
EDIT FOR ORACLE
The query works in Oracle with a slight modification, as shown below:
select distinct id
from (
select aman.*,
case when lag(d) over(partition by id order by line_no) > d
then 1
else 0
end as unsorted
from aman
) x
where unsorted = 1
See running example in Oracle.

I need a support for sql query

I have a table with 3 columns lower range,upper range, discount amount
I am passing a number and finding in which range it belongs to and retrieving its discount amount
But i am passing a number that not in this table in this case i need the last range discount amount from table
I need a sql query for the same
0-10 100
11-20 200
21-30 300
if i am passing 5 need to get 100
if i am passing 15 200 as result
but if i am passing 50 i need to get 300 as result
Ie. If the value that is passing not in the range need to get the highest ranges discount amount.
Plzz help. Mee
Try this. You can directly pass/use #value in the script as well.
DECLARE #Value INT
SET #Value = 35
SELECT SUM(DISCOUNT) Discount
FROM
(
SELECT
CASE
WHEN upper_range = (SELECT MAX(upper_range) FROM your_table) AND #Value > upper_range THEN DISCOUNT
WHEN #Value BETWEEN lower_range AND upper_range THEN DISCOUNT
ELSE 0
END
DISCOUNT
FROM your_table
) A
Output for value 35 is-
300
With UNION ALL:
select discount
from tablename
where x between lowerrange and upperrange
union all
select max(discount) from tablename
where not exists (
select 1 from tablename
where x between lowerrange and upperrange
)
If the 1st query does not return a result, then the value will be fetched by the 2nd query.
If the 1st query returns a result, then the 2nd will not return anything.
Applies to any major rdbms.

Alternative: Sql - SELECT rows until the sum of a row is a certain value

My question is very similar to my previous one posted here:
Sql - SELECT rows until the sum of a row is a certain value
To sum it up, I need to return the rows, until a certain sum is reached, but the difference this time, is that, I need to find the best fit for this sum, I mean, It doesn't have to be sequential. For example:
Let's say I have 5 unpaid receipts from customer 1:
Receipt_id: 1 | Amount: 110€
Receipt_id: 2 | Amount: 110€
Receipt_id: 3 | Amount: 130€
Receipt_id: 4 | Amount: 110€
Receipt_id: 5 | Amount: 190€
So, customer 1 ought to pay me 220€.
Now I need to select the receipts, until this 220€ sum is met and it might be in a straight order, like (receipt 1 + receipt 2) or not in a specific order, like (receipt 1 + receipt 4), any of these situations would be suitable.
I am using SQL Server 2016.
Any additional questions, feel free to ask.
Thanks in advance for all your help.
This query should solve it.
It is a quite dangerous query (containing a recursive CTE), so please be careful!
You can find some documentation here: https://www.essentialsql.com/recursive-ctes-explained/
WITH the_data as (
SELECT *
FROM (
VALUES (1, 1, 110),(1, 2,110),(1, 3,130),(1, 4,110),(1, 5,190),
(2, 1, 10),(2, 2,20),(2, 3,200),(2, 4,190)
) t (user_id, receipt_id, amount)
), permutation /* recursive used here */ as (
SELECT
user_id,
amount as sum_amount,
CAST(receipt_id as varchar(max)) as visited_receipt_id,
receipt_id as max_receipt_id,
1 as i
FROM the_data
WHERE amount > 0 -- remove empty amount
UNION ALL
SELECT
the_data.user_id,
sum_amount + amount as sum_amount,
CAST(concat(visited_receipt_id, ',', CAST(receipt_id as varchar))as varchar(max)) as visited_receipt_id,
receipt_id as max_receipt_id ,
i + 1
FROM the_data
JOIN permutation
ON the_data.user_id = permutation.user_id
WHERE i < 1000 -- max 1000 loops, means any permutation with less than 1000 different receipts
and receipt_id > max_receipt_id -- in order that sum in komutatif , we can check the sum in any unique order ( here we take the order of the reciept_id in fact we do not produce any duplicates )
-- AND sum_amount + amount <= 220 -- ignore everything that is bigger than the expected value (optional)
)
SELECT *
FROM permutation
WHERE sum_amount = 220
in order to select only one combination per user_id, replace the last three lines of the previous query by
SELECT *
FROM (
SELECT *, row_number() OVER (partition by user_id order by random() ) as r
FROM permutation
WHERE sum_amount = 220
) as t
WHERE r = 1
IF your target is to sum only 2 receipts in order to reach your value, this could be a solution:
DECLARE #TARGET INT = 220 --SET YOUR TARGET
, #DIFF INT
, #FIRSTVAL INT
SET #FIRSTVAL = (
SELECT TOP 1 AMOUNT
FROM myRECEIPTS
ORDER BY RECEIPT_ID ASC
)
SELECT TOP 1 *
FROM myRECEIPTS
WHERE AMOUNT = #TARGET - #FIRSTVAL
ORDER BY RECEIPT_ID ASC
this code will do it:
declare #sum1 int
declare #numrows int
set #numrows= 1
set #sum1 =0
while (#sum1 < 10)
begin
select top (#numrows) #sum1=sum(sum1) from receipts
set #numrows +=1
end
select top(#numrows) * from receipts

Piviot data in two tables?

I have two tables as follows:
Table 1
Columns - oppproductid, SKU, Price, Quantity, Date
Values - PR1, ABCSKU1, 1000,500, 10/2013
Table 2
Columns - opproductid, month_1, Month_2, Month_3, Month_4...Month_36
Values - PR1, 200, 100, NULL, 200...
The tables are 1-1. I need to get one row for each value in the month column that is not null for each record and calculate the date based on the months that are not null assuming that Month_1 is the date column in the primary table so the ideal result set based on the sample values is:
oppproductid SKU Price Quantity Date Deployment
PR1 ABCSKU1 1000 500 10/2013 200
PR1 ABCSKU1 1000 500 11/2013 100
PR1 ABCSKU1 1000 500 1/2014 200
NOTES:
Month_3 is NULL so 12/2013 does not yield results.
There are 36 months in the second table with the only requirement that one has to contain data.
Month_1 always equals the date on the first table.
Any help is appreciated.
Store your data using the proper data types. Dates should be date fields.
Normalise your data structures to make querying easier.
Try this
.
set dateformat dmy
select
t1.oppproductid,
t1.SKU,
t1.Price,
t1.Quantity,
dateadd(month, monthno-1, convert(date, '1/' + [date])),
deployment
from table1 t1
inner join
(
select *, convert(int,substring(mth,7,2)) as monthno from table2
unpivot (deployment for mth in (month_1,month_2,month_3,month_4...)) u
) u2
on t1.oppproductid = u2.opproductid

How to extract the balance qty

I hv a table like this:-
TABLE A
--------------------------------
Date | Serial | Qty
--------------------------------
20110101 ABC 1
20110102 ABC -1
20110105 ABC 1
20110106 ABC 1
20110108 ABC -1
I need to know what's the balance qty of ABC (based on FIFO) using SQL & the final output will look something like this:-
Date Serial Qty
20110106 ABC 1
Thanks.
You just want to pick out one record from the table? Like this?
SELECT * FROM your_table WHERE Date = '20110106' AND Serial = 'ABC'
I think you want something like
SELECT serial, max(date), 1 qty, sum( qty*size ) size
FROM tableA
GROUP BY serial
If you can have a negative final output, whatever database engine you're using should have something equivalent to Oracle's SIGN and ABS functions
SELECT serial, max(date), sign( sum( qty*size ) ) qty, abs( sum( qty*size ) ) size
FROM tableA
GROUP BY serial
Note that I'm guessing that you want the total remaining quantity as of the last day and that you get this by netting the positive quantities against the negative quantities for all the rows in the table for a particular SERIAL. I think this is close but I'm not sure how you're getting the DATE value
SELECT MAX(date) AS date, serial, ABS(SUM(qty * size)) AS size, SIGN(SUM(qty * size)) AS qty
FROM your_table
GROUP BY serial
something like this:
select Serial, sum(qty) as Total, Size from table_a group by Serial, Size
would give you:
TABLE A
Serial Total Size
ABC 0 23
ABC 0 22
ABC 1 24
you can, of course, add a where clause to restrict the query further:
select Serial, sum(qty) as Total, Size from table_a where Serial='ABC' and Size=24 group by Serial, Size
would give you information for Serial ABC with Size 24, only:
Serial Total Size
ABC 1 24
or you can filter out anything with Total of zero using a having clause:
select Serial, sum(qty) as Total, Size from table_a group by Serial, Size having sum(qty) <> 0
Serial Total Size
ABC 1 24