MySQL joined WHERE IN combined with NOT IN (categories) - mysql

I have a whitelist and a blacklist of category UIDs.
I am trying to tell MySQL that
i want all pages that have at least "57", but
that i don't want any pages that ALSO have "206".
Usually i would say "IN(57)" excludes everything else, like 206, but certain pages have both (57 and 206) so it's true either way.
The unwanted 206 page is still included.
Here's the Query:
SELECT pages.uid, pages.title
FROM pages
LEFT JOIN sys_category_record_mm AS cats ON (pages.uid = cats.uid_foreign AND cats.tablenames="pages" AND cats.fieldname="categories")
WHERE pages.hidden=0 AND pages.deleted=0
AND cats.uid_local IN (57)
AND cats.uid_local NOT IN (206)
ORDER BY (CASE WHEN pages.starttime > 0 THEN pages.starttime ELSE pages.crdate END) DESC
LIMIT 10
Here is a DB Fiddle for this Problem:
https://www.db-fiddle.com/f/fxGQbBVZHb8aJDJ4eiTUW1/0
I am out of ideas. Any help/hint would be much appreciated

I find this sort of logic simplest with group by and having:
select c.uid_foreign
from sys_category_record_mm c
where c.tablenames = 'pages' AND c.fieldname = 'categories'
group by c.uid_foreign
having sum(c.uid_local in (57)) > 0 and
sum(c.uid_local in (206)) = 0;
You can join back to your pages to get additional information.

Related

mysql order with many criterias on two tables

I'm trying to sort a visitor list after some different criteria and got stuck, as I can't figure out, how to do this.
I have a queue of people who check in first, and out of that the list is generated. The client is marked as showedUp, if he comes to the door (after called with his number on the list). If someone comes late, he must be at the end of the list. Another thing is, the list starts everytime with a different number.
Day 1 -> List from 1 to 160
Day 2 -> List from 33 to 160, 1 to 32
Day 3 -> List from 65 to 160, 1 to 64
If someone comes late, meaning the number after him is already called, he should be added to the end of the list, like 1 to 160, 10 was late, as 20 was already called, it should be 1 to 160, 10. If there is another starting number it should be 33 to 160, 1 to 32, 10. The criteria here is: if a placeNr after your number is already called (showedUp), than you be at the end of the list.
Tables
clients (id, name, placeNr)
visits (id, pid, checkInTime, showedUp, showedUpTime)
Select
SELECT clients.id AS id, visits.id AS visitId, clients.placeNr AS placeNr, clients.name AS name
FROM clients, visits
WHERE clients.id = visits.pid AND visits.checkInTime >= '1447286401' AND visits.checkInTime <= '1447372799'
ORDER BY clients.placeNr < '1', if(visits.showedUpTime < visits.checkInTime, clients.placeNr, 1), ttc.placeNr
So how do I get the late showers at the end of my list?
Thank you very much in advance!
If I follow your logic, you need to specify whether or not someone is late. The following is the structure that you want for this type of query. I think I've captured the rules in your question:
select v.id, v.id AS visitId, c.placeNr, c.name,
(case when v.showedUpTime >
(select min(v.checkInTime)
from visits v2 join
clients c2
on v2.pid = c2.id
where date(v2.showedUpTime) = date(v.showedUpTime) and
c2.placeNr > c.placeNr
)
then 1 else 0 end) as IsLate
from clients c join
visits v
on c.id = v.pid
order by date(v.showedUpTime),
isLate,
c.placeNr;

SQL SUM issues with joins

I got a quite complex query (at least for me).
I want to create a list of users that are ready to be paid. There are 2 conditions that need to be met: order status should be 3 and the total should be more then 50. Currently I got this query (generated with Codeingiter active record):
SELECT `services_payments`.`consultant_id`
, `consultant_userdata`.`iban`
, `consultant_userdata`.`kvk`, `consultant_userdata`.`bic`
, `consultant_userdata`.`bankname`
, SUM(`services_payments`.`amount`) AS amount
FROM (`services_payments`)
JOIN `consultant_userdata`
ON `consultant_userdata`.`user_id` = `services_payments`.`consultant_id`
JOIN `services`
ON `services`.`id` = `services_payments`.`service_id`
WHERE `services`.`status` = 3
AND `services_payments`.`paid` = 0
HAVING `amount` > 50
The services_payments table contains the commissions, consultant_userdata contains the userdata and services keeps the order data. The current query only gives me 1 result while I'm expecting 4 results.
Could someone please tell me what I'm doing wrong and what would be the solution?
For ActiveRecord, rsanchez' answer would be more of
$this->db->group_by('services_payments.consultant_id, consultant_userdata.iban, consultant_userdata.kvk, consultant_userdata.bic, consultant_userdata.bankname');

SQL Request on multiple tables

