string aggregate group and count on a value - sql

I have table like this.
| table |
| class_id| name | gender |
+---------+---------+----------+
| 1 | Jane | F |
| 1 | John | M |
| 1 | Tom | M |
| 1 | Bob | M |
| 2 | Jack | M |
| 2 | Kate | F |
I have a query like this.
select id, array_to_string(array_agg(name), ' - '::text) as name_list from table
group by class_id
My result is
| 1 | Jane-John-Tom-Bob |
But i'd like to count my gender count also i mean in the first group (cass 1) i need a column like 1 F + 3 M
My request is something like this and i'd like to use it in 1 group by.
| 1 | Jane-John-Tom-Bob |1F + 3M

You can do that with a filtered aggregate:
select id,
string_agg(name, ' - ') as name_list,
concat(
count(*) filter (where gender = 'F'),
'F + ',
count(*) filter (where gender = 'M'),
'M') as gender_count
from table
group by class_id;
If you are on an older Postgres version, you need to replace
count(*) filter (where gender = 'F')
with
count(case when gender = 'F' then 1 end)
(and the same for 'M')

There is also another solution without using Filter aggregate
select tt.class_id, string_agg ( t, ','::text) as gender, string_agg(distinct y,','::text) as name
from
(
select class_id, count(gender)::text|| string_agg( distinct gender, ',' ) as t
from string_test
group by class_id , gender
) tt ,
(
select class_id, string_agg( distinct name::text, ','::text ) as y
from string_test
group by class_id
) yy
where tt.class_id=yy.class_id
group by tt.class_id
Result;
+==========+========+===================+
| class_id | gender | name |
+==========+========+===================+
| 1 | 1F,3M | Bob,Jane,John,Tom |
+----------+--------+-------------------+
| 2 | 1F,1M | Jack,Kate |
+==========+========+===================+

Related

I want row data of same id in one single row

The Table Format is like
ID | MID | PID | Quantity
1 | 1 | 2 | 3
2 | 1 | 3 | 10
3 | 2 | 2 | 11
4 | 2 | 1 | 5
I want to result as following
ID | MID | Final
1 | 1 | 2(3),3(10)
2 | 2 | 2(11),1(5)
first concate two columns and then do string_agg. Here is the demo.
with cte as
(
select
mid,
concat(pid, '(', quantity, ')') as concat_col
from table1
)
select
row_number() over (order by mid) as id,
mid,
string_agg(concat_col, ', ') as final
from cte
group by
mid
output:
| id | mid | final |
| --- | --- | ----------- |
| 1 | 1 | 2(3), 3(10) |
| 2 | 2 | 2(11), 1(5) |
If you are using older version of SQL Server then try the following
with cte as
(
select
mid,
concat(pid, '(', quantity, ')') as concat_col
from table1
)
select
row_number() over (order by mid) as id,
mid,
stuff((
select ',' + concat_col
from cte c1
where c.mid = c1.mid
for XML PATH('')
), 1, 1, '') as final
from cte c
group by
mid
select MID, string_agg(concat(PID, '(', Quantity,')'), ', ')
from dbo.Sample
group by MID
Result :
MID FINAL
1 2(3), 3(10)
2 2(11), 1(5)

How to handle duplicates created by LEFT JOIN

