MySQL pivot from 3 tables - mysql

I'm trying to build a pivot table from three tables. I've been reading for example MySQL pivot with 3 tables but seem to get nowhere. Or have I been wasting my time and should have built the output in the programming language from the results of three queries instead of doing it in a single SQL query?
My tables are:
Table 1:
id | code | description etc.
1 | paraA1 | ...
2 | paraA2 | ...
3 | paraB1 | ...
4 | paraB2 | ...
5 | paraB3 | ...
....
n | paraZn | ...
Table 2:
id | tr | set | etc.
1234 | 11 | 86 | ...
1235 | 13 | 86 | ...
1236 | 14 | 86 | ...
1237 | 18 | 86 | ...
1238 | 11 | 87 | ...
1239 | 12 | 87 | ...
1240 | 13 | 87 | ...
Table 3:
id | table1_id | table2_id | value
345 | 1 | 1234 | 20
346 | 3 | 1237 | 17
347 | 2 | 1235 | 42
348 | 5 | 1235 | 33
And the output table I want is built with these restrictions:
table1.code as columns, more rows could be added in this table so it should be done dynamically
table2.tr values as rows WHERE set = x (i.e. for one set at the time)
table3.value values as cell contents
also blank rows and columns are included (in this example row 14 and column paraB2)
So the output from the above tables for set 86 would be
id | paraA1 | paraA2 | paraB1 | paraB2 | paraB3 | ... paraZn | set
11 | 20 | | | | | | 86
13 | | 42 | | | 33 | | 86
14 | | | | | | | 86
18 | | | 17 | | | | 86
Database structure for source tables unfortunately cannot be modified.

Related

Joining two MySQL tables and then performing basic filtering on one of them

I have two tables, they look roughly like below:
Categories:
+----+-----------+--------+----------------+
| id | entity_id | set_id | type |
+----+-----------+--------+----------------+
| 1 | 49 | 1 | signup |
| 2 | 57 | 1 | signup |
| 3 | 65 | 1 | signup |
| 4 | 69 | 1 | recommendation |
| 5 | 73 | 1 | signup |
| 6 | 77 | 1 | recommendation |
| 7 | 23 | 2 | comment |
| 8 | 45 | 2 | recommendation |
| 9 | 56 | 2 | signup |
| 10| 76 | 2 | signup |
+----+-----------+--------+----------------+
Steps:
+----+--------+----------+--------+
| id | set_id | start_id | end_id |
+----+--------+----------+--------+
| 1 | 1 | 49 | 57 |
| 2 | 1 | 57 | 65 |
| 3 | 1 | 65 | 69 |
| 4 | 1 | 69 | 73 |
| 5 | 1 | 77 | 57 |
| 6 | 2 | 23 | 45 |
| 7 | 2 | 45 | 56 |
| 8 | 2 | 56 | 76 |
+----+--------+----------+--------+
I need to select the rows of a given set_id from categories, but only if their entity_id is found in steps in the start_id column but NOT the end_id column.
So from this example, these are the two records I would expect to be returned:
+----+-----------+--------+----------------+
| id | entity_id | set_id | type |
+----+-----------+--------+----------------+
| 1 | 49 | 1 | signup |
| 6 | 77 | 1 | recommendation |
+----+--------+----------+-----------------+
I know how to perform a basic join on set_id, but I'm not sure how to search all the records of that set_id from steps and exclude those that have an entity_id that appears in end_id
Something like:
SELECT * FROM categories JOIN steps ON categories.set_id = steps.set_id WHERE ....categories.entity_id is present in steps.start_id but not in steps.end_id??
How should this logic be properly formed?
Exists logic works well here and is also straightforward:
SELECT c.*
FROM categories c
WHERE
EXISTS (SELECT 1 FROM steps s WHERE s.set_id = c.set_id AND c.entity_id = s.start_id)
AND
NOT EXISTS (SELECT 1 FROM steps s WHERE s.set_id = c.set_id AND c.entity_id = s.end_id);
The above query, read in plain English, says to find all category records such that there exists a matching record in steps where the entity_id matches the start_id, but there does not also exist a record in steps where the entity_id appears in end_id.

Sort Columns by Column_Totals through Alter Table or Select Query in MySQL at Runtime

