I have a table:
CREATE TABLE Orders (
ID INT,
Customer INT,
PRIMARY KEY(ID)
);
CREATE TABLE Items (
ID INT,
Barcode INT,
PRIMARY KEY(ID, Barcode)
);
INSERT INTO Orders VALUES
(1, 1), (2, 1), (3, 2), (4, 3), (5, 3);
INSERT INTO Items VALUES
(1, 1), (1, 2), (1, 3), (1, 7),
(2, 1), (2, 3), (3, 2), (3, 8),
(4, 2), (4, 3), (4, 8), (5, 4);
I'm trying to find all customers who have ordered the same item twice and specify the item, but not from the same order. I just need a list of Orders.Customer and Items.Barcode showing this.
Here's a query that helps illustrate:
SELECT i.ID, i.Barcode, o.Customer
FROM Items i, Orders o
WHERE i.ID = o.ID
Which produces the below:
+----+---------+----------+
| ID | Barcode | Customer |
+----+---------+----------+
| 1 | 1 | 1 | # A
| 1 | 2 | 1 |
| 1 | 3 | 1 | # B
| 1 | 7 | 1 |
| 2 | 1 | 1 | # A
| 2 | 3 | 1 | # B
| 3 | 2 | 2 |
| 3 | 8 | 2 |
| 4 | 2 | 3 |
| 4 | 3 | 3 |
| 4 | 8 | 3 |
| 5 | 4 | 3 |
+----+---------+----------+
Note where I tagged A, Barcode 1 appears in both ID 1 and ID 2. Both those orders have the same customer, same barcode, but different order IDs. B is another example.
How can I pull out these rows, so I have something like the below:
+---------+----------+
| Barcode | Customer |
+---------+----------+
| 1 | 1 |
| 3 | 1 |
+---------+----------+
More declaratively, I want to know what customers have ordered the same item twice, and list the items and customers. In other words, "Customer 1 has ordered Items 1 and 3 twice".
I'm trying to find all customers who have ordered the same item twice and specify the item, but not from the same order.
This is pretty simple with a HAVING clause:
SELECT o.Customer, i.Barcode
FROM Orders o JOIN
Items i
ON i.ID = o.ID
GROUP BY o.Customer, i.Barcode
HAVING MIN(o.id) <> MAX(o.id);
Note the use of proper, explicit, standard, readable JOIN syntax. Never use commas in the FROM clause.
If you need the expected result then you should group by with having clause like below.
SELECT i.Barcode, o.Customer
FROM Items i, Orders o
WHERE i.ID = o.ID
GROUP BY i.Barcode, o.Customer HAVING COUNT(*) >1
I am assuming that you need all records which are repeating more than once.
You may try this -
With cte1 as (SELECT i.ID as orderId, i.Barcode, o.Customer
FROM Items i, Orders o
WHERE i.ID = o.ID),
cte2 as (SELECT i.ID as orderId, i.Barcode, o.Customer
FROM Items i, Orders o
WHERE i.ID = o.ID)
Select distinct cte1.Barcode, cte1.Customer
from cte1, cte2
where
cte1.Barcode = cte2.Barcode
and cte1.Customer = cte2.Customer
and cte1.orderId <> cte2.orderId;
More elegant way - Please refer Amit Verma's answer
Related
How do I write a query that counts (totals) the number of values in a group for data spread across three tables? For each reporter and report date, I’d like a count of the number of sightings where species codes is 10 or 20 (using IN because species group has lots of codes).
REPORTER TABLE
reporter_id | reporter_num
-----------------------------
1 | 1111
2 | 2222
REPORT TABLE
report_id | reporter_id | report_date
-------------------------------------
1 | 1 | 2022-09-05
2 | 1 | 2022-09-05
3 | 1 | 2022-09-05
4 | 1 | 2022-09-16
5 | 2 | 2022-09-22
6 | 2 | 2022-09-22
SIGHTING TABLE
sighting_id | species_code
------------------------
1 | 10
2 | 55
3 | 20
4 | 35
5 | 55
6 | 20
This is essentially what I’m working with when the three tables are joined:
reporter_num | report_date | species_code
----------------------------------------
1111 | 2022-09-05 | 10
1111 | 2022-09-05 | 55
1111 | 2022-09-05 | 20
1111 | 2022-09-16 | 35
2222 | 2022-09-22 | 55
2222 | 2022-09-22 | 20
Query: for each reporter_num and report_date (one row per reporter_num and report_date), count the number of sightings where species_code is 10 or 20. Expected results:
reporter_num | report_date | my_count
----------------------------------------
1111 | 2022-09-05 | 2
1111 | 2022-09-16 | 0
2222 | 2022-09-22 | 1
A count in my query gives the total number of records for each reporter_num and report_date which isn’t what I want:
select
reporter.reporter_num, report.report_date,
count(sighting.species_code in (10, 20)) as my_count
from report
inner join reporter on report.reporter_id = report.reporter_id
inner join location on report.report_id = location.report_id
inner join method on location.location_id = method.location_id
left join sighting on method.method_id = sighting.method_id
group by
reporter.reporter_num, report.report_date;
Query Results – my_count is total number of records, which is incorrect:
reporter_num | report_date | my_count
----------------------------------------
1111 | 2022-09-05 | 3
1111 | 2022-09-16 | 1
2222 | 2022-09-22 | 2
Tried a subquery and the counts in both the outer query and subquery are incorrect:
select
reporter.reporter_num, report.report_date,
count(my_table.my_count)
from report
inner join reporter on report.reporter_id = reporter.reporter_id
inner join (
select
reporter.reporter_id, reporter.reporter_num,
report.report_date, sighting.species_code as my_count
from report
[... see joins in above query ...]
where sighting.species_code in (10, 20)
) as my_table on reporter.reporter_id = my_table.reporter_id
group by
reporter.reporter_num, report.report_date;
I feel like I’m close but missing something (or a couple somethings). Any suggestions? Many thanks.
SELECT reporter_num, report_date,
LENGTH(gs) + 2 - LENGTH(REGEXP_REPLACE(CONCAT(',', gs, ','), ',(10|20),', 'len')) cnt
FROM (
SELECT reporter.reporter_num, report.report_date, GROUP_CONCAT(sighting.species_code) gs
FROM report
JOIN reporter ON report.reporter_id = reporter.reporter_id
[... joins on location and method ...]
JOIN sighting ON link.sighting_id = sighting.sighting_id
GROUP BY reporter.reporter_num, report.report_date
) tbl;
Outputs:
| reporter_num | report_date | cnt |
|--------------|-------------|-----|
| 1111 | 2022-09-05 | 2 |
| 1111 | 2022-09-16 | 0 |
| 2222 | 2022-09-22 | 1 |
First creating an ad hoc, temporary table using GROUP_CONCAT and GROUP BY:
SELECT reporter.reporter_num, report.report_date, GROUP_CONCAT(sighting.species_code) gs
FROM report
JOIN reporter ON report.reporter_id = reporter.reporter_id
[... joins on location and method ...]
JOIN sighting ON link.sighting_id = sighting.sighting_id
GROUP BY reporter.reporter_num, report.report_date
Outputs:
| reporter_num | report_date | gs |
|--------------|-------------|----------|
| 1111 | 2022-09-05 | 10,55,20 |
| 1111 | 2022-09-16 | 35 |
| 2222 | 2022-09-22 | 55,20 |
Then counting occurrences of either 10 or 20 in the GROUP_CONCAT'ed gs column:
LENGTH(gs) + 2 - LENGTH(REGEXP_REPLACE(CONCAT(',', gs, ','), ',(10|20),', 'len'))
In this case using REGEXP_REPLACE instead of REPLACE as is used in the credited link.
-- create
CREATE TABLE reporter (
reporter_id INTEGER PRIMARY KEY,
reporter_num INTEGER NOT NULL
);
CREATE TABLE report (
report_id INTEGER PRIMARY KEY,
reporter_id INTEGER NOT NULL,
report_date TEXT NOT NULL
);
CREATE TABLE report_sighting (
report_id INTEGER NOT NULL,
sighting_id INTEGER NOT NULL
);
CREATE TABLE link (
report_id INTEGER NOT NULL,
sighting_id INTEGER NOT NULL
);
CREATE TABLE sighting (
sighting_id INTEGER PRIMARY KEY,
species_code INTEGER NOT NULL
);
-- insert
INSERT INTO reporter VALUES (1, 1111), (2, 2222);
INSERT INTO report VALUES (1, 1, '2022-09-05'), (2, 1, '2022-09-05'), (3, 1, '2022-09-05'), (4, 1, '2022-09-16'),
(5, 2, '2022-09-22'), (6, 2, '2022-09-22');
INSERT INTO link VALUES (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6);
INSERT INTO sighting VALUES (1, 10), (2, 55), (3, 20), (4, 35), (5, 55), (6, 20);
-- fetch
SELECT reporter_num, report_date,
LENGTH(gs) + 2 - LENGTH(REGEXP_REPLACE(CONCAT(',', gs, ','), ',(10|20),', 'len')) cnt
FROM (
SELECT reporter.reporter_num, report.report_date, GROUP_CONCAT(sighting.species_code) gs
FROM report
JOIN reporter ON report.reporter_id = reporter.reporter_id
JOIN link ON report.report_id = link.report_id
JOIN sighting ON link.sighting_id = sighting.sighting_id
GROUP BY reporter.reporter_num, report.report_date
) tbl;
Try it here: https://onecompiler.com/mysql/3ygv4cry2
Credit: https://www.tutorialspoint.com/finding-number-of-occurrences-of-a-specific-string-in-mysql
I wasn't using COUNT or COUNT(IF) correctly. The IF has to be 1 if true and NULL if false; otherwise, it will add 1 even if the species code isn't in the species group. The correct code:
select
reporter.reporter_num, report.report_date,
count(if(sighting.species_code in (10, 20), 1, NULL)) as my_count
from report
[... see joins in above query ...]
group by
reporter.reporter_num, report.report_date;
The above will also return report dates that don't have any species in the species group, my_count will be zero for that case, which is what I want.
To exclude report dates with zero species in the species group, use count(*) in SELECT and filter for the species codes in WHERE.
I have a table data as:
CREATE TABLE SERP (
id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
s_product_id INT,
search_product_result VARCHAR(255)
);
INSERT INTO SERP(s_product_id, search_product_result)
VALUES
(0, 'A'),
(0, 'B'),
(0, 'C'),
(0, 'D'),
(1, 'A'),
(1, 'E'),
(2, 'A'),
(2, 'B'),
(3, 'D'),
(3, 'E'),
(3, 'D');
The data set is as follows:
s_product_id | search_product_result
___________________________________________
0 | A
0 | B
0 | C
0 | D
-------------------------------------------
1 | A
1 | E
-------------------------------------------
2 | A
2 | B
-------------------------------------------
3 | D
3 | E
3 | D
I need to list all distinct search_product_result values and count frequencies of these values occurring in s_product_id.
Required Output result-set:
DISTINCT_SEARCH_PRODUCT | s_product_id_frequency_count
------------------------------------------------------------
A | 3
B | 2
C | 1
D | 2 [occurred twice in 3, but counted only once.]
E | 2
Here, A occurs in three s_product_id : 0, 1, 2, B in two : 0, 2, and so on.
D occurred twice in the same group 3, but is counted only once for that group.
I tried grouping by search_product_result, but this counts D twice in the same group.
select search_product_result, count(*) as Total from serp group by search_product_result
Output:
search_product_result | Total
------------------------------------
A | 3
B | 2
C | 1
D | 3 <---
B | 2
You can try below - use count(distinct s_product_id)
select search_product_result, count(distinct s_product_id) as Total
from serp group by search_product_result
use count(distinct()
select search_product_result, count(distinct s_product_id, search_product_result) as Total
from SERP
group by search_product_result
see dbfiddle
I want to display the number of purchases each customer has made. If they've made 0 purchases, I want to display 0.
Desired Output:
-------------------------------------
| customer_name | number_of_purchases |
-------------------------------------
| Marg | 0 |
| Ben | 1 |
| Phil | 4 |
| Steve | 0 |
-------------------------------------
Customer Table:
-----------------------------
| customer_id | customer_name |
-----------------------------
| 1 | Marg |
| 2 | Ben |
| 3 | Phil |
| 4 | Steve |
-----------------------------
Purchases Table:
--------------------------------------------------
| purchase_id | customer_id | purchase_description |
--------------------------------------------------
| 1 | 2 | 500 Reams |
| 2 | 3 | 6 Toners |
| 3 | 3 | 20 Staplers |
| 4 | 3 | 2 Copiers |
| 5 | 3 | 9 Name Plaques |
--------------------------------------------------
My current query is as follows:
SELECT customer_name, COUNT(*) AS number_of_purchaes
FROM customer
LEFT JOIN purchases ON customer.customer_id = purchases.customer_id
GROUP BY customer.customer_id
However, since it's a LEFT JOIN, the query results in rows for customers with no purchases, which makes them part of the COUNT(*). In other words, customers who've made 0 purchases are displayed as having made 1 purchase, like so:
LEFT JOIN Output:
-------------------------------------
| customer_name | number_of_purchases |
-------------------------------------
| Marg | 1 |
| Ben | 1 |
| Phil | 4 |
| Steve | 1 |
-------------------------------------
I've also tried an INNER JOIN, but that results in customers with 0 purchases not showing at all:
INNER JOIN Output:
-------------------------------------
| customer_name | number_of_purchases |
-------------------------------------
| Ben | 1 |
| Phil | 4 |
-------------------------------------
How could I achieve my Desired Output where customers with 0 purchases are shown?
Instead of count(*) use count(purchase_id)
SELECT customer_name, COUNT(purchase_id) AS number_of_purchaes
FROM customer
LEFT JOIN purchases ON customer.customer_id = purchases.customer_id
GROUP BY customer_id,customer_name
You can try like this:
Sample Data:
create table customer(customer_id integer, customer_name varchar(20));
create table purchaser(purchaser_id varchar(20), customer_id integer, description varchar(20));
insert into customer values(1, 'Marg');
insert into customer values(2, 'Ben');
insert into customer values(3, 'Phil');
insert into customer values(4, 'Steve');
insert into purchaser values(1, 2, '500 Reams');
insert into purchaser values(2, 3, '6 toners');
insert into purchaser values(3, 3, '20 Staplers');
insert into purchaser values(4, 3, '20 Staplers');
insert into purchaser values(5, 3, '20 Staplers');
SELECT c.customer_id, c.customer_name, COUNT(p.purchaser_id) AS number_of_purchaes
FROM customer c
LEFT JOIN purchaser p ON c.customer_id = p.customer_id
GROUP BY c.customer_id;
SQL fiddle: http://sqlfiddle.com/#!9/32ff0a/2
COUNT(*) returns the number of items in a group. This includes NULL values and duplicates.
COUNT(ALL expression) evaluates expression for each row in a group, and returns the number of nonnull values.
CREATE table customer(customer_id integer , customer_name varchar(20));
create table purchases(purchase_id integer , customer_id integer , purchase_description varchar(30));
INSERT INTO customer ( customer_id, customer_name )
VALUES ( 1, 'Marg' )
, ( 2, 'Ben' )
, ( 3, 'Phil' )
, ( 4, 'Steve' );
INSERT INTO purchases ( purchase_id, customer_id, purchase_description )
VALUES ( 1, 2, '500 Reams' )
, ( 2, 3, '6 toners' )
, ( 3, 3, '20 Staplers' )
, ( 4, 3, '2 Copiers' )
, ( 5, 3, '9 Name Plaques' );
SELECT c.customer_name
, COUNT(p.purchase_id) AS number_of_purchases
FROM customer c
LEFT JOIN purchases p
ON c.customer_id = p.customer_id
GROUP BY c.customer_name
COUNT(*) counts rows. You want to count matches, so count from the second table as following:
select customer.customer_name , a.number_of_purchases from (
SELECT customer_id, COUNT(purchases.purchase_id) AS number_of_purchaes
FROM customer
LEFT JOIN purchases ON customer.customer_id = purchases.customer_id
GROUP BY customer.customer_id) as a
inner join customer on customer.customer_id=a.customer_id;
In other words, the LEFT JOIN returns a row when there is no match. That row has a NULL value for all the columns in the purchases table.
SELECT
customer_name, COUNT(purchase_id) AS number_of_purchases
FROM
customer AS c
LEFT JOIN purchases AS p ON (c.cid = p.cid)
GROUP BY c.name
Instead of count(*) use COUNT(purchases.customer_id)
SELECT customer_name, COUNT(purchases.customer_id) AS number_of_purchaes
FROM customer
LEFT JOIN purchases ON customer.customer_id = purchases.customer_id
GROUP BY customer.customer_id
SELECT c.customer_name,count(p.purchase_id)number_of_purchases FROM Customer c
LEFT JOIN
Purchases AS p ON c.customer_id = p.customer_id
GROUP BY c.customer_name
I'm trying to use a LEFT JOIN in conjunction with a GROUP_CONCAT but not getting the expected results.
Two simple tables:
weather_alerts:
id | user_id | resort_id
1 | 1 | 1
2 | 1 | 2
3 | 1 | 3
4 | 1 | 5
weather_users
id | email
1 | me#me.com
The query:
SELECT GROUP_CONCAT(wa.resort_id) AS resort_ids, wu.email FROM weather_alerts wa LEFT JOIN weather_users wu ON wa.id = wu.user_id GROUP BY wu.email
Instead of generating:
email resort_ids
me#me.com 1,2,3,5
I get:
email resort_ids
NULL 2,3,5
me#me.com 1
I suspect this is an issue with the JOIN rather than the CONCAT.
It appears that your LEFT JOIN needs improvement.
create table weather_alerts (id int, user_id int, resort_id int);
insert into weather_alerts values (1, 1, 1), (2, 1, 2), (3, 1, 3), (4, 1, 5);
create table weather_users (id int, email varchar(100));
insert into weather_users values (1, 'me#me.com');
Query
SELECT GROUP_CONCAT(wa.resort_id ORDER BY wa.resort_id) AS resort_ids, wu.email
FROM weather_alerts wa
LEFT JOIN weather_users wu ON wa.user_id = wu.id
GROUP BY wu.email
Notice that you are joining on wa.id = wu.user_id. The join should be on wa.user_id = wu.id
Result
| resort_ids | email |
|------------|-----------|
| 1,2,3,5 | me#me.com |
I have two tables to join, and I want each row to be joined only once. Here is sample data:
CREATE TABLE A (id smallint, val varchar(1) );
CREATE TABLE B (id smallint, val varchar(1) );
INSERT INTO A VALUES (1, 'a'), (2, 'b'), (3, 'c'), (3, 'd');
INSERT INTO B VALUES (2, 'x'), (3, 'y'), (4, 'z'), (3, 'k');
When we join on id we obtain:
mysql> SELECT * FROM A JOIN B ON A.id = B.id;
+------+------+------+------+
| id | val | id | val |
+------+------+------+------+
| 2 | b | 2 | x |
| 3 | c | 3 | y |
| 3 | d | 3 | y |
| 3 | c | 3 | k |
| 3 | d | 3 | k |
+------+------+------+------+
What I want is either:
+------+------+------+------+ +------+------+------+------+
| id | val | id | val | | id | val | id | val |
+------+------+------+------+ or +------+------+------+------+
| 2 | b | 2 | x | | 2 | b | 2 | x |
| 3 | c | 3 | y | | 3 | d | 3 | y |
| 3 | d | 3 | k | | 3 | c | 3 | k |
+------+------+------+------+ +------+------+------+------+
The order and arrangement don’t matter.
Is it possible? How?
According to this answer I need to specify how to select the matching row. In that case I guess would need to check in a subquery if the row of the joined table has already been used; or a kind of counter related to the id... but I do not know how to write this.
Edit:
To clarify I want each row with id 3 to be mapped with another one in the joined table, such as each row is mapped only once (I am also interested to know what happens when the number of rows with same id is different in the two tables):
(3, c) -> (3, y) [join only with the first row such as B.id = 3]
(3, d) -> (3, k) [the first row has been used, so map with (and only with) the second row such as B.id = 3]
But as I said mapping may be in any other order (e.g. mapping rows in reverse order).
SQL Fiddle
MySQL 5.6 Schema Setup:
CREATE TABLE A (id smallint, val varchar(1) );
CREATE TABLE B (id smallint, val varchar(1) );
INSERT INTO A VALUES (1, 'a'), (2, 'b'), (3, 'c'), (3, 'd');
INSERT INTO B VALUES (2, 'x'), (3, 'y'), (4, 'z'), (3, 'k');
Query 1:
select
aa.id as aid
, aa.val as aval
, bb.id as bid
, bb.val as bval
from (
select
#row_num :=IF(#prev_value=a.id,#row_num+1,1)AS RowInGroup
, a.id
, a.val
, #prev_value := a.id
from (
SELECT id, val
FROM A
group by id, val
/* order by ?? */
) a
CROSS JOIN (
SELECT #row_num :=1, #prev_value :=''
) vars
) aa
INNER JOIN (
select
#row_num :=IF(#prev_value=b.id,#row_num+1,1)AS RowInGroup
, b.id
, b.val
, #prev_value := b.id
from (
SELECT id, val
FROM B
group by id, val
/* order by ?? */
) b
CROSS JOIN (
SELECT #row_num :=1, #prev_value :=''
) vars
) bb on aa.id = bb.id and aa.RowInGroup = bb.RowInGroup
order by
aa.id
, aa.val
Results:
| id | val | id | val |
|----|-----|----|-----|
| 2 | b | 2 | x |
| 3 | c | 3 | k |
| 3 | d | 3 | y |
nb: you could influence the final result by introducing order by in the subqueries that group by id, val where the sequence RowInGroup is calculated.
Finally I did it!
SELECT T.ID_A,
T.VAL_A,
T.XXXX,
T.ID_B,
T.VAL_B,
T.YYYY
FROM (
SELECT A.id AS ID_A,
A.VAL AS VAL_A,
ROW_NUMBER() OVER (PARTITION BY A.ID, A.VAL ORDER BY A.ID, A.VAL) AS XXXX,
B.ID AS ID_B,
B.VAL AS VAL_B,
ROW_NUMBER() OVER (PARTITION BY B.ID, B.VAL ORDER BY B.ID DESC, B.VAL) AS YYYY
FROM A INNER JOIN B ON A.id = B.id) AS T
WHERE T.YYYY = 1
I could make it thanks to this blog post:
SELECT A2.id, A2.val, B2.val FROM (
SELECT l.id, l.val, COUNT(*) AS n1 FROM A AS l JOIN A AS r ON l.id = r.id AND l.val >= r.val GROUP BY l.id, l.val
) AS A2 JOIN (
SELECT l.id, l.val, COUNT(*) AS n2 FROM B AS l JOIN B AS r ON l.id = r.id AND l.val >= r.val GROUP BY l.id, l.val
) AS B2 ON
A2.id = B2.id AND n1 = n2;
The result is:
+------+------+------+
| id | val | val |
+------+------+------+
| 2 | b | x |
| 3 | c | k |
| 3 | d | y |
+------+------+------+