Unknown column in WHERE clause in subquery - mysql

I have pages table which I'm trying to truncate.
I need to leave last 10 versions of each page (page is identified by uid).
id----|----uid----|----version
1 ----|---- 1 ----|---- 1
2 ----|---- 1 ----|---- 2
3 ----|---- 1 ----|---- 3
4 ----|---- 1 ----|---- 4
5 ----|---- 2 ----|---- 1
6 ----|---- 2 ----|---- 2
...........
55 ----|---- 1 ----|---- 23
56 ----|---- 2 ----|---- 14
57 ----|---- 2 ----|---- 15
I've tried with this MySQL query:
DELETE FROM pages AS p WHERE p.id IN (
SELECT Versions.id FROM
(SELECT q.id FROM pages AS q WHERE p.uid = q.uid ORDER BY q.version DESC LIMIT 10)
AS Versions
)
but above SQL returns:
Unknown column 'p.uid' in 'where clause'
On Stackoverflow is loads of answers for similar questions like mine but as I'm not SQL ninja I wasn't able to convert any of these to fit my problem.
I don't want to overkill my query as well.
There must be some simple answer to this.
I can achieve needed truncation in MSSQL Server with query below:
DELETE FROM pages WHERE id NOT IN (
SELECT Versions.id FROM
(SELECT TOP (10) q.id FROM pages AS q WHERE pages.uid = q.uid ORDER BY q.version DESC)
AS Versions
)
Thanks

You don't need to co-ordinate the queries together here; just select a list of IDs to delete- the tables are the same
DELETE FROM pages WHERE id NOT IN
(SELECT TOP (10) id FROM pages ORDER BY q.version DESC)
You can't "order by" a delete query..
If youre after the MySQL version, and your version of MySQL doesnt support LIMIT for subqueries, and it doesn't support window functions like this:
delete from pages where id in
(select id from
(select id, row_number() over(order by dt desc) as rown from pages) a where a.rown > 10)
)
then you could consider something like:
CREATE table x as SELECT * FROM pages ORDER BY q.Version DESC LIMIT 10
TRUNCATE /*or delete from*/ pages
INSERT INTO pages SELECT * FROM x

DELETE PG FROM pages PG
LEFT JOIN
(
SELECT q.id FROM pages AS q ORDER BY q.version DESC LIMIT 10 OFFSET 0
) Versions ON Versions.ID=PG.ID
WHERE Versions.ID IS NULL
You can try above query.

DELETE p FROM pages AS p WHERE p.id IN (
SELECT Versions.id FROM
(SELECT q.id FROM pages AS q WHERE p.uid = q.uid ORDER BY q.version DESC LIMIT 10)
AS Versions
)
Check if this works..

Related

Select values on one column having an identical set of values on another column

I have a "relational table" that stores which post has which tags. Just like Stack Overflow, a post can have many tags, and a tag can have many posts.
The table only has two columns and it looks like this:
pid tid
1 3
1 4
2 1
2 3
2 4
3 1
3 3
3 4
4 1
4 3
5 1
5 3
6 2
6 4
In the above table, post 2 and 3 have an identical set of tags (values on another column tid), and so are post 4 and 5.
I want to select all posts (post IDs) where there exists another post with the same set of tid, so the query should return 2 3 4 5 from column pid.
I'm running on MariaDB 10.1.38.
Here's my own attempt but apparently it fails:
SELECT p.pid
FROM post_tags AS p
WHERE EXISTS (
SELECT *
FROM post_tags AS p2
WHERE
GROUP_CONCAT(p.tid SEPARATOR ',') = GROUP_CONCAT(p2.tid SEPARATOR ',')
GROUP BY p2.pid
)
GROUP BY p.pid;
MariaDB told me:
ERROR 1111 (HY000): Invalid use of group function
GROUP_CONCAT() is an aggregate function, so you can't apply it in the WHERE clause, as it's evaluated in the SELECT clause (this happens after WHERE).
Also note, that you should add an ORDER BY in the GROUP_CONCAT() function. There's no guaranteed order in a relational database, unless you specifically specify it.
You could do it like this:
SELECT t1.pid FROM
(
SELECT
pid, GROUP_CONCAT(tid ORDER BY tid) AS gctid
FROM t t1
GROUP BY pid
) t1
JOIN (
SELECT
pid, GROUP_CONCAT(tid ORDER BY tid) AS gctid
FROM t t1
GROUP BY pid
) t2 ON t1.pid != t2.pid AND t1.gctid = t2.gctid
see it working live in an sqlfiddle

