MySQL dynamic cross tab - mysql

I have a table like this:
way stop time
1 1 00:55
1 2 01:01
1 3 01:07
2 2 01:41
2 3 01:47
2 5 01:49
3 1 04:00
3 2 04:06
3 3 04:12
and I want a table like this:
stop way_1 way_2 way_3 (way_n)
1 00:55 04:00
2 01:01 01:41 04:06
3 01:07 01:47 04:12
5 01:49
There are many solutions online about MySQL cross tab (pivot table), but how can I do this if I don't know how many "way" are there?
Thanks

The number and names of columns must be fixed at the time you prepare the query. That's just the way SQL works.
So you have two choices of how to solve this. Both choices involve writing application code:
(1) Query the distinct values of way and then write code to use these to construct the pivot query, appending as many columns in the SELECT-list as the number of distinct values.
foreach ($pdo->query("SELECT DISTINCT `way` FROM `MyTable`") as $row) {
$way = (int) $row["way"];
$way_array[] = "MAX(IF(`way`=$way, `time`)) AS way_$way";
}
$pivotsql = "SELECT stop, " . join(", ", $way_array) .
"FROM `MyTable` GROUP BY `stop`";
Now you can run the new query, and it has as many columns as the number of distinct way values.
$pivotstmt = $pdo->query($pivotsql);
(2) Query the data row by row as it is structured in your database, and then write code to pivot into columns before you display the data.
$stoparray = array();
foreach ($pdo->query("SELECT * FROM `MyTable`") as $row) {
$stopkey = $row["stop"];
if (!array_key_exists($stopkey, $stoparray)) {
$stoparray[$stopkey] = array("stop"=>$stopkey);
}
$waykey = "way_" . $row["way"];
$stoparray[$stopkey][$waykey] = $row["time"];
}
Now you have an array of arrays that looks the same as if you had run a pivot query, but the actual SQL you ran was a lot simpler. You post-processed the query result into a different set of arrays.

Related

Recursively running a MySQL function

