Select row based on values in associated rows in EAV - mysql

I have two tables (EAV relationship), Customer (id,name) and CustomerVarchar (id,customer_id,attribute_id,value) with relationships below.
Customer hasMany CustomerVarchar
CustomerVarchar belongsTo Customer
I'd like to select a row in Customer based on two rows with specific values in CustomreVarchar. More specifically, I want to retrieve a row in Customer that has these two associated rows with the below condition (must have both of the associated rows in CustomerVarchar):
row 1 has `attribute_id`= 5 and `value`=`John`
row 2 has `attribute_id`= 7 and `value`=`Doe`
Is this possible?

You can use the following to return the customer_id from the customerVarchar table with all of the attributes and values:
select customer_id
from customerVarchar
group by customer_id
having
sum(case when attribute_id = 5 and value = 'John' then 1 else 0 end) > 0
and sum(case when attribute_id = 7 and value = 'Doe' then 1 else 0 end) > 0;
Then you can JOIN this to your customer table to return the customers with that data:
select c.id, c.name
from customer c
inner join
(
select customer_id
from customerVarchar
group by customer_id
having
sum(case when attribute_id = 5 and value = 'John' then 1 else 0 end) > 0
and sum(case when attribute_id = 7 and value = 'Doe' then 1 else 0 end) > 0
) v
on c.id = v.customer_id;
See SQL Fiddle with Demo

Related

Query to display distinct combination and then display count for each distinct combination

There is a transid field for which multiple rows will exist in this table with a list of config values. For all the transid's, I would like to retrieve all the distinct combination of transid, config_name and value fields that exist in the table group by count
I have join query which is not returning the result as expected. Below is the table structure, query used, result and expected result
Table
transid config_name value
1 payment_fee instant
2 eligible_account true
1 Block_intl_trans false
5 payment_fee provider_charge
1 eligible_account false
1 KycEligible 0
2 KycEligible 1
5 KycEligible 1
5 Block_intl_trans true
2 Block_intl_trans false
2 payment_fee provider_charge
5 eligible_account true
The above table structure implies that below are the combination of configuration values for each user.
transid KycEligible payment_fee eligible_account Block_intl_trans
1 0 instant false false
2 1 provider_charge true false
5 1 provider_charge true false
Below is the query that I used to Convert rows to columns and then group them per config_name(For every config_name, instead of multiple rows for each config key and value combination). Then select all distinct combination of KycEligible, configname and value combination that are present in the table and count of each distinct combination.
select
distinct
max(case when b.config_name = 'KycEligible' then b.config_value end) KycEligible,
max(case when b.config_name = 'payment_fee' then b.config_value end) payment_fee,
max(case when b.config_name = 'eligible_account' then b.config_value end) eligible_account,
max(case when b.config_name = 'Block_intl_trans' then b.config_value end) Block_intl_trans,
count(*) AS COUNT
from tableA b
where b.config_name in ('KycEligible', 'payment_fee', 'eligible_account', 'Block_intl_trans')
group by b.config_name
having count(*) > 1
Expected Result:
KycEligible payment_fee eligible_account Block_intl_trans Count
0 instant false false 1
1 provider_charge true false 2
My query is not returning the expected result. Can someone please help with this query?
I think you need something like this:
SELECT
KycEligible, payment_fee, eligible_account, Block_intl_trans, COUNT(*) CNT
FROM (
SELECT
(SELECT MAX(t0.config_value) FROM test t0 WHERE t0.config_name = 'KycEligible' AND t0.transid = t.transid) as KycEligible,
(SELECT MAX(t0.config_value) FROM test t0 WHERE t0.config_name = 'payment_fee' AND t0.transid = t.transid) as payment_fee,
(SELECT MAX(t0.config_value) FROM test t0 WHERE t0.config_name = 'eligible_account' AND t0.transid = t.transid) as eligible_account,
(SELECT MAX(t0.config_value) FROM test t0 WHERE t0.config_name = 'Block_intl_trans' AND t0.transid = t.transid) as Block_intl_trans
FROM
test t
GROUP BY t.transid
) dt
GROUP BY KycEligible, payment_fee, eligible_account, Block_intl_trans
;
This would give the following:
ycEligible payment_fee eligible_account Block_intl_trans CNT
0 instantâ—‹ false false 1
1 provider_charge true false 1
1 provider_charge true true 1
The result differs from expected in question as:
2 and 5 transids have different Block_intl_trans
1 and 5 transids have different payment_fee
1 and 2 transids have different eligible_account
I'm giving two fiddle example because the example data you provide is not consistent.
This fiddle is for transid=5 with Block_intl_trans data is true - consistent with your table data example: https://www.db-fiddle.com/f/r1imsYP8dQxkLSo5SkYcVK/3
This fiddle is for transid=5 with Block_intl_trans data is false - consistent with the configuration combination that you illustrate: https://www.db-fiddle.com/f/r1imsYP8dQxkLSo5SkYcVK/4
I'm guessing the unique combination would be coming from all of the config_name values. Here is the example query:
SELECT KycEligible, payment_fee, eligible_account, Block_intl_trans, COUNT(*) FROM
(SELECT transid,
GROUP_CONCAT(CASE WHEN config_name="KycEligible" THEN config_value END) AS "KycEligible",
GROUP_CONCAT(CASE WHEN config_name="payment_fee" THEN config_value END) AS "payment_fee",
GROUP_CONCAT(CASE WHEN config_name="eligible_account" THEN config_value END) AS "eligible_account",
GROUP_CONCAT(CASE WHEN config_name="Block_intl_trans" THEN config_value END) AS "Block_intl_trans"
FROM TableA
GROUP BY transid) V
GROUP BY KycEligible, payment_fee, eligible_account, Block_intl_trans;
Simple query with Max,
count in result can be based on your input data (transid=5, config=Block_intl_trans, value=false or value=true)
SELECT KycEligible, payment_fee, eligible_account, Block_intl_trans, COUNT(*) FROM
(
SELECT
transid,
max(CASE WHEN config_name="KycEligible" THEN value END) AS "KycEligible",
max(CASE WHEN config_name="payment_fee" THEN value END) AS "payment_fee",
max(CASE WHEN config_name="eligible_account" THEN value END) AS "eligible_account",
max(CASE WHEN config_name="Block_intl_trans" THEN value END) AS "Block_intl_trans"
FROM <YOUR_TABLE_NAME>
GROUP BY transid
) tmp
GROUP BY KycEligible, payment_fee, eligible_account, Block_intl_trans;

