I want to select a userid from a single table based on multiple and condition.
UserID FieldID Value
-----------------------------------
1 51 Yes
1 6 Dog
2 6 Cat
1 68 TX
1 69 78701
2 68 LA
What I'm trying to get in simple words:
if user search for texas or 78701,
Select userId where (68 = TX OR 69=78701) AND (51=yes) AND (6=Dog)
This should return user id 1.
This is what I tried, but returns null.
SELECT user_id FROM `metadata`
WHERE ( (`field_id` = '68' AND value LIKE '%TX%')
OR (`field_id` = '69' AND value LIKE '%78701%') )
AND `field_id` = '51' AND value = 'Yes'
AND `field_id` = '6' AND value = 'Dog'
You can use GROUP BY with a HAVING clause that makes use of multiple conditional aggregates:
SELECT UserID
FROM metadata
GROUP BY UserID
HAVING SUM(field_id = '68' AND value LIKE '%TX%' OR
field_id = '69' AND value LIKE '%78701%') >= 1
AND
SUM(field_id = '51' AND value = 'Yes') >= 1
AND
SUM(field_id = '6' AND value = 'Dog') >= 1
Demo here
Explanation: In MysQL a boolean expression, like
field_id = '51' AND value = 'Yes'
returns 1 when true, 0 when false.
Also, each predicate of HAVING clause is applied to the whole group of records, as defined by GROUP BY.
Hence, predicate:
SUM(field_id = '51' AND value = 'Yes') >= 1
is like saying: return only those UserID groups having at least one (>=1) record with
field_id = '51' AND value = 'Yes' -> true
Your table structure resembles attribute+value modelling, which essentially splits up the columns of a row into individual pairs, and has the side effect of very weak typing.
As you've noted, this can also make things tricky to query, since you have to reason over multiple rows in order to make sense of the original data model.
One approach could be to take an opinion of a 'primary' criterion, and then apply additional criteria by reasoning over the shredded data, joined back by user id:
SELECT DISTINCT m.user_id
FROM `metadata` m
WHERE ((`field_id` = '68' AND value LIKE '%TX%')
OR (`field_id` = '69' AND value LIKE '%78701%'))
AND EXISTS
(SELECT 1
FROM `metadata` m2
WHERE m2.user_id = m.user_id AND m2.field_id = '51' AND m2.value = 'Yes')
AND EXISTS
(SELECT 1
FROM `metadata` m3
WHERE m3.user_id = m.user_id AND m3.field_id = '6' AND m3.value = 'Dog');
However, IMO, it may be better to attempt to remodel the table like so (and ideally choose better descriptions for the attributes as columns):
UserID Field51 Field6 Field68 Field69
----------------------------------------
1 Yes Dog TX 78701
2 No Cat LA NULL
This will make things much easier to query.
This approach is typically slower than simply LEFT JOINing that table on each criterion, but it can make the problem simpler to comprehend...
SELECT userid
, MAX(CASE WHEN fieldid = 51 THEN value END) smoker
, MAX(CASE WHEN fieldid = 6 THEN value END) favourite_pet
, MAX(CASE WHEN fieldid = 68 THEN value END) state
, MAX(CASE WHEN fieldid = 69 THEN value END) zip
FROM eav
GROUP
BY userid;
You can use HAVING, or bundle this into a subquery to get the desired results.
SELECT user_id FROM metadata
WHERE
(field_id = '68' AND value LIKE '%TX%')
OR (field_id = '69' AND value LIKE '%78701%')
AND (field_id = '51' AND value = 'Yes')
AND (field_id = '6' AND value = 'Dog');
I have little bit changed your query and tried with the same,it gives output as, user_id is 1
Related
I have a query to select all childs (positions) based ob the parent id (order_id):
SELECT * FROM positions WHERE order_id = X
The result looks like:
ID | order_id | checked_1 | checked_2
1 | 1 | J. Doe | M. Doe
2 | 1 | | Mr. Tester
3 | 1 | J. Joe |
Now i need a query to check if the fields checked_1 & checked_2 are not empty of all related childs. So if all fields of the childs are filled, there shoulbe be appear a success notice in frontend.
What is the best way to "migrate" all childs, so that i can afterwards create the php query?
May try this to count if empty value is there:
SELECT count(*) AS count_empty FROM positions
WHERE order_id = X AND (IFNULL(checked_1, '') = '' OR
IFNULL(checked_2, '') = '')
So now, if count_empty is zero you may show the success message.
Try this:
SELECT 'SUCCESS' as '' FROM postions WHERES orderid = x AND (checked_1 IS NOT NULL OR checked_1 = '') AND (checked_2 IS NOT NULL OR checked_2 = '');
We can aggregate here by order, and then check for the presence of an empty checked_1 or checked_2 column, for a given order_id group. You may try the following query:
SELECT
order_id,
CASE WHEN SUM(CASE WHEN COALESCE(checked_1, '') = '' THEN 1 ELSE 0 END) +
SUM(CASE WHEN COALESCE(checked_2, '') = '' THEN 1 ELSE 0 END) = 0
THEN 'valid' ELSE 'invalid' END AS status
FROM positions
WHERE order_id = 1
GROUP BY order_id;
Note that I don't know if the missing values are NULL or actually just empty string. My query covers for both possibilities, but if these missing values be NULL then you can remove my calls to COALESCE. Also, you may remove GROUP BY if you plan on only running this query for a single order_id. Though should you have the need to run the query for multiple orders at a time, what I wrote above should come in handy.
one way to do this:
select MIN(CASE WHEN checked_1 = '' OR checked_2 = '' THEN 0 ELSE 1 END) from positions group by order_id
You will get a '0' if any field is empty or '1' if all are <> ''.
http://sqlfiddle.com/#!9/f7518d/1
Trying to calculate daily acceptance ratios from the 'connecting' table which has 4 fields with sample values:
date action sender_id recipient_id
'2017-01-05', 'request_link', 'frank', 'joe'
'2017-01-06', 'request_link', 'sally', 'ann'
'2017-01-07', 'request_link', 'bill', 'ted'
'2017-01-07', 'accept_link', 'joe', 'frank'
'2017-01-06', 'accept_link', 'ann', 'sally'
'2017-01-06', 'accept_link', 'ted', 'bill'
Because there are 0 accepts and 1 request on 01-05, its daily acceptance ratio should be 0/1 = 0. Similarly, the ratio for 01-06 should be 2/1, and it should be 1/1 for 01-07.
It is important however that each accept_link has a corresponding request_link where the sender_id of the request_link = the recipient_id of the accept_link (and vice versa). So here a self-join is required I believe to ensure that Joe accepts Frank's request, regardless of the date.
How can the below query be corrected so that the aggregation works correctly while retaining the required join conditions? Will the query calculate correctly as is if the two WHERE conditions are removed, or are they necessary?
SELECT f1.date,
SUM(CASE WHEN f2.action = 'accept_link' THEN 1 ELSE 0 END) /
SUM(CASE WHEN f2.action = 'request_link' THEN 1 ELSE 0 END) AS acceptance_ratio
FROM connecting f1
LEFT JOIN connecting f2
ON f1.sender_id = f2.recipient_id
LEFT JOIN connecting f2
ON f1.recipient_id = f2.sender_id
WHERE f1.action = 'request_link'
AND f2.action = 'accept_link'
GROUP BY f1.date
ORDER BY f1.date ASC
Expected output should look something like:
date acceptance_ratio
'2017-01-05' 0.0000
'2017-01-06' 2.0000
'2017-01-07' 1.0000
Thanks in advance.
Once again, I don't think you need to be using a self join here. Instead, just use conditional aggregation over the entire table, and count the number of requests and accepts which happened on each day:
SELECT t.date,
CASE WHEN t.num_requests = 0
THEN 'No requests available'
ELSE CAST(t.num_accepts / t.num_requests AS CHAR(50))
END AS acceptance_ratio
FROM
(
SELECT c1.date,
SUM(CASE WHEN c1.action = 'accept_link' AND c2.action IS NOT NULL
THEN 1 ELSE 0 END) AS num_accepts,
SUM(CASE WHEN c1.action = 'request_link' THEN 1 ELSE 0 END) AS num_requests
FROM connecting c1
LEFT JOIN connecting c2
ON c1.action = 'accept_link' AND
c2.action = 'request_link' AND
c1.sender_id = c2.recipient_id AND
c2.recipient_id = c1.sender_id
GROUP BY c1.date
) t
ORDER BY t.date
Note here that I use a CASE expression to handle divide by zero, which could occur should a certain day no requests. I also assume here that the same invitation will not be sent out more than once.
I am trying to apply a conditional condition inside ON clause of a LEFT JOIN. What I am trying to achieve is somewhat like this:
Pseudo Code
SELECT * FROM item AS i
LEFT JOIN sales AS s ON i.sku = s.item_no
AND (some condition)
AND (
IF (s.type = 0 AND s.code = 'me')
ELSEIF (s.type = 1 AND s.code = 'my-group')
ELSEIF (s.type = 2)
)
I want the query to return the row, if it matches any one of the conditions (Edit: and if it matches one, should omit the rest for the same item).
Sample Data
Sales
item_no | type | code | price
1 0 me 10
1 1 my-group 12
1 2 14
2 1 my-group 20
2 2 22
3 2 30
4 0 not-me 40
I want the query to return
item_no | type | code | price
1 0 me 10
2 1 my-group 20
3 2 30
Edit: The sales is table is used to apply special prices for individual users, user groups, and/or all users.
if type = 0, code contains username. (for a single user)
if type = 1, code contains user-group. (for users in a group)
if type = 2, code contains empty-string (for all users).
Use the following SQL (assumed, the the table sales has a unique id field as usual in yii):
SELECT * FROM item AS i
LEFT JOIN sales AS s ON i.sku = s.item_no
AND id = (
SELECT id FROM sales
WHERE item_no = i.sku
AND (type = 0 AND code = 'me' OR
type = 1 AND code = 'my-group' OR
type = 2)
ORDER BY type
LIMIT 1
)
Try following -
SELECT *,SUBSTRING_INDEX(GROUP_CONCAT(s.type ORDER BY s.type),','1) AS `type`, SUBSTRING_INDEX(GROUP_CONCAT(s.code ORDER BY s.type),','1) AS `code`,SUBSTRING_INDEX(GROUP_CONCAT(s.price ORDER BY s.type),','1) AS `price`
FROM item AS i
LEFT JOIN sales AS s
ON i.sku = s.item_no AND (SOME CONDITION)
GROUP BY i.sku
The goal
Get the lowest price of a product.
The problem
To illustrate my problem:
Row 1
Product_Id = 1
Product_Name = "iPhone 5"
Market_Name = "Walmart"
Product_Original_Price = "359.00"
Product_Promotional_Price = "319.00"
Product_State = 1 (is on offer)
Row 2
Product_Id = 1
Product_Name = "iPhone 5"
Market_Name = "Apple"
Product_Original_Price = "359.00"
Product_Promotional_Price = "0.00"
Product_State = 0 (isn't on offer)
Row 3
Product_Id = 1
Product_Name = "iPhone 5"
Market_Name = "BestBuy"
Product_Original_Price = "359.00"
Product_Promotional_Price = "299.00"
Product_State = 1 (is on offer)
The query of the next topic (What I have) is returning me zero as the best price of the problem illustrated above — but the best price is 299.00, by BestBuy, because zero at Product_Promotional_Price means that the product isn't on offer.
What I have
SELECT
MIN(LEAST(`Product_Original_Price`, `Product_Promotional_Price`)) as `minProductPrice`
[...]
Details
My query:
SELECT `pr`.`Product_Id` as `productId`,
`pr`.`Product_Name` as `productName`,
ROUND(CAST(MIN(`map`.`Product_Original_Price`) AS DECIMAL)/100,2)
as `minProductPrice`,
`prm`.`Product_Measure_Name` as `measureName`,
`prm`.`Product_Measure_Shortname` as `measureShortName`,
`pri`.`Product_Thumbnail_Image_Url` as `thumbnailUrl`,
`pr`.`Product_Markets_Quantity` as `numberOfMarketsThatHaveThisProduct`
FROM `bm_market_products` as `map`
JOIN `bm_products` as `pr` ON `map`.`Product_Id` = `pr`.`Product_Id`
JOIN `bm_products_category_relationship` as `car` ON `pr`.`Product_Id` =
`car`.`Product_Id`
JOIN `bm_product_categories` as `ca` ON `car`.`Category_Id` = `ca`.`Category_Id`
JOIN `bm_products_measure_relationship` as `prmr` ON `pr`.`Product_Id` =
`prmr`.`Product_Id`
JOIN `bm_product_measures` as `prm` ON `prmr`.`Measure_Id` =
`prm`.`Product_Measure_Id`
JOIN `bm_products_images` as `pri` ON `pr`.`Product_Id` = `pri`.`Product_Id`
WHERE ("" IS NULL OR `map`.`Product_State` = 0)
AND ("" IS NULL OR `ca`.`Category_Id` = 14)
GROUP BY `map`.`Product_Id`;
What the query returns:
What I already have tried:
Considering that Product_State determines whether a product is on offer or not, follow this fragment:
SELECT `pr`.`Product_Id` as `productId`,
`pr`.`Product_Name` as `productName`,
(IF(`map`.`Product_State` <> 0) THEN
MIN(LEAST(`Product_Original_Price`, `Product_Promotional_Price`))
ELSE (`map`.Product_Original_Price) as `minProductPrice`,
`prm`.`Product_Measure_Name` as `measureName`,
`prm`.`Product_Measure_Shortname` as `measureShortName`,
`pri`.`Product_Thumbnail_Image_Url` as `thumbnailUrl`,
`pr`.`Product_Markets_Quantity` as `numberOfMarketsThatHaveThisProduct`
[...]
Can you see the IF/THEN/ELSE? This is what has been added in relation to the previous query.
The above query doesn't work — syntax isn't correct, I know, but it was just to illustrate.
The solution
Gordon Linoff posted this answer and with it, I made this:
SELECT [...]
ROUND(CAST(MIN(CASE WHEN `map`.`Product_Promotional_Price` = 0 THEN `map`.`Product_Original_Price`
ELSE LEAST(`map`.`Product_Promotional_Price`, `map`.`Product_Original_Price`)
end) AS DECIMAL)/100,2) as `minProductPrice`,
[...]
To clarify, I just adapted his [Gordon Linoff] syntax to my scenario — with ROUND to rounding numbers and CAST to set a value as a certain type.
Worked perfectly!! Thanks!!
You need to fix your logic for getting the lowest price. A case statement is the best way. Here is an example:
select MIN(case when `Product_Promotional_Price` = 0 then `Product_Original_Price`
else least(`Product_Promotional_Price`, `Product_Original_Price`)
end)
put where Product_Original_Price!=0 and Product_Promotional_Price!=0 to the end;
I have 3 columns in CATEGORY TABLE for storing pre-calculated counts of records for it in another table PRODUCTS.
CATEGORY(c_id,name,c30,c31,c32)
c30=count for New Products (value 30)
c31 count for used products (value 31)
c32 count for Damaged products (value 32)
PRODUCT(p_id,c_id,name,condition)
condition can be 30,31 or 32.
I am thinking to write a single UPDATE statement so, it will update respective category count.
Althogh below statement is syntactically wrong, but i am looking for similar type of solution.
select case product.condition
when 30 then update category set category.c30=category.c30+1 where category.c_id=product.category3
when 31 then update category set category.c31=category.c31+1 where category.c_id=product.category3
when 32 then update category set category.c32=category.c32+1 where category.c_id=product.category3
end case
from product
where product.c_id=12
Any suggestion!
You can do this:
UPDATE CATEGORY c
INNER JOIN
(
SELECT
c_id,
SUM(CASE WHEN `condition` = 30 THEN 1 ELSE 0 END) c30,
SUM(CASE WHEN `condition` = 31 THEN 1 ELSE 0 END) c31,
SUM(CASE WHEN `condition` = 32 THEN 1 ELSE 0 END) c32
FROM product
GROUP BY c_id
) p ON c.c_id = p.c_id
SET c.c30 = p.c30,
c.c31 = p.c31,
c.c32 = p.c32;
SQL Fiddle Demo
You can join both the tables and then update the value in same join query.