MySQL merge columns from multiple tables - mysql

i'm trying to select some data in the following way:
field:
+----------+------------+-----------+
| id | room_id | server_id |
+----------+------------+-----------+
| 1 | 34 | 0 |
| 2 | 34 | 0 |
| 3 | 35 | 1 |
+----------+------------+-----------+
user_position:
+----------+------------+-----------+
| user_id | server_id | position |
+----------+------------+-----------+
| 11 | 0 | 2 |
| 17 | 1 | 25 |
| 19 | 0 | 28 |
+----------+------------+-----------+
room:
+----------+------------+-----------+
| id | server_id | background|
+----------+------------+-----------+
| 34 | 0 | #d91a1a |
| 35 | 1 | #f81b2a |
| 36 | 0 | #191b4a |
+----------+------------+-----------+
RESULT:
(I hope I didn't mess it up)
+----------+------------+-----------+------------+------------+
| id | server_id | background| room_id | user_id |
+----------+------------+-----------+------------+------------+
| 1 | 0 | #d91a1a | 34 | null |
| 2 | 0 | #d91a1a | 34 | 11 |
| 3 | 1 | #f81b2a | 35 | null |
| 25 | 1 | null | null | 17 |
| 28 | 0 | null | null | 19 |
+----------+------------+-----------+------------+------------+
Unfortunately i couldn't write the right query to achieve this result. The best I could get was that the field.id, user_position.position and field.room_id, user_position.room_id columns were separated. I have no idea how to merge them together.
Can somebody help me?
UPDATE
OK, so after some trying I got this:
SELECT field.id, field.server_id, field.room_id, null AS user_id, room.background
FROM field
LEFT JOIN room ON room.id = field.room_id
WHERE field.server_id = 0
UNION
SELECT user_position.position, user_position.server_id, null, user_position.user_id, room.background
FROM user_position
LEFT JOIN room ON room.id = (SELECT field.room_id FROM field WHERE field.id = user_position.position)
WHERE user_position.server_id = 0
Now it is working I just want to ask if there isn't a better way to achieve the same result. Or do you think this query is good enough?

With the given set of data, you may try below -
SELECT COALESCE(F.id, UP2.position) id
,COALESCE(F.server_id, UP2.server_id) server_id
,UP2.background
,F.room_id
,UP2.user_id
FROM field F
LEFT OUTER JOIN (SELECT UP.server_id, R.background, UP.position, UP.user_id
FROM (SELECT server_id, position, user_id, ROW_NUMBER()OVER(PARTITION BY server_id ORDER BY user_id) rn
FROM user_position) UP
JOIN (SELECT server_id, background, ROW_NUMBER()OVER(PARTITION BY server_id ORDER BY id) rn
FROM room) R ON UP.rn = R.rn
AND UP.server_id = R.server_id) UP2 ON F.id = UP2.position
UNION
SELECT COALESCE(F.id, UP3.position) id
,COALESCE(F.server_id, UP3.server_id) server_id
,UP3.background
,F.room_id
,UP3.user_id
FROM field F
RIGHT OUTER JOIN (SELECT UP.server_id, R.background, UP.position, UP.user_id
FROM (SELECT server_id, position, user_id, ROW_NUMBER()OVER(PARTITION BY server_id ORDER BY user_id) rn
FROM user_position) UP
JOIN (SELECT server_id, background, ROW_NUMBER()OVER(PARTITION BY server_id ORDER BY id) rn
FROM room) R ON UP.rn = R.rn
AND UP.server_id = R.server_id) UP3 ON F.id = UP3.position
ORDER BY id
Here is the demo.

Related

MySQL query with sum fields from other table, with a twist

Sorry for the vague title, but I don't know how to word this type of problem better. Here is a simple example to explain it. I have to tables: OrderItemList and OrderHistoryLog.
OrderItemList:
|------------------------------|
| OrderNo | ItemNo | Loc | Qty |
|------------------------------|
| 100 | A | 1 | 1 |
| 101 | A | 1 | 2 |
| 102 | A | 1 | 1 |
| 103 | A | 2 | 1 |
| 104 | A | 2 | 1 |
OrderHistoryLog:
|------------------------------|
| OrderNo | ItemNo | Loc | Qty |
|------------------------------|
| 50 | A | 1 | 5 |
| 51 | A | 1 | 2 |
| 100 | A | 1 | 1 |
| 102 | A | 1 | 3 |
| 103 | A | 2 | 1 |
I need to show the records in the OrderItemList along with a LocHistQty field, which is the sum(Qty) from the OrderHistoryLog table for a given Item and Location, but only for the orders that are present in the OrderItemList.
For the above example, the result should be:
Result:
|------------------------------------------------------
| OrderNo | ItemNo | Loc | Qty | HistQty | LocHistQty |
|------------------------------|-----------------------
| 100 | A | 1 | 1 | 1 | 4 |
| 101 | A | 1 | 2 | 0 | 4 |
| 102 | A | 1 | 1 | 3 | 4 |
| 103 | A | 2 | 1 | 1 | 1 |
| 104 | A | 2 | 1 | 0 | 1 |
It is the last field, LocHistQty that I could use some help with. Here is what I started with (does not work):
select OI.OrderNo, OI.ItemNo, OI.Loc, OI.Qty, IFNULL(OL.Qty, 0) as HistQty, OL2.LocHistQty
from OrderItemList OI
left join OrderItemLog OL on OL.OrderNo = OI.OrderNo and OL.ItemNo = OI.ItemNo
join
(
select ItemNo, Loc, sum(qty) as LocHistQty
from zOrderItemLog
group by ItemNo, Loc
) as OL2
on OL2.ItemNo = OI.ItemNo and OL2.Loc = OI.Loc
order by OrderNo
The issue is with the above SQL is that LocHistQty contains the summary of the Qty for all orders (=11 for Loc 1 and 1 for Loc 2), not only the ones in OrderItemList.
Lastly, the real data is voluminous and query performance is important.
Help would be much appreciated.
The subquery can join with OrderItemList to restrict the order numbers that it sums.
select OI.OrderNo, OI.ItemNo, OI.Loc, OI.Qty, IFNULL(OL.Qty, 0) as HistQty, OL2.LocHistQty
from OrderItemList OI
left join OrderItemLog OL on OL.OrderNo = OI.OrderNo and OL.ItemNo = OI.ItemNo
join
(
select OL.ItemNo, OL.Loc, sum(OL.qty) as LocHistQty
from OrderItemLog AS OL
JOIN OrderItemList AS OI ON OL.OrderNo = OI.OrderNo
group by OL.ItemNo, OL.Loc
) as OL2
on OL2.ItemNo = OI.ItemNo and OL2.Loc = OI.Loc
order by OrderNo
DEMO
Option 1
SELECT
OrderNo,
ItemNo,
Loc,
Qty,
(SELECT
Qty
FROM
OrderHistoryLog AS A
WHERE
A.OrderNo = B.OrderNo AND A.Loc = B.Loc) AS HistQty,
(SELECT
SUM(Qty)
FROM
OrderHistoryLog AS D
WHERE
D.OrderNo = B.OrderNo AND D.Loc = B.Loc) AS LocHistQty
FROM
OrderItemList AS B;
Option 2
SELECT
B.OrderNo,
B.ItemNo,
B.Loc,
B.Qty,
C.Qty AS HistQty,
(SELECT
SUM(Qty)
FROM
OrderHistoryLog AS A
WHERE
A.OrderNo = B.OrderNo AND A.Loc = B.Loc) AS LocHistQty
FROM
OrderItemList AS B,
OrderHistoryLog AS C
WHERE
C.OrderNo = B.OrderNo AND C.Loc = B.Loc;

How to combine table records with some condition in SQL table?

I have six tables in the database, all tables are relative to each other and want to show records in one table.
Following are my tables:
1) mls_stores
*----------------------------*
| store_id | store_title |
*----------------------------*
| 1001 | ajmar-jaipur |
| 1002 | dwarka-delhi |
*----------------------------*
2) mls_category
*-------------------------------------------*
| cat_no | store_id | cat_value | cat_type |
*-------------------------------------------*
| 20 | 1001 | 1 | running |
| 21 | 1001 | 4 | cycling |
| 22 | 1002 | 1 | running |
| 23 | 1002 | 2 | swmining |
*-------------------------------------------*
3) mls_points_matrix
*----------------------------------------*
| store_id | value_per_point | maxpoint |
*----------------------------------------*
| 1001 | 1 | 10 |
| 1001 | 2 | 20 |
| 1002 | 1 | 20 |
| 1002 | 4 | 30 |
*----------------------------------------*
4) mls_user
*--------------------------*
| id | store_id | name |
*--------------------------*
| 1 | 1001 | sandeep |
| 2 | 1001 | jagveer |
| 3 | 1002 | gagan |
*--------------------------*
5) bonus_points
*---------------------------------------------------*
| user_id | store_id | bonus_points | bonus_type |
*---------------------------------------------------*
| 1 | 1001 | 10 | fixed |
| 3 | 1002 | 2 | % |
*---------------------------------------------------*
6) mls_entry
*-------------------------------------------------------*
| user_id | store_id | category | distance | status |
*-------------------------------------------------------*
| 1 | 1001 | 20 | 10 | approved |
| 1 | 1001 | 21 | 40 | approved |
| 1 | 1001 | 20 | 5 | reject |
| 2 | 1001 | 21 | 40 | approved |
| 3 | 1002 | 22 | 10 | approved |
| 3 | 1002 | 23 | 20 | approved |
*-------------------------------------------------------*
Now I want output as below:
*-----------------------------------------------------------------------------------*
| Name | Entries | Points Earned | Bonus Points | Total Points | Total Amount |
*-----------------------------------------------------------------------------------*
| Sandeep | running(1) | 20 | 10 | 30 | 60 |
| | cycling(1) | | | | |
*-----------------------------------------------------------------------------------*
| Jagveer | cycling(1) | 10 | 0 | 10 | 10 |
*-----------------------------------------------------------------------------------*
I am using following code:
SELECT
u.name,
ROUND(COALESCE(t1.points, 0)) AS points,
ROUND(COALESCE(b.bonus_points, 0)) AS bonus_points,
ROUND(COALESCE(t1.points, 0) + COALESCE(b.bonus_points, 0)) AS total_points
FROM mls_user u
LEFT JOIN
(
SELECT e.user_id, e.status, SUM(e.distance / c.cat_value) AS points
FROM mls_entry e
INNER JOIN mls_category c
ON e.store_id = c.store_id AND e.category = c.cat_no
GROUP BY e.user_id
HAVING e.status='approved'
) t1
ON u.id = t1.user_id
LEFT JOIN bonus_points b
ON u.id = b.user_id
WHERE u.store_id = '1001'
ORDER BY
total_points DESC
This SQL query giving me Point earned, bonus points and total points, But I am not able to find Entries And Total Amount and it is giving me wrong Point calculation for Sandeep, As per data one entry is rejected. so it should be 20, not 25.
My total amount will be for Sandeep 30X2(it is coming from point matrix) = 60
same like for jagveer, the total amount for jagveer 10X1 = 10.
I have created tables in DEMO
Try below :
SELECT
u.name,
ROUND(COALESCE(t1.points, 0)) AS points,
ROUND(COALESCE(b.bonus_points, 0)) AS bonus_points,
ROUND(COALESCE(t1.points, 0) + COALESCE(b.bonus_points, 0)) AS total_points,
ROUND(COALESCE(t1.points, 0) + COALESCE(b.bonus_points, 0)) * t1.countId as total_amount,
group_concat(t1.EntriesConcat) as Entries
FROM mls_user u
LEFT JOIN
(
SELECT e.user_id, e.status, SUM(e.distance / c.cat_value) AS points,
concat(c.cat_type, '(',count(e.user_id), ')' ) as EntriesConcat,
count(e.user_id) as countId -- it returns count of records according to group by part
FROM mls_entry e
INNER JOIN mls_category c
ON e.store_id = c.store_id AND e.category = c.cat_no
-- remove HAVING and use WHERE clause
WHERE e.status='approved'
GROUP BY e.user_id
) t1 ON u.id = t1.user_id
LEFT JOIN bonus_points b ON u.id = b.user_id
WHERE u.store_id = '1001'
ORDER BY total_points DESC
group_concat of mysql is useful to concatinating values on Group By

