SQL Get row number from the main table - sql

Using SQL, how can I get the actual row number of the data from the Master table, when I combined two tables (Master-table and Table-2). Please see illustration below:
Table-1 (Master Table)
Name Age
---------- ------
1 Ruth | 45
2 Jennifer | 52
3 Isabel | 29
4 Jo | 59
5 Dan | 35
6 Lem | 26
Table-2
Name Color
---------- ------
1 Ruth | Blue
2 Jennifer | Blue
3 Isabel | Red
4 Jo | Blue
5 Dan | Red
6 Lem | Blue
The result I want to get: (1) get records where color is blue, (2) new column (Row_Number from Table-1):
Name Age Color Row_Number
---------- ------ -------- ------------
1 Ruth | 45 | Blue | 1
2 Jennifer | 52 | Blue | 2
3 Jo | 59 | Blue | 4
4 Lem | 26 | Blue | 6

This would solve your issue :
with all_users as
(
select *, row_number over(order by name/*order by whatever you want*/) as seq
from table1
) select *, all_users.seq
from table2
join all_users using(name);
You could also use a subquery, but I prefer with when using Oracle.

use row_nummber(), but you need to specify your sorting condition
select t.name,t.age,tbl.color,tbl.rn as rownumber
from table2 t
join (select name,color,row_nummber() over (order by name) as rn from table1) tbl
on t.name=tbl.name
EDIT: and if you you don't have any sort condition (as mentioned in comment) then you can simply use rownum without specifying sort condition:
select t.name,t.age,tbl.color,tbl.rn as rownumber
from table2 t
join (select name,color,rownum as rn from table1) tbl
on t.name=tbl.name

Related

Calculate how many rows are ahead of position in column when condition is met

How can I calculate how many people are ahead of Jane on Floor 2 (not including those on floor 1)?
+------+---------+----------+
|Index | Name | Floor |
+------+---------+----------+
| 1 | Sally | 1 |
| 2 | Sue | 1 |
| 3 | Fred | 1 |
| 4 | Wally | 2 |
| 5 | Tommy | 2 |
| 6 | Jane | 2 |
| 7 | Bart | 2 |
| 8 | Sam | 3 |
+------+---------+----------+
The expected result is 2 as there are 2 people (Wally & Tommy) ahead of Jane on floor 2.
I've tried using CHARINDEX to find the row number from a temp table that I've generated but that doesn't seem to work:
SELECT CHARINDEX('Jane', Name) as position
INTO #test
FROM tblExample
WHERE Floor = 2
select ROW_NUMBER() over (order by position) from #test
WHERE position = 1
I think a simple row_number() would do the trick
Select Value = RN-1
From (
Select *
,RN = row_number() over (partition by [floor] order by [index])
From YourTable
Where [Floor]=2
) A
Where [Name]='Jane'
You could do:
select count(*)
from t
where t.floor = 2 and
t.id < (select t2.id from t t2 where t2.name = 'Jane' and t2.floor = 2);
With an index on (floor, name, id), I would expect this to be faster than row_number().

SQL Server - List single row multiple times in query result

