How to read out data from sql-database when several criteria match - mysql

I have a normalized sql-database with several tables and an html-form to read out the data. There is one table that contains product-characteristics and any product can have one or more characteristics.
Main table: product_main pm,
Table with characteristics: product_characteristics pc,
product_id is the foreign key.
id | product_id | product_name | characteristic
1 | 27 | Product_C | characteristic5
2 | 27 | Product_C | characteristic11
3 | 27 | Product_C | characteristic3
4 | 27 | Product_C | characteristic47
5 | 27 | Product_C | characteristic34
6 | 28 | Product_D | characteristic29
7 | 28 | Product_D | characteristic63
8 | 28 | Product_D | characteristic2
Now, I want to read out all product_names that match the selected criteria. This works fine, when only one characteristic is selected:
SELECT pm.name
FROM product_main pm, product_characteristics pc
WHERE pc.characteristic = 'characteristic47'
AND pm.id = pc.product_id
The correct result is: Product_C.
If more than one characteristics are selected simultaneously, e.g.:
SELECT pm.name
FROM product_main pm, product_characteristics pc
WHERE pc.characteristic = 'characteristic47'
AND pc.characteristic = 'characteristic11'
AND pm.id = pc.product_id
there is no result, although Product_C matches both criteria.
Could someone please tell me, how I can solve this problem?

One option uses conditional aggregation:
SELECT product_id, product_name
FROM product_main
GROUP BY product_id, product_name
HAVING SUM(CASE WHEN characteristic = 'characteristic47' THEN 1 ELSE 0 END) > 0 AND
SUM(CASE WHEN characteristic = 'characteristic11' THEN 1 ELSE 0 END) > 0
If you also want to retain all original records in product_main which have both matching characteristics, then you can join the above table back to your original table:
SELECT t1.*
FROM product_main t1
INNER JOIN
(
SELECT product_id
FROM product_main
GROUP BY product_id
HAVING SUM(CASE WHEN characteristic = 'characteristic47' THEN 1 ELSE 0 END) > 0 AND
SUM(CASE WHEN characteristic = 'characteristic11' THEN 1 ELSE 0 END) > 0
) t2
ON t1.product_id = t2.product_id

Well pc.characteristic cannot be both characteristic47 AND characteristic11 at the same time, but it could be (characteristic47 OR characteristic11)
So amend the query like so
SELECT pm.name
FROM product_main pm, product_characteristics pc
WHERE ( pc.characteristic = 'characteristic47' OR
pc.characteristic = 'characteristic11' )
AND pm.id = pc.product_id
You sould also be using JOIN's like so
SELECT pm.name
FROM product_main pm
JOIN product_characteristics pc ON pm.id = pc.product_id
WHERE ( pc.characteristic = 'characteristic47' OR
pc.characteristic = 'characteristic11' )

Related

count amount of transactions per type per ID in mysql

Having three tables
Users u
ID email
1 ..
2 ..
3 ..
4 ..
Transactions t
userID transactionID productID
1 1111 999
1 1112 897
2 1222 989
3 1333 989
4 1444 897
4 1114 897
Products p
productID type
999 Sports
989 Fashion
789 Fashion
897 Sports
Want to create a table in which I can find the total count of type per UserID. So ultimately I want to create the following
UserID Sports Fashion
1 1 1
2 0 1
3 0 1
4 2 0
Tried using the following code
SELECT u.email,
p.productID,
COUNT(CASE WHEN type = 'Sports' THEN 1 ELSE 0 END),
COUNT(CASE WHEN type = 'Fashion' THEN 1 ELSE 0 END)
from transactions t
LEFT JOIN users u ON u.ID = t.userID
LEFT JOIN products p ON t.productID = p.productID
group by u.email
Hence it does not work as hoped.
You can join and do conditional aggregation:
select
u.id,
sum(p.type = 'Sports') sports,
sum(p.type = 'Fashion') fashion
from users u
inner join transactions t on t.userid = u.id
inner join products p on p.id = t.productid
group by u.id
The problem with your original query is the use of count(): this aggregates function takes in account all non-null values: so both 1 and 0 are counted in. So you can either use a sum() (as shown above), or change the case expression to return null when there is no match, like so:
COUNT(CASE WHEN type = 'Sports' THEN 1 END),
COUNT(CASE WHEN type = 'Fashion' THEN 1 END)
Side note: presumably, you want inner join instead of left join. It does not look like your data has orphan records - and inner join is more efficient than left join.
Demo on DB Fiddle:
id | sports | fashion
-: | -----: | ------:
1 | 2 | 0
2 | 0 | 1
3 | 0 | 1
4 | 2 | 0

