What query to obtain the following on MySQL? - mysql

Users
+--------+--------------+----------+--------------+
| userID | userUsername | userName | userLastName |
+--------+--------------+----------+--------------+
| 6 | richard | Ricardo | Vega |
| 10 | jason | Jason | Bourne |
+--------+--------------+----------+--------------+
Restocks
+-----------+-------------+--------+--------+-----------------+
| restockID | restockDate | itemID | userID | restockQuantity |
+-----------+-------------+--------+--------+-----------------+
| 1 | 2012-02-29 | 1 | 6 | 48 |
| 2 | 2012-02-29 | 1 | 10 | 100 |
| 3 | 2012-02-29 | 2 | 10 | 50 |
| 4 | 2012-02-29 | 2 | 6 | 100 |
| 5 | 2012-02-29 | 2 | 6 | 200 |
| 6 | 2012-02-29 | 2 | 10 | 2000 |
| 7 | 2012-02-29 | 1 | 10 | 2000 |
+-----------+-------------+--------+--------+-----------------+
Items
+--------+--------------------+
| itemID | itemName |
+--------+--------------------+
| 1 | Coca Cola (lata) |
| 2 | Cerveza Sol (lata) |
+--------+--------------------+
Ok guys, i have supplied some sample data as requested. I need to get this table:
+--------+--------------------+---------------+-------------+----------+--------------+--------------+
| itemID | itemName | itemExistence | restockDate | userName | userLastName | userUsername |
+--------+--------------------+---------------+-------------+----------+--------------+--------------+
| 2 | Cerveza Sol (lata) | 2350 | 2012-02-29 | Jason | Bourne | jason |
| 1 | Coca Cola (lata) | 2148 | 2012-02-29 | Ricardo | Vega | richard |
+--------+--------------------+---------------+-------------+----------+--------------+--------------+
But, i need restockDate to be THE LATEST ONE for each itemName. In the example, it shows the first restock and not the latest one. I just need to show what's the existence for the item and when was restocked for last time, not first time.
If my tables are not good or so, please suggest a new schema.
I know maybe this is a lot so i will tip 5 USD (Paypal) to the one how can help me with this. Promise.

As discussed in comments, many restocks can be performed on the same day so it is not possible to compare dates in this case. Two options are presented here: Use the incremental PK from restocks table or restructure the table. For the first case, this is the solution:
select i.itemID, i.itemName, i.itemExistence, r.restockDate, u.userName,
u.userLastName, u.userUsername
from items i
left join (
select r1.restockDate, r1.itemID, r1.userID from restocks r1
left join restocks r2
on r1.itemId = r2.itemId and r1.restockId < r2.restockId
where r2.restockDate is null
) as r on i.itemID = r.itemID
inner join users u on r.userID = u.userID
For the second case, the restructre would imply changing the date field to a unique datetime that would uniquely identify a record. That is the best solution, however, it does require to also update any previous data present in the table. That means, to update all the records that have the same date for a single product restock and set different date times to them.
The lazy one (like me), would go for the first option :) Let me know if you have any doubt about this.

first get the distinct from items table and then use it to join others
SELECT items.*, restocks.restockDate, users.userName, users.userLastName, users.userUsername
FROM (SELECT DISTINCT items.itemID, items.itemName, items.itemExistence FROM items) AS items
LEFT JOIN restocks on items.itemID = restocks.itemID
LEFT JOIN users on restocks.userID = users.userID
GROUP BY items.itemName
Not Tested

UPDATED
select items.itemID, items.itemName, items.itemExistence, restocks.restockDate, users.userName, users.userLastName, users.userUsername
from items
inner join restocks on items.itemID = restocks.itemID
inner join users on restocks.userID = users.userID
GROUP BY items.itemName

select
items.itemID, items.itemName, items.itemExistence,
(select A.restockDate from restocks A where A.itemId = items.itemID limit 0, 1),
(select B.userID from restocks B where B.itemId = items.itemID limit 0, 1),
users.userName, users.userLastName, users.userUsername
from items
left join users on B.userID = users.userID
Please try this.

You don't mention what itemExistence is, so I'm hoping it's a column in the Items table.
Here's an easy way to do it with a self-join:
SELECT i.itemID, i.itemName, i.itemExistence, r1.restockDate,
u.userName, u.userLastName, u.userUsername
FROM Items i
JOIN Restocks r1
ON r1.itemID = i.itemID
JOIN Users u
ON u.userID = r1.userID
LEFT JOIN Restocks r2
ON r2.itemID = i.itemID
AND r2.restockDate > r1.restockDate
WHERE r2.itemID IS NULL
The LEFT JOIN with the WHERE clause ensures that we only pull the row with the latest restockDate.
The advantage of this approach is that it avoids subqueries, which often negate the use of indexes.
You can get duplicate records for a particular item if it was restocked more than once on the same date.