I have a dataset in a SQL Server database, and I'd like to list run a query to list records from a table, multiple times according to the respective integer value in a column of that table.
I've seen many of the posts about listing a record multiple times in a query, with most of the more suitable responses describing a solution involving Cross join. The situation I'm describing is slightly different from what I've seen of yet, however.
The table structure of the records is akin to the following:
+-----+-------+------+------+
| id | name | type | num |
+-----+-------+------+------+
| 1 | bob | red | 1 |
+-----+-------+------+------+
| 2 | sam | blue | 3 |
+-----+-------+------+------+
| 3 | viv | green| 2 |
+-----+-------+------+------+
I'd like to display this in a query result as follows...
+-----+-------+------+
| id | name | type |
+-----+-------+------+
| 1 | bob | red |
+-----+-------+------+
| 2 | sam | blue |
+-----+-------+------+
| 2 | sam | blue |
+-----+-------+------+
| 2 | sam | blue |
+-----+-------+------+
| 3 | viv | green|
+-----+-------+------+
| 3 | viv | green|
+-----+-------+------+
... where each record appears multiple times according to the number it is listed in the original row. (bob * 1, sam * 3, viv * 2)
Is this possible via cross-join or any other method available?
Note: This does not need to be terribly efficient.
You can try to use recursive CTE
;with cte as (
SELECT id,name,type,1 startnum,num
FROM T
UNION ALL
SELECT id,name,type , startnum+1,num
FROM cte
WHERE startnum+1<=num
)
SELECT id,name,type
FROM cte
order by id
sqlfiddle
Another option is an ad-hoc tally/numbers table
Example
Select A.ID
,A.[Name]
,A.[Type]
From YourTable A
Cross Apply (Select Top (A.[num]) N=Row_Number() Over (Order By (Select NULL)) From master..spt_values n1) B
Or a simple JOIN
Select A.ID
,A.[Name]
,A.[Type]
From YourTable A
Join (Select Top (100) N=Row_Number() Over (Order By (Select NULL)) From master..spt_values n1) B
on B.N<=A.[Num]
Both would Return
ID Name Type
1 bob red
2 sam blue
2 sam blue
2 sam blue
3 viv green
3 viv green
Use a Stored procedure
create a temporary table variable
Write insert into temporary table in a while loop
while counter < max(num)
insert into table where counter <=num
Finally select from table .
EDITED:
(Thanks, #MatBailie!)
Using a recursive CTE on the num column to generate the required number of repetitions, and doing a left-join:
Given table so1 as follows:
id name type num
1 bob red 1
2 sam blue 3
3 viv green 2
4 jon grey 7
Try:
WITH RECURSIVE exp_num AS
(
SELECT id, num FROM so1
UNION
SELECT id, num - 1 FROM exp_num WHERE num > 1
)
SELECT s1.* FROM so1 s1
LEFT JOIN
(
SELECT id FROM exp_num
) s2
ON s1.id = s2.id
Output:
id name type
1 bob red
2 sam blue
2 sam blue
2 sam blue
3 viv green
3 viv green
4 jon grey
4 jon grey
4 jon grey
4 jon grey
4 jon grey
4 jon grey
4 jon grey

how to select unique records from a table based on a column which has distinct values in another column

I have below table SUBJ_SKILLS which has records like
TCHR_ID | LINE_NBR | SUBJ | SUBJ_TYPE
--------| ------- | ---------- | ----------
1 | 1 | Maths | R
1 | 2 | 101 | U
2 | 1 | BehaviourialTech | U
3 | 2 | Maths | R
4 | 1 | RegionalLANG | U
5 | 3 | ForeignLANG | U
5 | 4 | Maths | R
6 | 2 | Science | R
7 | 1 | 101 | U
7 | 3 | Physics | R
..
..
I am trying to retrieve records like below (i.e. single teacher who taught multiple different subjects)
TCHR_ID | LINE_NBR | SUBJ | SUBJ_TYPE
--------| ------- | ---------- | ----------
5 | 3 | ForeignLANG | U
5 | 4 | Maths | R
7 | 1 | 101 | U
7 | 3 | Physics | R
1 | 1 | Maths | R
1 | 2 | 101 | U
Here, the line numbers are unique, means that TCHR_ID:5 taught Physics (which was LINE_NBR=1, but was removed later). So, the LINE_NBR are not updated and stay as is.
i also have a look up table (SUBJ_LKUP) for subject and their categories/type like below ('R' for Regular subject and 'U' for Unique subject )
SUBJ | SUBJ_TYPE
----------------- | ------------
Maths | R
Physics | R
ForeignLANG | U
101 | U
Science | R
BehaviourialTech | U
RegionalLANG | U
My approach to resolve this was to create a table which have 2 records for Teacher and use another query on base table (SUBJ_SKILLS) and new table to filter out distinct records. I came up with below queries..
Query-1:
create table tchr_with_2_subj as select SS.TCHR_ID
from SUBJ_SKILLS SS, SUBJ_LKUP SL
where SS.SUBJ = SL.SUBJ
and SL.SUBJ_TYPE IN ('R', 'U') AND SS.TCHR_ID IN
(select SS.TCHR_ID from SUBJ_SKILLS SS)
GROUP BY SS.TCHR_ID HAVING COUNT(*) = 2)
Query-2:
select SS.TCHR_ID from SUBJ_SKILLS SS, tchr_with_2_subj tw2s
where SS.TCHR_ID = tw2s.TCHR_ID
GROUP BY SS.TCHR_ID,SS.SUBJ_TYPE HAVING COUNT(*) > 1)
Question:
1)'IN' condition in Query-1 is causing problems and pulling wrong records.
2) Is there a better way to write query to pull matching records using a single query (i.e. instead of creating a table)
Could someone help me on this pls.
For the answer to your original question, I would use window functions:
select ss.*
from (select ss.*,
min(subj) over (partition by tchr_id) as mins,
max(subj) over (partition by tchr_id) as maxs
from SUBJ_SKILLS ss
) ss
where mins <> maxs;
It is unclear how the subject type fits in, but if you need to include that, similar logic will work.
Your second table can be obtained from your first table with:
select ss.*
from
subj_skills as ss
inner join (
select tchr_id
from subj_skills
group by tchr_id
having count(*) > 1
) as mult on mult.tchr_id=ss.tchr_id;
I'd use analytic functions here, asomething like:
select tchr_id, line_nbr, subj, SUBJ_TYPE
from (select count(distinct subj) over (partition by tchr_id) as grp_cnt,
s.*
from subj_skills s)
where grp_cnt > 1
If you need to filter out invalid records, you can do it in the inner query. If a teacher cannot teach the same subject multiple times (the req 'multiple different subjects' can be translated to 'multiple subjects'), then I'd rather use count(*) instead of count(distinct subj).

