How is mysql interpreting/grouping a duplicate WHERE statement with an OR statement in the middle? - mysql

I am using an Laravel's ORM. It is generating a huge query and inside that query I noticed that there is a WHERE statement that repeats itself, and the order of that WHERE statement seems to be very important. I believe it has something to do with how MySQL is grouping the WHERE statements but I don't understand how MySQL works well enough. I'm trying to understand why this works the way it does. How is mysql interpreting/grouping this?
Table
items
-----------------------------------------------
id | status
-----------------------------------------------
19 | 1
20 | 0
21 | 1
Results needed:
-----------------------------------------------
id | status
-----------------------------------------------
19 | 1
21 | 1
The query is much longer than this. But here is the code the ORM is generating that produces the above results needed:
SELECT * FROM campaigns WHERE status = 1 OR id IN ('20') AND status = 1 ORDER BY id DESC;
If I remove the last status = 1 the query does not return the needed results. Is MySQL grouping the WHERE statements like this:
SELECT * FROM campaigns WHERE status = 1 OR (id IN ('20') AND status = 1);
The query the ORM produces is a few pages long, so when reading this it is pretty confusing without the parentheses. It seems like MySQL is grouping it like this. I guess I don't understand well enough how MySQL works. Any recommendations on books to better understand MySQL?
Building up the Query/Trying to understand what MySQL is doing
1)
SELECT * FROM items WHERE status = 1
Results
-----------------------------------------------
id | status
-----------------------------------------------
19 | 1
21 | 1
2)
SELECT * FROM items WHERE status = 1 OR id IN ('20')
Results
-----------------------------------------------
id | status
-----------------------------------------------
19 | 1
20 | 0
21 | 1
3)
SELECT * FROM items WHERE status = 1 OR id IN ('20') AND status = 1
Results
-----------------------------------------------
id | status
-----------------------------------------------
19 | 1
21 | 1

AND has precedence over OR, see also SQL Logic Operator Precedence: And and Or.
For your example, this means
SELECT * FROM campaigns WHERE status = 1 OR id IN ('20') AND status = 1 ;
is automatically interpreted as
SELECT * FROM campaigns WHERE status = 1 OR (id IN ('20') AND status = 1);
even if you don't put the parenthesis.
It is a good idea to always write the parenthesis, even if you know they are not needed, to make the intention clear to other readers of your code (and to the compiler/interpreter, if needed).

Related

query over 2 columns where value never appears in either column more than once

