SQL Select Record Using Range of Row Value - sql

I have the following database records
ID Weight Cost
1 3 1.00
2 10 2.00
3 14 3.00
What I want is to grab the cost for a given weight. So if my Weight is 5, the cost would be 2.00.
The clarify, the ranges would be:
Cost 1.00, weight 0-3
Cost 2.00, weight 4-10
Cost 3.00, weight 11-14
I'm not sure what SQL should I use to get the row range, but using columns, I can use SELECT Cost FROM table1 WHERE column1 BETWEEN x AND y.
Any suggestions / comments are welcome.

Is this what you're after?
http://sqlfiddle.com/#!6/b5307/20
declare #Weight int = 5
select top 1 cost
from weightings
where weight >= #Weight
order by weight

There are several ways to do this, perhaps the easiest is to generate a sequence of weights/costs given the intervals present in the table. To do this we can use a numbers/tally-table (in this case I use the master..spt_values table which has a suitable range of numbers).
So given a table like:
declare #t table (ID int, Weight int, Cost decimal(10,2))
insert #t values (1, 3, 1.00),(2, 10, 2.00),(3, 14, 3.00)
We can define two versions of a query (wrapped in a common table expression for convenience). The first version uses the lag()window function and requires a version of SQL Server newer than 2012:
;with costs1 (weight, cost) as (
select number, cost
from master..spt_values
inner join (
select isnull(LAG(weight) over (order by id)+1,0) low, weight high, cost from #t
) t on number <= t.high and number >= t.low
where type= 'P'
)
select cost from costs1 where weight = 5;
The second version doesn't rely on lag() but uses a self-join instead:
;with costs2 (weight, cost) as (
select number, cost
from master..spt_values
inner join (
select
isnull(t2.weight + 1,0) as low,
t1.Weight as high,
t1.Cost
from #t t1
left join #t t2 on t1.ID - 1 = t2.ID
) t on number <= t.high and number >= t.low
where type= 'P'
)
select cost from costs2 where weight = 5
Another option would be to just compute the low/high points for each range and do a query like this:
select cost from (
select isnull(LAG(weight) over (order by id)+1,0) low, weight high, cost from #t
) t
where 5 between low and high
Sample SQL Fiddle with the queries above.

Related

SQL split totals

Currently I have the following result in SQL:
qty
description
volume
weight
4
Flowers
3,4
4
This is in an XML format. So what I want to do, and I think I need to user "FOR XML PATH" in a certain way, but I am not sure how to achieve the following result:
qty
description
volume
weight
1
Flowers
0,85
1
1
Flowers
0,85
1
1
Flowers
0,85
1
1
Flowers
0,85
1
So I need to divide the XML path based on the total qty (4). For each (4) products, I need to create a new row. Then divide the volume and the weight (/qty).
Can anyone help me to push me into the right direction?
Edit:
The first result, qty of 4, is a result in a temp table.
I extract the data from the temp table into XML format. Here is a snippet
(SELECT "qty" = value('(#col24)[1]', 'varchar(50)'), "weight" = value('(#col28)[1]', 'varchar(50)'), "volume" = value('(#col26)[1]', 'decimal(16,2)') FOR XML PATH('product'), ROOT('products'), TYPE)
The qty, weight and volume represents the totals.
This is what I want to devide to create a "product" for each "qty".
You can use a recursive CTE to split the rows (you might need to up the recursion limit if your quantity can be higher than 100).
declare #Test table (qty int, [description] varchar(64), volume decimal(9,2), [weight] decimal(9,2))
insert into #Test (qty, [description], volume, [weight]) values (4, 'Flowers', 3.4, 4);
with cte as (
select qty, [description], volume, [weight], 1 as rn
from #Test
union all
select qty, [description], volume, [weight], rn + 1
from cte
where rn < qty
)
select 1 qty, [description], cast(volume / qty as decimal(9,2)) volume, cast([weight] / qty as decimal(9,2)) [weight]
from cte
for xml path('product'), root('products'), type;
-- option (maxrecursion 200); -- If you need to increase it above the default of 100
Note: If you setup the DDL+DML, as I shown, in your questions you make it much easier for people to reply.
One way is by recursive CTE:
WITH cte AS (
SELECT *, qty AS cc, 1 org FROM #sale
UNION ALL
SELECT cte.qty /qty
, cte.description
, cte.volume /qty
, cte.weight /qty
, cte.cc - 1 AS cc
, 0 org
FROM cte
WHERE cc > 1
)
SELECT * FROM cte
where org = 0
FOR XML path
However if you have a tally table it will faster and simpler:
SELECT qty /qty
, description
, volume /qty
, weight /qty
FROM table
JOIN numbertables < -- number tables from 1 to max
on numbertables.values < qty
FOR XML path

