Creating columns with values from a group of values from another column - mysql

I have a database that has two columns in a table called jos_facileforms_subrecords. I want to report on: record, value
Record contains a set of unique record values, and values contains the values I want to turn into columns. i.e.:
RECORD | NAME | VALUE
1 | firstname | Frank
1 | lastname | Smith
1 | email | fsmith#email.com
2 | firstname | Sally
2 | lastname | Jones
2 | email | sjones#email.com
3 | firstname | Peter
3 | lastname | Baker
3 | email | pbaker#gmail.com
I want to turn this into a row per record set. i.e.
FIRST NAME | LAST NAME | EMAIL
Frank | Smith | fsmith#email.com
Sally | Jones | sjones#email.com
Peter | Baker | pbaker#email.com

What you are looking for is a PIVOT query. Other DBMS's have functions for this, unfortunately MySQL does not, so you have to do it manually. There are several ways to accomplish this. Here's one that will work for your situation:
select
f.`record` as `record`,
f.`value` as `first name`,
l.`value` as `last name`,
e.`value` as `email`
from
jos_facileforms_subrecords f
inner join jos_facileforms_subrecords l on f.record = l.record
inner join jos_facileforms_subrecords e on f.record = e.record
where
f.name = 'firstname'
and l.name = 'lastname'
and e.name = 'email'
group by f.`record`
See it working in this fiddle.

Related

What does the equal sign mean in this SQL Join statement?

I'm new to sql and do not understand what this join statement is doing. Does this statement ON people.state_code=states.state_abbrev mean that people.state_code and states.state_abbrev are now one?
SELECT people.first_name,
people.state_code, states.division
FROM people
JOIN states ON people.state_code=states.state_abbrev;
It will take the columns first_name and state_code from the table people and the column division from the table states and put them together in a join table where the entries in the state_code and state_abbrev columns match. The join table is produced only for display in response to this query; the underlying tables with the data entries are not amended.
In this case the '=' means equal (like values are equal) and is part of the join condition based on which data is retrieved by the select statement. You are 'linking' the two tables based on a condition so you can retrieve related data...
Relational data base - there are relations between tables and between data.
For example:
table_1
PERSON_ID FIRST_NAME LAST_NAME ADDRESS_ID
1 |John |Doe |2
table_2
ADRESS_ID STREET
1 | 5th Avenue
2 | 1st Street
SELECT FIRST_NAME, STREET
FROM table_1 t1
JOIN table_2 t2 ON t1.ADDRESS_ID = t2.ADDRESS_ID;
will return
John, 1st Street
Does this statement ON people.state_code=states.state_abbrev mean that people.state_code and states.state_abbrev are now one?
Answer: NO. people.state_code and states.state_abbrev should be the same value on the respective tables.
Let me give you an example taken from https://www.mysqltutorial.org/mysql-join/
Suppose you have below tables:
CREATE TABLE members (
member_id INT AUTO_INCREMENT,
members_name VARCHAR(100),
PRIMARY KEY (member_id)
);
CREATE TABLE committees (
committee_id INT AUTO_INCREMENT,
committees_name VARCHAR(100),
PRIMARY KEY (committee_id)
);
Some data examples:
+-----------+--------+
| member_id | members_name |
+-----------+--------+
| 1 | John |
| 2 | Jane |
| 3 | Mary |
| 4 | David |
| 5 | Amelia |
+-----------+--------+
+--------------+--------+
| committee_id | committees_name |
+--------------+--------+
| 1 | John |
| 2 | Mary |
| 3 | Amelia |
| 4 | Joe |
+--------------+--------+
To do the INNER JOIN we can use members_name and committees_name not the id because they are auto_increment and the data are not related.
So the query would be:
SELECT
m.member_id,
m.members_name AS member,
c.committee_id,
c.committees_name AS committee
FROM members m
INNER JOIN committees c ON c.name = m.name;
Giving below result:
+-----------+--------+--------------+-----------+
| member_id | member | committee_id | committee |
+-----------+--------+--------------+-----------+
| 1 | John | 1 | John |
| 3 | Mary | 2 | Mary |
| 5 | Amelia | 3 | Amelia |
+-----------+--------+--------------+-----------+
Conclusion: The values of the columns are equaly the same

MySQL: How to make a query between 2 tables that returns NULL to a row that isn't in the 2nd table?

