Can you adjust SQL query (outcome) based on conditions in table? - mysql

I am looking for a (sub-)query and/or If statement in a MYSQL query. After Googling and searching, I THINK it should be possible with a CASE statement, but I am not sure.
My Questions are:
1: is it possible at all?
2: If it is: Could someone point me in the right direction on how to achieve this?
ISSUE:
I have a table with PRODUCTS. Each PRODUCT can have a
-'in_stock' value of 0 or 1 (in stock NO or YES).
-'status' with possible value of 'NULL' or 'DELETED'.
A product that has the status 'DELETED' can NOT be ordered, UNLESS in is in stock.
QUESTION
Is it possible to apply (some kind of) IF statement in a SQL query? Something like:
SELECT
*
FROM
products
WHERE
CASE in_stock = 0
THEN (AND status != 'DELETED')
ELSE ()
I hope someone can help me out. Thanks in advance!

Consider:
SELECT *
FROM products
WHERE
(in_stock = 0 AND status <> 'DELETED') OR
in_stock > 0;

You can use AND and OR in a WHERE clause. The following query gives you all products that are either in stock or have not been deleted (or both):
SELECT *
FROM products
WHERE in_stock = 1 OR status <> 'DELETED';
Another way of writing the same:
SELECT *
FROM products
WHERE NOT (in_stock = 0 AND status = 'DELETED');
You can have many conditions that you combine with AND, OR, and parentheses.

Related

mysql Query performance is low

