Modify query to group by client identifier - sql

I have the following query.
Base query
WITH CTE (clientid, dayZero)
AS
-- Define the CTE query.
(
SELECT
clientid,
DATEDIFF(
DAY,
MIN(calendar),
MIN(CASE
WHEN total = 0
THEN calendar
END)
) as dayZero
FROM (
SELECT
clientid,
CONVERT(datetime, convert(varchar(10), calendar)) calendar,
TOTAL
FROM STATS s1
) a
GROUP BY clientid
),
cteb as
-- Define the outer query referencing the CTE name.
(SELECT cte.*, c.company, v.Name, m.id as memberid
FROM CTE
JOIN client c
on c.id = cte.CLIENTID
join Domain v
on v.Id = c.domainID
join subscriber m
on m.ClientId = c.id
join activity a
on m.id = a.memberid
where c.id != 023
),
ctec as
(
select count(distinct memberid) as Number from cteb
group by clientid
)
select clientid, dayzero, company, name, Number from cteb , ctec
The output of this query is -
clientid dayzero company name Number
21 35 School Boards Education 214
21 35 School Boards Education 214
I want it to only return 1 row per client. Any ideas on how to modify this query
Sub Query
select count(distinct memberid) as Number from cteb
group by clientid
When I only run the query until the above subquery and select like so -
select * from ctec
where clientid = 21
I get
clientid Number
21 214
22 423
This is what I would. But when I run the following select to get all the other columns I need, I start getting duplicates. The output makes sense because I am not grouping by clientid. But if I groupby how do I get the other columns I need?
select clientid, dayzero, company, name, Number from cteb , ctec
UPDATE
When I run the below select
select clientid, dayzero, company, name, Number from cteb , ctec
group by clientid, dayzero, company, name, Number
I still get
clientid dayzero company name Number
21 35 School Boards Education 214
21 35 School Boards Education 215
I don't understand why I am getting different numbers in the Number column (214 and 215 in this case). But when I run it with the group by as shown below, I get the correct numbers.
select count(distinct memberid) as Number from cteb
group by clientid
select * from ctec
where clientid = 21
I get
clientid Number
21 2190
Neither 214 nor 215 is correct. The correct number is 2190 which I get when I groupby as shown above.

If you want to show unique rows based on a particular column, you can use ROW_NUMBER() like following query.
select * from
(
select clientid, dayzero, company, name, Number,
ROW_NUMBER() OVER(PARTITION BY clientid ORDER BY Number DESC) RN
from cteb , ctec
) t
where RN=1

Related

Selecting sum of first and last rows in related table

In my table I have these three tables:
Teams:
Id GUID UNIQUE PRIMARY
Name NVARCHAR
Participants:
Id GUID UNIQUE PRIMARY
FirstName NVARCHAR
[....]
TeamId GUID
ParticipantData:
Id GUID UNIQUE PRIMARY
Weight FLOAT
Date DATETIME
ParticipantId GUID
TeamId GUID
What I need is an SQL query that gives me all columns from Teams AND:
The sum of the first (order by Date) entries in ParticipantData of participants in the team (TeamId)
The sum of the last (ordered by Date) entries in ParticipantData of participants in the team (TeamId)
Explanation:
I have lots of participants (team members) reporting their weight with some interval (Weight + Date). What I'm trying to accomplish is to calculate the weight loss of all the team members.
On 2019-01-03 Participant 1 reports Weight 78
On 2019-01-06 Participant 1 reports Weight 75
On 2019-01-04 Participant 2 reports Weight 86
On 2019-01-07 Participant 2 reports Weight 83
I need the query to get SumOfFirstWeights (78 + 86) = 164
I need the query to get SumOfLastWeights (75 + 83) = 158
Which gives me a weight loss of 6.
I've tried many combinations of:
Select *,
(SELECT TOP (1) Sum(Weight)
FROM ParticipantData WHERE (TeamId = Teams.Id)
GROUP BY ParticipantId
)
ORDER BY Date As SumOfFirstWeights
From Teams
Your problem is some kind of greatest/lowest per group, moreover, you want a sum of these values.
select t.id, t.name, sum(t1.weight), sum(t2.weight)
from teams t
left join
(
select pd.teamid, pd.participantid, pd.weight
from ParticipantData pd
join
(
select teamid, participantid, min(date) min_date
from ParticipantData
group by teamid, participantid
) t on pd.teamid = t.teamid and
pd.participantid = t.participantid and
pd.date = t.min_date
) t1 on t.id = t1.teamid
left join
(
select pd.teamid, pd.participantid, pd.weight
from ParticipantData pd
join
(
select teamid, participantid, max(date) max_date
from ParticipantData
group by teamid, participantid
) t on pd.teamid = t.teamid and
pd.participantid = t.participantid and
pd.date = t.max_date
) t2 on t1.teamid = t2.teamid and
t1.participantid = t2.participantid
group by t.id, t.name
You want to find the first and last row per team. You can use analytic functions for this. I'm using ROW_NUMBER which gives me exactly one record, even if a participant has two entries on the same day.
with pd as
(
select
participantid,
weight,
row_number() over (partition by participantid order by date) as one_is_first,
row_number() over (partition by participantid order by date desc) as one_is_last
from participantdata
)
, team_weights as
(
select
p.teamid,
sum(case when pd.one_is_first = 1 then weight end) as first_weight,
sum(case when pd.one_is_last = 1 then weight end) as last_weight,
sum(case when pd.one_is_last = 1 then weight end) -
sum(case when pd.one_is_first = 1 then weight end) as difference
from pd
join participant p on p.id = pd.participantid
where (pd.one_is_first = 1 or pd.one_is_last = 1)
group by p.teamid
)
select *
from teams
join team_weights on where team_weights.team_id = teams.id
order by teams.id;

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;

