MySQL concat columns and rows from multiple tables - mysql

I'm trying to concatenate data from three related tables according to:
orders orderrow orderrow_op
+----+ +----+----------+ +----+-------------+
| id | | id | id_order | | id | id_orderrow |
+----+ +----+----------+ +----+-------------+
| 1 | | 1 | 1 | | 1 | 1 |
| 2 | | 2 | 1 | | 2 | 1 |
| 3 | | 3 | 2 | | 3 | 2 |
+----+ | 4 | 3 | | 4 | 3 |
+----+----------+ | 5 | 3 |
| 6 | 3 |
+----+-------------+
The result i'm looking for is something like:
orderops (Desired Result)
+----------+-----------------+
| id_order | id_row:id_ops |
+----------+-----------------+
| 1 | 1:(1,2); 2:(3); |
| 2 | 3:(4,5,6) |
| 3 | 4:NULL |
+----------+-----------------+
I.e i want the operations and rows all be displayed on one row related to the order. So far i've tried things like:
SELECT
db.orders.id AS orderid,
db.orderrow.id AS rowids,
GROUP_CONCAT(DISTINCT db.orderrow.id) AS a,
GROUP_CONCAT(db.orderrow.id, ':', db.orderrow_op.id) AS b
FROM
db.orders
LEFT JOIN db.orderrow ON db.orders.id = db.orderrow.id_order
LEFT JOIN db.orderrow_op ON db.orderrow.id = db.orderrow_op.id_orderrow
GROUP BY orderid
Where in column 'a' i get the row ids and in column 'b' i get the operation_ids with corresponding row_id prepended. I'd like to combine the two into a single column such that related values in 'b' will start of with id from 'a' and only show once.
I'm fairly new to MySQL so i don't know if this is even possible or if i'ts a good idea at all? The aim is to structure the data into JSON for delivery via REST application so perhaps it's better to deliver the rows directly to the webserver and handle json parsing over there? I just figured that this approach might be faster.

This is not the nicest query but it's working for your example table setup.
SELECT
o.id AS id_order,
group_concat(sub.ops
SEPARATOR ' ') AS id_row_id_ops
FROM
(SELECT
orderrow.id_order,
IF(isnull(l3.ops), concat(orderrow.id, ':', 'NULL'), concat(orderrow.id, ':', l3.ops)) as ops
FROM
orderrow
LEFT JOIN (SELECT
orderrow_op.id_orderrow,
concat('(', group_concat(orderrow_op.id), '); ') as ops
FROM
orderrow_op
GROUP BY orderrow_op.id_orderrow) l3 ON l3.id_orderrow = orderrow.id) sub
LEFT JOIN
orders o ON o.id = sub.id_order
GROUP BY o.id;
One of the things to mind is the LEFT JOIN and that you need to cast a "null" value to a "null" text (otherwise your element 4 will vanish).
The output:

Related

MySQL getting maximum of column with fallback to second column

+------+---------+--------+---------+---------+---------+
| id | user_id | obj_id | created | applied | content |
+------+---------+--------+---------+---------+---------+
| 1 | 1 | 1 | 1 | 1 | ... |
| 2 | 1 | 2 | 1 | 1 | ... |
| 3 | 1 | 1 | 1 | 2 | ... |
| 4 | 1 | 2 | 2 | 2 | ... |
| 5 | 2 | 1 | 1 | 1 | ... |
| 6 | 2 | 2 | 1 | 1 | ... |
+------+---------+--------+---------+---------+---------+
I have a table similar to the one above. id, user_id and obj_id are foreign keys; created and applied are timestamps stored as integers. I need to get the entire row, grouped by user_id and obj_id, with the maximum value of applied. If two rows have the same applied value, I need to favour the maximum value of created. So for the above data, my desired output is:
+------+---------+--------+---------+---------+---------+
| id | user_id | obj_id | created | applied | content |
+------+---------+--------+---------+---------+---------+
| 1 | 1 | 1 | 1 | 1 | ... |
| 4 | 1 | 2 | 2 | 2 | ... |
| 5 | 2 | 1 | 1 | 1 | ... |
| 6 | 2 | 2 | 1 | 1 | ... |
+------+---------+--------+---------+---------+---------+
My current solution is to get everything ordered by applied then created:
select * from data order by applied desc created desc;
and sort things out in the code, but this table gets pretty big and I'd like an SQL solution that just gets the data I need.
select *
from my_table
where id in (
/* inner subquery b */
select max(id)
from my_table where
(user_id, obj_id, applied, created) in (
/* inner subquery A */
select user_id, obj_id, max(applied), max(created)
from my_table
group by user_id, obj_id
)
);
Then inner subquery A return the (distinct) rows having user_id, obj_id, max(applied), max(created). Using these with in clause the subquery B retrive a list of single ID each realated the a row with a proper value of user_id, obj_id, max(applied), max(created). so you have a collection of valid id for getting your result.
The main select use these ID for select the result you need.
Thanks to Mark Heintz in the comments, this answer got me to where I need to be.
SELECT
data.id,
data.user_id,
data.obj_id,
data.created,
data.applied,
data.content
FROM data
LEFT JOIN data next_max_applied ON
next_max_applied.user_id = data.user_id AND
next_max_applied.obj_id = data.obj_id AND (
next_max_applied.applied > data.applied OR (
next_max_applied.applied = data.applied AND
next_max_applied.created > data.created
)
)
WHERE next_max_applied.applied IS NULL
GROUP BY user_id, obj_id;
Go read the answer for details on how it works; the left join tries to find a more recently applied row for the same user and object. If there isn't one, it will find a row applied at the same time, but created more recently.
The above means that any row without a more recent row to replace it will have a next_max_applied.applied value of null. These rows are filtered for by the IS NULL clause.
Finally, the group by clause handles any rows that have identical user, object, applied and created columns.

