MySQL GROUP_CONCAT within GROUP_CONCAT different Group By values - sql

I am trying to get a group_concat to work within another group_concat but grouped by different values.
3 Tables Products, Customers , and Product_Customer ( which holds what product each customer bought and what size )
#Creates the Customer Table
CREATE TABLE Customer
(
Cus_Code INT AUTO_INCREMENT PRIMARY KEY,
Cus_Name VARCHAR(20)
);
#Creates the Product Table
CREATE TABLE Product
(
Prod_Code INT AUTO_INCREMENT PRIMARY KEY,
Prod_Name VARCHAR(30)
);
#Creates the Product_Customer Table
CREATE TABLE Product_Customer
(
Cus_Code INT references Customer(Cus_Code),
Prod_Code INT references Product(Prod_Code),
Size INT,
);
Sample Data
#Inserts data into Customer Table
INSERT INTO Customer (Cus_Name)
VALUES
('Aaron')
('Bob')
('Charlie')
#Inserts data into Product Table
INSERT INTO Product (Prod_Name)
VALUES
('A')
('B')
('C')
#Inserts data into Product_Customer Table
INSERT INTO Product_Customer (Cus_Code, Prod_Code, Size)
VALUES
(1, 1, 1),
(1, 1, 2),
(1, 2, 1),
(2, 1, 1),
(2, 2, 1),
(2, 2, 2),
(3, 1, 1),
(3, 2, 1),
(3, 3, 1),
(3, 3, 2)
Desired Output Something like this
Customer Name | Product(Size)
Aaron | A(1,2), B(1)
Bob | A(1), B(1,2)
Charlie | A(1), B(1), C(1,2)
So i need the Size grouped by the product_code , then all that grouped by customer code
I have tried with variations of the following but to no avail
SELECT Customer.Cus_Name, GROUP_CONCAT(DISTINCT Product.Prod_Code, '(', s.list, ')' SEPARATOR ', ') AS 'Products'
FROM Product
JOIN (
SELECT Product.Prod_Code AS id, GROUP_CONCAT(DISTINCT Product_Customer.Size SEPARATOR ',') AS list
FROM Product
INNER JOIN Product_Customer ON Product.Prod_Code = Product_Customer.Prod_Code
GROUP BY id;
) AS s ON s.id = Product_Customer.Prod_Code
INNER JOIN Product_Customer ON Product.Prod_Code = Product_Customer.Prod_Code
INNER JOIN Customer ON Product_Customer.Cus_Code = Customer.Cus_Code
GROUP BY Customer.Cus_Code;
It seems to include all the sizes bought for that product, not what size each customer bought.
Any help would be appreciated

SELECT c.Cus_Name,
GROUP_CONCAT(cncat.Size ORDER BY cncat.Prod_Name SEPARATOR ', ') AS Size
FROM Customer c
INNER JOIN
(
SELECT pc.Cus_Code,
p.Prod_Name,
CONCAT(p.Prod_Name, '(', GROUP_CONCAT(pc.size), ')') Size
FROM Product_Customer pc
INNER JOIN Product p
ON pc.Prod_Code = p.Prod_Code
GROUP BY pc.Cus_Code,
p.Prod_Name
) AS cncat
ON c.Cus_Code = cncat.Cus_Code
GROUP BY c.Cus_Name
SQLFiddle Demo

I think the following version will do what you want:
SELECT c.Cus_Name, ps.prodsizes
FROM Customer c JOIN
(select cus_code, group_concat(prod_code, '(', sizes, ')' separator ', ') as prodsizes
from (select pc.cus_code, pc.prod_code, group_concat(distinct p.size separator ',') as sizes
from Product_Customer pc join
Product p
on pc.prod_code = p.prod_code
group by pc.cus_code, pc.prod_code
) cp
group by cus_code
) ps
on ps.cus_code = c.cus_code
GROUP BY c.Cus_Code;
Note that there are two levels of aggregation to get the products and sizes together, first at the customer product level then at the customer level.
I also introduces table aliases to make the query easier to write and read. There is no need for a distinct at the outer level, because duplicates are combined in the subquery.

Related

How many elements in one column are linked to an element other column?

