optimise query with calculate field from several tables - mysql

I have four tables
store[store_id(pk),name]
itemsA(item_id(pk),store_id,name)
itemB(item_id(pk),store_id,name)
itemC(item_id(pk),store_id,name)
I want a query to retrieve a store and the number of items that he have. something like :
select s.store_id ,s.name,count() as numberOfItems from store limit 100
what is the optimal query to achieve that with the following restraints :
cannot create a function in the db
cannot create view
I can only run queries on the db
Thanks

I would recommend doing this with correlated subqueries:
select s.store_id, s.name,
((select count(*) from itemsA a where a.store_id = s.store_id) +
(select count(*) from itemsB b where b.store_id = s.store_id) +
(select count(*) from itemsC c where c.store_id = s.store_id)
) as numberOfItems
from store s
limit 100;
You then want an index in each of the item tables: itemsA(stored_id), itemsB(store_id), and itemsC(store_id).
The reason this is optimized is because it only has to calculate the values for the arbitrary 100 stores chosen by the limit. And, the calculation can be done directly from the index. Other approaches will require doing the calculation for all the stores.
Note: usually when using limit you want an order by clause.

Stores with no items will not show up with this query. If this is a requirement it will have to be tweaked somewhat.
SELECT s.store_id, COUNT(*)
FROM Store s
JOIN ItemA a ON a.store_id = s.store_id
JOIN ItemB b ON b.store_id = s.store_id
JOIN ItemC c ON c.store_id = s.store_id
GROUP BY s.store_id
A simple modification to also include stores with 0 items:
SELECT s.store_id, COUNT(a.store_id) + COUNT(b.store_id) + COUNT(c.store_id)
FROM Store s
LEFT JOIN ItemA a ON a.store_id = s.store_id
LEFT JOIN ItemB b ON b.store_id = s.store_id
LEFT JOIN ItemC c ON c.store_id = s.store_id
GROUP BY s.store_id

If i understood you correctly
DECLARE #store TABLE (store_id INT, name NVARCHAR(100))
DECLARE #itemsA TABLE (item_id INT,store_id INT, name NVARCHAR(100))
DECLARE #itemsB TABLE (item_id INT,store_id INT, name NVARCHAR(100))
DECLARE #itemsC TABLE (item_id INT,store_id INT, name NVARCHAR(100))
INSERT INTO #store VALUES (1,'Store1')
INSERT INTO #store VALUES (2,'Store2')
INSERT INTO #itemsA VALUES (1,1,'itemsA_item1')
INSERT INTO #itemsA VALUES (2,1,'itemsA_item2')
INSERT INTO #itemsA VALUES (3,1,'itemsA_item3')
INSERT INTO #itemsB VALUES (1,2,'itemsB_item1')
INSERT INTO #itemsB VALUES (2,2,'itemsB_item2')
INSERT INTO #itemsB VALUES (3,2,'itemsB_item3')
INSERT INTO #itemsB VALUES (4,1,'itemsB_item4')
INSERT INTO #itemsC VALUES (1,3,'itemsC_item1')
INSERT INTO #itemsC VALUES (2,3,'itemsC_item2')
INSERT INTO #itemsC VALUES (3,2,'itemsC_item3')
SELECT TOP 100 store_id, SUM(HasItems) AS TotalItems FROM
(
SELECT store_id, COUNT(name) AS HasItems FROM #itemsA GROUP BY store_id
UNION
SELECT store_id, COUNT(name) AS HasItems FROM #itemsB GROUP BY store_id
UNION
SELECT store_id, COUNT(name) AS HasItems FROM #itemsC GROUP BY store_id
) AS StoreItems
GROUP BY store_id

Related

All possible combinations of data in a single table in alphabetical order

