i have a table which contains some data as an example:
+----------+-----+------+
| order_id | poi | povi |
+----------+-----+------+
| 1 | A | a |
| 1 | B | b |
| 1 | C | c |
| 2 | A | a |
| 2 | B | b |
| 2 | C | c |
| 3 | A | a |
| 3 | B | b |
| 4 | C | c |
| 5 | A | a |
| 5 | B | b |
| 6 | C | c |
| 7 | A | a |
| 8 | B | b |
| 9 | C | c |
+----------+-----+------+
i have 3 set of values of poi and povi like {A,a},{B,b},{C,c}
i want to get the order_id which contains all three of them, like in the above case the output should be.(order_id which have poi and povi as {A,a} and {B,b} and {C,c} but the problem is that they are diffrent rows)
+----------+
| order_id |
+----------+
| 1 |
| 2 |
+----------+
any idea?
So many times people just getting started ask similar questions to those already asked and answered, including this common scenario. However, not being able to apply know answers to your scenario doesn't help you wrap your head around what is asked, or how the query works in their own scenario... That said, lets look at yours.
You want all DISTINCT orders that have ALL of the following A/a, B/b, C/c entries. Multiple ways to resolve, but the most common is with a where / group by / having.
Start with something simple, looking for any order that has A/a
select
yt.Order_id
from
YourTable yt
where
( yt.poi = 'A' AND yt.poiv = 'a' )
and you would get order 1, 2, 3, 5 and 7. That is simple...
Now, add in your other criteria
select
yt.Order_id
from
YourTable yt
where
( yt.poi = 'A' AND yt.poiv = 'a' )
OR ( yt.poi = 'B' AND yt.poiv = 'b' )
OR ( yt.poi = 'C' AND yt.poiv = 'c' )
and this will give you all rows, but not what you want, but you should be able to see the where criteria is checking for both parts of POI / POIV with an OR between each possible combination. You obviously can not have one record that has a POI of both "A" and "B", that is why the "OR" between each paired ( AND ) criteria. But again, this gives ALL rows. But it is also qualifying only the pieces. So lets add one next step... a group by via the order, but HAVING clause expecting 3 records...
select
ytA.Order_id
from
YourTable ytA
where
( yt.poi = 'A' AND yt.poiv = 'a' )
OR ( yt.poi = 'B' AND yt.poiv = 'b' )
OR ( yt.poi = 'C' AND yt.poiv = 'c' )
group by
yt.Order_id
HAVING
count(*) = 3
The count(*) is to count how many records qualified the WHERE clause and will only return those records that had 3 entries.
Now, what if someone has multiple orders of A/a, A/a, B/b... This COULD give a false answer returned value, but please confirm these queries to meet your needs.
Although accepted, here is another way I would have written the query... somewhat similar to another post below. The premise of this version of the query is to utilize an index and qualify at least 1 record found before trying to find ALL. In this case, it first qualifies for those with an A/a. If an order does not have that, it does not care about looking for a B/b, C/c. If it DOES, then the join qualifies to the next levels too
select
ytA.Order_id
from
YourTable ytA
JOIN YourTable ytB
on ytA.Order_id = ytB.Order_id
AND ytB.poi = 'B'
AND ytB.poiv = 'b'
JOIN YourTable ytC
on ytB.Order_id = ytC.Order_id
AND ytC.poi = 'C'
AND ytC.poiv = 'c'
where
ytA.poi = 'A'
AND ytA.poiv = 'a'
find the "intersection" of lists, each of which contains one set
select id
from
(select id from mytable where poi = 'A' and povi= 'a') t1
inner join
(select id from mytable where poi = 'B' and povi= 'b') t2
using(id)
inner join
(select id from mytable where poi = 'C' and povi= 'c') t3
using(id)
demo
Related
I've a table "products" and a table where are store some attributes of a product:
zd_products
----------
|ID|title|
----------
| 1| Test|
| 2| Prod|
| 3| Colr|
zd_product_attached_attributes
------------------
|attrid|pid|value|
------------------
|1 | 1 | A |
|2 | 1 | 10 |
|3 | 1 | AB |
|1 | 2 | B |
|2 | 2 | 22 |
|3 | 2 | BB |
|1 | 3 | A |
|2 | 3 | 10 |
|3 | 3 | CC |
I want to search in zd_products only the products that have some attributes values, for exam place
Get the product when the attribute 1 is A and the attribute 3 is AB
Get the product when the attribute 2 is 10 and the attribute 3 is CC
etc
How can i do this using a join ?
Oh, the Joys of the EAV model!
One way is to use a separate JOIN operation for each attribute value. For example:
SELECT p.id
, p.title
FROM zd_products p
JOIN zd_product_attached_attributes a1
ON a1.pid = p.id
AND a1.attrid = 1
AND a1.value = 'A'
JOIN zd_product_attached_attributes a3
ON a3.pid = p.id
AND a3.attrid = 3
AND a3.value = 'AB'
With appropriate indexes, that's likely going to be the most efficient approach. This isn't the only query that will return the specified result, but this one does make use of JOIN operations.
Another, less intuitive approach
If id is unique in the zd_products table, and we have guarantee that the (attrid,pid,value) tuple is unique in the zd_product_attached_attributes table, then this:
SELECT p.id
, p.title
FROM zd_products p
JOIN zd_product_attached_attributes a
ON a.pid = p.id
AND ( (a.attrid = 1 AND a.value = 'A')
OR (a.attrid = 3 AND a.value = 'AB')
)
GROUP
BY p.id
, p.title
HAVING COUNT(1) > 1
will return an equivalent result. The latter query is of a form that is particularly suitable for matching two criteria out of three, where we don't need a match on ALL of the attributes, but just some of them. For example, finding a product that matches any two of:
color = 'yellow'
size = 'bigger'
special = 'on fire'
And of course there are other approaches that don't make use of a JOIN.
FOLLOWUP
Q: And if I want to the same but using OR operator? I mean get ONLY if the attribute 1 is A or the attribute 2 is AB otherwise don't select the record.
A: A query of the form like the second one in my answer (above) is more conducive to the OR condition.
If you want XOR (exclusive OR), where one of the attributes has a matching value but the other one doesn't, just change the HAVING COUNT(1) > 1 to HAVING COUNT(1) = 1. Only rows from products that find one "matching" row in the attributes table will be returned. To match exactly 2 (out of several), HAVING COUNT(1) = 2, etc.
A query like the first one in my answer can be modified to use OUTER joins, to find matches, and then do a conditional test in the WHERE clause, to determine if a match was found.
SELECT p.id
, p.title
FROM zd_products p
LEFT
JOIN zd_product_attached_attributes a1
ON a1.pid = p.id
AND a1.attrid = 1
AND a1.value = 'A'
LEFT
JOIN zd_product_attached_attributes a3
ON a3.pid = p.id
AND a3.attrid = 3
AND a3.value = 'AB'
WHERE a1.pid IS NOT NULL
OR a3.pid IS NOT NULL
I've just added the LEFT keyword, to specify an outer join; rows from products will be returned with matching rows from a1 and a3, along with rows from products that don't have any matching rows found in a1 or a3.
The WHERE clause tests a column from a1 and a3 to see whether a matching row was returned. If a matching row was found in a1, we are guaranteed that the pid column from a1 will be non-NULL. That column will be returned as NULL only if a matching row was not found.
If we replaced the OR with an AND, we'd be negating the "outerness" of both joins, making it essentially equivalent to the first query above.
To get an XOR type operation (exclusive OR) where we find one matching attribute but not the other, we could change the WHERE clause to read:
WHERE (a1.pid IS NOT NULL AND a3.pid IS NULL)
OR (a3.pid IS NOT NULL AND a1.pid IS NULL)
Use a pivot
You can do this type of query using a pivot. As far as I know, MySQL doesn't have a native, built in pivot, but you can achieve this by transposing the rows and columns of your zd_product_attached_attributes table using:
SELECT pid,
MAX(CASE WHEN attrid = 1 THEN value END) `attrid_1`,
MAX(CASE WHEN attrid = 2 THEN value END) `attrid_2`,
MAX(CASE WHEN attrid = 3 THEN value END) `attrid_3`
FROM zd_product_attached_attributes
GROUP BY pid
This will pivot your table as shown:
+----+---------+-------+ +----+----------+----------+----------+
| attrid | pid | value | | pid| attrid_1 | attrid_2 | attrid_3 |
+----+---+-------------+ +----+----------+----------+----------+
| 1 | 1 | A | | 1 | A | 10 | AB |
| 2 | 1 | 10 | => | 2 | B | 22 | BB |
| 3 | 1 | AB | | 3 | A | 10 | CC |
| 1 | 2 | B | +----+----------+----------+----------+
| 2 | 2 | 22 |
| 3 | 2 | BB |
| 1 | 3 | A |
| 2 | 3 | 10 |
| 3 | 3 | CC |
+--------+---------+---+
So you can select the products id and title using:
SELECT id, title FROM zd_products
LEFT JOIN
(
SELECT pid,
MAX(CASE WHEN attrid = 1 THEN value END) `attrid_1`,
MAX(CASE WHEN attrid = 2 THEN value END) `attrid_2`,
MAX(CASE WHEN attrid = 3 THEN value END) `attrid_3`
FROM zd_product_attached_attributes
GROUP BY pid
) AS attrib_search
ON id = pid
WHERE ( attrib_1 = 'A' AND attrib_3 = 'AB' )
OR ( attrib_2 = 10 AND attrib_3 = 'CC' )
Note: You can use this type of query when you have guaranteed uniqueness on (pid, attrid)
(thanks #spencer7593)
I haven't tested this, but I think it should work:
select title
from zd_products p
join zd_product_attached_attributes a ON a.pid = p.id
where ( attrid = 1 and value = 'A' )
or ( attrid = 3 and value = 'AB' );
If you want to tack on more "searches" you could append more lines similar to the last one (ie. or "or" statements)
I have the following data:
| ID | Date | Code |
--------------------------
| 1 | 26/02/14 | 10 |
| 1 | 25/02/14 | 11 |
| 1 | 24/02/14 | 10 |
| 2 | 25/02/14 | 13 |
| 2 | 24/02/14 | 11 |
| 2 | 23/02/14 | 10 |
All I want is to group by the ID field and return the maximum value from the date field (i.e. most recent). So the final result should look like this:
| ID | Date | Code |
--------------------------
| 1 | 26/02/14 | 10 |
| 2 | 25/02/14 | 13 |
It seems though that if I want the "Code" field showing in the same query I also have to group or aggregate it as well... which makes sense because there could potentially be more than one value left on that field after the others are grouped/aggregated (even though there won't be in this case).
I thought I could handle this problem by doing the GroupBy and Max in a subquery on just those fields and then do a join on that subquery to bring in the "Code" field I don't want grouped or aggregated:
SELECT Q.ID, Q.MaxOfDate, A.Code
FROM
(SELECT B.ID, Max(B.Date) As MaxOfDate
FROM myTable As B
GROUP BY B.ID) As Q
LEFT JOIN myTable As A ON Q.ID = A.ID;
This isn't working though as it is still only giving me the original number of records I started with.
How do you do grouping and aggregation with fields you don't necessarily want grouped/aggregated?
An alternative to the answer I accepted:
SELECT Q.ID, Q.MaxOfDate, A.Code
FROM
(SELECT B.ID, Max(B.Date) As MaxOfDate
FROM myTable As B
GROUP BY B.ID) As Q
LEFT JOIN myTable As A ON (Q.ID = A.ID) AND (A.Date = Q.MaxOfDate);
Needed to do the LEFT JOIN on the Date field as well as the ID field.
If you want the CODE associated with the Max Date, you will have to use a subquery with a top 1, like this:
SELECT B.ID, Max(B.Date) As MaxOfDate,
(select top 1 C.Code
from myTable As C
where B.ID = C.ID
order by C.Date desc, C.Code) as Code
FROM myTable As B
GROUP BY B.ID
Let's say i've got this database:
book
| idBook | name |
|--------|----------|
| 1 |Book#1 |
category
| idCateg| category |
|--------|----------|
| 1 |Adventures|
| 2 |Science F.|
book_categ
| id | idBook | idCateg | DATA |
|--------|--------|----------|--------|
| 1 | 1 | 1 | (null) |
| 2 | 1 | 2 | (null) |
I'm trying to select only the books which are in category 1 AND category 2
This is what I've got so far:
SELECT book.* FROM book,book_categ
WHERE book_categ.idCateg = 1 AND book_categ.idCateg = 2
Obviously, this giving 0 results becouse each row has only one idCateg it does work width OR but the results are not what I need. I've also tried to use a join, but I just can't get the results I expect.
Here it's the SQLFiddle of my current project, the data at the begining is just a sample.
SQLFiddle
Any help will be really appreciated.
You could double join with a constraint on the category id:
SELECT a.* FROM book AS a
INNER JOIN book_categ AS b ON a.idBook = b.idBook AND b.idCateg = 1
INNER JOIN book_categ AS c ON a.idBook = c.idBook AND c.idCateg = 2
You could use a subquery:
SELECT a.* FROM book AS a
WHERE
(SELECT COUNT(DISTINCT idCateg) FROM book_categ AS b
WHERE b.idBook = a.idBook AND b.idCateg IN (1,2)) = 2
If you are on MySQL as your fiddle implies, you should prefer the join variant, since most joins are much faster in MySQL than subqueries.
edit
This one should also work:
SELECT a.* FROM book a
INNER JOIN book_categ AS b ON a.idBook = b.idCateg
WHERE b.idCateg IN (5, 6)
GROUP BY idBook
HAVING COUNT(DISTINCT b.idCateg) = 2
and should be faster than the two above, although you have to change the last number according to the number of category ids you are requesting.
I have a table as so...
----------------------------------------
| id | name | group | number |
----------------------------------------
| 1 | joey | 1 | 2 |
| 2 | keidy | 1 | 3 |
| 3 | james | 2 | 2 |
| 4 | steven | 2 | 5 |
| 5 | jason | 3 | 2 |
| 6 | shane | 3 | 3 |
----------------------------------------
I'm running a select like so:
SELECT * FROM table WHERE number IN (2,3);
The problem im trying to solve is that I want to only grab get results from groups that have 1 or more rows of each number. For instance the above query is returning id's 1-2-3-5-6, when I'd like the results to exclude id 3 since the group of '2' can only return 1 result for the number of '2' and not for BOTH 2 and 3, since there's no row with the number 3 for the group 2 i'd like it to not even select id 3 at all.
Any help would be great.
Try it this way
SELECT *
FROM table1 t
WHERE number IN(2, 3)
AND EXISTS
(
SELECT *
FROM table1
WHERE number IN(2, 3)
AND `group` = t.`group`
GROUP BY `group`
HAVING MAX(number = 2) > 0
AND MAX(number = 3) > 0
)
or
SELECT *
FROM table1 t JOIN
(
SELECT `group`
FROM table1
WHERE number IN(2, 3)
GROUP BY `group`
HAVING MAX(number = 2) > 0
AND MAX(number = 3) > 0
) q
ON t.`group` = q.`group`;
or
SELECT *
FROM table1
WHERE `group` IN
(
SELECT `group`
FROM table1
WHERE number IN(2, 3)
GROUP BY `group`
HAVING MAX(number = 2) > 0
AND MAX(number = 3) > 0
);
Sample output (for both queries):
| ID | NAME | GROUP | NUMBER |
|----|-------|-------|--------|
| 1 | joey | 1 | 2 |
| 2 | keidy | 1 | 3 |
| 5 | jason | 3 | 2 |
| 6 | shane | 3 | 3 |
Here is SQLFiddle demo
On this, you can approach from a fun way with multiple joins for what you WANT qualified, OR, apply a prequery to get all qualified groups as others have suggested, but readability is a bit off for me..
Anyhow, here's an approach going through the table once, but with joins
select DISTINCT
T.id,
T.Name,
T.Group,
T.Number
from
YourTable T
Join YourTable T2
on T.Group = T2.Group AND T2.Group = 2
Join YourTable T3
on T.Group = T3.Group AND T3.Group = 3
where
T.Number IN ( 2, 3 )
So on the first record, it is pointing to by it's own group to the T2 group AND the T2 group is specifically a 2... Then again, but testing the group for the T3 instance and T3's group is a 3.
If it cant complete the join to either of the T2 or T3 instances, the record is done for consideration, and since indexes work great for joins like this, make sure you have one index for your NUMBER criteria, and another index on the (GROUP, NUMBER) for those comparisons and the next query sample...
If doing by more than this simple 2, but larger group, prequery qualified groups, then join to that
select
YT2.*
from
( select YT1.group
from YourTable YT1
where YT1.Number in (2, 3)
group by YT1.group
having count( DISTINCT YT1.group ) = 2 ) PreQualified
JOIN YourTable YT2
on PreQualified.group = YT2.group
AND YT2.Number in (2,3)
Maybe this,if I understand you
SELECT id FROM table WHERE `group` IN
(SELECT `group` FROM table WHERE number IN (2,3)
GROUP BY `group`
HAVING COUNT(DISTINCT number)=2)
SQL Fiddle
This will return all ids where BOTH numbers exist in a group.Remove DISTINCT if you want ids for groups where just one numbers is in.
I have a two select statements joined by UNION ALL. In the first statement a where clause gathers only rows that have been shown previously to the user. The second statement gathers all rows that haven't been shown to the user, therefore I end up with the viewed results first and non-viewed results after.
Of course this could simply be achieved with the same select statement using a simple ORDER BY, however the reason for two separate selects is simple after you realize what I hope to accomplish.
Consider the following structure and data.
+----+------+-----+--------+------+
| id | from | to | viewed | data |
+----+------+-----+--------+------+
| 1 | 1 | 10 | true | .... |
| 2 | 10 | 1 | true | .... |
| 3 | 1 | 10 | true | .... |
| 4 | 6 | 8 | true | .... |
| 5 | 1 | 10 | true | .... |
| 6 | 10 | 1 | true | .... |
| 7 | 8 | 6 | true | .... |
| 8 | 10 | 1 | true | .... |
| 9 | 6 | 8 | true | .... |
| 10 | 2 | 3 | true | .... |
| 11 | 1 | 10 | true | .... |
| 12 | 8 | 6 | true | .... |
| 13 | 10 | 1 | false | .... |
| 14 | 1 | 10 | false | .... |
| 15 | 6 | 8 | false | .... |
| 16 | 10 | 1 | false | .... |
| 17 | 8 | 6 | false | .... |
| 18 | 3 | 2 | false | .... |
+----+------+-----+--------+------+
Basically I wish all non viewed rows to be selected by the statement, that is accomplished by checking weather the viewed column is true or false, pretty simple and straightforward, nothing to worry here.
However when it comes to the rows already viewed, meaning the column viewed is TRUE, for those records I only want 3 rows to be returned for each group.
The appropriate result in this instance should be the 3 most recent rows of each group.
+----+------+-----+--------+------+
| id | from | to | viewed | data |
+----+------+-----+--------+------+
| 6 | 10 | 1 | true | .... |
| 7 | 8 | 6 | true | .... |
| 8 | 10 | 1 | true | .... |
| 9 | 6 | 8 | true | .... |
| 10 | 2 | 3 | true | .... |
| 11 | 1 | 10 | true | .... |
| 12 | 8 | 6 | true | .... |
+----+------+-----+--------+------+
As you see from the ideal result set we have three groups. Therefore the desired query for the viewed results should show a maximum of 3 rows for each grouping it finds. In this case these groupings were 10 with 1 and 8 with 6, both which had three rows to be shown, while the other group 2 with 3 only had one row to be shown.
Please note that where from = x and to = y, makes the same grouping as if it was from = y and to = x. Therefore considering the first grouping (10 with 1), from = 10 and to = 1 is the same group if it was from = 1 and to = 10.
However there are plenty of groups in the whole table that I only wish the 3 most recent of each to be returned in the select statement, and thats my problem, I not sure how that can be accomplished in the most efficient way possible considering the table will have hundreds if not thousands of records at some point.
Thanks for your help.
Note: The columns id, from, to and viewed are indexed, that should help with performance.
PS: I'm unsure on how to name this question exactly, if you have a better idea, be my guest and edit the title.
What a hairball! This gets progressively harder as you move from most recent, to second most recent, to third most recent.
Let's put this together by getting the list of IDs we need. Then we can pull the items from the table by ID.
This, relatively easy, query gets you the ids of your most recent items
SELECT id FROM
(SELECT max(id) id, fromitem, toitem
FROM stuff
WHERE viewed = 'true'
GROUP BY fromitem, toitem
)a
Fiddle: http://sqlfiddle.com/#!2/f7045/27/0
Next, we need to get the ids of the second most recent items. To do this, we need a self-join style query. We need to do the same summary but on a virtual table that omits the most recent items.
select id from (
select max(b.id) id, b.fromitem, b.toitem
from stuff a
join
(select id, fromitem, toitem
from stuff
where viewed = 'true'
) b on ( a.fromitem = b.fromitem
and a.toitem = b.toitem
and b.id < a.id)
where a.viewed = 'true'
group by fromitem, toitem
)c
Fiddle: http://sqlfiddle.com/#!2/f7045/44/0
Finally, we need to get the ids of the third most recent items. Mercy! We need to join that query we just had, to the table again.
select id from
(
select max(d.id) id, d.fromitem, d.toitem
from stuff d
join
(
select max(b.id) id, b.fromitem, b.toitem
from stuff a
join
(
select id, fromitem, toitem
from stuff
where viewed = 'true'
) b on ( a.fromitem = b.fromitem
and a.toitem = b.toitem
and b.id < a.id)
where a.viewed = 'true'
group by fromitem, toitem
) c on ( d.fromitem = c.fromitem
and d.toitem = c.toitem
and d.id < c.id)
where d.viewed='true'
group by d.fromitem, d.toitem
) e
Fiddle: http://sqlfiddle.com/#!2/f7045/45/0
So, now we take the union of all those ids, and use them to grab the right rows from the table, and we're done.
SELECT *
FROM STUFF
WHERE ID IN
(
SELECT id FROM
(SELECT max(id) id, fromitem, toitem
FROM stuff
WHERE viewed = 'true'
GROUP BY fromitem, toitem
)a
UNION
select id from (
select max(b.id) id, b.fromitem, b.toitem
from stuff a
join
(select id, fromitem, toitem
from stuff
where viewed = 'true'
) b on ( a.fromitem = b.fromitem
and a.toitem = b.toitem
and b.id < a.id)
where a.viewed = 'true'
group by fromitem, toitem
)c
UNION
select id from
(
select max(d.id) id, d.fromitem, d.toitem
from stuff d
join
(
select max(b.id) id, b.fromitem, b.toitem
from stuff a
join
(
select id, fromitem, toitem
from stuff
where viewed = 'true'
) b on ( a.fromitem = b.fromitem
and a.toitem = b.toitem
and b.id < a.id)
where a.viewed = 'true'
group by fromitem, toitem
) c on ( d.fromitem = c.fromitem
and d.toitem = c.toitem
and d.id < c.id)
where d.viewed='true'
group by d.fromitem, d.toitem
) e
UNION
select id from stuff where viewed='false'
)
order by viewed desc, fromitem, toitem, id desc
Tee hee. Too much SQL. Fiddle: http://sqlfiddle.com/#!2/f7045/47/0
And now, we need to cope with your last requirement, the requirement that your graph is unordered. That is, that from=n to=m is the same as from=m to=n.
To do this we need a virtual table instead of the physical table. This will do the trick.
SELECT id, least(fromitem, toitem) fromitem, greatest(fromitem,toitem) toitem, data
FROM stuff
Now we need to use this virtual table, this view, everywhere the physical table used to appear. Let's use a view to do this.
CREATE VIEW
AS
SELECT id,
LEAST(fromitem, toitem) fromitem,
GREATEST (fromitem, toitem) toitem,
viewed,
data;
So, our ultimate query is:
SELECT *
FROM stuff
WHERE ID IN
(
SELECT id FROM
(SELECT max(id) id, fromitem, toitem
FROM STUFF_UNORDERED
WHERE viewed = 'true'
GROUP BY fromitem, toitem
)a
UNION
SELECT id FROM (
SELECT max(b.id) id, b.fromitem, b.toitem
FROM STUFF_UNORDERED a
JOIN
(SELECT id, fromitem, toitem
FROM STUFF_UNORDERED
WHERE viewed = 'true'
) b ON ( a.fromitem = b.fromitem
AND a.toitem = b.toitem
AND b.id < a.id)
WHERE a.viewed = 'true'
GROUP BY fromitem, toitem
)c
UNION
SELECT id FROM
(
SELECT max(d.id) id, d.fromitem, d.toitem
FROM STUFF_UNORDERED d
JOIN
(
SELECT max(b.id) id, b.fromitem, b.toitem
FROM STUFF_UNORDERED a
JOIN
(
SELECT id, fromitem, toitem
FROM STUFF_UNORDERED
WHERE viewed = 'true'
) b ON ( a.fromitem = b.fromitem
AND a.toitem = b.toitem
AND b.id < a.id)
WHERE a.viewed = 'true'
GROUP BY fromitem, toitem
) c ON ( d.fromitem = c.fromitem
AND d.toitem = c.toitem
AND d.id < c.id)
WHERE d.viewed='true'
GROUP BY d.fromitem, d.toitem
) e
UNION
SELECT id FROM STUFF_UNORDERED WHERE viewed='false'
)
ORDER BY viewed DESC,
least(fromitem, toitem),
greatest(fromitem, toitem),
id DESC
Fiddle: http://sqlfiddle.com/#!2/8c154/4/0