Consider I have two tables
Courses Program
---------------------------
course_ID program_id
course_title program_name
program_ID
Now, I want to check no of courses(by course_id) offered by each program (program_id).
select c.program_id ,p.program_name, count(course_id)
from courses c
join Program p on c.Program_id =p.Program_id
group by program_id,program_name
If I understood you correctly, you're searching for a GROUP BY and a corresponding aggregate.
--Creating sample tables and data
SELECT course_ID, course_title, program_ID
INTO #courses
FROM (
VALUES (0, 'course_0', 0),
(1, 'course_1', 0),
(2, 'course_2', 0),
(3, 'course_3', 0),
(4, 'course_4', 1),
(5, 'course_5', 1),
(NULL, 'course_6', 1)
) AS C (course_ID, course_title, program_ID)
SELECT program_ID, program_title
INTO #programs
FROM (
VALUES (0, 'program_0'),
(1, 'program_1')
) AS P (program_ID, program_title)
and after that execute the query
SELECT P.program_title, COUNT(C.course_ID) AS courses_amount
FROM #courses C
INNER JOIN #programs P ON C.program_ID = P.program_ID
GROUP BY P.program_ID, P.program_title
So you basically GROUP BY the value to which you to aggregate to and COUNT the 'course_id'.
COUNT(C.course_ID) only counts actual values and will ignore NULLs.
If you want to count the NULLs as well, just use COUNT(*).
EDIT: Forgot the result...
So it'll look like this:
program_title
courses_amount
program_0
4
program_1
2

select all records with multiple category

I am trying to figure out how to select all records that are associated with all categories on a list.
For instance take this DB setup:
create table blog (
id integer PRIMARY KEY,
url varchar(100)
);
create table blog_category (
id integer PRIMARY KEY,
name varchar(50)
);
create table blog_to_blog_category (
blog_id integer,
blog_category_id integer
);
insert into blog values
(1, 'google.com'),
(2, 'pets.com'),
(3, 'petsearch.com');
insert into blog_category values
(1, 'search'),
(2, 'pets'),
(3, 'misc');
insert into blog_to_blog_category values
(1,1),
(2,2),
(3,1),
(3,2),
(3,3);
I can query on the main table like this:
select b.*, string_agg(bc.name, ', ') from blog b
join blog_to_blog_category btbc on b.id = btbc.blog_id
join blog_category bc on btbc.blog_category_id = bc.id
where b.url like '%.com'
group by b.id
But lets say I want to only return blogs that have BOTH category 1 & 2 connected with them how do I do that?
This would return just the petsearch.com domain as it is only record to have both of those categories.
Here you go:
Added a check to count the blog_category id (HAVING Clause) and if it is 2 then it should be either 1 or 2 (IN Clause),
select b.*, string_agg(bc.name, ', ') from blog b
join blog_to_blog_category btbc on b.id = btbc.blog_id
join blog_category bc on btbc.blog_category_id = bc.id
where b.url like '%.com' and bc.id in (1,2)
group by b.id
having count(distinct bc.id ) =2
here is one way:
select * from blog where id in (
select blog_id
from blog_to_blog_category bbc
where blog_category_id in (1, 2)
group by blog_id
having count(distinct blog_category_id) = 2
)

SQL Query to satisfy 2 conditions