I'm trying to write a simple SQL query to show all possible combinations of data in a single table. Here's the table:
id
fruit
1
apple
2
orange
3
pear
4
plum
I've only got as fair as pairing all the data using CROSS JOIN: "apple,orange", "apple,pear" etc.
SELECT t1.fruit, t2.fruit
FROM fruits t1
CROSS JOIN fruits t2
WHERE t1.fruit < t2.fruit
Instead I'm looking for all unique combinations in alphabetical order, e.g.
apple
apple,orange
apple,orange,pear
apple,orange,pear,plum
apple,pear
apple,plum
apple,orange,plum
apple,pear,plum
orange
orange,pear
orange,pear,plum
orange,plum
pear
pear,plum
plum
i.e. as long as a combination exists once, it doesn't need to appear again in a different order, e.g. with apple,orange, there is no need for orange,apple
This should work for any table size.
Result here
Note: this requires MySQL 8+.
-- TABLE
CREATE TABLE IF NOT EXISTS `fruits`
(
`id` int(6) NOT NULL,
`fruit` char(20)
);
INSERT INTO `fruits` VALUES (1, 'apple');
INSERT INTO `fruits` VALUES (2, 'orange');
INSERT INTO `fruits` VALUES (3, 'pear');
INSERT INTO `fruits` VALUES (4 ,'plum');
-- QUERY
WITH RECURSIVE cte ( combination, curr ) AS (
SELECT
CAST(t.fruit AS CHAR(80)),
t.id
FROM
fruits t
UNION ALL
SELECT
CONCAT(c.combination, ', ', CAST( t.fruit AS CHAR(100))),
t.id
FROM
fruits t
INNER JOIN
cte c
ON (c.curr < t.id)
)
SELECT combination FROM cte;
Credit:
Code adapted from this answer
EDIT: This query doesn't give all the possible combinations.
Below query should work:
WITH RECURSIVE cte AS (
SELECT A.id,
CONCAT(A.fruit,',',GROUP_CONCAT(B.fruit ORDER BY B.id)) AS combinations,
COUNT(*) AS count_of_delims
FROM fruits A
INNER JOIN fruits B
ON A.id<B.id
GROUP BY A.id,A.fruit
UNION ALL
SELECT id,
SUBSTRING_INDEX(combinations,',',count_of_delims),
count_of_delims-1
FROM cte
WHERE count_of_delims>0
)
SELECT combinations FROM cte ORDER BY id;
Here is a working example in DB Fiddle.

MySQL UPDATE with SELECT statement - convert to JOIN

Problem: MySQL - Get the sum of IBB for each PersonID in Batting and update that IBB for each PersonID in Batting_Career
Could someone help me figure out the JOIN?
What I have that is not working:
UPDATE Batting_Career
SET Batting_Career.IBB = Batting.IBB WHERE PersonID IN
( SELECT DISTINCT
PersonID,
SUM(IBB) AS IBB
FROM
Batting
GROUP BY PersonID )
AND Batting_Career.PersonID = Batting.PersonID;
The Batting select statement works (...) - it gets me the sum of IBB in a column IBB for all playerIDs but when I add to the update using it does not work.
The error that I am getting is
Operand should contain 1 column(s)
I believe the rules are You can only select one column from such a query. Which mean I may have to Join the tables. Could anyone help me figure this out?
Aggregate in Batting and join to Batting_Career:
UPDATE Batting_Career bc
JOIN (
SELECT PersonID, SUM(IBB) as IBB
FROM Batting
GROUP BY PersonID
) b ON b.PersonID = bc.PersonID
SET bc.IBB = b.IBB;
I have written a query that will do your work :)
UPDATE Batting_Career t2
SET t2.IBB = t1.IBB where PersonID IN (
SELECT
PersonID,
SUM(IBB) AS IBB
FROM
Batting
WHERE
PersonID IN(
SELECT DISTINCT
PersonID
FROM
Batting
)
GROUP BY
PersonID
) t1
WHERE
t2.PersonID = t1.PersonID;

Mysql where if condition

