SQL Server Select Get Single Row From Another Table - sql

Needing some help with SQL Server select query here.
I have the following tables defined:
UserSource
UserSourceID ID Name Dept SourceID
1 1 John AAAA 1
2 1 John AAAA 2
3 2 Nena BBBB 1
4 2 Nena BBBB 2
5 3 Gord AAAA 2
6 3 Gord AAAA 1
7 4 Stan CCCC 3
Source
SourceID Description RankOrder
1 FromHR 1
2 FromTemp 2
3 Others 3
Need to join both tables and select only the row where the rank is the smallest. Such that the resulting row would be:
UserSourceID ID Name Dept SourceID Description RankOrder
1 1 John AAAA 1 FromHR 1
3 2 Nena BBBB 1 FromHR 1
6 3 Gord AAAA 1 FromHR 1
7 4 Stan CCCC 3 Others 3
TIA.
Edit:
Here's what I have come up so far, but I seem to be missing something:
WITH
TableA AS(
SELECT 1 AS UserSourceID, 1 AS ID, 'John' AS [Name], 'AAAA' as [Dept], 1 as SourceID
UNION SELECT 2, 1, 'John', 'AAAA', 2
UNION SELECT 3, 2, 'Nena', 'BBBB', 1
UNION SELECT 4, 2, 'Nena', 'BBBB', 2
UNION SELECT 5, 3, 'Gord', 'AAAA', 2
UNION SELECT 6, 3, 'Gord', 'AAAA', 1
UNION SELECT 7, 4, 'Stan', 'DDDD', 3)
,
TableB AS(
SELECT 1 as SourceID, 'FromHR' as [Description], 1 as RankOrder
UNION SELECT 2, 'FromTemp', 2
UNION SELECT 3, 'Others', 3
)
SELECT DISTINCT tblA.*, tblB.SourceID, tblB.Description
FROM TableB tblB
JOIN TableA tblA ON tblA.SourceID = tblB.SourceID
LEFT JOIN TableB b2 ON b2.SourceID = tblB.SourceID
AND B2.RankOrder < tblB.RankOrder
WHERE B2.SourceID IS NULL
UPDATE:
I scanned the tables and there might be some variations of data. I have updated the data for the question as above.
Practically, I need to join these two tables, and be able to only select the row which would have the least RankOrder. In case of record UserSourceID = 7, that particular record would be selected because there's only one row that exists after the tables have been joined.

I use windowed aggregates for this type of solution pretty regularly. ROW_NUMBER will order and number the rows based on the PARTITION and ORDER you specify in the OVER clause.
select UserSoruceID
, ID
, Name
, Dept
, SourceID
, Description
, RankOrder
FROM (SELECT UserSoruceID
, ID
, Name
, Dept
, u.SourceID
, Description
, RankOrder
, ROW_NUMBER() over(PARTITION BY ID ORDER BY RankOrder) ranknum
FROM UserSource u
INNER JOIN
Source s
on s.SourceID = u.SourceID ) a
WHERE ranknum = 1
So in this case, for every ID, number the rows based on RankOrder, and then filter where so you only view the first row.
Here's a helpful link to that function from Microsoft. ROW_NUMBER
----UPDATE----
Here's with Rank and Row Number as options.
select UserSoruceID
, ID
, Name
, Dept
, SourceID
, Description
, RankOrder
FROM (SELECT UserSoruceID
, ID
, Name
, Dept
, u.SourceID
, Description
, RankOrder
, ROW_NUMBER() over(PARTITION BY ID ORDER BY RankOrder) row_num
, RANK() over(PARTITION BY ID ORDER BY RankOrder) rank_num --use this if you want to see the duplicate records
FROM UserSource u
INNER JOIN
Source s
on s.SourceID = u.SourceID ) a
WHERE row_num = 1 --rank_num = 1
Replace row_num with rank_num to view any items with duplicate RankOrder entries

Related

Query to display one to many result in a single table