Group By only for some columns

How do I group only some of the top selected columns in my select query?
A wrong but easy answer I would think of is this code;
SELECT TOP 5 brand, name, delivered, count(*)
From myTB
Where type = 'jeans'
Group By brand, name
Order By Count(*) DESC
The result that I'm after should return the below results;
(the above code is wrong and returns an error)
Brand name Delivered Count
-------------------------------------
Levis 304 Slim 9/24 44
Croccer 500 Lose 3/14 22
Croccer 400 Botcut 4/7 14
Lee Botcut 33 5/5 16
Lee Slim 44 10/7 12
In the above results i get the brands together after one another even thuo the count is not decending.
I have tried and the closest that i get is with this code;
SELECT TOP 5 brand, name, delivered, count(*)
From myTB
Where type = 'jeans'
Group By brand, name, delivered
Order By Count(*) DESC
But that returns the data like this;
Brand name Delivered Count
-------------------------------------
Levis 304 Slim 9/24 44
Croccer 500 Lose 3/14 22
Lee Botcut 33 5/5 16
Croccer 400 Botcut 4/7 14
Lee Slim 44 10/7 12
If I try to use "order by count(*), brand" i get, for some reason, the brands in descending order regardles of the count value. It seams like it only order the brand column and not both brand and count
I also tried to do a left join on the same table so that i only needed to Group By in the primary table but thats not right either and the code I come up with was really confusing so I'm going to leave that outside this thread.
It seems like you want to order by the maximum count per brand first and the brand second.
select top 5 t1.* from (
select brand, name, delivered, count(*)
from myTB
where type = 'jeans'
group by brand, name, delivered
) t1 join (
select brand, cnt
from (
select brand, cnt,
row_number() over (partition by brand order by cnt desc) rn
from (select brand, count(*) cnt from myTB group by brand, name, delivered) t1
) t1
where rn = 1
) t2 on t1.brand = t2.brand
order by t2.cnt desc, t2.brand
try this
select TOP 5 t1.* from (SELECT brand, name, delivered, count(*)as 'test'
From myTB
Where type = 'jeans'
Group By brand, name,delivered
) as t1 order by t1.test desc

Retrieving Top 10 rows and sum all others in row 11

I have the following query that retrieve the number of users per country;
SELECT C.CountryID AS CountryID,
C.CountryName AS Country,
Count(FirstName) AS Origin
FROM Users AS U
INNER JOIN Country AS C ON C.CountryID = U.CountryOfOrgin
GROUP BY CASE C.CountryName,
C.CountryID
What I need is a way to get the top 10 and then sum all other users in a single row. I know how to get the top 10 but I`m stuck on getting the remaining in a single row. Is there a simple way to do it?
For example if the above query returns 17 records the top ten are displayed and a sum of the users from the 7 remaining country should appear on row 11. On that row 11 the countryid would be 0 and countryname Others
Thanks for your help!
You did not specify how you are ranking the top 10 so I'm assuming the highest counts are ranked higher?
With TopItems As
(
SELECT C.CountryID AS CountryID
, C.CountryName AS Country
, Count(FirstName) AS Origin
, ROW_NUMBER() OVER( ORDER BY Count(FirstName) DESC ) As Num
FROM Users AS U
JOIN Country AS C
ON C.CountryID = U.CountryOfOrgin
GROUP BY C.CountryName, C.CountryID
)
Select CountryId, Country, Origin
From TopItems
Where Num <= 10
Union ALL
Select 0, 'Others', Sum(Origin)
From TopItems
Where Num > 10
Something like this:
SELECT
-- show them
ROW_NUMBER() OVER (ORDER BY CASE WHEN country_code = 'Others' THEN 1 ELSE 0 END, SUM(n) DESC) AS nr,
countryID,
SUM(n)
FROM (
-- change name for some countries
SELECT
CASE WHEN nr >= 11 THEN 'Others' ELSE countryID END AS countryID,
n
-- select all countries
FROM (
SELECT
-- store number to recognize position
ROW_NUMBER() OVER (ORDER BY COUNT(*) DESC) AS nr,
countries.countryID,
COUNT(*) AS n
FROM
countries WITH (NOLOCK)
JOIN
users WITH (NOLOCK)
ON
users.countryID = countries.countryID
GROUP BY
countries.countryID
) AS x
) AS y
GROUP BY
countryID
ORDER BY
-- show Others as last one
CASE WHEN countryID = 'Others' THEN 1 ELSE 0 END,
SUM(n) DESC
works for me.

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