SQL query suggestions please - mysql

I'm trying to write a SQL query to calculate prices for licenses.
Please check the schema below:
Table: Prices
| ID (bigint) | USERS(Bigint) | TYPE (varchar) | PRICE (BIGINT)
------------------------------------------------------------------
| 1 | 1 | other | 20 |
| 2 | 15 | local | 13.96 |
Table: Licenses
| ID (bigint) | USERID (Bigint) |STATUS(VARCHAR) | USERS(bigint) | DEVICES(BIGINT) | TYPE(VARCHAR) | REQUEST_TYPE (VARCHAR) |
--------------------------------------------------------------------------------------------------------------
| 1 | 13 | 10 | 10 | local | add |
| 2 | 13 | 15 | 20 | other | extend |
My objective:
Given a userid and type, i want to calculate total prices of all the licenses of basing on following critirea:
For given userid and type:
1) Get all licenses which have request_type as either new (or) extend
2) For each such license, match the number of users (USERS column) with USERS column from 'prices' table and do calculation as devices*(associated price from prices table)
3) Using this calculate sum of all such prices and return a total price.
I'm trying to do this by using the following query but i'm not successful yet:
SELECT SUM(PRICE) FROM prices
LEFT OUTER JOIN licenses
ON (
prices.users=licenses.users
AND prices.type=licenses.type
)
WHERE licenses.userid=13
AND licenses.status='approved'
AND licenses.request_type IN ('add','extend')
Please check SQL Fiddle here: http://sqlfiddle.com/#!2/05f5cf
Pleas help.
Thanks,
David

the result will be null, because query cannot found the condition of LEFT OUTER JOIN
there is the query
SELECT SUM(PRICE) FROM prices
LEFT OUTER JOIN licenses
ON (
prices.users=licenses.users
AND prices.type=licenses.type //cannot found condition on table
)
WHERE licenses.userid=13
AND licenses.status='approved'
AND licenses.request_type IN ('add','extend')
in this is the data inside table
table licences
(1, 'approved', 10, 10, 'local', 'add', 13),
(2, 'approved', 15, 20, 'other', 'extend', 13);
and table prices
(1, 1, 'other', 20),
(2, 15, 'local', 13.96);
and your condition is
prices.users=licenses.users
AND prices.type=licenses.type //cannot found condition on table
that mean if from your table is
at licences table have a type="other" and users=15
but at prices table haven't have type="other" and users=15
so the result will be null
because when i change the first row of table prices
(1, 1, 'other', 20)
becomes (1, 15, 'other', 20),
that will be have a result = 20
you need change your first line of query
SELECT SUM(PRICE) FROM prices
be
SELECT IFNULL(SUM(PRICE),0) FROM prices
this will change the result if haven't found the row to 0 not null