Looking for a query that takes the following table ProductList
id| column_1 | column_2 | Sum
================================
1 | Product-A | Product-B | 67
2 | Product-A | Product-C | 55
3 | Product-A | Product-D | 23
4 | Product-B | Product-C | 95
5 | Product-C | Product-D | 110
and returns the first record Product-A_Product-B and then skips all records that contain Product-A or Product-B in either column and returns Product-C_Product-D.
I only want to return the row if everything in the row is appearing for the first time.
Assuming that the products don't contain ,, you could use a comma-delimited session variable to store already selected products and check for every row if one of the columns is already contained in that variable:
select column_1, column_2
from (
select l.*,
case when find_in_set(l.column_1, #products) or find_in_set(l.column_2, #products)
then 1
else (#products := concat(#products, ',', l.column_1, ',', l.column_2)) = ''
end as skip
from ProductList l
cross join (select #products := '') init
order by l.id
) t
where skip = 0;
Demo: http://rextester.com/NDVBW87988
But you should know the risks:
ORDER BY in a subquery is not really valid and usually doesn't make sence. The engine may skip it or move it to the outer query.
If you read and write the same session variable in one statement, the execution order is not defined. So the query might not work for all (future) versions.

MySQL special case pivot

I cant find an answer to this despite looking for several days!
In MySQL I have 2 Tables
ProcessList contains foreign keys all from the process Table
ID |Operation1|Operation2|Operation3|etc....
---------------------------------------
1 | 1 | 4 | 6 | ....
---------------------------------------
2 | 2 | 4 | 5 |....
---------------------------------------
.
.
.
Process Table
ID | Name
-------------------
1 | Quote
2 | Order
3 | On-Hold
4 | Manufacturing
5 | Final Inpection
6 | Complete
Now, I am new to SQL but I understand that MYSQL doesnt have a pivot function as Ive researched, and I see some examples with UNIONs etc, but I need an SQL expression something like (pseudocode)
SELECT name FROM process
(IF process.id APPEARS in a row of the ProcessList)
WHERE processListID = 2
so I get the result
Order
Manufacturing
Final Inspection
I really need the last line of the query to be
WHERE processListID = ?
because otherwise I will have to completely rewrite my app as the SQL is stored in a String in java, and the app suplies the key index only at the end of the statement.
One option is using union to unpivot the processlist table and joining it to the process table.
select p.name
from process p
join (select id,operation1 as operation from processlist
union all
select id,operation2 from processlist
union all
select id,operation3 from processlist
--add more unions as needed based on the number of operations
) pl
on pl.operation=p.id
where pl.id = ?
If you always consider only a single line in the process list (i.e. procsessListId = x), the following query should do a pretty simple and performant job:
select p.name from process p, list l
where l.id = 2
and (p.id in (l.operation1, l.operation2, l.operation3))

MySQL counting number of max groups

I asked a similar question earlier today, but I've run into another issue that I need assistance with.
I have a logging system that scans a server and catalogs every user that's online at that given moment. Here is how my table looks like:
-----------------
| ab_logs |
-----------------
| id |
| scan_id |
| found_user |
-----------------
id is an autoincrementing primary key. Has no real value other than that.
scan_id is an integer that is incremented after each successful scan of all users. It so I can separate results from different scans.
found_user. Stores which user was found online during the scan.
The above will generate a table that could look like this:
id | scan_id | found_user
----------------------------
1 | 1 | Nick
2 | 2 | Nick
3 | 2 | John
4 | 3 | John
So on the first scan the system found only Nick online. On the 2nd it found both Nick and John. On the 3rd only John was still online.
My problem is that I want to get the total amount of unique users connected to the server at the time of each scan. In other words, I want the aggregate number of users that have connected at each scan. Think counter.
From the example above, the result I want from the sql is:
1
2
2
EDIT:
This is what I have tried so far, but it's wrong:
SELECT COUNT(DISTINCT(found_user)) FROM ab_logs WHERE DATE(timestamp) = CURDATE() GROUP BY scan_id
What I tried returns this:
1
2
1
The code below should give you the results you are looking for
select s.scan_id, count(*) from
(select distinct
t.scan_id
,t1.found_user
from
tblScans t
inner join tblScans t1 on t.scan_id >= t1.scan_id) s
group by
s.scan_id;
Here is sqlFiddle
It assumes the names are unique and includes current and every previous scans in the count
Try with group by clause:
SELECT scan_id, count(*)
FROM mytable
GROUP BY scan_id

Return the query when count of a query is greater than a number?

I want to return all rows that have a certain value in a column and have more than 5 instances in which a number is that certain value. For example, I would like to return all rows of the condition in which if the value in the column M has the number 1 in it and there are 5 or more instances of M having the number 1 in it, then it will return all rows with that condition.
select *
from tab
where M = 1
group by id --ID is the primary key of the table
having count(M) > 5;
EDIT: Here is my table:
id | M | price
--------+-------------+-------
1 | | 100
2 | 1 | 50
3 | 1 | 30
4 | 2 | 20
5 | 2 | 10
6 | 3 | 20
7 | 1 | 1
8 | 1 | 1
9 | 1 | 1
10 | 1 | 1
11 | 1 | 1
Originally I just want to insert into a trigger so that if the number of M = 1's is greater than 5, then I want to create an exception. The query I asked for would be inserted into the trigger. END EDIT.
But my table is always empty. Can anyone help me out? Thanks!
Try this :
select *
from tab
where M in (select M from tab where M = 1 group by M having count(id) > 5);
SQL Fiddle Demo
please try
select *,count(M) from table where M=1 group by id having count(M)>5
Since you group on your PK (which seems a futile excercise), you are counting per ID, whicg will indeed always return 1.
As i explain after this code, this query is NOT good, it is NOT the answer, and i also explain WHY. Please do not expect this query to run correctly!
select *
from tab
where M = 1
group by M
having count(*) > 5;
Like this, you group on what you are counting, which makes a lot more sense. At the same time, this will have unexpected behaviour, as you are selecting all kinds of columns that are not in the group by or in any aggregate. I know mySQL is lenient on that, but I don;t even want to know what it will produce.
Try indeed a subquery along these lines:
select *
from tab
where M in
(SELECT M
from tab
group by M
having count(*) > 5)
I've built a SQLFiddle demo (i used 'Test' as table name out of habit) accomplishing this (I don't have a mySQL at hand now to test it).
-- Made up a structure for testing
CREATE TABLE Test (
id INT NOT NULL AUTO_INCREMENT,
PRIMARY KEY(id),
M int
);
SELECT id, M FROM tab
WHERE M IN (
SELECT M
FROM Test
WHERE M = 1
GROUP BY M
HAVING COUNT(M) > 5
)
The sub-query is a common "find the duplicates" kind of query, with the added condition of a specific value for the column M, also stating that there must be at least 5 dupes.
It will spit out a series of values of M which you can use to query the table against, ending with the rows you need.
You shouldn't use SELECT * , it's a bad practice in general: don't retrieve data you aren't actually using, and if you are using it then take the little time needed to type in a list of field, you'll likely see faster querying and on the other hand the code will be way more readable.

Nested CASE in MySQL Query

I want to count the total numbers of orders where task is 1 to 3.
Then if entry1 is already counted I need to check if the status is in-progress (all status which are not completed).
I need to use a nested CASE here I don't want to include it in my WHERE clause
because I will join more tables later.
This is my sample table:
ID + orderid + task + status
1 | 1 | 1 | Completed
2 | 2 | 1 | Saved
3 | 3 | 1 | Saved
4 | 1 | 2 | Completed
5 | 1 | 3 | Completed
As you can see in task field which have entry1 the total of in-progress is 2 and completed is 1.
When I execute my query I get results of 2 when I used entry1 and the status is not Saved but I also get the result of 2 when removing the NOT. But when I used ('COMPLETED') I get the right result. So what's the problem with ('SAVED') in my code. I hope I did not mistype anything in my question.
This is my sample query:
SELECT
COUNT(
CASE task
WHEN 1 THEN task ELSE NULL
END
AND
CASE `status`
WHEN NOT UPPER('SAVED') THEN `status` ELSE NULL
END
) e1_inprogress
FROM tableName;
For now I just use only task 1 but in my real code I need also to include all task and all status in COUNT.
I think this is simple maybe I missing something then please correct me.
If you have another way please let met know.
Use this
SELECT
COUNT(
CASE WHEN task = 1 AND Upper(`status`) = 'SAVED' THEN 1 END
) e1_inprogress
FROM tableName;
Why it didn't work?
WHEN NOT UPPER('SAVED')
NOT UPPER('SAVED') becomes NOT (TRUE) becomes FALSE, so you are comparing CASE Status against the value FALSE.
Your query could have been
CASE Upper(`Status`)
WHEN 'SAVED' THEN ... ELSE ...
(nb: swap the code for THEN and ELSE)