Group data with id and get data from first and last row - mysql

I have a database table schedules, which look like this:
Now, I am trying to get data such as:
----------------------------------------------
| id | bus_id | route | dept_time | arr_time |
----------------------------------------------
| 1 | 1 | 1, 4 | 07:00:59 | 23:30:30 |
----------------------------------------------
route is just the collection of station_id which can be indexed using route_index. When arr_time is NULL, its mean it is the departing station and when dept_time is NULL, its mean, it is the destination. I have group the route with this query:
SELECT id,bus_id,GROUP_CONCAT(station_id SEPARATOR ', ') AS route FROM schedules GROUP BY bus_id;
But I don't know how to get the arr_time and dept_time using this query. Also, how to get station names instead of id in this query. Station table only contains (id and name).

You can use a join to translate station IDs to their names. As for the arrival and departure times - it's a dirty trick, but since aggregate functions ignore nulls, you can use min/max to get them:
SELECT sch.bus_id,
GROUP_CONCAT(st.name ORDER BY route_index SEPARATOR ', ') AS route,
MIN(dept_time) AS dept_time,
MAX(arr_time) AS arr_time
FROM schedules sch
JOIN stations st ON sch.station_id = st.id
GROUP BY bus_id;

Related

ID of the longest string in "group by"

How do I get the id of MAX(name)? The id I get from below query doesn't correspond to the row where MAX(name) is in.
SELECT id, MAX(name) from table group by country
id name country
+---+-----------+---------+
| 1 | John | USA |
+---+-----------+---------+
| 2 | Joe | CHINA |
+---+-----------+---------+
| 3 | Jonah | USA |
+---+-----------+---------+
| 4 | Jonathan | USA |
+---+-----------+---------+
Edit:
The purpose is to get the longest name in every country. So from the table, I'd like to see the result to be id 2 and 4.
You can do it with variables, and self joins, and depending on your version of sql window functions, but you can also do some string manipulation.
SELECT country,
SUBSTRING_INDEX(ids, ',', 1) AS id
FROM (
SELECT country,
GROUP_CONCAT(id ORDER BY LENGTH(NAME) DESC) AS ids
FROM table
GROUP BY country
) z
Also, max(name) won't get you the one with the longest name, it will get you the one closest to the end of the alphabet.
You can also try (assuming you need to account for the possibility of more than one name per country having the same max length):
SELECT z.country, z.id
FROM table z
JOIN (
SELECT country, MAX(LENGTH(name)) AS maxLen
FROM table
GROUP BY country
) lens ON (lens.country = z.country AND lens.maxLen = LENGTH(z.name))
Use a subselect:
SELECT id, country
FROM table
WHERE (country, CHAR_LENGTH(name)) IN (SELECT country, MAX(CHAR_LENGTH(name))
FROM table
GROUP BY country)
GROUP BY country
Note, however, that there may be more than one row returned if there are more than one records having this length of the name!

How do I write a JOIN query in SQL linking to other tables? (Specific q in description)

I'm trying to solve this exercise:
The output for this query should show parent/carer title, first name and surname in a single column, and the total number of activities taken by all children registered to that parent/carer.
This is what I have so far:
SELECT CONCAT(carer_title, ' ', carer_fname, ' ', carer_sname) AS 'carer name'
FROM Carer;
Giving me the first column:
I appreciate this is tricky without looking at the full schema but here's the relevant info, let me know if you think I'm missing any information.
Schema:
Relevant tables and columns:
Carer | carer_id |carer_title | carer_fname | carer_sname | carer_phone | carer_address1 | carer_address2 | carer_town | carer_pcode
Child | child_id | child_fname | child_sname | child_gender | child_dob | child_carer | child_school
Childactivity (Table linking children and activities they partake in made up of foreign keys) | child_id | activity_id
Activity | activity_id | activity_name | activity_day | activity_fee
How would you go about adding a column which finds the total number of activities taken by all children registered to each carer?
You could join the other involved tables, and count per group:
select concat(carer_title, ' ', carer_fname, ' ', carer_sname) as `carer name`,
count(*) `number of activities`
from carer
left join child on child.child_carer = carer.carer_id
left join childactivity on childactivity.child_id = child.child_id
group by carer.id;
If only distinct activities should be counted, so not counting twice when two children participate in the same activity, then replace count(*) by count(distinct childactivity.activity_id)

MySQL JOIN Statement from Multiple Tables

