Multiple Alias, one Column - mysql

I am trying to give multiple Aliases to the same column, basically, i want these two queries to be one:
SELECT name AS singlePeople FROM People
JOIN ID FROM Numbers
ON People.ID=Numbers.ID
WHERE People.isMarried=f;
SELECT name AS marriedPeople FROM People
JOIN ID FROM Numbers
ON People.ID=Numbers.ID
WHERE People.isMarried=t;
I want my results to look like:
singlePeople marriedPeople
------------- --------------
Bob Kelly John SMith
John Adams

Is this sufficient?
SELECT (CASE WHEN p.isMarried THEN 'Married' ELSE 'Single' END) as which,
name
FROM People p JOIN
Numbers n
ON p.ID = n.ID;
If not, you can do this with variables:
select max(case when not ismarried then name end) as single,
max(case when ismarried then name end) as married
from (select name, p.ismarried,
(#rn := if(#i = ismarried, #rn + 1,
if(#i := ismarried, 1, 1)
)
) as seqnum
from people p join
numbers n
on p.id = n.id cross join
(select #i := NULL, #rn := 0) params
order by ismarried
) pn
group by rn;

Related

MySQL Getting results for a specific user

I have the following query which works as expected.
SELECT T.*, #curRank := #curRank + 1 AS rank
FROM ( SELECT e.guid,
e.name,
(SELECT COUNT(ev.event_vote_id)
FROM event_vote ev
WHERE ev.event_uid = s.guid) AS votes
FROM event e
) as T
CROSS JOIN (SELECT #curRank := 0) r
ORDER BY votes DESC
It returns the guid, name, vote count and rank of all events.
However I want to make it specific to a certain user by linking the user_event table using something like the following:
JOIN user_event ON t.guid = ue.event_uid
WHERE ue.user_uid = 'abc123'
However i'm unsure on where to put this or if this.
I have the following query as a start but it returns exactly the opposite to expected i.e. every event not belonging to the user.
SELECT t.*
FROM user_event ue
JOIN ( SELECT e.guid,
e.name,
e.ownerId,
e.thumbnailSrc,
#curRank := #curRank + 1 AS rank,
( SELECT COUNT(ev.event_vote_id)
FROM event_vote ev
WHERE ev.event_uid = e.guid) AS votes
FROM event e, (SELECT #curRank := 0) r
) AS t
ON t.guid = ue.event_uid
WHERE ue.user_uid = 'abc123'
ORDER BY rank ASC
expected results
list of all of the events:
guid | name | votes | rank
def test2 2 1 (user1)
abc test1 1 2 (user2)
ghi test3 0 3 (user1)
jkl test4 0 4 (user3)
what the query should return for user 1 (user1 guid being abc123)
guid | name | votes | rank
def test2 2 1
ghi test3 0 3
Let's forget about rank for a moment, if you want only event from one specific user you do:
SELECT e.*, ue.*, ( ... ) as votes
FROM event e
JOIN user_event ue
ON e.guid = ue.event_uid
WHERE ue.user_uid = 'abc123'
And over that result you can do ranking.
SELECT T.*, #curRank := #curRank + 1 AS rank
FROM ( ... previous query ... ) as T
CROSS JOIN (SELECT #curRank := 0) r
ORDER BY votes DESC
EDIT:
to filter for a single user you need create another subquery.
calculate votes
calculate ranks
filter user
So query become:
SELECT *
FROM ( SELECT T.*, #curRank := #curRank + 1 AS rank
FROM ( SELECT e.*, ue.*, ( ... ) as votes
FROM event e
JOIN user_event ue
ON e.guid = ue.event_uid ) as T
CROSS JOIN (SELECT #curRank := 0) r
ORDER BY votes DESC
) as ranked_result
WHERE ranked_result.user_uid = 'abc123'

Show in just one row all records that have the same id.

Lets say I have a table of product price history, which is the price and product id, with the following records:
id price
1 23
2 14
2 23
2 20
3 30
3 40
what I want is to show the data grouped by id, showing the prices at which has been sold each product.
What i expectis something like this:
id priceA PriceB PriceC
1 23 NULL NULL
2 14 23 20
3 30 40 NULL
This is not the right way to do things
you should use a separate table and try some primary keys.
suppose you have a poductprice table with id and price
make a view like
CREATE VIEW history AS (
SELECT
id,
CASE WHEN id = "1" THEN price END AS priceA,
CASE WHEN id = "2" THEN price END AS priceB,
CASE WHEN id = "3" THEN price END AS priceC
FROM productprice
);
SELECT * FROM history;
This requirement really a bad fit for SQL, but it can be achieved with a lot of fiddling involving "dynamic sql" and fudges to achieve te equivalent of row_number(). i.e. It would be easier to achieve with CTE and row_number() perhaps if MySQL gets bith this could be revisited.
Anyway, what is required is getting the prices into numbered columns, so the first price of each product goes in the first column, the second price in the second column and so on. So we need in the first instance a way to number the rows which will later be transformed into columns. In MySQL this can be done by using variables, like this:
select
#row_num := IF(#prev_value = p.id, #row_num+1, 1) AS RowNumber
, id
, price
, #prev_value := p.id
from (select distinct id, price from pricehistory) p
CROSS JOIN ( SELECT #row_num :=1, #prev_value :='' ) vars
order by id, price
So that snippet is used twice in the following. In the upper part it forms a set of case expressions that will do the transformation. I the lower part we combine those case expressions with the remainder of the wanted sql and then execute it.
set #sql = (
SELECT GROUP_CONCAT(col_ref)
FROM (
select distinct
concat(' max(case when RowNumber=',RowNumber,' then Price else NULL end) as c',RowNumber) col_ref
from (
select
#row_num := IF(#prev_value = p.id, #row_num+1, 1) AS RowNumber
, id
, price
, #prev_value := p.id
from (select distinct id, price from pricehistory) p
CROSS JOIN ( SELECT #row_num :=1, #prev_value :='' ) vars
order by id, price
) d
order by `RowNumber`
) dc
);
set #sql = concat('select id,', #sql,
' from (
select
#row_num := IF(#prev_value = p.id, #row_num+1, 1) AS RowNumber
, id
, price
, #prev_value := p.id
from (select distinct id, price from pricehistory) p
CROSS JOIN ( SELECT #row_num :=1, #prev_value :='''' ) vars
order by id, price
) d
Group By `id`');
#select #sql
PREPARE stmt FROM #sql;
EXECUTE stmt;
\\
The result of this, based on the sample given is:
id c1 c2 c3
1 1 23 NULL NULL
2 2 14 20 23
3 3 30 40 NULL
This solution can be tested and re-run at: http://rextester.com/AYAA36866
Note the fully generated sql reads like this:
select id
, max(case when RowNumber=1 then Price else NULL end) as c1
, max(case when RowNumber=2 then Price else NULL end) as c2
, max(case when RowNumber=3 then Price else NULL end) as c3
from (
select
#row_num := IF(#prev_value = p.id, #row_num+1, 1) AS RowNumber
, id
, price
, #prev_value := p.id
from (select distinct id, price from pricehistory) p
CROSS JOIN ( SELECT #row_num :=1, #prev_value :='' ) vars
order by id, price
) d
Group By `id`
You might want something like this:
SELECT id, GROUP_CONCAT(string SEPARATOR ' ') FROM priceHistory GROUP BY id;

Select first N messages each user receives

I have a table that stores messages sent to users, the layout is as follows
id (auto-incrementing) | message_id | user_id | datetime_sent
I'm trying to find the first N message_id's that each user has received, but am completely stuck. I can do it easily on a per-user basis (when defining the user ID in the query), but not for all users.
Things to note:
Many users can get the same message_id
Message ID's aren't sent sequentially (i.e. we can send message 400 before message 200)
This is a read only mySQL database
EDIT: On second thought I removed this bit but have added it back in since someone was kind enough to work on it
The end goal is to see what % of users opened one of the first N messages they received.
That table of opens looks like this:
user_id | message_id | datetime_opened
This is an untested answer to the original question (with 2 tables and condition on first 5):
SELECT DISTINCT user_id
FROM (
SELECT om.user_id,
om.message_id,
count(DISTINCT sm2.message_id) messages_before
FROM opened_messages om
INNER JOIN sent_messages sm
ON om.user_id = sm.user_id
AND om.message_id = sm.message_id
LEFT JOIN sent_messages sm2
ON om.user_id = sm2.user_id
AND sm2.datetime_sent < sm.datetime_sent
GROUP BY om.user_id,
om.message_id
HAVING messages_before < 5
) AS base
The subquery joins in sm2 to count the number of preceding messages that were sent to the same user, and then the having clause makes sure that there are fewer than 5 earlier messages sent. As for the same user there might be multiple messages (up to 5) with that condition, the outer query only lists the unique users that comply to the condition.
To get the first N (here 2) messages, try
SELECT
user_id
, message_id
FROM (
SELECT
user_id
, message_id
, id
, (CASE WHEN #user_id != user_id THEN #rank := 1 ELSE #rank := #rank + 1 END) AS rank,
(CASE WHEN #user_id != user_id THEN #user_id := user_id ELSE #user_id END) AS _
FROM (SELECT * FROM MessageSent ORDER BY user_id, id) T
JOIN (SELECT #cnt := 0) c
JOIN (SELECT #user_id := 0) u
) R
WHERE rank < 3
ORDER BY user_id, id
;
which uses a RANK substitute, derived from #Seaux response to Does mysql have the equivalent of Oracle's “analytic functions”?
To extend this to your original question, just add the appropriate calculation:
SELECT
COUNT(DISTINCT MO.user_id) * 100 /
(SELECT COUNT(DISTINCT user_id)
FROM (
SELECT
user_id
, message_id
, id
, (CASE WHEN #user_id != user_id THEN #rank := 1 ELSE #rank := #rank + 1 END) AS rank,
(CASE WHEN #user_id != user_id THEN #user_id := user_id ELSE #user_id END) AS _
FROM (SELECT * FROM MessageSent ORDER BY user_id, id) T
JOIN (SELECT #cnt := 0) c
JOIN (SELECT #user_id := 0) u
) R2
WHERE rank < 3
) AS percentage_who_read_one_of_the_first_messages
FROM MessageOpened MO
JOIN
(SELECT
user_id
, message_id
FROM (
SELECT
user_id
, message_id
, id
, (CASE WHEN #user_id != user_id THEN #rank := 1 ELSE #rank := #rank + 1 END) AS rank,
(CASE WHEN #user_id != user_id THEN #user_id := user_id ELSE #user_id END) AS _
FROM (SELECT * FROM MessageSent ORDER BY user_id, id) T
JOIN (SELECT #cnt := 0) c
JOIN (SELECT #user_id := 0) u
) R
WHERE rank < 3) MR
ON MO.user_id = MR.user_id
AND MO.message_id = MR.message_id
;
With no CTEs in MySQL, and being in a read-only database - I see no way around having the above query twice in the statement.
See it in action: SQL Fiddle.
Please comment if and as this requires adjustment / further detail.

What's the SQL idiom for zipping — in the functional sense — two queries?

For example, if I have a set of classes and a set of classrooms, and I want to pair the two up with some arbitrary pairing:
> SELECT class_name FROM classes ORDER BY class_name
Calculus
English
History
> SELECT room_name FROM classrooms ORDER BY room_name
Room 101
Room 102
Room 201
I'd like to "zip" them like this:
> SELECT class_name FROM classes ORDER … ZIP SELECT room_name FROM classrooms ORDER …
Calculus | Room 101
English | Room 102
History | Room 201
Currently I'm dealing with MySQL… but possibly — optimistically? — there is a reasonably standards compliant way to do this?
One way to do it in MySql
SELECT c.class_name, r.room_name
FROM
(
SELECT class_name, #n := #n + 1 rnum
FROM classes CROSS JOIN (SELECT #n := 0) i
ORDER BY class_name
) c JOIN
(
SELECT room_name, #m := #m + 1 rnum
FROM classrooms CROSS JOIN (SELECT #m := 0) i
ORDER BY room_name
) r
ON c.rnum = r.rnum
Output:
| CLASS_NAME | ROOM_NAME |
-------------|-----------|
| Calculus | Room 101 |
| English | Room 102 |
| History | Room 201 |
Here is SQLFIddle demo
Same thing in Postgres will look like
SELECT c.class_name, r.room_name
FROM
(
SELECT class_name,
ROW_NUMBER() OVER (ORDER BY class_name) rnum
FROM classes
) c JOIN
(
SELECT room_name,
ROW_NUMBER() OVER (ORDER BY room_name) rnum
FROM classrooms
) r
ON c.rnum = r.rnum
Here is SQLFiddle demo
And in SQLite
SELECT c.class_name, r.room_name
FROM
(
SELECT class_name,
(SELECT COUNT(*)
FROM classes
WHERE c.class_name >= class_name) rnum
FROM classes c
) c JOIN
(
SELECT room_name,
(SELECT COUNT(*)
FROM classrooms
WHERE r.room_name >= room_name) rnum
FROM classrooms r
) r
ON c.rnum = r.rnum
Here is SQLFiddle demo
This is a form of join, but you need to create the join key. Alas, though, this requires a full outer join, because you do not know which list is longer.
So, you can do this by using variables to enumerate the rows and then using union all and group by to get the values:
select max(case when which = 'class' then name end) as class_name,
max(case when which = 'room' then name end) as room_name
from ((SELECT class_name as name, #rnc := #rnc + 1 as rn, 'class' as which
FROM classes cross join
(select #rnc := 0) const
ORDER BY class_name
) union all
(select room_name, #rnr := #rnr + 1 as rn, 'room'
from classrooms cross join
(select #rnr := 0) const
ORDER BY room_name
)
) t
group by rn;

Greatest n-per-group With Multiple Joins

Evening,
I am trying to get an output of rows that are limited to n per group in MySQL. I can get it to work without joins, but with it I am just shy. I've pasted a dump of the relevant tables here:
http://pastebin.com/6F0v1jhZ
The query I am using is:
SELECT
title, catRef, RowNum, pCat, tog
FROM
(
SELECT
title, catRef,
#num := IF(#prevCat=catRef,#num+1,1) AS RowNum,
#prevCat AS tog,
#prevCat := catRef AS pCat
FROM (select #prevCat:=null) AS initvars
CROSS JOIN
(
SELECT p.title, oi.catRef
FROM resources p
INNER JOIN placesRel v ON (p.resId = v.refId)
INNER JOIN catRel oi ON (p.resId = oi.refId)
WHERE p.status = 'live' AND v.type = 'res' AND oi.type = 'res'
) AS T
) AS U
WHERE RowNum <= 5
ORDER BY catRef
I just can't get the row count to go up. Or any other solution would be greatly appreciated.
I'm looking for a result like this:
title catRef RowNum
Title1 1 1
Title2 1 2
Title3 1 3
Title4 2 1
Title5 2 2
Title6 3 1
At the moment, the RowNum column is always 1.
This works:
SET #num := 1, #prevCat := 0;
SELECT title, start, end, type, description, linkOut, outType, catRef, row_number
FROM (
SELECT title, start, end, type, description, linkOut, outType, catRef,
#num := if(#prevCat = catRef, #num + 1, 1) as row_number,
#prevCat AS tog,
#prevCat := catRef AS dummy
FROM (
SELECT title, start, end, resources.type, description, linkOut, outType, catRef
FROM resources LEFT JOIN placesRel ON placesRel.refId = resId LEFT JOIN catRel ON catRel.refId = resId
WHERE status = 'live' AND placesRel.type = 'res' AND catRel.type = 'res'
ORDER BY catRef
) AS w
) AS x WHERE x.row_number <= 4;
You need to put your joined query in a sub-query and order it by the column you want to group by. Use it's parent query to add row numbers. Then, the top-level query glues it all together.
If you don't put your joined query in it's own sub-query, the results won't be ordered as you wish, but instead will come out in the order they are in the database. This means the data is not grouped, so row numbers will no be applied to ordered rows.