Row to column transform - mysql

After multiple join, I have raw results.
+----------------------------------------------------------------------+
| Results |
+----+----------+-------------+----------+-----------+-----------------+
| id | group_id | question_id | question | answer_id | answer | input |
+----+----------+-------------+----------+-----------+-----------------+
| 1 | 10001 | 1 | How old | 1 | 25 | NULL |
| 2 | 10001 | 2 | What like| 3 | Cola | NULL |
| 3 | 10001 | 2 | What like| 4 | Other | HotDog |
| 4 | 10001 | 3 | City | 5 | NYC | NULL |
| 5 | 10001 | 4 | Name | 7 | Other | Alex |
| 6 | 10002 | 1 | How old | 1 | 25 | NULL |
| 7 | 10002 | 2 | What like| 6 | Candy | NULL |
| 8 | 10002 | 3 | City | 8 | LA | NULL |
| 9 | 10002 | 4 | Name | 7 | Other | Roman |
+----+----------+-------------+----------+-----------+--------+--------+
But now I want to see it in "one row view" by group_id.
Such as:
+----+----------+-------------+----------+-----------+
| id | How Old | What like | City | Name |
+----+----------+-------------+----------+-----------+
| 1 | 25 | Cola,HotDog | NYC | Alex |
| 2 | 25 | Candy | LA | Roman |
+----+----------+-------------+----------+-----------+
I don`t know normal group_by/concat construction for that. What must I do?

SET #i := 1;
SELECT #i := #i + 1 AS `id`
, GROUP_CONCAT(CASE WHEN question = 'How old' THEN answer ELSE NULL END) AS `How Old`
, GROUP_CONCAT(CASE WHEN question = 'What like' THEN IF(answer='Other', input, answer) ELSE NULL END) AS `What like`
, ....
FROM theTable
GROUP BY group_id
;
group_concat ignores null values, only returning null if the only values it received were null. For the CASE WHEN THEN ELSE END statements you could easily use IF(,,) like was used in the "other" check; but CASE is more portable (MS SQL Server only relatively recently added support for those kinds of IFs.)

Related

MySql - How to sort vertically stored data to order by one of field's values horizontally?

I have 3 mysql tables named records, fields and fields_values.
Records table consists of meta data about records and record id.
Fields table
No. of fields for a record are variable, and can be added dynamically.
Fields values contain info about fields shown on the record form eg: what type of fields is it, whether it is required or no etc.
Fields values table
This table contains actual data of records for each field.
For example, these tables have data as below:
Records Table:
+----+-------------+------------+--------+
| id | category_id | created_by | status |
+----+-------------+------------+--------+
| 1 | 1 | 10 | 1 |
| 2 | 1 | 10 | 1 |
| 3 | 1 | 10 | 1 |
+----+-------------+------------+--------+
Fields Table:
+----+------------+------------+------+--------+----------+------------+
| id | title | alias | type | status | required | created_by |
+----+------------+------------+------+--------+----------+------------+
| 1 | First Name | first_name | text | 1 | 1 | 100 |
| 2 | Last Name | last_name | text | 1 | 1 | 100 |
| 3 | City | city | text | 1 | 1 | 100 |
| 4 | State | state | text | 1 | 1 | 100 |
| 5 | Country | country | text | 1 | 1 | 100 |
| 6 | Mobile | mobile | text | 1 | 1 | 100 |
+----+------------+------------+------+--------+----------+------------+
Fields Values Table:
+----+----------+-----------+------------+
| id | field_id | record_id | value |
+----+----------+-----------+------------+
| 1 | 1 | 1 | Andy |
| 2 | 2 | 1 | A |
| 3 | 3 | 1 | Manchester |
| 4 | 4 | 1 | NWE |
| 5 | 5 | 1 | UK |
| 6 | 6 | 1 | 1234567898 |
| 7 | 1 | 2 | Sandy |
| 8 | 2 | 2 | B |
| 9 | 3 | 2 | NYC |
| 10 | 4 | 2 | NY |
| 11 | 5 | 2 | USA |
| 12 | 6 | 2 | 1234567891 |
| 13 | 1 | 3 | Mandy |
| 14 | 2 | 3 | P |
| 15 | 3 | 3 | Mumbai |
| 16 | 4 | 3 | MH |
| 17 | 5 | 3 | IN |
| 18 | 6 | 3 | 1234567893 |
+----+----------+-----------+------------+
And, I want to records as below and want to sort it based on one of the fields as selected by user eg: country
+----+------------+-----------+------------+-------+-----------+------------+
| id | first_name | last_name | city | state | country ^ | mobile |
+----+------------+-----------+------------+-------+---------+------------+
| 3 | Mandy | P | Mumbai | MH | IN | 1234567893 |
| 1 | Andy | A | Manchester | NWE | UK | 1234567898 |
| 2 | Sandy | B | NYC | NY | USA | 1234567891 |
+----+------------+-----------+------------+-------+-----------+------------+
How do I sort such vertically stored data to order by one of field's values in a single query so that it can be shown horizontally?
Just to observe that it would be more usual to write that this way...
SELECT r.id
, MAX(CASE WHEN field_id = 1 THEN value END) first_name
, MAX(CASE WHEN field_id = 2 THEN value END) last_name
, MAX(CASE WHEN field_id = 3 THEN value END) city
, MAX(CASE WHEN field_id = 4 THEN value END) state
, MAX(CASE WHEN field_id = 5 THEN value END) country
, MAX(CASE WHEN field_id = 6 THEN value END) mobile
FROM records r
LEFT
JOIN fields_values v
ON v.record_id = r.id
GROUP
BY r.id;

what is wrong in this sql query [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 3 years ago.
Improve this question
I had created two Tables 'tbl_book_info' and 'tbl_books' (table descriptions
are below) in MySQL Database
what should I change in my SQL query
I had used subquery inside WHERE clause.
All Records of 'tbl_books'-
+-------+-------+--------+
| accid | accno | status |
+-------+-------+--------+
| 10001 | 101 | I |
| 10001 | 102 | I |
| 10001 | 103 | A |
| 10002 | 101 | A |
| 10002 | 102 | A |
| 10002 | 103 | I |
| 10002 | 104 | I |
| 10002 | 105 | I |
| 10003 | 101 | A |
| 10003 | 102 | A |
| 10003 | 103 | A |
| 10003 | 104 | I |
| 10003 | 105 | I |
| 10004 | 101 | A |
| 10004 | 102 | I |
| 10004 | 103 | A |
| 10004 | 104 | A |
| 10004 | 105 | A |
| 10005 | 101 | A |
| 10005 | 102 | A |
| 10005 | 103 | A |
| 10005 | 104 | A |
| 10005 | 105 | A |
+-------+-------+--------+
23 rows in set (0.00 sec)
All Records of 'tbl_book_info'-
+----------+------------------+-------+-------------+---------+---------+-------------+---------+--------+---------+---------+
| b_acc_id | b_name | b_qty | b_type | b_auth1 | b_auth2 | b_pub | b_pages | b_rack | b_price | b_about |
+----------+------------------+-------+-------------+---------+---------+-------------+---------+--------+---------+---------+
| 10001 | Java | 3 | Programming | lala | - | kallo | 800 | 1 | 799.00 | - |
| 10002 | Cpp | 5 | Programming | Kallo | - | Mehta group | 400 | 2 | 300.00 | - |
| 10003 | VB.net | 5 | Programming | lalaji | - | amam co. | 479 | 3 | 100.00 | - |
| 10004 | DBMS | 5 | prog | lalal | - | kallo | 888 | 3 | 499.00 | - |
| 10005 | computer network | 5 | Networking | Mirabai | - | kabirdas | 789 | 2 | 800.00 | - |
+----------+------------------+-------+-------------+---------+---------+-------------+---------+--------+---------+---------+
desc tbl_books;
+--------+------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra |
+--------+------------+------+-----+---------+-------+ | accid | int(5) | NO | PRI | 0 | | | accno | int(3) | NO | PRI | 0 | | | status | varchar(1) | YES | | A | |
+--------+------------+------+-----+---------+-------+
desc tbl_book_info;
+----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+----------------+
| b_acc_id | int(5) | NO | PRI | NULL | auto_increment |
| b_name | varchar(50) | NO | | NULL | |
| b_qty | int(2) | NO | | NULL | |
| b_type | varchar(30) | NO | | NULL | |
| b_auth1 | varchar(50) | NO | | NULL | |
| b_auth2 | varchar(50) | YES | | NULL | |
| b_pub | varchar(50) | NO | | NULL | |
| b_pages | int(4) | NO | | NULL | |
| b_rack | int(5) | NO | | NULL | |
| b_price | decimal(6,2) | NO | | NULL | |
| b_about | text | YES | | NULL | |
+----------+--------------+------+-----+---------+----------------+
Here tbl_books.accid REFERS tbl_book_info.b_acc_id
My query is:
select b_name, b_qty, b_acc_id , (count(*)) Issued, (count(*)) Available
From tbl_book_info
where tbl_book_info.b_acc_id in (select accid from tbl_books
where status = 'I'
GROUP BY status) ;
I want to perform join between the tables 'tbl_book_info' and 'tbl_books' to print the Result as
Desired Output:
+--------+-------+----------+--------+-----------+
| b_name | b_qty | b_acc_id | Issued | Available |
+--------+-------+----------+--------+-----------+
| Java | 3 | 10001 | 2 | 1 |
+--------+-------+----------+--------+-----------+
| Cpp | 5 | 10002 | 3 | 2 |
+--------+-------+----------+--------+-----------+
...(and more)
Output Came:
+--------+-------+----------+--------+-----------+
| b_name | b_qty | b_acc_id | Issued | Available |
+--------+-------+----------+--------+-----------+
| Java | 3 | 10001 | 4 | 4 |
+--------+-------+----------+--------+-----------+
If you want to do a Join, then you should do it, not doing a strange subquery in a where. You can do a Join and a conditional case to sum in two different columns the Issued and the Avalilables
Select a.b_name, a.b_qty, a.b_acc_id , sum(case
when b.status='I' then 1
else 0
end) as Issued,
sum(case
when b.status='A' then 1
else 0
end) as Available
From tbl_book_info a left join tbl_books b on a.b_acc_id=b.accid
group by a.b_name, a.b_qty, a.b_acc_id
In MySQL, you can take a short-cut for this type of query:
select bi.b_name, bi.b_qty, bi.b_acc_id,
sum( b.status = 'I' ) as Issued,
sum( b.status = 'A' ) as Available
from tbl_book_info bi left join
tbl_books b
on bi.b_acc_id = b.accid
group by bi.b_name, bi.b_qty, bi.b_acc_id;
MySQL treats boolean expressions as integers in a numeric context (such as SUM()). Also note that the table aliases are abbreviations for the table name.
With an index on books(acc_id, status), this might be faster using a subqueries:
select bi.b_name, bi.b_qty, bi.b_acc_id,
(select count(*)
from tbl_books b
where b.accid = bi.b_acc_id and
b.status = 'I'
) as Issued,
(select count(*)
from tbl_books b
where b.accid = bi.b_acc_id and
b.status = 'A'
) as Available
from tbl_book_info bi ;
The performance gain is by avoiding the work for the outer group by (typically a sort). The index can be used instead.
I have not tried this solution yet, but I can say that you need to do Group By accid column of tbl_books table. So please make following change in your query:
select b_name, b_qty, b_acc_id , (count(*)) Issued, (count(*)) Available
From tbl_book_info
where tbl_book_info.b_acc_id in (select accid from tbl_books
where status = 'I'
GROUP BY accid) ;

MySQL query GROUP BY WHERE IN

I have a table like:
+------+--------+-----------+
| | name | type_id |
+------+--------+-----------+
| 1 | Bob | 3 |
| 2 | Tony | 2 |
| 3 | Sheila | 2 |
| 4 | Sarah | 8 |
| 5 | Tom | 7 |
+------+--------+-----------+
and I want to group my type_id into a new column, called 'type' . type_id 2 and 8 would have the value 'yes', everything else 'no' so my results would look something like:
+------+--------+-----------+------+
| | name | type_id | type |
+------+--------+-----------+------+
| 1 | Bob | 3 | no |
| 2 | Tony | 2 | yes |
| 3 | Sheila | 2 | yes |
| 4 | Sarah | 8 | yes |
| 5 | Tom | 7 | no |
+------+--------+-----------+------+
Is this even possible, if so whats it called, as I've searched the docs on 'GROUP BY' and 'JOINS' , but couldn't see a solution like this.
You can do that with a case:
SELECT *,CASE WHEN type_id IN (2,8) THEN 'yes' ELSE 'no' END as `type`
FROM yourTable
sqlfiddle demo

Mysql query to convert table from long format to wide format

I have a table called ContactAttrbiutes which contains a list of each contacts' attributes. The kind of data stored for these contacts include: Title, Forename, Surname telephone number etc.
Current Table
+-------------+-----------+------------------------------+
| attributeId | ContactId | AttributeValue |
+-------------+-----------+------------------------------+
| 1 | 5 | Lady |
| 2 | 5 | Elizabeth |
| 3 | 5 | E |
| 4 | 5 | Anson |
| 5 | 5 | |
| 6 | 5 | |
| 7 | 5 | |
| 8 | 5 | |
| 10 | 5 | 0207 72776 |
| 11 | 5 | |
| 12 | 5 | 0207 22996 |
| 13 | 5 | 0207 72761 |
| 14 | 5 | |
| 15 | 5 | |
| 60 | 5 | Lloyds |
| 61 | 5 | |
| 1 | 10 | Mr |
| 2 | 10 | John |
| 3 | 10 | J C |
| 4 | 10 | Beveridge |
| 5 | 10 | Esq QC |
| 6 | 10 | Retired |
| 7 | 10 | |
| 8 | 10 | |
| 10 | 10 | 0207 930 |
| 11 | 10 | |
| 12 | 10 | |
| 13 | 10 | 0207 930 |
| 14 | 10 | |
| 15 | 10 | |
| 60 | 10 | |
| 61 | 10 | |
+-------------+-----------+------------------------------+
However I would like to run a query to create a table that looks like...
New Table
+-----------+----------------------+-------------------------+-----------------------+------------------------+
| ContactId | AttributeValue_Title | AttributeValue_ForeName |AttributeValue_Initial | AttributeValue_Surname |
+-----------+----------------------+-------------------------+-----------------------+------------------------+
| 5 | Lady | Elizabeth | E | Anson |
+-----------+----------------------+-------------------------+-----------------------+------------------------+
| 10 | Mr | John | J C | Beveridge |
+-----------+----------------------+-------------------------+-----------------------+------------------------+
I am sure there is a very simple answer but I have spent hours looking. Can anyone help?
The above is only a small extract of my table, I have 750,000 contacts. In addition I would like the final table to have more columns than I have described above but they will come from different Attributes with the existing table.
Thank you very much in advance.
try this
SELECT ContactId ,
max(CASE when attributeId = 1 then AttributeValue end) as AttributeValue_Title ,
max(CASE when attributeId = 2 then AttributeValue end )as AttributeValue_ForeName ,
max(CASE when attributeId = 3 then AttributeValue end )as AttributeValue_Initial ,
max(CASE when attributeId = 4 then AttributeValue end) as AttributeValue_Surname
from Table1
group by ContactId
DEMO HERE
if you want to make your result more longer for other attributeId then just add a case statment as in the code.
SELECT
t_title.AttributeValue AS title,
t_name.AttributeValue AS name,
...
FROM the_table AS t_title
JOIN the_table AS t_firstname USING(contact_id)
JOIN ...
WHERE
t_title.attributeId = 1 AND
t_firstname.attributeId = 2 AND
...
EAV "model" is an antipattern in most cases. Are you really going to have a variable number of attributes? If yes, then no-SQL solution might be more appropriate than a relational database.

Query by Value in Adjacent Column in MySQL

I believe all questions of this nature start out this way...
I have two tables: fields and data. Fields describes the column names of a (non-existant) table and data contains the data of that (non-existant) table. Like so...
Fields:
+----+---------+-------------+---------------+-------+----------+
| ID | F_ORDER | NAME | LABEL | VALUE | TYPE |
+----+---------+-------------+---------------+-------+----------+
| 1 | 1 | IS_EMPLOYEE | Region | | checkbox |
| 2 | 3 | EM_AVATAR | Avatar | | avatar |
| 3 | 4 | EM_JOBTITLE | Job Title | | text |
| 4 | 5 | EM_COMPANY | Company | | text |
| 5 | 6 | EM_PHONE | Phone | | text |
| 6 | 2 | EM_ORDER | Display Order | 5 | text |
+----+---------+-------------+---------------+-------+----------+
Data:
+-----+----------+---------+--------------------------------------+
| ID | FIELD_ID | USER_ID | VALUE |
+-----+----------+---------+--------------------------------------+
| 5 | 1 | 1 | YES |
| 6 | 2 | 1 | |
| 7 | 3 | 1 | Owner |
| 8 | 4 | 1 | Acme, Inc. |
| 9 | 5 | 1 | 123-456-7987 |
| 150 | 5 | 31 | 123-623-5555 |
| 149 | 4 | 31 | Acme, Inc. |
| 148 | 3 | 31 | Sales and Customer Support |
| 147 | 2 | 31 | |
| 146 | 1 | 31 | YES |
| 26 | 1 | 6 | NO |
| 27 | 2 | 6 | http://example.com/avi/avi.jpeg |
| 28 | 3 | 6 | CEO |
| 29 | 4 | 6 | Acme |
| 30 | 5 | 6 | (123) 734-5555 |
| 31 | 1 | 7 | NO |
| 32 | 2 | 7 | http://example.com/avi/avi.jpeg |
| 33 | 3 | 7 | VP, Services |
| 34 | 4 | 7 | Acme |
| 35 | 5 | 7 | (913) 963-5555 |
| 36 | 1 | 14 | NO |
| 37 | 2 | 14 | http://example.com/avi/avi.jpeg |
| 38 | 3 | 14 | Senior Accountant |
| 39 | 4 | 14 | Acme |
| 40 | 5 | 14 | (123) 213-5555 |
| 41 | 1 | 10 | NO |
| 42 | 2 | 10 | http://example.com/avi/avi.jpeg |
| 43 | 3 | 10 | President |
| 44 | 4 | 10 | Acme |
| 45 | 5 | 10 | (123) 734-5555 |
| 46 | 1 | 12 | NO |
| 47 | 2 | 12 | http://example.com/avi/avi.jpeg |
| 48 | 3 | 12 | Services Supervisor |
| 49 | 4 | 12 | Acme |
| 50 | 5 | 12 | (123) 573-5555 |
| 51 | 1 | 11 | NO |
| 52 | 2 | 11 | http://example.com/avi/avi.jpeg |
| 53 | 3 | 11 | Operations Supervisor |
| 54 | 4 | 11 | Acme |
| 55 | 5 | 11 | (123) 259-5555 |
| 56 | 1 | 8 | NO |
| 57 | 2 | 8 | http://example.com/avi/avi.jpeg |
| 58 | 3 | 8 | General Information |
| 59 | 4 | 8 | Acme |
| 60 | 5 | 8 | (123) 213-5555 |
| 61 | 1 | 9 | NO |
| 62 | 2 | 9 | http://example.com/avi/avi.jpeg |
| 63 | 3 | 9 | VP, Sales |
| 64 | 4 | 9 | Acme |
| 65 | 5 | 9 | (123) 210-5555 |
+-----+----------+---------+--------------------------------------+
The basic verbiage of the query I'm looking for is: I want all information for all people who are employees (IS_EMPLOYEE = "YES") and ordered by their display order column (EM_ORDER).
My query so far gets me nowhere. I get results a little like this:
+--------+------------+------------+-------+---------+-------+
ID FIELD_ID NAME LABEL TYPE VALUE
+--------+------------+------------+-------+---------+-------+
7 1 IS_EMPLOYEE Region checkbox YES
+--------+------------+------------+-------+---------+-------+
What I need are results like this:
+-------+------------+---------+-----------+----------+-------------+--------+
USER_ID IS_EMPLOYEE EM_AVATAR EM_JOBTITLE EM_COMPANY EM_PHONE EM_ORDER
+-------+------------+---------+-----------+----------+-------------+--------+
6 YES http:// CEO Acme 123-123-555 5
+-------+------------+---------+-----------+----------+-------------+--------+
And of course I'm trying to get it all back into PHP as a usable array ($results['user_id']['jobtitle'] etc.). I could just get everything and work through it with PHP, but I'm trying to learn MySQL and I think this is a faster method than doing several foreach blocks ... although I could be wrong.
Thanks in advance for any help.
Your data table is organized in the Entity-Attribute-Value manner, which is not a valid way of storing relational data. It's no wonder that it's awkward to retrieve it with SQL into a conventional row with one column per attribute.
To do this with SQL, you basically need to do a pivot query. This is hard to write and terribly inefficient to execute.
You're better off fetching all the data for employees:
SELECT e.*, f.label
FROM Data AS is_employee
JOIN Data AS e USING (user_id)
JOIN Fields AS f ON e.field_id = f.id
WHERE (is_employee.field_id, is_employee.value) = (1, 'Yes');
And then reorganizing it into a collection of associative arrays in PHP code:
while ($row = $stmt->fetch()) {
$data[ $row['user_id'] ][ $row['label'] ] = $row['value'];
}
This is basically a PIVOT but MySQL does not have a PIVOT function so you can replicate it using an aggregate function and a CASE statement.
A Static version is when you know all of the values that you want to transform into columns (these are your field names):
select
d.user_id,
max(case when f.name = 'IS_EMPLOYEE' then d.value else null end) IS_EMPLOYEE,
max(case when f.name = 'EM_AVATAR' then d.value else null end) EM_AVATAR,
max(case when f.name = 'EM_JOBTITLE' then d.value else null end) EM_JOBTITLE,
max(case when f.name = 'EM_COMPANY' then d.value else null end) EM_COMPANY,
max(case when f.name = 'EM_PHONE' then d.value else null end) EM_PHONE,
max(case when f.name = 'EM_ORDER' then d.value else null end) EM_ORDER
from data d
left join fields f
on f.id = d.FIELD_ID
group by d.user_id
See SQL Fiddle with Demo
Result:
| USER_ID | IS_EMPLOYEE | EM_AVATAR | EM_JOBTITLE | EM_COMPANY | EM_PHONE | EM_ORDER |
---------------------------------------------------------------------------------------------------------------------------------
| 1 | YES | (null) | Owner | Acme, Inc. | 123-456-7987 | (null) |
| 6 | NO | http://example.com/avi/avi.jpeg | CEO | Acme | (123) 734-5555 | (null) |
| 7 | NO | http://example.com/avi/avi.jpeg | VP, Services | Acme | (913) 963-5555 | (null) |
| 8 | NO | http://example.com/avi/avi.jpeg | General Information | Acme | (123) 213-5555 | (null) |
| 9 | NO | http://example.com/avi/avi.jpeg | VP, Sales | Acme | (123) 210-5555 | (null) |
| 10 | NO | http://example.com/avi/avi.jpeg | President | Acme | (123) 734-5555 | (null) |
| 11 | NO | http://example.com/avi/avi.jpeg | Operations Supervisor | Acme | (123) 259-5555 | (null) |
| 12 | NO | http://example.com/avi/avi.jpeg | Services Supervisor | Acme | (123) 573-5555 | (null) |
| 14 | NO | http://example.com/avi/avi.jpeg | Senior Accountant | Acme | (123) 213-5555 | (null) |
| 31 | YES | (null) | Sales and Customer Support | Acme, Inc. | 123-623-5555 | (null) |
If you have an unknown number of values to turn into columns, then you can use a prepared statement to generate the sql dynamically.
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'max(case when f.name = ''',
name,
''' then d.value end) AS ',
name
)
) INTO #sql
FROM fields;
SET #sql = CONCAT('SELECT d.user_id, ', #sql, '
from data d
left join fields f
on f.id = d.FIELD_ID
GROUP BY d.user_id');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
See SQL Fiddle with Demo
Both will produce the same result.
You can then add a WHERE clause to filter out any of the unneeded rows.