Calculating value using previous value of a row in T-SQL - sql-server

I got following table and want to calculate value of Column2 on each row using the value of the same column (Column2) from the previous row in a sql without using cursor or while loop.
Id Date Column1 Column2
1 01/01/2011 5 5 => Same as Column1
2 02/01/2011 2 18 => (1 + (value of Column2 from the previous row)) * (1 + (Value of Column1 from the current row)) i.e. (1+5)*(1+2)
3 03/01/2011 3 76 => (1+18)*(1+3) = 19*4
and so on
Any thoughts?

Assuming at least SQL Server 2005 for the recursive CTE:
;with cteCalculation as (
select t.Id, t.Date, t.Column1, t.Column1 as Column2
from YourTable t
where t.Id = 1
union all
select t.Id, t.Date, t.Column1, (1+t.Column1)*(1+c.Column2) as Column2
from YourTable t
inner join cteCalculation c
on t.Id-1 = c.id
)
select c.Id, c.Date, c.Column1, c.Column2
from cteCalculation c

I solved the problem, just mentioned.
This is my code:
;with cteCalculation as (
select t.Id, t.Column1, t.Column1 as Column2
from table_1 t
where t.Id = 1
union all
select t.Id, t.Column1, (1+t.Column1)*(1+c.Column2) as Column2
from table_1 t
inner join cteCalculation c
on t.Id-1 = c.id
),
cte2 as(
select t.Id, t.Column1 as Column3
from table_1 t
where t.Id = 1
union all
select t.Id, (select column2+1 from cteCalculation c where c.id = t.id) as Column3
from table_1 t
inner join cte2 c2
on t.Id-1 = c2.id
)
select c.Id, c.Column1, c.Column2, c2.column3
from cteCalculation c
inner join cte2 c2 on c.id = c2.id
The result is as I was expected:
1 5 5 5
2 2 18 19
3 3 76 77

Here is an example using ROW_NUMBER() if the Id's aren't necessarily in order:
;with DataRaw as (
select 1 as Id, '01/01/11' as Date, 5 as Column1 union
select 2 as Id, '02/01/11' as Date, 2 as Column1 union
select 4 as Id, '03/01/11' as Date, 3 as Column1
),
Data as (
select RowId = ROW_NUMBER() over (order by Id), Id, Date, Column1 from DataRaw
),
Data2 as (
select
RowId, id, Date, Column1, Column1 as Column2
from
Data d
where
RowId = 1
union all
select
d1.RowId, d1.id, d1.Date, d1.Column1, (1+d1.column1)*(1+d2.column2) as column2
from
Data d1
cross join
Data2 d2
where
d2.RowId + 1 = d1.RowId
)
select
Id, Date, Column1, Column2
from
Data2

edit: shoudld have read the question better...
Another go woudl be this:
;with DataRaw as (
select 1 as Id, '01/01/11' as Date, 5 as Column1 union
select 2 as Id, '02/01/11' as Date, 2 as Column1 union
select 4 as Id, '03/01/11' as Date, 3 as Column1
),
Data as (
select Ord = ROW_NUMBER() over (order by Id), Id, Date, Column1 from DataRaw
),
select -- formula goes here, using current and prev as datasources.
from data current
left join data prev on current.Ord = prev.Ord + 1 -- pick the previous row by adding 1 to the ordinal
I think a normal join to get to the previous row would be faster than a CTE. You;d have to check for yourself though.
Looks easier to me.
Good luck, GJ

Related

Unexpected result using CTE to perform a random join on two tables for all rows one-to-many

