mysql conditional field update - mysql

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.

Related

MySQL Select User id based on multiple AND conditions on same table

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

Calculating acceptance_ratio with LEFT JOIN and SELF JOIN and aggregate function

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.

MySQL Select find similar item that has stock

I have this resultset of my table of products, while trying to return correct results in the last two columns, similar_sku_exists and similar_sku_in_stock.
The goal is, to first determine whether a similar sku exists in the table and return yes/no.
second to determine if either one of the similar skus have stock and return that sku name/s.
A similar sku is defined by the same sku name + first letter of grade.
Eg, all instances of "ABC-11-A" will be similar as they bear the same condition of new. Likewise "ABC-11-B" will be similar in that they are all refurb condition.
id sku condition grade stock similar_sku_exists similar_sku_in_stock
1 ABC-11-A1 new A1 0 yes ABC-11-A2
2 ABC-11-A2 new A2 10 yes
3 ABC-11-B1 refurb B1 10 yes
4 ABC-11-B2 refurb B2 0 yes ABC-11-B1|ABC-11-B2-LP
5 ABC-11-B2-LP refurb B2-LP 10 yes
6 DEF-2-F-A1 new A1 0 no
7 DEF-2-G-B1 refurb B1 10 yes
8 DEF-2-G-B2 refurb B2 0 yes DEF-2-G-B1
So far i have this query but that dosn'nt seem to return correct results
select
id,
sku,
`condition`,
grade,
stock,
case when left(p.sku, length(p.sku)-length(p.grade)+1)
in (select left(p.sku, length(p.sku)-length(p.grade)+1))
then 'yes' else 'no' end as similar_sku_exists,
if(p.stock = 0,
case when left(p.sku, length(p.sku)-length(p.grade)+1)
in (select left(p.sku, length(p.sku)-length(p.grade)+1) and p.stock >0
)
then group_concat(distinct(p.sku) separator '|')
else '' end,'') as similar_sku_in_stock
from products as p
Much appreciated any tought
Here is a method to get the similar skus in stock:
select p.*,
(select group_concat(p2.sku)
from products p2
where substring_index(p2.sku, '-', 1) = substring_index(p.sku, '-', 1) and
left(subsctring_index(p2.sku, '-', -1), 1) = left(substring_index(p.sku, '-', -1), 1) and
p2.sku <> p.sku and
stock > 0
) similar_skus_in_stock
from products p;
A similar query can be used to determine if similar skus exist.

How do I fetch count of two columns for a given criteria grouped by another column?

