MySQL Selecting Rows and Grouping - mysql

I have a ( Joomla) database table called field_values, the contents are below;
+----+----------+---------+---------+
| id | field_id | item_id | value |
+----+----------+---------+---------+
| 1 | 2 | 446 | Jones |
| 2 | 2 | 447 | Smith |
| 3 | 2 | 448 | Jenkins |
| 4 | 3 | 446 | Paul |
| 5 | 3 | 447 | Peter |
| 6 | 3 | 448 | Sally |
| 7 | 4 | 446 | London |
| 8 | 4 | 447 | Dublin |
| 9 | 4 | 448 | Paris |
+----+----------+---------+---------+
I'm only displaying 9 rows from the table, but I actually have thousands, so the successful query would need to take this into account.
Columns explained;
id (primary / auto-increment)
field_id (FK to another fields table, 2 = surname, 3 = first name, 4 = location)
item_id (FK to another users table)
value (contents of field)
How can I select all the values from the above table but display them as follows;
+------------+-----------+----------+
| first_name | last_name | location |
+------------+-----------+----------+
| Paul | Jones | London |
| Peter | Smith | Dublin |
| Sally | Jenkins | Paris |
+------------+-----------+----------+
The id field isn't really necessary in the desired results above, I just added it to emphasise that each row is unique.
I'm not sure if I need to use a subquery or group by, maybe neither?
Thanks in advance.

A pivot query should work here:
SELECT
MAX(CASE WHEN field_id = 3 THEN value END) AS first_name,
MAX(CASE WHEN field_id = 2 THEN value END) AS last_name,
MAX(CASE WHEN field_id = 4 THEN value END) AS location
FROM yourTable
GROUP BY
item_id
ORDER BY
item_id;
Your current table structure is a denormalized key value store, a style which WordPress uses in some of its tables.

you could avoid subquery and grou by.
You could use the same table 3 times
select a.id, b.value firts_name, a.value last_name , c.value location
from field_values a
inner join field_values b on a.item_id = b.item_id and b.field_id = 3
inner join field_values bc on a.item_id = c.item_id and b.field_id = 4
where a.item_id = 2

Related

query to get customer list using JOIN with sum () of the amounts spent in orders

I have the following tables
table anag (customer registry)
id | surname | name | phone |
----------------------------------------------
1 | Brown | Jack | +3989265781 |
2 | Smith | Bill | +3954872358 |
3 | Rogers | Stan | +3912568453 |
4 | Pickford | Eric | +3948521358 |
----------------------------------------------
table levels (table that connects each customer to his salesperson. For database registration reasons, the link between customer and seller is given by the customer's telephone number)
id | client_phone | id_seller |
--------------------------------------
1 | +3989265781 | 4 |
2 | +3954872358 | 7 |
3 | +3912568453 | 7 |
4 | +3948521358 | 8 |
--------------------------------------
table orders (contains all purchases made by customers, of course)
id | id_client | id_item | id_seller | price | status |
--------------------------------------------------------------------
1 | 1 | 2 | 4 | 12.50 | 2 |
2 | 2 | 2 | 7 | 12.50 | 2 |
3 | 2 | 3 | 7 | 10.00 | 3 |
4 | 2 | 3 | 7 | 10.00 | 3 |
5 | 2 | 4 | 7 | 20.50 | 1 |
6 | 3 | 2 | 7 | 12.50 | 1 |
7 | 3 | 5 | 7 | 19.00 | 3 |
8 | 3 | 7 | 7 | 31.00 | 2 |
9 | 4 | 1 | 8 | 5.00 | 1 |
--------------------------------------------------------------------
What I'm trying to do is get from the JOIN of these tables a complete list by seller of his customers sorted in descending order by the amount spent on orders as long as the order status is 2 or 3
Something like this (example seller id 7):
id | surname | name | amaount |
----------------------------------------
3 | Rogers | Stan | 50.00 |
2 | Smith | Bill | 32.50 |
----------------------------------------
I have tried with this query which seems correct to me, but unfortunately it returns me error in fetch_assoc()
SELECT a.id, a.surname, a.name, o.amount FROM levels AS l
JOIN anag AS a ON a.phone = l.client_phone
JOIN {
SELECT id_client, SUM(price) AS amount FROM orders
WHERE id_seller = '7' AND (status = '2' OR status = '3') GROUP BY id_client
} AS o ON o.id_client = a.id
WHERE l.id_seller = '7'
ORDER BY o.amount DESC
If I separate the subquery from the main query, both return the data I expect and it seems strange to me the JOIN between the two does not work properly
I think the only real error is using curly braces instead of parentheses:
SELECT a.id, a.surname, a.name, o.amount
FROM levels l JOIN
anag a
ON a.phone = l.client_phone JOIN
(SELECT id_client, SUM(price) AS amount
FROM orders
WHERE id_seller = '7' AND status IN ('2', '3'))
GROUP BY id_client
) o
ON o.id_client = a.id
WHERE l.id_seller = '7'
ORDER BY o.amount DESC;
In addition:
You can use IN to shorten an equality comparison to multiple values.
Although I left them in, status and id_seller look like numbers. If so, drop the single quotes. Don't mix data types.
Your question is ambiguous on what to do if the seller in orders differs from the seller in anag for a customer. This keeps your logic (the sellers need to match).
SELECT a.id, a.surname, a.name, sum(o.price) 'amount'
FROM anag a
LEFT JOIN levels l ON l.id =a.id
LEFT JOIN orders of ON o.id_seller = l.id_seller AND o.id_client = l.id
GROUP BY o.id_seller
ORDER BY amount DESC