I have a query which is running for around 2 hours in last few days. But
before that it took only 2 to 3 minutes of time. i could not able to find
the reason for its sudden slowness. Can any one help me on this?
Please find the below query explain plan[![enter image description here][1]]
[1]...
select
IFNULL(EMAIL,'') as EMAIL,
IFNULL(SITE_CD,'') as SITE_CD,
IFNULL(OPT_TYPE_CD,'') as OPT_TYPE_CD,
IFNULL(OPT_IN_IND,'') as OPT_IN_IND,
IFNULL(EVENT_TSP,'') as EVENT_TSP,
IFNULL(APPLICATION,'') as APPLICATION
from (
SELECT newsletter_entry.email email,
newsletter.site_cd site_cd,
REPLACE (newsletter.TYPE, 'OPTIN_','') opt_type_cd,
CASE
WHEN newsletter_event_temp.post_status = 'SUBSCRIBED' THEN 'Y'
WHEN newsletter_event_temp.post_status = 'UNSUBSCRIBED' THEN
'N'
ELSE ''
END
opt_in_ind,
newsletter_event_temp.event_date event_tsp,
entry_context.application application
FROM amg_toolkit.newsletter_entry,
amg_toolkit.newsletter,
(select NEWSLETTER_EVENT.* from amg_toolkit.NEWSLETTER_EVENT,
amg_toolkit.entry_context where newsletter_event.EVENT_DATE >= '2017-07-11
00:01:23' AND newsletter_event.EVENT_DATE < '2017-07-11 01:01:23' and
newsletter_event.ENTRY_CONTEXT_ID = entry_context.ENTRY_CONTEXT_ID and
entry_context.APPLICATION != 'feedbackloop') newsletter_event_temp,
amg_toolkit.entry_context
WHERE newsletter_entry.newsletter_id = newsletter.newsletter_id
AND newsletter_entry.newsletter_entry_id =
newsletter_event_temp.newsletter_entry_id
AND newsletter.TYPE IN ('OPTIN_PRIM', 'OPTIN_THRD', 'OPTIN_WRLS')
AND newsletter_event_temp.entry_context_id NOT IN
(select d.ENTRY_CONTEXT_ID from amg_toolkit.sweepstake a,
amg_toolkit.sweepstake_entry b, amg_toolkit.user_entry c,
amg_toolkit.entry_context d where a.exclude_data = 'Y' and
a.sweepstake_id=b.sweepstake_id and b.USER_ENTRY_ID=c.USER_ENTRY_ID and
c.ENTRY_CONTEXT_ID = d.ENTRY_CONTEXT_ID)
AND newsletter_event_temp.entry_context_id =
entry_context.entry_context_id
AND newsletter_event_temp.event_date >= '2017-07-11 00:01:23'
AND newsletter_event_temp.event_date < '2017-07-11 01:01:23') a;`
[1]: https://i.stack.imgur.com/cgsS1.png
dont use .*
select only the columns of data you are using in your query.
Avoid nested sub selects if you dont need them.
I don't see a need for them in this query. You query the data 3 times this way instead of just once.
Slowness can be explained by an inefficient query haveing to deal with tables that have a growing number of records.
"Not in" is resource intensive. Can you do that in a better way avoiding "not in" logic?
JOINs are usually faster than subqueries. NOT IN ( SELECT ... ) can usually be turned into LEFT JOIN ... WHERE id IS NULL.
What is the a in a.exclude_data? Looks like a syntax error.
These indexes are likely to help:
newsletter_event: INDEX(ENTRY_CONTEXT_ID, EVENT_DATE) -- in this order
You also need it for newsetter_event_temp, but since that is not possible, something has to give. What version of MySQL are you running? Perhaps you could actually CREATE TEMPORARY TABLE and ADD INDEX.

MySQL GROUP BY HAVING returns nothing

I have many categories with the same name and parent in my Opencart database (duplicates). Need to find all of them. That's my query:
SELECT *
FROM
(SELECT `oc_category`.category_id,
`oc_category`.parent_id,
`oc_category_description`.name
FROM `oc_category`, `oc_category_description`
WHERE `oc_category`.category_id = `oc_category_description`.category_id
) cats
GROUP BY `cats`.parent_id, `cats`.name
HAVING COUNT(*) > 1
But this query returns nothing. Please tell me if I'm wrong.
No problem with the query, it does work, check this out:
http://sqlfiddle.com/#!9/3d170/4
Please fiddle with that and populate it with the data which produces no records, and add it to your question.

SELECT different row IF no rows returned

This is kind of hard to headline, but given this statement:
SELECT DISCOUNT FROM people_discounts WHERE ID = ? AND PGROUP = ?
Let's assume I enter ID 5 and PGROUP 2. This row does not exist in the table (fetch returns false in the case of PHP and mysqli). In that case I want the row that would have been presented had the statement been:
SELECT DISCOUNT FROM people_discounts WHERE ID = ? AND PGROUP = 0
With ID still being 5, but PGROUP "fallen back" to 0 instead, which let's assume always exists.
Is this possible with some kind of IF, CASE or maybe some other keyword that I'm not aware of?
I know this can of course be accomplished by running a second statement in the case of no rows, but that's not what I'm trying to do.
presuming the query should only ever return a single row
select discount from people_discount
where id = ? and (pgroup = ? or pgroup = 0)
order by pgroup desc
limit 1;
If limit seems "impure" you could use other tricks.
This might be more portable though it assumes that discount is never negative:
select abs(max(case when proup <> 0 then discount else -discount end))
from people_discount
where id = ? and pgroup in (?, 0)
group by id
Not every platform has a top/limit/fetch option. And some people object to non-set-based queries. I'm simply offering a hack of sorts with those people in mind or at least as an exercise in thinking about problems in a different way.
The idea is just to assume there cannot be a negative discount and to use that end of the number scale to temporarily "store" the fallback discount for pgroup 0. MAX() accomplishes the equivalent of sorting and limiting by favoring the positive value when both are present. ABS() restores the sign of the result.

Avoiding recomputing values in WHERE statement already computed in column list

I would really love to not have to recompute values in my WHERE statement that have already been computed in the column list.
For example, I have to do this:
SELECT albumId, fnAlbumGetNumberOfPhotos(album.albumId) AS albumPhotoCount
FROM album
WHERE albumIsActive = 1
AND fnAlbumGetNumberOfPhotos(album.albumId) > 0
But would like to know why I can't do it like this, or if there are any other workarounds that don't require calling the function again:
SELECT albumId, fnAlbumGetNumberOfPhotos(album.albumId) AS albumPhotoCount
FROM album
WHERE albumIsActive = 1
AND albumPhotoCount > 0
This is a simplified version of a much more complex query, but it illustrates the problem.
Surely there must be a way to handle this without multiple function calls??
Help is greatly appreciated. Thanks
Use HAVING.
SELECT albumId, fnAlbumGetNumberOfPhotos(album.albumId) AS albumPhotoCount
FROM album
WHERE albumIsActive = 1
HAVING albumPhotoCount > 0
HAVING is calculated after the SELECT, when albumPhotoCount exists.

SQL Query returns fake duplicate results?

I've been trying to write a few little plugins for personal use with WHMCS. Essentially what I'm trying to do here is grab a bunch of information about a certain order(s), and return it as an array in Perl.
The Perl bit I'm fine with, it's the MySQL query I've formed that's giving me stress..
I know it's big and messy, but what I have is:
SELECT tblhosting.id, tblhosting.userid, tblhosting.orderid, tblhosting.packageid, tblhosting.server, tblhosting.domain, tblhosting.username, tblorders.invoiceid, tblproducts.gid, tblservers.ipaddress, tblinvoices.status
FROM tblhosting, tblproducts, tblorders, tblinvoices, tblservers
WHERE tblorders.status = 'Pending'
AND tblproducts.gid = '2'
AND tblservers.id = tblhosting.server
AND tblorders.id = tblhosting.orderid
AND tblinvoices.id = tblorders.invoiceid
AND tblinvoices.status = 'Paid'
I don't know if this /should/ work, but I assume I'm on the right track as it does return what I'm looking for, however it returns everything twice.
For example, I created a new account with the domain 'sunshineee.info', and then in PHPMyAdmin ran the above query.
id userid orderid packageid server domain username invoiceid gid ipaddress status
13 7 17 6 1 sunshineee.info sunshine 293 2 184.22.145.196 Paid
13 7 17 6 1 sunshineee.info sunshine 293 2 184.22.145.196 Paid
Could anyone give me a heads up on where I've gone wrong with this one.. Obvioiusly (maybe not obviously enough) I want this as only one row returned per match.. I've tried it with >1 domain in the database and it returned duplicates for each of the matches..
Any help would be much appreciated
:)
SELECT distinct tblhosting.id, tblhosting.userid, tblhosting.orderid, tblhosting.packageid, tblhosting.server, tblhosting.domain, tblhosting.username, tblorders.invoiceid, tblproducts.gid, tblservers.ipaddress, tblinvoices.status
FROM tblhosting, tblproducts, tblorders, tblinvoices, tblservers
WHERE tblorders.status = 'Pending'
AND tblproducts.gid = '2'
AND tblservers.id = tblhosting.server
AND tblorders.id = tblhosting.orderid
AND tblinvoices.id = tblorders.invoiceid
AND tblinvoices.status = 'Paid'
Well, its near impossible without any table definitions, but you are doing a lot of joins there. You are starting with tblhosting.id and working your way 'up' from there. If any of the connected tables has a double entry, you'll get more hits
You could add a DISTINCT to your query, but that would not fix the underlying issue. It could be a problem with your data: do you have 2 invoices? Maybe you should select everything (SELECT * FROM) and check what is returned, maybe check your tables for double content.
Using DISTINCT is most of the time not a good choice: it means either your query or your data is incorrect (or you don't understand them thoroughly). It might get you the right result for now, but can get you in trouble later.
A guess about the reason this happens:
You do not connect the products table to the chain of id's. So you are basically adding a '2' to your result as far as I can see. You join on products, and the only thing that limits that table is that "gid" should be 2. So if you add a product with gid 2 you get another result. Either join it (maybe tblproduct.orderid = tblorders.id ? just guessing here) or just remove it, as it does nothing as far as I can see.
If you want to make your query a bit clearer, try not implicitly joining, but do it like this. So you can actually see what's happening
SELECT tblhosting.id, tblhosting.userid, tblhosting.orderid, tblhosting.packageid, tblhosting.server, tblhosting.domain, tblhosting.username, tblorders.invoiceid, tblproducts.gid, tblservers.ipaddress, tblinvoices.status
FROM tblhosting
JOIN tblproducts ON /*you're missing something here!*/
JOIN tblorders ON tblorders.id = tblhosting.orderid
JOIN tblinvoices ON tblinvoices.id = tblorders.invoiceid
JOIN tblservers ON tblservers.id = tblhosting.server
WHERE
tblorders.status = 'Pending'
AND tblproducts.gid = '2'
AND tblinvoices.status = 'Paid'
I don't see in your query JOIN to tblproducts, it seems to be a reason.