Mysql group-concat is giving wrong output

My purchaseproduct table
+------------+------------+
| productids | quantities |
+------------+------------+
| 1,3,4,5 | 1,1,1,1 |
| 2,3,4,5 | 1,1,1,1 |
+------------+------------+
My product table
productsid | productsname |
+------------+-----------------------------+
| 1 | Phone |
| 2 | Laptop |
| 3 | Charger |
| 4 | Earphone |
| 5 | Camera |
I want to get product name based on productids in purchaseproduct table
Like below Out put is needed
Phone,Charger,Earphone,Camera (In row one)
Laptop,Charger,Earphone,Camera (In row two)
I tried this below statement and many other
select group_concat(p.productsname) from purchaseproducts as pp join products as p on find_in_set(p.productsid,pp.productids);
But the output I get is
Phone,Charger,Earphone,Camera,Laptop,Charger,Earphone,Camera (All in one row)
How can I achieve the output I need?
You can simply use DISTINCT inside the GROUP_CONCAT :
select pp.productsid , group_concat(DISTINCT p.productsname)
from purchaseproducts pp
join products p
on find_in_set(p.productsid,pp.productids);
GROUP BY pp.productsid

How to join tables with SQL query and take number of tied columns?

I'm having BookTable in database (with foregin hey LibID):
| BookID | BookName | BookPrice | LibID |
-------------------------------------------
| 1 | Book_1 | 200 | 1 |
| 2 | Book_2 | 100 | 1 |
| 3 | Book_3 | 300 | 2 |
| 4 | Book_4 | 150 | 4 |
and also LibraryTable:
| LibID | LibName | LibLocation |
-----------------------------------
| 1 | Lib_1 | Loc_1 |
| 2 | Lib_2 | Loc_2 |
| 3 | Lib_3 | Loc_3 |
| 4 | Lib_4 | Loc_4 |
I need to write SQL query that will return be the info about the library and number of books for that library:
| LibID | LibName | NumberOfBooks|
------------------------------------
| 1 | Lib_1 | 2 |
| 2 | Lib_2 | 1 |
| 3 | Lib_3 | 0 |
| 4 | Lib_4 | 1 |
It should be one SQL query, probably with nested queries or joins.. Not sure how the query should look like:
SELECT L.LibID AS LibID, L.LibName AS LibName, COUNT(B) AS NumberOfBooks
FROM LibraryTable L, BookTable B
WHERE L.LibID = B.LibID
Will that work?
No, this query will not work. COUNT aggregates data, so you must explicitely tell the DBMS for which group of data you want the count. In your case this is the library (you want one result record per library).
COUNT's parameter is a column, not a table, so change this to * (i.e. count records) or a certain column (e.g. LibID).
The join syntax you are using is valid, but deprecated. Use explicit joins instead. In your case an outer join would even show libraries that have no books at all, if such is possible.
select l.libid, l.libname, count(b.libid) as numberofbooks
from librarytable l
left outer join booktable b on b.libid = l.libid
group by l.libid;
You could also do all this without a join at all and get the book count in a subquery instead. Then you wouldn't have to aggregate. That's way simpler and more readable in my opinion.
select
l.libid,
l.libname,
(select count(*) booktable b where b.libid = l.libid) as numberofbooks
from librarytable l;
SELECT lt.LibID AS LibID, lt.LibName AS LibName, count(*) AS NumberOfBooks
FROM BookTable AS bt
LEFT JOIN LibraryTable AS lt ON bt.LibID = lt.LibID
GROUP BY bt.LibID

MySQL/MariaDB GROUP BY, ORDER BY returns same result twice