How to select working time by calling time, group id?

I need to select working time - SUM(users_worktime.length) as working_time) when users was calling by the calling time and users group id. How to do that?
Here is my select:
SELECT
users_groups.name as name,
COUNT(DISTINCT calls.id) as calls,
SUM(calls.status = 'ended') as answers,
COUNT(DISTINCT orders.id) as deals,
ROUND(COUNT(DISTINCT orders.id) * 100 / SUM(calls.status = 'ended'),2) as rate,
SUM(case when calls.status = 'ended' then calls.call_length else 0 end) as talking_time,
//SUM(users_worktime.length) as working_time
FROM
users_groups
LEFT JOIN users ON users.group_id = users_groups.id
LEFT JOIN calls ON (calls.user_id = users.id AND calls.created_at >= '2015-12-30 00:00:00' AND calls.created_at <= '2015-12-31 23:59:59')
LEFT JOIN orders ON orders.id = calls.order_id AND orders.status = 'finished'
WHERE 1
AND users.group_id = 1
GROUP BY
users_groups.id
I need result like this:
| name | calls | answers | deals | rate | talking_time| working_time |
------------------------------------------------------------------------
| Group | 4 | 3 | 2 | 75 % | 180 | 355.00 |
And here are my data tables:
users_worktime:
| id | user_id | length | start |
-----------------------------------------------
| 1 | 2 | 130 | 2015-12-30 07:53:38 |
| 2 | 8 | 55 | 2015-12-30 12:53:38 |
| 3 | 8 | 170 | 2015-12-31 22:53:38 |
users:
| id | username | group_id |
----------------------------
| 2 | Thomas | 1 |
| 8 | Haroldas | 1 |
groups:
| id | name |
-------------
| 1 | Group |
calls:
| id | user_id | order_id | status | call_length | created_at |
-------------------------------------------------------------------------
| 1 | 2 | 3 | ended | 35 | 2015-12-30 07:53:38 |
| 2 | 8 | 4 | ended | 100 | 2015-12-31 12:53:38 |
| 3 | 8 | NULL | started | 15 | 2015-12-31 14:53:38 |
| 4 | 8 | NULL | ended | 45 | 2015-12-31 20:53:38 |
orders:
| id | user_id | call_id | start |
-----------------------------------------------
| 3 | 2 |1 | 2015-12-30 07:53:38 |
| 4 | 8 |2 | 2015-12-31 12:53:38 |
Thank you
EDIT:
I trying with subquery like this but is not correct because SUM is only one user of group
SELECT
users_groups.name as name,
COUNT(DISTINCT calls.id) as calls,
SUM(calls.status = 'ended') as answers,
COUNT(DISTINCT orders.id) as deals,
ROUND(COUNT(DISTINCT orders.id) * 100 / SUM(calls.status = 'ended'),2) as rate,
SUM(case when calls.status = 'ended' then calls.call_length else 0 end) as talking_time,
(SELECT SUM(users_worktime.length) FROM users_worktime WHERE users_worktime.user_id = users.id AND users_worktime.start >= '2015-12-30 00:00:00' AND users_worktime.start <= '2015-12-31 23:59:59') as working_time
FROM
users_groups
LEFT JOIN users ON users.group_id = users_groups.id
LEFT JOIN calls ON (calls.user_id = users.id AND calls.created_at >= '2015-12-30 00:00:00' AND calls.created_at <= '2015-12-31 23:59:59')
LEFT JOIN orders ON orders.id = calls.order_id AND orders.status = 'finished'
WHERE 1
AND users.group_id = 1
GROUP BY
users_groups.id