Group By MySQL Query that returns specific results with parameters

Before a user starts a private chat (between 2 members, not a group chat) I want to check and see if there is already a chat consisting of only these two members. In case they've deleted the chat on their end, when they go to message that same user again I want it to merge with the old chat instead of starting a duplicate chat for the same two members.
This is my structure
`chats` table
id created_time
1 [TIMESTAMP]
2 [TIMESTAMP]
`chats.parties` table
id chat_id member_id invited_by
1 1 1 0 // creator of chat
2 1 2 1
3 1 3 1
4 2 1 0
5 2 2 1
Group by chat_id but only return results that contain a row with member_id=1 and member_id=2; no more, no less.
In the case of the tables above, only the chat_id=2 row(s) would be returned because chat_id=1 contains a 3rd member.
Is this possible with raw SQL? I'd prefer to not loop through in php as it would take a while with a lot of chats.
Here are two different ways to get the result you are looking for:
-- using conditional aggregation
select chat_id from chat_parties
group by chat_id
having sum(case when member_id = 1 then 1 else 0 end) > 0
and sum(case when member_id = 2 then 1 else 0 end) > 0
and sum(case when member_id not in (1, 2) then 1 else 0 end) = 0
-- using a correlated subquery
select chat_id from chat_parties c1
where member_id in (1,2)
and not exists (
select 1 from chat_parties where chat_id = c1.chat_id and member_id not in (1,2)
)
group by chat_id having count(distinct member_id) = 2
Change the table names to fit your actual setup.
Using conditional COUNT
SQL Fiddle Demo
SELECT c.`id`
FROM chats c
LEFT JOIN chats_parties cp
ON c.`id`= cp.`chat_id`
GROUP BY c.`id`
HAVING COUNT(case when `member_id` = 1 then 1 END) >= 1
AND COUNT(case when `member_id` = 2 then 1 END) >= 1
AND COUNT(DISTINCT `member_id` ) = 2

Mysql Nested Query with two relational table