I am attempting to randomly join the rows of two tables (TableA and TableB) such that each row in TableA is joined to only one row in TableB and every row in TableB is joined to at least one row in TableA.
For example, a random join of TableA with 5 distinct rows and TableB with 3 distinct rows should result in something like this:
TableA TableB
1 3
2 1
3 1
4 2
5 1
However, sometimes not all the rows from TableB are included in the final result; so in the example above might have row 2 from TableB missing because in its place is either row 1 or 3 joined to row 4 on TableA. You can see this occur by executing the script a number of times and checking the result. It seems that it is necessary for some reason to use an interim table (#Q) to be able to ensure that a correct result is returned which has all rows from both TableA and TableB.
Can someone please explain why this is happening?
Also, can someone please advise on what would be a better way to get the desired result?
I understand that sometimes no result is returned due to a failure of some kind in the cross apply and ordering which i have yet to identify and goes to the point that I am sure there is a better way to perform this operation. I hope that makes sense. Thanks in advance!
declare #TableA table (
ID int
);
declare #TableB table (
ID int
);
declare #Q table (
RN int,
TableAID int,
TableBID int
);
with cte as (
select
1 as ID
union all
select
ID + 1
from cte
where ID < 5
)
insert #TableA (ID)
select ID from cte;
with cte as (
select
1 as ID
union all
select
ID + 1
from cte
where ID < 3
)
insert #TableB (ID)
select ID from cte;
select * from #TableA;
select * from #TableB;
with cte as (
select
row_number() over (partition by TableAID order by newid()) as RN,
TableAID,
TableBID
from (
select
a.ID as TableAID,
b.ID as TableBID
from #TableA as a
cross apply #TableB as b
) as M
)
select --All rows from TableB not always included
TableAID,
TableBID
from cte
where RN in (
select
top 1
iCTE.RN
from cte as iCTE
group by iCTE.RN
having count(distinct iCTE.TableBID) = (
select count(1) from #TableB
)
)
order by TableAID;
with cte as (
select
row_number() over (partition by TableAID order by newid()) as RN,
TableAID,
TableBID
from (
select
a.ID as TableAID,
b.ID as TableBID
from #TableA as a
cross apply #TableB as b
) as M
)
insert #Q
select
RN,
TableAID,
TableBID
from cte;
select * from #Q;
select --All rows from both TableA and TableB included
TableAID,
TableBID
from #Q
where RN in (
select
top 1
iQ.RN
from #Q as iQ
group by iQ.RN
having count(distinct iQ.TableBID) = (
select count(1) from #TableB
)
)
order by TableAID;
See if this gives you what you're looking for...
DECLARE
#CountA INT = (SELECT COUNT(*) FROM #TableA ta),
#CountB INT = (SELECT COUNT(*) FROM #TableB tb),
#MinCount INT;
SELECT #MinCount = CASE WHEN #CountA < #CountB THEN #CountA ELSE #CountB END;
WITH
cte_A1 AS (
SELECT
*,
rn = ROW_NUMBER() OVER (ORDER BY NEWID())
FROM
#TableA ta
),
cte_B1 AS (
SELECT
*,
rn = ROW_NUMBER() OVER (ORDER BY NEWID())
FROM
#TableB tb
),
cte_A2 AS (
SELECT
a1.ID,
rn = CASE WHEN a1.rn > #MinCount THEN a1.rn - #MinCount ELSE a1.rn end
FROM
cte_A1 a1
),
cte_B2 AS (
SELECT
b1.ID,
rn = CASE WHEN b1.rn > #MinCount THEN b1.rn - #MinCount ELSE b1.rn end
FROM
cte_B1 b1
)
SELECT
A = a.ID,
B = b.ID
FROM
cte_A2 a
JOIN cte_B2 b
ON a.rn = b.rn;

How to use CTE to get a query repeated for multiple inputs?