Get values from 3 tables into a summarized table SQL

I've tried other solutions I've found here but I don't get the correct information back.
I have a table with many different records. A list of names with a status of active. Then I have another table which holds information for each name with a ticket number and then an assignment 'Assigned' and 'Feedback'. Not all names have a ticket.
Then 1 more table that holds a number of hours that goes towards that ticket number.
I want a summary of this information for each name whether there is info there or not. So I started with a subquery here is what I have.
select z.name as 'Name', round(coalesce(sum(x.Hours),0),2) as "Assigned",
round(coalesce(sum(y.Hours),0),2) as "Feedback" from
(select name from namelist where status = 'Active') as z
left join
(select e.realname as "Name", b.id as "Ticket", b.status as "Status", c.value -
COALESCE(sum(a.Hours),0) as "Hours" from user_table e
join ticket_table b ON b.handler_id = e.id
join custom_table c ON c.bug_id = b.id AND c.field_id = 7
left custom_table d ON d.bug_id = b.id AND d.field_id = 15
left hours_table a ON a.Ticket = b.id
where (b.status = 50)
Group By b.id
ORDER BY `Name` ASC, `Status` DESC) x on z.Name= x.Name
left JOIN
(select e.realname as "Name", b.id as "Ticket", b.status as "Status", c.value -
COALESCE(sum(a.Hours),0) as "Hours" from user_table e
join ticket_table b ON b.handler_id = e.id
join custom_table c ON c.bug_id = b.id AND c.field_id = 7
left custom_table d ON d.bug_id = b.id AND d.field_id = 15
left hours_table a ON a.Ticket = b.id
where (b.status = 20)
Group By b.id
ORDER BY `Name` ASC, `Status` DESC) y on z.Name= y.Name
Group by Name
I've changed some of the names around but this is the basic idea. b.status = 50 means Assigned, and 20 means Feedback. Those joins create a table that looks like this:
---------------------------------------------------------------------------------------------------
| Name | Ticket | Status| Hours ((value from custom_table)-(sum from hours table based on ticket))|
| Joe | 234 | 50 | 20 |
| Joe | 235 | 50 | 30 |
| Joe | 236 | 50 | 40 |
| John | 233 | 50 | 10 |
| John | 237 | 50 | 20 |
| John | 238 | 50 | 20 |
---------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------
| Name | Ticket | Status| Hours ((value from custom_table)-(sum from hours table based on ticket))|
| Joe | 134 | 20 | 60 |
| Joe | 135 | 20 | 30 |
| Joe | 136 | 20 | 40 |
| John | 133 | 20 | 70 |
| John | 137 | 20 | 20 |
| John | 138 | 20 | 20 |
---------------------------------------------------------------------------------------------------
-----------------
| Name | Status |
| Joe | Active|
| John | Active|
| Mary | Active|
| Tom | Active|
| John |Inactive|
-----------------
Desired result:
----------------------------
| Name | Assigned| Feedback|
| Joe | 90 | 130 |
| John | 50 | 110 |
| Mary | 0 | 0 |
| Tom | 0 | 0 |
----------------------------
Now the Hours table is c.value which is a 1 to 1 relation subtract sum(hours) from hours table 1 to many relationship.
If I take out one of the joins, the table works. When I put them together like this, the numbers are incorrect. I can get the assigned correct if I only use that join. I can get the feedback numbers correct if I only use the feeback join. However it doesn't work when trying to get either from them. Let me know if you need more info I'll try my best to provide.
Example results:
----------------------------
| Name | Assigned| Feedback|
| Joe | 392 | 145 |
| John | 125 | 94 |
| Mary | 0 | 0 |
| Tom | 0 | 0 |
----------------------------
If I just use the table with status 50.
----------------------------
| Name | Assigned|
| Joe | 90 |
| John | 50 |
| Mary | 0 |
| Tom | 0 |
----------------------------
If I just use the table with status 20.
----------------------------
| Name | Assigned|
| Joe | 130 |
| John | 110 |
| Mary | 0 |
| Tom | 0 |
----------------------------
Don't worry about the custom tables so much, there is a reason they are there but aren't a part of my question. The biggest thing is simply getting the c.value from there, the other join to that table is only for another status, but not relevant to what I'm trying to accomplish.
The two Left Joins seem identical to me apart from the status (unless I am missing something)
Have you tried using a single left join and then an aggregation with a Case statement i.e.
SELECT Name,
SUM(CASE WHEN Status = 50 THEN Hours ELSE 0 END) AS Assigned,
SUM(CASE WHEN Status = 20 THEN Hours ELSE 0 END) AS Feedback
FROM table
GROUP BY Name
I appreciate the answer here is over-simplified but it's more a suggestions since I don't know the content of all the tables mentioned in the query
SQL FIDDLE Example (apologies using SQL SERVER but logic in MySQL is the same for this simple example)
http://sqlfiddle.com/#!18/f77e4/5/0
The output matches the desired results but the issue might lie with another table.
This assumes a single table but the logic should work the same using a left join