Select the first 5 rows and get count

I'm trying to write a mysql query:
first select 5 rows and then get count with a where
first select 5 rows
table
id user_id
--------
1 1
2 2
3 3
4 1
5 1
6 4
7 3
8 1
id user_id
----------
1 1
2 2
3 3
4 1
5 1
And then get count this table where user_id =1
result = 3
You can try somthing like that
Select count(*) From
(Select * From T
order by ID asc Limit 5) as child
where user_id = 1
Looks like you want to present two different result sets together. You need to use a JOIN for this. Something like so will do the trick for you.
SELECT T.*,c.cnt
FROM T
JOIN ( SELECT COUNT(*) cnt FROM T where user_id = 1 ) c
LIMIT 5
The subquery generates your count as a one-row resultset, and the JOIN (which lacks an ON condition) puts it into every row of your other resultset.
If you wanted to show five rows from your table, and have each row mention the count for the userid in that row, you could do this.
SELECT T.*,c.cnt
FROM T
JOIN ( SELECT COUNT(*) cnt, user_id
FROM T
GROUP BY user_id
) c ON T.user_id = c.user_id
LIMIT 5
The way that summary (COUNT(), etc) queries and detail queries work together is a little intricate, but you will figure it out.
Beware, though: If you do a LIMIT without first doing an ORDER BY, MySQL is free to return any five rows it pleases.

SQL - How to calculate column value and join with another table