I have an old database of entries from an abandoned "Joomgalaxy" Joomla plugin.
There are three tables, joomgalaxy_entries, joomgalaxy_fields, and joomgalaxy_entries_data
The id from the entries table matches the entry_id in the entries_data table, but the actual field name is saved in another table, fields
Can someone please help me with the correct SQL statement to obtain results like you can see below in Ultimate Goal? My MySQL knowledge is very basic, and from my searching it sounds like I need to use a LEFT JOIN, but I have no idea how to use the value from field_name as the column name for returned values
Thank You!!
joomgalaxy_entries
---------------------------------------
| id | title | longitude | latitude |
---------------------------------------
| 50 | John | -79.333333 | 43.669999 |
| 51 | Bob | -79.333333 | 43.669999 |
---------------------------------------
joomgalaxy_fields
This is just two examples below to keep it simple, there are more than just these two, so it would have to be able to handle dynamically using the field_name as the column name.
--------------------------------
| id | field_type | field_name |
--------------------------------
| 1 | textbox | websiteurl |
| 2 | dropdown | occupation |
--------------------------------
joomgalaxy_entries_data
"Technically" there shouldn't be any duplicate entries (fieldid and entry_id), so from my understanding that shouldn't affect using the field_name from above as the column name, but what if there ends up being one?
-------------------------------------
| fieldid | field_value | entry_id |
-------------------------------------
| 1 | google.com | 50 |
| 2 | unemployed | 50 |
| 1 | doctor.com | 51 |
| 2 | doctor | 51 |
-------------------------------------
Ultimate Goal
Ultimately trying to get this type of result, so I can then use that statement in MySQL Workbench to export the data that would look like this:
------------------------------------------------------------------
| id | title | longitude | latitude | websiteurl | occupation |
------------------------------------------------------------------
| 50 | John | -79.333333 | 43.669999 | google.com | unemployed |
| 51 | Bob | -79.333333 | 43.669999 | doctor.com | doctor |
------------------------------------------------------------------
EDIT:
There are more than just the two fields websiteurl and occupation, I was just using those two as examples, there are numerous fields that are all different, so in theory pulling the value from field_name would be used for the column name
You can use some conditional logic, like a CASE statement, along with an aggregate function like max() or min() to return those values as columns:
SELECT je.id,
je.title,
je.longitude,
je.latitude,
max(case when jf.fieldid = 1 then jed.field_value end) as WebsiteUrl,
max(case when jf.fieldid = 2 then jed.field_value end) as Occupation
FROM joomgalaxy_entries je
INNER JOIN joomgalaxy_entries_data jed
on je.id = jed.entry_id
GROUP BY je.id,
je.title,
je.longitude,
je.latitude
Using an INNER JOIN will only return the joomgalaxy_entries rows that have values in each table, if you want to return all joomgalaxy_entries even if there are no matching rows to join on in the other tables, then change the INNER JOIN to a LEFT JOIN.
You can write a simple SELECT query like this:
SELECT je.id, je.title, je.longitude, je.latitude,
(SELECT field_value FROM joomgalaxy_entries_data WHERE fieldid = 1 AND entry_id = je.id) AS websiteurl,
(SELECT field_value FROM joomgalaxy_entries_data WHERE fieldid = 2 AND entry_id = je.id) AS occupation
FROM joomgalaxy_entries je;
First step is easy:
SELECT JE.id, JE.title, JE.longitude, JE.latitude
FROM joomgalaxy_entries JE
Now you need to JOIN:
SELECT JE.id, JE.title, JE.longitude, JE.latitude,
JD.*
FROM joomgalaxy_entries JE
JOIN joomgalaxy_entries_data JD
ON JE.id = JD.entry_id
Now you need convert rows to columns
SELECT JE.id, JE.title, JE.longitude, JE.latitude,
MIN(CASE WHEN fieldid = 1 THEN JD.field_value END) as WebsiteUrl,
MIN(CASE WHEN fieldid = 2 THEN JD.field_value END) as Occupation
FROM joomgalaxy_entries JE
JOIN joomgalaxy_entries_data JD
ON JE.id = JD.entry_id
GROUP BY JE.id, JE.title, JE.longitude, JE.latitude
This depend on you only have two field for each entry, if number of field is dynamic you would need a different aproach.
This should work:
select id, title, longitude, latitude,
(select field_value from joomgalaxy_entries_data jed
where fieldid = (select id from joomgalaxy_fields
where field_name = 'websiteurl')
and jed.entry_id = je.id
) as websiteurl,
(select field_value from joomgalaxy_entries_data jed
where fieldid = (select id from joomlgalaxy_fields
where field_name = 'occupation')
and jed.entry_id = je.id) as occupation
from joomgalaxy_entries je;
Note that the reason to have a left join would be if either websiteurl or occupation were null, however, this solution should work in that case anyway.
Well, that certainly makes it a bit more difficult... :) Honestly, I'm not sure what you're asking is possible with a static sql query. I'm sure someone will speak up, however, if I'm wrong.
That said, I do have a few options you can try:
Option 1 - Generate the SQL Dynamically
Assuming this is mysql, if you execute the following SQL, it will generate the subqueries dynamically:
select concat('(select field_value from joomgalaxy_entries_data jed ',
'where fieldid = (select id from joomgalaxy_fields ',
'where field_name = ''', field_name, ''') ',
'and jed.entry_id = je.id) as ', field_name, ',')
from joomgalaxy_fields;
Take the result of that command, copy-paste it into a text editor and add the following at the beginning:
select id, title, longitude, latitude,
And the rest of this at the end:
from joomgalaxy_entries je;
Then run your new uber-query and go grab a cup of copy, lunch, or a good night's sleep depending on how much data is in your database.
Alternatively, you could add all of this to a stored procedure so you don't have to hand edit the SQL. Also, note that my syntax works for MySQL. Other databases have different concatenation operators so you may have to work around that if applicable. Also, with 50+ subqueries there is a good chance this uber-query will be quite slow, maybe too slow to make this option viable.
Option 2 - Create a table structured the way you want, and populate it
Hopefully, this is self-explanatory, but just create a new table with all of the necessary columns from the joomgalaxy_fields table. Then populate each column separately with a long series of what should be pretty straightforward sql commands. Granted this option is only viable if the database is no longer in use which I believe you indicated. From there the result is just:
select * from my_new_table;

MySQL: Transfer Data Based on a Column Without Also Transferring That Column

My table stores revision data for my CMS entries. Each entry has an ID and a revision date, and there are multiple revisions:
Table: old_revisions
+----------+---------------+-----------------------------------------+
| entry_id | revision_date | entry_data |
+----------+---------------+-----------------------------------------+
| 1 | 1302150011 | I like pie. |
| 1 | 1302148411 | I like pie and cookies. |
| 1 | 1302149885 | I like pie and cookies and cake. |
| 2 | 1288917372 | Kittens are cute. |
| 2 | 1288918782 | Kittens are cute but puppies are cuter. |
| 3 | 1288056095 | Han shot first. |
+----------+---------------+-----------------------------------------+
I want to transfer some of this data to another table:
Table: new_revisions
+--------------+----------------+
| new_entry_id | new_entry_data |
+--------------+----------------+
| | |
+--------------+----------------+
I want to transfer entry_id and entry_data to new_entry_id and new_entry_data. But I only want to transfer the most recent version of each entry.
I got as far as this query:
INSERT INTO new_revisions (
new_entry_id,
new_entry_data
)
SELECT
entry_id,
entry_data,
MAX(revision_date)
FROM old_revisions
GROUP BY entry_id
But I think the problem is that I'm trying to insert 3 columns of data into 2 columns.
How do I transfer the data based on the revision date without transferring the revision date as well?
You can use the following query:
insert into new_revisions (new_entry_id, new_entry_data)
select o1.entry_id, o1.entry_data
from old_revisions o1
inner join
(
select max(revision_date) maxDate, entry_id
from old_revisions
group by entry_id
) o2
on o1.entry_id = o2.entry_id
and o1.revision_date = o2.maxDate
See SQL Fiddle with Demo. This query gets the max(revision_date) for each entry_id and then joins back to your table on both the entry_id and the max date to get the rows to be inserted.
Please note that the subquery is only returning the entry_id and date, this is because we want to apply the GROUP BY to the items in the select list that are not in an aggregate function. MySQL uses an extension to the GROUP BY clause that allows columns in the select list to be excluded in a group by and aggregate but this could causes unexpected results. By only including the columns needed by the aggregate and the group by will ensure that the result is the value you want. (see MySQL Extensions to GROUP BY)
From the MySQL Docs:
MySQL extends the use of GROUP BY so that the select list can refer to nonaggregated columns not named in the GROUP BY clause. ... You can use this feature to get better performance by avoiding unnecessary column sorting and grouping. However, this is useful primarily when all values in each nonaggregated column not named in the GROUP BY are the same for each group. The server is free to choose any value from each group, so unless they are the same, the values chosen are indeterminate. Furthermore, the selection of values from each group cannot be influenced by adding an ORDER BY clause. Sorting of the result set occurs after values have been chosen, and ORDER BY does not affect which values the server chooses.
If you want to enter the last entry you need to filter it before:
select entry_id, max(revision_date) as maxDate
from old_revisions
group by entry_id;
Then use this as a subquery to filter the data you need:
insert into new_revisions (new_entry_id, new_entry_data)
select entry_id, entry_data
from old_revisions as o
inner join (
select entry_id, max(revision_date) as maxDate
from old_revisions
group by entry_id
) as a on o.entry_id = a.entry_id and o.revision_date = a.maxDate

MySQL results by userd_id?

I have the following MySQL database table structure:
name | product | client_id
JOHN BROWN | test1.com | 122
JANE SMITH | hosting1 | 122
DAN JOHNSON | test2.com | 355
How to show mysql query results in php in order to get tables with results grouped by client_id. The main point is I need those details emailed by client_id, so in the current example I should get two separated tables fore those two clients.
Assuming your table is called signups
SELECT client_id,
GROUP_CONCAT(DISTINCT name_product_email
ORDER BY name_product_email DESC SEPARATOR ',')
FROM (
SELECT CONCAT(name, ' has this product: ', product) as name_product_email, client_id FROM signups)
GROUP BY client_id;
select client_id, group_concat(product)
from my_table
group by 1;
will produce output like:
122, "test1.com,hosting1"
355, "test2.com"
etc