Ive been trying to use the GROUP function and also PIVOT but I cannot wrap my head around how to merge these tables and combine duplicate rows. Currently my SELECT statement returns results with duplicate UserID rows but I want to consolidate them into columns.
How would I join TABLE1 and TABLE2 into a new table which would look something like this:
NEW TABLE:
UserID Username ParentID 1 ParentID 2
--------- -------- -------- ----------
1 Dave 1 2
2 Sally 3 4
TABLE1:
UserID Username ParentID
--------- -------- --------
1 Dave 1
1 Dave 2
2 Sally 3
2 Sally 4
Table 2:
ParentID Username
--------- --------
1 Sarah
2 Joe
3 Tom
4 Mark
O r a c l e
The with clause is here just to generate some sample data and, as such, it is not a part of the answer.
After joining the tables you can use LAST_VALUE analytic function with windowing clause to get the next PARENT_ID of the user. That column (PARENT_ID_2) contains a value only within the first row of a particular USER_ID (ROW_NUMBER analytic function). Afterwords just filter out rows where PARENT_ID_2 is empty...
Sample data:
WITH
tbl_1 AS
(
Select 1 "USER_ID", 'Dave' "USER_NAME", 1 "PARENT_ID" From Dual Union All
Select 1 "USER_ID", 'Dave' "USER_NAME", 2 "PARENT_ID" From Dual Union All
Select 2 "USER_ID", 'Sally' "USER_NAME", 3 "PARENT_ID" From Dual Union All
Select 2 "USER_ID", 'Sally' "USER_NAME", 4 "PARENT_ID" From Dual
),
tbl_2 AS
(
Select 1 "PARENT_ID", 'Sarah' "USER_NAME" From Dual Union All
Select 2 "PARENT_ID", 'Joe' "USER_NAME" From Dual Union All
Select 3 "PARENT_ID", 'Tom' "USER_NAME" From Dual Union All
Select 4 "PARENT_ID", 'Mark' "USER_NAME" From Dual
)
Main SQL:
SELECT
*
FROM (
SELECT
t1.USER_ID "USER_ID",
t1.USER_NAME "USER_NAME",
t1.PARENT_ID "PARENT_ID_1",
CASE
WHEN ROW_NUMBER() OVER(PARTITION BY t1.USER_ID ORDER BY t1.USER_ID) = 1
THEN LAST_VALUE(t1.PARENT_ID) OVER(PARTITION BY t1.USER_ID ORDER BY t1.USER_ID ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING)
END "PARENT_ID_2"
FROM
tbl_1 t1
INNER JOIN
tbl_2 t2 ON(t1.PARENT_ID = t2.PARENT_ID)
)
WHERE PARENT_ID_2 Is Not Null
... and the Result ...
-- USER_ID USER_NAME PARENT_ID_1 PARENT_ID_2
-- ---------- --------- ----------- -----------
-- 1 Dave 1 2
-- 2 Sally 3 4
The windowing clause in this answer
ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING
takes curent and next row and returns the value defined by the analytic function (LAST_VALUE) taking care of grouping (PARTITION BY) and ordering of the rows. Regards...
This is mySql ver 5.6. Create a concatenated ParentID using group concat then separate the concatenated ParentID (1,2) and (3,4) into ParentID 1 and Parent ID 2.
SELECT t1.UserID,
t1.Username,
SUBSTRING_INDEX(SUBSTRING_INDEX(GROUP_CONCAT(t1.ParentID), ',', 1), ',', -1) AS `ParentID 1`,
SUBSTRING_INDEX(SUBSTRING_INDEX(GROUP_CONCAT(t1.ParentID), ',', 2), ',', -1) as `ParentID 2`
FROM TABLE1 t1
INNER JOIN TABLE2 t2 on t1.ParentID = t2.ParentID
GROUP BY t1.UserID
ORDER BY t1.UserID;
Result:
UserID Username ParentID 1 ParentID 2
1 Dave 1 2
2 Sally 3 4

ORACLE get rows with condition value equals something but not equals to anything else

