Cannot figure out query for situation where I want to display only customers with unverified order but do not include customers who already have at least one verified order. One customer can have more records in DB since for every order also new record in customers table is made so the only way how to track specific user is by customer_number.
My DB structure (simplified):
customers:
id | customer_number
orders:
id | customer_id | isVerified
I would probably need to combine join and correlated queries (to search records in customers table for every customer_number and check isVerified column for false) which in the end could be really slow especially for thousands of records.
I use Laravel so Eloquent ORM is available if this can make things easier.
(Second thought: Or maybe it would be faster and more efficient to rewrite that part to create only one user record for orders of specific user.)
Any ideas? Thank you
There are probably a few ways to do this, but you can achieve this result with a join, aggregation and conditional sum:
select a.customer_id,
sum( case when isVerified = 1 then 1 else 0 end ) as Num_Verified,
sum( case when isVerified = 0 then 1 else 0 end ) as Num_unVerified
from customers as a
left join
orders as b
on a.customer_id = b.customer_id
group by a.customer_id
having Num_Verified = 0
and Num_unVerified > 0
SQLfiddle here
You can achieve like this:
$customer_id = Customer::join('orders','customers.id','orders.cutomer_id')
->where('isVerified',true)
->select('orders.*')
->groupBy('customer_id')
->pluck('customer_id');
This will give customers with at least one verified order.
Now get customers with unverified orders as:
$customers = Customer::join('orders','customers.id','orders.customer_id')
->where('isVerified',false)
->whereNotIn('customer_id',$customer_id)
->select('customers.customer_number','orders.*')
->groupBy('customer_id')
->pluck('customer_id');
How about this one?
$customer_list = customers::where('customer_number',$customer_number)->with('orders',function($query){
$query->where('isVerified',0);
})->get();
One method is an aggregation query:
select c.customer_number
from customers c join
orders o
on c.customer_id = o.customer_id
group by c.customer_number
having sum(isVerified = 1) = 0 and
sum(isVerified = 0) > 0;
This structure assumes that isVerified is a number that takes on the values of 0 for false and 1 for true.
Related
I'm using doctrine as an ORM layer but by putting a plain mysql query in my database tool i got the same results. So my problem:
I have an invoices table, invoice_items and invoice_payments table and what i would like to get as an result is all invoices that are not paid or at least not fully paid yet. I know that the query should be almost correctly because its giving the correct amount of items back... the only thing is that it is multiplying the amount by a number of probably joined rows.
So the query that i have for now:
select invoice.*, sum(item.amount * item.quantity) as totalDue,
sum(payment.amount) as totalPaid
from invoices as invoice
left join invoice_items as item on item.invoice_id = invoice.id
left join invoice_payments as payment on payment.invoice_id = invoice.id
and payment.status = 'successful'
where invoice.invoice_number is not null
and invoice.sent_at is not null
and invoice.due_date >= '2018-05-15'
group by invoice.id
having count(payment.id) = 0
or sum(payment.amount) < sum(item.amount * item.quantity)
order by invoice.issue_date desc, sum(payment.amount) desc;
As you can see i also have totalDue and totalPaid in my select (those are for reference only and should be removed if the query is correct).
What i saw is that the amount is multiplied by six (because it has 6 items in the payments table).
So maybe someone could help me pointing in the right direction that it doesn't do the multiplying on the totalDue. I was thinking its probably because the group by but without my query is failing.
By simply using a distinct in my query i fixed the problem.
select invoice.*, sum(distinct(item.amount * item.quantity)) as totalDue,
sum(payment.amount) as totalPaid
from invoices as invoice
left join invoice_items as item on item.invoice_id = invoice.id
left join invoice_payments as payment on payment.invoice_id = invoice.id
and payment.status = 'successful'
where invoice.invoice_number is not null
and invoice.sent_at is not null
and invoice.due_date >= '2018-05-15'
group by invoice.id
having count(payment.id) = 0
or sum(payment.amount) < sum(distinct(item.amount * item.quantity))
order by invoice.issue_date desc, sum(payment.amount) desc;
I would like to thank all the people who had taken the time to re-style my question ;-)
I am running this query on my website in order to find a ToDo list based on specific criteria. But it runs too slow and it is probably possible to write it in another way.
SELECT * FROM lesson WHERE
id IN
(SELECT `lesson_id` FROM `localization_logging`
WHERE `language_id` = 2 AND `action_id` = 1)
AND `id` NOT IN
(SELECT `lesson_id` FROM `localization_logging`
WHERE `language_id` = 2 AND `part_id` = 1 AND `action_id` = 6)
What the query does is that it looks in the lesson table to find all lesson list names and then checks if a specific task is done. If the task is done in one todo than show it in the next. Action 1 is done but not action 6 in this case.
I hope I'm explaining this good enough. On my local machine the query takes 1.8 seconds, and sometimes I have to print multiple lists next to each others and then it takes 1.8 times the lists which makes the page load super slow.
Something like this for mark id as completed:
SELECT l.*, SUM(ll.action_id=6) completed FROM lesson l
INNER JOIN localization_logging ll ON ll.lesson_id = l.id
WHERE ll.language_id = 2 AND
(
ll.action_id = 1
OR
ll.action_id = 6 AND ll.part_id == 1
)
GROUP BY l.id
And now we can wrap it with:
SELECT t.* FROM (...) t WHERE t.completed = 0
You'll usually get faster queries filtering rows with INNER/LEFT JOIN, but you need to test it.
SELECT lesson.* FROM lesson
INNER JOIN localization_logging task1
ON lesson.id = task1.lesson_id
LEFT JOIN localization_logging task2
ON lesson.id = task2.lesson_id
AND task2.language_id = 2
AND task2.part_id = 1
AND task2.action_id = 6
WHERE task1.language_id = 2
AND task1.action_id = 1
AND task2.lesson_id IS NULL
Second table is joined on multiple conditions, but have to list them within ON clause because only results that were in result "force joined" as nulls (left join means left side stays no matter what) are required.
Btw. You'll get multiple rows from lesson if task1 condition is not limiting results to one row - GROUP BY lesson.id then.
I have the below two tables and I need to be able to search by items to find the shopping_list_id. Also, I want to limit the query so that it doesn't bring back other shopping lists with additional items on it. Essentially, I'm checking to see if this is a shopping list the user has saved before. The below query does NOT handle if there are shopping lists that match but with additional items, I'm stumped as to how to do that.
tables:
shopping_list
shopping_list_id
user
shopping_list_name
shopping_list_item
shopping_list_item_id
shopping_list_id
category_id
qty
qty_unit_id
This example has three items, but there could be any number. My PHP code dynamically generates the SQL joins and where clause based on the user's input.
Query that I have:
SELECT DISTINCT sli.shopping_list_id
FROM shopping_list_item sli
JOIN shopping_list sl ON sli.shopping_list_id=sl.shopping_list_id
JOIN shopping_list_item sli0 on sli.shopping_list_id=sli0.shopping_list_id
JOIN shopping_list_item sli1 on sli.shopping_list_id=sli1.shopping_list_id
JOIN shopping_list_item sli2 on sli.shopping_list_id=sli2.shopping_list_id
WHERE sl.user_id=:webuser_id
AND sli0.category_id=3 AND sli0.qty=1 AND sli0.qty_unit_id=3
AND sli1.category_id=683 AND sli1.qty=1 AND sli1.qty_unit_id=3
AND sli2.category_id=309 AND sli2.qty=1 AND sli2.qty_unit_id=7
You can do this pretty easily with the group by/having approach to this type of query:
select sli.shopping_list_id
from shopping_list_item sli
group by sli.shopping_list_id
having sum(sli.category_id = 3 AND sli.qty = 1 AND sli.qty_unit_id) = 1 and
sum(sli.category_id = 683 AND sli.qty = 1 AND sli.qty_unit_id = 3) = 1 and
sum(sli.category_id = 309 AND sli.qty = 1 AND sli.qty_unit_id = 7) = 1 and
count(*) = 3;
In my current setup, I have two tables: product and rating.
Product Table
product_id
rating
The product table contains a whole bunch additional of information, but for this question, I am focussed on those two fields only.
Rating Table
product_id
rating
user_id (who rated)
is_admin - bool on whether the user that rated was an admin
The reason we collect the admin ratings in the first place, is because we want to weigh admin ratings slightly higher (60%) in comparison to regular users (40%). The rating column in the product table is equal to the AVG of all the admin ratings. Ratings in general are between 1 and 5.
So for each product we have to consider four scenarios:
RATINGS BY TOTAL
USER ADMIN RATING
---- -----
no no = 0
yes no = AVG of user ratings (`ratings` table)
yes yes = 0.6 AVG of admin ratings (`product_table`) + 0.4 AVG of user ratings (`ratings` table)
no yes = AVG of admin ratings (`product_table`)
The SQL query which currently retrieves the datasets looks like this:
$sql = "SELECT p.product_id,
(COALESCE(p.rating,0)+COALESCE(j.sum,0)) / (COALESCE(p.rating/p.rating,0)
+ COALESCE(j.tot,0)) AS rating
FROM product p
LEFT JOIN
(SELECT SUM(rating) AS sum ,
COUNT(rating) AS tot,
product_id FROM rating
WHERE is_admin_rating=FALSE GROUP BY product_id) j
ON (p.product_id = j.product_id) LEFT JOIN product_description pd
ON (p.product_id = pd.product_id) LEFT JOIN product_to_store p2s
ON (p.product_id = p2s.product_id)";
This query then gets appended with a variety of different sort options (rating being the default), in addition to that we also use LIMIT to "paginate" the search results.
Is there a way in to incorporate the weighted ratings into the query? Or will I have to break it up into several queries?
Since this obviously looks like a web-based system, I would strongly suggest a slight denormalization and tacking on 5 columns to the product table for
UserRatings, UserCount, AdminRatings, AdminCount, FinalRating
When any entries are added or updated to the ratings table, you could apply a simple update trigger, something like
update Product p,
( select r.product_id,
sum( is_admin_rating=FALSE, 1, 0 ) as UserCount,
sum( is_admin_rating=FALSE, rating, 0 ) as UserRatings,
sum( is_admin_rating=TRUE, 1, 0 ) as AdminCount,
sum( is_admin_rating=TRUE, rating, 0 ) as AdminRatings
from Ratings r
where r.product_id = ProductIDThatCausedThisTrigger
group by r.product_id ) as PreSum
set p.UserCount = PreSum.UserCount,
p.UserRatings = PreSum.UserRatings,
p.AdminrCount = PreSum.AdminCount,
p.AdminRatings = PreSum.AdminRatings,
p.FinalRating = case when PreSum.UserCount = 0 and PreSum.AdminCount = 0
then 0
when PreSum.UserCount = 0
then PreSum.AdminRatings / PreSum.AdminCount
when PreSum.AdminCount = 0
then PreSum.UserRatings / PreSum.UserCount
else
( PreSum.UserRatings / PreSum.UserCount * .4 )
/ ( PreSum.AdminRatings / PreSum.AdminCount * .6 )
end
where p.product_id = PreSum.product_id
This way, you will never have to do a separate join to the ratings table and do aggregations which will just get slower as more data is accumulated. Then your query can just use the tables and not have to worry about coalesce, your count per each and their ratings will be there.
The case/when for the FinalRatings is basically doing it all wrapped up as the combination of the user counts and admin counts can be 0/0, +/0, 0/+ or +/+
So, if no count for either, the case/when sets rating to 0
if only the user count has a value, just get that average rating (userRatings / userCounts)
if only the admin count has a value, get admin avg rating (adminRatings / adminCounts)
if BOTH have counts, you are taking the respective averages * .4 and * .6 respectively. This would be the one factoring adjustment you might want to tweak.
Although the query itself looks somewhat monstrous and confusing, if you look at the "PreSum" query, you are only doing it for the 1 product that has just been rated and basis from the trigger. Then, a simple update based on the results of that joined by the single product ID.
Getting this to work might offer a better long-term solution for you.
I have been doing a lots of research online and from my understanding i think my query is ok
That is why i need your help to point me out what im doing wrong.
What My Query Should Do
My query should fetch our stock level from both warehouse
Problem Is
if the product is not in both warehouse the query dont give any result.
Ok so first i have two database of warehouse stock level. that look like that.
Databases
-warehouse1
-warehouse2
Table
-product
Columns
-id
-SKU
-qty
So my Query is
SELECT
warehouse1.product.id as 1_id,
warehouse2.product.id as 2_id ,
warehouse1.product.SKU,
warehouse1.product.qty as 1_qty,
warehouse2.product.qty as 2_qty
FROM `warehouse1`.`product`
LEFT JOIN `warehouse2`.`product`
ON
(`warehouse1`.`product`.`SKU` = `warehouse2`.`product`.`SKU`)
WHERE
warehouse1.product.SKU = '$sku'
OR
warehouse2.product.SKU = '$sku'
ORDER BY
(1_qty + 2_qty) DESC
if i make the where clause like this
WHERE warehouse1.product.SKU = '$sku'
it is then working but i can't get stock from both warehouse.
What should i do if i want to receive the stock level from both warehouse even if there is no product that im asking for in this database.
Thanks
Try a FULL OUTER JOIN. You're using a LEFT JOIN. That requires that the DB fetch all records that match your WHERE clause on the LEFT side of the join, which is warehouse1, and any potentially matching records from warehouse2 (the right side of the join). If a SKU exists only in warehouse2, you don't see it.
Switching to a FULL OUTER JOIN forces the DB to fetch all matching records from BOTH sides of the join, regardless of which side(s) the matching records exist on.
you can also do this with a union
(SELECT
warehouse1.product.id as 1_id,
warehouse1.product.SKU,
warehouse1.product.qty as 1_qty
FROM `warehouse1`.`product`
WHERE
warehouse1.product.SKU = '$sku' )
union
(SELECT
warehouse2.product.id as 2_id ,
warehouse2.product.SKU,
warehouse2.product.qty as 2_qty
FROM `warehouse2`.`product`
WHERE warehouse2.product.SKU = '$sku' )
Combine your OR's in () (... OR ...):
SELECT
warehouse1.product.id as 1_id,
warehouse2.product.id as 2_id ,
warehouse1.product.SKU,
warehouse1.product.qty as 1_qty,
warehouse2.product.qty as 2_qty
FROM `warehouse1`.`product`
LEFT JOIN `warehouse2`.`product`
ON
(`warehouse1`.`product`.`SKU` = `warehouse2`.`product`.`SKU`)
WHERE (warehouse1.product.SKU = '$sku'
OR
warehouse2.product.SKU = '$sku')
ORDER BY
(1_qty + 2_qty) DESC