SQL return n rows per row value - sql

Greetings SQL people of all nations.
Simple question, hopefully a simple answer.
I have an Oracle database table with persons' information. Columns are:
FirstName, LastName, BirthDate, BirthCountry
Let's say in this table I have 1500 persons born in Aruba (BirthCountry = "Aruba"), 678 Botswanans (BirthCountry = "Botswana"), 13338 Canadians (BirthCountry = "Canadia").
What query would I need to write extract a sample batch of 10 records from each country? It doesn't matter which 10, just as long as there are 10.
This one query would output 30 rows, 10 rows from each BirthCountry.

This will select 10 youngest people from each country:
SELECT *
FROM (
SELECT p.*,
ROW_NUMBER() OVER (PARTITION BY birthCountry ORDER BY birthDate DESC) rn
FROM persons p
)
WHERE rn <= 10

This would pick ten random persons, different ones each time you run the query:
select *
from (
select row_number() over (partition by BirthCountry
order by dbms_random.value) as rn
, FirstName
, LastName
, BirthDate
, BirthCountry
from YourTable
)
where rn <= 10

Related

Select row with max value from each group in Oracle SQL [duplicate]

This question already has answers here:
Fetch the rows which have the Max value for a column for each distinct value of another column
(35 answers)
Closed 1 year ago.
I have table people containing people, their city and their money balance:
id city_id money
1 1 25
2 1 13
3 2 97
4 2 102
5 2 37
Now, I would like to select richest person from each city. How can I do that using Oracle SQL? Desired result is:
id city_id money
1 1 25
4 2 102
Something like that would be useful:
SELECT * as tmp FROM people GROUP BY city_id HAVING money = MAX(money)
You should be thinking "filtering", not "aggregation", because you want the entire row. You can use a subquery:
select p.*
from people p
where p.money = (select max(p2.money) from people p2 where p2.city_id = p.city_id);
You can use DENSE_RANK() analytic function through grouping by city_id(by using partition by clause) and descendingly ordering by money within the subquery to pick the returned values equal to 1 within the main query in order to determine the richest person including ties(the people having the same amount of money in each city) such as
SELECT id, city_id, money
FROM( SELECT p.*,
DENSE_RANK() OVER ( PARTITION BY city_id ORDER BY money DESC ) AS dr
FROM people p )
WHERE dr = 1
You can use RANK() as its flexible as you can get richest or top N richest
SELECT
id, city_id, money
FROM (
SELECT
p.* ,RANK() OVER (PARTITION BY city_id ORDER BY money DESC ) as rank_per_city
FROM
people p )
WHERE
rank_per_city = 1

SQL query to select up to N records per criteria

I was wondering if it's possible to use SQL (preferably snowflake) to select up to N records given certain criteria.
To illustrate:
Lets say I have a table with 1 million records, containing full names and phone numbers.
There's no limits on the amount of phone numbers that can be assigned to X person, but I only want to select up to 10 numbers per person, even if the person has more than 10.
Notice I don't want to select just 10 records, I want the query to return every name in the table, I only want to ignore extra phone numbers when the person already has 10 of them.
Can this be done?
You can use window functions to solve this greatest-n-per-group problem:
select t.*
from (
select
t.*,
row_number() over(partition by name order by phone_number) rn
from mytable t
) t
where rn <= 10
Note that you need an ordering column to define what "top 10" actually means. I assumed phone_number, but you can change that to whatever suits your use case best.
Better yet: as commented by waldente, snowflake has the qualify syntax, which removes the need for a subquery:
select t.*
from mytable t
qualify row_number() over(partition by name order by phone_number) <= 10
This query will help your requirement:
select
full_name,
phonenumber
from
(select
full_name,
phonenumber,
ROW_NUMBER() over (partition by phonenumber order by full_name desc) as ROW_NUMBER from sample_tab) a
where
a.row_number between 1 and 10
order by
full_name asc,
phonenumber desc;
using Snowflake Qualify function:
select
full_name,
phonenumber
from
sample_tab qualify row_number() over (partition by phonenumber order by full_name) between 1 and 10
order by
full_name asc ,
phonenumber desc;

SQL Server, how to get younger users?