I want to alter or generate Select Query of the Source_Table below at runtime by getting the column total (sum) first then sort according to its result:
Source_Table:
+----+------------+-----------+-----------+-----------+-----------+-----------+
| ID | Name | Field_1 | Field_2 | Field_3 | Field_4 | Field_5 |
+----+------------+-----------+-----------+-----------+-----------+-----------+
| 1 | abc | 10 | 18 | 5 | 21 | 6 |
+----+------------+-----------+-----------+-----------+-----------+-----------+
| 2 | ghq | 22 | 14 | 12 | 11 | 23 |
+----+------------+-----------+-----------+-----------+-----------+-----------+
| 3 | xyz | 35 | 8 | 16 | 7 | 4 |
+----+------------+-----------+-----------+-----------+-----------+-----------+
The Result_Table I am looking at is:
|--------------- sorted fields based on total --------------|
+------------+-----------+-----------+-----------+-----------+-----------+
| Name | Field_5 | Field_3 | Field_4 | Field_2 | Field_1 |
+------------+-----------+-----------+-----------+-----------+-----------+
| abc | 4 | 5 | 21 | 18 | 10 |
+------------+-----------+-----------+-----------+-----------+-----------+
| ghq | 23 | 12 | 11 | 14 | 22 |
+------------+-----------+-----------+-----------+-----------+-----------+
| xyz | 4 | 16 | 7 | 8 | 35 |
+------------+-----------+-----------+-----------+-----------+-----------+
| Total | 31 | 33 | 39 | 40 | 67 | --> get column sum and sort from lowest to highest
+------------+-----------+-----------+-----------+-----------+-----------+
I am not so sure if this is possible with MySQL as I am not able to find good reference in the internet for this case. But I will try..

MySQL get multiple rows into columns

I have a table called visits where concat(s_id, c_id) is unique and id is the primary key. s_id is the ID number of a website and c_id is a campaign ID number. I want to show all the hits each campaign is getting and group by the site. I want each site on a single row
+-----+------+------+------+
| id | s_id | c_id | hits |
+-----+------+------+------+
| 1 | 13 | 8 | 245 |
| 2 | 13 | 8 | 458 |
| 3 | 13 | 3 | 27 |
| 4 | 13 | 4 | 193 |
| 5 | 14 | 1 | 320 |
| 6 | 14 | 1 | 183 |
| 7 | 14 | 3 | 783 |
| 8 | 14 | 4 | 226 |
| 9 | 5 | 8 | 671 |
| 10 | 5 | 8 | 914 |
| 11 | 5 | 3 | 548 |
| 12 | 5 | 4 | 832 |
| 13 | 22 | 8 | 84 |
| 14 | 22 | 1 | 7 |
| 15 | 22 | 3 | 796 |
| 16 | 22 | 4 | 0 |
+----+------+------+-------+
I would like to have the following result set:
s_id | hits | hits | hits| hits
13 | 245 | 458 | 27 | 193
14 | 320 | 183 | 783 | 226
5 | 671 | 914 | 548 | 832
22 | 84 | 7 | 796 | 0
Here is what I have tried which does not pull all the hits columns back.
SELECT v.*, v2.* FROM visits v
INNER JOIN visits v2 on v.s_id = v2.s_id
GROUP BY s_id
How can I get multiple rows into columns?
If your'e data set is not crazy huge and you are just trying to get the multiple rows as a single row.... one way to do this...
SELECT
s_id,
GROUP_CONCAT(hits SEPARATOR ',') as hits_list
FROM
visits
GROUP BY s_id
Since it doesn't use any joins or subqueries etc, i find this way to be quite fast.
you can later split/explode the data based on the ',' separator in PHP or whatever language you are using.
$hits = explode($hits_list, ','); //get them in an array

Mysql difference between max() and min()?