I have the following query:
SELECT **top 1** account, date, result
FROM table_1 as t1
JOIN table_2 at t2 ON t1.accountId = t2.frn_accountId
WHERE accountID = 1
ORDER BY date
This query returns the result that I want however I want that result for multiple accountID. They query should return the top 1 value for each accountID.
The query that produce the list of the accountID-s is:
SELECT accountID from lskin WHERE refname LIKE '%BHA%' and isactive = 1
How can I write this query so it can produce the desired result? I have been playing around with CTE but haven't been able to make it correct. It doesn't have to be with CTE, I just thought it can be easier using CTE...
Here is CTE solution.
SELECT *
FROM (SELECT account
, date
, result
, ROW_NUMBER() OVER (PARTITION BY t1.accountId ORDER BY date DESC) AS Rownum
FROM table_1 AS t1
INNER JOIN table_2 AS t2
ON t1.accountId = t2.frn_accountId
INNER JOIN lskin AS l
ON l.accountID = t1.accountID
WHERE l.refname LIKE '%BHA%'
) a
WHERE a.Rownum = 1;
Use max on your date and group by the account, or what ever columns are appropriate.
SELECT
account,
DT = max(date),
result
FROM table_1 as t1
JOIN table_2 as t2 ON t1.accountId = t2.frn_accountId
JOIN lskin as l on l.accountID = t1.accountID
WHERE l.refname like '%BHA%'
GROUP BY
account
,result
If the grouping isn't correct, just join to a sub-query to limit it with max date. Just change the table names as necessary.
SELECT
account,
date,
result
FROM table_1 as t1
JOIN table_2 as t2 ON t1.accountId = t2.frn_accountId
JOIN lskin as l on l.accountID = t1.accountID
INNER JOIN (select max(date) dt, accountID from table_1 group by accountID) tt on tt.dt = t1.accountId and tt.accountId = t1.accountId
WHERE l.refname like '%BHA%'
Ignore the CTE at the top. That's just test data.
/* CTE Test Data */
; WITH table_1 AS (
SELECT 1 AS accountID, 'acc1' AS account UNION ALL
SELECT 2 AS accountID, 'acc2' AS account UNION ALL
SELECT 3 AS accountID, 'acc3' AS account
)
, table_2 AS (
SELECT 1 AS frn_accountID, 'new1' AS result, GETDATE() AS [date] UNION ALL
SELECT 1 AS frn_accountID, 'mid1' AS result, GETDATE()-1 AS [date] UNION ALL
SELECT 1 AS frn_accountID, 'old1' AS result, GETDATE()-2 AS [date] UNION ALL
SELECT 2 AS frn_accountID, 'new2' AS result, GETDATE() AS [date] UNION ALL
SELECT 2 AS frn_accountID, 'mid2' AS result, GETDATE()-1 AS [date] UNION ALL
SELECT 2 AS frn_accountID, 'old2' AS result, GETDATE()-2 AS [date] UNION ALL
SELECT 3 AS frn_accountID, 'new3' AS result, GETDATE() AS [date] UNION ALL
SELECT 3 AS frn_accountID, 'mid3' AS result, GETDATE()-1 AS [date] UNION ALL
SELECT 3 AS frn_accountID, 'old3' AS result, GETDATE()-2 AS [date]
)
, lskin AS (
SELECT 1 AS accountID, 'purple' AS refName, 1 AS isActive UNION ALL
SELECT 2 AS accountID, 'blue' AS refName, 1 AS isActive UNION ALL
SELECT 3 AS accountID, 'orange' AS refName, 0 AS isActive UNION ALL
SELECT 4 AS accountID, 'blue' AS refName, 1 AS isActive
)
,
/* Just use the below and remove comment markers around WITH to build Orders CTE. */
/* ; WITH */
theCTE AS (
SELECT s1.accountID, s1.account, s1.result, s1.[date]
FROM (
SELECT t1.accountid, t1.account, t2.result, t2.[date], ROW_NUMBER() OVER (PARTITION BY t1.account ORDER BY t2.[date]) AS rn
FROM table_1 t1
INNER JOIN table_2 t2 ON t1.accountID = t2.frn_accountID
) s1
WHERE s1.rn = 1
)
SELECT lskin.accountID
FROM lskin
INNER JOIN theCTE ON theCTE.accountid = lskin.accountID
WHERE lskin.refName LIKE '%blue%'
AND lskin.isActive = 1
;
EDITED:
I'm still making a lot of assumptions about your data structure. And again, make sure you're querying what you need. CTEs are awesome, but you don't want to accidentally filter out expected results.

How join two tables using SQL without a common column

Example:
Table 1:
Column1 Column2
----------- -------------
A 1
B 2
D 3
E 4
Table 2:
Column3 Column4
----------- -------------
A 7
E 9
Z 5
Expected output:
Column1 Column2 Column3 Column4
----------- ------------- ------------- -------------
A 1 A 7
B 2 E 9
D 3 Z 5
E 4 NULL NULL
I want the output as shown in the picture.
If there are two tables with the columns Column1, Coumn2 and Column3, Column4,
then the expected output should be Column1, Column2, Column3, Column4, without any joins. It should have Table2's columns to the right hand side of the Table1's columns. NULL values will consume the empty rows if the number of rows in each table don't match.
You can use ROW_NUMBER window function to create a calculated field that can be used to join the two tables together:
SELECT t1.Column1, t1.Column2, t2.Column3, t2.Column4
FROM (
SELECT Column1, Column2,
ROW_NUMBER() OVER (ORDER BY Column1) AS rn
FROM Table1) AS t1
FULL OUTER JOIN (
SELECT Column3, Column4,
ROW_NUMBER() OVER (ORDER BY Column3) AS rn
FROM Table2) AS t2
ON t1.rn = t2.rn
A FULL OUTER JOIN is necessary in case either Table1 or Table2 has more rows.
This would work:-
SELECT Column1, Column2, Column3, Column4 FROM
(SELECT ROW_NUMBER() OVER(ORDER BY Column1) row, Table1.*
FROM Table1) t1
FULL OUTER JOIN (SELECT ROW_NUMBER() OVER(ORDER BY Column3) row, Table2.*
FROM Table2) t2
ON t1.row = t2.row
SQLFIDDLE
Alternatively, you could also do a left join instead of full outer join:
SELECT Column1
,Column2
,t.Column3
,t.Column4
FROM (
SELECT ROW_NUMBER() OVER (
ORDER BY Column1
) rn
,Table1.*
FROM Table1
) t1
LEFT JOIN (
SELECT ROW_NUMBER() OVER (
ORDER BY Column3
) AS rn
,t2.*
FROM Table2 t2
) t ON t1.rn = t.rn;
SQL Fiddle Demo
Note: Ofcourse, this is only going to work if you have more records in TableA.
The simplest way is using CROSS JOIN
SELECT t1.FIELD_A, t2.FIELD_B, t1.FIELD_C, t2.FIELD_D, t2.FIELD_E
FROM tbl01 t1
CROSS JOIN
tbl02 t2

