I have the following mysql table with sample data as follows:
id location parentid
1 UK 0
2 East Anglia 1
3 Cambridgeshire 2
4 Norfolk 2
5 Suffolk 2
6 East Midlands 1
7 Derbyshire 6
8 Leicestershire 6
9 EU Countries 0
10 Austria 9
11 Belgium 9
I want to generate a query whereby I can get a list of locations by location name but the location should include any parent location. e.g.
A search for folk should return:
id location
4 Norfolk, East Anglia, UK
5 Suffolk, East Anglia, UK
A search for East should return:
id location
2 East Anglia, UK
6 East Midlands, UK
A search for Bel should return:
id location
11 Belgium
In the above we are excluding concatanting EU countries
Obviously the following doesnt work:
select c.id, CONCAT_WS(', ', c.location, p.location, pp.location) as location
from tbl_locations c
outer left join tbl_locations p on p.id = c.parentid
outer left join tbl_locations pp on pp.id = p.parentid
where c.location like '%whatever%'
If you only want a the parent location this is done with a self join:
select c.id, c.location, p.id, p.location
from tbl_locations c
outer left join tbl_locations p on p.id = c.parentid
where c.location like '%whatever%'
This can be extended (via outer joins) to an arbitrary number of levels but the query will get long. Eg. to three levels:
select c.id, c.location, p.id, p.location, pp.id, pp.location
from tbl_locations c
outer left join tbl_locations p on p.id = c.parentid
outer left join tbl_locations pp on pp.id = p.parentid
where c.location like '%whatever%'
More general recursive queries depend on the details of the RDBMS. The easiest approach is to use a Common Table Expression (CTE). But MySQL doesn't support them (at least, not yet). Other approaches can be used: Generating Depth based tree from Hierarchical Data in MySQL (no CTEs).
1) There is no standard SQL query which can calculate the "transitive closure" a transitive relation. If you want to nest select statements you will always have a maximum depth that can be reached.
2) There is no standard SQL query which will return a row with a variable number of columns. So you would have to format your results in some way or another (e.g. csv).
However, you can accomplish that in MySQL with a stored procedure:
1 CREATE DATABASE IF NOT EXISTS test;
2 USE test;
3
4
5 DROP TABLE IF EXISTS location;
6 CREATE TABLE location (id INT UNSIGNED PRIMARY KEY, name VARCHAR(30) NOT NULL, parent_id INT UNSIGNED NULL REFERENCES location(id));
7
8 INSERT INTO location VALUES
9 (1,"UK",0),
10 (2,"East Anglia",1),
11 (3,"Cambridgeshire",2),
12 (4,"Norfolk",2),
13 (5,"Suffolk",2),
14 (6,"East Midlands",1),
15 (7,"Derbyshire",6),
16 (8,"Leicestershire",6);
17
18
19
20
21 DROP FUNCTION IF EXISTS location_with_parents;
22 DELIMITER //
23 CREATE FUNCTION location_with_parents(location_id INT UNSIGNED) RETURNS VARCHAR(255) READS SQL DATA
24 BEGIN
25 DECLARE LOC_STR VARCHAR(255) DEFAULT NULL;
26 DECLARE LOC_ADD VARCHAR(255) DEFAULT NULL;
27 DECLARE PAR_ID INT UNSIGNED DEFAULT location_id;
28
29 SELECT name INTO LOC_STR FROM location where id=PAR_ID;
30 loop_label: LOOP
31 SELECT parent_id INTO PAR_ID FROM location where id=PAR_ID;
32
33 IF PAR_ID = 0 THEN
34 LEAVE loop_label;
35 ELSE
36 SELECT name INTO LOC_ADD FROM location where id=PAR_ID;
37 SET LOC_STR = CONCAT(LOC_STR, ', ', LOC_ADD);
38 ITERATE loop_label;
39 END IF;
40 END LOOP loop_label;
41 RETURN LOC_STR;
42
43 END;
44 //
45
46 DELIMITER ;
47
48
49
50 SELECT location_with_parents(id) FROM location WHERE name LIKE "%folk%";
51
52 DROP DATABASE test;
Works for me with MySQL 5.6.35
Hope this helps!
Below Query gives you exact result which you want using Recursion method.
Select S.ID ,
concat( S.location,',', Group_concat
(distinct A.location ORDER BY A.location SEPARATOR ',' ) ) as location
from
( SELECT distinct #r AS _id ,location,
(
SELECT #r := parentid
FROM tbl_locations
WHERE id = _id
) AS parentid,
#l := #l + 1 AS level
FROM (
SELECT #r := h.ID,
#l := 0,
#cl := 0
from tbl_locations h
where location like '%folk%'
) vars,
tbl_locations h
WHERE #r <> 0
)A , tbl_locations S
where s.location like '%folk%'
group by S.ID
OutPut :
location like '%East%' :
location like '%Folk%'
its good question and check and ask if you have any concerns.
Related
My requirement is below .
I have two tables let's call them Table A and Table B :
PARTNER_ID PARTNER_Registration Partner_PANNUMBER
----------
1 11 AB1
2 22 AB2
3 33 AB3
4 44 AB4
5 55 AB5
6 66 AB6
7 77 AB5
8 88 AB8
i Will have another table B which contains PID , Preg, Ppan as follows
PID PREG PPAN
----------
1 11 AB1
2 22 AB2
3 33 AB3
4 44
5 AB5
66 AB6
Now I should create a column Output in table A and have output as follows
PARTNER_ID PARTNER_Registration Partner_PANNUMBER Output
----------
1 11 AB1 All three Found
2 22 AB2 All three Found
3 33 AB3 All three found
4 44 AB4 PPAN NOT FOUND
5 55 AB5 PARTNER_Registration Not Found in TABLE B
6 66 AB6 PARTNER_ID Not found in Table B
7 77 AB5 PARTNER_ID, PARTNER_Registration Not found in Table B
8 88 AB8 None of them Found in Table B
Can some one help me find an easy way to acheive this in SQL,
I would like to populate which values of 3 columns are not present in another and update output column accordingly..
Thanks
I would just add up the number of matches:
select a.*,
( exists (select 1 from b where b.PID = a.PARTNER_ID) +
exists (select 1 from b where b.PREG = a.PARTNER_Registration) +
exists (select 1 from b where b.PPAN = a.Partner_PANNUMBER)
) as num_matches
from a;
You can use multiple LEFT JOIN with table B, and test which ones produce NULL values.
SELECT a.*,
CASE WHEN b1.pid IS NOT NULL AND b2.preg IS NOT NULL AND b3.ppan IS NOT NULL
THEN 'All three found'
WHEN b1.pid IS NULL AND b2.preg IS NULL AND p3.ppan IS NULL
THEN 'None of them found in Table B'
ELSE CONCAT(CONCAT_WS(', '
IF(b1.pid IS NULL, 'Partner_ID', NULL),
IF(b2.preg IS NULL, 'Partner_Registration', NULL),
IF(b3.ppan IS NULL, 'PPAN', NULL)),
' not found in Table B') AS Output
FROM TableA AS a
LEFT JOIN TableB AS b1 ON a.partner_id = b1.pid
LEFT JOIN TableB AS b2 ON a.partner_registration = b2.preg
LEFT JOIN TableB AS b3 ON a.partner_pannumber = b.ppan
CONCAT_WS() will ignore null values, so with the IF statements inverting NULL with the names of the missing values, you get the list of results you want.
I would use multiple LEFT JOIN because of the use case of OP where we can manipulate the null values means missing in table2 to achieve the exact output you want.
The query looks big but it is just manipulating the string using specific string functions to get the final string output.
select a.partner_id
,a.partner_registration
,a.partner_pannumber
,case
when chk = ''
then 'All three found'
else
concat(case (length(chk) - length(replace(chk,',','')))
when 3
then 'None of them found'
when 1
then replace(chk,',',' not found')
else
regexp_replace(chk,'[,]',' not found',1,2)
end
,' in table2'
)
end Remarks
from
(
select a.*
,concat(case when pi.pid is null then 'Partner Id,' else '' end
,case when pr.preg is null then 'Partner Registration,' else '' end
,case when pp.ppan is null then 'PPAN,' else '' end
) chk
from table1 a
left join table2 pi
on a.partner_id = pi.pid
left join table2 pr
on a.partner_registration = pr.preg
left join table2 pp
on a.partner_pannumber = pp.ppan
) a
P.S. I personally like the answer with usage of concat_ws as it has less code but you need a little modification for non of them match in ... case.
kon
id | name
1 alex
2 peter
3 john
ticket
id | amount | kon_id | package
122 13 1 234
123 12 1 234
124 20 2 NULL
125 23 2 235
126 19 1 236
I would like to get a list of all contacts with the sum of the amount, except tickets, where the package entry is NULL.
My problem is, that I only get the contacts which have a ticket, because of the WHERE clause.
SELECT
kon.id,
kon.name,
SUM(ticket.amount)
FROM kon LEFT JOIN ticket ON kon.id = ticket.kon_id
WHERE ticket.package IS NOT NULL
GROUP BY kon.id
At the moment, the output looks like this
1 alex 44
2 peter 23
but it should look like this
1 alex 44
3 john NULL
2 peter 23
I use a MySQL Server.
Is it possible to solve this?
Replace Where with AND
SELECT
kon.id,
kon.name,
SUM(ticket.amount)
FROM kon LEFT JOIN ticket ON kon.id = ticket.kon_id AND ticket.package IS NOT NULL
GROUP BY kon.id
Check This.
SELECT
k.id,
k.name ,
coalesce (SUM(t.amount) ,0)
FROM kon k LEFT JOIN
( select id,amount,kon_id,package from ticket where package is not null ) t
ON k.id = t.kon_id
GROUP BY k.id, k.name
OutPut :
Begin Tran
Create Table #Kon (id INt , name Nvarchar(255))
Insert into #Kon
Select 1,'alex' UNION ALL
Select 2,'peter' UNION ALL
Select 3,'john'
Create Table #Ticket (id int,amount int,Kon_Id Int,Package Int)
INSERT INTO #Ticket
SELECT 122,13,1,234 UNION ALL
SELECT 123,12,1,234 UNION ALL
SELECT 124,20,2,NULL UNION ALL
SELECT 125,23,2,235 UNION ALL
SELECT 126,19,1,236
SELECT K.id, Name,SUM(amount) amount
FROM #Kon k
LEFT JOIN #Ticket T ON K.id=T.Kon_Id
GROUP BY K.id,Name
RollBAck Tran
Generally, "ticket.package IS NOT NULL" is wrong condition: your query becomes inner join from left join. If ticket.package should be NOT NULL to add from amount, it should be not in condition, but inside SUM agregate function.
working example for MS SQL
SELECT
kon.id,
min(kon.name),
SUM(case when package is NULL then 0 else ticket.amount end)
FROM #kon kon LEFT JOIN #ticket ticket ON kon.id = ticket.kon_id
GROUP BY kon.id
Answer from Mr. Bhosale is right too, but for big tables will have worse performance (the reason is subquery)
the following query return your expected result
SELECT
kon.id,
kon.name,
SUM(ticket.amount) as 'amount'
FROM kon LEFT JOIN ticket ON kon.id = ticket.kon_id
GROUP BY kon.id, kon.name
attached image shows the result
I figured out the fastest way to solve the problem. It takes about 0.2s compared to the other solutions (2s - 2min). The CAST is important, otherwise the summation of double variables is wrong (float string problem).
SELECT
kon1,
kon2,
SUM(CAST(kon3 AS DECIMAL(7,2)))
FROM (
SELECT k.id kon1, k.name kon2, t.amount kon3 FROM kon as k
LEFT JOIN ticket t ON k.id = t.ticket_kon
WHERE t.package IS NOT NULL
UNION ALL
SELECT k.id kon1, k.name kon2, NULL kon3 FROM kon k WHERE) t1
GROUP BY kon1, kon2
I have a data and they are recorded by each year, I am trying to compare two years( the past year and the current year) data within one mysql query
Below are my tables
Cost Items
| cid | items |
| 1 | A |
| 2 | B |
Cost
| cid | amount | year |
| 1 | 10 | 1 |
| 1 | 20 | 2 |
| 1 | 30 | 1 |
This is the result I am expecting when i want to compare the year 1 and year 2. Year 1 is the past year and year 2 is the current year
Results
items | pastCost | currentCost |
A | 10 | 20 |
A | 30 | 0 |
However the below query is what i used by gives a strange answer.
SELECT
IFNULL(ps.`amount`, '0') as pastCost
IFNULL(cs.`amount`, '0') as currentCost
FROM
`Cost Items` b
LEFT JOIN
`Cost` ps
ON
b.cID=ps.cID
AND
ps.Year = 1
LEFT JOIN
`Cost` cu
ON
b.cID=cu.cID
AND
cu.Year =2
This is the result i get from my query
items | pastCost | currentCost |
A | 10 | 20 |
A | 30 | 20 |
Please what am i doing wrong? Thanks for helping.
I'm missing something about your query; the SQL text shown can't produce that result.
There is no source for the items column in the SELECT list, and there is no table aliased as cs. (Looks like the expression in the SELECT list would need to be cu.amount
Aside from that, the results being returned look exactly like what we'd expect. Each row returned from year=2 is being matched with each row returned from year=1. If there were three rows for year=1 and two rows for year=2, we'd get six rows back... each row for year=1 "matched" with each row for year=2.
If (cid, year) tuple was UNIQUE in Cost, then this query would return a result similar to what you expect.
SELECT b.items
, IFNULL(ps.amount, '0') AS pastCost
, IFNULL(cu.amount, '0') AS currentCost
FROM `Cost Items` b
LEFT
JOIN `Cost` ps
ON ps.cid = b.cid
AND ps.Year = 1
LEFT
JOIN `Cost` cu
ON cu.cid = b.cid
AND cu.Year = 2
Since (cid, year) is not unique, you need some additional column to "match" a single row for year=1 with a single row for year=2.
Without some other column in the table, we could use an inline view to generate a value. I can illustrate how we can make MySQL return a resultset like the one you show, one way that could be done, but I don't think this is really the solution to whatever problem you are trying to solve:
SELECT b.items
, IFNULL(MAX(IF(a.year=1,a.amount,NULL)),0) AS pastCost
, IFNULL(MAX(IF(a.year=2,a.amount,NULL)),0) AS currentCost
FROM `Cost Items` b
LEFT
JOIN ( SELECT #rn := IF(c.cid=#p_cid AND c.year=#p_year,#rn+1,1) AS `rn`
, #p_cid := c.cid AS `cid`
, #p_year := c.year AS `year`
, c.amount
FROM (SELECT #p_cid := NULL, #p_year := NULL, #rn := 0) i
JOIN `Cost` c
ON c.year IN (1,2)
ORDER BY c.cid, c.year, c.amount
) a
ON a.cid = b.cid
GROUP
BY b.cid
, a.rn
A query something like that would return a resultset that looks like the one you are expecting. But again, I strongly suspect that this is not really the resultset you are really looking for.
EDIT
OP leaves comment with vaguely nebulous report of observed behavior: "the above solution doesnt work"
Well then, let's check it out... create a SQL Fiddle with some tables so we can test the query...
SQL Fiddle here http://sqlfiddle.com/#!9/e3d7e/1
create table `Cost Items` (cid int unsigned, items varchar(5));
insert into `Cost Items` (cid, items) values (1,'A'),(2,'B');
create table `Cost` (cid int unsigned, amount int, year int);
insert into `Cost` (cid, amount, year) VALUES (1,10,1),(1,20,2),(1,30,1);
And when we run the query, we get a syntax error. There's closing paren missing in the expressions in the SELECT list, easy enough to fix.
SELECT b.items
, IFNULL(MAX(IF(a.year=1,a.amount,NULL)),0) AS pastCost
, IFNULL(MAX(IF(a.year=2,a.amount,NULL)),0) AS currentCost
FROM `Cost Items` b
LEFT
JOIN ( SELECT #rn := IF(c.cid=#p_cid AND c.year=#p_year,#rn+1,1) AS `rn`
, #p_cid := c.cid AS `cid`
, #p_year := c.year AS `year`
, c.amount
FROM (SELECT #p_cid := NULL, #p_year := NULL, #rn := 0) i
JOIN `Cost` c
ON c.year IN (1,2)
ORDER BY c.cid, c.year, c.amount
) a
ON a.cid = b.cid
GROUP
BY b.cid
, a.rn
Returns:
items pastCost currentCost
------ -------- -----------
A 10 20
A 30 0
B 0 0
I have started learning MySQL and I'm having a problem with JOIN.
I have two tables: purchase and sales
purchase
--------------
p_id date p_cost p_quantity
---------------------------------------
1 2014-03-21 100 5
2 2014-03-21 20 2
sales
--------------
s_id date s_cost s_quantity
---------------------------------------
1 2014-03-21 90 9
2 2014-03-22 20 2
I want these two tables to be joined where purchase.date=sales.date to get one of the following results:
Option 1:
p_id date p_cost p_quantity s_id date s_cost s_quantity
------------------------------------------------------------------------------
1 2014-03-21 100 5 1 2014-03-21 90 9
2 2014-03-21 20 2 NULL NULL NULL NULL
NULL NULL NULL NULL 2 2014-03-22 20 2
Option 2:
p_id date p_cost p_quantity s_id date s_cost s_quantity
------------------------------------------------------------------------------
1 2014-03-21 100 5 NULL NULL NULL NULL
2 2014-03-21 20 2 1 2014-03-21 90 9
NULL NULL NULL NULL 2 2014-03-22 20 2
the main problem lies in the 2nd row of the first result. I don't want the values
2014-03-21, 90, 9 again in row 2... I want NULL instead.
I don't know whether it is possible to do this. It would be kind enough if anyone helps me out.
I tried using left join
SELECT *
FROM sales
LEFT JOIN purchase ON sales.date = purchase.date
output:
s_id date s_cost s_quantity p_id date p_cost p_quantity
1 2014-03-21 90 9 1 2014-03-21 100 5
1 2014-03-21 90 9 2 2014-03-21 20 2
2 2014-03-22 20 2 NULL NULL NULL NULL
but I want 1st 4 values of 2nd row to be NULL
Since there are no common table expressions or full outer joins to work with, the query will have some duplication and instead need to use a left join unioned with a right join;
SELECT p_id, p.date p_date, p_cost, p_quantity,
s_id, s.date s_date, s_cost, s_quantity
FROM (
SELECT *,(SELECT COUNT(*) FROM purchase p1
WHERE p1.date=p.date AND p1.p_id<p.p_id) rn FROM purchase p
) p LEFT JOIN (
SELECT *,(SELECT COUNT(*) FROM sales s1
WHERE s1.date=s.date AND s1.s_id<s.s_id) rn FROM sales s
) s
ON s.date=p.date AND s.rn=p.rn
UNION
SELECT p_id, p.date p_date, p_cost, p_quantity,
s_id, s.date s_date, s_cost, s_quantity
FROM (
SELECT *,(SELECT COUNT(*) FROM purchase p1
WHERE p1.date=p.date AND p1.p_id<p.p_id) rn FROM purchase p
) p RIGHT JOIN (
SELECT *,(SELECT COUNT(*) FROM sales s1
WHERE s1.date=s.date AND s1.s_id<s.s_id) rn FROM sales s
) s
ON s.date=p.date AND s.rn=p.rn
An SQLfiddle to test with.
In a general sense, what you're looking for is called a FULL OUTER JOIN, which is not directly available in MySQL. Instead you only get LEFT JOIN and RIGHT JOIN, which you can UNION together to get essentially the same result. For a very thorough discussion on this subject, see Full Outer Join in MySQL.
If you need help understanding the different ways to JOIN a table, I recommend A Visual Explanation of SQL Joins.
The way this is different from a regular FULL OUTER JOIN is that you're only including any particular row from either table at most once in the JOIN result. The problem being, if you have one purchase record and two sales records on a particular day, which sales record is the purchase record associated with? What is the relationship you're trying to represent between these two tables?
It doesn't sound like there's any particular relationship between purchase and sales records, except that some of them happened to take place on the same day. In which case, you're using the wrong tool for the job. If all you want to do is display these tables side by side and line the rows up by date, you don't need a JOIN at all. Instead, you should SELECT each table separately and do your formatting with some other tool (or manually).
Here's another way to get the same result, but the EXPLAIN for this is horrendous; and performance with large sets is going to be atrocious.
This is essentially two queries UNIONed together. The first query is essentially "purchase LEFT JOIN sales", the second query is essentially "sales ANTI JOIN purchase".
Because there is no foreign key relationship between the two tables, other than rows matching on date, we have to "invent" a key we can join on; we use user variables to assign ascending integer values to each row within a given date, so we can match row 1 from purchase to row 1 from sales, etc.
I wouldn't normally generate this type of result using SQL; it's not a typical JOIN operation, in the sense of how we traditionally join tables.
But, if I had to produce the specified resultset using MySQL, I would do it like this:
SELECT p.p_id
, p.p_date
, p.p_cost
, p.p_quantity
, s.s_id
, s.s_date
, s.s_cost
, s.s_quantity
FROM ( SELECT #pl_i := IF(pl.date = #pl_prev_date,#pl_i+1,1) AS i
, #pl_prev_date := pl.date AS p_date
, pl.p_id
, pl.p_cost
, pl.p_quantity
FROM purchase pl
JOIN ( SELECT #pl_i := 0, #pl_prev_date := NULL ) pld
ORDER BY pl.date, pl.p_id
) p
LEFT
JOIN ( SELECT #sr_i := IF(sr.date = #sr_prev_date,#sr_i+1,1) AS i
, #sr_prev_date := sr.date AS s_date
, sr.s_id
, sr.s_cost
, sr.s_quantity
FROM sales sr
JOIN ( SELECT #sr_i := 0, #sr_prev_date := NULL ) srd
ORDER BY sr.date, sr.s_id
) s
ON s.s_date = p.p_date
AND s.i = p.i
UNION ALL
SELECT p.p_id
, p.p_date
, p.p_cost
, p.p_quantity
, s.s_id
, s.s_date
, s.s_cost
, s.s_quantity
FROM ( SELECT #sl_i := IF(sl.date = #sl_prev_date,#sl_i+1,1) AS i
, #sl_prev_date := sl.date AS s_date
, sl.s_id
, sl.s_cost
, sl.s_quantity
FROM sales sl
JOIN ( SELECT #sl_i := 0, #sl_prev_date := NULL ) sld
ORDER BY sl.date, sl.s_id
) s
LEFT
JOIN ( SELECT #pr_i := IF(pr.date = #pr_prev_date,#pr_i+1,1) AS i
, #pr_prev_date := pr.date AS p_date
, pr.p_id
, pr.p_cost
, pr.p_quantity
FROM purchase pr
JOIN ( SELECT #pr_i := 0, #pr_prev_date := NULL ) prd
ORDER BY pr.date, pr.p_id
) p
ON p.p_date = s.s_date
AND p.i = s.i
WHERE p.p_date IS NULL
ORDER BY COALESCE(p_date,s_date),COALESCE(p_id,s_id)
I have a collection content that has four columns; id, timestamp, locationID, and authorID. Here is an example of my data; in production, this is tens of millions of rows in length.
id timestamp locationID authorID
1 2012-03-01 11:52:00 1 1
2 2012-03-16 19:56:00 1 2
3 2012-04-02 11:26:00 2 1
4 2012-04-22 11:52:00 2 3
5 2012-05-19 09:48:00 2 2
6 2012-05-30 07:12:00 2 1
7 2012-06-04 19:17:00 1 2
I'd like to collect the list of authorIDs whose most recent content (ordered by timestamp) matched a specific locationID.
The correct values for a query of locationID = 2 would be: [ 1, 3 ], as authorID 1 and 3 were most recently 'seen' at locationID = 2, while authorID 2's most recent content was at locationID 1.
I can certainly execute one query per authorID, but on production the authorID array has a length >100,000. This seems terribly inefficient (especially when each 'subquery' would be hitting this multi-million row content collection), and I'm looking for a better way to emerge this data from my dataset, ideally fast enough to be executed on a page render.
Something like this? This is from SQL Server, but I think it should work in mySQL as well.
DECLARE #locationId INT
SET #locationId = 2;
SELECT *
FROM (SELECT AuthorId, Max(TimeStamp) as MaxTimeStamp
FROM Content C
WHERE LocationId = #locationId
GROUP BY AuthorId) AS CBL
LEFT JOIN Content AS C ON CBL.AuthorId = C.AuthorId
AND C.TimeStamp > CBL.MaxTimeStamp
WHERE C.AuthorId IS NULL
For locationId = 2, it returns 1 and 3; and for locationId = 1, it returns 2
Per JW (thanks!), the correct mySql approach:
SET #locationId := 2;
SELECT *
FROM (SELECT AuthorId, Max(TimeStamp) as MaxTimeStamp
FROM Content C
WHERE LocationId = #locationId
GROUP BY AuthorId) AS CBL
LEFT JOIN Content AS C ON CBL.AuthorId = C.AuthorId
AND C.TimeStamp > CBL.MaxTimeStamp
WHERE C.AuthorId IS NULL
Try derieved subquery
SELECT
*
FROM content as c
INNER JOIN(
SELECT
MAX(id) as ID
FROM content
WHERE locationID = 2
GROUP BY authorID
) as t on t.ID = c.id
SQL FIDDLE DEMO