MySQL Limit Rows / Group By - mysql

I have searched a lot of different posts about this and i didn't find something that works for me.
This Using LIMIT within GROUP BY to get N results per group? and http://www.sqlines.com/mysql/how-to/get_top_n_each_group didn't work for me.
I have a simple MySQL Table with the following columns
id , package, user_id, date
I want to perform a query in which i will get
X Rows / user_id WHERE date > Number Order By date ASC
In short, we want to perform a Group By user_id with LIMIT of X rows / group but have in mind the statement using the date column
Example Full Table:
id , package, user_id, date
1, full, 1 , 1447003159
2, full, 1 , 1447003055
3, full, 1 , 1447002022
4, full, 1 , 1447001013
5, full, 1 , 1447000031
6, mid, 2 , 1447003159
7, mid, 2 , 1447003055
8, mid, 2 , 1447002022
9, mid, 2 , 1447001013
10, mid, 2 , 1447000031
From the above table we want to Select only 2 rows / user_id but where date >= 1447000031 (But make ORDER BY date ASC first)
Expected Output:
4, full, 1 , 1447001013
3, full, 1 , 1447002022
9, mid, 2 , 1447001013
8, mid, 2 , 1447002022

E.g.:
SELECT x.*
FROM my_table x
JOIN my_table y
ON y.id >= x.id
AND y.user_id = x.user_id
WHERE y.date > 1447000031
GROUP
BY x.id
HAVING COUNT(*) <= 2;
or, faster, but more typing...
SELECT id
, package
, user_id
, date
FROM
( SELECT x.*
, CASE WHEN #prev_user = user_id THEN #i:=#i+1 ELSE #i:=1 END rank
, #prev_user := user_id
FROM my_table x
, ( SELECT #prev_user = 0,#i:=1 ) vars
WHERE date > 1447000031
ORDER
BY user_id
, date
) a
WHERE rank <= 2;

Related

Select multiple columns by date, count occurrence for each column

I am trying to get this query...
SELECT `Num_1`, COUNT(`Num_1`) AS `value_occurrence` FROM numbers WHERE MONTH(`Dates`) = 1 AND YEAR(`Dates`) = 1995 GROUP BY `Num_1` ORDER BY `value_occurrence` DESC
But for multiple columns as in 'Num_1', 'Num_2', 'Num_3', 'Num_4', 'Num_5' and return with the occurrence of each column such as 'num_1_occurrence', 'num_2_occurrence', 'num_3_occurrence', 'num_4_occurrence', 'num_5_occurrence' and all within the date specified.
numbers table
Example output
I had tried using...
SELECT `Num_1`,`Num_2`, `Num_3`,`Num_4`,`Num_5`,COUNT(`Num_1`,`Num_2`,`Num_3`,`Num_4`,`Num_5`) AS `num_1_occurrence`,`num_2_occurrence`,`num_3_occurrence`, `num_4_occurrence`,`num_5_occurrence`FROM numbers WHERE MONTH(`Dates`) = 1 AND YEAR(`Dates`) = 1995
but just threw errors, I have searched extensively for days and have not found the correct way to do it.
I'd do it like this:
SELECT n.num
, MAX(IF(n.q='n1',n.cnt,NULL)) AS num_1_occurrence
, MAX(IF(n.q='n2',n.cnt,NULL)) AS num_2_occurrence
, MAX(IF(n.q='n3',n.cnt,NULL)) AS num_3_occurrence
FROM (
SELECT 'n1' AS q
, n1.Num_1 AS num
, COUNT(n1.Num_1) AS cnt
FROM numbers n1
WHERE n1.Dates >= '1995-01-01'
AND n1.Dates < '1995-01-01' + INTERVAL 1 MONTH
GROUP BY n1.Num_1
UNION ALL
SELECT 'n2' AS q
, n2.Num_2 AS num
, COUNT(n2.Num_2) AS cnt
FROM numbers n2
WHERE n2.Dates >= '1995-01-01'
AND n2.Dates < '1995-01-01' + INTERVAL 1 MONTH
GROUP BY n2.Num_2
UNION ALL
SELECT 'n3' AS q
, n3.Num_3 AS num
, COUNT(n3.Num_3) AS cnt
FROM numbers n3
WHERE n3.Dates >= '1995-01-01'
AND n3.Dates < '1995-01-01' + INTERVAL 1 MONTH
GROUP BY n3.Num_3
) n
GROUP BY n.num
ORDER BY GREATEST(num_1_occurrence,num_2_occurrence,num_3_occurrence) DESC
I'm only doing Num_1 and Num_2 here, but I think it's what you're looking for, or close. This will give you the list in "tall" format, with the original column name, the value in that column, and the count of that value in that column going across...
SELECT 'Num_1' AS field_name, Num_1 AS value, value_count
FROM (SELECT Num_1, COUNT(Num_1) AS value_count
FROM numbers
GROUP BY Num_1) AS num1_counts
WHERE MONTH(`Dates`) = 1 AND YEAR(`Dates`) = 1995
UNION
SELECT 'Num_2' AS field_name, Num_2 AS value, value_count
FROM (SELECT Num_2, COUNT(Num_2) AS value_count
FROM numbers
GROUP BY Num_2) AS num2_counts
WHERE MONTH(`Dates`) = 1 AND YEAR(`Dates`) = 1995

SELECT CASE to return accumulative results

I'm trying to create a query using SELECT CASE that would return the accumulative results.
Here is my query, it works but doesnt return the correct result.
SELECT total, count(*) as count FROM
(
SELECT case
WHEN ( X ) < 1 THEN '1 km'
WHEN ( X ) < 3 THEN '3 km'
WHEN ( X ) < 5 THEN '5 km'
WHEN ( X ) < 10 THEN '10 km'
WHEN ( X ) < 15 THEN '15 km'
WHEN ( X ) < 25 THEN '25 km'
WHEN ( X ) < 50 THEN '50 km'
WHEN ( X ) < 100 THEN '100 km'
WHEN ( X ) > 0 THEN '-1'
else '-2'
end AS `total`
FROM `store` AS d WHERE d.pending!='1'
) AS someRandomAliasHere
GROUP BY `total`
X is a formula i'm using to calculate radius from a lat and lang. total is NOT a column in my database table, just a result to calculations of X
The query above gives me this..
1 km (4)
3 km (19)
5 km (103)
25 km (540)
50 km (61)
....
4,19,103,540,62 are the total matches found.
The total count for 3 should be 19+4=23.
5 should be 103+19+4=122 etc. And WHEN ( X ) > 0 THEN '-1' should show the total count. of matches
I tried using BETWEEN 0 AND 1, BETWEEN 0 AND 3 etc but it still didn't give me the correct results
Any ideas?
Another approach is to calculate the results independently then union them:
SELECT 1 AS total, COUNT(*) AS cnt FROM `store` WHERE store.pending != 1 AND ( X ) < 1
UNION ALL
SELECT 3 AS total, COUNT(*) AS cnt FROM `store` WHERE store.pending != 1 AND ( X ) < 3
UNION ALL
SELECT 5 AS total, COUNT(*) AS cnt FROM `store` WHERE store.pending != 1 AND ( X ) < 5
UNION ALL
/** ... **/
SELECT 100 AS total, COUNT(*) AS cnt FROM `store` WHERE store.pending != 1 AND ( X ) < 100
In addition to the accumulation, you also want a total value at the end with -1. This is a bit of a pain, but it can be accomplished.
The simplest way to do cumulative sums in MySQL is using variables. The basic idea is this:
SELECT total, cnt, (#cnt := #cnt + cnt) as count
FROM (SELECT (case WHEN ( X ) < 1 THEN '1'
WHEN ( X ) < 3 THEN '3'
WHEN ( X ) < 5 THEN '5'
WHEN ( X ) < 10 THEN '10'
WHEN ( X ) < 15 THEN '15'
WHEN ( X ) < 25 THEN '25'
WHEN ( X ) < 50 THEN '50'
WHEN ( X ) < 100 THEN '100'
WHEN ( X ) > 0 THEN '-1'
else '-2'
end) AS total, COUNT(*) as cnt
FROM store s
WHERE s.pending <> '1'
GROUP BY total
) t CROSS JOIN
(SELECT #cnt := 0) vars
ORDER BY total;
The issue with this is that you will not get an overall total of the non-negative values. Let me assume that you have no negative values. This requires adding another row into the total line:
SELECT total, cnt, (#cnt := #cnt + cnt) as count
FROM (SELECT (case WHEN ( X ) < 1 THEN '1'
WHEN ( X ) < 3 THEN '3'
WHEN ( X ) < 5 THEN '5'
WHEN ( X ) < 10 THEN '10'
WHEN ( X ) < 15 THEN '15'
WHEN ( X ) < 25 THEN '25'
WHEN ( X ) < 50 THEN '50'
WHEN ( X ) < 100 THEN '100'
WHEN ( X ) > 0 THEN '-1'
else '-2'
end) AS total, COUNT(*) as cnt
FROM store s
WHERE s.pending <> '1'
GROUP BY `total`
UNION ALL
SELECT -1, 0
) t CROSS JOIN
(SELECT #cnt := 0) vars
ORDER BY (total >= 0) desc, total;
I've changed the order by as well. Note that the value -2 is probably meaningless, because X < 1 and X > 0 cover all possible values of X (except for NULL). If you actually have values 100 or greater, there are some small changes to refine the query. You do not describe what to do with those values, so clarification on the question would be helpful.
Not sure if this works since I don't have a database to test this on. Also not exactly in the format you want.
select sum(if(X<1,1,0)) as C1,
sum(if(X<3,1,0)) as C3,
sum(if(X<5,1,0)) as C5,
sum(if(X<10,1,0)) as C10,
sum(if(X<15,1,0)) as C15,
sum(if(X<25,1,0)) as C25,
sum(if(X<50,1,0)) as C50,
sum(if(X<100,1,0)) as C100,
sum(if(X>=100,1,0)) as C100P
from store
where store.pending != '1'
Unfortunatelly MySQL do not have analytical functions and windowed functions, but in this case, you can achieve your goal using a variable and a nested subquery:
SELECT
total,
cnt,
#rollupCount:=#rollupCount+cnt AS rollupCount
FROM
(
SELECT
total,
count(*) AS cnt
FROM
(
SELECT
CASE
WHEN ( X ) < 1 THEN '1'
WHEN ( X ) < 3 THEN '3'
WHEN ( X ) < 5 THEN '5'
WHEN ( X ) < 10 THEN '10'
WHEN ( X ) < 15 THEN '15'
WHEN ( X ) < 25 THEN '25'
WHEN ( X ) < 50 THEN '50'
WHEN ( X ) < 100 THEN '100'
WHEN ( X ) > 0 THEN '-1'
ELSE '-2'
END AS `total`
FROM
`store` AS d
WHERE
d.pending != '1'
) AS someRandomAliasHere
GROUP BY
`total`
) AS anotherRandomAliasHere
, (SELECT #rollupCount:=0) AS RC
ORDER BY
total ASC
This is the same as when you want to calculate the row number for each record:
SELECT
#rowNumber:=#rowNumber+1 AS rowNumber,
sourceColumns
FROM
sourceTable, (SELECT #rowNumber:=0) AS t
ORDER BY
orderColumn;
A column to give cumulative totals from an existing query can be created relatively simply:
SELECT X,
total,
(SELECT SUM(total)
FROM (<<<your_current_query>>>) ycq2
WHERE ycq2.X <= ycq1.X) `cumulative_total`
FROM (<<<your_current_query>>>) ycq1
Of course this will expand quite a lot when pasting your current query in the two marked places.
See SQL fiddle demo.
try this:
/sql server version/
DECLARE #GROUPS TABLE (TOTAL INT)
INSERT INTO #GROUPS
VALUES (1),
(3),
(5),
(10),
(15),
(25),
(50),
(100),
(-1)
SELECT a.TOTAL, z.[COUNT] FROM #GROUPS a
CROSS APPLY
(SELECT COUNT(*) as [COUNT] FROM store x WHERE CASE WHEN a.TOTAL = -1 THEN -x.X ELSE x.X END < REPLACE(a.TOTAL,-1,0)
AND pending != 1) z
/mysql version/
CREATE TEMPORARY TABLE GROUPS TABLE (TOTAL INT)
INSERT INTO GROUPS
VALUES (1),
(3),
(5),
(10),
(15),
(25),
(50),
(100),
(-1)
SELECT a.TOTAL, z.[COUNT] FROM GROUPS a
CROSS APPLY
(SELECT COUNT(*) as [COUNT] FROM store x WHERE CASE WHEN a.TOTAL = -1 THEN -x.X ELSE x.X END < REPLACE(a.TOTAL,-1,0)
AND pending != 1) z
try this one :
1.) value after "THEN" must be numeric
2.) make temporary table with running id (example for SQL SERVER)
SELECT identity(int, 1, 1) as id,
case
WHEN ( X ) < 1 THEN 1
WHEN ( X ) < 3 THEN 3
WHEN ( X ) < 5 THEN 5
WHEN ( X ) < 10 THEN 10
WHEN ( X ) < 15 THEN 15
WHEN ( X ) < 25 THEN 25
WHEN ( X ) < 50 THEN 50
WHEN ( X ) < 100 THEN 100
WHEN ( X ) > 0 THEN -1
else '-2' end AS total
Into storeID
FROM store AS d
WHERE d.pending!='1'
Order BY total
3.) join table with same table, with criteria
Select a.*, sum(b.total) as NewTotal
From storeID a
Left Join storeID b
On b.id <= a.id
Group By a.id, a.total
4.) i think, "NewTotal" is what you are looking for
Your question is a bit hard to understand. Is this what you want?
CREATE TABLE Totals (X INT, SUM INT);
INSERT INTO Totals VALUES
(1, 4),
(3, 19),
(5, 103),
(25, 540),
(50, 61)
SELECT first.X
, Sum(second.SUM)
FROM Totals first
JOIN Totals second
ON first.x >= second.x
GROUP BY first.X
UNION
SELECT 0, SUM(sum) * 2
FROM Totals
http://sqlfiddle.com/#!3/65665/12
I suppose that you X function returns a floating point number. If I understand your logic correctly, you want to group together values where X is >=0 and <1, where X >=0 and <3, >=0 and <5 and so on, and you want to return -1 when the value is >=0, and -2 when the value is a negative <0 number.
I would use an intervals table, defined like this:
CREATE TABLE intervals (
i_begin INT,
i_end INT,
i_value INT
);
INSERT INTO intervals VALUES
(0, 1, 1),
(0, 3, 3),
(0, 5, 5),
(0, 10, 10),
(0, 15, 15),
(0, 25, 25),
(0, 50, 50),
(0, 100, 100),
(0, null, -1),
(null, 0, -2);
or you can play with values in this table to make it suite your needs.
then you can just use an INNER JOIN and a GROUP BY query:
SELECT
i_value, COUNT(*)
FROM
store INNER JOIN intervals
ON ((i_begin IS NULL OR X>=i_begin) AND (i_end IS NULL OR X<i_end))
WHERE
store.pending<>1
GROUP BY
i_value
Please see an example here.

select from sql database with avg function

I have a database as shown below
id , name , var1
and I want to write a sql query like this:
select name
from table
where last var1 > avg of var1 s of each name
Notice that i want to select between names that last var1 is greater than avrage of var1s of each name
i write this code :
select name
from table
where var1>(select avg(var1) from table ) limit 0 , 1
but this code gets avrage from all var1s and I dont know whether this works or not!
for example we have these data:
1 , John , 32
2 , John , 21
3 , Mike , 22
4 , John , 11
5 , Mike , 5
6 , Mike , 45
=> for John , we have: 32+21+11 /3 =21.3 , but the last data is 11 , so John shouldnt be chosen
=> for Mike , avrage of var1 is 24 , and last row for Mike is 45 that is greater than the avrage , so Mike should be chosen.
Can anyone help me?
SELECT y.* FROM
your_table y
JOIN
(
SELECT name, AVG(var1) AS av, MAX(id) AS mx
FROM your_table
GROUP BY name
) tab
ON y.name = tab.name
AND y.id = tab.mx
AND y.var1 > tab.av
Here is the code at SQL Fiddle
[EDIT]:
Based on your latest requirement, what you want to accomplish is LIMIT N within group, which can be done with the following query:
SET #N := 2;
SELECT * FROM
(
SELECT (#rownumber:= #rownumber + 1) AS rn, yt.*
FROM your_table yt,(SELECT #rownumber:= 0) nums
ORDER BY name, id
) k
JOIN
(
SELECT t.name, MAX(rn) AS MaxRN FROM
(
SELECT (#rownumber:= #rownumber + 1) AS rn, yt.*
FROM your_table yt,(SELECT #rownumber:= 0) nums
ORDER BY name, id
) t
GROUP BY name
) l
ON k.rn <= l.MaxRN AND k.rn > l.MaxRN - #N
Here #N variable holds number of records we want to select within each group
Check the code at SQL Fiddle
Now in an outer query we can take the avg of the resultset returned by above.
Let me know if you could accomplish what you wanted with my inputs.

Trying to use ID in MySQL SubSubQuery

So I'll show you what I'm trying to do and explain my problem, there may be an answer different to the approach I'm trying to take.
The query I'm trying to perform is as follows:
SELECT *
FROM report_keywords rk
WHERE rk.report_id = 231
AND (
SELECT SUM(t.conv) FROM (
SELECT conv FROM report_keywords t2 WHERE t2.campaign_id = rk.campaign_id ORDER BY conv DESC LIMIT 10
) t
) >= 30
GROUP BY rk.campaign_id
The error I get is
Unknown column 'rk.campaign_id' in 'where clause'
Obviously this is saying that the table alias rk is not making it to the subsubquery. What I'm trying to do is get all of the campaigns where the sum of the top 10 conversions is greater than or equal to 30.
The relevant table structure is:
id INT,
report_id INT,
campaign_id INT,
conv INT
Any help would be greatly appreciated.
Update
Thanks to Kickstart I was able to do what I wanted. Here's my final query:
SELECT campaign_id, SUM(conv) as sum_conv
FROM (
SELECT campaign_id, conv, #Sequence := if(campaign_id = #campaign_id, #Sequence + 1, 1) AS aSequence, #campaign_id := campaign_id
FROM report_keywords
CROSS JOIN (SELECT #Sequence := 0, #campaign_id := 0) Sub1
WHERE report_id = 231
ORDER BY campaign_id, conv DESC
) t
WHERE aSequence <= 10
GROUP BY campaign_id
HAVING sum_conv >= 30
Possibly use a user variable to add a sequence number to get the latest 10 records for each one, then use SUM to get the count of those.
Something like this:-
SELECT rk.*
FROM report_keywords rk
INNER JOIN
(
SELECT campaign_id, SUM(conv) AS SumConv
FROM
(
SELECT campaign_id, conv, #Sequence := if(campaign_id = #campaign_id, #Sequence + 1, 1) AS aSequence, #campaign_id := campaign_id
FROM report_keywords
CROSS JOIN (SELECT #Sequence := 0, #campaign_id := "") Sub1
ORDER BY campaign_id, conv
) Sub2
WHERE aSequence <= 10
GROUP BY campaign_id
) Sub3
ON rk.campaign_id = Sub3.campaign_id AND Sub3.SumConv >= 30
WHERE rk.report_id = 231

MySQL SUM DISTINCT with Conditional

I need to gather sums using conditional statements as well as DISTINCT values
with a multiple GROUP BY. The example below is a simplified version of a much much more complex query.
Because the real query is very large, I need to avoid having to drastically re-write the query.
DATA
Contracts
id advertiser_id status
1 1 1
2 2 1
3 3 2
4 1 1
A Query that's close
SELECT
COUNT( DISTINCT advertiser_id ) AS advertiser_qty,
COUNT( DISTINCT id ) AS contract_qty,
SUM( IF( status = 1, 1, 0 ) ) AS current_qty,
SUM( IF( status = 2, 1, 0 ) ) AS expired_qty,
SUM( IF( status = 3, 1, 0 ) ) AS other_qty
FROM (
SELECT * FROM `contracts`
GROUP BY advertiser_id, id
) AS temp
Currently Returns
advertiser_qty contract_qty current_qty expired_qty other_qty
3 4 3 1 0
Needs to Return
advertiser_qty contract_qty current_qty expired_qty other_qty
3 4 2 1 0
Where current_qty is 2 which is the sum of records with status = 1 for only DISTINCT advertiser_ids and each sum function will need the same fix.
I hope someone has a simple solution that can plug into the SUM functions.
-Thanks!!
try this
SELECT
COUNT( DISTINCT advertiser_id ) AS advertiser_qty,
COUNT( DISTINCT id ) AS contract_qty,
(select count(distinct advertiser_id) from contracts where status =1
) AS current_qty,
SUM( IF( status = 2, 1, 0 ) ) AS expired_qty,
SUM( IF( status = 3, 1, 0 ) ) AS other_qty
FROM (
SELECT * FROM `contracts`
GROUP BY advertiser_id, id
) AS temp
DEMO HERE
EDIT:
you may look for this without subselect.
SELECT COUNT(DISTINCT advertiser_id) AS advertiser_qty,
COUNT(DISTINCT id) AS contract_qty,
COUNT(DISTINCT advertiser_id , status = 1) AS current_qty,
SUM(IF(status = 2, 1, 0)) AS expired_qty,
SUM(IF(status = 3, 1, 0)) AS other_qty
FROM (SELECT *
FROM `contracts`
GROUP BY advertiser_id, id) AS temp
DEMO HERE