SQL query for displaying count if same name comes in adjacent row it should show the count else 1

I have a table tb1 with columns id,name,
if same name comes in adjacent row it should display the count count else 1
For eg:
id name
1 sam
2 jose
3 sam
4 sam
5 dev
6 jose
Result want to be
name counts
sam 1
jose 1
sam 2
dev 1
jose 1
please help.
Check out this one :(SELF JOIN)
create table #sampele(id int,name varchar(50))
insert into #sampele values(1,'sam')
insert into #sampele values(2,'jose')
insert into #sampele values(3,'sam')
insert into #sampele values(4,'sam')
insert into #sampele values(5,'dev')
insert into #sampele values(6,'jose')
select a.id,a.name,case when a.name = b.name then 2 else 1 end as cnt from
#sampele a
left outer join
#sampele b
on a.id = b.id+1
Try a combination with a sub query, "COUNT(*) OVER (PARTITION", and row_number():
--DROP TABLE #Test;
SELECT id = IDENTITY(INT,1,1), name INTO #Test FROM
(
SELECT name = 'sam' UNION ALL
SELECT 'jose' UNION ALL
SELECT 'sam ' UNION ALL
SELECT 'sam ' UNION ALL
SELECT 'sam ' UNION ALL
SELECT 'dev ' UNION ALL
SELECT 'dev ' UNION ALL
SELECT 'jose' UNION ALL
SELECT 'sam ' UNION ALL
SELECT 'sam ' UNION ALL
SELECT 'jose'
) a;
GO
WITH GetEndID AS (
SELECT *
, EndID =(SELECT MIN(id) FROM #Test b WHERE b.name != a.name AND b.id > a.id)
FROM #Test a
), GetCount AS
(
SELECT
*
, NameCount = COUNT(*) OVER (PARTITION BY EndID)
, OrderPrio = ROW_NUMBER() OVER (PARTITION BY EndID ORDER BY id)
FROM GetEndID
)
SELECT id, name, NameCount FROM GetCount WHERE OrderPrio = 1 ORDER BY id;
select distinct a.name,case when a.name = b.name then 2 else 1 end as cnt from
tb1 a
left outer join
tb1 b
on a.id = b.id+1
sQlfiddle
Click to see running

Select only unique values

Tabls is
ID Count
1 30
2 30
3 10
4 15
5 10
6 25
I want query which will give me
4 15
6 25
in result
You can use NOT EXISTS:
SELECT ID, Count
FROM dbo.TableName t1
WHERE NOT EXISTS
(
SELECT 1 FROM dbo.TableName t2
WHERE t1.ID <> t2.ID AND t1.Count = t2.Count
)
Demo
The following should select what you want:
SELECT t.ID, t.[Count]
FROM Table t
WHERE
(SELECT COUNT(*) FROM Table t1 WHERE t1.[Count] = t.[Count]) = 1
Please note that you should really have an index on Table.[Count].
you could also do it with a grouping statement
SELECT MIN(ID), Count
FROM Table
GROUP BY Count
HAVING COUNT(*) = 1
Use HAVING together with COUNT DISTINCT, to limit the result:
SELECT [Id], [Count]
FROM MyTable
WHERE [Count] IN (
SELECT [Count]
FROM MyTable
GROUP BY [Count]
HAVING COUNT(DISTINCT [Count]) = 1
)

Resources