"variable" cumulative value in SQL Server - sql

I have a table that looks like this:
id count price
1 100 2
2 50 3
3 10 4
I want to get the price for certain cumulative count values, for instance:
when I "need" a count of 120, SQL cumulates the first x rows, and checks if the the cumulative value in the x'th row meets the requirements (>120) and then gives me back the price value of the x'th row.
For 120 I want to get 3 as price, for 159.5 then 4, for 80 a 2 etc.
Is that possible in SQL Server?

Think you want this:
select top 1
[price]
from(
select [id],
[count],
[price],
sum([count]) over(order by [id]) as run_sum
from tbl
) x
where 120 <= run_sum
order by run_sum
Fiddle: http://www.sqlfiddle.com/#!6/a1976/5/0
Fiddle example for 159.5, http://www.sqlfiddle.com/#!6/a1976/6/0
Fiddle example for 80, http://www.sqlfiddle.com/#!6/a1976/7/0

select top 1 price from
(
select t1.id, t1.count, SUM(t2.count) as sum, t1.price
from table_name t1
inner join table_name t2 on t1.id >= t2.id
group by t1.id, t1.count, t1.price
) t where t.sum >= 120 order by sum
fiddle

Related

How to minus current and previous value in SQL Server

Have one table, need to minus one column previous and current amount. Table value is below, need to write syntax for Cal-Amount column
Id Amount Cal-Amount
1 100 0
2 200 0
3 400 0
4 500 0
Cal-Amount calculation formula with sample value
Id Amount Cal-Amount
1 100 (0-100)=100
2 200 (100-200)=100
3 400 (200-400)=200
4 500 (400-500)=100
Need SQL syntax to minus column current and previous value
LAG is one option if you are using SQL Server 2012 or later:
SELECT
Id,
Amount,
LAG(Amount, 1, 0) OVER (ORDER BY Id) - Amount AS [Cal-Amount]
FROM yourTable;
If you are using an earlier version of SQL Server, then we can use a self join:
SELECT
Id,
Amount,
COALESCE(t2.Amount, 0) - t1.Amount AS [Cal-Amount]
FROM yourTable t1
LEFT JOIN yourTable t2
ON t1.Id = t2.Id + 1;
But note that the self join option might only work if the Id values are continuous. LAG is probably the most efficient way to do this, and is also robust to non sequential Id values, so long as the order is correct.
Well, Tim beat me to the lag(), so here's the old-school using join:
select t.Id,t.Amount,t.Amount-isnull(t2.Amount,0) AS [Cal-Amount]
from yourtable t
left join yourtable t2 on t.id=t2.id+1
SQL Server 2012 or newer:
Select
ID, Amount, [Cal-Amount] = Amount - LAG(Amount, 1, 0) OVER (ORDER BY Id)
From
table
or
Select
current.ID, Current.Amount, Current.Amount - Isnull(Prior.Amount, 0)
from
table current
left join
table prior on current.id - 1 = prior.id
You can use the LAG function if your SQL Server >= 2012
declare #t table (id int, amount1 int)
insert into #t
values (1, 100), (2, 200), (3, 400), (4, 500)
select
*, amount1 - LAG(amount1, 1, 0) over (order by id) as CalAmount
from
#t
You can also use apply :
select t.*, t.Amount - coalesce(tt.Amount, 0) as CalAmount
from table t outer apply (
select top (1) *
from table t1
where t1.id < t.id
order by t1.id desc
) tt;

Compare two rows in two columns

I have a table for example like below
column1 column2
110 100
50 125
120 80
I want a selection in such a way that i will get something like this
column1 column2 difference
110 100 0
50 125 50
120 80 5
or just to be able to identify the difference between first row of column2 and second row of column1
You can do this with a LEFT JOIN:
SQL Fiddle
WITH Cte AS(
SELECT *,
rn = ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
FROM tbl
)
SELECT
t1.*,
difference = ISNULL(t2.column2 - t1.column1, 0)
FROM cte t1
LEFT JOIN Cte t2
ON t1.rn = t2.rn + 1
Since there is no column to indicate the order, I added a ROW_NUMBER. Modify the ORDER BY clause to your preference.
Another way, could be this:
SELECT TB.COLUMN1,TB.COLUMN2,
(ISNULL(TB2.COLUMN2,TB.COLUMN1)-TB.COLUMN1) AS 'DIF'
FROM
(SELECT COLUMN1,COLUMN2,ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1 AS 'R' FROM TEST ) TB
LEFT JOIN
(SELECT COLUMN1,COLUMN2,ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS 'R' FROM TEST ) TB2
ON TB.R = TB2.R
Until before the post I didn't know how dont let row_number 'order by' affected the query, but based on the above answer, now I Know it, using select null ;) thank you #Felix Pamittan