MySQL selct entry with multiple values from same table

I have two tables (as seen bellow) and i want to join them and select the person who has multiple values form the second table. The first table is a list of all people and their unique IDs, the second table is a list of peoples favorite colors.
Table A.: Unique ID | Name
+-----+-------------+
| uid | name |
+-----+-------------+
| 321 | Ana |
| 662 | Nick |
| 003 | Fred |
+-----+-------------+
Table B.: Table ID | Unique ID | Color ID
+----------+--------+-----------+
| id | uid | color_id |
+----------+--------+-----------+
| 1 | 121 | 1 |
| 2 | 127 | 2 |
| 3 | 003 | 11 |
| 4 | 002 | 11 |
| 5 | 111 | 3 |
| 6 | 044 | 5 |
| 7 | 003 | 5 |
| 8 | 003 | 8 |
+----------+--------+-----------+
So i want to select only the users (uid) who matches all the colors given, for example 11 and 8 (red and fusia). In this case that would be user 003 | Fred, and only have 1 row per match, and not multiple (for each value).
I have tried using where color_id IN (x,y,z...) but this will return any person who has at least 1 color in the list
You can try below -
select uniqueid, name from tableA a
inner join tableB b on a.uniqueid=b.uid
where color_id in (11,8)
group by uniqueid, name
having count(distinct color_id)=2
you can do your wish by
select distinct tableA.uid, tableA.name tableB.color_id from tableA
inner join tableB on tableA.uniqueid=tableB.uid
group by tableA.uid , tableA.name ,tableB.color_id
having count(distinct color_id)=2

