Zend Framework 1 query, ordering results by satisfied criteria - mysql

I've to extract all products from "product" table (DBMS: MySQL, Adapter: PDO), ordering result by the number of filtering criteria that are matched.
This is an example of raw SQL query (but I'll use Zend_DB classes and adapters):
SELECT *
FROM product
WHERE price < 300
AND price > 100
AND discount = TRUE
AND used = FALSE
AND type = MEAL
and a lot of other optionals filter criteria that end user could introduce from the UI.
All the filter criteria (where conditions in the query) could be optionally matched by the user in the form of the web app, and the GOAL the my algorithm is to order the results from the most matching criteria product to the product that match at least 2 criteria.
I'm using Zend Framework 1, and my question is:
Is there any Zend class that could help me in this particular algorithm?
If no, could anyone suggest a solution for this problem?
I've tried a crude solution where I'll compose the query considering all the possible combination of the criteria, but considering that there are a lot of criteria, the algorithm complexity increases so much, so I suppose that an alternative may exists.
Thanks

Something like...
SELECT * FROM (
SELECT P.*,
case when price < 300 and price > 100 then 1 else 0 end +
case when discount = true then 1 else 0 end +
case when used = false then 1 else 0 end +
case when type = 'MEAL' then 1 else 0 end +
... (for each possible outcome) as Matches
FROM product p)
Where matches > 2
Order by Matches descending

Related

MySQL Case function behave strange and inconstent

We are using MySQL 8 as our java application DB.
We have a query with the following format:
select
id,
group_concat(NAME ORDER BY ID separator ',,') AS Code,
CASE
WHEN MAX(p.VARIABLEfactor) = 1 THEN MAX(i.factor) ELSE MAX(p.factor) END AS factor
from MA_TABLE
join TABLE_P p on (...)
join TABLE_I i on (...)
group by id
The query worked very fine in our development environments until deploy with client where the factor column is getting null.
We have run the same query in the client environment from MySQL Workbench and we can see that the factor column is getting well populated.
After some debugging,we changed :
CASE
WHEN MAX(p.VARIABLEfactor) = 1 THEN MAX(i.factor) ELSE MAX(p.factor) END AS factor
to
MAX(
WHEN p.VARIABLEfactor = 1 THEN i.factor ELSE p.factor END ) AS factor,
and the query worked correctly.
Any help here please?
From your explanation I gather that you don't understand the difference of your two case expressions. But they are very different. Let's look at an example for one ID:
ID
VARIABLEfactor
i.factor
p.factor
100
0
null
10
100
1
null
20
Your expression
CASE WHEN MAX(p.VARIABLEfactor) = 1 THEN MAX(i.factor) ELSE MAX(p.factor) END
looks at the maximum VARIABLEfactor, which is 1, so the THEN case applies and the maximum i.factor is returned. This is null, as all i.factor are null.
Your expression
MAX(WHEN p.VARIABLEfactor = 1 THEN i.factor ELSE p.factor END)
looks at each row's VARIABLEfactor. For the first row this is 0, so the ELSE case applies and p.factor 10 is used. For the second row the VARIABLEfactor is 1, so its i.factor null gets used. Of these you take the maximum, which is 10.
To recap: The first expression is just a CASE expression on the aggregation results. It returns null here. The second expression is a conditional aggregation. It returns 10 for the sample data.

SQL query - find values that meet m conditions out of n

Is there any way to find values that meet any m conditions out of given n conditions? For instance, if there are 10 conditions, and I want to find values that meet any 2 of them.
Use CASE expressions in the WHERE clause, 1 for each condition like this:
WHERE 2 =
CASE WHEN <condition1> THEN 1 ELSE 0 END +
CASE WHEN <condition2> THEN 1 ELSE 0 END +
CASE WHEN <condition3> THEN 1 ELSE 0 END +
..........................................
You can change the = sign to > or < to meet your requirement.
There is. It's not gonna be pretty though.
Start with your conditions as SELECT expressions.
select T.*,
case
when T.SOME_NUMERIC_COLUMN > 0 then 1
else 0
end IS_POSITIVE,
(select sign(COUNT(*))
from SOME_OTHER_TABLE
where parent_id = T.ID) HAS_CHILDREN
...
from SOME_TABLE T
Design these expression in such a way that you get 1 when a condition is met and 0 when it's not.
Then sum up the score and add a WHERE clause.
SELECT *
FROM (
SELECT R.*,
IS_POSITIVE + HAS_CHILDREN + ... SCORE
FROM (...) R)
WHERE SCORE > 2
Of course you're gonna pay a hefty price in performance for this. You won't be able to use your conditions directly to limit the resultset so I'd expect the execution plans to be extremely disappointing. That said, it's not like what you have in mind is a standard task for RDBMS so it should be enough for a proof of concept.

Get Multi Columns Count in Single Query

I am working on a application where I need to write a query on a table, which will return multiple columns count in a single query.
After research I was able to develop a query for a single sourceId, but what will happen if i want result for multiple sourceIds.
select '3'as sourceId,
(select count(*) from event where sourceId = 3 and plateCategoryId = 3) as TotalNewCount,
(select count(*) from event where sourceId = 3 and plateCategoryId = 4) as TotalOldCount;
I need to get TotalNewCount and TotalOldCount for several source Ids, for example (3,4,5,6)
Can anyone help, how can I revise my query to return a result set of three columns including data of all sources in list (3,4,5,6)
Thanks
You can do all source ids at once:
select source_id
sum(case when plateCategoryId = 3 then 1 else 0 end) as TotalNewCount,
sum(case when plateCategoryId = 4 then 1 else 0 end) as TotalOldCount
from event
group by source_id;
Use a where (before the group by) if you want to limit the source ids.
Note: The above works in both Vertica and MySQL, and being standard SQL should work in any database.

Combine 2 SELECTs into one SELECT in my RAILS application

I have a table called ORDEREXECUTIONS that stores all orders that have been executed. It's a multi currency application hence the table has two columns CURRENCY1_ID and CURRENCY2_ID.
To get a list of all orders for a specific currency pair (e.g. EUR/USD) I need to lines to get the totals:
v = Orderexecution.where("is_master=1 and currency1_id=? and currency2_id=? and created_at>=?",c1,c2,Time.now()-24.hours).sum("quantity").to_d
v+= Orderexecution.where("is_master=1 and currency1_id=? and currency2_id=? and created_at>=?",c2,c1,Time.now()-24.hours).sum("unitprice*quantity").to_d
Note that my SUM() formula is different depending on the the sequence of the currencies.
e.g. If I want the total ordered quantities of the currency pair USD it then executes (assuming currency ID for USD is 1 and EUR is 2.
v = Orderexecution.where("is_master=1 and currency1_id=? and currency2_id=? and created_at>=?",1,2,Time.now()-24.hours).sum("quantity").to_d
v+= Orderexecution.where("is_master=1 and currency1_id=? and currency2_id=? and created_at>=?",2,1,Time.now()-24.hours).sum("unitprice*quantity").to_d
How do I write this in RoR so that it triggers only one single SQL statement to MySQL?
I guess this would do:
v = Orderexecution.where("is_master=1
and ( (currency1_id, currency2_id) = (?,?)
or (currency1_id, currency2_id) = (?,?)
)
and created_at>=?"
,c1, c2, c2, c1, Time.now()-24.hours
)
.sum("CASE WHEN currency1_id=?
THEN quantity
ELSE unitprice*quantity
END"
,c1
)
.to_d
So you could do
SELECT SUM(IF(currency1_id = 1 and currency2_id = 2, quantity,0)) as quantity,
SUM(IF(currency2_id = 1 and currency1_id = 2, unitprice * quantity,0)) as unitprice _quantity from order_expressions
WHERE created_at > ? and (currency1_id = 1 or currency1_id = 2)
If you plug that into find_by_sql you should get one object back, with 2 attributes, quantity and unitprice_quantity (they won't show up in the output of inspect in the console but they should be there if you inspect the attributes hash or call the accessor methods directly)
But depending on your indexes that might actually be slower because it might not be able to use indexes as efficiently. The seemly redundant condition on currency1_id means that this would be able to use an index on [currency1_id, created_at]. Do benchmark before and after - sometimes 2 fast queries are better than one slow one!

Return records where at least 2 or more boolean columns are 'true' in MySQL

I have a table that stores information about vendors. In it, there about 10 boolean columns that indicate what departments the vendor belongs to. The vendor can belong to more than one department.
How do I query the table to find any vendors that have more than one of the boolean columns marked true?
My tbl_vendor_departments Columns:
vendor_id (varchar) vendor_name (varchar) clothing(bool) automotive(bool) lawn_garden(bool) tools(bool)... and so on.
In MySQL, boolean is just an integer 0 or 1. So you can then use arithmetic:
SELECT ...
FROM tbl_vendor_departments
WHERE (clothing + automotive + lawn_garden + tools) >= 2
Strictly speaking, in MySQL zero is false and any non-zero value is true. So you could get unexpected results if your "boolean" columns contain true values larger values than 1 (e.g. 42). To compensate, you can invert true values to false, and then invert again. This should convert 42 to 0 and then back to 1. Use parentheses to control operator precedence.
SELECT ...
FROM tbl_vendor_departments
WHERE (
(NOT NOT clothing) +
(NOT NOT automotive) +
(NOT NOT lawn_garden) +
(NOT NOT tools)
) >= 2
Don't try this with other SQL implementations, though. Most do not implement boolean in a way that is compatible with integer arithmetic, so the above query would (and should, per ANSI SQL) generate an error. For those, you could use CASE expressions to convert from true to 1:
SELECT ...
FROM tbl_vendor_departments
WHERE (
CASE clothing WHEN true THEN 1 ELSE 0 END +
CASE automotive WHEN true THEN 1 ELSE 0 END +
CASE lawn_garden WHEN true THEN 1 ELSE 0 END +
CASE tools WHEN true THEN 1 ELSE 0 END
) >= 2
I think booleans are actually treated as tiny ints (0 or 1). In that case, you can add up all boolean fields and check if the result > 1.
....
WHERE
clothing + automotive + lawn_garden + .... + tools > 1
I do not know if it is a guaranteed feature (but suspect that it is) but boolean fields set to true will, in my experience, always be 1 and when false, zero. So just adding all the fields up and selecting the records where the total is 2 or more will do the job.