I have rows that look like .
OrderNo OrderStatus SomeOtherColumn
A 1
A 1
A 3
B 1 X
B 1 Y
C 2
C 3
D 2
I want to return all orders that have only one possible value of orderstatus. For e.g Here order B has only order status 1 SO result should be
B 1 X
B 1 Y
Notes:
Rows can be duplicated with same order status. For e.g. B here.
I am interested in the order having a very peculiar status for e.g. 1 here and not having any other status. So if B had a status of 3 at any point of time it is disqualified.
You can use not exists:
select t.*
from t
where not exists (select 1
from t t2
where t.orderno = t2.orderno and t.OrderStatus = t2.OrderStatus
);
If you just want the orders where this is true, you can use group by and having:
select orderno
from t
group by orderno
having min(OrderStatus) = max(OrderStatus);
If you only want a status of 1 then add max(OrderStatus) = 1 to the having clause.
Here is one way to do it. It does not handle the case where the status can be NULL; if that is possible, you will need to explain how you want it handled.
SQL> create table test_data ( orderno, status, othercol ) as (
2 select 'A', 1, null from dual union all
3 select 'A', 1, null from dual union all
4 select 'A', 3, null from dual union all
5 select 'B', 1, 'X' from dual union all
6 select 'B', 1, 'Y' from dual union all
7 select 'C', 2, null from dual union all
8 select 'C', 3, null from dual union all
9 select 'D', 2, null from dual
10 );
Table created.
SQL> variable input_status number
SQL> exec :input_status := 1
PL/SQL procedure successfully completed.
SQL> column orderno format a8
SQL> column othercol format a8
SQL> select orderno, status, othercol
2 from (
3 select t.*, count(distinct status) over (partition by orderno) as cnt
4 from test_data t
5 )
6 where status = :input_status
7 and cnt = 1
8 ;
ORDERNO STATUS OTHERCOL
-------- ---------- --------
B 1 X
B 1 Y
One way to handle NULL status (if that may happen), if in that case the orderno should be rejected (not included in the output), is to define the cnt differently:
count(case when status != :input_status or status is null then 1 end)
over (partition by orderno) as cnt
and in the outer query change the WHERE clause to a single condition,
where cnt = 0
Count distinct OrderStatus partitioned by OrderNo and show only rows where number equals one:
select OrderNo, OrderStatus, SomeOtherColumn
from ( select t.*, count(distinct orderstatus) over (partition by orderno) cnt
from t )
where cnt = 1
SQLFiddle demo
Just wanted to add something to Gordon's answer, using a stats function:
select orderno
from t
group by orderno
having variance(orderstatus) = 0;

How do I display Rows in a table where all values but the first one for a column is null

So I am trying to pull rows from a table where there are more than one version for an ID that has at least one person for the ID that is not null but the versions that come after it are null.
So, if i had a statement like:
select ID, version, person from table1
the output would be:
ID Version Person
-- ------- ------
1 1 Tom
1 2 null
1 3 null
2 1 null
2 2 null
2 3 null
3 1 Mary
3 2 Mary
4 1 Joseph
4 2 null
4 3 Samantha
The version number can have an infinite value and is not limited.
I want to pull ID 1 version 2/3, and ID 4 Version 2.
So in the case of ID 2 where the person is null for all three rows I don't need these rows. And in the case of ID 3 version 1 and 2 I don't need these rows because there is never a null value.
This is a very simple version of the table I am working with but the "real" table is a lot more complicated with a bunch of joins already in it.
The desired output would be:
ID Version Person
-- ------- ------
1 2 null
1 3 null
4 2 null
The result set that I am looking for is where in a previous version for the same ID there was a person listed but is now null.
You are seeking all rows where the person is not null and that id has null rows, and the not null person version is less than the null version for the same person id:
Edited predicate based on comment
with sample_data as
(select 1 id, 1 version, 'Tom' person from dual union all
select 1, 2, null from dual union all
select 1, 3, null from dual union all
select 2, 1, null from dual union all
select 2, 2, null from dual union all
select 2, 3, null from dual union all
select 3, 1, 'Mary' from dual union all
select 3, 2, 'Mary' from dual union all
select 4, 1, 'Joseph' from dual union all
select 4, 2, null from dual union all
select 4, 3, 'Samantha' from dual)
select *
from sample_data sd
where person is null
and exists
(select 1 from sample_data
where id = sd.id
and person is not null
and version < sd.version);
/* Old predicate
and id in
(select id from sample_data where person is not null);
*/
I think this query translates pretty nicely into what you asked for?
List all the rows (R) where the person is null, but only if a previous row (P) with a non-null name exists.
select *
from table1 r
where r.person is null
and exists(
select 'x'
from table1 p
where p.id = r.id
and p.version < r.version
and p.person is not null
);
I believe the below should work.
select ID, listagg(version, ', ') within group (order by version) as versions
from table1 t1
where 0 < (select count(*) from table1 t1A where t1A.ID = t1.ID and t1A.version is not null)
and 0 < (select count(*) from table1 t1B where t1B.ID = t1.ID and t1B.version is null)
and person is null
group by ID
This should do what you want:
select id, version, person
from
(
select id, version, person,
lag(person, 1) ignore nulls
over (partition by id
order by version) as x
from table1
) dt
where person is null
and x is not null

How to get the max version records?