I've put some information in this SQL Fiddle : http://www.sqlfiddle.com/#!2/0745c0/4/0
You'll see table structure and my request.
SELECT
cov.id AS cov_id,
cov.utilisateurs_uid AS cov_uid,
cov.timestamp_created as timestamp,
cov.invisible as invisible,
COALESCE(numreports, 0) AS numreports
FROM
cforge_covers AS cov
LEFT OUTER JOIN cforge_votes AS vot ON cov.id = vot.covers_id AND vot.utilisateurs_uid != 123456789
LEFT JOIN (SELECT rep.covers_id,
COUNT(rep.id) AS numreports
FROM cforge_reports AS rep
GROUP BY rep.covers_id) reports ON reports.covers_id = cov.id
WHERE invisible=0 AND (numreports < 2 OR numreports IS null OR valide > 0) AND cov.timestamp_created > '1370815140' AND cov.timestamp_created < '1373493540' GROUP BY cov.id
ORDER BY rand() DESC LIMIT 0,2
When a user creates a picture, a line is created in cforge_covers, with his Facebook UID, ID of the picture and a timestamp.
Cover pictures can be voted, once per user, and the votes ares stored in cforge_votes, where you can have the same covers_id multiple times and utilisateurs_uid (users_id) only once per covers_id max.
Cover pictures can also be reported on the same basis as votes : once per user.
The purpose of my request is to fetch :
two random covers
which haven't been reported or moderated (values stored in cforge_reports)
for which the user hasn't already voted
The last part is causing my problem : I try to exclude a specific utilisateurs_uid but as many users vote for an image, other votes for the cover make it valid to show even if the user has already voted for it.
How can I write this request better ?
Thanks in advance for your time !
You want to make use of WHERE NOT EXISTS rather than LEFT JOIN. It is unclear if you want an item that has never been moderatred, or one that has been moderated once or none (your requirements and code say different things). If you truly want one that has never been moderated, then try NOT EXISTS in both cases.
Also, I would suggest that you be diligent about formatting and aliasing All The Things. It will help those reading your code and even you, when you go back to look at it later.
SELECT cov.id AS cov_id
,cov.utilisateurs_uid AS cov_uid
,cov.timestamp_created as timestamp
,cov.invisible as invisible
,COALESCE(reports.numreports, 0) AS numreports
FROM cforge_covers AS cov
LEFT JOIN (SELECT rep.covers_id,
COUNT(rep.id) AS numreports
FROM cforge_reports AS rep
GROUP BY rep.covers_id) reports ON reports.covers_id = cov.id
WHERE cov.invisible=0
AND (reports.numreports < 2
OR reports.numreports IS null
OR ????.valide > 0)
AND cov.timestamp_created > '1370815140'
AND cov.timestamp_created < '1373493540'
AND NOT EXISTS ( SELECT 1 FROM cforge_votes vot WHERE cov.id = vot.covers_id AND vot.utilisateurs_uid = 123456789)
ORDER BY RAND() DESC LIMIT 0,2

MySQL 2 Columns from 2 tables

There we go actually I think it should be ok but it isn't
SELECT animize_users.username, animize_profile.avatar
FROM animize_users, animize_profile
WHERE `animize_profile.userid` = 1 AND `animize_users.id` = 1
LIMIT 0 , 1
Please don't tell me to put this in a a seperate query each - as i don't want to make needless calls...
Just drop the quotes entirely if they're not required, i.e.
SELECT animize_users.username, animize_profile.avatar FROM animize_users au join animize_profile ap on ap.userid = au.id WHERE au.id = 1
If you've got a proper PK on user id, the user shouldn't repeat and really limit 1 would only effect your selection of profile. I'd suggest adding to the where to ensure you get the most appropriate profile.
Also, if "animize" is your product, don't prefix your tables with it, it's redundant.
Try it this way with backticks correctly used:
SELECT animize_users.username, animize_profile.avatar
FROM animize_users, animize_profile
WHERE `animize_profile`.`userid` = 1 AND `animize_users`.`id` = 1
LIMIT 0 , 1

Combine Multiple child rows into one row MYSQL

