MySQL fetch X columns for each category_id - mysql

I don't know if this is possible in mysql via ONE query .
assuming I have a table "products" that has "id","category_id","product_name","price"
Case 1 : I want to fetch 5 products from each category where price is more than 100$
Case 2: I want to fetch
1-"3 products from category 1"
2- "5 products from category 2"
3- "3 products from category 2 where the price is more than 100 and is not fetched in point 2 above"
each case in ONE query , is that possible ?
PS : The table has about 100K rows ...

I found this method : it is VERY fast and gave me exactly what i wanted :
SELECT l.*
FROM (
SELECT category,
COALESCE(
(
SELECT id
FROM writings li
WHERE li.category= dlo.category
ORDER BY
li.category, li.id
LIMIT 15, 1
), CAST(0xFFFFFFFF AS DECIMAL)) AS mid
FROM (
SELECT id as category
FROM cats dl
) dlo
) lo, writings l
WHERE l.category>= lo.category
AND l.category<= lo.category
AND l.id <= lo.mid

I think that you could do this using nested select statements.
http://dev.mysql.com/tech-resources/articles/4.1/subqueries.html
Although, I'm not sure how you would utilize them as seperate select statements after you've gotten your query. I mean, they will all be one record set.

Case 2 I think I've got...
(
select * from products where category_id = 1 limit 3
)
union
(
select * from products where category_id = 2 limit 5
)
union (
select * from products where category_id = 2 and price > 100 limit 3
)
Note that union will eliminate duplicates, union all will allow duplicates (and is therefor faster).
Have you seen?
mySQL Returning the top 5 of each category
That looks like what you're asking...

Related

How to fetch record from another table if first table return no record using UNION in MySQL query

I have two table name 'activites' & 'archived_activities'. I devide my activities table record into another table record. Activities table contain only first 200 latest activities of users and remaining record moved to archived_activities table. Now I want to join both table only when activities table return null then I want to use same offset and limit for archived_activities table to fetch next record. Below I my query that is not working fine.
SELECT * FROM activities WHERE user_id=87 LIMIT 180,20
UNION ALL
SELECT * FROM activities WHERE user_id=87 LIMIT 180,20
But this query working not fine.
Any help?
One approach here would be to do a union to get both current and archived records into one logical table, but to order them such that current records get higher priority, should they exist. I assign a position of 1 to current records and 2 to archived records. Then, I order by this position and retain 200 records.
SELECT col1, col2, ...
FROM
(
SELECT col1, col2, ..., 1 AS position
FROM activities
WHERE user_id = 87
UNION ALL
SELECT col1, col2, ..., 2
FROM archived_activities
WHERE user_id = 87
) t
ORDER BY
position
LIMIT 200;
You can use NOT EXISTS() :
SELECT * FROM activities
WHERE user_id=87
LIMIT 180,20
UNION ALL
SELECT * FROM archieve_activities
WHERE NOT EXISTS(SELECT 1 FROM activities
WHERE user_id = 87)
AND user_id=87
LIMIT 180,20
You can try this query
select * from activities as a
union all
select * from archived_activities as b
where b.user_id not in
(select r.user_id
from activities as r)
Neither of the UNION Answers does the LIMIT 180, 20 optimally for the general case.
( SELECT ... ORDER BY .. LIMIT 200 )
UNION ALL
( SELECT ... ORDER BY .. LIMIT 200 )
ORDER BY .. LIMIT 180, 20
This will get the 'right' 20 rows regardless of whether either SELECT finds less than, or more than, 200 rows.
(Note, the '200' comes from '180+20'.)

Adding one extra row to the result of MySQL select query

