Limit data from another table attribule - mysql

I have table Job and table Company. Each job item will be for a company. I would like to write a query that will list the jobs.
Company has a quantity column. When jobs are listed from Job, the number of jobs listed for a given company is the number in this Quantity column.
Data example
For example: Company A has quantity = 2. The query data will return the top 2 jobs.
How can I do this in a sql query?
Table Job
| id | Title | Company ID |
|:---|------:|:------------:|
| 1 | Job 1 | 1
| 2 | Job 2 | 1
| 3 | Job 3 | 1
| 4 | Job 4 | 2
| 5 | Job 5 | 2
Table Company
| id | Title | Quantity|
|:---|----------:|:-------:|
| 1 | Company 1| 2
| 2 | Company 2| 2
And the result of query Select * From Job => With condition limit quantity of company.
| id | Title | Company ID |
|:---|------:|:----------:|
| 1 | Job 1 | 1
| 2 | Job 2 | 1
| 4 | Job 4 | 2
| 5 | Job 5 | 2

Create procedure and you can use variable with LIMIT
DELIMITER $$
CREATE PROCEDURE `GetJobs`()
NO SQL
BEGIN
DECLARE cid INT;
DECLARE jobsLimit INT;
DECLARE n INT DEFAULT 0;
DECLARE i INT DEFAULT 0;
CREATE TEMPORARY TABLE JobListTemp (
id INT,
Title VARCHAR(255),
CompanyID INT
) ENGINE=MEMORY;
SELECT COUNT(*) FROM Company INTO n;
SET i=0;
WHILE i<n DO
SET cId = (SELECT id FROM Company LIMIT i,1);
SET jobsLimit = (SELECT quantity FROM Company WHERE id = cId LIMIT 1);
INSERT INTO JobListTemp SELECT * FROM Job WHERE CompanyID = cId LIMIT jobsLimit;
SET i = i + 1;
END WHILE;
SELECT * FROM JobListTemp;
DROP TABLE JobListTemp;
END$$
DELIMITER ;

I think you can use the inner sqlquery.
For example:
select innerTab.id,innerTab.Title,innerTab.companyid
from (select * from job order by id limit 2) innerTab
inner join Company on innerTab.companyid=company.companyid
The colname is not same with you.
So you can change it to make sure the sql can run.

Related

Transform multiple rows into a single row in same table (reduce by merge group by)

