UNION CTE with ORDER BY on 3 Tables - sql

This is not easy to explain, however, I do believe there is a much nicer way to do what I managed to do and hope to get some help.
I have 3 tables (T1, T2, and T3). I need to get the latest from T1 and T2, then with those results return the results from t3 or from the previous result if t3 is empty. So the LatestDate doesn't really matter if there is a record in t3. Also, if there is no data in t3 and the LatestDate is the same on t1 and t2 (rarely will happen, but want to plan accordingly), I want results from t1.
Here's a sample of what I got, mind you the actual query is has many more fields, but the concept is the same.
CREATE TABLE [dbo].[t1](
[Id] [INT] NOT NULL,
[LatestDate] [DATETIME] NOT NULL
);
CREATE TABLE [dbo].[t2](
[Id] [INT] NOT NULL,
[LatestDate] [DATETIME] NOT NULL
);
CREATE TABLE [dbo].[t3](
[Id] [INT] NOT NULL,
[LatestDate] [DATETIME] NOT NULL
);
INSERT t1 (Id, LatestDate) VALUES (1, CAST(N'2000-01-01T00:00:00.000' AS DateTime));
INSERT t1 (Id, LatestDate) VALUES (2, CAST(N'2001-01-01T00:00:00.000' AS DateTime));
INSERT t1 (Id, LatestDate) VALUES (3, CAST(N'2002-01-01T00:00:00.000' AS DateTime));
INSERT t2 (Id, LatestDate) VALUES (1, CAST(N'2001-01-01T00:00:00.000' AS DateTime));
INSERT t2 (Id, LatestDate) VALUES (2, CAST(N'2002-01-01T00:00:00.000' AS DateTime));
INSERT t2 (Id, LatestDate) VALUES (4, CAST(N'2003-01-01T00:00:00.000' AS DateTime));
INSERT t3 (Id, LatestDate) VALUES (1, CAST(N'2001-01-01T00:00:00.000' AS DateTime));
INSERT t3 (Id, LatestDate) VALUES (2, CAST(N'2000-01-01T00:00:00.000' AS DateTime));
INSERT t3 (Id, LatestDate) VALUES (5, CAST(N'2004-01-01T00:00:00.000' AS DateTime));
GO;
WITH CTE AS (
SELECT TOP 1 * FROM (
SELECT 2 AS Sort, * FROM t1 WHERE t1.id = #UserId
UNION
SELECT 3 AS Sort, * FROM t2 WHERE t2.id = #UserId
) AS t
ORDER BY
t.LatestDate DESC
)
SELECT TOP 1 * FROM (
SELECT TOP 1 * FROM CTE
UNION
SELECT 1 AS Sort, * FROM t3 WHERE t3.id = #UserId
) AS t
ORDER BY
t.Sort;
Expected results:
When #UserID = 1:
Sort Source Id LatestDate
1 t3 1 1/1/2001
When #UserID = 2:
Sort Source Id LatestDate
1 t3 2 1/1/2000
When #UserID = 3:
Sort Source Id LatestDate
2 t1 3 1/1/2002
When #UserID = 4:
Sort Source Id LatestDate
3 t2 4 1/1/2003
When #UserID = 5:
Sort Source Id LatestDate
1 t3 5 1/1/2004
Thanks!

If I understand you correctly you want something like:
DECLARE #UserID INT = 5;
WITH cte AS (
SELECT 1 AS src, 1 as [tbl], * FROM t1 WHERE id = #UserId
UNION ALL SELECT 1, 2, * FROM t2 WHERE id = #UserId
UNION ALL SELECT 3, 3, * FROM t3 WHERE id = #UserId
), cte2 AS (
SELECT * , ROW_NUMBER() OVER(ORDER BY src DESC, LatestDate DESC, tbl ASC) AS rn
FROM cte
)
SELECT Id, LatestDate
FROM cte2
WHERE rn = 1;
RextesterDemo
Using PARTITION BY you could move filtering #userId to final part:
DECLARE #UserID INT = 5;
WITH cte AS (
SELECT 1 AS src, 1 as [tbl], * FROM t1
UNION ALL SELECT 1, 2, * FROM t2
UNION ALL SELECT 3, 3, * FROM t3
), cte2 AS (
SELECT *
,ROW_NUMBER() OVER(PARTITION BY Id ORDER BY src DESC, LatestDate DESC, tbl ASC) AS rn
FROM cte
)
SELECT Id, LatestDate
FROM cte2
WHERE rn = 1
AND id = #UserID
Rextester Demo2

