SQL Server, how to get younger users? - sql

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;

Related

Get highest value from every group

I have table:
Id, Name, Account, Date, ItemsToSend
I want to group rows by Name and Account
From each group I want to get elements with latest Date
And display element's Name, Account and ItemsToSend
I managed something like this:
select
Name,
Account,
max(Date),
max(CountItemsSend)
from History
where
Date = (
select max(Date)
from History as p
where
p.Account = History.Account
and p.Name = History.Name
)
group by
Name,
Account
I am afraid of max(Date), max(CountItemsSend). I dont think it is ok. After where there is only 1 result for each group, so what is the point of max use there?
A CTE can make this neater.
WITH maxDates as
(
SELECT Name, Account, MAX(Date) as LatestDate
FROM History
GROUP BY Name, Account
)
SELECT h.Name, h.Account, h.Date, h.CountItemsSend
FROM History h
INNER JOIN maxDates m
on m.Name = h.Name and m.Account = h.Account and m.LatestDate = h.Date
Another possible approach is to use ROW_NUMBER() to number rows grouped by name and account and ordered by date descending and then select the rows with number equal to 1. These rows are with max Date per group and CountItemsSend from the same row.
SELECT
t.[Name],
t.[Account],
t.[Date],
t.[CountItemsSend]
FROM (
SELECT
[Name],
[Account],
[Date],
[CountItemsSend],
ROW_NUMBER() OVER (PARTITION BY [Name], [Acount] ORDER BY [Date] DESC) AS Rn
FROM History
) t
WHERE t.Rn = 1
You don't need aggregation. Just:
select h.*
from History h
where h.Date = (select max(h2.Date)
from History h2
where h2.Account = h.Account and
h2.Name = h.Name
);

Insert into another table after fetching latest date and and performing an inner join

I have a table called "Member_Details" which has multiple records for each member_ID. For Example,
I have another table called "BMI_Data" that looks like the following.
The goal is to fetch the names of those members whose "BMI" in "Member_Details" is less than the "target_BMI" in "BMI_Data" table and insert it into a new table called "results" with "Member_ID, First_Name and BMI" as its schema.
Also, one consideration is to fetch the latest data available in the "Member_Details" for each member (based on date) and then do the comparison
The result for the above scenario would be something like this.
I tried using the following query
INSERT INTO results_table (Member_ID, First_Name, BMI)
select c.Member_ID, First_Name, BMI
from
(SELECT *, ROW_NUMBER() OVER (PARTITION BY Member_ID ORDER BY Date desc)
AS ROWNUM FROM Member_Details) x
JOIN
BMI_Data c ON x.Member_ID = c.Member_ID
where
x.BMI < c.Target_BMI
The above query doesn't fetch the latest date and simply loads all records in which member BMI is less than target_BMI.
Please help !
An alternate query might be
INSERT INTO results_table (Member_ID, First_Name, BMI)
select md2.member_ID, md2.First_Name, md2.BMI
from BMI_Data bd
inner join (select distinct md.member_ID ,md.First_Name ,(select top 1 BMI from Member_Details where member_ID = md.member_ID order by Date desc) BMI from Member_Details md) md2 on md2.member_ID = bd.member_ID
where md2.BMI < bd.Target_BMI
First you haven't specify the condition after row_numbers defined
INSERT INTO results_table (Member_ID, First_Name, BMI)
select c.Member_ID, First_Name, BMI
from (SELECT *,
ROW_NUMBER() OVER (PARTITION BY Member_ID ORDER BY Date desc) AS ROWNUM
FROM Member_Details
) x JOIN BMI_Data c
ON x.Member_ID = c.Member_ID
where x.ROWNUM = 1 and x.BMI < c.Target_BMI;
Wanted to note - there is no such date as '31-April-2018'! You might meant '1-May-2018'
In any case - it is important to make sure that when you are ordering by Date you first cast it to data type of DATE otherwise ordering is not correct. Below makes this ordering proper and in addition proposes alternative way by using ARRAY_AGG() with ORDER BY and LIMIT 1
#standardSQL
INSERT INTO results_table (Member_ID, First_Name, BMI)
SELECT * EXCEPT(Target_BMI)
FROM (
SELECT Member_ID, First_Name,
ARRAY_AGG(BMI ORDER BY PARSE_DATE('%d-%B-%Y', Date) DESC LIMIT 1)[OFFSET(0)] BMI
FROM `project.dataset.member_details`
GROUP BY Member_ID, First_Name
) d
JOIN `project.dataset.bmi_data` t
USING(Member_ID)
WHERE BMI < Target_BMI

