GROUP BY this OR that - php

I've got a web app that uses either Facebook ID or Contact # as an entrant identifier. This is because the client doesn't want those without Facebook to miss out on entering (so there's a web app as well as a Facebook app)
So I'm trying to:
SELECT *, COUNT(*)
FROM `entries_table`
WHERE is_daily_winner = 2
GROUP BY fbid OR contact_no
HAVING COUNT(*) <=4
Basically, what I'm trying to achieve is selecting entries with is_daily_winner = 2 is less than or equal to 4 rows per fbid or contact_no.
Currently I only get one row back, rather than the 3 I was expecting.
What am I doing wrong? or can I even GROUP BY x OR y ?
EDIT: I'm expanding this question, as I forgot to include another clause.
The HAVING COUNT(*) <=4 should be across all entries, but I only need to get those entered within the last day.
I've just tried:
SELECT * FROM `entries_table`
WHERE `timestamp` >= (CURDATE() - INTERVAL 1 DAY) IN
(SELECT * FROM `entries_table`
WHERE is_daily_winner = 2
GROUP BY fbid
HAVING COUNT(*) <=4
UNION
SELECT * FROM `entries_table`
WHERE is_daily_winner = 2
GROUP BY contact_no
HAVING COUNT(*) <= 4)
And I get the error Operand should contain 1 column(s)

This one should do what you want:
SELECT * FROM (
SELECT *, COUNT(*) AS nr FROM `entries_table`
WHERE is_daily_winner = 2
AND `timestamp` >= (CURDATE() - INTERVAL 1 DAY)
GROUP BY fbid
UNION ALL
SELECT *, COUNT(*) AS nr FROM `entries_table`
WHERE is_daily_winner = 2
AND `timestamp` >= (CURDATE() - INTERVAL 1 DAY)
GROUP BY contact_no
) sq
WHERE nr <= 4
I changed UNION to UNION ALL, because UNION implies a DISTINCT. I don't think, that's what you want (or even need, UNION ALL is also faster, because it doesn't have to check if there are duplicate rows).
And note, that selecting * and grouping by something is actually bad practice. By grouping and not having aggregate functions on the columns that are not mentioned in the group by, random rows are picked for each group to display. Just wanted to mention that.

You can Better use UNION like
SELECT *, COUNT(*)
FROM `entries_table`
WHERE is_daily_winner = 2
GROUP BY fbid
HAVING COUNT(*) <=4
UNION
SELECT *, COUNT(*)
FROM `entries_table`
WHERE is_daily_winner = 2
GROUP BY contact_no
HAVING COUNT(*) <=4

Related

0 When no row exist

I have following sql query
SELECT DATE_FORMAT(date,'%d.%m. %y') as date, COUNT(idStat) as number
FROM stat
WHERE IdGame = ? AND date >= ? AND date <= ?
GROUP BY date
It returns date and how much people visited game this day, but how to return 0, when no rows exist this day?
For example day 15.12.1993 does not exist in db, but user pick date between 15.10.1950 and 12.15.2020.
I want to return this non existing date 15.12.1993 but with count 0.
Is this even possible?
Thanks for help,
Filip.
The best way is to have a Calendar table handy with relevant dates. You can then use a left join to get the dates. Something like this:
SELECT DATE_FORMAT(c.date,'%d.%m. %y') as date, COUNT(s.idStat) as number
FROM Calendar c LEFT JOIN
stat s
ON c.date = s.date AND s.IdGame = ?
WHERE c.date >= ? AND c.date <= ?
GROUP BY c.date;
If you have games on every date but the problem is that the particular game is not one the day, you can use this short-cut:
SELECT DATE_FORMAT(date,'%d.%m. %y') as date, SUM(IdGame = ?) as number
FROM stat
WHERE date >= ? AND date <= ?
GROUP BY date;
This doesn't work in all cases, but it can be a useful short-cut.
I used RedFilter Answer to solve my problem, from this link: generate days from date range
My query now looks like this:
select DATE_FORMAT(a.Date,'%d.%m. %Y'), COUNT(stat.IdStat)
from (
select curdate() - INTERVAL (a.a + (10 * b.a) + (100 * c.a)) DAY as Date
from (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as a
cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as b
cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as c
) as a LEFT JOIN stat ON a.Date = stat.date
where a.Date between ? and ? AND (stat.IdGame = ? OR stat.IdGame IS NULL) GROUP BY a.Date
But I need remove future dates form my datapicker, because when i use futue date in this sql, no data will be return... I need set min and max of my datapicker.

Select results from table1 based on entries on table2

I have 2 tables;
banner_views (id, b_id, b_date)- this record a banner view every time it gets displayed
banners_dynamic (id, status, static_iname, static_keywords, static_url, static_alt, static_type, static_image, b_views, b_clicks) - stores the banner data
I would like to select 3 banners_dynamic results which have had the least views in the last 7 days.
I did put somethign together (see below) but I realised it was grabbing the total views for all banner rather than uniquely by id.
SELECT *,
(SELECT COUNT(*) FROM banner_views v WHERE v.b_date >= DATE(NOW()) - INTERVAL 7 DAY) as post_count
FROM banners_dynamic b
WHERE static_keywords LIKE '%test%' AND b.status='1' AND b.static_type='1'
ORDER BY post_count ASC LIMIT 3
Can anyone point me in the correct direction?
You must join both banners_dynamic table and your subquery with corresponding banner IDs:
SELECT
b.*, p.b_count
FROM
banners_dynamic b
INNER JOIN (
SELECT
b_id,
COUNT(*) AS b_count
FROM
banner_views v
WHERE
v.b_date >= DATE(NOW() - INTERVAL 7 DAY)
GROUP BY
b_id
) p on p.b_id = b.id
WHERE
b.static_keywords LIKE '%test%'
AND b.`status` = '1'
AND b.static_type = '1'
ORDER BY
p.b_count ASC
LIMIT 3
UPDATE: You can do it even without subquery:
SELECT
b.*, COUNT(v.b_id) AS b_count
FROM
banners_dynamic b
INNER JOIN banner_views v ON v.b_id = b.id
WHERE
v.b_date >= DATE_ADD(NOW(), INTERVAL - 7 DAY)
AND b.static_keywords LIKE '%test%'
AND b.`status` = '1'
AND b.static_type = '1'
GROUP BY
v.b_id
ORDER BY
b_count ASC
LIMIT 3;
If you want to include banners without any views (count=0) then you must do a LEFT JOIN:
SELECT
b.*, COUNT(v.b_id) AS b_count
FROM
banners_dynamic b
LEFT JOIN banner_views v ON v.b_id = b.id
AND v.b_date >= DATE_ADD(NOW(), INTERVAL - 7 DAY)
WHERE
b.static_keywords LIKE '%test%'
AND b.`status` = '1'
AND b.static_type = '1'
GROUP BY
v.b_id
ORDER BY
b_count ASC
LIMIT 3;

How break ties after getting rank

I have a query that gets me a users rank in a table of scores.
SELECT
*
FROM
(SELECT
*, #rank:=#rank + 1 rank
FROM
(SELECT
user_id, SUM(round_total) TotalPoints
FROM
sx14sp_mem_picks
GROUP BY user_id) s, (SELECT #rank:=0) init
ORDER BY TotalPoints DESC) r
WHERE
user_id = 22234
There is a problem with ties. I have a table field "pick_date" that i would like to use to break ties with. The user who made his picks first beats the tie.
Any ideas?
If sx14sp_mem_picks.pickdate is the field to break ties then in the order by sx14sp_mem_picks subquery, add
min( pickdate) asc
This will put the earliest pickdate first - you have to use MIN() bc you need to use an aggregate function given the use of "group by".
You need to order by the pick date in addition to the total points. However, you are talking about multiple rows per user. So, let's take the last pick date:
SELECT *
FROM (SELECT *, (#rank:=#rank + 1) as rank
FROM (SELECT user_id, SUM(round_total) as TotalPoints, max(pick_date) as max_pick_date
FROM sx14sp_mem_picks
GROUP BY user_id
) s CROSS JOIN
(SELECT #rank := 0) init
ORDER BY TotalPoints DESC, max_pick_date asc
) r
WHERE user_id = 22234;

Verify a specific and unique group of values in a group

I have a MySQL table with the following structure:
I want a query that would receive a group of uids (or a single uid) and then check for their existence in a closed group under a specific mid. If they exist, the query should return the mid under which they exist. For example in the table above:
('chuks.obima', 'crackhead') should return '2
('vweetah','crackhead') should return '1'
('vweetah','crackhead','chuks.obima') should return 3
('crackhead') should return an empty result
I think you need something like this:
SELECT mid
FROM your_table
WHERE uid in ('favour','crackhead','charisma')
GROUP BY mid
HAVING COUNT(*)=3
EDIT: based on your second example, this is what you are looking for:
SELECT mid
FROM your_table
WHERE uid in ('vweetah', 'crackhead')
GROUP BY mid
HAVING
COUNT(distinct uid)=
(select count(*)
from (select 'vweetah' union select 'crackhead') s)
or you can just substitute last subquery with the number of elements you are looking for, e.g. HAVING COUNT(distinct uid) = 2
EDIT2: now i understand exactly what you are looking for. This should give you the correct results:
SELECT your_table.mid, s.tot_count, count(distinct uid)
FROM
your_table inner join
(select mid, seq, count(distinct uid) tot_count from your_table group by mid, seq) s
on your_table.mid = s.mid and your_table.seq=s.seq
WHERE your_table.uid in ('crackhead')
GROUP BY your_table.mid
HAVING COUNT(distinct uid)=s.tot_count AND COUNT(distinct uid)=1
where the last count is equal to the number of elements you are looking for. This could be simplified like this:
SELECT your_table.mid
FROM your_table
GROUP BY your_table.mid
HAVING
count(distinct uid)=
count(distinct case when your_table.uid in ('vweetah','crackhead','chuks.obima') then your_table.uid end)
and count(distinct uid)=3
If the group is to considered closed if all uid are under the same seq, you also have to modify group by with: group by your_table.mid, your_table.seq and your select with SELECT distinct your_table.mid
To verify that it is a closed group, you can get the aggregate COUNT() of the total members of that mid group and compare it to the number of people in your list. If they are equal, it is closed.
The following would return a 1 if all 3 are in the group, and the total number of people in the group is also 3.
SELECT
(((SELECT COUNT(*) FROM yourtable WHERE `uid` IN ('favour','crackhead','charisma') AND `mid` = 2)
=
(SELECT COUNT(*) FROM yourtable WHERE `mid` = 2))
AND (SELECT COUNT(*) FROM yourtable WHERE `mid` = 2) = 3) AS group_is_closed
Wrap it in a subquery to avoid counting the mid twice.
SELECT
/* 3 is the number of uid you are looking for */
(mid_count = 3 AND mid_count = member_count) AS group_is_closed
FROM (
SELECT
/* Find how many of your uids are in the `mid` */
(SELECT COUNT(*) FROM yourtable WHERE `uid` IN ('favour','crackhead','charisma') AND `mid` = 2) AS member_count,
/* Find the total number of uids in the `mid` */
(SELECT COUNT(*) FROM yourtable WHERE `mid` = 2) AS mid_count
) subq
SQLFiddle demos (aka wow, it actually works):
Positive result (Only the 3 selected are in the mid, returns 1)
Negative result (A user not among the 3 is also in the mid, returns 0)
Negative result 2 (One of the 3 users is not in the mid, returns 0)
Try this:
SELECT mid
FROM your_table
WHERE uid in ('favour','crackhead','charisma')
GROUP BY mid
HAVING COUNT(DISTINCT uid) = 3

Limit the amount of results from mySQL conditionally?

Here is my query:
SELECT * FROM Photos WHERE Event_ID IN ($eventidstring)
I know I can limit the total amount of results from this query using LIMIT 5
I need the Limit the amount of results Per value in $eventidstring.
So if $eventidstring = 23,41,23*
*And there are 10 results WHERE Event_ID = 23, I want to limit this amount to 5. The same for all the other values in $eventidstring.
You may have some joy doing something similar to Oracle's RANK by PARITION in MySQL.
Sadly this feature is not available in MySQL though you can work around it using this method
Dump that in an inline view and then select those rows with rank <= 5;
Hence:
SELECT t.* FROM (
SELECT (
CASE Event_id
WHEN #curEventId
THEN #curRow := #curRow + 1
ELSE #curRow := 1 AND #curEventId := Event_Id END
) AS rank,
p.*
FROM Photos p INNER JOIN (SELECT #curRow := 0, #curEventId := '') r
ORDER BY p.Event_Id DESC
) t
WHERE t.rank <= 5 ORDER BY t.Event_Id asc;
Consider how you are going to 'choose' the top five by Event_Id too. You can always add in more after the ORDER BY p.Event_Id DESC to decide this.
I take it you're writing that query somewhere inside your PHP, so you need to split the $eventidstring into it's component values, form a SELECT for each and UNION all after the first one.
You sould do this with a loop of some sort, and concatenate the query strings in the loop...
If I understand correctly and you want to get five of each, you can use this:
(SELECT * FROM Photos WHERE Event_ID = 23 LIMIT 5)
UNION
(SELECT * FROM Photos WHERE Event_ID = 41 LIMIT 5)
UNION
(SELECT * FROM Photos WHERE Event_ID = ... LIMIT 5)
...
Maybe with a SELECT UNION but you need a new select for each value:
SELECT * FROM Photos WHERE Event_ID = 23 LIMIT 5
UNION SELECT * FROM Photos WHERE Event_ID = 41 LIMIT 5
UNION SELECT ...

Categories