As I am not good with MySQL query's so I wish someone help me for creating this kind of sql query.
I having two MySQL tables which is describe bellow:
Table Name: rating
-------------------
property_id user_id area_rate_count safety_rate_count friendly_rate_count walkability_rate_count
4 28 1 1 1 2
5 38 2 3 4 1
5 40 2 2 3 1
6 40 2 3 1 4
10 43 2 2 3 1
Table Name: listing
-------------------
property_id title
4 Sample 1
5 Sample 2
6 Sample 3
10 Sample 4
11 Sample 5
12 Sample 6
Now first I want to sum each column and divide. (area_rate_count, safety_rate_count, friendly_rate_count, walkability_rate_count). For example In property_id:5 having two times so first calculate column sum and divide by 2.
After calculation we will get this output:
Table Name: rating (After Calculation)
--------------------------------------
property_id rate
4 5
5 9 (Divided by 2 because this property_id is two times in table)
6 10
10 8
And Finally I want join this result to my listing table and result looks something like this:
Table Name: listing
-------------------
property_id title rate
4 Sample 1 5
5 Sample 2 9 (Divided by 2 becouse property_id is two times in table)
6 Sample 3 10
10 Sample 4 8
11 Sample 5 0
12 Sample 6 0
Thanks.
I think you want the avg() aggregation function along with a join:
select l.property_id, l.title,
coalesce(avg(area_rate_count + safety_rate_count + friendly_rate_count + walkability_rate_count
), 0) as rate
from listing l left outer join
property_id p
on l.property_id = p.property_id
group by l.property_id, l.title ;
If I understood it right I think you need this:
select l.property_id, l.title, coalesce(r.ssum/if(r.ct=0,1,r.ct), 0) as rate
from listing l LEFT JOIN
(select property_id,
sum(area_rate_count+safety_rate_count
+friendly_rate_count+walkability_rate_count) ssum,
count(*) ct
from rating
group by property_id ) r
ON l.property_id = r.property_id
order by l.property_id
See it here on fiddle: http://sqlfiddle.com/#!2/589d6/5
Edit
As OP asked on the comments that he wants all columns from listing here is what he want:
select l.*, coalesce(r.ssum/if(r.ct=0,1,r.ct), 0) as rate
from listing l LEFT JOIN
(select property_id,
sum(area_rate_count+safety_rate_count
+friendly_rate_count+walkability_rate_count) ssum,
count(*) ct
from rating
group by property_id ) r
ON l.property_id = r.property_id
order by l.property_id
CREATE TEMPORARY TABLE IF NOT EXISTS
temp_table ( INDEX(col_2) )
ENGINE=MyISAM
AS (
SELECT
property_id,
AVG(area_rate_count) as area_rate_count,
AVG(safety_rate_count) as safety_rate_count,
AVG(friendly_rate_count) as friendly_rate_count,
AVG(walkability_rate_count) as walkability_rate_count
FROM rating
GROUP BY property_id
)
SELECT * FROM listing L
JOIN temp_table T
ON L.property_id = T.property_id
Use the below statement to get distinct property_id with its own rate
select property_id, sum(separaterating)/count(property_id) from (
select property_id,sum(area_rate_count , safety_rate_count , friendly_rate_count , walkability_rate_count) as separaterating from rating group by property_id AS temp ) group by
property_id
you can then join with the other table to get the final result as below
select * from ( select property_id, sum(separaterating)/count(property_id) from (
select property_id,sum(area_rate_count , safety_rate_count , friendly_rate_count , walkability_rate_count) as separaterating from rating group by property_id AS temp ) group by
property_id) AS A inner join listing AS B on A.property_id = B.property_id
try this:
select a.prop_id as property_id, l.title, a.allratings / b.numberofreviews as rate
from
(
select property_id as prop_id, SUM(coalesce(area_rate_count,0) + coalesce(safety_rate_count,0) + coalesce(friendly_rate_count,0) + coalesce(walkability_rate_count,0)) as allratings
from rating
group by property_id
) a inner join
(
select property_id, count(distinct user_id) as numberofreviews
from rating
group by property_id
) b on a.property_id = b.property_id
inner join listing l on a.property_id = l.property_id
Try This Query
select ls.property_id,ls.title,inr.rate from listing as ls
left join
(select r.property_id as pid,r.rate/r.cnt as rate from
(select property_id,user_id,(area_rate_count+safefty_rate_count+friendly_rate_count+walkability_rate_count) as rate,count(*) as cnt from rating group by property_id) as r) as inr on inr.pid=ls.property_id

MySQL query limit per join