Hy, i want reduce my table and updating himself (group and sum some columns, and delete rows)
Source table "table_test" :
+----+-----+-------+----------------+
| id | qty | user | isNeedGrouping |
+----+-----+-------+----------------+
| 1 | 2 | userA | 1 | <- row to group + user A
| 2 | 3 | userB | 0 |
| 3 | 5 | userA | 0 |
| 4 | 30 | userA | 1 | <- row to group + user A
| 5 | 8 | userA | 1 | <- row to group + user A
| 6 | 6 | userA | 0 |
+----+-----+-------+----------------+
Wanted table : (Obtained by)
DROP TABLE table_test_grouped;
SET #increment = 2;
CREATE TABLE table_test_grouped
SELECT id, SUM(qty) AS qty, user, isNeedGrouping
FROM table_test
GROUP BY user, IF(isNeedGrouping = 1, isNeedGrouping, #increment := #increment + 1);
SELECT * FROM table_test_grouped;
+----+------+-------+----------------+
| id | qty | user | isNeedGrouping |
+----+------+-------+----------------+
| 1 | 40 | userA | 1 | <- rows grouped + user A
| 3 | 5 | userA | 0 |
| 6 | 6 | userA | 0 |
| 2 | 3 | userB | 0 |
+----+------+-------+----------------+
Problem : i can use another (temporary) table, but i want update initial table, for :
grouping by user and sum qty
replace/merge rows into only one by group
The result must be a reduce of initial table, group by user, and qty summed.
And it's a minimal exemple, and i don't want full replace inital from table_test_grouped, beacause in my case, i have another colum (isNeedGrouping) for decide if y group or not.
For flagged rows "isNeedGrouping", i need grouping.
For this exemple, a way to do is sequentialy to :
CREATE TABLE table_test_grouped SELECT id, SUM(qty) AS qty, user, isNeedGrouping FROM table_test WHERE isNeedGrouping = 1 GROUP BY user ;
DELETE FROM table_test WHERE isNeedGrouping = 1 ;
INSERT INTO table_test SELECT * FROM table_test_grouped ;
Any suggestion for a simpler way?
It is probably simpler to empty and refill the table than to try and update/delete. Also, the aggregation logic can be simplified to avoid the use for a user variable.
You could write this as:
create table table_test_tmp as
select min(id) id, sum(qty) qty, user, isneedgrouping
from table_test tt
group by tt.user, case when tt.isneedgrouping = 0 then tt.id end;
truncate table table_test; -- back it up first!
insert into table_test (id, qty, user, isneedgrouping)
select id, qty, user, isneedgrouping
from table_test_tmp;
drop table table_test_tmp;

Join a table and calculate a percentage from this new table

I'm trying to make a report of financial datas for my company:
I have actually two two tables:
___BillableDatas:
|--------|------------|----------|----------|--------------|---------------------|
| BIL_Id | BIL_Date | BIL_Type | BIL_Rate | BIL_Quantity | BIL_ApplicableTaxes |
|--------|------------|----------|----------|--------------|---------------------|
| 1 | 2017-01-01 | Night | 95 | 1 | 1 |
| 2 | 2017-01-02 | Night | 95 | 1 | 1 |
| 3 | 2017-01-15 | Night | 105 | 1 | 1 |
| 4 | 2017-01-15 | Item | 8 | 2 | 1,2 |
| 5 | 2017-02-14 | Night | 95 | 1 | 1 |
| 6 | 2017-02-15 | Night | 95 | 1 | 1 |
| 7 | 2017-02-16 | Night | 95 | 1 | 1 |
| 8 | 2017-03-20 | Night | 89 | 1 | 1 |
| 9 | 2017-03-21 | Night | 89 | 1 | 1 |
| 10 | 2017-03-21 | Item | 8 | 3 | 1,2 |
|--------|------------|----------|----------|--------------|---------------------|
___SalesTaxes:
|--------|------------|
| STX_Id | STX_Amount |
|--------|------------|
| 1 | 14.00 |
| 2 | 5.00 |
|--------|------------|
I need to know for each month the sum of my revenue with and without taxes.
Actually I can make the report but don't know how to loop into the ___SalesTaxes table.
What I have actually:
SELECT month(BIL_Date) AS month,
sum(BIL_Rate * BIL_Quantity) AS sumval
FROM `___BillableDatas`
WHERE BIL_Date BETWEEN "2017-01-01" AND "2017-12-31"
AND BIL_Type = "Night" OR BIL_Type = "Item"
GROUP BY year(BIL_Date), month(BIL_Date)
Thanks for your help.
as kbball mentioned you have an unresolved many to many relationship in your main table. A proper table should never be designed to have more than one value per field. Resolving many to many relationships is quite simple. You will need to create a new table something like bill_taxType or some relation like that. The new table would have two fields as well as the standard primary key, it will have bill_id and applicable tax id. In the case of your 1,2 fields like bill id 4 in the new table it will look like
primary key, bill id, applicable tax id
1 4 1
2 4 2
In your final query you will join all three together on the appropriate primary key-foreign key relationship. This final query should have the data that you need.
This will work, I've created following example will help you lot for debugging and implementation. try to implement as below :
If(OBJECT_ID('tempdb..#___BillableDatas') Is Not Null)
Begin
Drop Table #___BillableDatas
End
If(OBJECT_ID('tempdb..#___SalesTaxes') Is Not Null)
Begin
Drop Table #___SalesTaxes
End
CREATE TABLE #___BillableDatas
(
BIL_Id INT IDENTITY (1,1),
BIL_Date DATETIME,
BIL_Type VARCHAR(50),
BIL_Rate FLOAT,
BIL_Quantity INT,
BIL_ApplicableTaxes VARCHAR(10)
);
INSERT INTO #___BillableDatas (BIL_Date,BIL_Type,BIL_Rate,BIL_Quantity,BIL_ApplicableTaxes)
VALUES ('2017-01-01','Night',95,1,'1'),
('2017-01-02','Night',95,1,'1'),
('2017-01-15','Night',105,1,'1'),
('2017-01-15','Item',8,2,'1,2'),
('2017-02-14','Night',95,1,'1'),
('2017-02-15','Night',95,1,'1'),
('2017-02-16','Night',95,1,'1'),
('2017-03-20','Night',89,1,'1'),
('2017-03-21','Night',89,1,'1'),
('2017-03-21','Item',8,1,'1,2')
CREATE TABLE #___SalesTaxes
(
STX_Id INT IDENTITY (1,1),
STX_Amount FLOAT
);
INSERT INTO #___SalesTaxes (STX_Amount) VALUES (14.00),(5.00)
-----------------------------------------------------------------
SELECT * FROM #___BillableDatas
SELECT * FROM #___SalesTaxes
SELECT MONTH(BD.BIL_Date) AS [Month],SUM(BD.BIL_Rate * BD.BIL_Quantity) AS 'Without Tax'
,(SUM(BD.BIL_Rate * BD.BIL_Quantity)+((SUM(BD.BIL_Rate * BD.BIL_Quantity)/100)*BD.Tax1)) AS 'With Tax 1'
,(SUM(BD.BIL_Rate * BD.BIL_Quantity)+((SUM(BD.BIL_Rate * BD.BIL_Quantity)/100)*BD.Tax2)) AS 'With Tax 2'
FROM
(
SELECT *,
(SELECT ST1.STX_Amount FROM Func_Split(BIL_ApplicableTaxes,',') AS F LEFT JOIN #___SalesTaxes AS ST1 ON F.Element=ST1.STX_Id WHERE F.Element='1') AS Tax1 ,
(SELECT ST1.STX_Amount FROM Func_Split(BIL_ApplicableTaxes,',') AS F LEFT JOIN #___SalesTaxes AS ST1 ON F.Element=ST1.STX_Id WHERE F.Element='2') AS Tax2
FROM #___BillableDatas) AS BD
WHERE BD.BIL_Date BETWEEN '2017-01-01' AND '2017-12-31' AND BD.BIL_Type = 'Night' OR BD.BIL_Type = 'Item'
GROUP BY YEAR(BD.BIL_Date), MONTH(BD.BIL_Date),BD.Tax1,BD.Tax2
You will require function Func_Split for above solution, use this :
CREATE FUNCTION [dbo].[func_Split]
(
#DelimitedString varchar(8000),
#Delimiter varchar(100)
)
RETURNS #tblArray TABLE
(
ElementID int IDENTITY(1,1), -- Array index
Element varchar(1000) -- Array element contents
)
AS
BEGIN
-- Local Variable Declarations
-- ---------------------------
DECLARE #Index smallint,
#Start smallint,
#DelSize smallint
SET #DelSize = LEN(#Delimiter)
-- Loop through source string and add elements to destination table array
-- ----------------------------------------------------------------------
WHILE LEN(#DelimitedString) > 0
BEGIN
SET #Index = CHARINDEX(#Delimiter, #DelimitedString)
IF #Index = 0
BEGIN
INSERT INTO
#tblArray
(Element)
VALUES
(LTRIM(RTRIM(#DelimitedString)))
BREAK
END
ELSE
BEGIN
INSERT INTO
#tblArray
(Element)
VALUES
(LTRIM(RTRIM(SUBSTRING(#DelimitedString, 1,#Index - 1))))
SET #Start = #Index + #DelSize
SET #DelimitedString = SUBSTRING(#DelimitedString, #Start , LEN(#DelimitedString) - #Start + 1)
END
END
RETURN
END

Update a table in MySQL with all data from another table

I have 3 tables: ak_class, ak_objects, ak_class_object
ak_class:
class_id | class_description | class_name |
1 | some description | some name |
2 | some description | some name |
3 | some description | some name |
ak_objects:
object_id | object_description | object_name |
1 | some description | some name |
2 | some description | some name |
3 | some description | some name |
ak_class_object:
class_object_id | class_id | object_id |
1 | 1 | 1 |
2 | 2 | 2 |
3 | 3 | 3 |
I need to fill in the ak_class_object with a class_id from ak_class table and object_id from ak_objects table.
The question is how can I update (I need to update as there is some wrong data currently) the class_id from the ak_class table with all the ids? I was thinking of using it with JOIN ut I don't know which id to use to Join them as class_id is only to be updated
UPD: I was trying to do it like this, but it didn't work:
DELIMITER $$
DROP PROCEDURE class_object_1$$
CREATE PROCEDURE class_object_1()
BEGIN
DECLARE i INT DEFAULT 0;
WHILE (i < 250000) DO
UPDATE ak_class_object
SET class_id = SELECT DISTINCT class_id from ak_class, object_id = SELECT DISTINCT class_id from ak_objects;
SET i = i + 1;
END WHILE;
END$$
I am writing the generic syntax, change the table names and column name as per your requirements.
update table1 inner join table2
on table1.id = table2.fk_id
set table1.column = table1.columnUpdated

MySQL Trigger to update all entries with a many-many relationship

I am trying to make a MySQL trigger that will update all my users scores if a levels reward is updated. I have got as far as working out the difference between the old and new reward but am stuck on how to update every users score who has completed a level of that. Below is a simplified table structure.
Users
+---------+-------+
| user_id | score |
+---------+-------+
Users_levels
+---------+----------+
| user_id | level_id |
+---------+----------+
Levels
+----------+---------+
| level_id | tier_id |
+----------+---------+
Tier_reward
+---------+----------+
| tier_id | reward |
+---------+----------+
This is how far I have got so far:
CREATE TRIGGER update_level_reward AFTER UPDATE ON tier_reward FOR EACH ROW
BEGIN
DECLARE REWARD INT;
SET REWARD = OLD.reward - NEW.reward;
-- UPDATE users SET score = score - REWARD WHERE user_id = ;
END$$
I have tried something along the lines of:
UPDATE users
INNER JOIN users_levels
ON users.user_id = users_levels.user_id
INNER JOIN levels
ON users_levels.level_id = levels.level_id
SET score = score - REWARD
WHERE levels.tier = OLD.tier;
However this only reduces the score once even if a user has completed more than one level from that tier.
Example
Users
+---------+-------+
| user_id | score |
+---------+-------+
| 1 | 400 |
| 2 | 700 |
+---------+-------+
Users_levels
+---------+----------+
| user_id | level_id |
+---------+----------+
| 1 | 1 |
| 1 | 2 |
| 2 | 1 |
| 2 | 3 |
+---------+----------+
Levels
+----------+---------+
| level_id | tier_id |
+----------+---------+
| 1 | 1 |
| 2 | 1 |
| 3 | 2 |
+----------+---------+
Tier_reward
+---------+----------+
| tier_id | reward |
+---------+----------+
| 1 | 200 |
| 2 | 500 |
+---------+----------+
Now if the reward for tier_id 1 is reduced to 100. User 1 should now have a score of 200 as they have completed two levels of that tier. User 2 should loose only 100 points as they have only completed one level of that tier.
Users
+---------+-------+
| user_id | score |
+---------+-------+
| 1 | 200 |
| 2 | 600 |
+---------+-------+
Tier_reward
+---------+----------+
| tier_id | reward |
+---------+----------+
| 1 | 100 |
| 2 | 500 |
+---------+----------+
Sorry about the delay !!
Here is what you can do, you need to first get the count per tier per user. Then use cursor to set these values and finally update the users table.
delimiter //
create trigger update_level_reward after update on tier_reward
for each row
begin
DECLARE done INT DEFAULT FALSE;
DECLARE tier_count int;
DECLARE user_id_fetch int;
DECLARE reward INT;
DECLARE reward_recal int ;
DECLARE cur CURSOR FOR
select
ul.user_id, count(*) as total
from users_levels ul
join levels l on l.level_id = ul.level_id
where l.tier_id = new.tier_id
group by ul.user_id;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
SET reward = OLD.reward - NEW.reward;
OPEN cur;
update_loop: LOOP
FETCH cur INTO user_id_fetch,tier_count;
IF done THEN
LEAVE update_loop;
END IF;
set reward_recal = tier_count*reward ;
update users
SET score = score - reward_recal
where user_id = user_id_fetch ;
END LOOP;
CLOSE cur;
end ; //
delimiter ;
Here what I did in mysql
create table users (user_id int,score int);
insert into users values (1,400),(2,700);
create table users_levels(user_id int,level_id int);
insert into users_levels values
(1,1),(1,2),(2,1),(2,3);
create table levels (level_id int,tier_id int);
insert into levels values
(1,1),(2,1),(3,2);
create table tier_reward(tier_id int,reward int);
insert into tier_reward values
(1,200),(2,500);
mysql> update tier_reward set reward = 100 where tier_id = 1 ;
Query OK, 1 row affected (0.15 sec)
mysql> select * from users ;
+---------+-------+
| user_id | score |
+---------+-------+
| 1 | 200 |
| 2 | 600 |
+---------+-------+
2 rows in set (0.00 sec)

Expanding a mySQL table using only mySQL

Let's say I have a mySQL table whose structure is like this:
mysql> select * from things_with_stuff;
+----+---------+----------+
| id | counter | quantity |
+----+---------+----------+
| 1 | 101 | 1 |
| 2 | 102 | 2 |
| 3 | 103 | 3 |
+----+---------+----------+
My goal is to "expand" the table so I end up with something like:
mysql> select * from stuff;
+----+---------+
| id | counter |
+----+---------+
| 1 | 101 |
| 2 | 102 |
| 3 | 102 |
| 4 | 103 |
| 5 | 103 |
| 6 | 103 |
+----+---------+
And I want to do this "expansion" using only mysql. Note that I end up with a row per quantity and per counter. Any suggestions? A stored procedure is not an option here (I know they offer while loops).
Thanks!
The following will do the trick as long as some_large_table has a length greater than or equal to the largest quantity in things_with_stuff. For example, let some_large_table be a big fact table in a data warehouse.
SELECT #kn:=#kn+1 AS id, counter
FROM (SELECT #kn:=0) k, things_with_stuff ts
INNER JOIN (
SELECT #rn:=#rn+1 AS num
FROM (SELECT #rn:=0) t, some_large_table
) nums ON num <= ts.quantity;
Assuming there is a maximum value for quantity, you could do:
INSERT INTO things SELECT counter FROM things_with_stuff WHERE quantity > 0;
INSERT INTO things SELECT counter FROM things_with_stuff WHERE quantity > 1;
INSERT INTO things SELECT counter FROM things_with_stuff WHERE quantity > 2;
--... and so on until the max.
It's a bit of a hack but it should do the job.
If the ordering is important you could do a clean up afterwards.
I have sometimes in databases a table named num (number) with a single column i, filled with all integers from 1 to 1000000. It's not hard to make such a table and populate it.
Then you could use this if stuff.id is auto incremented:
INSERT INTO stuff
( counter )
SELECT ts.counter
FROM things_with_stuff AS ts
JOIN num
ON num.i <= ts.quantity