Table reports :
id (int)
catid (int)
pub_date (datetime)
Table categories :
id (int)
parent_id (int)
I want to get the report results with count(id) 's and group by catid's parent_id.
For example :
Date Count(parent_id = 1) Count(parent_id = 2) Count(parent_id = 3)
2014-02-24 2 5 8
We should have get the results in only a line per day.
Thanks too much in advance.
How about using the below query (SQL Fiddle):
SELECT SUM(CASE WHEN r.id = 1 THEN 1 ELSE 0 END) AS PID_1,
SUM(CASE WHEN r.id = 2 THEN 1 ELSE 0 END) AS PID_2,
SUM(CASE WHEN r.id = 3 THEN 1 ELSE 0 END) AS PID_3
FROM reports AS r
INNER JOIN categories AS c ON c.parent_id = r.id
GROUP BY r.pub_date

How to create date in MySQL from 'year' and 'month' values taken from different rows of the table and use it in 'where' clause

In MySQL database I have table item with some columns describing items, and another table itemExtendedFields. The second one has columns: id, itemId, name, value. In the itemExtendedFields additional information about items are stored.
Some items have expiry date that can be constructed from two values kept in two rows of itemExtendedFields table.
For example for an item with itemId = 34 i have following two rows in itemExtendedFields:
id | itemId | name | value
--------------------------
87 | 34 | ExpiryYear | 2014
88 | 34 | ExpiryMonth | 2
I need to write query that selects all items from items along with expiryYear and ExpiryMonth values from itemExtendedFields table in separate columns. Moreover i need to select items that will expire on given date (for example February 2014). I have no idea how to construct proper query. I cannot change the construction of the tables.
this will list all columns from items table along with the ExpiryYear and ExpiryMonth from the itemExtendedFields table.
SELECT a.*, b.ExpiryYear, b.ExpiryMonth
FROM items a
INNER JOIN
(
SELECT itemID,
MAX(CASE WHEN name = 'ExpiryYear' THEN `value` ELSE NULL END) ExpiryYear,
MAX(CASE WHEN name = 'ExpiryMonth' THEN `value` ELSE NULL END) ExpiryMonth
FROM itemExtendedFields
GROUP BY itemID
) b ON a.itemID = b.itemID
WHERE b.ExpiryYear = 2012 AND
b.ExpiryMonth = 2
It is not exactly clear what you are trying to do but since you have the values in a single column, it might be easier to pivot the data in the itemExtendedFields table first, then filter similar to this:
select *
from
(
select i.id,
MAX(case when f.name = 'ExpiryYear' then f.value end) ExpiryYear,
MAX(case when f.name = 'ExpiryMonth' then f.value end) ExpiryMonth
from item i
left join itemExtendedFields f
on i.id = f.itemid
group by i.id
) src
where ExpiryYear = 2014
and ExpiryMonth = 2
This pivoting is taking the values which are stored in a single column/multiple rows and places it into multiple columns with a single row. It might be possible to add more columns from the item table to the subquery or you can join this subquery back to the item table to get the additional details.
Similar to this:
select *
from item i
left join
(
select f.itemid,
MAX(case when f.name = 'ExpiryYear' then f.value end) ExpiryYear,
MAX(case when f.name = 'ExpiryMonth' then f.value end) ExpiryMonth
from itemExtendedFields f
group by f.itemid
) src
on i.id = src.itemid
where src.ExpiryYear = 2014
and src.ExpiryMonth = 2
SELECT ItemId,
MAX(CASE WHEN name = 'ExpiryYear' THEN value END) ExpiryYear,
MAX(CASE WHEN name = 'ExpiryMonth' THEN value END) ExpiryMonth
FROM itemExtendedFields
GROUP BY itemId
SQL Fiddle

How to return multi-count of specific values

I have a table like this:
id|category
where category is an integer between 0 and 3. How do I count how many rows are in each category. Is it possible in a single query?
F.x. if I have these rows:
a|3
b|1
d|1
f|0
g|2
the result shoud be:
(1,2,1,1)
ie. 1 in category = 0, 2 in category = 1 etc.
This will get you a row for each category.
SELECT category, COUNT(*) as catcount
FROM YourTable
GROUP BY category
To get output in the exact format you specified (1 row, 4 columns):
SELECT SUM(CASE WHEN category = 0 THEN 1 ELSE 0 END) AS cat0count,
SUM(CASE WHEN category = 1 THEN 1 ELSE 0 END) AS cat1count,
SUM(CASE WHEN category = 2 THEN 1 ELSE 0 END) AS cat2count,
SUM(CASE WHEN category = 3 THEN 1 ELSE 0 END) AS cat3count
FROM YourTable