how to convert a table into another based on each Columns in SQL

I have the following table(each name is unique):
TABLE1:
+----+----------+----------------+----------------+----------------+----------------+
| id | workflow | tire1_approver | tire2_approver | tire3_approver | tire4_approver |
+----+----------+----------------+----------------+----------------+----------------+
| 1 | 1 | John | Mike | Tom | Kevin |
+----+----------+----------------+----------------+----------------+----------------+
| 2 | 2 | Mike | Andrew | An | Bob |
+----+----------+----------------+----------------+----------------+----------------+
I need to translate it into the following table, a person can appear more than once:
TABLE2:
+----+--------+----------------+-----------+----------------------+
| ID | Name | Position | Workflow | upper_level_approver |
+----+--------+----------------+-----------+----------------------+
| 1 | John | tire1_approver | 1 | Mike |
+----+--------+----------------+-----------+----------------------+
| 2 | Mike | tire2_approver | 1 | Tom |
+----+--------+----------------+-----------+----------------------+
| 3 | Tom | tire3_approver | 1 | Kevin |
+----+--------+----------------+-----------+----------------------+
| 4 | Kevin | tire4_approver | 1 | N/A |
+----+--------+----------------+-----------+----------------------+
| 5 | Mike | tire1_approver | 2 | Andrew |
+----+--------+----------------+-----------+----------------------+
| 6 | Andrew | tire2_approver | 2 | An |
+----+--------+----------------+-----------+----------------------+
| 7 | An | tire3_approver | 2 | Bob |
+----+--------+----------------+-----------+----------------------+
| 8 | Bob | tire4_approver | 2 | N/A |
+----+--------+----------------+-----------+----------------------+
I'm using sql developer, i have tried loop and join but aren't able to get to what I want.
TABLE1 and TABLE2 are both in the database,
Ultimately I would like to store this in the a stored Procedure, when the Front end makes an update to TABLE1, it also calls this procedure and automatically updates TABLE2.
please help
Here is a solution using a cross join (instead of the unpivot operator). This will work in older versions of Oracle. Simulated data and query output are the same as in my other answer (with unpivot).
The OP did not mention whether null is possible in the input table (in the approver columns). If it is possible, this cross join solution will handle them differently from the unpivot solution. The unpivot solution can be modified to produce the same result as the cross join, by using the optional include nulls directive in the unpivot clause. Or, if the null values should not be included, that can be handled in the cross join solution with a where condition.
select id, name, position, workflow,
lead(name, 1, 'N/A') over (partition by id, workflow order by lvl)
as upper_level_approver
from ( select t.id,
case h.lvl when 1 then t.tire1_approver
when 2 then t.tire2_approver
when 3 then t.tire3_approver
when 4 then t.tire4_approver
end
as name,
case h.lvl when 1 then 'tire1_approver'
when 2 then 'tire2_approver'
when 3 then 'tire3_approver'
when 4 then 'tire4_approver'
end
as position,
t.workflow,
h.lvl
from table1 t
cross join
( select level as lvl from dual connect by level <= 4 ) h
)
;
Depending on your Oracle version, you may or may not be able to use the solution below. It uses the UNPIVOT operator, so it requires Oracle 11.2 or higher. In earlier versions, you can use a cross join.
with
table1 ( id, workflow, tire1_approver, tire2_approver,
tire3_approver, tire4_approver )
as (
select 1, 1, 'John', 'Mike' , 'Tom', 'Kevin' from dual union all
select 2, 2, 'Mike', 'Andrew', 'An' , 'Bob' from dual
)
-- End of simulated table (for testing; not part of the solution).
-- SQL query begins BELOW THIS LINE
select id, name, position, workflow,
lead(name, 1, 'N/A') over ( partition by id, workflow order by lvl )
as upper_level_approver
from table1
unpivot ( name for (position, lvl) in ( tire1_approver as ('tire1_approver', 1),
tire2_approver as ('tire2_approver', 2),
tire3_approver as ('tire3_approver', 3),
tire4_approver as ('tire4_approver', 4)
)
)
;
ID NAME POSITION WORKFLOW UPPER_LEVEL_APPROVER
-- ------ -------------- --------- --------------------
1 John tire1_approver 1 Mike
1 Mike tire2_approver 1 Tom
1 Tom tire3_approver 1 Kevin
1 Kevin tire4_approver 1 N/A
2 Mike tire1_approver 2 Andrew
2 Andrew tire2_approver 2 An
2 An tire3_approver 2 Bob
2 Bob tire4_approver 2 N/A

