I got a table on my database that contains Book ISBNs and Suppliers:
ISBN | Supplier
12345 | iSupply
12345 | disal
13333 | disal
14444 | iSupply
15555 | disal
16666 | emporio
I need to make a query that shows the count of ISBN that appears only in one supplier. In this example the query should show:
iSupply (1)
disal (2)
emporio (1)
since ISBN 12345 appears both on iSupply and disal it should not count.
Is it possible to do that ?
EDIT: I'm trying to use some of the solutions you guys posted but it keeps loading for 5-10 with no results. The database has 100-150k records, is this a problem ? Thank you for answers
SELECT x.supplier
, COUNT(*)
FROM my_table x
LEFT
JOIN my_table y
ON y.isbn = x.isbn
AND y.supplier <> x.supplier
WHERE y.isbn IS NULL
GROUP
BY x.supplier;
Group by the SUPPLIER and get the count of each group. The inner query filters out ISBN that are in multiple SUPPLIERs
select supplier, count(*)
from your_table
where isbn not in
(
select isbn
from your_table
group by isbn
having count(*) > 1
)
group by supplier
OK, I am obviously a bit late with my answer, but it works:
SELECT suppl,count(*) cnt FROM tmp
WHERE isbn IN (SELECT isbn FROM tmp GROUP BY isbn HAVING COUNT(*)=1)
GROUP BY suppl
The subquery in the where clause returns only isbns that appear uniquely, then the outer select groups together the counts for each supplier.
Yeah, I just looked up and discovered that it is almost the same anser as "juergen d"'s but it was written independently.
Or, "a variation on the theme":
SELECT suppl,count(*) cnt FROM tmp t
WHERE not exists (SELECT 1 FROM tmp WHERE isbn=t.isbn AND suppl!=t.suppl )
GROUP BY suppl
In the end it is a matter of taste. ;-)
Related
I have a table for payments. It has a column named user_id, & payment_type. For every payment, a user can have multiple payment types.
I want to find the users that have used only one payment_type in their entire lifetime.
Let me make it clear through an example:
Let's say I have the following data:
user_id payment_type
1 UPI
1 NB
2 UPI
2 UPI
For the above, I only want user_id 2 as the output since for both the payments, it has used only 1 payment_type.
Can someone help?
A simple HAVING with COUNT should do the trick:
select user_id
from my_table
group by user_id
having count(distinct payment_type)=1;
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=65f673a7df3ac0ee18c13105a2ec17ad
If you want to include payment_type in the result set , use:
select my.user_id,my.payment_type
from my_table my
inner join ( select user_id
from my_table
group by user_id
having count(distinct payment_type)=1
) as t1 on t1.user_id=my.user_id
group by my.user_id,my.payment_type ;
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=cc4704c9e51d01e4e8fc087702edbe6e
I've tried a few things but I've ended up confusing myself.
What I am trying to do is find the most recent records from a table and left join the first after a certain date.
An example might be
id | acct_no | created_at | some_other_column
1 | A0001 | 2017-05-21 00:00:00 | x
2 | A0001 | 2017-05-22 00:00:00 | y
3 | A0001 | 2017-05-22 00:00:00 | z
So ideally what I'd like is to find the latest record of each acct_no sorted by created_at DESC so that the results are grouped by unique account numbers, so from the above record it would be 3, but obviously there would be multiple different account numbers with records for different days.
Then, what I am trying to achieve is to join on the same table and find the first record with the same account number after a certain date.
For example, record 1 would be returned for a query joining on acct_no A0001 after or equal to 2017-05-21 00:00:00 because it is the first result after/equal to that date, so these are sorted by created_at ASC AND created_at >= "2017-05-21 00:00:00" (and possibly AND id != latest.id.
It seems quite straight forward but I just can't get it to work.
I only have my most recent attempt after discarding multiple different queries.
Here I am trying to solve the first part which is to select the most recent of each account number:
SELECT latest.* FROM my_table latest
JOIN (SELECT acct_no, MAX(created_at) FROM my_table GROUP
BY acct_no) latest2
ON latest.acct_no = latest2.acct_no
but that still returns all rows rather than the most recent of each.
I did have something using a join on a subquery but it took so long to run I quite it before it finished, but I have indexes on acct_no and created_at but I've also ran into other problems where columns in the select are not in the group by. I know this can be turned off but I'm trying to find a way to perform the query that doesn't require that.
Just try a little edit to your initial query:
SELECT latest.* FROM my_table latest
join (SELECT acct_no, MAX(created_at) as max_time FROM my_table GROUP
BY acct_no) latest2
ON latest.acct_no = latest2.acct_no AND latest.created_at = latest2.max_time
Trying a different approach. Not sure about the performance impact. But hoping that avoiding self join and group by would be better in terms of performance.
SELECT * FROM (
SELECT mytable1.*, IF(#temp <> acct_no, 1, 0) selector, #temp := acct_no FROM `mytable1`
JOIN (SELECT #temp := '') a
ORDER BY acct_no, created_at DESC , id DESC
) b WHERE selector = 1
Sql Fiddle
you need to get the id where max date is created.
SELECT latest.* FROM my_table latest
join (SELECT max(id) as id FROM my_table GROUP
BY acct_no where created_at = MAX(created_at)) latest2
ON latest.id = latest2.id
This is my table. Now i have to find out the latest company_name of each user_id. The fact is that, if exp_to is null then it will be considered latest, or if both exp_from & exp_to are not null then i have to find the latest company name using exp_to date. Note that, user_id is appearing multiple times in the given table.
Here is what i want:
id | company_name
-----------------
14 | Deltalife Insurance Company Ltd.
1 | Orbital Software
25 | MMTV
18 | Noakhali Science & Technology University
and so on.....
How can i do it with mysql? Thanks in advance.
This is pretty straightforward aggregation problem. As you have likely figured out, nulls cause some interesting behavior in SQL. You can use CASE WHEN to check for null and replace with a different value.
In your case:
SELECT
data.*
FROM (
SELECT
*,
CASE WHEN exp_to is null THEN 1 ELSE exp_to END max_date
FROM your_table
) data
INNER JOIN (
SELECT
user_id,
CASE WHEN MAX(exp_to is null) = 1 THEN 1 ELSE MAX(exp_to) END max_date
FROM
your_table
GROUP BY user_id
) j
ON data.user_id = j.user_id AND data.max_date = j.max_date
Hope this helps, and good luck!
With whatever information you have provided, I can figure-out this query
Select * from Table_Name where exp_to = null || exp_to = (select max(exp_to) from Table_Name)
This is an application for the structured part of Structured query language.
First, you need to know which date is the latest for each user_id. This subquery yields that information:
SELECT user_id,
MAX(IFNULL(NOW(), exp_to) exp_to
FROM mytable
GROUP BY user_id
Then, you need to use that result to find the company, by joining it to your table.
SELECT a.user_id, a.company_name
FROM mytable a
JOIN (
SELECT user_id,
MAX(IFNULL(NOW(), exp_to) exp_to
FROM mytable
GROUP BY user_id
) b ON a.user_id = b.user_id AND IFNULL(NOW(), a.exp_to) = b.exp_to
That should locate the company name associated with the most recent exp_to for each user_id. IFNULL(NOW(),exp_to) implements your requirement that a NULL date be considered as if it were the present time.
This query is an example of a general pattern of queries: Find the rows with extreme (max,min) values of a particular values.
I have a table of records (lets call them TV shows) with an air_date field.
I have another table of advertisements that are related by a show_id field.
I am trying to get the average number of advertisements per show for each date (with a where clause specifying the shows).
I currently have this:
SELECT
`air_date`,
(SELECT COUNT(*) FROM `commercial` WHERE `show_id` = `show`.`id`) AS `num_commercials`,
FROM `show`
WHERE ...
This gives me a result like so:
air_date | num_commercials
2015-6-30 | 6
2015-6-30 | 3
2015-6-30 | 8
2015-6-30 | 2
2015-6-31 | 9
2015-6-31 | 4
When I do a GROUP_BY, it only gives me one of the records, but I want the average for each air_date.
Not too sure I am clear on what you want - but does this do it
SELECT `air_date`,
AVG((SELECT COUNT(*) FROM `commercial` WHERE `show_id` = `show`.`id`)) AS `num_commercials`,
FROM `show`
WHERE .....
GROUP BY `air_date`
(Note double parentheses for AVG function is required)
You can use a sub-query to select count of commercials by air_date/show, then use an outer query to select the average commercials count per air_date.
Something like this should work:
select air_date, avg(num_commercials)
from
(
select show.air_date as air_date,
show.id as show_id,
count(*) as num_commercials
from show
inner join commercial on commercial.show_id = show.id
group by show.air_date, show.id
where ...
) sub
group by air_date
I have a MySQL table where there are many rows for each person, and I want to write a query which aggregates rows with special constraint. (one per person)
For example, lets say the table is consist of following data.
name date reason
---------------------------------------
John 2013-04-01 14:00:00 Vacation
John 2013-03-31 18:00:00 Sick
Ted 2012-05-06 20:00:00 Sick
Ted 2012-02-20 01:00:00 Vacation
John 2011-12-21 00:00:00 Sick
Bob 2011-04-02 20:00:00 Sick
I want to see the distribution of 'reason' column. If I just write a query like below
select reason, count(*) as count from table group by reason
then I will be able to see number of reasons for this table overall.
reason count
------------------
Sick 4
Vacation 2
However, I am only interested in single reason from each person. The reason that should be counted should be from a row with latest date from the person's records. For example, John's latest reason would be Vacation while Ted's latest reason would be Sick. And Bob's latest reason (and the only reason) is Sick.
The expected result for that query should be like below. (Sum of count will be 3 because there are only 3 people)
reason count
-----------------
Sick 2
Vacation 1
Is it possible to write a query such that single latest reason will be counted when I want to see distribution(count) of reasons?
Here are some facts about the table.
The table has tens of millions of rows
For most of times, each person has one reason.
Some people have multiple reasons, but 99.99% of people have fewer than 5 reasons.
There are about 30 different reasons while there are millions of distinct names.
The table is partitioned based on date range.
SELECT T.REASON, COUNT(*)
FROM
(
SELECT PERSON, MAX(DATE) AS MAX_DATE
FROM TABLE-NAME
GROUP BY PERSON
) A, TABLE-NAME T
WHERE T.PERSON = A.PERSON AND T.DATE = A.MAX_DATE
GROUP BY T.REASON
Try this
select reason, count(*) from
(select reason from table where date in
(select max(date) from table group by name)) t
group by reason
In MySQL, it's not very efficient to do this kind of query since you don't have access to tools like partitionning query in SQL Server or Oracle.
You can still emulate it by doing a subquery and retrieve the rows based on the condition you need, here the maximum date :
SELECT t.reason, COUNT(1)
FROM
(
SELECT name, MAX(adate) AS maxDate
FROM #aTable
GROUP BY name
) maxDateRows
INNER JOIN #aTable t ON maxDateRows.name = t.name
AND maxDateRows.maxDate = t.adate
GROUP BY t.reason
You can see a sample here.
Test this query on your samples, but I'm afraid that it will be slow as hell.
For your information, you can do the same thing in a more elegant and much much faster way in SQL Server :
SELECT reason, COUNT(1)
FROM
(
SELECT name
, reason
, RANK() OVER(PARTITION BY name ORDER BY adate DESC) as Rank
FROM #aTable
) AS rankTable
WHERE Rank = 1
GROUP BY reason
The sample is here
If you are really stuck to MySql, and the first query is too slow, then you can split the problem.
Do a first query creating a table:
CREATE TABLE maxDateRows AS
SELECT name, MAX(adate) AS maxDate
FROM #aTable
GROUP BY name
Then create index on both name and maxDate.
Finally, get the results :
SELECT t.reason, COUNT(1)
FROM maxDateRows m
INNER JOIN #aTable t ON m.name = t.name
AND m.maxDate = t.adate
GROUP BY t.reason
The solution you are looking for seems to be solved by this query :
select
reason,
count(*)
from (select * from tablename group by name) abc
group by
reason
It is quite fast and simple. You can view the SQL Fiddle
Apologies if this answer duplicates an existing. Maybe I'm suffering from some form aphasia but I cannot see it...
SELECT x.reason
, COUNT(*)
FROM absentism x
JOIN
( SELECT name,MAX(date) max_date FROM absentism GROUP BY name) y
ON y.name = x.name
AND y.max_date = x.date
GROUP
BY reason;