Order by Lastname and ID - sql

I have two DB tables
FAMILY
------
family_id (PK)
address
....
PERSON
-----
person_id (PK)
lastname
birthday
family_id (FK)
family_role (numeric >1 male, 2 female, 3 male(child), 4 female(child)
....
I want to create an alphabetic family list, in which the lastname of the oldest person must be leading and ordered by the FAMILY_ROLE. I was trying something like:
select *
from person, family
where person.family_id = family.family_id
order by lastname, family_role
Now this is not working, because last names in the same family doesn't have to be the same. So this is working
select *
from person, family
where person.family_id = family.family_id
order by family_id, family_role
But that doesn't give me an alphabetic list.
The output must be something like:
Test street 1 - New York (family_id = 100)
John Bla (family_id = 100 | person_id =10 | family_role=1)
Sara Bla (family_id = 100 | person_id =13 | family_role=2)
----
Apple street 1 - New York (family_id = 45)
Rick Cha (family_id = 45 | person_id =1 | family_role=1)
Lin Cha (family_id = 45 | person_id =5 | family_role=2)
Jean App (family_id = 45 | person_id =3 | family_role=4)
----
Cherry street 114 - New York (family_id = 23)
Becky Cha (family_id = 23 | person_id =122 | family_role=2)
----
Can someone help me? Database version MSSQL 2008.
Also please keep in mind that people can have the same last name, but belong in a different family (see the Cha families in the output example).
Thanks!

You need a way to associate the last name of the oldest person in a family with everyone in the family. You can use ROW_NUMBER() in a cte/subquery for this:
;with cte AS (SELECT *,ROW_NUMBER() OVER(PARTITION BY Family_ID ORDER BY Age DESC) AS RN
FROM Person
)
SELECT p.*,f.*
FROM person p
JOIN family f
ON p.family_id = f.family_id
JOIN cte
ON p.family_id = cte.Family_ID
AND cte.RN = 1
ORDER BY cte.lastname
The ROW_NUMBER() function assigns a number to each row. PARTITION BY is optional, but used to start the numbering over for each value in that group, ie: if you PARTITION BY Family_ID then for each unique Family_ID value the numbering would start over at 1. ORDER BY of course is used to define how the counting should go, and is required in the ROW_NUMBER() function.
So the cte/subquery gives you a way to see who the oldest person is and access their last name in one step, then you can JOIN to this to use in your ORDER BY.
Note: Old-style joins should also be avoided, and so I updated that as well, and I'm assuming you have an Age field in your Person table to determine who the oldest is, the cte may require updating based on actual table structure.

Well, you have to decide how you are going to choose which family member is going to determine the alphabetical placement of the family. For example if family #100 has Jon Doe and Jane Smith, will it come in the Ds alphabetically or the Ss?
You could write the query like this
SELECT f.*,p.*,A.FamilyNameOrderKey FROM
(
SELECT
Min(lastname) As FamilyNameOrderKey, Family_id
FROM
person
Group by Family_id
) As A
INNER JOIN
family f
ON
A.family_id = f.family_id
INNER JOIN
people p
ON
p.family_id = f.family_id
)
Order by A.FamilyNameOrderKey
This will sort by the last name of the alphabetically first last name in each family

Related

How to check how many times some values are duplicated?