DataBase: SQL Fiddle
Query needed: To return the number of women and men of age 25-35 years for each Insurance Company.
My Progress:
CREATE VIEW MenInAge AS
SELECT p.pname,p.pid,p.cid
FROM Patient p
WHERE p.gender = 'm' and p.age between 25 and 35;
CREATE VIEW WomenInAge AS
SELECT p.pname,p.pid,p.cid
FROM Patient p
WHERE p.gender = 'f' and p.age between 25 and 35;
CREATE VIEW MenInAgeCount AS
SELECT m.cid, COUNT(m.pid) as c
FROM MenInAge m
GROUP BY m.cid;
CREATE VIEW WomenInAgeCount AS
SELECT w.cid, COUNT(w.pid) as c
FROM WomenInAge w
GROUP BY w.cid;
How do I show for every InsuranceCompany.cid the WomenInAgeCount.c and the MenInAgeCount.c columns?
Explanation:
You have to join the tables InsuranceCompanies and Patient using the LEFT OUTER JOIN by joining the records on cid column in both tables and also apply the filter to select only patients between age 25 and 35 (including those boundary values). The CASE statement simply checks whether the patient is male or female and computes two different columns by assigning values of 1 if the values match and 0 if the values don't match. Finally you have to group the result by cname to fetch the count by insurance company name.
Explanation about CASE:
In the CASE expression, the query states WHEN gender field value is f assign the column female with the value 1. The value 1 is hard coded because it means the query found 1 row matching the gender='f' record and this also represent 1 person. You can also state ELSE 0 but it is implicit so not necessary to specify that. This CASE expression evaluates for every record in the query result. Finanlly, you will get all the rows with female column containing either 1 or 0. When you sum this column female, you will get the total number of females, the same logic goes for male column.
With COALESCE:
COALESCE here replaces any NULL values with the given value in the second parameter (here in this case zero).
Click here to view the demo in SQL Fiddle.
Script:
SELECT ic.cname
, COALESCE(SUM(CASE WHEN gender = 'f' THEN 1 END), 0) female
, COALESCE(SUM(CASE WHEN gender = 'm' THEN 1 END), 0) male
FROM InsuranceCompanies ic
LEFT OUTER JOIN Patient p
ON p.cid = ic.cid
AND age BETWEEN 25 AND 35
GROUP BY ic.cname;
Output:
CNAME FEMALE MALE
---------- ------ ----
Clalit Inc 0 2
Harel Inc 2 0
Without COALESCE:
Click here to view the demo in SQL Fiddle
Script:
SELECT ic.cname
, SUM(CASE WHEN gender = 'f' THEN 1 END) female
, SUM(CASE WHEN gender = 'm' THEN 1 END) male
FROM InsuranceCompanies ic
LEFT OUTER JOIN Patient p
ON p.cid = ic.cid
AND age BETWEEN 25 AND 35
GROUP BY ic.cname;
Output:
CNAME FEMALE MALE
---------- ------ ----
Clalit Inc NULL 2
Harel Inc 2 NULL
How about a JOIN?
SELECT I.cname, ISNULL(W.c,0) AS WomenCount, ISNULL(M.c,0) as MenCount
FROM InsuranceCompanies AS I
LEFT JOIN MenInAgeCount AS M ON M.cid = I.cid
LEFT JOIN WomenInAgeCount AS W ON W.cid = I.cid
LEFT JOIN here in case the Men or Women view don't contain an entry for each row in the InsuranceCompanies table. The ISNULL is for SQL Server, but you can modify for MySQL, Oracle as needed.
this should give you some help -
select count(pid) as numPatients, cid, gender
from patient
group by cid, gender

How to group and count in one row?

To simplify, I have tree tables: products, products-vs-orders, orders
products fields : 'ProductID', 'Name', 'isGratis', ...
products-vs-orders fields : 'ProductID', 'OrderID'
orders fields : 'OrderID', 'Title', ...
Actually, I have a query like this:
SELECT orders.OrderID, orders.Title, COUNT(`products`.`isGratis`) AS "Quantity", `products`.`isGratis`
FROM `orders`, `products-vs-orders`, `products`
WHERE `orders`.`OrderID` = `products-vs-orders`.`OrderID` AND `products-vs-orders`.`ProductID` = `products`.`ProductID`
GROUP BY `products`.`PackID`, `products`.`isGratis`
This query works and return this surch of result:
OrderID, Title, Quantity, isGratis
1 My Order 20 0
1 My Order 3 1
2 An other 8 0
2 An other 1 1
How can I retrieve the count of products 'gratis' and 'paid' in to separate cols ?
OrderID, Title, Qt Paid, Qt Gratis
1 My Order 20 3
2 An other 8 1
Try this:
SELECT
orders.OrderID,
orders.Title,
COUNT(orders.OrderId) - SUM(`products`.`isGratis`) AS "Qt Paid",
SUM(`products`.`isGratis`) AS "Qt Gratis"
WHERE `orders`.`OrderID` = `products-vs-orders`.`OrderID`
AND `products-vs-orders`.`ProductID` = `products`.`ProductID`
GROUP BY `products`.`PackID`
SUM(products.isGratis) depends on the fact that a boolean value is internally represented by the database as a single numeric bit, so false = 0 and true = 1.
This may not be the case in ALL DB implementations. Therefore, SUM over a boolean field may cause implementation-dependent behavior.
Converting the boolean into actual 0 and 1 values before summing should be more proper:
SELECT orders.OrderID, orders.Title,
SUM(CASE WHEN products.isGratis THEN 0 ELSE 1 END) AS "Qt Paid",
SUM(CASE WHEN products.isGratis THEN 1 ELSE 0 END) AS "Qt Gratis"
FROM orders INNER JOIN `products-vs-orders` ON (orders.OrderID = `products-vs-orders`.OrderID)
INNER JOIN products ON (`products-vs-orders`.ProductID = products.ProductID)
GROUP BY orders.OrderID, orders.Title
select orderid,title,sum(if(isgratis=0,quantity,0)) as paid,sum(if(isgratis=1,quantity,0)) as gratis from ...