SQL selecting most recent row inside join

I have 2 tables companies and invoices
I want to select all companies with their most recent invoice price.
I don't seem to get it working.
This is what I tried:
SELECT *
FROM companies H INNER JOIN
invoices V
ON H.company_id = V.BC_ID
WHERE V.ISCOMMISSIE = 0 AND
V.DATE = (SELECT MAX(v2.DATE) FROM invoices v2 WHERE v2.BC_ID = V.BC_ID AND v2.ISCOMMISSIE = 0);
But the query loads very long and I don't know why.
The structure looks like this:
companies
company_id | company_name |
1 | company 1 |
2 | company 2 |
invoices
invoice_id | BC_ID | DATE | ISCOMMISSIE | price |
1 | 2 | 2020-01-01 | 0 | 340,40 |
2 | 1 | 2020-01-11 | 0 | 240,40 |
3 | 1 | 2020-01-08 | 0 | 250,30 |
4 | 2 | 2020-01-18 | 0 | 150,30 |
5 | 2 | 2020-01-19 | 1 | 150,30 |
The BC_ID is the same as the company_id and ISCOMMISSIE should be 0.
I want to select the most recent date.
Does someone have an idea on how to do this and also make the query as fast as possible?
http://sqlfiddle.com/#!9/2fc3a/1
Try:
SELECT H.*, V.*
FROM companies H
INNER JOIN invoices V ON H.company_id = V.BC_ID
INNER JOIN ( SELECT v2.BC_ID, MAX(v2.DATE) DATE
FROM invoices v2
WHERE v2.ISCOMMISSIE = 0
GROUP BY v2.BC_ID ) v3 ON v.BC_ID = v3.BC_ID
AND v.DATE = v3.DATE
AND V.ISCOMMISSIE = 0
And the index invoices (ISCOMMISSIE, BC_ID, DATE) may help...
Your query is fine:
SELECT *
FROM companies H INNER JOIN
invoices V
ON H.company_id = V.BC_ID
WHERE V.ISCOMMISSIE = 0 AND
V.DATE = (SELECT MAX(v2.DATE)
FROM invoices v2
WHERE v2.BC_ID = V.BC_ID AND
v2.ISCOMMISSIE = 0
);
For performance, you want an index on invoices(BC_ID, ISCOMMISSIE, DATE).
A good alternative is to use window functions:
SELECT *
FROM companies H INNER JOIN
(SELECT V.*,
ROW_NUMBER() OVER (PARTITION BY BC_ID ORDER BY DATE DESC) as seqnum
FROM invoices V
WHERE V.ISCOMMISSIE = 0
) V
ON H.company_id = V.BC_ID
WHERE seqnum = 1;
Depending on columns you need, you might not need to join with companies table. Also it is not needed to test for iscommissie = 0 two times, you can just test it one time in the subquery before joining.
See the query below :
SELECT i.*
FROM invoices i
JOIN (
SELECT i.bc_id, MAX(date) AS max_date
FROM invoices i
WHERE iscommissie = 0
GROUP BY i.bc_id
) i_temp ON i.bc_id = i_temp.bc_id AND i.date = i_temp.max_date
FIND A DEMO HERE
Another way to get the expected output:
select * from companies A join (
select * from invoices where (BC_ID,DATE) in(
select BC_ID as BC_ID, MAX(DATE) DATE from invoices where ISCOMMISSIE = 0 group by
BC_ID
))B on A.company_id=B.BC_ID;

MySQL GROUP_CONCAT having all values same