How's this?
This assumes you will take the t3 result if it exists
or the max result from T1 or T2 if not.
if exists(select * from t3 where ID= #ID)
(
select ID, Max(LatestDate), TN='T3'
from t3
where ID= #ID
)
else
(
select top 1 ID, Max(LatestDate) LD,TN
from (Select *,TN='t1' from T1
union all
Select *, TN='t2' from t2) as a
where id=#ID
group by ID,TN
order by LD desc
)

Related

Insert multiple row different column value

This is my existing table
In this table, each user has their own respective data according to their Status. Each of the user will surely have Status 1.
Now, there are 3 Status to be stored for every user.
Was trying to make every user to have 3 Status, by inserting new row of user copying their Status 1 data, such that:
User Ali currently only have Status 1 and its data, so need insert a new
row Ali with Status 2 and copy along the data from Status 1, again,
insert a new row Ali with Status 3 and copy along the data from
Status 1.
User John currently only have Status 1 and 2, so need insert a new
row John with Status 3 and copy along the data from Status 1.
continue same pattern with other user
Expected result:
I would use CROSS JOIN and NOT EXISTS
with data as
(
select name,
column1,
column2
from your_table
where status = 1
), cross_join_data as
(
select d1.name, t.status, d1.column1, d1.column2
from data d1
cross join
(
select 1 status
union
select 2 status
union
select 3 status
) t
where not exists (
select 1
from your_table d2
where d2.name = d1.name and
d2.status = t.status
)
)
select *
from your_table
union all
select *
from cross_join_data
dbfiddle demo
This should work
with cte as (
select
[Name], coalesce(max(iif([Status]=1, [Column1], null)), max(iif([Status]=2, [Column1], null)), max(iif([Status]=3, [Column1], null))) col1
, coalesce(max(iif([Status]=1, [Column2], null)), max(iif([Status]=2, [Column2], null)), max(iif([Status]=3, [Column2], null))) col2
from
MyTable
group by [Name]
)
--insert into MyTable
select
cte.[Name], nums.n, cte.col1, cte.col2
from
cte
cross join (values (1),(2),(3)) nums(n)
left join MyTable on cte.[Name]=MyTable.[Name] and n=MyTable.[Status]
where
MyTable.[Status] is null
This works if data is not nullable
declare #table table (name varchar(10), status int, data int);
insert into #table values
('a', 1, 2)
, ('a', 2, 5)
, ('a', 3, 7)
, ('b', 1, 5)
, ('b', 2, 6)
, ('c', 1, 3)
select stats.status as statusStats
, tn.name as nameTN
, t.status as statusData, t.name, t.data
, ISNULL(t.data, t1.data) as 'fillInData'
from (values (1),(2),(3)) as stats(status)
cross join (select distinct name from #table) tn
left join #table t
on t.status = stats.status
and t.name = tn.name
join #table t1
on t1.name = tn.name
and t1.status = 1
order by tn.name, stats.status
Here is what I would do:
CREATE TABLE #existingtable (Name VARCHAR(50), Status INT, Column1 VARCHAR (10), Column2 VARCHAR(10));
INSERT INTO #existingtable (Name,Status,Column1,Column2) Values('Ali',1,'100','90');
INSERT INTO #existingtable (Name,Status,Column1,Column2) Values('John',1,'20','200');
INSERT INTO #existingtable (Name,Status,Column1,Column2) Values('John',2,'80','90');
INSERT INTO #existingtable (Name,Status,Column1,Column2) Values('Ming',1,'54','345');
INSERT INTO #existingtable (Name,Status,Column1,Column2) Values('Mei',1,'421','123');
INSERT INTO #existingtable (Name,Status,Column1,Column2) Values('Mei',3,'24','344');
SELECT * FROM #existingtable;
WITH CTE (Name,Column1,Column2)
AS
(
SELECT DISTINCT NAME,COLUMN1,COLUMN2
FROM #existingtable
)
, CTE2 (NAME,Status,Column1,Column2)
AS
(
SELECT NAME,1 AS STATUS,COLUMN1,COLUMN2
FROM CTE
UNION
SELECT NAME,2 AS STATUS,COLUMN1,COLUMN2
FROM CTE
UNION
SELECT NAME,3 AS STATUS,COLUMN1,COLUMN2
FROM CTE
)
INSERT INTO #existingtable (Name,Status,Column1,Column2)
SELECT C.Name,C.Status,C.Column1,C.Column2
FROM CTE2 AS C
LEFT JOIN #existingtable AS E
ON C.NAME = E.Name
AND C.Status = E.Status
WHERE E.Status IS NULL
SELECT * FROM #existingtable
ORDER BY Name, status
This has 2 edits. Initial edit added a where clause to the CTE
Second edit added the values added by the OP

how to join two tables in hive with unique value in column

I have two tables in hive, table1 and table2 with the following values:
TABLE1:........................................TABLE2:
date.................value.....................date.....................value
'2016-01-01'....one.......................'2016-01-01'..........two
'2016-01-01'....three.....................'2016-01-01'..........four
'2016-01-01'....five
and I need to join both tables to create a third table as (with three rows) :
date.......................value1...........................value2
'2016-01-01'..........one................................two
'2016-01-01'..........three..............................four
'2016-01-01'..........five
I have tried many options but none of them worked.
Any idea.
DECLARE #table1 TABLE ([Date] Date, [Value] VarChar(10))
DECLARE #table2 TABLE ([Date] Date, [Value] VarChar(10))
INSERT INTO #table1([Date], [Value])
SELECT '2016-01-01', 'one'
UNION ALL
SELECT '2016-01-01', 'three'
UNION ALL
SELECT '2016-01-01', 'five'
INSERT INTO #table2([Date], [Value])
SELECT '2016-01-01', 'two'
UNION ALL
SELECT '2016-01-01', 'four'
SELECT [Date] = t1.[Date],
[Value1] = t1.[Value],
[Value2] = t3.[Value]
FROM
(SELECT [Date] = t.[Date],
[Value] = t.[Value],
[ID] = ROW_NUMBER() OVER(ORDER BY t.[Date] DESC)
FROM #table1 t) t1
OUTER APPLY
(
SELECT TOP 1 t2.[Value]
FROM
(SELECT [Date] = t.[Date],
[Value] = t.[Value],
[ID] = ROW_NUMBER() OVER(ORDER BY t.[Date] DESC)
FROM #table2 t) t2
WHERE t2.[Date] = t1.[Date]
AND t2.[ID] = t1.[ID]
) t3

Full Outer Join Using Each Row Once

I'm wondering if anyone's come across a neat solution to this problem. I'm trying to select data from a couple of tables, having the records match up row by row. I'm basically after a full outer join, but there's one crucial difference. If I have four rows with a particular value in the column I'm joining on in one table, and three rows with this value in another, I only want the first three results to be joined, and the fourth to act as if there had been no match.
The reason for this is to create a reconciliation report which ensures transactions are not counted multiple times when comparing results. I can get around this issue by using a bit of grouping and some aggregate functions, but this hides some of the detail which I'd like to keep.
Below is an example to show the sort of thing I'm after, with the invalid/pseudo code in the comments illustrating how I'm thinking of this as working:
declare #t1 table (id bigint identity(1,1) primary key clustered, foreignKeyId bigint, otherData nvarchar(10))
declare #t2 table (id bigint identity(1,1) primary key clustered, foreignKeyId bigint, moreData nvarchar(10))
insert #t1 select 1, '1.1.1'
union all select 1, '1.1.2'
union all select 1, '1.1.3'
union all select 3, '1.3.1'
union all select 3, '1.3.2'
union all select 3, '1.3.3'
union all select 4, '1.4.3'
insert #t2 select 1, '2.1.1'
union all select 1, '2.1.2'
union all select 1, '2.1.3'
union all select 2, '2.2.1'
union all select 3, '2.3.1'
union all select 3, '2.3.2'
union all select 5, '2.5.1'
union all select 5, '2.5.2'
--demo of the functionality i'm hoping to acheive
--
/*
select t1.id id1
, t2.id id2
, t1.foreignKeyId fk1
, t2.foreignKeyId fk2
, t1.otherData otherData
, t2.moreData moreData
from #t1 t1
full funky join #t2 t2
on t1.foreignKeyId = t2.foreignKeyId
order by t1.id, t2.id --we'd need an order by to ensure the match could be applied in a predictable manner
*/
--
declare #funkyjoin table (id1 bigint, id2 bigint, fk1 bigint, fk2 bigint, otherData nvarchar(10), moreData nvarchar(10))
declare #id1 bigint, #id2 bigint
insert #funkyjoin (id1, fk1, otherData)
select id, foreignKeyId, otherData from #t1
while exists(select 1 from #t2)
begin
select top 1 #id2 = id from #t2 order by id
set #id1 = null
select top 1 #id1 = id1
from #funkyjoin
where fk2 is null
and fk1 in (select foreignKeyId from #t2 where id = #id2)
if #id1 is null
begin
insert #funkyjoin (id2, fk2, moreData)
select id, foreignKeyId, moreData
from #t2
where id = #id2
end
else
begin
update #funkyjoin
set id2 = #id2
, fk2 = fk1 --since we're joining on this we can just match it
, moreData = (select moreData from #t2 where id = #id2)
where id1 = #id1
end
delete from #t2 where id = #id2 --since this is only an example let's not worry about keeping our source data
end
select *
from #funkyjoin
order by coalesce(id1, id2)
I've written a similar solution for when this scenario occurs on spreadsheets previously: http://officemacros.codeplex.com/#WorksheetMergeMacro
If I understand correctly, this may be what you're after:
select *
from (
select *,
row_number() over (partition by foreignKeyId order by id) as n
from #t1
) t1
full outer join (
select *,
row_number() over (partition by foreignKeyId order by id) as n
from #t2
) t2 on t1.foreignKeyId = t2.foreignKeyId and t1.n = t2.n
The best way to use up the rows is to add a pseudo-row number (using ROW_NUMBER) and include that in the join.

interesting t-sql exercise

I am trying to resolve t-sql exercise
I need to update first table with values from second by joining by id. If I can not join then use value from default ID (default iD is the Id that is null)
please run it to see it
declare #t as table (
[id] INT
,val int
)
insert into #t values (null, null)
insert into #t values (2, null)
insert into #t values (3, null)
insert into #t values (4, null)
declare #t2 as table (
[id] INT
,val int
)
insert into #t2 values (null, 11)
insert into #t2 values (2, 22)
insert into #t2 values (3, 33)
select * from #t
select * from #t2
update t
set t.val = t2.val
from #t as t join #t2 as t2
on t.id = t2.id
or
(
(t.id is null or t.id not in (select id from #t2))
and t2.id is null
)
select * from #t
here is result
--#t
id val
---------------
NULL NULL
2 NULL
3 NULL
4 NULL
--#t2
id val
---------------
NULL 11
2 22
3 33
--#t after update
id val
---------------
NULL 11
2 22
3 33
4 NULL
how to make val in last row equal 11?
4 11
This solution left joins to t2 twice and then does a coalesce.
The ON on the second join matches on records that failed on the join and then looks for the "Default" case.
UPDATE t
set t.val = COALESCE(t2.val,t3.val)
from #t as t
LEFT join #t2 as t2
on t.id = t2.id
LEFT JOIN #t2 t3
ON t2.id is null and t3.id is null
See it working here
try this for the update...
update t
set t.val = t2.val
from #t as t join #t2 as t2
on t.id = t2.id
or
(
(t.id is null or not exists (select * from #t2 where id = t.id))
and t2.id is null
)
Problem is with not in operator and null values. This would also work...
update t
set t.val = t2.val
from #t as t join #t2 as t2
on t.id = t2.id
or
(
(t.id is null or t.id not in (select id from #t2 where id is not null))
and t2.id is null
)
Here's a technique that may help.
Start with the kind of simple code you want to be writing:
MERGE INTO #t AS target
USING source
ON target.id = source.id
WHEN MATCHED THEN
UPDATE
SET val = source.val;
Then write a table expression (source) that satisfies the requirements.
Requirement 1: "joining by id"
-- simple existential quantification e.g.
SELECT id, val
FROM #t2 AS T2
WHERE id IN ( SELECT id FROM #t )
Requirement 2: "If I can not join then use value from default ID (default iD is the Id that is null)"
-- first find the id values in the target that do not exist in the source:
SELECT id
FROM #t
EXCEPT
SELECT id
FROM #t2
then cross join the result with the row from the source where Id is null:
SELECT DT1.id, T2.val
FROM ( SELECT id
FROM #t
EXCEPT
SELECT id
FROM #t2 ) AS DT1,
#t2 AS T2
WHERE T2.id IS NULL
At this point you will want to query some test data to ensure each query satisfies its respective requirement.
Union the above two results to form a single table expression:
SELECT id, val
FROM #t2 AS T2
WHERE id IN ( SELECT id
FROM #t )
UNION
SELECT DT1.id, T2.val
FROM ( SELECT id
FROM #t
EXCEPT
SELECT id
FROM #t2 ) AS DT1,
#t2 AS T2
WHERE T2.id IS NULL
Then plug the table expression into the MERGE boilerplate code:
WITH source
AS
(
SELECT id, val
FROM #t2 AS T2
WHERE id IN ( SELECT id
FROM #t )
UNION
SELECT DT1.id, T2.val
FROM ( SELECT id
FROM #t
EXCEPT
SELECT id
FROM #t2 ) AS DT1,
#t2 AS T2
WHERE T2.id IS NULL
)
MERGE INTO #t AS target
USING source
ON target.id = source.id
WHEN MATCHED THEN
UPDATE
SET val = source.val;

SQL Query for Grouping the results based on sequence

I have a table like this:
ID Seq Amt
1 1 500
1 2 500
1 3 500
1 5 500
2 10 600
2 11 600
3 1 700
3 3 700
I want to group the continuous sequence numbers into a single row like this:
ID Start End TotalAmt
1 1 3 1500
1 5 5 500
2 10 11 1200
3 1 1 700
3 3 3 700
Please help to achieve this result.
WITH numbered AS (
SELECT
ID, Seq, Amt,
SeqGroup = ROW_NUMBER() OVER (PARTITION BY ID ORDER BY Seq) - Seq
FROM atable
)
SELECT
ID,
Start = MIN(Seq),
[End] = MAX(Seq),
TotalAmt = SUM(Amt)
FROM numbered
GROUP BY ID, SeqGroup
ORDER BY ID, Start
;
Well, there's perhaps a more elegant way to do this (something hints at me that there is), but here's an approach that will work if you're using a version of SQL Server that accepts common table expressions:
use Tempdb
go
create table [Test]
(
[id] int not null,
[Seq] int not null,
[Amt] int not null
)
insert into [Test] values
(1, 1, 500),
(1, 2, 500),
(1, 3, 500),
(1, 5, 500),
(2, 10, 600),
(2, 11, 600),
(3, 1, 700),
(3, 3, 700)
;with
lower_bound as (
select *
from Test
where not exists (
select *
from Test as t1
where t1.id = Test.id and t1.Seq = Test.Seq - 1
)
),
upper_bound as (
select *
from Test
where not exists (
select *
from Test as t1
where t1.id = Test.id and t1.Seq = Test.Seq + 1
)
),
bounds as (
select id, (select MAX(seq) from lower_bound where lower_bound.id = upper_bound.id and lower_bound.Seq <= upper_bound.Seq) as LBound, Seq as Ubound
from upper_bound
)
select Test.id, LBound As [Start], UBound As [End], SUM(Amt) As TotalAmt
from Test
join bounds
on Test.id = bounds.id
and Test.Seq between bounds.LBound and bounds.Ubound
group by Test.id, LBound, UBound
drop table [Test]
This seems to work nicely. #breakingRows will contain all rows that break the sequence of id and seq (i.e. if id changes or if seq is not 1 more than the previous seq). With that table you can select all rows of such a sequence within #temp. I must add however that performance will probably be not all that good because of all the subqueries but you'll need to test to be sure.
declare #temp table (id int, seq int, amt int)
insert into #temp select 1, 1, 500
insert into #temp select 1, 2, 500
insert into #temp select 1, 3, 500
insert into #temp select 1, 5, 500
insert into #temp select 2, 10, 600
insert into #temp select 2, 11, 600
insert into #temp select 3, 1, 700
insert into #temp select 3, 3, 700
declare #breakingRows table (ctr int identity(1,1), id int, seq int)
insert into #breakingRows(id, seq)
select id, seq
from #temp t1
where not exists
(select 1 from #temp t2 where t1.id = t2.id and t1.seq - 1 = t2.seq)
order by id, seq
select br.id, br.seq as start,
isnull ((select top 1 seq from #temp t2
where id < (select id from #breakingRows br2 where br.ctr = br2.ctr - 1) or
(id = (select id from #breakingRows br2 where br.ctr = br2.ctr - 1) and
seq < (select seq from #breakingRows br2 where br.ctr = br2.ctr - 1))
order by id desc, seq desc),
br.seq)
as [end],
(select SUM(amt) from #temp t1 where t1.id = br.id and
t1.seq <
isnull((select seq from #breakingRows br2 where br.ctr = br2.ctr - 1 and br.id = br2.id),
(select max(seq) + 1 from #temp)) and
t1.seq >= br.seq)
from #breakingRows br
order by id, seq
Since Andriy has already posted the gold solution, here's my take using an UPDATE statement to get the result from a temp table, just for fun.
declare #tmp table (
id int, seq int, amt money, start int, this int, total money,
primary key clustered(id, seq))
;
insert #tmp
select *, start=seq, this=seq, total=convert(money,amt)
from btable
;
declare #id int, #seq int, #start int, #amt money
update #tmp
set
#amt = total = case when id = #id and seq = #seq+1 then #amt+total else amt end,
#start = start = case when id = #id and seq = #seq+1 then #start else seq end,
#seq = this = seq,
#id = id = id
from #tmp
option (maxdop 1)
;
select id, start, max(this) [end], max(total) total
from #tmp
group by id, start
order by id, start
Notes:
btable: your table name
id int, seq int, amt money: expected columns in your table
Try following query.
select id, min(seq), max(seq), sum(amt) from table group by id
OOps, sorry, it is wrong query as you need sequence
SELECT Id, MIN(Seq) as Start, MAX(Seq) as End, SUM(Amount) as Total
FROM (
SELECT t.*, Seq - ROW_NUMBER() OVER (PARTITION BY Id ORDER BY Seq) Rn
FROM [Table] t
) as T
GROUP BY Id, Rn
ORDER BY Id, MIN(Seq)