Self join and get unique records between a date - sql-server

I have my table and data as follows where I am trying to filter based on period and get results
CREATE TABLE testData
(
Id int,
period date,
value decimal(18,2)
)
INSERT INTO testData
VALUES (1, '2001-08-01', 400), (2, '2001-09-01', 400), (2, '2001-09-01', 300)
I have a fiddle which is giving results but not as expected you can check fiddle here http://sqlfiddle.com/#!6/beb4c/5
This is my SQL query
SELECT
a.id,
[value] - (SELECT TOP 1 b.[value]
FROM testData b
WHERE b.period = a.period
ORDER BY b.id DESC) x
FROM
testData a
Output I am expecting is
1 2001-08-01 400
2 2001-09-01 100

try This
WITH CTE
AS
(
SELECT
SeqNo = ROW_NUMBER() OVER(PARTITION BY Id ORDER BY Period),
*
FROM TestData
)
SELECT
A.Id,
A.Period,
Value = ISNULL(A.Value,0) - ISNULL(Q.Value,0)
FROM CTE A
LEFT JOIN(
SELECT
B.Id,
B.period,
Value = SUM(B.Value)
FROM CTE B
WHERE B.SeqNo <> 1
GROUP BY B.Period,B.Id
)Q
ON A.Id = Q.Id
WHERE A.SeqNo = 1
Fiddle Link Here

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 set an order when using CTE?