I have a table like the following:
------------------------------------
Id FId UId Version
1 1 1 1
2 1 2 1
3 1 3 1
4 1 2 2
5 1 3 2
6 1 3 2
7 1 4 2
8 2 1 1
9 2 2 1
then I want the result to be:
--------------------------
FId UId Version
1 2 2
1 3 2
1 4 2
2 1 1
2 2 1
How to write the query based on the max 'Version' of each FId-UId pair?
The following gives the output requested.
select distinct t2.FId, t2.UId, t2.Version
from
(
select FId, max(Version) as "Version"
from MyTable
group by FId
) t1
inner join MyTable t2 on (t1.FId = t2.FId and t1.Version = t2.Version)
order by t2.FId, t2.UId
This will work on SQL 2005 and later:
DECLARE #t TABLE
(Id INT,
Fid INT,
[uid] INT,
[VERSION] INT
)
INSERT #t
SELECT 1,1,1,1
UNION ALL SELECT 2,1,2,1
UNION ALL SELECT 3,1,3,1
UNION ALL SELECT 4,1,2,2
UNION ALL SELECT 5,1,3,2
UNION ALL SELECT 6,1,3,2
UNION ALL SELECT 7,1,4,2
UNION ALL SELECT 8,2,1,1
UNION ALL SELECT 9,2,2,1
;WITH myCTE
AS
(
SELECT *,
RANK() OVER (PARTITION BY Fid
ORDER BY [VERSION] DESC
) AS rnk
FROM #t
)
SELECT DISTINCT Fid, [uid],[VERSION]
FROM myCTE
WHERE rnk = 1
ORDER BY Fid, [uid]
select FId, UId, Version
from MyTable
join (select Fid, Max(Version) as MaxVersion group by Fid) x
on x.FId = MyTable.FId and x.MaxVersion = MyTable.Version
Is the result you show correct- 1,3,2 should appear twice.If you need only once use select distinct
The foll query is working
with t as(
select 1 as id, 1 as fid , 1 as uid, 1 as version union all
select 2 , 1 , 2 , 1 union all
select 3 , 1 , 3 , 1 union all
select 4 , 1 , 2 , 2 union all
select 5 , 1 , 3 , 2 union all
select 6 , 1 , 3 , 2 union all
select 7 , 1 , 4 , 2 union all
select 8 , 2 , 1 , 1 union all
select 9 , 2 , 2 , 1)
select distinct t.fid,t.uid,t.version from t
inner join(
select fid,max(version) as maxversion from t
group by fid)as grp
on t.fid=grp.fid
and t.version=grp.maxversion

Replace beginning words

I have the below tables.
tblInput
Id WordPosition Words
-- ----------- -----
1 1 Hi
1 2 How
1 3 are
1 4 you
2 1 Ok
2 2 This
2 3 is
2 4 me
tblReplacement
Id ReplacementWords
--- ----------------
1 Hi
2 are
3 Ok
4 This
The tblInput holds the list of words while the tblReplacement hold the words
that we need to search in the tblInput and if a match is found then we need to replace
those.
But the problem is that, we need to replace those words if any match is found at the beginning.
i.e. in the tblInput,
in case of ID 1, the words that will be replaced is only 'Hi' and not 'are'
since before 'are', 'How' is there and it is not in the tblReplacement list.
in case of Id 2, the words that will be replaced are 'Ok' & 'This'. Since these both
words are present in the tblReplacement table and after the first word i.e. 'Ok' is
replaced, the second word which is 'This' here comes first in the list of
ID category 2
. Since it is available in the tblReplacement, and is the first word now, so this will
also be replaced.
So the desired output will be
Id NewWordsAfterReplacement
--- ------------------------
1 How
1 are
1 you
2 is
2 me
My approach so far:
;With Cte1 As(
Select
t1.Id
,t1.Words
,t2.ReplacementWords
From tblInput t1
Cross Join tblReplacement t2)
,Cte2 As(
Select Id, NewWordsAfterReplacement = REPLACE(Words,ReplacementWords,'')
From Cte1)
Select * from Cte2 where NewWordsAfterReplacement <> ''
But I am not getting the desired output. It is replacing all the matching words.
Urgent help needed**.( SET BASED )**
I am using SQL Server 2005.
Thanks
I couldnt exactly understand your requirement.But from what I understand this works for the given data
with cte1 as(
select 1 as id, 1 as wp, 'Hi' as words union all
select 1, 2 , 'How' union all
select 1 , 3 , 'are' union all
select 1 , 4 , 'you' union all
select 2 , 1 , 'Ok' union all
select 2 , 2 , 'This' union all
select 2 , 3 , 'is' union all
select 2 , 4 , 'me'
),
cte2 as(
select 1 as id, 'Hi' as rep union all
select 2 , 'are' union all
select 3 , 'Ok' union all
select 4 , 'This')
select ID,words from cte1
except
select ID,words from(
select a.id,a.words,a.wp,RANK() over(partition by a.id order by wp) as rnk
from cte1 as a inner join
cte2 as b on a.words=b.rep
) as x
where x.wp=x.rnk