Dummy table:
id FileName DateLastSaved
1 Marium.doc 2015-01-01
2 Amna.doc 2016-01-01
3 Marium.doc 2016-01-01
I want the query to return such rows where FileName is unique in the whole table. Rows should be returned for particular date range.
Suppose date ranges are of 2016 only, so third row should not be returned as FileName is not unique.
The query that I have created is:
$presentquery="SELECT * FROM InitialLog i WHERE MDid='$MDid' AND
(DateLastSaved>='$firstdate' AND DateLastSaved<='$presentdate') AND NOT
EXISTS (SELECT id FROM InitialLog i2 WHERE i2.id<i.id AND i.FileName=i2.FileName )";
(Where $firstdate and $presentdate are 2 dates for date ranges)
The query is returning the accurate results but it's taking time to execute. Is there any other way that I can rewrite this query??
(I have table with many rows)
I put this query together and it returns the results very quickly.
Select *
FROM foo
Where (`datelastsaved` > '2015-12-31' && `datelastsaved` < '2017-01-01')
AND `filename` NOT IN (
Select `filename`
FROM foo
GROUP BY `filename`
HAVING COUNT(*) > 1);
The first part is your normal select statement with the where clauses to filter on the dates.
The second part is the NOT IN where the select statement finds all of the ones with duplicate filenames.
Select `filename` FROM foo GROUP BY `filename` HAVING COUNT(*) > 1)
You can get the same logic using a LEFT JOIN and looking for nulls, that is,
$presentquery = "SELECT DISTINCT i.* FROM InitialLog i
LEFT JOIN InitialLog i2 ON i2.id<i.id AND i.FileName=i2.FileName
WHERE i.MDid='$MDid'
AND i.DateLastSaved>='$firstdate'
AND i.DateLastSaved<='$presentdate'
AND i2.id IS NULL";
This way you are doing a single join rather than subquerying against each value in i.
It looks like you are trying to get the data associated with the first occurrence of each file name, this should work:
SELECT *
FROM InitialLog i
WHERE MDid='$MDid'
AND DateLastSaved>='$firstdate'
AND DateLastSaved<='$presentdate'
AND id IN (SELECT MIN(id) FROM InitialLog GROUP BY FileName)
;
Alternatively, you can do a JOIN with the same subquery instead:
SELECT i.*
FROM InitialLog AS i
INNER JOIN (SELECT MIN(id) AS id
FROM InitialLog
GROUP BY FileName
) AS firsts USING (id)
WHERE i.MDid='$MDid'
AND i.DateLastSaved>='$firstdate'
AND i.DateLastSaved<='$presentdate'
;
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 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;
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
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 ...