I have this 2 tables
1st Table "Users"
+----+-----------+----------+
| ID | FirstName | LastName |
+----+-----------+----------+
| 1 | Jeff | Bezos |
| 2 | Bill | Gates |
| 3 | Elon | Musk |
+----+-----------+----------+
2nd Table "Records"
+----+--------+------------+
| ID | IDUser | RecordDate |
+----+--------+------------+
| 1 | 1 | 15/06/2021 |
| 2 | 2 | 05/06/2021 |
| 3 | 2 | 12/06/2021 |
| 4 | 2 | 02/06/2021 |
| 5 | 1 | 17/06/2021 |
+----+--------+------------+
So this 2 tables are linked each other by using a Foreing key Records.IDUsers -> Users.ID
I wanted to make a query that does this
+-----------+----------+----------------+--------------------+
| FirstName | LastName | Lastest Record | Numbers of Records |
+-----------+----------+----------------+--------------------+
| Jeff | Bezos | 17/06/2021 | 2 |
| Bill | Gates | 12/06/2021 | 3 |
| Elon | Musk | NULL | NULL |
+-----------+----------+----------------+--------------------+
You need to use LEFT JOIN in order to get back users without records too; then the MAX and COUNT aggregate functions.
First version: This will return 0 for the number of records instead of NULL, when there are no records for a specific user. Latest record will be NULL as expected.
SELECT
FirstName,
LastName,
MAX(RecordDate) AS LatestRecord,
COUNT(Records.ID) AS NumberOfRecords
FROM Users LEFT JOIN Records on Users.ID = Records.IDUser
GROUP BY Users.ID;
If you want NULL instead of 0 (which normally you do not want), you can use the IF function like this:
SELECT
FirstName,
LastName,
MAX(RecordDate) AS LatestRecord,
IF(COUNT(Records.ID) > 0, COUNT(Records.ID), NULL) AS NumberOfRecords
FROM Users LEFT JOIN Records on Users.ID = Records.IDUser
GROUP BY Users.ID;
Second version: It might happen that running the above query will return an error, something like:
Error: ER_WRONG_FIELD_WITH_GROUP: ...; this is incompatible with sql_mode=only_full_group_by
This happens when/if the ONLY_FULL_GROUP_BY SQL mode is enabled (which it is by default since MySQL 5.7.5). In order to get around this error, you can use the ANY_VALUE function to select the nonaggregated fields:
SELECT
ANY_VALUE(FirstName) AS FirstName,
ANY_VALUE(LastName) AS LastName,
MAX(RecordDate) AS LatestRecord,
COUNT(Records.ID) AS NumberOfRecords
FROM Users LEFT JOIN Records on Users.ID = Records.IDUser
GROUP BY Users.ID;
left join select all user even if does not have records
select * from users left join records on records.IDUser = ID;

GroupBy ID but keep multiple values

I have the following MySQL table:
+----------+----------+---------+-------------+------------+----------+----------+-----------+
| queue_id | email_id | user_id | customer_id | send_date | campaign | approved | scheduled |
+----------+----------+---------+-------------+------------+----------+----------+-----------+
| 1 | 1 | 1 | 1 | 2018-10-30 | 1 | 1 | 1 |
| 2 | 1 | 2 | 1 | 2018-10-30 | 1 | 1 | 1 |
| 3 | 2 | 1 | 1 | 2018-11-02 | 1 | 1 | 1 |
| 4 | 2 | 2 | 1 | 2018-11-02 | 1 | 0 | 1 |
| 5 | 2 | 3 | 1 | 2018-11-02 | 1 | 1 | 1 |
+----------+----------+---------+-------------+------------+----------+----------+-----------+
Where the email_id, user_id, and customer_id are all foreign keys.
What I need to do is return the send_date, subject (which is apart of the email table that the email_id references), and name (which is apart of the business table that the user_id references) but only for columns where the approved column is true. The idea is to ultimately display the data to a user in an HTML table where the table would look like the following (using the sample data provided):
+--------------------+--------------------------+---------------+
| October 30th, 2018 | Subject for email_id "1" | View Approved |
| November 2nd, 2018 | Subject for email_id "2" | View Approved |
+--------------------+--------------------------+---------------+
Whenever the user would click on the "View Approved" cell, then it would display all of the business names that approved that particular email.
I tried using the following query, but it is only returning one value in the name column:
SELECT
DATE_FORMAT(q.`send_date`, "%M %D, %Y") AS `date_visited`,
e.`subject`,
b.`name`
FROM
`email_queue` AS q
INNER JOIN
`email` AS e ON q.`email_id` = e.`email_id`
INNER JOIN
`user` AS u ON q.`user_id` = u.`user_id`
INNER JOIN
`business` AS b ON u.`business_id` = b.`business_id`
WHERE
q.`approved` = true
GROUP BY
e.`email_id`
ORDER BY
q.`send_date` DESC
How can I structure my query to where it would return all of the business names in the name column instead of just one?
You can get all the unqiue business names in a Comma separated string, using Group_Concat() function with Distinct clause.
Try:
GROUP_CONCAT(DISTINCT b.`name` SEPARATOR ',') AS name
instead of:
b.`name`
Note:
You can avoid the usage of Distinct clause, if there would not be any duplicate user_id (for a specific email_id), thus ensuring that b.name is also unique.
You can also use any separator, instead of comma. For eg: to use separator as pipe character |, you would write the query as:
GROUP_CONCAT(DISTINCT b.nameSEPARATOR '|') AS name

SQL alternative to double subquery