MySQL How to Select smth by MAX(id)....WHERE userID = some number GROUP BY smth

I have next table in my DB:
personal_prizes
___________ ___________ _________ __________
| id | userId | specId| grp |
|___________|___________|_________|__________|
| 1 | 1 | 1 | 1 |
| 2 | 1 | 2 | 1 |
| 3 | 2 | 3 | 1 |
| 4 | 2 | 4 | 2 |
| 5 | 1 | 5 | 2 |
| 6 | 1 | 6 | 2 |
| 7 | 2 | 7 | 3 |
| 8 | 1 | 13 | 4 |
|___________|___________|_________|__________|
I need to select specId by max id group by grp.
So I have composed next query
SELECT pp.specId
FROM personal_prizes pp
WHERE pp.specId IN (SELECT MAX(pp1.id)
FROM personal_prizes pp1
WHERE pp1.userId = 1
GROUP BY pp1.grp)
And it's work for my little table. But when I try to implement it for my prod db with personal_prizes > 100,000.
Please help me optimize it
The query you have should work fine. Make sure though that you not only have an index on id (which I suppose is the primary key), but also one on specId.
Just as an alternative, you might try this one:
select group_concat(pp.specId order by pp1.id desc)+0 as result_specId
from personal_prizes pp1
left join personal_prizes pp on pp.specId = pp1.id
where pp1.userId = 1
group by pp1.grp
having result_specId is not null;
The idea here is that the sub query is promoted to the main query, and the specId is retrieved by an outer join. The group_concat aggregation function will list the one of interest as the first. The having clause will exclude the cases where no matching specId was found.
Note that this will only give the same results if the specId field is guaranteed to be non-null.

Select 10 records associated with each key