I have a MySQL table like this
id Name count
1 ABC 1
2 CDF 3
3 FGH 4
using simply select query I get the values as
1 ABC 1
2 CDF 3
3 FGH 4
How I can get the result like this
1 ABC 1
2 CDF 3
3 FGH 4
4 NULL 0
You can see Last row. When Records are finished an extra row in this format
last_id+1, Null ,0 should be added. You can see above. Even I have no such row in my original table. There may be N rows not fixed 3,4
The answer is very simple
select (select max(id) from mytable)+1 as id, NULL as Name, 0 as count union all select id,Name,count from mytable;
This looks a little messy but it should work.
SELECT a.id, b.name, coalesce(b.`count`) as `count`
FROM
(
SELECT 1 as ID
UNION
SELECT 2 as ID
UNION
SELECT 3 as ID
UNION
SELECT 4 as ID
) a LEFT JOIN table1 b
ON a.id = b.id
WHERE a.ID IN (1,2,3,4)
UPDATE 1
You could simply generate a table that have 1 column preferably with name (ID) that has records maybe up 10,000 or more. Then you could simply join it with your table that has the original record. For Example, assuming that you have a table named DummyRecord with 1 column and has 10,000 rows on it
SELECT a.id, b.name, coalesce(b.`count`) as `count`
FROM DummyRecord a LEFT JOIN table1 b
ON a.id = b.id
WHERE a.ID >= 1 AND
a.ID <= 4
that's it. Or if you want to have from 10 to 100, then you could use this condition
...
WHERE a.ID >= 10 AND
a.ID <= 100
To clarify this is how one can append an extra row to the result set
select * from table union select 123 as id,'abc' as name
results
id | name
------------
*** | ***
*** | ***
123 | abc
Simply use mysql ROLLUP.
SELECT * FROM your_table
GROUP BY Name WITH ROLLUP;
select
x.id,
t.name,
ifnull(t.count, 0) as count
from
(SELECT 1 AS id
-- Part of the query below, you will need to generate dynamically,
-- just as you would otherwise need to generate 'in (1,2,3,4)'
UNION ALL SELECT 2
UNION ALL SELECT 3
UNION ALL SELECT 4
UNION ALL SELECT 5
) x
LEFT JOIN YourTable t
ON t.id = x.id
If the id does not exist in the table you're selecting from, you'll need to LEFT JOIN against a list of every id you want returned - this way, it will return the null values for ones that don't exist and the true values for those that do.
I would suggest creating a numbers table that is a single-columned table filled with numbers:
CREATE TABLE `numbers` (
id int(11) unsigned NOT NULL
);
And then inserting a large amount of numbers, starting at 1 and going up to what you think the highest id you'll ever see plus a thousand or so. Maybe go from 1 to 1000000 to be on the safe side. Regardless, you just need to make sure it's more-than-high enough to cover any possible id you'll run into.
After that, your query can look like:
SELECT n.id, a.*
FROM
`numbers` n
LEFT JOIN table t
ON t.id = n.id
WHERE n.id IN (1,2,3,4);
This solution will allow for a dynamically growing list of ids without the need for a sub-query with a list of unions; though, the other solutions provided will equally work for a small known list too (and could also be dynamically generated).

How to SELECT a record per category in mysql query?

In a table of articles
title varchar(255),
category int(11),
processed enum('yes', 'no'),
... other columns
, I want to process rows (SELECT a row and then UPDATE). However, I need to do this diversely for all categories. Not processing randomly, e.g. all records for a category, but nothing for another.
Basic Case: process x rows for each category.
Advanced Case: define a daily limit for each category (in its table). This will be similar to crawlers, as we define how many pages should be crawled for a domain in a given period of time.
Example:
SELECT * from articles WHERE process='no' LIMIT 1
edit the columns in PHP
UPDATE articles .... WHERE id=xx (id comes from SELECT).
Table:
id title category process
1 title1 3 no
2 title2 3 no
3 title3 3 no
4 title4 3 no
5 title5 5 no
6 title6 5 no
7 title7 5 no
If I run the query regularly by cron, it will process all articles in category 3 then category 5. I want a query to process one from category 3, then one from category 5, and so forth. I want to process from all categories gradually.
SELECT *
FROM Table
WHERE category =
(SELECT category
FROM Table
WHERE process = 'no'
GROUP BY category
ORDER BY COUNT(category) DESC
LIMIT 1)
ORDER BY id
LIMIT 1
..will give you one row with the smallest id for the category with the most rows that haven't been processed. The subbquery returns the category with the most process='no' rows.
If you have lots more 5s than 3s, this will keep giving you 5s until there are more 3s than 5s then it will start alternating with each query (as long as you are marking the row as process = 'yes' each time).
To select $n from each category:
SET #last := NULL;
SELECT * FROM (
SELECT *,
#fetch:=IF(category=#last, #fetch-1, $n) x,
#last :=category
FROM articles
WHERE process='no'
ORDER BY category
) t WHERE t.x > 0;
To select for each category the associated number from the numbers table:
SET #last := NULL;
SELECT * FROM (
SELECT *,
#fetch:=IF(category=#last, #fetch-1, numbers.number) x,
#last :=category
FROM articles JOIN numbers USING (category)
WHERE process='no'
ORDER BY category
) t WHERE t.x > 0;
See them on sqlfiddle.
I suppose the processing that is to be done in PHP is somethign that invloves either user editing or some complex procedure (crawling) that cannot be done by SQL. In that case, you can use this query to get the columns you need from articles table.
One article per category:
SELECT
a.*
FROM
category AS c
JOIN
articles AS a
ON a.id =
( SELECT id
FROM articles AS aa
WHERE category = c.id
AND process = 'no'
ORDER BY whatever
LIMIT 1
) ;
and then update:
UPDATE
articles
SET
process = 'yes'
, other_column = ...
WHERE
id = ? --- one of the ids you have previously
--- selected and processed.
Update query of eggyal : set variable for #n
`
SET #n := 3;
SET #last := NULL;
SELECT * FROM (
SELECT *,
#fetch:=IF(category=#last, #fetch-1, #n) x,
#last :=category
FROM articles
WHERE process='no'
ORDER BY category
) t WHERE t.x > 0;
//query run
`

