Oracle Order By different columns same select statement - sql

I have an interesting problem here. But it is just for knowledge because I already solve it in a non elegant way.
I have a table that have costumers and they can be holders or dependants and this relation is described as a
family. Each family can have only a holder and 0-n dependants. A holder is identified by an H and a Dependant by a D.
What I need is a way to order the data by name of holder and theirs dependants. So the sample data below
idcostumer name idfamily relation
1 Natalie Portman 1 H
2 Mark Twain 3 D
3 Carl Sagan 2 D
4 Bob Burnquist 2 H
5 Sheldon Cooper 1 D
6 Anakin Skywalker 4 H
7 Luke Skywalker 4 D
8 Leia Skywalker 4 D
9 Burnquist Jr. 2 D
10 Micheal Jackson 3 H
11 Sharon Stone 1 H
12 Michelle Pfeiffer 3 D
Is it possible to get the above results in just one query? As you can see the order is name (just for the holders)
idcostumer name idfamily relation
6 Anakin Skywalker 4 H
8 Leia Skywalker 4 D
7 Luke Skywalker 4 D
4 Bob Burnquist 2 H
9 Burnquist Jr. 2 D
3 Carl Sagan 2 D
10 Micheal Jackson 3 H
2 Mark Twain 3 D
12 Michelle Pfeiffer 3 D
11 Sharon Stone 1 H
1 Natalie Portman 1 D
5 Sheldon Cooper 1 D
The test case data for this example.
create table costumer (
idcostumer integer primary key,
name varchar2(20),
idfamily integer,
relation varchar2(1)
);
This is the inserts statments for this table:
insert into costumer values ( 1 , 'Natalie Portman' , 1, 'D');
insert into costumer values ( 2 , 'Mark Twain' , 3, 'D');
insert into costumer values ( 3 , 'Carl Sagan' , 2, 'D');
insert into costumer values ( 4 , 'Bob Burnquist' , 2, 'H');
insert into costumer values ( 5 , 'Sheldon Cooper' , 1, 'D');
insert into costumer values ( 6 , 'Anakin Skywalker' , 4, 'H');
insert into costumer values ( 7 , 'Luke Skywalker' , 4, 'D');
insert into costumer values ( 8 , 'Leia Skywalker' , 4, 'D');
insert into costumer values ( 9 , 'Burnquist Jr.' , 2, 'D');
insert into costumer values ( 10, 'Micheal Jackson' , 3, 'H');
insert into costumer values ( 11, 'Sharon Stone' , 1, 'H');
insert into costumer values ( 12, 'Michelle Pfeiffer', 3, 'D');
I've tried some things, create a father sun relationship with connect by statement and familyid concatenated with the relation.
Used a row_count with a over clause ordering by relation desc and family id, but this way I lost the name order.

If I understand you correctly, you want to first order the families by the name of the holder, then by the names of the dependents. The following does that.
with family_order as (
select idfamily, rownum r from (
select idfamily from costumer where relation = 'H' order by name
)
)
select c.* from costumer c
inner join family_order fo on c.idfamily = fo.idfamily
order by fo.r, relation desc, name
Fiddle here

Try:
select * from table order by idfamily desc, relation desc, name asc
Link to Fiddle
For un-natural order you can use "union all":
select * from (select idcostumer, name, idfamily, relation from costumer
where idfamily > 3
order by idfamily desc, relation desc, name asc)
union all
select * from (
select idcostumer, name, idfamily, relation from costumer
where idfamily = 2
order by idfamily desc, relation desc, name asc)
union all
select * from (
select idcostumer, name, idfamily, relation from costumer
where idfamily != 2 and idfamily < 4
order by idfamily desc, relation desc, name asc)
Link to Fiddle

Related

How to get next row with id [duplicate]

This question already has answers here:
Is there a way to access the "previous row" value in a SELECT statement?
(9 answers)
Closed 4 months ago.
Lets say I have a table like this;
Id
Name
1
John
2
Doe
5
Rose
11
Michael
15
Pedro
and my select query like this;
Id
Name
1
John
5
Rose
I want to select next rows according to my query which like this;
Id
Name
2
Doe
11
Michael
1 Johns next row is 2 Doe and 5 Roes's next row 11 Michael
One of many ways to do this:
WITH
RowNumbers AS (
SELECT
*,
ROW_NUMBER() OVER (ORDER BY ID) AS RowNumber
FROM
TableName -- replace with your table name
)
SELECT
Id,
Name
FROM
RowNumbers
WHERE
RowNumber IN (
SELECT
RowNumber+1
FROM
RowNumbers
WHERE
Name IN ('John', 'Rose')
)
;
You could use a CTE to create row_numbers and then select and join to it.
create table my_data (
id integer,
name varchar(20)
);
insert into my_data (id, name) values
(1, 'john'),
(2, 'doe'),
(5, 'rose'),
(11, 'michael'),
(15, 'pedro');
with row_data as (
select id, name,
row_number() over (order by id) as rn
from my_data
)
select b.id, b.name
from row_data a
join row_data b
on a.rn + 1 = b.rn
where a.name in ('john','rose')
id
name
2
doe
11
michael
fiddle