I have table like below:
city | segment
------------------
London | A
London | B
New York | A
Berlin | B
Barcelona | C
Barcelona | H
Barcelona | E
Each city should have only one segment, but as you can see there are two cities (London and Barcelona) that have more than one segment.
It is essential that in result table I need only these cities which have > 1 segmnet
As a result I need somethig like below:
city - city based on table above
no_segments - number of segments which have defined city based on table above
segments - segments of defined city based on table above
city
no_segments
segments
London
2
A
B
Barcelona
3
C
H
E
How can I do that in Oracle?
You can use COUNT(*) OVER ()(in order to get number of segments) and ROW_NUMBER()(in order to prepare the results those will be conditionally displayed) analytic functions such as
WITH t1 AS
(
SELECT city,
segment,
COUNT(*) OVER (PARTITION BY city) AS no_segments,
ROW_NUMBER() OVER (PARTITION BY city ORDER BY segment) rn
FROM t
)
SELECT DECODE(rn,1,city) AS city,
DECODE(rn,1,no_segments) AS no_segments,
segment
FROM t1
WHERE no_segments > 1
ORDER BY t1.city, segment
Demo
Another way to do this is:
SELECT NULLIF(CITY, PREV_CITY) AS CITY,
SEGMENT
FROM (SELECT CITY,
LAG(CITY) OVER (ORDER BY CITY DESC) AS PREV_CITY,
SEGMENT,
COUNT(SEGMENT) OVER (PARTITION BY CITY) AS CITY_SEGMENT_COUNT
FROM CITY_SEGMENTS)
WHERE CITY_SEGMENT_COUNT > 1
Using LAG() to determine the "previous" CITY allows us to directly compare the CITY values, which in my mind is clearer that using ROW_NUMBER = 1.
db<>fiddle here
;with cte as (
Select city, count(seg) as cntseg
From table1
Group by city having count(seg) > 1
)
Select a.city, b.cntseg, a.seg
From table1 as a join cte as b
On a.city = b.city

SELECT Top 1 ID, DISTINCT Field

I have a table sample table as follows:
ID | City
--------------
1 | New York
2 | San Francisco
3 | New York
4 | Los Angeles
5 | Atlanta
I would like to select the distinct City AND the TOP ID for each. E.g., conceptually I would like to do the following
SELECT TOP 1 ID, DISTINCT City
FROM Cities
Should give me:
ID | City
--------------
1 | New York
2 | San Francisco
4 | Los Angeles
5 | Atlanta
Because New York appears twice, it's taken the first ID 1 in this instance.
But I get the error:
Column 'Cities.ID' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
Try this way:
SELECT min(ID), City
FROM Cities
Group by City
MIN function is used for choose one of the ID from two New York cities.
You need to have your city in a GROUP BY
SELECT MIN(ID), City
FROM Cities
GROUP BY City
More general solution is to use row_number in order to get other details of table:
select * from
(select *, row_number() over(partition by City order by ID) as rn from Cities)
where rn = 1
But for this particular table just grouping will do the work:
select City, Min(ID) as ID
from Cities
group by City
If you have a complex scenario where Group By cannot use, You could use Row_Number() function with Common Table Expression.
;WITH CTE AS
(
SELECT ID, City, ROW_NUMBER() OVER (PARTITION BY City ORDER BY Id) rn
FROM YourTable
)
SELECT Id, City
FROM CTE
WHERE rn = 1

Create view by joining three tables in SQL

I have three tables STUDENTS, SUBJECTS, RANK ,with data as -
1) STUDENTS [NAME(Primary)]
NAME
--------
Alex
Greg
2) SUBJECTS [ID(Primary)]:
ID
--------
100
101
102
3) RANK [SEQ(Primary), NAME, ID, RANK]
SEQ NAME ID RANK
------ ------- ------ ------
1 Alex 100 A
2 Greg 100 A
3 Greg 101 B
I want to create a view that should display data as
NAME ID RANK
------- ------ ------
Alex 100 A
Alex 101 Z
Alex 102 Z
Greg 100 A
Greg 101 B
Greg 102 Z
So, for every student and for every subject, the View should display the RANK if present in RANK table, else replace the NULL with 'Z'.
I'm a newbie to SQL. So any help in forming the query would be deeply appreciated!
cross join student and subject then left outer join the result with rank to get ranks for all (student, subject) combination. selecting column with NVL OR COALESCE will replace NULL with 'z'.
SELECT st.name,
su.id,
NVL(ra.rank,'Z') Rank, --COALESCE(ra.rank,'Z') Rank
FROM student st
CROSS JOIN subject su
LEFT OUTER JOIN rank ra
ON ra.name = st.name
AND ra.id = su.id
ORDER BY st.name,su.id
Note : ORDER BY can be removed from above query if you don't need.
fiddle
SELECT r.NAME, r.ID, NVL(r.RANK, 'Z')
FROM RANK r, studendts st, SUBJECTS su
WHERE st. NAME = r. NAME
AND su.ID = r.ID
ORDER BY 1,2,3

SQL problem - one name 2 address in the same table