mysql select rows with same ids and preserve their order?

just a quick question:
i have to have one single query that has multiple rows - some rows are identicle - and the order of rows must be preserved in the result -
some idea of what im refering to:
SELECT id,date
FROM items
WHERE id IN (1,2,1,3)
ORDER BY id=1 DESC,id=2 DESC,id=1 DESC,id=3 DESC;
unfortunately mysql result is this:
1,2,3
not 1,2,1,3
it removes the duplicate which i have to have in my result to display in multiple panels on the same webpage -
i really dont want to loop thru each id one by one to get them the way i want to display -
is there a way to actually have one single query that will preserve the order and pull out rows based on request whether its unique or not -
Your query as it stands will never work, because duplicate values in a list of values of an IN clause are ignored. The only way to make this work is by using UNION ALL:
SELECT id, date FROM items where id = 1
UNION ALL
SELECT id, date FROM items where id = 2
UNION ALL
SELECT id, date FROM items where id = 1
UNION ALL
SELECT id, date FROM items where id = 3;
But to be frank, I suspect your data model so far past screwed it's unusable.
try
SELECT
id,
date
FROM items
WHERE id IN (1,2,1,3)
ORDER BY FIND_IN_SET(id, '1,2,1,3')
Another scrupulous way to answer a suspicious question:
SELECT
items.id,
items.date
FROM
items
JOIN
( SELECT 1 AS id, 1 AS ordering
UNION ALL
SELECT 2, 2
UNION ALL
SELECT 1, 3
UNION ALL
SELECT 3, 4
) AS auxilary
ON
auxilary.id = items.id
ORDER BY
auxilary.ordering
Another approach (untested, but should give you the idea):
CREATE TEMPORARY TABLE tt (id INT, ai int unsigned auto_increment primary key);
INSERT INTO tt (id) VALUES (1), (2), (1), (3);
SELECT
id,
date
FROM items JOIN tt USING (id)
ORDER BY tt.ai;
keeps the given order.
If you want to include the records with id=1 and the order doesn't matter as long as you get them, you can split your query into two queries, one for (1,2,3) union all the other query for id=1 or just do:
... In (1,2)
Union all
... In (1,3)
Example:
Select * from
(Select case id when 1 then 1 when 2 then 2 as pseudocol, othercolumns
From table where Id in (1,2)
Union all
Select case id when 1 then 3 when 3 then 4 as pseudocol, othercolumns
From table where Id in (1,3)) t order by pseudocol
Instead of doing what you are trying to, just select the unique rows you need. In the frontend code, store each unique row once in a key=>value structure, where key is the item ID and value is whatever data you need about that item.
Once you have that you can use frontend logic to output them in the desired order including duplicates. This will reduce the amount of redundant data you are trying to select.
For example This is not usable code - exact syntax required depends on your scripting language
-- setup a display order
displayOrder= [1,2,1,3];
-- select data from database, order doesn't matter here
SELECT id,date
FROM items
WHERE id IN (displayOrder);
-- cache the results in a key=> value array
arrCachedRows = {};
for (.... each db row returned ...) {
arrCachedRows[id] = date;
}
-- Now output in desired order
for (listIndex in displayOrder) {
-- Make sure the index is cached
if (listIndex exists in arrCachedRow) {
echo arrCachedRows[listIndex ];
}
}
If you must persist in using UNION despite my warnings
If you go against the above recommendation and absolutely MUST have them back in 1 query in that order then add on an additional row which will enforce the row order. See below query where I use variable #subIndex to add an incrementing value as subIndex. This in turn lets you reorder by that and it'll be in the requested order.
SELECT
i.*
FROM (
SELECT #subIndex:=#subIndex+1 AS subIndex, id, date FROM items where id = 1
UNION
SELECT #subIndex:=#subIndex+1 AS subIndex, id, date FROM items where id = 2
UNION
SELECT #subIndex:=#subIndex+1 AS subIndex, id, date FROM items where id = 1
UNION
SELECT #subIndex:=#subIndex+1 AS subIndex, id, date FROM items where id = 3
) AS i,(SELECT #subIndex:=0) v
ORDER BY i.subIndex
Or a slightly cleaner version that keeps item selection until the outside and hides the subindex
SELECT
items.*
FROM items
-- initialise variable
INNER JOIN (SELECT #subIndex:=0) v
-- create a meta-table with the ids desired in the order desired
INNER JOIN (
SELECT #subIndex:=#subIndex+1 AS subIndex, 1 AS id
UNION
SELECT #subIndex:=#subIndex+1 AS subIndex, 2 AS id
UNION
SELECT #subIndex:=#subIndex+1 AS subIndex, 1 AS id
UNION
SELECT #subIndex:=#subIndex+1 AS subIndex, 3 AS id
) AS i
ON i.id = items.id
-- order by the subindex from i
ORDER BY i.`subIndex` ASC

very difficult mysql query - random order on two tables

Consider this classical setup:
entry table:
id (int, PK)
title (varchar 255)
entry_category table:
entry_id (int)
category_id (int)
category table:
id (int, PK)
title (varchar 255)
Which basically means entries can be in one or more categories (the entry_category table is used as MM/join table)
Now I need to query 6 unique categorys along with 1 unique entries from these categories by RANDOM!
EDIT: To clarify: the purpose of this is to display 6 random categories with 1 random entry per category.
A correct result set would look like this:
category_id entry_id
10 200
20 300
30 400
40 500
50 600
60 700
This would be incorrect as there are duplicates in the category_id column:
category_id entry_id
10 300
20 300
...
And this is incorrect as there are duplicates in the member_id column:
category_id entry_id
20 300
20 400
...
How can I query this?
If I use this simple query with order by rand, the result contains duplicated rows:
select c.id, e.id
from category c
inner join entry_category ec on ec.category_id = c.id
inner join entry e on e.id = ec.entry_id
group by c.id
order by rand()
Performance is at the moment not the most important factor, but I would need a reliably working query for this, and the above is pretty much useless and does not do what I want at all.
EDIT: as an aside, the above query is no better when using select distinct ... and leaving out the group by. This includes duplicate rows as distinct only makes sure that the combinations of c.id and e.id are unique.
EDIT: one solution I found, but probably slow as hell on larger datasets:
select t1.e_id, t2.c_id
from (select e.id as e_id from entry e order by rand()) t1
inner join (select ec.entry_id as e_id, ec.category_id as c_id from entry_category ec group by e_id order by rand()) t2 on t2.e_id = t1.e_id
group by t2.c_id
order by rand()
SELECT category_id, entity_id
FROM (
SELECT category_id,
#ce :=
(
SELECT entity_id
FROM category_entity cei
WHERE cei.category_id = ced.category_id
AND NOT FIND_IN_SET(entity_id, #r)
ORDER BY
RAND()
LIMIT 1
) AS entity_id,
(
SELECT #r := CAST(CONCAT_WS(',', #r, #ce) AS CHAR)
)
FROM (
SELECT #r := ''
) vars,
(
SELECT DISTINCT category_id
FROM category_entity
ORDER BY
RAND()
LIMIT 15
) ced
) q
WHERE entity_id IS NOT NULL
LIMIT 6
This solution is not a piece of code I'd be proud of, since it relies on black magic of session variables in MySQL to keep the recursion stack. However, it works.
Also it's not perfectly random and can in fact yield less than 6 values (if entity_id's duplicate across the categories too often). In this case, you can increase the value of 15 in the innermost query.
Create a unique index or a PRIMARY KEY on category_entity (category_id, entity_id) for this to work fast.
Seems to me that the good way to do this is to pick 6 distinct values from each set, shuffle each list of values (each list individually), and then glue the lists together into a two-column result.
To randomize which six you get, shuffle the entire list of each type of value, and grab the first six.