I have a table with 2 columns (id, childId). The data is as follows:
1, 2
3, 4
2, null
4, null
I'm using a CTE so that I get the child records:
DECLARE #id TABLE (id int);
INSERT INTO #id SELECT 1;
INSERT INTO #id SELECT 3;
WITH cte AS
(
SELECT id, childId
FROM mytable
WHERE
id IN (SELECT id FROM #id)
UNION ALL
SELECT b.id, b.childId
FROM mytable b
INNER JOIN cte
ON b.id = cte.childId
)
SELECT * FROM cte
The result always come back as:
1, 2
3, 4
4, null
2, null
But I need the result to look like:
1, 2
2, null
3, 4,
4, null
That is, first the anchor records then the records for the recursive sql for each anchor record.
Is this possible?
Add a static value to in anchor query. Then in recursive part add a static value greater than the static value of anchor query. Now the use static value in Order by
Try this
WITH cte AS
(
SELECT 0 as rn, id, childId
FROM mytable
WHERE
id IN (SELECT id FROM #id)
UNION ALL
SELECT 1 as rn,b.id, b.childId
FROM mytable b
INNER JOIN cte
ON b.id = cte.childId
)
SELECT * FROM cte
Order by rn,id
Also consider adding option(Maxrecursion N). By default it just makes only 100 recursions
By Adding a Seq, the results will be displayed in the proper order/nesting
DECLARE #id TABLE (id int);
INSERT INTO #id SELECT 1;
INSERT INTO #id SELECT 3;
WITH cte AS
(
SELECT id, childId
,Seq = cast(100000+Row_Number() over (Order by id) as varchar(500))
FROM mytable
WHERE
id IN (SELECT id FROM #id)
UNION ALL
SELECT b.id, b.childId
,Seq = cast(concat(cte.Seq,'.',100000+Row_Number() over (Order by b.id)) as varchar(500))
FROM mytable b
INNER JOIN cte
ON b.id = cte.childId
)
SELECT * FROM cte
Order By Seq

How to change duplicate values to unique values in a column using by stored procedure in SQL Server

I have to change RowSpan value if OccupationalInjuryId's duplicate value exists. Here is the screenshot:
Should I use T-SQL or #temporary table structure. But I couldn't figure out how to fix. For example there are two values with OccupationalId = 1100 and both rowspan's are 2. It should be 1 for second value's RowSpan no.
My SQL query:
select *
from
(select
Row_Number() over (order by oi.Id) as RowNo,
oid.OccupationalInjuryId
from
OTH_OccupationalInjury oi
left join
OTH_OccupationalInjuryDetail oid on oi.Id = oid.OccupationalInjuryId
where
1 = 1) t1
left join
(select
count(OccupationalInjuryId) end as RowSpan,
oid.OccupationalInjuryId
from
OTH_OccupationalInjury oi
left join
OTH_OccupationalInjuryDetail oid on oi.Id = oid.OccupationalInjuryId
where
1=1
group by
OccupationalInjuryId
having
(Count(OccupationalInjuryId) > 1) ) t2 on t1.OccupationalInjuryId = t2.OccupationalInjuryId
END
If I got you correctly, you only care about first RowSpan value for each OccupationalInjuryId and for others you want 1. If I am wrong, correct me.
So, Can you try something like :
DECLARE #BaseTable TABLE(ID INT, OccupationalInjuryId INT, RowSpan INT)
INSERT INTO #BaseTable (ID, OccupationalInjuryId, RowSpan)
SELECT 1, 1100, 2 UNION ALL
SELECT 2, 1100, 2 UNION ALL
SELECT 3, 1099, 2 UNION ALL
SELECT 4, 1099, 3 UNION ALL
SELECT 5, 1106, NULL
SELECT
ID,
OccupationalInjuryId,
CASE WHEN rn = 1 THEN RowSpan ELSE 1 END AS RowSpan
FROM
(
SELECT ID, OccupationalInjuryId, RowSpan, ROW_NUMBER() OVER (PARTITION BY OccupationalInjuryId, RowSpan ORDER BY ID) AS rn FROM #BaseTable
) tmp

How to find the maximum value in join without using if in sql stored procedure

I have a two tables like below
A
Id Name
1 a
2 b
B
Id Name
1 t
6 s
My requirement is to find the maximum id from table and display the name and id for that maximum without using case and if.
i findout the maximum by using below query
SELECT MAX(id)
FROM (SELECT id,name FROM A
UNION
SELECT id,name FROM B) as c
I findout the maximum 6 using the above query.but i can't able to find the name.I tried the below query but it's not working
SELECT MAX(id)
FROM (SELECT id,name FROM A
UNION
SELECT id,name FROM B) as c
How to find the name?
Any help will be greatly appreciated!!!
First combine the tables, since you need to search both. Next, determine the id you need. JOIN the id back with the temporarily created table to retreive the name that belongs to that id:
WITH tmpTable AS (
SELECT id,name FROM A
UNION
SELECT id,name FROM B
)
, max AS (
SELECT MAX(id) id
FROM tmpTable
)
SELECT t.id, t.name
FROM max m
JOIN tmpTable t ON m.id = t.id
You could use ROW_NUMBER(). You have to UNION ALL TableA and TableB first.
WITH TableA(Id, Name) AS(
SELECT 1, 'a' UNION ALL
SELECT 2, 'b'
)
,TableB(Id, Name) AS(
SELECT 1, 't' UNION ALL
SELECT 6, 's'
)
,Combined(Id, Name) AS(
SELECT * FROM TableA UNION ALL
SELECT * FROM TableB
)
,CTE AS(
SELECT *, RN = ROW_NUMBER() OVER(ORDER BY ID DESC) FROM Combined
)
SELECT Id, Name
FROM CTE
WHERE RN = 1
Just order by over the union and take first row:
SELECT TOP 1 * FROM (SELECT * FROM A UNION SELECT * FROM B) x
ORDER BY ID DESC
This won't show ties though.
For you stated that you used SQL Server 2008. Therefore,I used FULL JOIN and NESTED SELECT to get what your looking for. See below:
SELECT
(SELECT
1,
ISNULL(A.Id,B.Id)Id
FROM A FULL JOIN B ON A.Id=B.Id) AS Id,
(SELECT
1,
ISNULL(A.Name,B.Name)Name
FROM A FULL JOIN B ON A.Id=B.Id) AS Name
It's possible to use ROW_NUMBER() or DENSE_RANK() functions to get new numiration by Id, and then select value with newly created orderId equal to 1
Use:
ROW_NUMBER() to get only one value (even if there are some repetitions of max id)
DENSE_RANK() to get all equal max id values
Here is an example:
DECLARE #tb1 AS TABLE
(
Id INT
,[Name] NVARCHAR(255)
)
DECLARE #tb2 AS TABLE
(
Id INT
,[Name] NVARCHAR(255)
)
INSERT INTO #tb1 VALUES (1, 'A');
INSERT INTO #tb1 VALUES (7, 'B');
INSERT INTO #tb2 VALUES (4, 'C');
INSERT INTO #tb1 VALUES (7, 'D');
SELECT * FROM
(SELECT Id, Name, ROW_NUMBER() OVER (ORDER BY Id DESC) AS [orderId]
FROM
(SELECT Id, Name FROM #tb1
UNION
SELECT Id, Name FROM #tb2) as tb3) AS TB
WHERE [orderId] = 1
SELECT * FROM
(SELECT Id, Name, DENSE_RANK() OVER (ORDER BY Id DESC) AS [orderId]
FROM
(SELECT Id, Name FROM #tb1
UNION
SELECT Id, Name FROM #tb2) as tb3) AS TB
WHERE [orderId] = 1
Results are:

Combine two tables in SQL Server

I have tow tables with the same number of rows
Example:
table a:
1,A
2,B
3,C
table b:
AA,BB
AAA,BBB,
AAAA,BBBB
I want a new table made like that in SQL SErver:
1,A,AA,BB
2,B,AAA,BBB
3,C,AAAA,BBBB
How do I do that?
In SQL Server 2005 (or newer), you can use something like this:
-- test data setup
DECLARE #tablea TABLE (ID INT, Val CHAR(1))
INSERT INTO #tablea VALUES(1, 'A'), (2, 'B'), (3, 'C')
DECLARE #tableb TABLE (Val1 VARCHAR(10), Val2 VARCHAR(10))
INSERT INTO #tableb VALUES('AA', 'BB'),('AAA', 'BBB'), ('AAAA', 'BBBB')
-- define CTE for table A - sort by "ID" (I just assumed this - adapt if needed)
;WITH DataFromTableA AS
(
SELECT ID, Val, ROW_NUMBER() OVER(ORDER BY ID) AS RN
FROM #tablea
),
-- define CTE for table B - sort by "Val1" (I just assumed this - adapt if needed)
DataFromTableB AS
(
SELECT Val1, Val2, ROW_NUMBER() OVER(ORDER BY Val1) AS RN
FROM #tableb
)
-- create an INNER JOIN between the two CTE which just basically selected the data
-- from both tables and added a new column "RN" which gets a consecutive number for each row
SELECT
a.ID, a.Val, b.Val1, b.Val2
FROM
DataFromTableA a
INNER JOIN
DataFromTableB b ON a.RN = b.RN
This gives you the requested output:
You could do a rank over the primary keys, then join on that rank:
SELECT RANK() OVER (table1.primaryKey),
T1.*,
T2.*
FROM
SELECT T1.*, T2.*
FROM
(
SELECT RANK() OVER (table1.primaryKey) [rank], table1.* FROM table1
) AS T1
JOIN
(
SELECT RANK() OVER (table2.primaryKey) [rank], table2.* FROM table2
) AS T2 ON T1.[rank] = T2.[rank]
Your query is strange, but in Oracle you can do this:
select a.*, tb.*
from a
, ( select rownum rn, b.* from b ) tb -- temporary b - added rn column
where a.c1 = tb.rn -- assuming first column in a is called c1
if there is not column with numbers in a you can do same trick twice
select ta.*, tb.*
from ( select rownum rn, a.* from a ) ta
, ( select rownum rn, b.* from b ) tb
where ta.rn = tb.rn
Note: be aware that this can generate random combination, for example
1 A AA BB
2 C A B
3 B AAA BBB
because there is no order by in ta and tb

Resources