Related

Join Table B to Table A only if entry in Table B equals entry in Table C

I have 3 tables. clients, sales and potential_sales.
The basic structure is as follows:
Clients Table:
+-----------+-------+----------------+
| client_id | name | address |
+-----------+-------+----------------+
| 1 | john | 12 blue ave |
| 2 | paul | 34 green lane |
| 3 | peter | 69 yellow road |
+-----------+-------+----------------+
Potential Sales Table:
+----------+------------+---------------------+
|product_id | client_id | received_free_promo |
+-----------+------------+---------------------+
| 3 | 1 | 1 |
| 4 | 2 | 0 |
| 5 | 2 | 1 |
+-----------+------------+---------------------+
Sales:
+----------+-----------+-----------+
| sales_id | client_id | product_id |
+----------+-----------+------------+
| 1 | 2 | 4 |
| 2 | 43 | 4 |
| 3 | 2 | 5 |
| 4 | 18 | 93 |
+----------+-----------+------------+
I want to join clients and potential_sales tables ONLY IF
1) received_promo equals 1 AND
2) they actually bought the promo package (i.e. the product_id for the potential sale has an entry into the sales table ). If they didn't eventually buy the free_promo product then I do not want to join the clients and potential_sales table at all. This is important - I can't simply JOIN to figure it out because this is only a small part of a bigger query and I can't afford to JOIN for no reason.
(Here is how I would like it to work. It's mainly pseudo-code to describe what I want to happen)
SELECT
c.*
FROM
clients c
LEFT JOIN potential_sales ps ON ps.client_id=c.id
LEFT JOIN sales ps ON s.product_id=ps.product_id
IF(s.sales_id) JOIN potential_sales ps ON ps.client_id=c.id
How do I do this in MySQL? I haven't come close to a solution. Please help!
Try this:
SELECT A.*, B.product_id, B.received_free_promo
FROM Clients A JOIN
(SELECT * FROM PotentialSales
WHERE received_free_promo=1) B
ON A.client_id=B.client_id
WHERE EXISTS (SELECT 1 FROM Sales C
WHERE A.client_id=C.client_id
AND B.product_id=C.product_id);
See Demo on SQL Fiddle.
What you are missing is the EXISTS clause:
SELECT
C.*,
P.*
FROM
Clients AS C
INNER JOIN PotentialSales AS P ON C.client_id = P.client_id
WHERE
P.received_free_promo = 1 AND
EXISTS (
SELECT
'the client already sold that product'
FROM
Sales AS S
WHERE
S.client_id = C.client_id AND
S.product_id = P.product_id)
Try this..." select * from client as c natural join potential as p join sales as s on p.product_id = s.product_id where received_promo = 1". select * will mention everything from all the 3 tables. You can choose what you want as the result.

Querying MySQL tables for a item a user hasnt 'scored' yet

Tables
__________________ ________________________________
|______name________| |____________scores______________|
|___id___|__name___| |_id_|_user-id_|_name-id_|_score_|
| 1 | bob | | 1 | 3 | 1 | 5 |
| 2 | susan | | 2 | 1 | 3 | 4 |
| 3 | geoff | | 3 | 3 | 2 | 3 |
| 4 | larry | | 4 | 2 | 4 | 5 |
| 5 | peter | | 5 | 1 | 1 | 0 |
-------------------- ----------------------------------
Im looking to write a query that returns a RANDOM name from the 'name' table, that the user hasnt scored so far.
So given user '1' for example, it could return 'susan, larry or peter' as user '1' hasnt given them a score yet.
SELECT *
FROM names
LEFT JOIN
votes
ON names.id = votes.name_id
WHERE votes.user_id = 1
AND (votes.score IS NULL);
So far I have this, but it doesnt seem to be working as I would like
(atm it doesnt return a random, but all, but this is wrong)
Any help would be appreciated.
If you are filtering on some field of outer joined table type of join is automatically changed to inner. In your case it's condition
votes.user_id = 1
So you need to move that condition from WHERE to ON
SELECT *
FROM names
LEFT JOIN
votes
ON names.id = votes.name_id and votes.user_id = 1
WHERE (votes.score IS NULL);
Consider moving the condition from WHERE to JOIN ON clause since you are performing an OUTER JOIN else the effect would be same as INNER JOIN
LEFT JOIN votes
ON names.id = votes.name_id
AND votes.user_id = 1
WHERE votes.score IS NULL
ORDER BY RAND();
You could apply :
SELECT name FROM name join scores on name.id=scores.user_id WHERE scores.score=0
You can perform this as a sub-query
SELECT *
FROM names
WHERE id NOT IN (SELECT name_id FROM votes WHERE user_id=1)
ORDER BY RAND()
LIMIT 1

Mysql - Multiple groups query

I need a little help here
It is very simple problem but I don't know why I can't get what I want
Database
store
id | value
5 | s
favorite
id | store_id | value
1 | 5 | f
2 | 5 | f
3 | 5 | f
party
id |store_id | value
1 | 5 | p
2 | 5 | p
my query :
SELECT COUNT(p.`id`) AS parties, COUNT(f.`id`) AS favorites,s.* FROM store s
LEFT JOIN party p ON p.`store_id` = s.`id`
LEFT JOIN favorite f ON f.`store_id` = s.`id`
GROUP BY s.`id`
result
parties | favorites | id | value
6 | 6 | 5 | s
this is the result I want
parties | favorites | id | value
2 | 3 | 5 | s
Can anyone help me?, I already tried all approach I can think of
You need to count DISTINCT id's if you want a separate count of parties/favorites per store, that will eliminate the duplicates you're getting due to the JOIN;
SELECT COUNT(DISTINCT p.`id`) AS parties,
COUNT(DISTINCT f.`id`) AS favorites,
s.*
FROM store s
LEFT JOIN party p ON p.`store_id` = s.`id`
LEFT JOIN favorite f ON f.`store_id` = s.`id`
GROUP BY s.`id`;

Select a MYSQL result where tables one and two match and two and three do not match

I have a two tables with users in an old format and a new format. I want to match the users with the old format to a separate table, then exclude all users who also show up in the new user format table. My data is like this:
Table newUsers:
+----+-------+-------+----------+
| id | oldid | first | last |
+----+-------+-------+----------+
| 1 | 10 | John | Kennedy |
| 2 | 66 | Mitch | Kupchak |
+----+-------+-------+----------+
Table posts:
+----+---------+
| id | user_id |
+----+---------+
| 1 | 10 |
| 1 | 66 |
| 1 | 88 |
| 2 | 88 |
| 2 | 28 |
| 3 | 10 |
+----+---------+
Table oldUsers:
+----+----------+-------+----------+
| id | username | first | last |
+----+----------+-------+----------+
| 10 | A | John | Kennedy |
| 66 | B | Mitch | Kupchak |
| 88 | C | Dale | Earnhardt|
+----+----------+-------+----------+
Result wantend:
+----+----------+-------+----------+
| id | username | first | last |
+----+----------+-------+----------+
| 88 | C | Dale | Earnhardt|
+----+----------+-------+----------+
I want to select my result by specifying: posts.id = 1 and posts.user_id = oldUsers.id and newUsers.oldid != oldUsers.id so that I only receive oldUser.id equaling 88 because he wasn't in the newUsers list.
I have tried all kinds of JOINS and SUBQUERIES. I keep getting all of the results and not the results minus corresponding entries in the newUsers table.
select * from oldusers where id in
select * from
(select id from oldusers where id in
select distinct userid from posts where id=1)
where id not in (select oldid from newusers);
Here is a way to do it
select
o.* from oldUsers o
left join newUsers n on o.id = n.oldid
left join posts p on n.oldid = p.user_id or o.id = p.user_id
where n.id is null and p.id= 1;
For better performance add the following indexes
alter table newUsers add index oldid_idx(oldid);
alter table posts add index user_post_idx (id,user_id);
I ended up finding my answer on my own and then came here to find others tried. Abhik's code did work, but was too inefficient to use. I ended up playing with my own code and IS NULL until I found something that was much more efficient.
select o.* from posts p, oldUsers o
LEFT JOIN newUsers n ON o.id = n.oldid
WHERE p.user_id = o.id AND p.id = 1 AND n.id IS NULL
Executes in .0044 seconds. Something I can use on a production site.
With indexes added from previous answer it now executes in .001x seconds so definately going with my own code.

Fastest way to select min row with join

In this example, I have a listing of users (main_data), a pass list (pass_list) and a corresponding priority to each pass code type (pass_code). The query I am constructing is looking for a list of users and the corresponding pass code type with the lowest priority. The query below works but it just seems like there may be a faster way to construct it I am missing. SQL Fiddle: http://sqlfiddle.com/#!2/2ec8d/2/0 or see below for table details.
SELECT md.first_name, md.last_name, pl.*
FROM main_data md
JOIN pass_list pl on pl.main_data_id = md.id
AND
pl.id =
(
SELECT pl2.id
FROM pass_list pl2
JOIN pass_code pc2 on pl2.pass_code_type = pc2.type
WHERE pl2.main_data_id = md.id
ORDER BY pc2.priority
LIMIT 1
)
Results:
+------------+-----------+----+--------------+----------------+
| first_name | last_name | id | main_data_id | pass_code_type |
+------------+-----------+----+--------------+----------------+
| Bob | Smith | 1 | 1 | S |
| Mary | Vance | 8 | 2 | M |
| Margret | Cough | 5 | 3 | H |
| Mark | Johnson | 9 | 4 | H |
| Tim | Allen | 13 | 5 | M |
+------------+-----------+----+--------------+----------------+
users (main_data)
+----+------------+-----------+
| id | first_name | last_name |
+----+------------+-----------+
| 1 | Bob | Smith |
| 2 | Mary | Vance |
| 3 | Margret | Cough |
| 4 | Mark | Johnson |
| 5 | Tim | Allen |
+----+------------+-----------+
pass list (pass_list)
+----+--------------+----------------+
| id | main_data_id | pass_code_type |
+----+--------------+----------------+
| 1 | 1 | S |
| 3 | 2 | E |
| 4 | 2 | H |
| 5 | 3 | H |
| 7 | 4 | E |
| 8 | 2 | M |
| 9 | 4 | H |
| 10 | 4 | H |
| 11 | 5 | S |
| 12 | 3 | S |
| 13 | 5 | M |
| 14 | 1 | E |
+----+--------------+----------------+
Table which specifies priority (pass_code)
+----+------+----------+
| id | type | priority |
+----+------+----------+
| 1 | M | 1 |
| 2 | H | 2 |
| 3 | S | 3 |
| 4 | E | 4 |
+----+------+----------+
Due to mysql's unique extension to its GROUP BY, it's simple:
SELECT * FROM
(SELECT md.first_name, md.last_name, pl.*
FROM main_data md
JOIN pass_list pl on pl.main_data_id = md.id
ORDER BY pc2.priority) x
GROUP BY md.id
This returns only the first row encountered for each unique value of md.id, so by using an inner query to order the rows before applying the group by you get only the rows you want.
A version that will get the details as required, and should also work across different flavours of SQL
SELECT md.first_name, md.last_name, MinId, pl.main_data_id, pl.pass_code_type
FROM main_data md
INNER JOIN pass_list pl
ON md.id = pl.main_data_id
INNER JOIN pass_code pc
ON pl.pass_code_type = pc.type
INNER JOIN
(
SELECT pl.main_data_id, pl.pass_code_type, Sub0.MinPriority, MIN(pl.id) AS MinId
FROM pass_list pl
INNER JOIN pass_code pc
ON pl.pass_code_type = pc.type
INNER JOIN
(
SELECT main_data_id, MIN(priority) AS MinPriority
FROM pass_list a
INNER JOIN pass_code b
ON a.pass_code_type = b.type
GROUP BY main_data_id
) Sub0
ON pl.main_data_id = Sub0.main_data_id
AND pc.priority = Sub0.MinPriority
GROUP BY pl.main_data_id, pl.pass_code_type, Sub0.MinPriority
) Sub1
ON pl.main_data_id = Sub1.main_data_id
AND pl.id = Sub1.MinId
AND pc.priority = Sub1.MinPriority
ORDER BY pl.main_data_id
This does not rely on the flexibility of MySQLs GROUP BY functionality.
I'm not familiar with the special behavior of MySQL's group by, but my solution for these types of problems is to simply express as where there doesn't exist a row with a lower priority. This is standard SQL so should work on any DB.
select distinct u.id, u.first_name, u.last_name, pl.pass_code_type, pc.id, pc.priority
from main_data u
inner join pass_list pl on pl.main_data_id = u.id
inner join pass_code pc on pc.type = pl.pass_code_type
where not exists (select 1
from pass_list pl2
inner join pass_code pc2 on pc2.type = pl2.pass_code_type
where pl2.main_data_id = u.id and pc2.priority < pc.priority);
How well this performs is going to depend on having the proper indexes (assuming that main_data and pass_list are somewhat large). In this case indexes on the primary (should be automatically created) and foreign keys should be sufficient. There may be other queries that are faster, I would start by comparing this to your query.
Also, I had to add distinct because you have duplicate rows in pass_list (id 9 & 10), but if you ensure that duplicates can't exist (unique index on main_data_id, pass_code_type) then you will save some time by removing the distinct which forces a final sort of the result set. This savings would be more noticeable the larger the result set is.