From your comments and updates, I think you want (not sure if it's necessary to compare users in license and users in price, but it seems youd want this)
select coalesce(sum( p.users * p.price), 0)
FROM licenses l
inner join prices p
on p.type = l.type
--and p.users = l.users
where l.status = 'approved'
and l.request_type in ('add', 'extend')
and l.userid = 13
Edit
In fact, do you need to check that type AND users are identical, or just users ?
If you need only check on users, then
inner join prices p
on p.users = l.users
If you need only check on type
inner join prices p
on p.type = l.type
If you need both, you will get 0 with your sample datas.
See SqlFiddle with 3 versions.

Related

MYSQL How find order products matching current storage products (compare two tables for 100% match)

i'm looking for solution to check if multiple rows from one table have match in other table. In my situation i need to check if items from orders are in storage. Currently I use php to check orders - script is taking open orders and foreach one by one to check storage. It's generating quite a lot of queries and it's not efficient at all and i'm looking for solution to do this via sql query.
Desired result should be:
OrderId | Date | Products
1002/02 | 2020/08/16 | 1x Ipod; 2x battery; 9x some item;
0333/4 | 2020/06/22 | 10x shelf
Storage products table
id | id_product | quantity
Orders
id | reference | id_status | created_at
Order Products
Id | id_order | quantity | id_storage_product
I've written some code to generate table visible above but result it's not even close to desired.
select('orders.id', orders.created_at','orders.reference', 'storage_products.id as storageProductId')
->join('order_products', 'orders.id', '=', 'order_products.id_order')
->join('storage_products', 'order_products.id_product', '=', 'storage_products.id_product')
->where('storage_products.quantity', '>=', 'order_products.quantity')
->whereIn('orders.id_status', array(1, 2)) //get new orders/ open
->where('order_products.id_storage_product', null)
->groupBy('orders.id');
Clean sql:
SELECT `orders`.`id`,
`orders`.`created_at`,
`orders`.`reference`,
`storage_products`.`id` AS `storageProductId`,
`order_products`.`id_order`
FROM `orders`
INNER JOIN `order_products`
ON `orders`.`id` =
`order_products`.`id_order`
INNER JOIN `storage_products`
ON `order_products`.`id_product` =
`storage_products`.`id_product`
WHERE `storage_products`.`quantity` >=
'order_products.quantity'
AND `orders`.`id_status` IN ( 1, 2 )
AND `order_products`.`id_storage_product` IS NULL
GROUP BY `orders`.`id`
ORDER BY `orders`.`id` ASC
So code should find open orders (id_status); where storage quantity is equal or greater than product in order; where id_storage_products is null (means product bought on website but it was not in storage when ordered).
Upper query result is wrong because it showed me partial match to storage - even without checking quantity (some products have 0 but still displayed).
For any help many thanks
EDIT: fiddle sample: https://www.db-fiddle.com/f/6jKvKXPYvsLeXgm3Qv1nHu/0
Your query contains the condition:
AND `order_products`.`id_storage_product` IS NULL
but in your sample data all values are 0.
So instead I use COALESCE() to cover both cases.
Also I removed the condition:
AND `orders`.`id_status` IN ( 1, 2 )
because the column id_status is not included in the definition of the table orders in your sample data.
This query works:
SELECT o.id,
o.reference,
o.created_at,
GROUP_CONCAT(op.quantity, 'x', op.id_product separator ' ;') products
FROM orders o
INNER JOIN order_products op ON o.id = op.id_order
INNER JOIN storage_products sp ON op.id_product = sp.id_product
WHERE sp.quantity >= op.quantity AND COALESCE(op.id_storage_product, 0) = 0
GROUP BY o.id, o.reference, o.created_at
ORDER BY o.id ASC
See the demo.
Results:
| id | reference | created_at | products |
| --- | --------- | ------------------- | ------------- |
| 2 | 345554/02 | 2020-08-22 00:00:00 | 3x188 ; 1x155 |
If you also join the table products (I assume there is such a table) you can get the names of the products instead of their ids.
I tried the following query on the db-fiddle link and this works.
SELECT
orders.reference, orders.created_at, order_products.id_product
FROM
storage_products
LEFT JOIN
order_products ON storage_products.id_product = order_products.id_product
LEFT JOIN
orders ON orders.id = order_products.id_order;
What I did in the query is calling all storage_products with the same id_product in order_products and proceed to call all orders in the called order_products.

SQL Order results by Match Against Relevance and display the price based on sellers rank

Looking to display results based on 'relevance' of the users search along with the price of the seller that ranks highest. A live example to what i'm after is Amazons search results, now I understand their algorithm is extremely complicated, but i'm after a simplified version.
Lets say we search for 'Jumper' the results that are returned are products related to 'Jumper' but then the price is not always the cheapest is based on the sellers rank. The seller with the highest rank gets his/hers prices displayed.
Heres what I have been working on but not giving me the expected results at mentioned above, and to be honest I don't think this is very efficient.
SELECT a.catalogue_id, a.productTitle, a.prod_rank, b.catalogue_id, b.display_price, b.sellers_rank
FROM
(
SELECT c.catalogue_id,
c.productTitle,
MATCH(c.productTitle) AGAINST ('+jumper*' IN BOOLEAN MODE) AS prod_rank
FROM catalogue AS c
WHERE c.catalogue_id IN (1, 2, 3)
) a
JOIN
(
SELECT inventory.catalogue_id,
inventory.amount AS display_price,
(accounts.comsn + inventory.quantity - inventory.amount) AS sellers_rank
FROM inventory
JOIN accounts ON inventory.account_id = accounts.account_id
WHERE inventory.catalogue_id IN (1, 2, 3)
) AS b
ON a.catalogue_id = b.catalogue_id
ORDER BY a.prod_rank DESC
LIMIT 100;
Sample Tables:
Accounts:
----------------------------
account_id | comsn
----------------------------
1 | 100
2 | 9999
Catalogue:
----------------------------
catalogue_id | productTitle
----------------------------
1 | blue jumper
2 | red jumper
3 | green jumper
Inventory:
-----------------------------------------------
product_id | catalogue_id | account_id | quantity | amount |
-----------------------------------------------
1 | 2 | 1 | 6 | 699
2 | 2 | 2 | 2 | 2999
Expected Results:
Product Title:
red jumper
Amount:
29.99 (because he/she has sellers rank of: 7002)
First, you should limit the results only to the matches for the first subquery:
Second, you should eliminate the second subquery:
SELECT p.catalogue_id, p.productTitle, p.prod_rank,
i.amount as display_price,
(a.comsn + i.quantity - i.amount)
FROM (SELECT c.catalogue_id, c.productTitle,
MATCH(c.productTitle) AGAINST ('+jumper*' IN BOOLEAN MODE) AS prod_rank
FROM catalogue AS c
WHERE c.catalogue_id IN (1, 2, 3)
HAVING prod_rank > 0
) p JOIN
inventory i
ON i.catalogue_id = c.catalogue_id join
accounts a
ON i.account_id = a.account_id
ORDER BY c.prod_rank DESC
LIMIT 100;
I'm not sure if you can get rid of the final ORDER BY. MATCH with JOIN can be a bit tricky in that respect. But only ordering by the matches should help.

How to return all columns from 1st table and only 1 column from 2nd table

I have a first table called "purchases"
news_series, transaction_id, owner_id, amount
I have another table called "Events"
news_name, news_id, series_id, news_description
The issue that I'm running into is that if I do
purchases.news_series joins to events.series_id
The issues is that there can be multiple events with the series id....
I need to join just one to get the news_name from the joined table so the base select is
Select * from purchases where owner_id=29
140, asldkfj_sdfx34, 29, 40
then I add the joined table
Select *
from purchases
LEFT JOIN events on purchases.news_series=events.series_id
where owner_id=29
140, asldkfj_sdfx34, 29, 40,"THIS EVENT", 606, 140, "MY FIRST EVENT"
140, asldkfj_sdfx34, 29, 40,"THIS EVENT", 607, 140, "MY FIRST EVENT"
and I end up with a few rows returned...I just need one to capture the new_name from the events table.
I just need one to capture the news_name from the events table.
This is what I would do:
PURCHASES TABLE:
+-------------+----------------+----------+--------+
| news_series | transaction_id | owner_id | amount |
+-------------+----------------+----------+--------+
| 140 | asldkfj_sdfx34 | 29 | 40 |
+-------------+----------------+----------+--------+
EVENTS TABLE:
+------------+---------+-----------+------------------+
| news_name | news_id | series_id | news_description |
+------------+---------+-----------+------------------+
| THIS EVENT | 606 | 140 | MY FIRST EVENT |
+------------+---------+-----------+------------------+
| THIS EVENT | 607 | 140 | MY FIRST EVENT |
+------------+---------+-----------+------------------+
SELECT DISTINCT just the one column you want from the joined table:
SELECT DISTINCT p.*, e.news_name
FROM Purchases p
LEFT JOIN Events e ON p.news_series = e.series_id
WHERE p.owner_id = 29
If you do not SELECT DISTINCT, this is why you are getting two rows.
Test:
;WITH Purchases (news_series, transaction_id, owner_id, amount) AS (
SELECT '140','asldkfj_sdfx34','29','40'
), Events (news_name,news_id,series_id,news_description) AS (
SELECT 'THIS EVENT','606','140','MY FIRST EVENT' UNION ALL
SELECT 'THIS EVENT','607','140','MY FIRST EVENT' )
SELECT DISTINCT p.*, e.news_name
FROM Purchases p
LEFT JOIN Events e ON p.news_series = e.series_id
WHERE p.owner_id = 29
Might I suggest:
Select p.*,
(select e.news_name
from events e
where p.news_series = e.series_id
limit 1
)
from purchases p
where owner_id = 29;
This is guaranteed to return one row per purchase.

How to get multiple columns on subquery or group by

I have two tables on MySql, the first contains an ID and the name of some products. I have to get the cheapest combination of brand/market for each product. So, I've inserted some itens into both tables:
UPDATE: Inserted new product (bed) with no 'Product_Brand_Market' to test LEFT JOIN.
UPDATE: Changed some product prices for better testing.
CREATE TABLE Product(
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(20) NOT NULL);
CREATE TABLE Product_Brand_Market(
product INT UNSIGNED,
market INT UNSIGNED, /*this will be a FOREIGN KEY*/
brand INT UNSIGNED, /*this will be a FOREIGN KEY*/
price DECIMAL(10,2) UNSIGNED NOT NULL,
PRIMARY KEY(product, market, brand),
CONSTRAINT FOREIGN KEY (product) REFERENCES Product(id));
INSERT INTO Product
(name) VALUES
('Chair'), /*will get id=1*/
('Table'), /*will get id=2*/
('Bed'); /*will get id=3*/
INSERT INTO Product_Brand_Market
(product, market, brand, price) VALUES
(1, 1, 1, 8.00), /*cheapest chair (brand=1, market=1)*/
(1, 1, 2, 8.50),
(1, 2, 1, 9.00),
(1, 2, 2, 9.50),
(2, 1, 1, 11.50),
(2, 1, 2, 11.00),
(2, 2, 1, 10.50),
(2, 2, 2, 10.00); /*cheapest table (brand=2, market=2)*/
/*no entries for bed, must return null*/
And tried the following code to get the desired values:
UPDATE: Changed INNER JOIN for LEFT JOIN.
SELECT p.id product, MIN(pbm.price) price, pbm.brand, pbm.market
FROM Product p
LEFT JOIN Product_Brand_Market pbm
ON p.id = pbm.product
GROUP BY p.id;
The returned price is OK, but I'm getting the wrong keys:
| product | price | brand | market |
|---------|-------|-------|--------|
| 1 | 8 | 1 | 1 |
| 2 | 10 | 1 | 1 |
| 3 | null | null | null |
So the only way I could think to solve it is with subqueries, but I had to use two subqueries to get both brand and market:
SELECT
p.id product,
(
SELECT pbm.brand
FROM Product_Brand_Market pbm
WHERE p.id = pbm.product
ORDER BY pbm.price
LIMIT 1
) as brand,
(
SELECT pbm.market
FROM Product_Brand_Market pbm
WHERE p.id = pbm.product
ORDER BY pbm.price
LIMIT 1
) as market
FROM Product p;
It returns the desired table:
| product | brand | market |
|---------|-------|--------|
| 1 | 1 | 1 |
| 2 | 2 | 2 |
| 3 | null | null |
But I want to know if I really should use these two similar subqueries or there is a better way to do that on MySql, any ideas?
Use a correlated subquery with LIMIT 1 in the WHERE clause:
SELECT product, brand, market
FROM Product_Brand_Market pbm
WHERE (pbm.brand, pbm.market) = (
SELECT pbm1.brand, pbm1.market
FROM Product_Brand_Market pbm1
WHERE pbm1.product = pbm.product
ORDER BY pbm1.price ASC
LIMIT 1
)
This will return only one row per product, even if there are two or many of them with the same lowest price.
Demo: http://rextester.com/UIC44628
Update:
To get all products even if they have no entries in the Product_Brand_Market table, you will need a LEFT JOIN. Note that the condition should be moved to the ON clause.
SELECT p.id as product, pbm.brand, pbm.market
FROM Product p
LEFT JOIN Product_Brand_Market pbm
ON pbm.product = p.id
AND (pbm.brand, pbm.market) = (
SELECT pbm1.brand, pbm1.market
FROM Product_Brand_Market pbm1
WHERE pbm1.product = pbm.product
ORDER BY pbm1.price ASC
LIMIT 1
);
Demo: http://rextester.com/MGXN36725
The follwing query might make a better use of your PK for the JOIN:
SELECT p.id as product, pbm.brand, pbm.market
FROM Product p
LEFT JOIN Product_Brand_Market pbm
ON (pbm.product, pbm.market, pbm.brand) = (
SELECT pbm1.product, pbm1.market, pbm1.brand
FROM Product_Brand_Market pbm1
WHERE pbm1.product = p.id
ORDER BY pbm1.price ASC
LIMIT 1
);
An index on Product_Brand_Market(product, price) should also help to improve the performance of the subquery.

Converting IN to EXISTS for counting users who have taken multiple kinds of actions

I have a table full of users, timestamps, and different types of actions. Let's call them type A, B, and C:
| User ID | Date | ActionType |
--------------------------------------
| 1 | 10/2/14 | A |
| 2 | 10/12/14 | A |
| 3 | 11/1/14 | B |
| 1 | 11/15/14 | B |
| 2 | 12/2/14 | C |
I'm trying to get counts of the number of users who have taken combinations of different action types within a time period -- for example, the number of users who have done both action A and action B between October and December.
This code works (for one combination of actions at a time), but takes forever to run:
SELECT
COUNT(DISTINCT `cm0`.`User ID`) AS `Users`
FROM `mytable` AS `cm0`
WHERE
(`cm0`.`User ID` IN (SELECT `cm1`.`User ID` FROM `mytable` AS `cm1` WHERE
(`cm1`.`ActionType` = 'A' AND (`cm1`.`Date` BETWEEN dateA AND
dateB)))
AND (`cm0`.`ActionType` = 'B')
AND (`cm0`.`Date` BETWEEN dateA AND dateB))
I researched ways to do this using common table expressions, and then realized I couldn't do those in mySQL. Now I'm trying to figure out how to optimize with EXISTS instead of IN, but I'm having trouble fitting examples into what I need. Any help would be much appreciated!
Try this:
SELECT COUNT(DISTINCT cm0.User_ID) AS Users
FROM mytable AS cm0
WHERE cm0.ActionType IN ('A', 'B') AND cm0.Date BETWEEN dateA AND dateB
GROUP BY cm0.User_ID
HAVING COUNT(DISTINCT cm0.ActionType) = 2;
The above query will return the number of users who have done both action A and action B between October and December, but if you still want to use EXISTS then check below query:
SELECT COUNT(DISTINCT cm0.User_ID) AS Users
FROM mytable AS cm0
WHERE EXISTS (SELECT 1 FROM mytable AS cm1
WHERE cm0.User_ID = cm1.User_ID AND cm1.ActionType = 'A' AND cm1.Date BETWEEN dateA AND dateB
) AND cm0.ActionType = 'B' AND cm0.Date BETWEEN dateA AND dateB