I have two tables, one of items, and one of users who have flagged the items. Here is an example:
items: flags:
item_id | item_name | owner_id item_id | flagged_by
------------------------------ --------------------
1 | foo | 1 1 | 2
2 | bar | 2 2 | 4
3 | baz | 2 2 | 7
2 | 7
I want to select the information from the item table about all the items that are in the flag table, ordered by the number of flags. So for the above example, my desired output would be
item_id | item_name | owner_id
------------------------------
2 | bar | 2
1 | foo | 1
The query I have right now is select * from items where id in (select item_id from flags group by item_id order by count(*) desc);
I know that the inner query works correctly (returns all the IDs in the correct order) but when I run the overall query, I just get the items in order of item id. How do I fix my query?
You are ordering the subquery only currently, which doesn't have an effect on the order of the outer query. If you join the tables rather than using a subquery, you should be able to apply an order to the whole query:
select i.*
from items i
join flags f on i.item_id = f.item_id
group by i.item_id
order by count(f.item_id) desc
Demo: http://www.sqlfiddle.com/#!2/f141b/2
Related
I have the following MySQL DB structure:
table sales_order - id, name, ...
id | name
------------------
1 | Order Test
table sales_order_item - id, order_id, name, amount_dispatched ...
id | order_id | name | amount_dispatched
------------------------------------------
1 | 1 | Item 1 | 5
2 | 1 | Item 2 | 10
table sales_order_item_invoice - id, item_id, amount, ...
id | item_id | amount
---------------------
1 | 1 | 3
2 | 2 | 5
3 | 2 | 5
These three tables are in chain via the foreign keys. Table "invoice" can have more rows for one row in "item". Table "item" can have more rows for one row in "order".
Now, I need to create SQL query that returns all rows from table sales_order and appends there some data from the other tables - amount_dispatched and amount_invoiced:
dispatched = sum of all order's items' amount_dispatched
invoiced = sum of all invoices' amount (or 0 if no invoice exists)
Such query seems to be straightforward:
SELECT
`sales_order`.*,
SUM(`sales_order_item`.`amount_dispatched`) AS dispatched,
SUM(`sales_order_item_invoice`.`amount`) AS invoiced,
FROM `sales_order`
LEFT JOIN `sales_order_item` ON `sales_order`.`id` = `sales_order_item`.`order_id`
LEFT JOIN `sales_order_item_invoice` ON `sales_order_item`.`id` =`sales_order_item_invoice`.`item_id`
GROUP BY `sales_order`.`id`
The result contains all orders - ok
The result contains sum of invoices amount - ok
The result of "amount_dispatched" is invalid - if the item has more rows in item_invoice, the item's amount is summed several times, so for the example above, I get:
id | name | dispatched | invoiced
---------------------------------------
1 | Order Test | 25 | 13
Amount_dispatched is 25, but I would expect it to be 15.
Any idea how to correct my SQL query?
Thank you.
Firstly, use subquery do aggregation for invoice amount in sales_order_item_invoice, then left join.
SELECT
`sales_order`.*,
SUM(`sales_order_item`.`amount_dispatched`) AS dispatched,
SUM(t.`amount`) AS invoiced
FROM `sales_order`
LEFT JOIN `sales_order_item` ON `sales_order`.`id` = `sales_order_item`.`order_id`
LEFT JOIN (
SELECT item_id, SUM(amount) AS amount
FROM `sales_order_item_invoice`
GROUP BY item_id
) t ON `sales_order_item`.`id` = t.`item_id`
GROUP BY `sales_order`.`id`
I want to show first two top voted Posts then others sorted by id
This is table
+----+-------+--------------+--------+
| Id | Name | Post | Votes |
+====+=======+==============+========+
| 1 | John | John's msg | -6 |
| 2 |Joseph |Joseph's msg | 8 |
| 3 | Ivan | Ivan's msg | 3 |
| 4 |Natalie|Natalie's msg | 10 |
+----+-------+--------------+--------+
After query result should be:
+----+-------+--------------+--------+
| Id | Name | Post | Votes |
+====+=======+==============+========+
| 4 |Natalie|Natalie's msg | 10 |
| 2 |Joseph |Joseph's msg | 8 |
-----------------------------------------------
| 1 | John | John's msg | -6 |
| 3 | Ivan | Ivan's msg | 3 |
+----+-------+--------------+--------+
I have 1 solution but i feel like there is better and faster way to do it.
I run 2 queries, one to get top 2, then second to get others:
SELECT * FROM table order by Votes desc LIMIT 2
SELECT * FROM table order by Id desc
And then in PHP i make sure that i show 1st query as it is, and on displaying 2nd query i remove entry's that are in 1st query so they don't double.
Can this be done in single query to select first two top voted, then others?
You would have to use subqueries or union - meaning you have a single outer query, which contains multiple queries inside. I would simply retrieve the IDs from the first query and add a id not in (...) criterion to the where clause of the 2nd query - thus filtering out the posts retrieved in the first query:
SELECT * FROM table WHERE Id NOT IN (...) ORDER BY Id DESC
With union the query would look like as follows:
(SELECT table.*, 1 as o FROM table order by Votes desc LIMIT 2)
UNION
(SELECT table.*, 0 FROM table
WHERE Id NOT IN (SELECT Id FROM table order by Votes desc LIMIT 2))
ORDER BY o DESC, if(o=1,Votes,Id) DESC
As you can see, it wraps 3 queries into one and has a more complicated ordering as well because in union the order of the records retrieved is not guaranteed.
Two simple queries seem to be a lot more efficient to me in this particular case.
There could be different ways to write a query that returns the rows in the order you want. My solution is this:
select
table.*
from
table left join (select id from table order by votes desc limit 2) l
on table.id = l.id
order by
case when l.id is not null then votes end desc,
tp.id
the subquery will return the first two id ordered by votes desc, the join will succeed whenever the row is one of the first two otherwise l.id will be null instead.
The order by will order by number of votes desc whenever the row is the first or the second (=l.id is not null), when l.id is null it will put the rows at the bottom and order by id instead.
hi i have 2 mysql table as follow:
items
id item_name user_id
1 test1 1
2 test2 1
3 test3 1
4 test4 1
project
id user_id items
1 1 1,3
2 1 2,4
how can write a join query that can return each items in a project?
project1 =>
item1=>
[id1] =>
[name1] =>
item3=>
[id3] =>
[name3] =>
Thanks.
UPDATE
item tbl
project tbl
First of all don't store strings of delimited values in your db. You're limiting your self with the means to normally maintain and query data. Normalize your data (in this case by introducing project_items table with project_id and item_id columns). It'll pay off big time in a long run.
In the mean time you can use FIND_IN_SET() to join your tables
SELECT p.id project_id, p.user_id, i.id item_id, i.item_name
FROM project p LEFT JOIN items i
ON FIND_IN_SET(i.id, p.items) > 0
AND p.user_id = i.user_id
ORDER BY p.id, i.id
Output:
| PROJECT_ID | USER_ID | ITEM_ID | ITEM_NAME |
----------------------------------------------
| 1 | 1 | 1 | test1 |
| 1 | 1 | 3 | test3 |
| 2 | 1 | 2 | test2 |
| 2 | 1 | 4 | test4 |
Here is SQLFiddle demo
UPDATE: Values of items should not contain spaces. Either remove them or use REPLACE() like this
ON FIND_IN_SET(i.id, REPLACE(p.items, ' ', '')) > 0
Here is SQLFiddle demo
Your approach is not good. You have to create a relational table between items and projects. Including the list of values followed by commas in a record is not a good idea.
You should create an additional table relational called: project_items
and you can use the following sentence to retrieve the items from a project
select project_items.id_project, items.item_name, items.user_id
from project_items
left join items on project_items.id_item = items.id
That's a better approach
I have TableA which contains:
ID | CATEGORY | NAME
-------------------------
1 | A | NAME01
2 | B | NAME02
3 | C | NAME03
4 | D | NAME04
5 | B | NAME05
So how can I count which of the Categorys in my table I have the most?
We can see it is the Category B, but in my real table I have more than 200 records added.
In the table I have 5 different Categories.
It seems like you can just use an aggregate function (count()) and a group by:
select category, count(category) Total
from TableA
group by category
order by Total desc
See SQL Fiddle with Demo
If you then want to return only the record with the most, you can add a LIMIT to it:
select category, count(category) Total
from TableA
group by category
order by Total Desc
limit 1
See SQL Fiddle with Demo
Let's say we have this query
SELECT * FROM table
And this result from it.
id | user_id
------------
1 | 1
------------
2 | 1
------------
3 | 2
------------
4 | 1
How could I get the count of how often a user_id appears as another field (without some major SQL query)
id | user_id | count
--------------------
1 | 1 | 3
--------------------
2 | 1 | 3
--------------------
3 | 2 | 1
--------------------
4 | 1 | 3
We have this value currently in code, but we are implementing sorting to this table and I would like to be able to sort in the SQL query.
BTW if this is not possible without some major trick, we are just going to skip sorting on that field.
You'll just want to add a subquery on the end, I believe:
SELECT
t.id,
t.user_id,
(SELECT COUNT(*) FROM table WHERE user_id = t.user_id) AS `count`
FROM table t;
SELECT o.id, o.user_id, (
SELECT COUNT(id)
FROM table i
WHERE i.user_id = o.user_id
GROUP BY i.user_id
) AS `count`
FROM table o
I suspect this query as not being a performance monster but it should work.