SQL Statement Construction: Selecting Unique Records

I currently have a table of Users, and at what time they connected to a device (e.g. a Wifi Router).
+-------------+-----------+---------+------------+---------------------+
| location_id | device_id | user_id | dwell_time | date |
+-------------+-----------+---------+------------+---------------------+
| 14 | 1 | 1 | 27.000000 | 2014-01-04 00:51:12 |
| 15 | 2 | 1 | 12.000000 | 2014-01-04 01:08:56 |
| 16 | 1 | 1 | 12.000000 | 2014-01-04 01:09:26 |
| 17 | 2 | 1 | 318.000000 | 2014-01-04 01:09:38 |
| 18 | 1 | 2 | 20.000000 | 2014-01-04 01:30:03 |
| 19 | 2 | 3 | 20.000000 | 2014-01-04 01:30:03 |
+-------------+-----------+---------+------------+---------------------+
I need to write a query title "Get Latest User Connections".
Basically, it needs to go through the history table shown above, and pick the latest record (based on Date) for each user and display it. In the example above, the result should be:
+-------------+-----------+---------+------------+---------------------+
| location_id | device_id | user_id | dwell_time | date |
+-------------+-----------+---------+------------+---------------------+
| 17 | 2 | 1 | 318.000000 | 2014-01-04 01:09:38 |
| 18 | 1 | 2 | 20.000000 | 2014-01-04 01:30:03 |
| 19 | 2 | 3 | 20.000000 | 2014-01-04 01:30:03 |
+-------------+-----------+---------+------------+---------------------+
Can someone please help me write a SQL statement that does this?
Assuming the combination of user_id and date is unique in the table, you could
SELECT
tablename.*
FROM tablename
INNER JOIN (
SELECT user_id, MAX(`date`) AS maxdate
FROM tablename
GROUP BY user_id
) AS selector
ON tablename.user_id=selector.user_id AND tablename.`date`=selector.maxdate
select *
from users
inner join (select user_id,max(date) as maxdate
from users
group by user_id)T1
on T1.user_id = users.user_id
AND T1.maxdate = users.date
or if you don't want to have a subquery, you can user #variables like this query below
SELECT location_id,device_id,user_id,dwell_time,date,
IF(#prevUserId IS NULL OR #prevUserId != user_id,#row:=1,#row:=#row+1) as row,
#prevUserId := user_id
FROM users
HAVING row = 1
ORDER BY user_id,date DESC
here's the sqlFiddle
Try this:
SELECT u.location_id, u.device_id, u.user_id, u.dwell_time, u.datec
FROM (SELECT u.location_id, u.device_id, u.user_id, u.dwell_time, u.date
FROM users u ORDER BY u.user_id, u.date DESC
) A
GROUP BY u.user_id
OR
SELECT u.location_id, u.device_id, u.user_id, u.dwell_time, u.date
FROM users u
INNER JOIN (SELECT u.user_id, MAX(u.date) AS latestDate
FROM users u GROUP BY u.user_id
) A ON u.user_id = A.user_id AND A.latestDate = u.date

Getting last row of each group in mysql

I have two tables as following:
messages
+----+--------+-----------------+
| id | sender | body |
+----+--------+-----------------+
| 11 | 4 | test msg one |
| 12 | 4 | test msg |
| 13 | 1 | this is test |
| 14 | 4 | WANT TO SHOW G1 |
| 15 | 4 | WANT TO SHOW G2 |
+----+--------+-----------------+
message_receivers
+----------+------------+
| receiver | message_id |
+----------+------------+
| 1 | 11 |
| 1 | 12 |
| 4 | 13 |
| 1 | 14 |
| 3 | 15 |
+----------+------------+
I have written following query
SELECT
`messages`.`id`,
`messages`.`body` ,
`messages`.`sender`,
MAX(`messages`.`id`) AS MID,
IF(`messages`.`sender`>`message_receivers`.`receiver`,`CONCAT_WS`(',',`messages`.`sender`,`message_receivers`.`receiver`),
`CONCAT_WS`(',',`message_receivers`.`receiver`,`messages`.`sender`)) AS `conc`
FROM
`messages`
JOIN `message_receivers` ON `messages`.`id` = `message_receivers`.`message_id`
WHERE
`message_receivers`.`receiver` = '4'
OR `messages`.`sender` = '4'
GROUP BY conc
which give me the following result
+----+-----------------+--------+------+------+
| id | body | sender | MID | conc |
+----+-----------------+--------+------+------+
| 11 | test msg one | 4 | 14 | 4,1 |
| 15 | WANT TO SHOW G2 | 4 | 15 | 4,3 |
+----+-----------------+--------+------+------+
But I want
+----+-----------------+--------+------+------+
| id | body | sender | MID | conc |
+----+-----------------+--------+------+------+
| 14 | WANT TO SHOW G1 | 4 | 14 | 4,1 |
| 15 | WANT TO SHOW G2 | 4 | 15 | 4,3 |
+----+-----------------+--------+------+------+
What should I do?
Thanks in advance.
Using a sub query to get the max message, and then joining that back to get the other fields:-
SELECT messages.id, messages.body, messages.sender, Sub1.MID, Sub1.conc
FROM messages
INNER JOIN message_receivers
ON messages.id = message_receivers.message_id
INNER JOIN
(
SELECT
IF(messages.sender>message_receivers.receiver,
CONCAT_WS(',',messages.sender,message_receivers.receiver),
CONCAT_WS(',',message_receivers.receiver,messages.sender)) AS conc,
MAX(messages.id) AS MID
FROM messages
JOIN message_receivers
ON messages.id = message_receivers.message_id
WHERE message_receivers.receiver = '4'
OR messages.sender = '4'
GROUP BY conc
) Sub1
ON Sub1.MID = messages.id
AND Sub1.conc = IF(messages.sender>message_receivers.receiver,
CONCAT_WS(',',messages.sender,message_receivers.receiver),
CONCAT_WS(',',message_receivers.receiver,messages.sender))
SQL fiddle for it - http://sqlfiddle.com/#!2/8ee98/2
The standard solution is to use an uncorrelated subquery, as follows:
SELECT x.*
FROM my_table
JOIN
( SELECT grouping_column
, MAX(ordering_column) max_ordering_column
FROM my_table
GROUP
BY grouping_column
) y
ON y.grouping_column = x.grouping_column
AND y.max_ordering_column = x.ordering_column;
or, where a key is formed on multiple columns...
SELECT x.*
FROM my_table
JOIN
( SELECT grouping_column1
, grouping_column2
, MAX(ordering_column) max_ordering_column
FROM my_table
GROUP
BY grouping_column1
, grouping_coulmn2
) y
ON y.grouping_column1 = x.grouping_column1
AND y.grouping_column2 = x.grouping_column2
AND y.max_ordering_column = x.ordering_column;