Summing Columns

This is my sample table
create table #t (id int,qty int)
insert into #t values(1,100),(2,200),(3,150),(4,50)
I am trying to get this
id qty total_qty
1 100 100
2 200 300
3 150 450
4 50 500
this is not working
select a.id,a.qty,sum(b.qty) as total_qty from #t
You want a cumulative sum. In SQL Server 2012, you can do this directly:
select a.id, a.qty, sum(a.qty) over (order by a.id) as total_qty
from #t a;
In earlier versions of SQL Server, you can do this with a correlated subquery or non-equijoin. Below is an example:
select t.id, t.qty,
(select sum(t2.qty) from #t t2 where t2.id <= t.id) as total_qty
from #t t;
EDIT:
The expression sum(a.qty) over (order by a.id) is a window function that expresses a cumulative sum. The over is saying "do something for each row". What it does for each row is take the sum of a.qty. But, not just any sum, a sum that takes the ordering into account. The order by is what makes this a cumulative sum. (Without the order by clase, sum(a.qty) over () would return the sum of from all the rows, as an additional column in each row.)
Use a join !
select a.id,a.qty,sum(b.qty) as total_qty from #t a cross join #t b where b.id<=a.id
group by a.id,a.qty
order by a.id
see Demo

Is there something equivalent to putting an order by clause in a derived table?

This is sybase 15.
Here's my problem.
I have 2 tables.
t1.jobid t1.date
------------------------------
1 1/1/2012
2 4/1/2012
3 2/1/2012
4 3/1/2012
t2.jobid t2.userid t2.status
-----------------------------------------------
1 100 1
1 110 1
1 120 2
1 130 1
2 100 1
2 130 2
3 100 1
3 110 1
3 120 1
3 130 1
4 110 2
4 120 2
I want to find all the people who's status for THEIR two most recent jobs is 2.
My plan was to take the top 2 of a derived table that joined t1 and t2 and was ordered by date backwards for a given user. So the top two would be the most recent for a given user.
So that would give me that individuals most recent job numbers. Not everybody is in every job.
Then I was going to make an outer query that joined against the derived table searching for status 2's with a having a sum(status) = 4 or something like that. That would find the people with 2 status 2s.
But sybase won't let me use an order by clause in the derived table.
Any suggestions on how to go about this?
I can always write a little program to loop through all the users, but I was gonna try to make one horrendus sql out of it.
Juicy one, no?
You could rank the rows in the subquery by adding an extra column using a window function. Then select the rows that have the appropriate ranks within their groups.
I've never used Sybase, but the documentation seems to indicate that this is possible.
With Table1 As
(
Select 1 As jobid, '1/1/2012' As [date]
Union All Select 2, '4/1/2012'
Union All Select 3, '2/1/2012'
Union All Select 4, '3/1/2012'
)
, Table2 As
(
Select 1 jobid, 100 As userid, 1 as status
Union All Select 1,110,1
Union All Select 1,120,2
Union All Select 1,130,1
Union All Select 2,100,1
Union All Select 2,130,2
Union All Select 3,100,1
Union All Select 3,110,1
Union All Select 3,120,1
Union All Select 3,130,1
Union All Select 4,110,2
Union All Select 4,120,2
)
, MostRecentJobs As
(
Select T1.jobid, T1.date, T2.userid, T2.status
, Row_Number() Over ( Partition By T2.userid Order By T1.date Desc ) As JobCnt
From Table1 As T1
Join Table2 As T2
On T2.jobid = T1.jobid
)
Select *
From MostRecentJobs As M2
Where Not Exists (
Select 1
From MostRecentJobs As M1
Where M1.userid = M2.userid
And M1.JobCnt <= 2
And M1.status <> 2
)
And M2.JobCnt <= 2
I'm using a number of features here which do exist in Sybase 15. First, I'm using common-table expressions both for my sample data and clump my queries together. Second, I'm using the ranking function Row_Number to order the jobs by date.
It should be noted that in the example data you gave, no user satisfies the requirement of having their two most recent jobs both be of status "2".
__
Edit
If you are using a version of Sybase that does not support ranking functions (e.g. Sybase 15 prior to 15.2), then you need simulate the ranking function using Counts.
Create Table #JobRnks
(
jobid int not null
, userid int not null
, status int not null
, [date] datetime not null
, JobCnt int not null
, Primary Key ( jobid, userid, [date] )
)
Insert #JobRnks( jobid, userid, status, [date], JobCnt )
Select T1.jobid, T1.userid, T1.status, T1.[date], Count(T2.jobid)+ 1 As JobCnt
From (
Select T1.jobid, T2.userid, T2.status, T1.[date]
From #Table2 As T2
Join #Table1 As T1
On T1.jobid = T2.jobid
) As T1
Left Join (
Select T1.jobid, T2.userid, T2.status, T1.[date]
From #Table2 As T2
Join #Table1 As T1
On T1.jobid = T2.jobid
) As T2
On T2.userid = T1.userid
And T2.[date] < T1.[date]
Group By T1.jobid, T1.userid, T1.status, T1.[date]
Select *
From #JobRnks As J1
Where Not Exists (
Select 1
From #JobRnks As J2
Where J2.userid = J1.userid
And J2.JobCnt <= 2
And J2.status <> 2
)
And J1.JobCnt <= 2
The reason for using the temp table here is for performance and ease of reading. Technically, you could plug in the query for the temp table into the two places used as a derived table and achieve the same result.

SQL stored procedure to add up values and stop once the maximum has been reached

I would like to write a SQL query (SQL Server) that will return rows (in a given order) but only up to a given total. My client has paid me a given amount, and I want to return only those rows that are <= to that amount.
For example, if the client paid me $370, and the data in the table is
id amount
1 100
2 122
3 134
4 23
5 200
then I would like to return only rows 1, 2 and 3
This needs to be efficient, since there will be thousands of rows, so a for loop would not be ideal, I guess. Or is SQL Server efficient enough to optimise a stored proc with for loops?
Thanks in advance. Jim.
A couple of options are.
1) Triangular Join
SELECT *
FROM YourTable Y1
WHERE (SELECT SUM(amount)
FROM YourTable Y2
WHERE Y1.id >= Y2.id ) <= 370
2) Recursive CTE
WITH RecursiveCTE
AS (
SELECT TOP 1 id, amount, CAST(amount AS BIGINT) AS Total
FROM YourTable
ORDER BY id
UNION ALL
SELECT R.id, R.amount, R.Total
FROM (
SELECT T.*,
T.amount + Total AS Total,
rn = ROW_NUMBER() OVER (ORDER BY T.id)
FROM YourTable T
JOIN RecursiveCTE R
ON R.id < T.id
) R
WHERE R.rn = 1 AND Total <= 370
)
SELECT id, amount, Total
FROM RecursiveCTE
OPTION (MAXRECURSION 0);
The 2nd one will likely perform better.
In SQL Server 2012 you will be able to so something like
;WITH CTE AS
(
SELECT id,
amount,
SUM(amount) OVER(ORDER BY id
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
AS RunningTotal
FROM YourTable
)
SELECT *
FROM CTE
WHERE RunningTotal <=370
Though there will probably be a more efficient way (to stop the scan as soon as the total is reached)
Straight-forward approach :
SELECT a.id, a.amount
FROM table1 a
INNER JOIN table1 b ON (b.id <=a.id)
GROUP BY a.id, a.amount
HAVING SUM(b.amount) <= 370
Unfortunately, it has N^2 performance issue.
something like this:
select id from
(
select t1.id, t1.amount, sum( t2.amount ) s
from tst t1, tst t2
where t2.id <= t1.id
group by t1.id, t1.amount
)
where s < 370