I have the following query:
SELECT * FROM charges WHERE (
charges.id not in (
select charge_id from billing_invoice_charges where is_deactivated = 0
)
)
I need to convert it into a JOIN QUERY, so I'm trying:
SELECT charges.id, group_concat(bic.is_deactivated) AS active_statuses
FROM charges LEFT JOIN billing_invoice_charges AS bic
ON bic.charge_id = charges.id GROUP BY charges.id
HAVING .......; <--- Check if all values are 1's
The output of GROUP_CONCAT is:
+------+-----------------+
| id | active_statuses |
+------+-----------------+
| 2 | 0,1,1 |
| 3 | 1,1 |
| 6 | 1 |
| 7 | 1,1,1 |
| 12 | 0,0,1 |
+------------------------+
How can I check if all the values if active_statuses in HAVING clause are 1's? This should give me the charges I'm looking for.
Try a HAVING clause which uses conditional aggregation to ensure that no non 1 statuses occur for each group.
SELECT
charges.id,
GROUP_CONCAT(bic.is_deactivated) AS active_statuses
FROM charges
LEFT JOIN billing_invoice_charges AS bic
ON bic.charge_id = charges.id
GROUP BY charges.id
HAVING SUM(CASE WHEN bic.is_deactivated <> 1 THEN 1 ELSE 0 END) = 0
Try:
SELECT
charges.id,
GROUP_CONCAT(bic.is_deactivated) AS active_statuses
FROM charges
LEFT JOIN billing_invoice_charges AS bic
ON bic.charge_id = charges.id
GROUP BY charges.id
HAVING active_statuses NOT LIKE '%0%'
which should ignore any results with a 0 in active_statuses

mySQL Query select / join not working as expected