I have a table MyTable with values that look like this:
| id | name | type | category |
---------------------------------------------------
| 1 | Rob | Red | Rock |
| 2 | Rob | Blue | Rap |
| 2 | Rob | Blue | Rock |
| 3 | Jane | Green | Country |
| 3 | Jane | Green | Rap |
| 4 | Meg | Yellow | Rock |
| 5 | Jane | Blue | Rap |
| 5 | Jane | Blue | Rock |
| 6 | Jane | Red | Country |
| 6 | Jane | Red | Rock |
| 7 | Rob | Red | Rap |
| 7 | Rob | Red | Country |
| 8 | Meg | Green | Country |
| 9 | Meg | Blue | Rap |
Now, my issue resides in the fact that (as the data is given to me), there are duplicate ids. Each id stands for a report, so id of 1 is for report 1. In this report, there is a name, type, and category. The report can only have one type, but as many categories as it likes. Hence, the duplicate ids come from there being different categories, each constituting a new row. The end result i wish to achieve it to list the names in one row, along with all the types + count (where count is the count of the distinct types, as in one type per report) in the next row. It would look like such:
| Rob | Red(2), Blue(1) |
| Jane | Green(1), Blue(1), Red(1) |
| Meg | Yellow(1), Green(1), Blue(1)|
Now, I've developed a query that actually uses two subqueries and successfully achieves this result. It goes
select name as firstCol, group_concat(type_counts order by countName desc separator ', ') as secondCol from
(select name, concat(type,' (',count(name),')') as type_counts, count(name) as countName from
(select name, type from
some join stuff
where (date_reported between '2014-11-01' and '2014-11-31')
group by id order by type, name) a
group by name, type order by name, count(name) desc) a
group by name;
This query essentially groups by id first, to remove the duplicate ids and disregard the split due to differing categories. The query wrapping it then groups by name and traffic type, concatenating the traffic type and the count of names together as "type (count)". The third groups solely by name and group concats all of the types to have a column for the name, and then a second column with all of the type+counts listed and separated by commas. I was just wondering...is there a query that could make it faster, by not having to use so many subqueries and such? Thanks.
The end result i wish to achieve it to list the names in one row, along with all the types + count (where count is the count of the distinct types, as in one type per report) in the next row.
Use a subquery as an expression in the SELECT clause and another in the WHERE clause. For example:
SELECT B.Name, B.UserId AS [User Link], (SELECT Count(B2.Name) FROM Badges B2 WHERE B2.Name = B.Name) as [Total Gold, Silver, and Bronze Badges Awarded for this Tag]
FROM Badges B
WHERE Class = 2
AND TagBased = 1
AND (SELECT Count(B2.Name)
FROM Badges B2
WHERE B2.Name = B.Name
AND B2.Class = 2
AND B2.TagBased = 1) = 1
GROUP BY B.Name, B.UserId
ORDER BY B.Name
References
Users with their own Silver Tag Badges - Stack Exchange Data Explorer
Relational Algebra and SQL (pdf)
A Gentle Introduction to SQL

Can you return different values for multiple rows on the same join?

I have two tables:
Parent Information
Child Information
both are structured (almost) the same with a few nuances.
The table structures are as follows:
Parent
ID | First | Last | DOB | Address
-------------------------------------------------
1 | John | Doe | 1980-01-01 | 123 street
Dependents
ParentID | Type | First | Last | DOB
--------------------------------------------------
1 | Spouse | Jane | Doe | 1981-02-01
1 | Child | Mike | Doe | 1999-08-01
1 | Child | Zoe | Doe | 2002-04-01
I want to build a query (ideally single call with joins which returns the following:
Table Results
First | Last | Type | DOB | Address
----------------------------------------------------------------
John | Doe | Parent | 1980-01-01 | 123 Street
Jane | Doe | Spouse | 1981-02-01 | 123 Street
Mike | Doe | Child | 1999-08-01 | 123 street
Zoe | Doe | Child | 2002-04-01 | 123 Street
I suppose I could build the originally subquery with a LEFT JOIN on the dependents table (not all parents have dependents) then run a primary query which filters that table, however - when i do this, the query takes over a full minute to produce. (my tables change hundreds of times a day so keeping an index of the tables is not really an option as I'd have to rebuild constantly).
UPDATE
The more I think about it even the left join would not work necessarily because the parent information and first set of dependent information would reside on the same row from the subquery (and in turn make it 'impossible' for the primary query to filter the single row into multiple).
Any ideas?
SELECT t.First, t.Last, t.Type, t.DOB, t.Address
FROM (SELECT ID, First, Last, 'Parent' as Type, DOB, Address, 1 as SortKey
FROM Parent
UNION ALL
SELECT p.ID, d.First, d.Last, d.Type, d.DOB, p.Address,
CASE WHEN d.Type = 'Spouse' THEN 2 ELSE 3 END as SortKey
FROM Dependents d
INNER JOIN Parent p
ON d.ParentID = p.ID) t
ORDER BY t.ID, t.SortKey