Assume I have the following table
+----+--------+--------+
| id | result | person |
+----+--------+--------+
| 1 | 1 | 1 |
| 2 | 2 | 2 |
| 3 | 2 | 2 |
| 4 | 4 | 3 |
| 5 | 4 | 1 |
| 6 | 1 | 2 |
+----+--------+--------+
Now I want to get the best result by each person ordered high to low, where best result means highest value of the result-column, so basically I want to GROUP BY person and ORDER BY result. Also if a person has the same result more than one time, I only want to return want one of those results. So the return I want is this:
+----+--------+--------+
| id | result | person |
+----+--------+--------+
| 4 | 4 | 3 |
| 5 | 4 | 1 |
| 2 | 2 | 2 |
+----+--------+--------+
The following query almost gets me there:
SELECT id, groupbytest.result, groupbytest.person
FROM groupbytest
JOIN (
SELECT MAX(result) as res, person
FROM groupbytest
GROUP BY person
) AS tmp
ON groupbytest.result = tmp.res
AND groupbytest.person = tmp.person
ORDER BY groupbytest.result DESC;
but returns two rows for the same person, if this person has made the same best result twice, so what I get back is
+----+--------+--------+
| id | result | person |
+----+--------+--------+
| 4 | 4 | 3 |
| 5 | 4 | 1 |
| 2 | 2 | 2 |
| 3 | 2 | 2 |
+----+--------+--------+
If two results for the same person are similar, only the one with lowest id should be returned, so instead of returning rows with ids 2 and 3, only row with id 2 should be returned.
Any ideas how to implement this?
Try this:
SELECT ttable.* from ttable
inner join
(
SELECT max(ttable.id) as maxid FROM `ttable`
inner join (SELECT max(`result`) as res, `person` FROM `ttable` group by person) t
on
ttable.result = t.res
and
ttable.person = t.person
group by ttable.person ) tt
on
ttable.id = tt.maxid
Check if tmp results in the correct resulting table. I think tmp should group correctly. The join adds new rows, because you have different values of "id".
Hence the rows with different id's will be treatet as different rows, no matter if the other columns are equal. You do not have duplicate results as long as there is no duplicate id. Try to remove the id from the SELECT. Then you should have the result you wanted, but without the id.
Example: Imagine Rooms with your id's from above. Let result be the amount of tables in the room and person the amount of people. Just because you have randomly the same amount of tables and people in room 2 and 3, it doesn't mean, that this are the same rooms.

How to determine if entries are in a list or not for all existing lists in a many to many relation

Please forgive me in case a similar question has already been asked, but I was not able to find it. The problem with searching is that expressing the question is difficult in a few words, though the setup is quite simple:
Basically, I have entries that can be assigned to lists. For that purpose I have three tables:
mysql> SELECT * FROM list;
+-----+-----------+
| lid | listname |
+-----+-----------+
| 1 | Fine List |
| 2 | Bad List |
+-----+-----------+
2 rows in set (0.00 sec)
mysql> SELECT * FROM entry;
+-----+-----------+
| eid | entryname |
+-----+-----------+
| 1 | red |
| 2 | green |
| 3 | blue |
| 4 | gray |
| 5 | black |
+-----+-----------+
5 rows in set (0.00 sec)
mysql> SELECT * FROM entry2list;
+-----+-----+
| eid | lid |
+-----+-----+
| 1 | 1 |
| 2 | 1 |
| 5 | 1 |
| 2 | 2 |
| 4 | 2 |
+-----+-----+
5 rows in set (0.00 sec)
So as a result I have 2 lists, one with 2 colors assigned, one with 3.
I can easily get a list with entries on a certain list:
SELECT * FROM list
LEFT JOIN entry2list USING (lid)
LEFT JOIN entry USING (eid)
WHERE lid=1
But I need the missing entries also, which is much more dificult:
SELECT tmp.eid, e.* FROM entry e
LEFT JOIN (
SELECT * FROM list
LEFT JOIN entry2list USING (lid)
LEFT JOIN entry USING (eid)
WHERE lid=2) AS tmp
USING (eid)
+---------+-----+-----------+
| tmp.eid | eid | entryname |
+---------+-----+-----------+
| NULL | 1 | red |
| 2 | 2 | green |
| NULL | 3 | blue |
| 2 | 4 | gray |
| NULL | 5 | black |
+---------+-----+-----------+
In my case, I need a yes/no list to see if an entry is in the list or not:
SELECT GROUP_CONCAT(IF(tmp.eid IS NULL, 'n', 'y')) AS is_set FROM entry e
LEFT JOIN (
SELECT * FROM list
LEFT JOIN entry2list USING (lid)
LEFT JOIN entry USING (eid)
WHERE lid=2) AS tmp
USING (eid)
which results in
+-----------+
| is_set |
+-----------+
| n,y,n,y,n |
+-----------+
But now I am stuck; The result is basically what I need, but the last demand on the request is that I need a similar row for every list, so the lid in the where class has be substituted somehow (or, more probable, the request has to be converted completely).
What I would like to get as a result is something like
+-----+-----------+
| lid | is_set |
+-----+-----------+
| 1 | y,y,y,n,n |
| 2 | n,y,n,y,n |
+-----+-----------+
Is that possible? How can it be done?
First, generate the rows that you need -- which are all lists and all entries. Then bring in the information about what belongs to what, and aggregate to get the strange form of output that you want.
The following produces what you want, with a separate row for each list and entry:
select l.listname, e.entryname,
(case when el.lid is null then 'n' else 'y' end) as InList
from list l cross join
entry e left join
entry2list el
on l.lid = el.lid and e.eid = el.eid;
For the format that you want, you need a group by and group concat. Something like this:
select l.lid, l.listname,
group_concat(case when el.lid is null then 'n' else 'y' end order by e.entryname) as ListFlags
from list l cross join
entry e left join
entry2list el
on l.lid = el.lid and e.eid = el.eid
group by l.lid;