I'm new to SQL and after designing the database, i'm having trouble with some queries. The query i'm currently struggling with states:
"A list of the customers who have ordered at least one project with a higher than average expected duration."
SELECT Customer.name
FROM Project, Customer
WHERE Project.c_id = Customer.c_id AND Project.exp_duration > AVG(Project.exp_duration)
I tried to implement this code but i keep gettin the following error message : "An aggregate may not appear in the WHERE clause unless it is in a subquery contained in a HAVING clause or a select list, and the column being aggregated is an outer reference."
Can someone help me with this? I've thought about using joins but i can't get it to work either.
Thanks in advance!
Replace the table variables (#Project & #Customer) with your real tables (Project & Customer).
DECLARE #Project TABLE
(
p_id INT,
exp_duration DECIMAL(18,2),
c_id INT
)
DECLARE #Customer TABLE
(
c_id INT,
name VARCHAR(20)
)
INSERT #Project VALUES (1, 10, 1), (2, 5, 1), (3, 20, 1), (4, 10, 2), (5, 15, 2), (6, 20, 1)
INSERT #Customer VALUES (1, 'C1'), (2, 'C2')
-- average duration
-- SELECT AVG(exp_duration) FROM #Project
SELECT DISTINCT C.name
FROM #Customer C INNER JOIN #Project P ON C.c_id = P.c_id
WHERE p.exp_duration > (SELECT AVG(exp_duration) FROM #Project)
The following query gives the list of Customers who have ordered at least one project (i.e. being a part of one or more projects) and whose ExpectedDuration is greater than the Average ExpectedDuration.
I have used left outer join, group by, count and avg aggregate functions.
Select
C.CustomerID,
C.Name
From SampleCustomer C
Left Join SampleProject P
On C.CustomerID = P.CustomerID
Where P.ExpectedDuration > (Select Avg(ExpectedDuration) From SampleProject Where CustomerID = C.CustomerID)
Group By C.CustomerID, C.Name
Having Count(P.ProjectID) >= 1
Order By C.CustomerID;

How to join id occurrences instead of simply showing count(*)?

I did this snippet to demonstrate: http://sqlfiddle.com/#!6/ed243/2
Schema:
create table professional(
id int identity(1,3) primary key,
name varchar(20)
)
insert into professional values('professional A')
insert into professional values('professional B')
insert into professional values('professional C')
create table territory(
id int identity(2,3) primary key,
name varchar(20)
)
insert into territory values('territory A')
insert into territory values('territory B')
insert into territory values('territory C')
create table panel(
id int identity(3,3) primary key,
idProfessional int not null,
idTerritory int not null,
)
insert into panel values(1, 2)
insert into panel values(4, 5)
insert into panel values(7, 8)
insert into panel values(1, 5)
insert into panel values(7, 8)
insert into panel values(7, 2)
And the query I've got so far:
select
p.id, p.name, count(*) as Territories
from
(select distinct idProfessional, idTerritory from panel) panel
inner join
professional p
on p.id = panel.idProfessional
group by
p.id,
p.name
having count(*) > 1
order by p.id
The above query shows as result in how many territories each professional works filtering with distinct and by showing only professionals that work in more than one territory with having:
-------------------------------------------------------
| id | name | Territories |
-------------------------------------------------------
| 1 | professional A | 2 |
| 7 | professional C | 2 |
-------------------------------------------------------
Ok, but.. is it possible to show in Territories each idTerritory joined like "2, 5" instead of count(*) ?
Thanks in advance.
When it's necessary, I usually use the FOR XML function to do this kind of concatenation of multiple rows. I think this query does what you are looking for:
select
p.id, p.name, STUFF(
(select ', ' + CAST(t.id AS VARCHAR(10))
from panel panel2
inner join territory t
ON t.id = panel2.idTerritory
where panel2.idProfessional = p.id
order by t.name
for xml path(''), root('XMLVal'), type
).value('/XMLVal[1]','varchar(max)')
, 1, 2, '') as Territories
from panel
inner join
professional p
on p.id = panel.idProfessional
group by
p.id,
p.name
having count(*) > 1
order by p.id
I used this blog in creating my answer: http://blogs.lobsterpot.com.au/2010/04/15/handling-special-characters-with-for-xml-path/

How to show fields from most recently added detail in a view?

QUERY:
drop table #foot
create table #foot (
id int primary key not null,
name varchar(50) not null
)
go
drop table #note
create table #note (
id int primary key not null,
note varchar(MAX) not null,
foot_id int not null references #foot(id)
)
go
insert into #foot values
(1, 'Joe'), (2, 'Mike'), (3, 'Rob')
go
insert into #note (id, note, foot_id) values (1, 'Joe note 1', 1)
go
insert into #note (id, note, foot_id) values(2, 'Joe note 2', 1)
go
insert into #note (id, note, foot_id) values(3, 'Mike note 1', 2)
go
select F.name, N.note, N.id
from #foot F left outer join #note N on N.foot_id=F.id
RESULT:
QUESTION:
How can I create a view/query resulting in one row for each master record (#foot) along with fields from the most recently inserted detail (#note), if any?
GOAL:
(NOTE: the way I would tell which one is most recent is the id which would be higher for newer records)
select t.name, t.note, t.id
from (select F.name, N.note, N.id,
ROW_NUMBER() over(partition by F.id order by N.id desc) as RowNum
from #foot F
left outer join #note N
on N.foot_id=F.id) t
where t.RowNum = 1
Assuming the ID created in the #note table is always incremental (imposed by using IDENTITY or by controlling the inserts to always increment the by by max value) you can use the following query (which uses rank function):
WITH Dat AS
(
SELECT f.name,
n.note,
n.id,
RANK() OVER(PARTITION BY n.foot_id ORDER BY n.id DESC) rn
FROM #foot f LEFT OUTER JOIN #note n
ON n.foot_id = f.id
)
SELECT *
FROM Dat
WHERE rn = 1