I am struggling to find an optimal solution for the following problem.
Suppose I have a table 'Table' like this:
id name report_id
1 name1 1
2 name2 3
3 name3 5
4 name1 7
5 name3 8
....................
I want to select for each value in a set: ('name1', 'name2') 10 random unique rows.
Of course it is possible to do with union like:
(SELECT * FROM Table
WHERE
name='name1'
ORDER BY RAND() LIMIT 10)
UNION
(SELECT * FROM Table
WHERE
name='name2'
ORDER BY RAND() LIMIT 10)
But if I have 100 unique names for which I have to select 10 random records - this query is going to be a bit large.
SQLFiddle demo
select ID,NAME,REPORT_ID
from
(
select *, #row:=if(name=#name,#row,0)+1 as rn, #name:=name from
(select *,RAND() as trand from t) t1,
(select #row:=0,#name:='') tm2
order by name,trand
) t2
where rn<=10
Try this:
SELECT
id,
name,
report_id
FROM
(
SELECT id,
report_id,
name,
CASE WHEN #name != name THEN #rn := 1 ELSE #rn := #rn + 1 END rn,
#name:=name
FROM (SELECT * FROM tbl ORDER BY RAND()) a,
(SELECT #rn:=0, #name := NULL) r
ORDER BY name
) s
WHERE rn <= 10;
SQL FIDDLE DEMO
This doesn't work in MySQL, but in PostgreSQL you can use partition by
select name,report_id from
(select name,report_id,row_number()
over
(partition by name order by random())
as rn from Table) a
where rn<=10
I had this same question and found this answer from a colleague.
I am wondering how to write this query.
I know this actual syntax is bogus, but it will help you understand what I want.
I need it in this format, because it is part of a much bigger query.
SELECT distributor_id,
COUNT(*) AS TOTAL,
COUNT(*) WHERE level = 'exec',
COUNT(*) WHERE level = 'personal'
I need this all returned in one query.
Also, it need to be in one row, so the following won't work:
'SELECT distributor_id, COUNT(*)
GROUP BY distributor_id'
You can use a CASE statement with an aggregate function. This is basically the same thing as a PIVOT function in some RDBMS:
SELECT distributor_id,
count(*) AS total,
sum(case when level = 'exec' then 1 else 0 end) AS ExecCount,
sum(case when level = 'personal' then 1 else 0 end) AS PersonalCount
FROM yourtable
GROUP BY distributor_id
One way which works for sure
SELECT a.distributor_id,
(SELECT COUNT(*) FROM myTable WHERE level='personal' and distributor_id = a.distributor_id) as PersonalCount,
(SELECT COUNT(*) FROM myTable WHERE level='exec' and distributor_id = a.distributor_id) as ExecCount,
(SELECT COUNT(*) FROM myTable WHERE distributor_id = a.distributor_id) as TotalCount
FROM (SELECT DISTINCT distributor_id FROM myTable) a ;
EDIT:
See #KevinBalmforth's break down of performance for why you likely don't want to use this method and instead should opt for #Taryn♦'s answer. I'm leaving this so people can understand their options.
SELECT
distributor_id,
COUNT(*) AS TOTAL,
COUNT(IF(level='exec',1,null)),
COUNT(IF(level='personal',1,null))
FROM sometable;
COUNT only counts non null values and the DECODE will return non null value 1 only if your condition is satisfied.
Building on other posted answers.
Both of these will produce the right values:
select distributor_id,
count(*) total,
sum(case when level = 'exec' then 1 else 0 end) ExecCount,
sum(case when level = 'personal' then 1 else 0 end) PersonalCount
from yourtable
group by distributor_id
SELECT a.distributor_id,
(SELECT COUNT(*) FROM myTable WHERE level='personal' and distributor_id = a.distributor_id) as PersonalCount,
(SELECT COUNT(*) FROM myTable WHERE level='exec' and distributor_id = a.distributor_id) as ExecCount,
(SELECT COUNT(*) FROM myTable WHERE distributor_id = a.distributor_id) as TotalCount
FROM myTable a ;
However, the performance is quite different, which will obviously be more relevant as the quantity of data grows.
I found that, assuming no indexes were defined on the table, the query using the SUMs would do a single table scan, while the query with the COUNTs would do multiple table scans.
As an example, run the following script:
IF OBJECT_ID (N't1', N'U') IS NOT NULL
drop table t1
create table t1 (f1 int)
insert into t1 values (1)
insert into t1 values (1)
insert into t1 values (2)
insert into t1 values (2)
insert into t1 values (2)
insert into t1 values (3)
insert into t1 values (3)
insert into t1 values (3)
insert into t1 values (3)
insert into t1 values (4)
insert into t1 values (4)
insert into t1 values (4)
insert into t1 values (4)
insert into t1 values (4)
SELECT SUM(CASE WHEN f1 = 1 THEN 1 else 0 end),
SUM(CASE WHEN f1 = 2 THEN 1 else 0 end),
SUM(CASE WHEN f1 = 3 THEN 1 else 0 end),
SUM(CASE WHEN f1 = 4 THEN 1 else 0 end)
from t1
SELECT
(select COUNT(*) from t1 where f1 = 1),
(select COUNT(*) from t1 where f1 = 2),
(select COUNT(*) from t1 where f1 = 3),
(select COUNT(*) from t1 where f1 = 4)
Highlight the 2 SELECT statements and click on the Display Estimated Execution Plan icon. You will see that the first statement will do one table scan and the second will do 4. Obviously one table scan is better than 4.
Adding a clustered index is also interesting. E.g.
Create clustered index t1f1 on t1(f1);
Update Statistics t1;
The first SELECT above will do a single Clustered Index Scan. The second SELECT will do 4 Clustered Index Seeks, but they are still more expensive than a single Clustered Index Scan. I tried the same thing on a table with 8 million rows and the second SELECT was still a lot more expensive.
For MySQL, this can be shortened to:
SELECT distributor_id,
COUNT(*) total,
SUM(level = 'exec') ExecCount,
SUM(level = 'personal') PersonalCount
FROM yourtable
GROUP BY distributor_id
Well, if you must have it all in one query, you could do a union:
SELECT distributor_id, COUNT() FROM ... UNION
SELECT COUNT() AS EXEC_COUNT FROM ... WHERE level = 'exec' UNION
SELECT COUNT(*) AS PERSONAL_COUNT FROM ... WHERE level = 'personal';
Or, if you can do after processing:
SELECT distributor_id, COUNT(*) FROM ... GROUP BY level;
You will get the count for each level and need to sum them all up to get the total.
I do something like this where I just give each table a string name to identify it in column A, and a count for column. Then I union them all so they stack. The result is pretty in my opinion - not sure how efficient it is compared to other options but it got me what I needed.
select 'table1', count (*) from table1
union select 'table2', count (*) from table2
union select 'table3', count (*) from table3
union select 'table4', count (*) from table4
union select 'table5', count (*) from table5
union select 'table6', count (*) from table6
union select 'table7', count (*) from table7;
Result:
-------------------
| String | Count |
-------------------
| table1 | 123 |
| table2 | 234 |
| table3 | 345 |
| table4 | 456 |
| table5 | 567 |
-------------------
Based on Taryn's response with an added nuance using OVER():
SELECT distributor_id,
COUNT(*) total,
SUM(case when level = 'exec' then 1 else 0 end) OVER() ExecCount,
SUM(case when level = 'personal' then 1 else 0 end) OVER () PersonalCount
FROM yourtable
GROUP BY distributor_id
Using OVER() with nothing in the () will give you the total count for the whole dataset.
I think this can also works for you select count(*) as anc,(select count(*) from Patient where sex='F')as patientF,(select count(*) from Patient where sex='M') as patientM from anc
and also you can select and count related tables like this select count(*) as anc,(select count(*) from Patient where Patient.Id=anc.PatientId)as patientF,(select count(*) from Patient where sex='M') as patientM from anc
In Oracle you'll do something like
SELECT
(SELECT COUNT(*) FROM schema.table1),
(SELECT COUNT(*) FROM schema.table2),
...
(SELECT COUNT(*) FROM schema.tableN)
FROM DUAL;
If your flavor of SQL supports it, you can use COUNT_IF() to count based on a condition.
SELECT
distributor_id,
COUNT(*) AS total_count,
COUNT_IF(level = 'exec') AS exec_count,
COUNT_IF(level = 'personal') AS personal_count
FROM table_name
GROUP BY distributor_id
The recently added PIVOT functionality can do exactly what you need:
SELECT *
FROM ( SELECT level from your_table )
PIVOT ( count(*) for level in ('exec', 'personal') )
i try this query to get row number in selected rows, but the output comes like 23,56,78,.... i need to get 1,2,3 to every selected row. please help me
SET #row=0;
SELECT `table1`.`col1`,`table1`.`col2`,#row:=#row+1
FROM `table1`
LEFT OUTER JOIN `table2` ON `table1`.`col1` = `table2`.`col5`
WHERE `table2`.`col5` IS NOT NULL
GROUP BY `col1` ORDER BY `table1`.`col7` DESC LIMIT 0,10
Move the query with the ORDER BY clause into a subquery.
SET #row = 0;
SELECT col1, col2, #row := #row+1
FROM (SELECT table1.col1, table1.col2
FROM table1
LEFT JOIN table2 ON table1.col1 = table2.col5
WHERE table2.col5 IS NOT NULL
GROUP BY col1
ORDER BY table1.col7 DESC
LIMIT 0, 10) AS subquery
You can try using an inline view. Use a query that gets the rows you want to return, and then wrap that in parens, and reference that query in the FROM clause of an outer query.
Something like this:
SELECT v.`col1`
, v.`col2`
, #row := #row + 1 AS `rn`
FROM ( SELECT `table1`.`col1`
, `table1`.`col2`
FROM `table1`
...
ORDER BY ...
LIMIT 10
) v
Supposedly I have this sql result in PHP ($myresults = mysql_fetch_… either assoc/row/array):
SELECT table1.name as name, sum(table2.numbers) as numbers FROM table2 INNER JOIN
table1 ON table2.fk_id = table1.id GROUP BY name ORDER BY numbers DESC
---------------
| John | 800 |
---------------
| Mark | 500 |
---------------
| Bill | 300 |
---------------
So I am logged as Mark ($_SESSION['name'] == "Mark") and I want to know in which row # the value 'Mark' is located (in this case, row number 1, considering the first row is 0).
How to I get that via PHP?
Thanks…
EDIT: think of it as a High Score or Leaderboards table, I don't need the user id, but the row in which the user is located as of right now…
You should use user defined variables this way:
SELECT table1.name as name, sum(table2.numbers) as numbers,
#rank := #rank + 1 rank
FROM table2
CROSS JOIN (SELECT #rank := 0) init
JOIN table1 ON table2.fk_id = table1.id
GROUP BY name
ORDER BY numbers DESC
After a second thought, the group by might give you some trouble with the counting of the UDVs. This is another alternative but will be less performant than the previous approach.
SELECT *, #rank := #rank + 1 rank FROM (
SELECT table1.name as name, sum(table2.numbers) as numbers
FROM table2
JOIN table1 ON table2.fk_id = table1.id
GROUP BY name
) s
CROSS JOIN (SELECT #rank := 0) init
ORDER BY numbers DESC
Anyway, I would recommend counting directly in PHP. That will be more flexible and performant.
Modify your SQL to select primary ID along with the other data:
SELECT
table1.id as id,
table1.name as name,
sum(table2.numbers) as numbers
FROM
table2
INNER JOIN
table1 ON table2.fk_id = table1.id
GROUP BY
name
ORDER BY
numbers DESC
I found some good answers for this question, but I can't really get them to work.
I wan't to get a players rank from a hiscore table.
id name score
1 John 10
2 Linda 5
3 Emmy 25
I want to pass in a name in the query (Linda) and get her rank (She only have 5 points in the table above), and get her rank (nr 3).
I found a similar question with this answer, but don't understand it:
SELECT uo.*,
(
SELECT COUNT(*)
FROM users ui
WHERE (ui.points, ui.id) >= (uo.points, uo.id)
) AS rank
FROM users uo
WHERE id = #id
Thanks in advance
SELECT (COUNT(*) + 1) AS rank FROM hiscore WHERE score > (SELECT score FROM hiscore WHERE name = 'Linda')
Try this query -
SELECT name, score, rank FROM
(SELECT *, #r:=#r + 1 rank FROM table_name ORDER BY score DESC) t1,
(SELECT #r:=0) t2
WHERE name = 'Linda'
SET #rownum := 0;
SELECT rank, name, score
FROM
(SELECT #rownum := #rownum + 1 AS rank, name, score
FROM players
ORDER BY score DESC)