Sum Quantity and Filter Results

I have the following table with order id's and quantities. I need to be able to sum the quantity and retrieve the id's that that equal less than the provided number.
| id | quantity |
|------|----------|
| 100 | 1 |
| 200 | 25 |
| 300 | 15 |
For example, I need the id's where the sum of quantity equals less than 25.
When I try the following it only provides me the first id (100).
Select *
from (
select *,
SUM (Quantity) OVER (ORDER BY Id) AS SumQuantity
from dbo.Orders
) as A
where SumQuantity <= 25
Is it possible to adjust this query where it will provide me id 100 and 300, since the sum total of those orders is less than 25?
I know I can use a where clause on for quantity less than 25, but the important thing here is I need to be able to sum the quantity and pull id's that give me less than the provided number.
Thank you in advance!
Perhaps you want to order by the quantity instead of id?
Select o.*
from (select o.*, SUM (Quantity) OVER (ORDER BY quantity) AS SumQuantity
from dbo.Orders
) o
where SumQuantity <= 25;
This chooses the smallest values so you will get the most rows.
Group by Id and set the condition in the HAVING clause:
select Id, SUM(Quantity) AS SumQuantity
from Orders
group by Id
having SUM(Quantity) <= 25
See the demo.
Results:
Id | SumQuantity
100 | 1
200 | 25
300 | 15
If you want to include all the columns you can modify your query to not ORDER BY id but PARTITION BY id:
select *
from (
select *,
SUM (Quantity) OVER (PARTITION BY Id) AS SumQuantity
from Orders
) as A
where SumQuantity <= 25
For this dataset:
CREATE TABLE Orders([id] varchar(6), [quantity] int);
INSERT INTO Orders([id], [quantity])VALUES
('100', '1'), ('100', '2'),
('200', '25'), ('200', '3'),
('300', '15'), ('300', '5');
Results:
id | quantity | SumQuantity
100 | 1 | 3
100 | 2 | 3
300 | 15 | 20
300 | 5 | 20
See the demo.
Setup:
Your threshold can vary, so let's make it into a variable:
declare #threshold int = 25;
But I also imagine that your table values can vary, like if we add another row only having a quantity of 2:
declare #orders table (id int, quantity int)
insert #orders values (100,1), (200,25), (300,15), (400, 2);
Solution:
For this, we'll need a recursive kind of cross joining:
with
traverse as (
select ids = convert(nvarchar(255), id),
id,
quantity
from #orders
where quantity < #threshold
union all
select ids =
convert(nvarchar(255), tv.ids + ',' +
convert(nvarchar(255), o.id)),
o.id,
quantity = tv.quantity + o.quantity
from traverse tv
cross join #orders o
where tv.id < o.id
and tv.quantity + o.quantity < #threshold
)
select t.ids, t.quantity
from traverse t;
which will produce:
Explanation:
The above code is an algorithm that builds a tree. It starts with your base id's and quantities as nodes (the anchor part of the CTE). It trims anything not meeting the threshold.
It then adds edges by cross joining with orders table again (the recursive part of the CTE), but it only includes the following:
Id's that are greater than the last id considered in the current node (this is so that we avoid duplicate considerations, such as ids = '300,400' and ids = '400,300').
Ids where the sum of quantities is less than the threshold.
Warnings:
But beware, the type of problem you're considering will have computational complexity considerations. But because of the trimming conditions, it will be more efficient than doing all the cross joins first and then filtering the result set at the end.
Also, keep in mind that you may get rows in your table where there is no single set of numbers that will sum up to less than 25. Rather, you can get different paths to that sum. The way I produce the results here will help you identify such a situation.
cross join is perfect for this task, try:
declare #tbl table (id int, quantity int);
insert into #tbl values
(100, 1), (200, 25), (300, 15), (400, 10);
select distinct case when t1.id > t2.id then t1.id else t2.id end,
case when t1.id < t2.id then t1.id else t2.id end
from #tbl t1
cross join #tbl t2
where t1.id <> t2.id
and t1.quantity + t2.quantity < 25

Find the reversal transactions in my table