excluding dups which are lower than max values in SQL

I have the following simple table (Table1), where each row is a student_ID and their name, and each student has one or multiple wins (Wins). I would like to output: Student_ID, Student_name, count of Wins, sorted by count of Wins (descending) and then Student_ID (ascending), excluding those students who have the same count of Wins which is less than the max of the Wins (i.e.5). In other words, Lizzy and Mark have the same count of wins, and 3 is lower than 5, so the output will exclude the two students, Lizzy and Mark.
From comments: "Betty, David and Cathy should be excluded", also.
Table1:
student_id
student_name
wins
1
John
YES
1
John
YES
1
John
YES
1
John
YES
1
John
YES
2
Brandon
YES
2
Brandon
YES
2
Brandon
YES
2
Brandon
YES
2
Brandon
YES
3
Lizzy
YES
3
Lizzy
YES
3
Lizzy
YES
4
Mark
YES
4
Mark
YES
4
Mark
YES
5
Betty
YES
6
David
YES
7
Cathy
YES
8
Joe
YES
8
Joe
YES
Desired output:
student_id
student_name
cnt_wins
1
John
5
2
Brandon
5
8
Joe
2
Here is my SQL in Oracle. I can't figure out what went wrong. The log says "(SELECT b.cnt_wins, count(b.student_id) has too many values".
WITH st_cte AS
(SELECT student_id, student_name, count(wins) cnt_wins
FROM Table1
GROUP BY student_id, student_name
ORDER BY count(wins) DESC, student_id)
SELECT *
FROM st_cte a
WHERE a.cnt_wins not in
(SELECT b.cnt_wins, count(b.student_id)
FROM st_cte b
WHERE b.cnt_wins <
(SELECT max(c.cnt_wins) FROM st_cte c)
GROUP BY b.cnt_wins
HAVING count(b.student_id) > 1);
There are too many values selected inside the 'in' select:
WHERE a.cnt_wins -- 1 value
not in
(SELECT b.cnt_wins, count(b.student_id) -- 2 values
FROM st_cte b
you shoud either do :
WHERE a.cnt_wins not in
(SELECT b.cnt_wins
FROM st_cte ...
or
WHERE (a.cnt_wins, count(something)) not in
(SELECT b.cnt_wins, count(b.student_id)
FROM st_cte ...
Updated based on updated requirements...
The requirement was ambiguous in that Betty, David, and Cathy seem to also meet the criteria to be removed from the result. This requirement was clarified and those rows should have been removed.
Logic has been added to allow only all max_cnt rows, plus any students with a unique count.
Also note that if wins can be any other non-null value, COUNT(wins) is not correct.
Given all that, maybe something like this is a starting point:
Fiddle
WITH cte AS (
SELECT student_id, student_name
, COUNT(wins) cnt_wins
, MAX(COUNT(wins)) OVER () AS max_cnt
FROM Table1
GROUP BY student_id, student_name
)
, cte2 AS (
SELECT cte.*
, COUNT(*) OVER (PARTITION BY cnt_wins) AS cnt_students
FROM cte
)
SELECT student_id, student_name, cnt_wins
FROM cte2
WHERE max_cnt = cnt_wins
OR cnt_students = 1
ORDER BY cnt_wins DESC, student_id
;
and to handle wins that can be other non-null values:
WITH cte AS (
SELECT student_id, student_name
, COUNT(CASE WHEN wins = 'YES' THEN 1 END) cnt_wins
, MAX(COUNT(CASE WHEN wins = 'YES' THEN 1 END)) OVER () AS max_cnt
FROM Table1
GROUP BY student_id, student_name
)
, cte2 AS (
SELECT cte.*
, COUNT(*) OVER (PARTITION BY cnt_wins) AS cnt_students
FROM cte
)
SELECT student_id, student_name, cnt_wins
FROM cte2
WHERE max_cnt = cnt_wins
OR cnt_students = 1
ORDER BY cnt_wins DESC, student_id
;
Result (with data to test the new requirement, one student (Joe) with unique counts (2)):
STUDENT_ID
STUDENT_NAME
CNT_WINS
1
John
5
2
Brandon
5
8
Joe
2
Setup:
CREATE TABLE table1 (
Student_ID int
, Student_Name VARCHAR2(20)
, Wins VARCHAR2(10)
);
BEGIN
-- Assume only wins are stored.
INSERT INTO table1 VALUES ( 1, 'John', 'YES');
INSERT INTO table1 VALUES ( 1, 'John', 'YES');
INSERT INTO table1 VALUES ( 1, 'John', 'YES');
INSERT INTO table1 VALUES ( 1, 'John', 'YES');
INSERT INTO table1 VALUES ( 1, 'John', 'YES');
INSERT INTO table1 VALUES ( 2, 'Brandon', 'YES');
INSERT INTO table1 VALUES ( 2, 'Brandon', 'YES');
INSERT INTO table1 VALUES ( 2, 'Brandon', 'YES');
INSERT INTO table1 VALUES ( 2, 'Brandon', 'YES');
INSERT INTO table1 VALUES ( 2, 'Brandon', 'YES');
INSERT INTO table1 VALUES ( 3, 'Lizzy', 'YES');
INSERT INTO table1 VALUES ( 3, 'Lizzy', 'YES');
INSERT INTO table1 VALUES ( 3, 'Lizzy', 'YES');
INSERT INTO table1 VALUES ( 4, 'Mark', 'YES');
INSERT INTO table1 VALUES ( 4, 'Mark', 'YES');
INSERT INTO table1 VALUES ( 4, 'Mark', 'YES');
INSERT INTO table1 VALUES ( 5, 'Betty', 'YES');
INSERT INTO table1 VALUES ( 6, 'David', 'YES');
INSERT INTO table1 VALUES ( 7, 'Cathy', 'YES');
INSERT INTO table1 VALUES ( 8, 'Joe', 'YES');
INSERT INTO table1 VALUES ( 8, 'Joe', 'YES');
END;
/
Correction to the original query in the question:
WITH st_cte AS
(SELECT student_id, student_name, count(wins) cnt_wins
FROM Table1
GROUP BY student_id, student_name
ORDER BY count(wins) DESC, student_id
)
SELECT *
FROM st_cte a
WHERE a.cnt_wins not in
(SELECT b.cnt_wins
FROM st_cte b
WHERE b.cnt_wins < (SELECT max(c.cnt_wins) FROM st_cte c)
GROUP BY b.cnt_wins
HAVING count(b.student_id) > 1
)
;

SQL View to flatten out a hierarchy

I have a table with some parent child relationships. I want to create a view that has all possible ids for a location id.
I need the sql to do this
Table:
ID PARENT_ID LOCATION_ID
1 NULL ABC
2 1 XYZ
3 NULL EFG
view results:
LOCATION_ID ID
XYZ 1
XYZ 2
ABC 1
ABC 2
EFG 3
You don't mention the database you are using, so I'll assume PostgreSQL. You can adjust the answer to your specific engine:
with recursive
n as (
select id, id as grp, location_id from t where parent_id is null
union all
select t.id, n.grp, t.location_id
from n
join t on t.parent_id = n.id
)
select b.id, a.location_id
from n a
join n b on a.grp = b.grp
Result:
id location_id
-- -----------
1 ABC
2 ABC
1 XYZ
2 XYZ
3 EFG
For the record, the data script I used is:
create table t (
id int,
parent_id int,
location_id varchar(10)
);
insert into t (id, parent_id, location_id) values
(1, null, 'ABC'),
(2, 1, 'XYZ'),
(3, null, 'EFG');

How to Get All Data along with Group BY Count in SQL Server

So I have table with ID, CustomerID, Name, Salary
Way I want data back is All Data with New Column that shows total of records group by CustomerID,
ID CustomerID Name Salary
1 1 John 3000
2 1 Kim 1000
3 2 Sarah 2000
4 2 Jim 4000
5 2 Kane 2000
6 3 Bul 2500
So I want something like this, new column that show group by total of records,
ID CustomerID Name Salary Count
1 1 John 3000 2
2 1 Kim 1000 2
3 2 Sarah 2000 3
4 2 Jim 4000 3
5 2 Kane 2000 3
6 3 Bul 2500 1
You are looking for window functions, in this case, count():
select t.*, count(*) over (partition by CustomerId) as cnt
from table t;
select
ID,CustomerID,Name,Salary, COUNT(CustomerID) Count
from tableName
group by ID,CustomerID,Name,Salary
You can just join to a subquery that calculates the counts:
CREATE TABLE #customer
(
[ID] INT ,
[CustomerID] INT ,
[Name] VARCHAR(5) ,
[Salary] INT
);
INSERT INTO #customer
( [ID], [CustomerID], [Name], [Salary] )
VALUES ( 1, 1, 'John', 3000 ),
( 2, 1, 'Kim', 1000 ),
( 3, 2, 'Sarah', 2000 ),
( 4, 2, 'Jim', 4000 ),
( 5, 2, 'Kane', 2000 ),
( 6, 3, 'Bul', 2500 );
SELECT c.* ,
t.TheCount
FROM #customer c
INNER JOIN ( SELECT CustomerID ,
COUNT(*) AS TheCount
FROM #customer
GROUP BY CustomerID
) t ON t.CustomerID = c.CustomerID
DROP TABLE #customer
Try this:
SELECT t.*, c.[Count]
FROM tbl t
INNER JOIN
(
SELECT CustomerID, Count(1) AS [Count]
FROM tbl
GROUP BY CustomerID
)c ON t.CustomerID = c.CustomerID

SQL Server Select Get Single Row From Another Table

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