I got a problem with a mySql query and max() function.
If I do :
Select * from Data group by experiment having min(timestamp)
This query return what I want, and correct value.
I got this :
+----------+---------+----------+---------------------+----------------+------------+
| id | mote_id | label_id | timestamp | value | experiment |
+----------+---------+----------+---------------------+----------------+------------+
| 3768806 | 10 | 30 | 2014-04-22 14:37:07 | 0 | 13 |
| 10989209 | 12 | 22 | 2014-04-25 10:44:03 | 2.532958984375 | 15 |
| 11943537 | 6 | 19 | 2014-05-05 17:20:15 | 1228 | 16 |
| 12042549 | 16 | 26 | 2014-05-06 10:48:59 | 22.86 | 17 |
| 12176642 | 15 | 23 | 2014-05-07 15:19:35 | 0 | 18 |
| 12195344 | 10 | 6 | 2014-05-07 15:27:23 | 3460 | 19 |
| 12222470 | 15 | 8 | 2014-05-07 15:38:38 | 1 | 21 |
| 12343934 | 10 | 19 | 2014-05-12 10:35:42 | 742 | 23 |
+----------+---------+----------+---------------------+----------------+------------+
But, if i do :
Select * from Data group by experiment having max(timestamp)
This query return wrong values... like this :
+----------+---------+----------+---------------------+----------------+------------+
| id | mote_id | label_id | timestamp | value | experiment |
+----------+---------+----------+---------------------+----------------+------------+
| 3768806 | 10 | 30 | 2014-04-22 14:37:07 | 0 | 13 |
| 10989209 | 12 | 22 | 2014-04-25 10:44:03 | 2.532958984375 | 15 |
| 11943537 | 6 | 19 | 2014-05-05 17:20:15 | 1228 | 16 |
| 12042549 | 16 | 26 | 2014-05-06 10:48:59 | 22.86 | 17 |
| 12176642 | 15 | 23 | 2014-05-07 15:19:35 | 0 | 18 |
| 12195344 | 10 | 6 | 2014-05-07 15:27:23 | 3460 | 19 |
| 12222470 | 15 | 8 | 2014-05-07 15:38:38 | 1 | 21 |
| 12343934 | 10 | 19 | 2014-05-12 10:35:42 | 742 | 23 |
+----------+---------+----------+---------------------+----------------+------------+
In the first query, if I replace min(timestamp) by timestamp=min(timestamp), it works, but in the second, "timestamp=max(timestamp)" return nothing
Finally, Select experiment,max(timestamp) return correct values.
mysql> select *,max(timestamp) from Data group by experiment;
+----------+---------+----------+---------------------+----------------+------------+---------------------+
| id | mote_id | label_id | timestamp | value | experiment | max(timestamp) |
+----------+---------+----------+---------------------+----------------+------------+---------------------+
| 3768806 | 10 | 30 | 2014-04-22 14:37:07 | 0 | 13 | 2014-04-24 16:03:29 |
| 10989209 | 12 | 22 | 2014-04-25 10:44:03 | 2.532958984375 | 15 | 2014-05-05 10:34:35 |
| 11943537 | 6 | 19 | 2014-05-05 17:20:15 | 1228 | 16 | 2014-05-06 10:35:15 |
| 12042549 | 16 | 26 | 2014-05-06 10:48:59 | 22.86 | 17 | 2014-05-07 15:19:33 |
| 12176642 | 15 | 23 | 2014-05-07 15:19:35 | 0 | 18 | 2014-05-07 15:27:23 |
| 12195344 | 10 | 6 | 2014-05-07 15:27:23 | 3460 | 19 | 2014-05-07 15:38:01 |
| 12222470 | 15 | 8 | 2014-05-07 15:38:38 | 1 | 21 | 2014-05-07 16:30:38 |
| 12343934 | 10 | 19 | 2014-05-12 10:35:42 | 742 | 23 | 2014-05-14 09:25:44 |
+----------+---------+----------+---------------------+----------------+------------+---------------------+
I know I can make a subquery to solve my probleme, but the tables contains thousands rows, and this solution is too long...
Ps : I can't use Select*, max(timestamp) even if it works because the query is run by EJB in JEE.
You select not determined values grouped by field experiment. No one can give you a guarantee that non-agregated fields would correspond to MIN or MAX values of some aggregated field.
You HAVE TO use sub-query or self-join to get the right records.
See more here: http://dev.mysql.com/doc/refman/5.6/en/example-maximum-column-group-row.html
The HAVING clause expects a boolean expression. In other DBMS your code sample would trigger an error. In MySQL, you'll get the expression cast to boolean:
Zero → false
Non-zero → true
And since your expression is constant for the whole set, it won't filter out partial rows.
As about this:
HAVING timestamp = max(timestamp)
The HAVING clause evaluates after WHERE and GROUP BY. At that point, using individual row values of the timestamp column doesn't make any sense. As usual, MySQL allows that but you must take into account that:
In standard SQL, a query that includes a GROUP BY clause cannot refer
to nonaggregated columns in the HAVING clause that are not named in
the GROUP BY clause. A MySQL extension permits references to such
columns to simplify calculations. This extension assumes that the
nongrouped columns will have the same group-wise values. Otherwise,
the result is indeterminate.
In other words, your results are arbitrary (not even random).

mysql: If a value appears more than once, get values from the others similar rows and create new columns

Here is an example of my table patients:
id | case_id | patient_id | syndrome | age |
------------------------------------------------------
1 | 23 | 24 | stable | 45 |
2 | 24 | 25 | stable | 64 |
3 | 25 | 24 | coronary | 46 |
4 | 26 | 27 | stable | 73 |
5 | 27 | 24 | stable | 48 |
6 | 28 | 25 | coronary | 67 |
7 | 29 | 40 | coronary | 68 |
If patient_id appears more than once, i only want to show the first row and add extra columns to this row containing the values of the others similar patient_ids.
For example
id | case_id | patient_id | syndrome | age | syndrome2 | age2 | syndrome3 | age3
--------------------------------------------------------------------------------------------
1 | 23 | 24 | stable | 45 | coronary | 46 | stable | 48
2 | 24 | 25 | stable | 64 | coronary | 67 | |
7 | 29 | 40 | coronary | 68 |
Is this feasible using mysql code?
Currently, I have this code:
SELECT *, count(patients.patient_id) as PatientsNo FROM patients
GROUP BY patient_id
HAVING COUNT(patient_id) > 1
Any suggestions please?
Thank you in advance,
Zinon