Basically I have a table all customer will have a default row with customer_group_id=0
But some of this customer will belong to customer_group_id=1
When that happen a new row is created for customer with customer_group_id=1 therefore now I have 2 rows for same customer but different customer_group_id.
Now when I fetch the data I need first to select * from customer table where customer_group_id =1 but if doesn't exist give me then with customer_group_id = 0 which is the default, and continue until it returns all data.
Anyone know the best way to achieve this fast?
UPDATE: screen shoot show 2 rows with same customer_id different customer_group_id:
I need to one or the other no both so hierarchy is: if customer_group_id=1 exist then return that row and ignore the ohter otherwise return default which is customer_group_id=0
My full query:
SELECT `main_table`.*, `secondTable`.* FROM `customer` AS `main_table`
LEFT JOIN `customer_group` AS `secondTable` ON main_table.customer_id = secondTable.customer_id
WHERE (secondTable.customer_group_id = '1' )
AND (`secondTable`.`is_active` = '1')
If you have only two groups, then aggregation is simple:
select customer_id, max(customer_group_id)
from t
group by customer_id;
In MySQL 8+, you can implement a more customer prioritization using row_number():
select t.*
from (select t.*,
row_number() over (partition by customer_id order by customer_group_id desc) as seqnum
from t
) t
where seqnum = 1;
What you can do is fetch all the rows for the customers with customer_group_id=1 and then use UNION ALL to fetch the rows of the customers that do not have any row with customer_group_id=1 by using NOT EXISTS:
select * from tablename
where customer_group_id=1
union all
select * from tablename t
where t.customer_group_id=0
and not exists (
select 1 from tablename
where customer_id = t.customer_id and customer_group_id=1
)
For the rows with customer_group_id = 0 verify that there are no rows with customer_group_id = 1 for the same customer_id. You can use a NOT EXISTS subquery:
SELECT c.*, g.*
FROM customer AS c
JOIN customer_group AS g ON c.customer_id = g.customer_id
WHERE NOT EXISTS (
SELECT *
FROM customer_group AS g2
WHERE g2.customer_id = c.customer_id
AND g2.customer_group_id = 1
AND g.customer_group_id = 0
)

How to retrieve all members from a transitive relationship through one SQL query [duplicate]

I have a table with following structure
Table name: matches
That basically stores which product is matching which product. I need to process this table
And store in a groups table like below.
Table Name: groups
group_ID stores the MIN Product_ID of the Product_IDS that form a group. To give an example let's say
If A is matching B and B is Matching C then three rows should go to group table in format (A, A), (A, B), (A, C)
I have tried looking into co-related subqueries and CTE, but not getting this to implement.
I need to do this all in SQL.
Thanks for the help .
Try this:
;WITH CTE
AS
(
SELECT DISTINCT
M1.Product_ID Group_ID,
M1.Product_ID
FROM matches M1
LEFT JOIN matches M2
ON M1.Product_Id = M2.matching_Product_Id
WHERE M2.matching_Product_Id IS NULL
UNION ALL
SELECT
C.Group_ID,
M.matching_Product_Id
FROM CTE C
JOIN matches M
ON C.Product_ID = M.Product_ID
)
SELECT * FROM CTE ORDER BY Group_ID
You can use OPTION(MAXRECURSION n) to control recursion depth.
SQL FIDDLE DEMO
Something like this (not tested)
with match_groups as (
select product_id,
matching_product_id,
product_id as group_id
from matches
where product_id not in (select matching_product_id from matches)
union all
select m.product_id, m.matching_product_id, p.group_id
from matches m
join match_groups p on m.product_id = p.matching_product_id
)
select group_id, product_id
from match_groups
order by group_id;
Sample of the Recursive Level:
DECLARE #VALUE_CODE AS VARCHAR(5);
--SET #VALUE_CODE = 'A' -- Specify a level
WITH ViewValue AS
(
SELECT ValueCode
, ValueDesc
, PrecedingValueCode
FROM ValuesTable
WHERE PrecedingValueCode IS NULL
UNION ALL
SELECT A.ValueCode
, A.ValueDesc
, A.PrecedingValueCode
FROM ValuesTable A
INNER JOIN ViewValue V ON
V.ValueCode = A.PrecedingValueCode
)
SELECT ValueCode, ValueDesc, PrecedingValueCode
FROM ViewValue
--WHERE PrecedingValueCode = #VALUE_CODE -- Specific level
--WHERE PrecedingValueCode IS NULL -- Root