LEFT TABLE:
+------+---------+--------+
| Name | Surname | Salary |
+------+---------+--------+
| Foo | Bar | 100 |
| Foo | Kar | 300 |
| Fo | Ba | 35 |
+------+---------+--------+
RIGHT TABLE:
+------+-------+
| Name | Bonus |
+------+-------+
| Foo | 10 |
| Foo | 20 |
| Foo | 50 |
| Fo | 10 |
| Fo | 100 |
| F | 1000 |
+------+-------+
DESIRED OUTPUT:
+------+---------+--------+-------+
| Name | Surname | Salary | Bonus |
+------+---------+--------+-------+
| Foo | Bar | 100 | 80 |
| Foo | Kar | 300 | 0 |
| Fo | Ba | 35 | 110 |
+------+---------+--------+-------+
The closest I get is this:
SELECT
a.Name,
Surname,
sum(Salary),
sum(Bonus)
FROM (SELECT
Name,
Surname,
sum(Salary) as Salary
FROM input
GROUP BY 1,2) a LEFT JOIN (SELECT Name,
SUM(Bonus) as Bonus
FROM input2
GROUP BY 1) b
ON a.Name = b.Name
GROUP BY 1,2;
Which gives:
+------+---------+-------------+------------+
| Name | Surname | sum(Salary) | sum(Bonus) |
+------+---------+-------------+------------+
| Fo | Ba | 35 | 110 |
| Foo | Bar | 100 | 80 |
| Foo | Kar | 300 | 80 |
+------+---------+-------------+------------+
I can't figure out how to get rid of Bonus duplication. Ideal solution for me would be as specified in the 'DESIRED OUTPUT', which is adding Bonus to only one Name and for other records with the same Name adding 0.
You can use row_number():
select l.*, (case when l.seqnum = 1 then r.bonus else 0 end) as bonus
from (select l.*, row_number() over (partition by name order by salary) as seqnum
from "left" l
) l left join
(select r.name, sum(bonus) as bonus
from "right" r
group by r.name
) r
on r.name = l.name
Try a Row_number over the Name category partioned by Name. This will give you different numbers for your duplicates. You can then search for the case when this number is 1 and return the result you want. Else return 0. The code can look something like this.
SELECT
a.Name,
Surname,
sum(Salary),
Case when Duplicate_Order = 1
then bonus
else 0
end as 'Bonus'
FROM (SELECT
Name,
Surname,
sum(Salary) as Salary
,ROW_NUMBER() over (partition by Name order by name) as [Duplicate_Order]
FROM input
GROUP BY 1,2) a
LEFT JOIN (SELECT Name,
SUM(Bonus) as Bonus
FROM input2
GROUP BY 1) b
ON a.Name = b.Name
GROUP BY 1,2;
Hope that helps!
You can use Correlated Subquery with sum() aggregation to compute the bonus column, and then apply lag() window analytic function to get the zeros for successively identical valued column values for the name column :
select Name, Surname, Salary,
bonus - lag(bonus::int,1,0) over (partition by name order by salary) as bonus
from
(
select i1.*,
( select sum(Bonus)
from input2 i2
where i1.Name = i2.Name
group by i2.Name ) as bonus
from input i1
) ii
order by name desc, surname;
Demo

Join Table From Minimum Value and Specific Name

I have:
Table id
+--------+
| number |
+--------+
| 1 |
| 2 |
| 3 |
+--------+
Table data
+-------+--------------+
| name | phone_number |
+-------+--------------+
| Bob | 111 |
| John | 333 |
| Alice | 555 |
+-------+--------------+
How to join table with results: (number from minimum value & name='John') ?
+--------+-------+--------------+
| number | name | phone_number |
+--------+-------+--------------+
| 1 | John | 333 |
+--------+-------+--------------+
You can try below -
select
(select min(number) FROM ID) as number, name, phone_number
from date
where name = 'John'
You can use cross join:
select min(number) as number, name, phone_number
from Table_Id
cross join Table_Data
group by name, phone_number
Depending on the RDBMS you're using, this query should get you close.
SELECT
MIN_NUMBER, NAME, PHONE_NUMBER
FROM
DATA LEFT JOIN (SELECT MIN(NUMBER) AS MIN_NUMBER FROM ID) ON 1=1
WHERE NAME = 'JOHN'

SQL Sum and Group by changing value of group

How do I group values ​​and change one of them when there is more than one
Table
ID | VALUE | NAME
1 | 2 | John
1 | 5 | Carl
2 | 4 | Elis
2 | 1 | Ted
3 | 2 | James
RESULT
ID | VALUE | NAME
1 | 7 | *
2 | 5 | *
3 | 2 | James
Here is one way that should work in any database:
select id, sum(value) as value,
(case when min(name) = max(name) then min(name) else '*' end) as name
from t
group by id;
This is the others query you must try
Select ID, sum(Value) Value, NAME = ( Select b.Name + ' ' AS [text()]
From dbo.test b
Where a.ID = b.ID
ORDER BY a.ID
For XML PATH (''))
from test a
group by ID
order by ID

Sql two table query most duplicated foreign key

I got those two tables sport and student:
First table sport:
|idsport | name |
_______________________
| 1 | bobsled |
| 2 | skating |
| 3 | boarding |
| 4 | iceskating |
| 5 | skiing |
Second table student:
foreign key
|idstudent | name | sport_idsport
__________________________________________
| 1 | john | 3 |
| 2 | pauly | 2 |
| 3 | max | 1 |
| 4 | jane | 2 |
| 5 | nico | 5 |
so far i did this it output which number is mostly inserted, but cant get it to work
with two tables
SELECT sport_idsport
FROM (SELECT sport_idsport FROM student GROUP BY sport_idsport ORDER BY COUNT(*) desc)
WHERE ROWNUM<=1;
I need to output name of most popular sport, in that case it would be skating.
I use oracle sql.
with counter as (
Select sport_idsport,
count(*) as cnt,
dense_rank() over (order by count(*) desc) as rn
from student
group by sport_idsport
)
select s.*, c.cnt
from sport s
join counter c on c.sport_idsport = s.idsport and c.rn = 1;
SQLFiddle example: http://sqlfiddle.com/#!4/b76e21/1
select cnt, sport_idsport from (
select count(*) cnt, sport_idsport
from student
group by sport_idsport
order by count(*) desc
)
where rownum = 1