I tried asking question before, but it's hard to ask in specific without right terminology I am not quite familiar with. So here is an example
Take this query for example:
(
SELECT *
FROM comments
WHERE depth = 0
ORDER BY id DESC
LIMIT 2
)
UNION ALL
(
SELECT c.*
FROM comments c JOIN
(
SELECT id
FROM comments
WHERE depth = 0
ORDER BY id DESC
LIMIT 2
) p ON c.parent_id = p.id
LIMIT 5
)
id parent_id depth title
1 0 0 Title 1
2 0 0 Title 2
3 1 1 Title 3
4 1 1 Title 4
5 1 1 Title 5
6 1 1 Title 6
7 1 1 Title 7
I get two depth 0 rows and in join I get 5 child elements of those two returned queries as well. What I would like to get is to get 5 child elements of each of those two queries, total of 10 rows (of depth 1). For example:
id parent_id depth title
1 0 0 Title 1
2 0 0 Title 2
3 1 1 Title 3
4 1 1 Title 4
5 1 1 Title 5
6 1 1 Title 6
7 1 1 Title 7
8 2 1 Title 8
9 2 1 Title 9
10 2 1 Title 10
11 2 1 Title 11
12 2 1 Title 12
Is that even possible with adjacency list and a requirement to return everything as union (flat)?
edit:
Thanks to Bill Karwin's answer, I got it working now. I wonder still if there is a shorter way to write this. I have 6 (0-5) depth levels, so my query is rather long (and probably not optimal). Here is what it looks like for three levels (you can imagine what the full one looks like).
-- DEPTH LEVEL 0
(
SELECT * FROM (
SELECT *, 1 as _rn, #parent:=0
FROM comments
WHERE depth = 0
ORDER BY id DESC
LIMIT 2
) as D0
)
union all
-- DEPTH LEVEL 1
(
SELECT *
FROM (
SELECT c.*, #row:=IF(#parent=c.comment_id,#row+1,1) AS _rn, #parent:=c.comment_id
FROM (SELECT #parent:=null) AS _init
STRAIGHT_JOIN comments c
INNER JOIN
(
SELECT id
FROM comments
WHERE depth = 0
ORDER BY id DESC
LIMIT 2
) p ON c.comment_id = p.id
) AS _ranked
WHERE _ranked._rn <= 5
)
union all
-- DEPTH LEVEL 2
(
SELECT *
FROM (
SELECT c.*, #row:=IF(#parent=c.comment_id,#row+1,1) AS _rn, #parent:=c.comment_id
FROM (SELECT #parent:=null) AS _init
STRAIGHT_JOIN comments c
INNER JOIN
(
(
SELECT *
FROM (
SELECT c.*, #row:=IF(#parent=c.comment_id,#row+1,1) AS _rn, #parent:=c.comment_id
FROM (SELECT #parent:=null) AS _init
STRAIGHT_JOIN comments c
INNER JOIN
(
SELECT id
FROM comments
WHERE depth = 0
ORDER BY id DESC
LIMIT 2
) p ON c.comment_id = p.id
) AS _ranked
WHERE _ranked._rn <= 2
)
) p ON c.comment_id = p.id
) AS _ranked
WHERE _ranked._rn <= 2
)
You can't do this with LIMIT, because LIMIT is applied after the result set is completely finished, after all joining, grouping, sorting, etc.
You're using a variation of the greatest-n-per-group type of query. It's tricky to do this in MySQL because MySQL doesn't support the ROW_NUMBER() window function supported by many other SQL databases.
Here's a workaround for MySQL, in which user-defined variables can take the place of partitioned row numbers:
SELECT *
FROM (
SELECT c.*, #row:=IF(#parent=c.parent_id,#row+1,1) AS _rn, #parent:=c.parent_id
FROM (SELECT #parent:=null) AS _init
STRAIGHT_JOIN comments c
INNER JOIN
(
SELECT id
FROM comments
WHERE depth = 0
ORDER BY id DESC
LIMIT 2
) p ON c.parent_id = p.id
) AS _ranked
WHERE _ranked._rn <= 5

Counting matches in mysql

I need to make a query that will match blogs that have matching tag_id in table tags_blogs. There is another table that contains the actual tags which I am not concerned about at this stage.
How do I take this table contents:
reference_id tag_id blog_id
1 1 1
2 2 1
3 10 6
4 11 6
5 10 7
6 11 7
7 11 8
And return this where (for example) blog_id = 6:
blog_id total_matches
7 2
8 1
In other words return any blog's ids that have matching tag_id to the parameter provided, as well as a count of how many matches were achieved.
This is the code I have so far (I am way off so far):
SELECT blog_id FROM tags_blogs WHERE blog_id = 6
You need a subquery to select all of 6 (or whatever blog id) tags and if a blog has a tag id IN that subquery it gets selected, then groups same blog_ids together and counts them.
SELECT
a.blog_id,
count(*) as total_matches
FROM
tags_blogs as a
WHERE
a.tag_id IN
( SELECT tag_id FROM tags_blogs WHERE b.blog_id=6 ) AND
a.blog_id!=6
GROUP BY a.blog_id
will return results like
blog_id total_matches
7 2
8 2
From your comments, here's more like what you are looking for. Note that this particular query isn't necessarily optimal
select tb.blog_id, count(*)
as total_matches
from tags_blogs tb
where tag_id in (select distinct tag_id from tags_blogs where blog_id = 6)
and blog_id != 6
group by blog_id
Link to SQL Fiddle
You might find that this is a little more efficient in some circumstances, or easier to create:
select tb.blog_id, count(*)
as total_matches
from tags_blogs tb
join tags_blogs tb1 on tb1.tag_id = tb.tag_id and tb1.blog_id != tb.blog_id
where tb1.blog_id = 6
group by tb.blog_id;