WITH clause in MySQL?

Does MySQL support common table expressions? For example in Oracle there's the WITH clause? :
WITH aliasname
AS
( SELECT COUNT(*) FROM table_name )
SELECT COUNT(*) FROM dept,aliasname
SELECT t.name,
t.num
FROM TABLE t
JOIN (SELECT c.id,COUNT(*) 'num1'
FROM TABLE1 c
WHERE c.column = 'a'
GROUP BY c.id) ta1 ON ta1.id = t.id
JOIN (SELECT d.id,COUNT(*) 'num2'
FROM TABLE2 d
WHERE d.column = 'a'
GROUP BY d.id) ta2 ON ta2.id = t.id
One way is to use a subquery:
SELECT COUNT(*)
FROM dept,
(
SELECT COUNT(*)
FROM table_name
) AS aliasname
Note that the , between the two tables will cross join the two tables the same as in your query you posted. IF there is any relation between them you can JOIN them instead.
No, MySQL does not support Common Table Expressions (CTE). So instead of using WITH tablealias as (....), you will have to do a subquery.
For example,
WITH totalcount AS
(select userid, count(*) as tot from logins group by userid)
SELECT a.firstname, a.lastname, b.tot
FROM users a
INNER JOIN
totalcount b
on a.userid = b.userid
can be re-written in MySQL as
SELECT a.firstname, a.lastname, b.totalcount
FROM users a
INNER JOIN
(select userid, count(*) as tot from logins group by userid) b
on a.userid = b.userid
So let's talk about WITH clause .
WITH clause and INNER JOIN otherwise JOIN are a kind of same , but WITH clause gives you much more latitude especially in WHERE clause ;
I am going to make a view that'll get values like count of users , user name and etc.
First (Creating our tables users and inserted_users) :
inserted_users table :
CREATE TABLE users (id BIGINT(10) AUTO INCEREMENT PRIMARY KEY , name VARCHAR(50))
users table :
CREATE TABLE users (id BIGINT(10) AUTO INCEREMENT PRIMARY KEY , name VARCHAR(50) , gender TINYINT(1))
Second (Inserting some values to work with) :
users table :
INSERT INTO users (name,gender) VALUES ('Abolfazl M' , 1)
I don't want to insert into inserted_users by query , but I want to add a TRUGGER which will insert data automatically to users_inserted table before data be inserted into users table.
Third (Creating trigger add_uinserted) :
DELIMITER $$
CREATE TRIGGER IF NOT EXISTS add_uinserted BEFORE INSERT ON users FOR EACH ROW
BEGIN
IF NEW.name <> '' THEN
INSERT INTO users_inserted (name) VALUES (NEW.name);
ELSE
INSERT INTO users (name,gender) VALUES ('Unknown',NEW.gender);
INSERT INTO users_inserted (name) VALUES ('Unknown');
END IF;
END$$
DELIMITER ;
Run the query and the trigger will be created and at last let's create a view to give us result from a query having WITH clause .
CREATE VIEW select_users AS
WITH GetCAll AS (
SELECT u1.id As Uid ,COUNT(u1.name) AS CnAll FROM users u1
)
SELECT u1.name AS NAME,CASE
WHEN s1.gender = 1 THEN "MALE"
WHEN s1.gender = 0 THEN "FEMALE"
ELSE "UNKNOWN"
END AS GENDER,CASE
WHEN u1.id = gca.Uid THEN "INSERTED TO users_inserted"
ELSE "NOT INSERTED TO users_inserted"
END AS INSERTED,gca.CnAll FROM GetCAll AS gca INNER JOIN users u1;
After you query got ran the view will be created and by calling the view select_users the data will be shown
Last step (calling the select_users view) :
SELECT * FROM select_users
Thanks for taking a look at my answer , and that's it !!