I have a function in MySQL that needs to be run about 50 times (not a set value) in a query. the inputs are currently stored in an array such as
[1,2,3,4,5,6,7,8,9,10]
when executing the MySQL query individually it's working fine, please see below
column_name denotes the column it's getting the data for, in this case, it's a DOUBLE in the database
The second value in the MOD() function is the input I'm supplying MySQL from the aforementioned array
SELECT id, MOD(column_name, 4) AS mod_output
FROM table
HAVING mod_output > 10
To achieve the output I require* the following code works
SELECT id, MOD(column_name, 4) AS mod_output1, MOD(column_name, 5) AS mod_output2, MOD(column_name, 6) AS mod_output3
FROM table
HAVING mod_output1 > 10 AND mod_output2 > 10 AND mod_output3 > 10
However this obviously is extremely dirty, and when having not 3 inputs, but over 50, this will become highly inefficient.
Appart from calling over 50 individual querys, is there a better way to acchieve the same sort (see below) of output?
In escennce i need to supply MySQL with a list of values and have it run MOD() over all of them on a specified column.
The only data I need returned is the id's of the rows that match the MOD() functions output with the specified input (see value 2 of the MOD() function) where the output is less than 10
Please note, MOD() has been used as an example function, however, the final function required *should* be a drop in replacement
example table layout
id | column_name
1 | 0.234977
2 | 0.957739
3 | 2.499387
4 | 48.395777
5 | 9.943782
6 | -39.234894
7 | 23.49859
.....
(The title may be worded wrong, I'm not quite sure how else you'd explain what I'm trying to do here)
Use a join and derived table or temporary table:
SELECT n.n, t.id, MOD(t.column_name, n.n) AS mod_output
FROM table t CROSS JOIN
(SELECT 4 as n UNION ALL SELECT 5 UNION ALL SELECT 6 . . .
) n
WHERE MOD(t.column_name, n.n) > 10;
If you want the results as columns, you can use conditional aggregation afterwards.

How can I query empty tables to check emptiness?

I have the following database structure.
applets
--id (incr)
applet_stats
--id (incr)
--applet_id (relates to applet.id)
--date ("Y-m-d" format)
Example data:
applets
1
2
3
applet_stats
1 1 2015.01.15
2 1 2015.01.15
3 1 2015.01.15
4 1 2015.01.15
5 2 2015.01.14
In the above example, we have 3 applets. Applet 1 has 4x statistics on 2015.01.15, Applet 2 1x statistics on 2015.01.14 (yesterday)
How can I query the database so I get the following output:
// Show me the applets which doesn't have any statistics on "2015.01.15".
[2,3]
I tried it myself using leftJoin but I'm overlooking something very simple so I get wrong result each time.
Ps. Would be even better in an Eloquent statement.
Eloquent way, assumings stats is hasMany relation:
$date = '2015-01-15';
Applet::whereDoesntHave('stats', function ($q) use ($date) {
$q->where('date', $date);
})->get();
SELECT id
from applets
where id not in(select distinct applet_id from applet_stats where date ='2015.01.15')
this may helps you

MySql NOT IN failing to return empty set

I am currently having a problem when trying to select where a job is listed in the tbl_jobs table and has not been assigned to a delivery item in the tbl_delivery_items table by using a NOT IN subquery.
The sub query should return supplier_job_job_id 1 (which it does when you run this as a seperate query), with the NOT IN excluding the job with an id of 1. Alas, it is not working and causing me a headache by returningthe job with a job_id of 1 when I was expecting an empty set. Here is the codeigniter code generating the query:
$this->db->join("tbl_jobs", "tbl_jobs.job_id = tbl_supplier_jobs.supplier_job_job_id");
$this->db->where_not_in("supplier_job_job_id", "SELECT delivery_item_job_id FROM tbl_delivery_items");
$result = $query->result_array();
echo $this->db->last_query();
return $result;
Here is the query it generates:
SELECT * FROM (`tbl_supplier_jobs`) JOIN `tbl_jobs` ON `tbl_jobs`.`job_id` = `tbl_supplier_jobs`.`supplier_job_job_id` WHERE `supplier_job_job_id` NOT IN ('SELECT delivery_item_job_id FROM tbl_delivery_items') AND `supplier_job_supplier_id` = '1' ORDER BY `tbl_jobs`.`job_number` DESC
And here is the data:
tbl_supplier_jobs
supplier_job_id | supplier_job_job_id | supplier_job_supplier_id
1 1 1
2 2 2
tbl_jobs
job_id | job_number | job_description | job_delivered
1 1024 aaaaa 0
2 2048 bbbbb 0
tbl_delivery_items
delivery_item_id | delivery_item_delivery_id | delivery_item_job_id | delivery_item_toa | delivery_item_pallet_quantity | delivery_item_box_quantity
1 1 1 2014-08-18 16:23:04 2 1
Any ideas?
The problem is that the subquery is rendered as a string. You can see this clearly in the generated query that you supplied.
This seems to be a limitation in the where_not_in method of CodeIgniter. A possible solution, change the code to call the where method and render a slightly larger part of the query yourself:
$this->db->where("supplier_job_job_id NOT IN (SELECT delivery_item_job_id FROM tbl_delivery_items)");
The query isn't executing the subquery it is using the string value:
`supplier_job_job_id` NOT IN (
'SELECT delivery_item_job_id FROM tbl_delivery_items'
)
Will check if supplier_job_job_id equals the string 'SELECT delivery_item_job_id FROM tbl_delivery_items'.
You should consider a LEFT JOIN to tbl_delivery_items and a WHERE condition of delivery_item_job_id IS NULL.. which should be fairly easy in your framework.
Your subselect is being output as a string. Note that it is in single quotes in your resulting query. That of course will not work.
I would actually question your intended approach here. As your tbl_delivery_items table gets bigger and bigger your query will get slower and slower. This is not a scalable approach. You should revisit your table schema and get a more direct way of flagging completed deliveries.

MySQL - get all column averages also with a 'total' average

I have a MySQL table which looks like this:
id load_transit load_standby
1 40 20
2 30 15
3 50 10
I need to do the following calculations:
load_transit_mean = (40+30+50)/3 = 40
load_standby_mean = (20+15+10)/3 = 15
total_mean = (40+15)/2 = 27.5
Is it possible to do this in a single query? What would the best design be?
I need my answer to be scalable (the real design has more rows and columns), and able to handle some rows containing NULL.
I believe this would do it:
SELECT AVG(Load_transit)
, AVG(load_standby)
, (AVG(Load_transit) + AVG(load_standby))/2.0
FROM table
The AVG() function handles NULL's in that it ignores them, if you want the NULL row to be counted in your denominator you can replace AVG() with SUM() over COUNT(*), ie:
SUM(load_transit)/COUNT(*)
Regarding scalability, manually listing them out like above is probably the simplest solution.

MySQL fetch results, sum it up

i'm trying to sum up values from one record (A) then add it to the sum values from another record (B), am wondering if this is possible?
ID ValueA ValueB ValueC
1 5 5 5
2 1 2 3
3 6 3 3
So what i'm trying to do here is to take ValueA, ValueB and ValueC of each record, adds it up so i can make an average.
So for
ID 1, i'll have 15 divide by 3 = 5
ID 2 i'll have 6 divide by 3 = 2
ID 3 i'll have 12 divide by 3 = 4
then i will have to add all 3 of these up
i'll get 11
and divide it by 3 and get an average of 3.67.
My Query
$result = mysql_query('SELECT * FROM teams WHERE UPPER(team)=UPPER("'.$team.'")');
while($row = mysql_fetch_array($result))
{
$ValueA = $row['ValueA'];
$ValueB = $row['ValueB'];
$ValueC = $row['ValueC'];
$All = $ValueA + $ValueB + $ValueC;
}
I know how to get the sum of 1 record, but not sure how can i do it with all 3 records. any help?
Edit : Sorry i forgot to add that i'll have to do average on each record first.
SELECT AVG(valueA + valueB + valueC)
FROM teams
should do the trick. Since you're selecting all records from the table, there's on grouping required.
Note that by default MySQL uses case-insensitive collations on tables, so your UPPER(team) business might not be required - removing the function calls would allow indexes (if any) to be applied to that particular match.
You can sum SQL functions together:
SELECT
(sum(ValueA) + sum(ValueB) + sum(ValueC)) / 3 as average
FROM
<your table>
WHERE
<your conditions>
SELECT SUM(ValueA + ValueB + ValueC) / (SELECT COUNT(*) FROM Teams)
FROM Teams
That will you the average you are looking for.