SQL distinct/groupby on combination of columns

I am trying to do a SQL select on a table based on two columns, but not in the usual way where the combination of values in both columns must be unique; I want to select where the value can only appear once in either column.
Given the dataset:
|pkid | fkself | otherData |
|-----+--------+-----------|
| 1 | 4 | there |
| 4 | 1 | will |
| 3 | 6 | be |
| 2 | 5 | other |
| 5 | 2 | data |
| 6 | 3 | columns |
I need to return either
|pkid | fkself | otherData |
|-----+--------+-----------|
| 1 | 4 | there |
| 3 | 6 | be |
| 2 | 5 | other |
or
|pkid | fkself | otherData |
|-----+--------+-----------|
| 4 | 1 | will |
| 5 | 2 | data |
| 6 | 3 | columns |
The only way I can think of to do this is to concatenate `pkid and fkid in order so that both row 1 and row 2 would concatenate to 1,4, but I'm not sure how to do that, or if it is even possible.
The rows will have other data columns, but it does not matter which row I get, only that I get each ID only once, whether the value is in pkid or fkself.
You can use least and greatest to get the smallest or biggest value of the two. That allows you to put them in the right order to generate those keys for you. You could concatenate the values as you suggested, but it's not needed in this solution. With dense_rank you can generate a sequence for each of those fictional keys. Then, you can get the first OtherData from that sequence.
select
pkid,
fkself,
otherData
from
(select
pkid,
fkself,
otherData,
dense_rank() over (partition by least(pkid, fkself), greatest(pkid, fkself) order by pkid) as rank
from
YourTable t)
where
rank = 1
Your idea is possible, and it should produce the results you want.
SELECT DISTINCT joinedID
FROM (
SELECT min(id) & "," & max(id) as joinedID
FROM (
SELECT pkid as id, someUniqueValue
FROM table
UNION ALL
SELECT fkself as id, someUniqueValue
FROM table)
GROUP BY someUniqueValue )
This will give you a unique list of IDs, concatenated as you like. You can easily include other fields by adding them to each SELECT statement. Also, someUniqueValue can be either an existing unique field, a new unique field, or the concatenated pkid and fkself, if that combination is unique.
The only way I can think of to do this is to concatenate `pkid and
fkid in order so that both row 1 and row 2 would concatenate to 1,4,
but I'm not sure how to do that, or if it is even possible.
You could do it using a CASE statement in Oracle:
SQL> SELECT * FROM sample
2 /
PKID FKSELF
---------- ----------
1 4
4 1
3 6
2 5
5 2
7 7
6 rows selected.
SQL> l
1 SELECT DISTINCT *
2 FROM (
3 SELECT CASE WHEN pkid <= fkself THEN pkid||','||fkself
4 ELSE fkself||','||pkid
5 END "JOINED"
6 FROM sample
7* )
SQL> /
JOINED
-------------------------------------------------------------------------------
1,4
2,5
3,6
7,7