I have two tables
Ordered_Item
ID | Item_Name
1 | Pizza
2 | Stromboli
Ordered_Options
Ordered_Item_ID | Option_Number | Value
1 43 Pepperoni
1 44 Extra Cheese
2 44 Extra Cheese
What I am looking to output is a mysql query is something to this effect
Output
ID | Item_Name | Option_1 | Option_2
1 Pizza Pepperoni Extra Cheese
2 Stromboli NULL Extra Cheese
I have tried numerous options most ending in syntax error, I have tried group_concat but thats not really what I am looking for. I have a crude example below of what I think might be a start. I need the options to be in the same order every time. And in the program where the info is collected there is no way to reliable ensure that will happen. Is it possible to have them concatenate according to option number. Also I know that I will never have over 5 options so a static solution would work
Select Ordered_Items.ID,
Ordered_Items.Item_Name,
FROM Ordered_Items
JOIN (SELECT Ordered_Options.Value FROM Ordered_Options Where Option_Number = 43) as Option_1
ON Ordered_Options.Ordered_Item_ID = Ordered_Item.ID
JOIN (SELECT Ordered_Options.Value FROM Ordered_Options Where Option_Number = 44) as Option_2
ON Ordered_Options.Ordered_Item_ID = Ordered_Item.ID;
The easiest way would be to make use of the GROUP_CONCAT group function here..
select
ordered_item.id as `Id`,
ordered_item.Item_Name as `ItemName`,
GROUP_CONCAT(Ordered_Options.Value) as `Options`
from
ordered_item,
ordered_options
where
ordered_item.id=ordered_options.ordered_item_id
group by
ordered_item.id
Which would output:
Id ItemName Options
1 Pizza Pepperoni,Extra Cheese
2 Stromboli Extra Cheese
That way you can have as many options as you want without having to modify your query.
Ah, if you see your results getting cropped, you can increase the size limit of GROUP_CONCAT like this:
SET SESSION group_concat_max_len = 8192;
I appreciate the help, I do think I have found a solution if someone would comment on the effectiveness I would appreciate it. Essentially what I did is. I realize it is somewhat static in its implementation but I does what I need it to do (forgive incorrect syntax)
SELECT
ordered_item.id as `Id`,
ordered_item.Item_Name as `ItemName`,
Options1.Value
Options2.Value
FROM ORDERED_ITEMS
LEFT JOIN (Ordered_Options as Options1)
ON (Options1.Ordered_Item.ID = Ordered_Options.Ordered_Item_ID
AND Options1.Option_Number = 43)
LEFT JOIN (Ordered_Options as Options2)
ON (Options2.Ordered_Item.ID = Ordered_Options.Ordered_Item_ID
AND Options2.Option_Number = 44);
If you really need multiple columns in your result, and the amount of options is limited, you can even do this:
select
ordered_item.id as `Id`,
ordered_item.Item_Name as `ItemName`,
if(ordered_options.id=1,Ordered_Options.Value,null) as `Option1`,
if(ordered_options.id=2,Ordered_Options.Value,null) as `Option2`,
if(ordered_options.id=43,Ordered_Options.Value,null) as `Option43`,
if(ordered_options.id=44,Ordered_Options.Value,null) as `Option44`,
GROUP_CONCAT(if(ordered_options.id not in (1,2,43,44),Ordered_Options.Value,null)) as `OtherOptions`
from
ordered_item,
ordered_options
where
ordered_item.id=ordered_options.ordered_item_id
group by
ordered_item.id
If you know you're going to have a limited number of max options then I would try this (example for max of 4 options per order):
Select OI.ID, OI.Item_Name, OO1.Value, OO2.Value, OO3.Value, OO4.Value
FROM Ordered_Items OI
LEFT JOIN Ordered_Options OO1 ON OO1.Ordered_Item_ID = OI.ID
LEFT JOIN Ordered_Options OO2 ON OO2.Ordered_Item_ID = OI.ID AND OO2.ID != OO1.ID
LEFT JOIN Ordered_Options OO3 ON OO3.Ordered_Item_ID = OI.ID AND OO3.ID != OO1.ID AND OO3.ID != OO2.ID
LEFT JOIN Ordered_Options OO4 ON OO4.Ordered_Item_ID = OI.ID AND OO4.ID != OO1.ID AND OO4.ID != OO2.ID AND OO4.ID != OO3.ID
GROUP BY OI.ID, OI.Item_Name
The group by condition gets rid of all of the duplicates that you would otherwise get. I've just implemented something similar on a site I'm working on where I knew I'd always have 1 or 2 matched in my child table, and I wanted to make sure I only had 1 row for each parent item.
What you want is called a pivot, and it's not directly supported in MySQL, check this answer out for the options you've got:
How to pivot a MySQL entity-attribute-value schema
Here is how you would construct your query for this type of requirement.
select ID,Item_Name,max(Flavor) as Flavor,max(Extra_Cheese) as Extra_Cheese
from (select i.*,
case when o.Option_Number=43 then o.value else null end as Flavor,
case when o.Option_Number=44 then o.value else null end as Extra_Cheese
from Ordered_Item i,Ordered_Options o) a
group by ID,Item_Name;
You basically "case out" each column using case when, then select the max() for each of those columns using group by for each intended item.
Joe Edel's answer to himself is actually the right approach to resolve the pivot problem.
Basically the idea is to list out the columns in the base table firstly, and then any number of options.value from the joint option table. Just left join the same option table multiple times in order to get all the options.
What needs to be done by the programming language is to build this query dynamically according to a list of options needs to be queried.