Вопрос

Background info

System

OS:

bash$ lsb_release -a
No LSB modules are available.
Distributor ID: Debian
Description:    Debian GNU/Linux 6.0.5 (squeeze)
Release:        6.0.5
Codename:       squeeze

MySQL:

bash$ mysql --version
mysql  Ver 14.14 Distrib 5.1.63, for debian-linux-gnu (x86_64) using readline 6.1

Database

Engine: MyISAM

Table: post_votes

mysql> describe post_votes;
+-----------+---------------------+------+-----+---------+----------------+
| Field     | Type                | Null | Key | Default | Extra          |
+-----------+---------------------+------+-----+---------+----------------+
| id        | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| post_uuid | binary(16)          | YES  |     | NULL    |                |
| user_uuid | binary(16)          | YES  |     | NULL    |                |
| vote      | tinyint(4)          | YES  |     | 0       |                |
+-----------+---------------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)

Table: posts

mysql> describe posts;
+-------------+---------------------+------+-----+----------------------+----------------+
| Field       | Type                | Null | Key | Default              | Extra          |
+-------------+---------------------+------+-----+----------------------+----------------+
| id          | bigint(20) unsigned | NO   | PRI | NULL                 | auto_increment |
| post_uuid   | binary(16)          | YES  | UNI | NULL                 |                |
| owner_uuid  | binary(16)          | YES  |     | NULL                 |                |
| created     | int(10) unsigned    | YES  |     | NULL                 |                |
| edited      | int(10) unsigned    | YES  |     | NULL                 |                |
| title       | varchar(50)         | YES  |     | NULL                 |                |
| description | varchar(150)        | YES  |     | NULL                 |                |
| link        | varchar(100)        | YES  |     | NULL                 |                |
| properties  | varchar(20)         | YES  |     | 00000000000000000000 |                |
+-------------+---------------------+------+-----+----------------------+----------------+
9 rows in set (0.00 sec)

The Problem

I want to select posts depending on their upvote/downvote ratio. I've come up with a query that counts upvotes and downvotes but can't seem to join it to the actual posts.

The working (uncomplete) query

(
    SELECT COUNT(*), post_uuid, 'upvotes' AS 'mode'
    FROM post_votes a
    WHERE a.vote = 1
    GROUP BY post_uuid
)
UNION
(
    SELECT COUNT(*), post_uuid, 'downvotes' AS 'mode'
    FROM post_votes a
    WHERE a.vote = -1
    GROUP BY post_uuid
)

The broken query

(
    (
        SELECT COUNT(*), post_uuid, 'upvotes' AS 'mode'
        FROM post_votes a
        WHERE a.vote = 1
        GROUP BY post_uuid
    )
    UNION
    (
        SELECT COUNT(*), post_uuid, 'downvotes' AS 'mode'
        FROM post_votes a
        WHERE a.vote = -1
        GROUP BY post_uuid
    )
)
a
LEFT JOIN posts b ON a.post_uuid = b.post_uuid

What am I doing wrong? the error message I get is:

ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that
corresponds to your MySQL server version for the right syntax to use near 'UNION
(
SELECT COUNT(*), post_uuid, 'downvotes' AS 'mode'
Это было полезно?

Решение

The UNION pair is effectively a derived table and must appear in a FROM clause to participate in the JOIN. Since it is in the FROM clause now, you must SELECT its columns, otherwise your query has no SELECT clause at all. MySQL's syntax is lenient, but not so lenient as to permit a missing SELECT clause.

/* SELECT the cols produced by the UNION */
SELECT 
   a.`num`, 
   a.`post_uuid`,
   a.`mode`,
  /* And other columns from `posts` if needed */
   b.`something`,
   b.`something_else`,
   b.`some_other_thing`
/* UNIONs are a derived table, belonging in FROM */
FROM (
    (
        SELECT COUNT(*) as num, post_uuid, 'upvotes' AS 'mode'
        FROM post_votes a
        WHERE a.vote = 1
        GROUP BY post_uuid
    )
    UNION
    (
        SELECT COUNT(*) num, post_uuid, 'downvotes' AS 'mode'
        FROM post_votes a
        WHERE a.vote = 1
        GROUP BY post_uuid
    )
) a
LEFT JOIN posts b ON a.post_uuid = b.post_uuid

Looking over this, I believe you can simplify away the UNION by using a SUM(CASE) aggregate pattern:

SELECT 
  b.post_uuid,
  SUM(CASE WHEN a.vote = 1 THEN 1 ELSE 0 END) AS upvotes,
  /* Assume -1 for downvotes? */
  SUM(CASE WHEN a.vote = -1 THEN 1 ELSE 0 END) AS downvotes
FROM
  posts b
  LEFT JOIN post_votes a ON a.post_uuid = b.post_uuid
GROUP BY b.post_uuid
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top