CName | AddressLine
-------------------------------
John Smith | 123 Nowheresville
Jane Doe | 456 Evergreen Terrace
John Smith | 999 Somewhereelse
Joe Bloggs | 1 Second Ave
If i have this table is possible to do a select to put like this
CNAME | Address1 | Address2
John Smith | 123 Nowheresville | 999 Somewhereelse
I'm using oracle
It is considered a bad design (inefficient memory usage) to add a new column for appearance of duplications in just some rows . Maybe you should consider using inner-join and a separate table for the address column!
As your table stands, you cannot use a simple self-join to reduce this to a single line. You can bring back rows that have all of the addresses (so long as you hard-code for a particular maximum number of addresses), but you will always have the same number of rows as there are addresses for a given user (unless you have a way of identifying a single address as "primary").
In order to reduce your result set to a single line, you'll have to provide some way of marking a "first" address. With SQL Server (or similar professional-grade RDBM's), you could use a common table expression with ranking/row numbering functions to do this:
with Addresses as
(select
CName,
AddressLine,
row_number() over (partition by CName order by AddressLine) as RowNum
from YourTable)
select
a1.CName,
a1.AddressLine as Address1,
a2.AddressLine as Address2,
a3.AddressLine as Address3
from Addresses a1
left join Addresses a2 on a2.CName = a1.CName and a2.RowNum = 2
left join Addresses a3 on a3.CName = a1.CName and a3.RowNum = 3
where a1.RowNum = 1
temp = your table name
select distinct cname, addressline as [address1],
(
ISNULL((select addressline from temp where cname = t.cname and addressline != t.addressline), '')
) as address2
from
temp t
The problem is resolve, Frank Kulash in oracle forum solved the problem
Here is the solution:
WITH got_r_num AS
(
SELECT cname, addressline
, ROW_NUMBER () OVER ( PARTITION BY cname
ORDER BY addressline
) AS r_num
FROM table_x
-- WHERE ... -- If you need any filtering, put it here
)
SELECT cname
, MIN (CASE WHEN r_num = 1 THEN addressline END) AS addressline1
, MIN (CASE WHEN r_num = 2 THEN addressline END) AS addressline2
FROM got_r_num
GROUP BY cname
Tanks to all for the help

Get top results for each group (in Oracle)

How would I be able to get N results for several groups in
an oracle query.
For example, given the following table:
|--------+------------+------------|
| emp_id | name | occupation |
|--------+------------+------------|
| 1 | John Smith | Accountant |
| 2 | Jane Doe | Engineer |
| 3 | Jack Black | Funnyman |
|--------+------------+------------|
There are many more rows with more occupations. I would like to get
three employees (lets say) from each occupation.
Is there a way to do this without using a subquery?
I don't have an oracle instance handy right now so I have not tested this:
select *
from (select emp_id, name, occupation,
rank() over ( partition by occupation order by emp_id) rank
from employee)
where rank <= 3
Here is a link on how rank works: http://www.psoug.org/reference/rank.html
This produces what you want, and it uses no vendor-specific SQL features like TOP N or RANK().
SELECT MAX(e.name) AS name, MAX(e.occupation) AS occupation
FROM emp e
LEFT OUTER JOIN emp e2
ON (e.occupation = e2.occupation AND e.emp_id <= e2.emp_id)
GROUP BY e.emp_id
HAVING COUNT(*) <= 3
ORDER BY occupation;
In this example it gives the three employees with the lowest emp_id values per occupation. You can change the attribute used in the inequality comparison, to make it give the top employees by name, or whatever.
Add RowNum to rank :
select * from
(select emp_id, name, occupation,rank() over ( partition by occupation order by emp_id,RowNum) rank
from employee)
where rank <= 3
tested this in SQL Server (and it uses subquery)
select emp_id, name, occupation
from employees t1
where emp_id IN (select top 3 emp_id from employees t2 where t2.occupation = t1.occupation)
just do an ORDER by in the subquery to suit your needs
I'm not sure this is very efficient, but maybe a starting place?
select *
from people p1
join people p2
on p1.occupation = p2.occupation
join people p3
on p1.occupation = p3.occupation
and p2.occupation = p3.occupation
where p1.emp_id != p2.emp_id
and p1.emp_id != p3.emp_id
This should give you rows that contain 3 distinct employees all in the same occupation. Unfortunately, it will give you ALL combinations of those.
Can anyone pare this down please?