let me preface that I've done a good bit of searching to see if this issue can be resolved but have yet to find an answer that relates, so I'll ask now. I have a query that works (mostly) to pull from two separate tables to show a total amount of pc models in each of four locations(these locations are in a single column, so I'm trying to select them as their own columns through aliases). The query pulls all required info, but not as I was hoping it would.
Expectation:
model | loc1 | loc2 | loc3 | loc4
5530 | 3 | 2 | 1 | 0
6440 | 10 | 0 | 3 | 20
Reality:
model | loc1 | loc2 | loc3 | loc4
5530 | 3 | null | null | null
5530 | null | 2 | null | null
5530 | null | null | 1 | null
etc..
Here is my query, any help to make the reality match the expectation if possible would be appreciated.
SELECT
a.model,
(select COUNT(a.model)
WHERE a.location = 'AoC-Reno') AS Reno,
(select count(a.model)
where a.location = 'AoC-Fargo') AS Fargo,
(select count(a.model)
where a.location = 'EoC') AS EoC,
(select count(a.model)
where a.location = 'APoC') as APoC
FROM assets AS a
join models m on m.model = a.model
WHERE m.type IN ('Desktop','Laptop')
AND a.model = m.model
AND a.status != 'Recycled'
GROUP BY m.model, a.location
ORDER BY m.model
Use conditional aggregation:
SELECT a.model,
SUM(a.location = 'AoC-Reno') AS Reno,
SUM(a.location = 'AoC-Fargo') AS Fargo,
SUM(a.location = 'EoC') AS EoC,
SUM(a.location = 'APoC') as APoC
FROM models m JOIN
assets a
on m.model = a.model
WHERE m.type IN ('Desktop', 'Laptop') and a.status <> 'Recycled'
GROUP BY m.model
ORDER BY m.model;
Your query has multiple issues:
You are using a "subquery", but there is no FROM clause.
You are aggregating by both model and location, but you want only one row per model.
The JOIN condition is both in the ON clause and repeated in the WHERE clause.
You can do a conditional aggregation (count)
SELECT a.model,
SUM(CASE WHEN a.location = 'AoC-Reno' THEN 1 END) reno,
SUM(CASE WHEN a.location = 'AoC-Fargo' THEN 1 END) farggo,
SUM(CASE WHEN a.location = 'EoC' THEN 1 END) eoc,
SUM(CASE WHEN a.location = 'APoC' THEN 1 END) apoc
FROM assets AS a JOIN models m
ON m.model = a.model
WHERE m.type IN ('Desktop','Laptop')
AND a.status != 'Recycled'
GROUP BY m.model
ORDER BY m.model

sum two rows and order by date / total

need some help to build a query, this is my current scheme:
users:
+----+------------+
| id | username |
+----+------------+
| 1 | rob |
| 2 | john |
| 3 | jane | <--- jane never has donated
| 4 | mike |
+----+------------+
donations:
+--------------------+------------+
| uid | amount | date |
+---------+----------+------------+
| 1 | 20 | 2013-10-10 |
| 2 | 5 | 2013-10-03 |
| 2 | 50 | 2013-09-25 |
| 2 | 5 | 2013-10-01 |
| 4 | 100 | 2012-10-01 | <-- past year
+---------+----------+------------+
Result I want:
+---------+-------------+---------+-------------+---------------+----------+
| id | username | amount | monthly | totalamount | total |
+---------+-------------+---------+-------------+ --------------+----------+
| 1 | rob | 20 | 1 | 20 | 1 |
| 2 | john | 60 | 3 | 60 | 3 |
| 3 | jane | 0 | 0 | 0 | 0 |
| 4 | mike | 0 | 0 | 100 | 1 |
+---------+-------------+-----------------------+---------------+----------+
This is my query:
SELECT
u.*,
COALESCE(sum(d.amount), 0) amount,
COUNT(d.uid) monthly,
COUNT(d.amount) as Total, <-- need to get sum all time donations and number of times donated
FROM users u
LEFT JOIN donations d
ON u.id = d.uid
AND (month(d.date), year(d.date)) = (month(CURDATE()), year(CURDATE()))
GROUP BY u.id ORDER BY u.id ASC
So i need to add 2 different sums from same data.
EDIT: http://sqlfiddle.com/#!2/20a974/9 schema and data
How I can do this?
For this we need to filter the data on the select and not on the join.
Remove this condition:
AND (month(d.date), year(d.date)) = (month(CURDATE()), year(CURDATE()))
and add this to the select:
SUM (CASE WHEN (month(d.date), year(d.date)) = (month(CURDATE()), year(CURDATE())) THEN 1 ELSE 0 END) as monthly
Edit:
whole query:
SELECT users.id, users.username,
COALESCE(sum(CASE WHEN (month(donations.date), year(donations.date)) = (month(CURDATE()), year(CURDATE())) THEN donations.amount ELSE 0 END), 0) monthly_sum,
COALESCE(sum(CASE WHEN (month(donations.date), year(donations.date)) = (month(CURDATE()), year(CURDATE())) THEN 1 ELSE 0 END), 0) monthly_amount,
COALESCE(sum(donations.amount), 0) total_sum,
count(*) total_amount
from users
left join donations
on donations.uid = users.id
group by users.id, users.username
http://sqlfiddle.com/#!2/20a974/20/0
For me the easiest way to think about the separately grouped information is to put it into separate queries and then just join the results back together. This is not likely to be the most efficient, but it helps to get something working.
select auo.id, auo.username,
coalesce(monthly_count, 0), coalesce(monthly_total, 0),
coalesce(total, 0), coalesce(total_amount, 0)
from aaa_users auo
left join (
select au.id as id, count(adm.amount) as monthly_count, SUM(adm.amount) as monthly_total
from aaa_users au join aaa_donations adm on au.id = adm.uid and adm.donate_date > GETDATE()-30
group by au.id
) as monthly on monthly.id = auo.id
left join (
select au.id as id, count(ady.amount) total, SUM(ady.amount) as total_amount
from aaa_users au join aaa_donations ady on au.id = ady.uid and ady.donate_date > getDate()-450
group by au.id
) as yearly on yearly.id = auo.id
As #CompuChip said, it's cleaner to just join to the donations table twice, but I have something wrong in my join logic as the values for john are getting duplicated. I think there would need to be a donations.id column to prevent the monthly and total donations from being combined. Anyway, here's an example even though it isn't working correctly
select au.id, au.username,
count(adm.amount), SUM(adm.amount) as monthly_total,
count(ady.amount), SUM(ady.amount) as total_amount
from aaa_users au
left outer join aaa_donations adm on au.id = adm.uid and adm.donate_date > GETDATE()-60
left outer join aaa_donations ady on au.id = ady.uid and ady.donate_date > getDate()-450
group by au.id, au.username
order by au.id, au.username
You can do another join to donations, giving it a different alias: LEFT JOIN donations d2 on d2.uid = u.id. Then sum over d2.amount for the last two fields, e.g.
SELECT u.*,
COALESCE(sum(d.amount), 0) amount,
COUNT(d.uid) monthly,
COUNT(d.amount) as Total,
COALESCE(sum(d2.amount), 0) amountAll,
COUNT(d2.uid) monthlyAll,
COUNT(d2.amount) as TotalAll
FROM users u
LEFT JOIN donations d ON u.id = d.uid AND (month(d.date), year(d.date)) = (month(CURDATE()), year(CURDATE()))
LEFT JOIN donations d2 ON u.id = d2.uid
GROUP BY u.id ORDER BY u.id ASC