I have a table data like the bellow
Date Amount
01022017 300
01052017 -300
03042016 200
06112016 400
05042016 -200
30012016 150
I need only the list like below (the negatives are reversals happen to those transactions).
My expected result like
Date Amount
06112016 400
30012016 150
I need to avoid the reversal transactions. But here in my table I don't have any reference column to indicate the transaction as reversal or normal transaction.
You can achieve this with a LEFT JOIN:
SELECT
m.Date
, m.Amount
FROM myTable m
LEFT JOIN myTable m2 on m.Amount = (-1 * m2.Amount)
WHERE m2.Date IS NULL
As noted in the comments, relying in the amount seems flaky to me but here is a try:
SELECT t.date, t.amount
FROM TableName t
WHERE NOT EXISTS (SELECT 1
FROM TableName t1 INNER JOIN TableName t2
ON T1.amount = -t2.amount and T1.date<=t2.date
WHERE (t.date=t1.date or t.date=t2.date) and t.amount=t1.amount
Note that this will not display amounts that appear more than twice.
based on your Assumed table data using Lag function we can do this
Sample Data :
DECLARE #Table1 TABLE
(Dates int, Amount int)
;
INSERT INTO #Table1
(Dates, Amount)
VALUES
(01022017, 300),
(01052017, -300),
(03042016, 200),
(06112016, 400),
(05042016, -200),
(30012016, 150)
;
Script :
Select t.Amount from (
select amount,
coalesce(lag(amount) over (PARTITION BY RIGHT (dates,4) order by (select null)), 0) + amount as diff
from #Table1) T
WHERE NOT EXISTS (select 1 from #Table1 where T.diff = Amount ) and T.diff <> 0
Try this one for oracle, The Problem with the below query is it reads the main table twice.
select Date,Amount from table where amount>0
minus
select Date,-1*amount as Amount from table where amount<0;

how to get query value from 1 row to use to another row?

This is example query:
payment_Type payment_value cost_type cost value
Cost I 100 Registration 40
Cost I 100 books 40
Cost I 100 Lab 40
The COST I has 3 elements Cost_Type that have their own Cost_value.
I want to manipulate like this:
payment_Type payment_value cost_type cost value Payment_by_cost_type
Cost I 100 Registration 40 40
Cost I 100 books 40 40
Cost I 100 Lab 40 20
The point is the I want to divided the payment_value into each cost_value. In the example the payment_by_cost becomes 40, 40, 20 = 100.
The lab cost_value is 40 but it can assign value is 20 because remains from the divided 2 cost type above.
Is it possible that I can use the value from Payment_by_cost_type in the next row record? I have been trying to insert the value Payment_by_cost_type to a temporary table but select cannot have insert statement.
Does anyone have any ideas on how to solve this? I've been consulting to DWH he said it must using Store procedure it cannot done by query.
I guess your table contains not only "Cost I" but other values so here is a query to output results for all groups (by Payment_type) in the table:
;with table1 as
(select
t.*,
row_number()
OVER
(PARTITION BY payment_Type order by cost_type) rn
from t
)
,table2 as
( select t4.*,isnull((select sum(cost_value) from table1
where table1.payment_type=t4.payment_type and rn<t4.rn),0) CumSum
from table1 t4
)
select payment_type,payment_value,cost_type,cost_value,
case when cost_value+CumSum<=payment_value then cost_value
else
payment_value-Cumsum
end
from table2
order by Payment_type,rn;
SQLFIDDLE demo
You need to define some kind of order for your records to define in which order the payments should be applied
Once you have done that (i'm using ID in this example)...
select *
, case
when payment_value-(select isnull(SUM(cost_value),0) from yourtable t2 where t2.id<t1.id)<cost_value
then payment_value-(select isnull(SUM(cost_value),0) from yourtable t2 where t2.id<t1.id)
else cost_value
end
from yourtable t1
Doing it step by step using common table expressions.
declare #t table (
payment_type varchar(20),
payment_value int,
cost_type varchar(20),
cost_value int,
cost_id int --for the ordering
)
insert #t values
('Cost I',100,'Registration',40,1),
('Cost I',100,'books',40,2),
('Cost I',100,'Lab',40,3),
('Cost 2',100,'Registration',40,4),
('Cost 2',100,'books',50,5),
('Cost 2',100,'Lab',40,6)
--get count for each payment_type to determine last row
;with payment_value_cte(payment_type,payment_value,count) as
(
select payment_type,payment_value,COUNT(*) from #t group by payment_type,payment_value
),
--use sequential index for each row in payment type
payment_value_index_cte(
payment_type ,
payment_value,
cost_type,
cost_value,
cost_id,
row) as
(
select *,ROW_NUMBER() OVER(PARTITION BY payment_type ORDER BY cost_id) from #t --assumes order is by an id
),
--get sum of each row for payment type except last row
payment_value_sum_except_last_cte(
payment_type,
payment_value,
current_sum) as
(
select pi.payment_type,pi.payment_value,SUM(cost_value)
from payment_value_index_cte pi
inner join payment_value_cte pt on pt.payment_type = pi.payment_type
where pi.row < pt.count
group by pi.payment_type,pi.payment_value
)
select
pi.payment_type,pi.payment_value,pi.cost_type,pi.cost_value,
--if last row calculate difference, else use the cost_value
case when pi.row = pt.count then pt.payment_value - pe.current_sum else pi.cost_value end [Payment_by_cost_type]
from payment_value_index_cte pi
inner join payment_value_cte pt on pt.payment_type = pi.payment_type
inner join payment_value_sum_except_last_cte pe on pe.payment_type = pi.payment_type
SELECT payment_Type, payment_value, cost_type, cost_value,
CASE WHEN ROW_NUMBER() OVER (ORDER BY(SELECT 1)) = COUNT(*) OVER (PARTITION BY payment_Type)
THEN SUM(cost_value) OVER (PARTITION BY payment_Type) - payment_value
ELSE cost_value END AS Payment_by_cost_type
FROM dbo.your_table
Demo on SQLFiddle

How to select info from row above?

I want to add a column to my table that is like the following:
This is just an example of how the table is structured, the real table is more than 10.000 rows.
No_ Name Account_Type Subgroup (New_Column)
100 Sales 3
200 Underwear 0 250 *100
300 Bikes 0 250 *100
400 Profit 3
500 Cash 0 450 *400
So for every time there is a value in 'Subgroup' I want the (New_Column) to get the value [No_] from the row above
No_ Name Account_Type Subgroup (New_Column)
100 Sales 3
150 TotalSales 3
200 Underwear 0 250 *150
300 Bikes 0 250 *150
400 Profit 3
500 Cash 0 450 *400
There are cases where the table is like the above, where two "Headers" are above. And in that case I also want the first above row (150) in this case.
Is this a case for a cursor or what do you recommend?
The data is ordered by No_
--EDIT--
Starting from the first line and then running through the whole table:
Is there a way I can store the value for [No_] where [Subgroup] is ''?
And following that insert this [No_] value in the (New_Column) in each row below having value in the [Subgroup] row.
And when the [Subgroup] row is empty the process will keep going, inserting the next [No_] value in (New_Column), that is if the next line has a value in [Subgroup]
Here is a better image for what I´m trying to do:
SQL Server 2012 suggests using Window Offset Functions.
In this case : LAG
Something like this:
SELECT [No_]
,[Name]
,[Account_Type]
,[Subgroup]
,LAG([No_]) OVER(PARTITION BY [Subgroup]
ORDER BY [No_]) as [PrevValue]
FROM table
Here is an example from MS:
http://technet.microsoft.com/en-us/library/hh231256.aspx
The ROW_NUMBER function will allow you to find out what number the row is, but because it is a windowed function, you will have to use a common table expression (CTE) to join the table with itself.
WITH cte AS
(
SELECT [No_], Name, Account_Type, Subgroup, [Row] = ROW_NUMBER() OVER (ORDER BY [No_])
FROM table
)
SELECT t1.*, t2.[No_]
FROM cte t1
LEFT JOIN cte t2 ON t1.Row = t2.Row - 1
Hope this helps.
Next query will return Name of the parent row instead of the row itself, i.e. Sales for both Sales, Underwear, Bikes; and Profit for Profit, Cash:
select ISNULL(t2.Name, t1.Name)
from table t1
left join table t2 on t1.NewColumn = t2.No
So in SQL Server 2008 i created test table with 3 values in it:
create table #ttable
(
id int primary key identity,
number int,
number_prev int
)
Go
Insert Into #ttable (number)
Output inserted.id
Values (10), (20), (30);
Insert in table, that does what you need (at least if understood correctly) looks like this:
declare #new_value int;
set #new_value = 13; -- NEW value
Insert Into #ttable (number, number_prev)
Values (#new_value,
(Select Max(number) From #ttable t Where t.number < #new_value))
[This part added] And to work with subgroup- just modify the inner select to filter out it:
Select Max(number) From #ttable t
Where t.number < #new_value And Subgroup != #Subgroup
SELECT
No_
, Name
, Account_Type
, Subgroup
, ( SELECT MAX(above.No_)
FROM TableX AS above
WHERE above.No_ < a.No_
AND above.Account_Type = 3
AND a.Account_Type <> 3
) AS NewColumn
FROM
TableX AS a