mysql group by with multi rows from one table [duplicate] - mysql

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
mysql converting multiple rows into columns in a single row
i have a mysql table like this:
id | p | c | v
1 p1 10 1
2 p1 20 2
3 p1 30 3
4 p2 40 1
5 p2 50 2
6 p2 60 3
now i need to run a sql and get result like this:
p | as_c1 | as_c2 | as_c3
p1 10 20 30
p2 40 50 60
i used this query but it's not enough:
select
p,
c as as_c1,
c as as_c2,
c as as_c3
from test_tbl group by p, c
i searched every where, is this possible? i just need some guide.

This is basically a PIVOT that you are trying to perform. Unfortunately, MySQL does not have a PIVOT function. There are two ways to do this static or dynamic. If you know the values that you want to transform into columns, then you can use a static version but if the values are unknown then you can use a prepared statement to generate this dynamically:
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'sum(case when v = ''',
v,
''' then c end) AS as_c',
v
)
) INTO #sql
FROM table1;
SET #sql = CONCAT('SELECT p, ', #sql, '
FROM table1
GROUP BY p');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
See SQL Fiddle with Demo
The static version would be similar to this:
select p,
sum(case when v=1 then c end) as_c1,
sum(case when v=2 then c end) as_c2,
sum(case when v=3 then c end) as_c3
from table1
group by p
See SQL Fiddle with Demo

SELECT p,
( CASE WHEN v = 1 THEN c ELSE NULL END ) AS as_c1,
( CASE WHEN v = 2 THEN c ELSE NULL END ) AS as_c2,
( CASE WHEN v = 3 THEN c ELSE NULL END ) AS as_c3
FROM `test_tbl`
GROUP BY p;
I think that ought to do it.

Related

Retrieving values from a table using JOIN [mySQL] [duplicate]

Say I have two tables in a MySQL Database.
Table 1:
ID Name
1 Jim
2 Bob
Table 2:
ID Place Race_Number
1 2nd 1
1 3rd 2
1 4th 3
2 1st 1
2 2nd 2
2 2nd 3
When selecting rows from the database, is there any way to join rows from the second table as columns to the first table? Currently I am using SELECT * FROM Table1 NATURAL JOIN Table2.
This outputs:
ID Name Place Race_Number
1 Jim 2nd 1
1 Jim 3rd 2
1 Jim 4th 3
2 Bob 1st 1
2 Bob 2nd 2
2 Bob 2nd 3
Currently I am sorting through this in my PHP script to sort it into an array. This is a pain, as I have to look at the IDs and see if they're the same and then sort accordingly. I feel like there is a way to do this right in MySQL, without having to sort it into an array in the PHP. There can be an unlimited number of entries in the second table for each ID.
The desired result right from the MySQL query is:
ID Name Race1 Race2 Race3
1 Jim 2nd 3rd 4th
2 Bob 1st 2nd 2nd
I can't make columns for Race1, Race2 etc in the table themselves because there can be an unlimited number of races for each ID.
Thanks for any help!
An INNER JOIN will suffice your needs. MySQL has no PIVOT function by you can still simulate it using CASE and MAX() function.
SELECT a.ID, a.NAME,
MAX(CASE WHEN b.Race_Number = 1 THEN b.Place ELSE NULL END) Race1,
MAX(CASE WHEN b.Race_Number = 2 THEN b.Place ELSE NULL END) Race2,
MAX(CASE WHEN b.Race_Number = 3 THEN b.Place ELSE NULL END) Race3
FROM Table1 a
INNER JOIN Table2 b
ON a.ID = b.ID
GROUP BY a.ID, a.Name
But if you have unknown number of RACE, then a DYNAMIC SQL is much more preferred.
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT('MAX(CASE WHEN b.Race_Number = ', Race_Number,
' THEN b.Place END) AS ', CONCAT('`Race', Race_Number, '`'))
) INTO #sql
FROM Table2;
SET #sql = CONCAT('SELECT s.Student_name, ', #sql, '
FROM Table1 a
LEFT JOIN Table2 b
ON ON a.ID = b.ID
GROUP BY a.ID, a.Name');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

How to Pivot Mysql data without without repeating the records. I have used group by, but I am not getting the results I want [duplicate]

Lets say I have three different MySQL tables:
Table products:
id | name
1 Product A
2 Product B
Table partners:
id | name
1 Partner A
2 Partner B
Table sales:
partners_id | products_id
1 2
2 5
1 5
1 3
1 4
1 5
2 2
2 4
2 3
1 1
I would like to get a table with partners in the rows and products as columns. So far I was able to get an output like this:
name | name | COUNT( * )
Partner A Product A 1
Partner A Product B 1
Partner A Product C 1
Partner A Product D 1
Partner A Product E 2
Partner B Product B 1
Partner B Product C 1
Partner B Product D 1
Partner B Product E 1
Using this query:
SELECT partners.name, products.name, COUNT( * )
FROM sales
JOIN products ON sales.products_id = products.id
JOIN partners ON sales.partners_id = partners.id
GROUP BY sales.partners_id, sales.products_id
LIMIT 0 , 30
but I would like to have instead something like:
partner_name | Product A | Product B | Product C | Product D | Product E
Partner A 1 1 1 1 2
Partner B 0 1 1 1 1
The problem is that I cannot tell how many products I will have so the column number needs to change dynamically depending on the rows in the products table.
This very good answer does not seem to work with mysql: T-SQL Pivot? Possibility of creating table columns from row values
Unfortunately MySQL does not have a PIVOT function which is basically what you are trying to do. So you will need to use an aggregate function with a CASE statement:
select pt.partner_name,
count(case when pd.product_name = 'Product A' THEN 1 END) ProductA,
count(case when pd.product_name = 'Product B' THEN 1 END) ProductB,
count(case when pd.product_name = 'Product C' THEN 1 END) ProductC,
count(case when pd.product_name = 'Product D' THEN 1 END) ProductD,
count(case when pd.product_name = 'Product E' THEN 1 END) ProductE
from partners pt
left join sales s
on pt.part_id = s.partner_id
left join products pd
on s.product_id = pd.prod_id
group by pt.partner_name
See SQL Demo
Since you do not know the Products you will probably want to perform this dynamically. This can be done using prepared statements.
With dynamic pivot tables (transform rows to columns) your code would look like this:
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'count(case when Product_Name = ''',
Product_Name,
''' then 1 end) AS ',
replace(Product_Name, ' ', '')
)
) INTO #sql
from products;
SET #sql = CONCAT('SELECT pt.partner_name, ', #sql, ' from partners pt
left join sales s
on pt.part_id = s.partner_id
left join products pd
on s.product_id = pd.prod_id
group by pt.partner_name');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
See SQL Demo
It's probably worth noting that GROUP_CONCAT is by default limited to 1024 bytes. You can work around this by setting it higher for the duration of your procedure, ie. SET ##group_concat_max_len = 32000;

Putting subquery into offset clause

Let's say I have table (numbers) with one column:
n
---
4
5
67
23
7
89
and I want to get median (only even list of numbers). I thought it would be easy so I wrote:
SELECT SUM(ord.n)/2
FROM (
SELECT n
FROM numbers
ORDER BY n ASC
LIMIT 2 OFFSET (SELECT COUNT(n)/2-1 FROM numbers)
) AS ord
but of course it throw me syntax error. I guess I can't insert subquery into offset but I want to know what I have to do to get expected result? I know there are different ways to write query to get median but I need to know is there any possibility to insert 'variable' into offset instead of placing some number?
I think you are looking for the query below this should work from MySQL version 5.1 and up
SELECT
AVG(filter.n)
FROM (
SELECT
*
, (#position := #position + 1) AS init_position
FROM
t
CROSS JOIN (
SELECT
#position := 0
, #max := (SELECT COUNT(t.n) FROM t)
, #median_mode := (CASE WHEN ((#max % 2) = 0) THEN 'even' ELSE 'odd' END)
) AS init_user_param
ORDER BY
t.n ASC
) AS filter
WHERE
CASE
WHEN #median_mode = 'even'
THEN filter.init_position BETWEEN (#max / 2) AND ((#max / 2) + 1)
WHEN #median_mode = 'odd'
THEN filter.init_position = ((#max + 1) / 2)
END
Result
| AVG(filter.n) |
| ------------- |
| 15 |
see demo
Result when 89 is out the list.
| AVG(filter.n) |
| ------------- |
| 7 |
see demo
You can use ROW_NUMBER() window function:
WITH cte AS (SELECT COUNT(*) counter FROM numbers)
SELECT AVG(n) median
FROM (
SELECT
row_number() over (order by n) ordinal,
n
FROM numbers
) t
WHERE (SELECT counter FROM cte) IN (2 * ordinal, 2 * (ordinal - 1))
See the demo.
Result:
| median |
| ------ |
| 15 |
The (probably) most similar solution to your algorithm is to use two queries. First get the offset. Then insert it into your query. The SQL-only way would be to use a prepared statement:
set #offset = (SELECT COUNT(n)/2-1 FROM numbers);
set #sql = "
SELECT SUM(ord.n)/2
FROM (
SELECT n
FROM numbers
ORDER BY n ASC
LIMIT 2 OFFSET ?
) AS ord
";
prepare stmt from #sql;
execute stmt using #offset;
db-fiddle

How to Convert MySQL query rows to columns by Date field [duplicate]

Lets say I have three different MySQL tables:
Table products:
id | name
1 Product A
2 Product B
Table partners:
id | name
1 Partner A
2 Partner B
Table sales:
partners_id | products_id
1 2
2 5
1 5
1 3
1 4
1 5
2 2
2 4
2 3
1 1
I would like to get a table with partners in the rows and products as columns. So far I was able to get an output like this:
name | name | COUNT( * )
Partner A Product A 1
Partner A Product B 1
Partner A Product C 1
Partner A Product D 1
Partner A Product E 2
Partner B Product B 1
Partner B Product C 1
Partner B Product D 1
Partner B Product E 1
Using this query:
SELECT partners.name, products.name, COUNT( * )
FROM sales
JOIN products ON sales.products_id = products.id
JOIN partners ON sales.partners_id = partners.id
GROUP BY sales.partners_id, sales.products_id
LIMIT 0 , 30
but I would like to have instead something like:
partner_name | Product A | Product B | Product C | Product D | Product E
Partner A 1 1 1 1 2
Partner B 0 1 1 1 1
The problem is that I cannot tell how many products I will have so the column number needs to change dynamically depending on the rows in the products table.
This very good answer does not seem to work with mysql: T-SQL Pivot? Possibility of creating table columns from row values
Unfortunately MySQL does not have a PIVOT function which is basically what you are trying to do. So you will need to use an aggregate function with a CASE statement:
select pt.partner_name,
count(case when pd.product_name = 'Product A' THEN 1 END) ProductA,
count(case when pd.product_name = 'Product B' THEN 1 END) ProductB,
count(case when pd.product_name = 'Product C' THEN 1 END) ProductC,
count(case when pd.product_name = 'Product D' THEN 1 END) ProductD,
count(case when pd.product_name = 'Product E' THEN 1 END) ProductE
from partners pt
left join sales s
on pt.part_id = s.partner_id
left join products pd
on s.product_id = pd.prod_id
group by pt.partner_name
See SQL Demo
Since you do not know the Products you will probably want to perform this dynamically. This can be done using prepared statements.
With dynamic pivot tables (transform rows to columns) your code would look like this:
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'count(case when Product_Name = ''',
Product_Name,
''' then 1 end) AS ',
replace(Product_Name, ' ', '')
)
) INTO #sql
from products;
SET #sql = CONCAT('SELECT pt.partner_name, ', #sql, ' from partners pt
left join sales s
on pt.part_id = s.partner_id
left join products pd
on s.product_id = pd.prod_id
group by pt.partner_name');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
See SQL Demo
It's probably worth noting that GROUP_CONCAT is by default limited to 1024 bytes. You can work around this by setting it higher for the duration of your procedure, ie. SET ##group_concat_max_len = 32000;

Transposing using variables in SQL [duplicate]

Lets say I have three different MySQL tables:
Table products:
id | name
1 Product A
2 Product B
Table partners:
id | name
1 Partner A
2 Partner B
Table sales:
partners_id | products_id
1 2
2 5
1 5
1 3
1 4
1 5
2 2
2 4
2 3
1 1
I would like to get a table with partners in the rows and products as columns. So far I was able to get an output like this:
name | name | COUNT( * )
Partner A Product A 1
Partner A Product B 1
Partner A Product C 1
Partner A Product D 1
Partner A Product E 2
Partner B Product B 1
Partner B Product C 1
Partner B Product D 1
Partner B Product E 1
Using this query:
SELECT partners.name, products.name, COUNT( * )
FROM sales
JOIN products ON sales.products_id = products.id
JOIN partners ON sales.partners_id = partners.id
GROUP BY sales.partners_id, sales.products_id
LIMIT 0 , 30
but I would like to have instead something like:
partner_name | Product A | Product B | Product C | Product D | Product E
Partner A 1 1 1 1 2
Partner B 0 1 1 1 1
The problem is that I cannot tell how many products I will have so the column number needs to change dynamically depending on the rows in the products table.
This very good answer does not seem to work with mysql: T-SQL Pivot? Possibility of creating table columns from row values
Unfortunately MySQL does not have a PIVOT function which is basically what you are trying to do. So you will need to use an aggregate function with a CASE statement:
select pt.partner_name,
count(case when pd.product_name = 'Product A' THEN 1 END) ProductA,
count(case when pd.product_name = 'Product B' THEN 1 END) ProductB,
count(case when pd.product_name = 'Product C' THEN 1 END) ProductC,
count(case when pd.product_name = 'Product D' THEN 1 END) ProductD,
count(case when pd.product_name = 'Product E' THEN 1 END) ProductE
from partners pt
left join sales s
on pt.part_id = s.partner_id
left join products pd
on s.product_id = pd.prod_id
group by pt.partner_name
See SQL Demo
Since you do not know the Products you will probably want to perform this dynamically. This can be done using prepared statements.
With dynamic pivot tables (transform rows to columns) your code would look like this:
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'count(case when Product_Name = ''',
Product_Name,
''' then 1 end) AS ',
replace(Product_Name, ' ', '')
)
) INTO #sql
from products;
SET #sql = CONCAT('SELECT pt.partner_name, ', #sql, ' from partners pt
left join sales s
on pt.part_id = s.partner_id
left join products pd
on s.product_id = pd.prod_id
group by pt.partner_name');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
See SQL Demo
It's probably worth noting that GROUP_CONCAT is by default limited to 1024 bytes. You can work around this by setting it higher for the duration of your procedure, ie. SET ##group_concat_max_len = 32000;