I'm trying to get users from a younger country for example I have the following tables.
If there is more than one user of the youngest who have the same age, they should also be shown
Thanks
You can try this query, get MIN birthday on subquery then self join on users table.
select u.idcountry,t.name,u.username, (DATEPART(year, getdate()) - t.years) 'age'
from
(
SELECT u.idcountry,c.name,DATEPART(year, u.birthday) as 'years',count(*) as 'cnt'
FROM users u inner join country c on u.idcountry = c.idcountry
group by u.idcountry,c.name,DATEPART(year, u.birthday)
) t inner join users u on t.idcountry = u.idcountry and t.years = DATEPART(year, u.birthday)
where t.cnt > 1
sqlfiddle:https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=9baab959f79b1fa8c28ed87a8640e85d
Use the rank() window function:
select ...
from ...
where rank() over (partition by idcountry order by birthday) = 1
Rows with the same birthday in a country are ranked the same, so this returns all youngest people with if there’s more than one.
This is a little tricky. I would use window functions -- count the people of a particular age and choose the ones where there are duplicates for the youngest.
You don't specify how to define age, so I'll just use the earliest calendar year:
select u.*
from (select u.*,
count(*) over (partition by idcountry, year(birthday)) as cnt_cb,
rank() over (partition by idcountry order by year(birthday)) as rnk
from users u
) u
where cnt_cb > 1 and rnk = 1;
I'll let you handle the joins to bring in the country name.
Your sample data and desired results show the oldest users within each country when more than one of the oldest have the same age. The query below will do that, assuming age is calculated using full birth date.
WITH
users AS (
SELECT
username
, birthday
, idcountry
, (CAST(CONVERT(char(8),GETDATE(),112) AS int) - CAST(CONVERT(char(8),birthday,112) AS int)) / 10000 AS age
, RANK() OVER(PARTITION BY idcountry ORDER BY (CAST(CONVERT(char(8),GETDATE(),112) AS int) - CAST(CONVERT(char(8),birthday,112) AS int)) / 10000 DESC) AS age_rank
FROM dbo.Users
)
, oldest_users AS (
SELECT
username
, birthday
, idcountry
, age
, COUNT(*) OVER(PARTITION BY idcountry, age_rank ORDER BY age_rank) AS age_count
FROM users
WHERE age_rank = 1
)
SELECT
c.idcountry
, c.name
, oldest_users.age
, oldest_users.username
FROM oldest_users
JOIN dbo.Country AS c ON c.idcountry = oldest_users.idcountry
WHERE
oldest_users.age_count > 1;

How to get the max value for each group in Oracle?

I've found some solutions for this problem, however, they don't seem to work with Oracle.
I got this:
I want a view to present only the informations about the oldest person for each team. So, my output should be something like this:
PERSON | TEAM | AGE
Sam | 1 | 23
Michael | 2 | 21
How can I do that in Oracle?
Here is an example without keep but with row_number():
with t0 as
(
select person, team, age,
row_number() over(partition by team order by age desc) as rn
from t
)
select person, team, age
from t0
where rn = 1;
select * from table
where (team, age) in (select team, max(age) from table group by team)
One method uses keep:
select team, max(age) as age,
max(person) keep (dense_rank first order by age desc) as person
from t
group by team;
There are other methods, but in my experience, keep works quite well.
select * from (select person,team,age,
dense_rank() over (partition by team order by age desc) rnk)
where rnk=1;
Using an Analytic Function returns all people with maximum age per team (needed if there are people with identical ages), selects Table one time only and is thus faster than other solutions that reference Table multiple times:
With MaxAge as (
Select T.*, Max (Age) Over (Partition by Team) MaxAge
From Table T
)
Select Person, Team, Age From MaxAge Where Age=MaxAge
;
This also works in MySQL/MariaDB.

SQL query to return only 1 record per group ID

I'm looking for a way to handle the following scenario. I have a database table that I need to return only one record for each "group id" that is contained within the table, furthermore the record that is selected within each group should be the oldest person in the household.
ID Group ID Name Age
1 134 John Bowers 37
2 134 Kerri Bowers 33
3 135 John Bowers 44
4 135 Shannon Bowers 42
So in the sample data provided above I would need ID 1 and 3 returned, as they are the oldest people within each group id.
This is being queried against a SQL Server 2005 database.
SELECT t.*
FROM (
SELECT DISTINCT groupid
FROM mytable
) mo
CROSS APPLY
(
SELECT TOP 1 *
FROM mytable mi
WHERE mi.groupid = mo.groupid
ORDER BY
age DESC
) t
or this:
SELECT *
FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY groupid ORDER BY age DESC) rn
FROM mytable
) x
WHERE x.rn = 1
This will return at most one record per group even in case of ties.
See this article in my blog for performance comparisons of both methods:
SQL Server: Selecting records holding group-wise maximum
Use:
SELECT DISTINCT
t.groupid,
t.name
FROM TABLE t
JOIN (SELECT t.groupid,
MAX(t.age) 'max_age'
FROM TABLE t
GROUP BY t.groupid) x ON x.groupid = t.groupid
AND x.max_age = t.age
So what if there's 2+ people with the same age for a group? It'd be better to store the birthdate rather than age - you can always calculate the age for presentation.
Try this (assuming Group is synonym for Household)
Select * From Table t
Where Age = (Select Max(Age)
From Table
Where GroupId = t.GroupId)
If there are two or more "oldest" people in some household (They all are the same age and there is noone else older), then this will return all of them, not just one at random.
If this is an issue, then you need to add another subquery to return an arbitrary key value for one person in that set.
Select * From Table t
Where Id =
(Select Max(Id) Fom Table
Where GroupId = t.GroupId
And Age =
(Select(Max(Age) From Table
Where GroupId = t.GroupId))
SELECT GroupID, Name, Age
FROM table
INNER JOIN
(
SELECT GroupID, MAX(Age) AS OLDEST
FROM table
) AS OLDESTPEOPLE
ON
table.GroupID = OLDESTPEOPLE.GroupID
AND
table.Age = OLDESTPEOPLE.OLDEST
SELECT GroupID, Name, Age
FROM table
INNER JOIN
(
SELECT GroupID, MAX(Age) AS OLDEST
FROM table
**GROUP BY GroupID**
) AS OLDESTPEOPLE
ON
table.GroupID = OLDESTPEOPLE.GroupID
AND
table.Age = OLDESTPEOPLE.OLDEST