Oracle - optimising SQL query

I have two tables - countries (id, name) and users (id, name, country_id). Each user belongs to one country. I want to select 10 random users from the same random country. However, there are countries that have less than 10 users, so I can't use them. I need to select only from those countries, that have at least 10 users.
I can write something like this:
SELECT * FROM(
SELECT *
FROM users u
{MANY_OTHER_JOINS_AND_CONDITIONS}
WHERE u.country_id =
(
SELECT *
FROM
(
SELECT c.id
FROM countries c
JOIN
(
SELECT users.country_id, COUNT(*) as cnt
FROM users
{MANY_OTHER_JOINS_AND_CONDITIONS}
GROUP BY users.country_id
) X ON X.country_id = c.id
WHERE X.cnt >= 10
ORDER BY DBMS_RANDOM.RANDOM
) Y
WHERE ROWNUM = 1
)
ORDER BY DBMS_RANDOM.RANDOM
) Z WHERE ROWNUM < 10
However, In my real scenario, I have more conditions and joins to other tables for determining which user is applicable. By using this query, I must have these conditions on two places - in query that actually selects data and in the count subquery.
Is there any way how to write query like this but without having those other conditions on two places (which is probably not good performance-wise)?
You can use a CTE for the user criteria to avoid repeating the logic and to allow the DB to cache that set once (though in my experience the DB isn't as good at that as it should be, so check your execution plan).
I'm more of a Sql Server guy than Oracle, and syntax is subtly different so this may need some tweaks yet, but try this:
WITH SafeUsers (ID, Name, country_id) As
(
--criteria for users only has to specified here
SELECT ID, Name, country_id
FROM users
WHERE ...
),
RandomCountry (ID) As
(
SELECT ID
FROM (
SELECT u.country_id AS ID
FROM SafeUsers u -- but we reference it HERE
GROUP BY u.country_id
HAVING COUNT(u.Id) >= 10
ORDER BY DBMS_RANDOM.RANDOM
) c
WHERE ROWNUM = 1
)
SELECT u.*
FROM (
SELECT s.*
FROM SafeUsers s -- and HERE
INNER JOIN RandomCountry r ON s.country_id = r.ID
ORDER BY DBMS_RANDOM.RANDOM
) u
WHERE ROWNUM <= 10
And by removing nesting and introducing names for each intermediate step, this query is suddenly much easier to read and maintain.
you could create a view
for
create view user_with_many_cond as
SELECT *
FROM users u
{MANY_OTHER_JOINS_AND_CONDITIONS}
ths looking to your query
You could use having instead of a where outside the query
The order by seems could be placed inside the inner query
so the filter for one row
SELECT * FROM(
SELECT *
FROM user_with_many_cond u
WHERE u.country_id =
(
SELECT c.id
FROM countries c
JOIN
(
SELECT users.country_id, COUNT(*) as cnt
FROM user_with_many_cond
GROUP BY users.country_id
HAVING cnt >=10
ORDER BY DBMS_RANDOM.RANDOM
) X ON X.country_id = c.id
WHERE ROWNUM = 1
)
ORDER BY DBMS_RANDOM.RANDOM
) Z WHERE ROWNUM < 10
To get countries with more than 10 users:
SELECT users.country_id
, row_number() over (order by dbms_random.value()) as rn
FROM users
GROUP BY users.country_id having count(*) > 10
Use this as a sub-query to choose a country and grab some users:
with ctry as (
SELECT users.country_id
, row_number() over (order by dbms_random.value()) as ctry_rn
FROM users
GROUP BY users.country_id having count(*) > 10
)
, usr as (
select user_id
, row_number() over (order by dbms_random.value()) as usr_rn
from ctry
join users
on users.country_id = ctry.country_id
where ctry.ctry_rn = 1
)
select users.*
from usr
join users
on users.user_id = usr.user_id
where usr.usr_rn <= 10
/
This example ignores your {MANY_OTHER_JOINS_AND_CONDITIONS}: please inject them back where you need them.

How to output results by order of year and count of records?

I'm trying to write a query that gives me the count of records where execoffice_status=1
(could equal =0 too).
I want to output the results by using a different table employee which gives me their
names.
The query I wrote seems to give me some results but gives me all the records in the table
even where execoffice_status=0 (not sure how I would add that to the query).
What I'm trying to get out off the query is the count of records that execoffice_status=1
and from what year (execoffice_date), what eventually i would like from the query is the top 10
from each year (order by year).
With the query below I get all the record and even where execoffice_status=0
query:
SELECT *
FROM (
select ROW_NUMBER() OVER(PARTITION BY e.emp_namelast order by year(c.execoffice_date) desc ) as RowNum,
year(c.execoffice_date) as year, e.emp_nameFirst + ' ' + e.emp_namelast as fullname, count(c.execoffice_status) as stars
from phonelist.dbo.employee e
join intranet.dbo.CSEReduxResponses c on c.employee = e.emp_id
group by emp_namelast, emp_namefirst, year(c.execoffice_date)
) a
order by year
Here is a http://sqlfiddle.com/#!3/79f253/1 that I made with some dummy data.
For the first bit of your question you can simply add a where clause.
where c.execoffice_status=1
To get the top values for each year, Rank can accomplish this:
SELECT *
FROM (
select RANK() OVER(PARTITION BY year(c.execoffice_date) order by e.emp_namelast desc ) as Rank,
year(c.execoffice_date) as year, e.emp_nameFirst + ' ' + e.emp_namelast as lastName, sum(c.execoffice_status) as stars
from employee e
join CSEReduxResponses c on c.employeee = e.emp_id
where c.execoffice_status=1
group by emp_namelast, emp_namefirst, year(c.execoffice_date)
) a
where rank <= 2
order by year
fiddle
This numbers the users by their stars and gives you the top 2 for each year. (for 10 just <= 10)
SELECT * FROM (
select ROW_NUMBER() OVER(PARTITION BY e.emp_namelast order by year(c.execoffice_date) desc ) as RowNum,
year(c.execoffice_date) as year, e.emp_nameFirst + ' ' + e.emp_namelast as lastName, sum(c.execoffice_status) as stars
from employee e
join CSEReduxResponses c on c.employeee = e.emp_id
where c.execoffice_status <> 0
group by emp_namelast, emp_namefirst, year(c.execoffice_date)
) a WHERE RowNum<=10 order by year
Here is how I understood your requirements.
Get count of records for each employee per year where execoffice_status is 1
execoffice_status can be one or zero
in this case, you can use sum and group by, if execoffice_status can be another number other than one or zero, then we would need to use rownum and count, instead of sum and group by
let me know if this does what you want.
select * from(
select a.employeEe,a.execoffice_date, SUM(a.execoffice_status) execoffice_status_count
from CSEReduxResponses a
group by a.employeEe,execoffice_date
) a
left outer join employee b
on b.emp_id = a.employeee
where EXECOFFICE_STATUS_COUNT > 0
order by execoffice_date desc;
also if you want to get the top 10 rows, I think with sql server you do Select TOP 10 field1, field2, field3 from table

SQL I have to find the entire row of people that did something the same day. count function?

I have a table called Donates.
I have to find all d_names who donated more than once on a single day.
I have no idea how to combine those 2 queries.
Any help is appreciated.
This is my table.
3 fields.
donors receivers giftdate
a donor could only give a receiver a gift one time.
Donors can donate more than once and receivers can receive more than once.
I just have to find who donated a gift more than once on a day. But i need to know when and to who.
You are correct that you would use COUNT, and you would use a HAVING clause to filter:
select d_name
from Donates
group by d_name
having count(1) > 1
You will of course need to add whatever other clauses to meet your requirements, such as limiting to or grouping by day. The simplest being to limit the results to one single day (you can use both WHERE and HAVING in the same query):
select d_name
from Donates
where g_date = #Date
group by d_name
having count(1) > 1
Responding to your comment, you can join on this query as a derived table:
select *
from Donates
inner join (
select d_name
from Donates
where g_date = #Date
group by d_name
having count(1) > 1
) x on Donates.d_name = x.d_name
After all the comments in multiple places, I believe you're finally looking for something like:
select d_name, r_name, g_date
from Donates
inner join (
select d_name, g_date
from Donates
group by d_name, g_date
having count(1) > 1
) x on Donates.d_name = x.d_name and Donates.g_date = x.g_date
OP now says he is using Oracle, can't use GROUP BY, and wants all fields in the table.
He wants donors who donated more than once in any given day (regardless of the receivers).
select distinct d1.*
from Donates d1
inner join Donates d2
on d1.donors = d2.donors
and trunc(d1.giftdate) = trunc(d2.giftdate)
and d1.rowid < d2.rowid
;
select *
from Donates
where d_name in (
select d_name
from Donates
where cast(d_date as Date) in (
select cast(d_date as Date)
from Donates
group by cast(d_date as Date)
having count(cast(d_date as Date)) > 1
)
group by d_name
)
I would suggest simply using analytic functions:
select d.*
from (select d.*, count(*) over (partition by trunc(d.giftdate), d.name) as cnt
from donates d
) d
where cnt > 1;