Here is the case I have two tables tags and customers as the following structure
Tags Table
ID Name
1 Tag1
2 Tag2
Customers Table
ID Tag_ID Name
1 1 C1
2 2 C2
3 1 C3
I want a SQL statement to get the first 10 customers (alphabetically) for each tag? is it possible to be done in one query.
P.S the data in the tables are sample data not the actual data
Consider the following:
DROP TABLE IF EXISTS tags;
CREATE TABLE tags
(tag_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,name VARCHAR(12) NOT NULL
);
INSERT INTO tags VALUES
(1,'One'),
(2,'Two'),
(3,'Three'),
(4,'Four'),
(5,'Five'),
(6,'Six');
DROP TABLE IF EXISTS customers;
CREATE TABLE customers
(customer_id INT NOT NULL
,customer VARCHAR(12)
);
INSERT INTO customers VALUES
(1,'Dave'),
(2,'Ben'),
(3,'Charlie'),
(4,'Michael'),
(5,'Steve'),
(6,'Clive'),
(7,'Alice'),
(8,'Ken'),
(9,'Petra');
DROP TABLE IF EXISTS customer_tag;
CREATE TABLE customer_tag
(customer_id INT NOT NULL
,tag_ID INT NOT NULL
,PRIMARY KEY(customer_id,tag_id)
);
INSERT INTO customer_tag VALUES
(1,1),
(1,2),
(1,4),
(2,3),
(2,2),
(3,1),
(4,4),
(4,2),
(5,2),
(5,5),
(5,6),
(6,6);
The following query returns all customers associated with each tag, and their respective 'rank' when sorted alphabetically...
SELECT t.*, c1.*, COUNT(ct2.tag_id) rank
FROM tags t
JOIN customer_tag ct1
ON ct1.tag_id = t.tag_id
JOIN customers c1
ON c1.customer_id = ct1.customer_id
JOIN customer_tag ct2
ON ct2.tag_id = ct1.tag_id
JOIN customers c2
ON c2.customer_id = ct2.customer_id
AND c2.customer <= c1.customer
GROUP
BY t.tag_id, c1.customer_id
ORDER
BY t.tag_id,rank;
+--------+-------+-------------+----------+------+
| tag_id | name | customer_id | customer | rank |
+--------+-------+-------------+----------+------+
| 1 | One | 3 | Charlie | 1 |
| 1 | One | 1 | Dave | 2 |
| 2 | Two | 2 | Ben | 1 |
| 2 | Two | 1 | Dave | 2 |
| 2 | Two | 4 | Michael | 3 |
| 2 | Two | 5 | Steve | 4 |
| 3 | Three | 2 | Ben | 1 |
| 4 | Four | 1 | Dave | 1 |
| 4 | Four | 4 | Michael | 2 |
| 5 | Five | 5 | Steve | 1 |
| 6 | Six | 6 | Clive | 1 |
| 6 | Six | 5 | Steve | 2 |
+--------+-------+-------------+----------+------+
If we just want the top 2, say, for each tag, we can rewrite that as follows...
SELECT t.*
, c1.*
FROM tags t
JOIN customer_tag ct1
ON ct1.tag_id = t.tag_id
JOIN customers c1
ON c1.customer_id = ct1.customer_id
JOIN customer_tag ct2
ON ct2.tag_id = ct1.tag_id
JOIN customers c2
ON c2.customer_id = ct2.customer_id
AND c2.customer <= c1.customer
GROUP
BY t.tag_id, c1.customer_id
HAVING COUNT(ct2.tag_id) <=2
ORDER
BY t.tag_id, c1.customer;
+--------+-------+-------------+----------+
| tag_id | name | customer_id | customer |
+--------+-------+-------------+----------+
| 1 | One | 3 | Charlie |
| 1 | One | 1 | Dave |
| 2 | Two | 2 | Ben |
| 2 | Two | 1 | Dave |
| 3 | Three | 2 | Ben |
| 4 | Four | 1 | Dave |
| 4 | Four | 4 | Michael |
| 5 | Five | 5 | Steve |
| 6 | Six | 6 | Clive |
| 6 | Six | 5 | Steve |
+--------+-------+-------------+----------+
This is fine, but where performance is an issue, a solution like the following will be faster - although you may need to run SET NAMES utf8; prior to constructing the tables (as I had to) in order for it to work properly:
SELECT tag_id, name, customer_id,customer
FROM
(
SELECT t.*
, c.*
, CASE WHEN #prev=t.tag_id THEN #i:=#i+1 ELSE #i:=1 END rank
, #prev := t.tag_id
FROM tags t
JOIN customer_tag ct
ON ct.tag_id = t.tag_id
JOIN customers c
ON c.customer_id = ct.customer_id
JOIN ( SELECT #i:=1, #prev:=0) vars
ORDER
BY t.tag_id
, c.customer
) x
WHERE rank <=2
ORDER
BY tag_id,customer;
+--------+-------+-------------+----------+
| tag_id | name | customer_id | customer |
+--------+-------+-------------+----------+
| 1 | One | 3 | Charlie |
| 1 | One | 1 | Dave |
| 2 | Two | 2 | Ben |
| 2 | Two | 1 | Dave |
| 3 | Three | 2 | Ben |
| 4 | Four | 1 | Dave |
| 4 | Four | 4 | Michael |
| 5 | Five | 5 | Steve |
| 6 | Six | 6 | Clive |
| 6 | Six | 5 | Steve |
+--------+-------+-------------+----------+
To achieve this, we have to use two session variables, one for the row number and the other for storing the old customer ID to compare it with the current one as the following query:
select c.name, #row_number:=CASE
WHEN #cid = c.id THEN #row_number + 1
ELSE 1
END AS rows,
#id:=c.id as CustomerId from tags t, customers c where t.id=c.id group by c.name where Rows<=10
We used CASE statement in the query. If the customer number remains the same, we increase the row_number variable
Reference
Your question reminds me of this one (see especially the top-voted answer), so I came up with this query:
SELECT Tags.ID,
Tags.Name,
SUBSTRING_INDEX(GROUP_CONCAT(Customers.Name
ORDER BY Customers.Name),
',', 10) AS Customers
FROM Customers
INNER JOIN Tags
ON Tags.ID = Customers.Tag_ID
GROUP BY Tags.ID
ORDER BY Tags.Id;
It works, but this is clearly a hacky way to do this, because MySQL does not offer yet tools to do this more naturally.