How to create new column and new row based on two tables? - mysql

I have two tables:
Table 1
MARKET ATC3 ATC4 PRODUCT BOOLEAN FLAG JOINING COLUMN
A1 B1 B1C1 D1 1 ATC4
A2 B1 B1C2 D2 1 ATC4
A2 B1 B1C3 ATC4
FAMILY A B1 ATC3
Table 2:
PRODUCT ATC3 ATC4 VALUES
D1 B1 B1C1 10
D1 B1 B1C1 20
D2 B1 B1C2 15
D2 B1 B1C2 25
D2 B1 B1C2 10
D3 B1 B1C3 5
My desired output:
PRODUCT ATC3 ATC4 VALUES MARKET VALUES
D1 B1 B1C1 10 A1 10
D1 B1 B1C1 20 A1 20
D2 B1 B1C2 15 A2 15
D2 B1 B1C2 25 A2 25
D2 B1 B1C2 10 A2 10
D3 B1 B1C3 5 A2 5
ALL D1+D2+D3 FAMILY A 85
The idea is, Table 2 has many rows and products but does not have Market. Table 1 helps you find out which product in Table 2 belongs to which Market-based on the Joining column. For example, There are 3 Markets present in Table 1, I want to then assign a new column Market in Table 2 such that all PRODUCTS in Table 2 with the ATC4 code of B1C1 belongs to the Market A1. Why? Because in Table 1, it says that Market A1 should follow the Joining Column of ATC4 - which corresponds to the code B1C1. In Table 1, we also provided a Product column, this is just for our purpose of identifying our own companies product name. Now if you see that for Table 1, there are two rows of Market A2, with different ATC4, this is very normal, because maybe Product D2 and D10 belong to Market A2, but both may contain different ATC4!
There is also one more nuance to it, we have Family A! This is merely a combination of A1+A2, but in my Table 2, there is no such row value that sums up to Family A. So I need to achieve two things:
I want to make a new column Market in Table 2 so that each product is mapped to the market.
I want to create extra rows to account for the Market Family A (A1+A2) and call the product Name "Lovely Family A" or something. The above table 3 provides an expected output.
Since I am new to SQL, I tried to first use CASE Statements, to map slowly one by one, but soon it gets tedious and I wonder if there's some tricks.
My CASE looks like this
,CASE WHEN ATC4 LIKE '%B1C1%' THEN 'A1'
WHEN ATC4 LIKE '%B1C2%' OR ATC4 LIKE '%B1C3%' THEN 'A2' ELSE ATC4 END AS MARKET_NAME
but have yet to figure out how to add the additional row where I can sum up A1+A2.

You seem to want something like this:
with rows as (
select PRODUCT, ATC3, ATC4, VALUES
from table2
union all
select 'ALL D1+D2+D3', ATC3, NULL, SUM(VALUES)
from table2
group by ATC3
)
select r.*, t1.market
from rows r join
table1 t1
on (t1.joining_column = 'ATC3' and t1.atc3 = r.atc3) or
(t1.joining_column = 'ATC4' and t1.atc4 = r.atc4);
I see no reason to repeat the values column. And values is a really bad name for a column because it is a SQL keyword.

Related

Can I use a column dynamically using CASE WHEN in a GROUP BY query?

The query statement I want is whether GROUP BY can be changed according to the value of the selected column.
For example, The query I was thinking of is as below,
SELECT * FROM (
SELECT DATE, A,sum(B),C FROM TABLE
GROUP BY DATE
CASE WHEN C ='1' THEN A,B
ELSE '2' THEN A END.
)
If C is 1, the final query is
SELECT DATE, A,sum(B),C FROM TABLE
GROUP BY DATE, A
If C is 2, the final query is
SELECT DATE, A,sum(B),C FROM TABLE
GROUP BY DATE
If the table has data like below,
DATE A B C
---- ------------------
2022.01 A1 50 1
2022.01 A1 50 1
2022.01 A2 30 1
2022.01 A2 30 1
2022.02 A1 20 2
2022.02 A1 20 2
2022.02 A2 25 2
2022.02 A2 25 2
2022.03 A1 10 1
2022.03 A2 20 1
The query is applied like this according to each C value, so the result I want is
DATE A B C
---- -----------------------
2022.01 A1 100 1
2022.01 A2 60 1
2022.02 (A1+A2) 90 2
2022.03 A1 10 1
2022.03 A2 20 1
want Can you make a sql query with this syntax?
To change and apply the columns grouped according to the C value.. ;(
You might be able to get the result you want by combining the result of two queries using UNION ALL. The exact syntax may differ (sometimes you have to select from a subquery) and you may want to specify ordering but I think this could work.
SELECT DATE,A,B,C
FROM Table
WHERE C = '1'
GROUP BY DATE,A,B
UNION ALL
SELECT DATE,A,B,C
FROM Table
WHERE C = '2'
GROUP BY DATE,A;
EDIT:
You can play around by adding more data and adjust the column types, names, etc. according to your needs. Here's the link to the MySql 5.7 fiddle I used to test the query.
https://www.db-fiddle.com/f/gpu8cUo5wRa2Kxn86Ghjde/0
CREATE TABLE ChodingTable(SomeDate DATE, A VARCHAR(2), B INTEGER, C INTEGER);
INSERT INTO ChodingTable
(SomeDate, A, B, C)
VALUES ('2022-01-01', 'A1', 50, 1),
('2022-01-01', 'A1', 50, 1),
('2022-01-02', 'A1', 20, 2),
('2022-01-02', 'A2', 20, 2);
SELECT SomeDate,GROUP_CONCAT(DISTINCT(A) SEPARATOR '+'),SUM(B),(C)
FROM ChodingTable
WHERE C=1
GROUP BY SomeDate,A
UNION ALL
SELECT SomeDate,GROUP_CONCAT(DISTINCT(A) SEPARATOR '+'),SUM(B),C
FROM ChodingTable
WHERE C=2
GROUP BY SomeDate;

How to add up unique values from table in SQL

I have a table in SQL that looks like this
Person brand brand_spend category category_spend
0 p1 b1 20 c1 100
1 p1 b2 50 c1 100
2 p2 b1 25 c2 40
3 p1 b3 30 c1 100
4 p1 b2 15 c2 70
I need to tag every customer based on percentage spend he has made at a brand based on total category spend where that brand is present.
So essentially I would want to tag Person p1 for brand b1' as percentage spend at 'b1 which should be calculated as 45/ 140
How this can be achieved. If I roll up on brand level to find total category spend then I think duplicates rows would add up.
I just want to find customer's Spend at a brand based on total spend at Brand for all categories where that brand is present.
You need grouping on two levels, as shown here:
select person, brand, sum(brand_spend) personspend, spendbrandallcats,
round(sum(brand_spend)/spendbrandallcats,3) pbratio from tbl t
inner join ( -- brand statistics: sum of all spends per brand in all categories
select brand br, sum(casp) spendbrandallcats from tbl
inner join ( -- category statistics: total category sums
select category ca, sum(brand_spend) casp from tbl group by category
) catspend ON ca=category
group by brand
) brandstats on br=brand
group by person,brand
These are the results:
person brand personspend spendbrandallcats pbratio
p1 b1 20 140 0.143
p1 b2 65 140 0.464
p1 b3 30 100 0.3
p2 b1 25 140 0.179
See the little demo here: https://rextester.com/SVNH27609
Sometimes things are not as complicated as it seems and a simple query does the trick.
select person , brand , sum(prsn_brand_spend) ,
sum(category_spend) , (sum(prsn_brand_spend)/sum(category_spend)) as perc_spend
from tbl group by person , brand
The Results are
person brand brand_spend category_spend perc
p1 b1 20 100 0.200000
p1 b2 65 170 0.382353
p1 b3 30 100 0.300000
p2 b1 25 40 0.625000

Sql, print only rows with a value that appear at least n times

I have two tables created in sql.
players(code(PK),surname,name)
and
errors(playererrorcode(PK),date,money,code(FK to players))
So two tables looks like:
Players:
A1 Surname1 Name1
A2 Surname2 Name2
And errors for example:
E1 2015/7/10 10$ A1
E2 2015/2/20 50$ A1
E3 2015/1/30 40$ A2
E4 2015/5/20 30$ A1
Well, now, i have to print(select in this language) ONLY players that have at least 3 errors! In this case i want to print only the row that include: surname and name of who have at least 3 errors, then it have to print only surname1 and name1.
How to do that?
In SQL Server you can use the Count aggregation to get the number of errors per player and use Having to specify the number of error required -- like this:
SELECT
p.surname,
p.name,
FROM players as p
INNER JOIN errors as e
on p.code=e.code
GROUP BY
p.surname,
p.name
HAVING
COUNT(e.playererrorcode)>=3

mysql display multiple rows in one row

I have a table tbl_usi in mysql with records as below:
present_date usi_value deal_count
----------------------------------------------------------
2015-10-13 b1 c1
2015-10-12 b2 c2
2015-10-11 b3 c3
I want to write a query that will do this using present_date field to select the present date and the date before it and display them together:
present_date usi_value deal_count previous_date previous_usi_value previous_deal_count
----------------------------------------------------------
2015-10-13 b1 c1 2015-10-12 b2 c2
2015-10-12 b2 c2 2015-10-11 b3 c3
2015-10-11 b3 c3 2015-10-10 b4 c4
How do I achieve this. Thanks
Select everything from your table, then join it to itself, making sure the 2 joined tables are given different names so you can distinguish them (I used 'a' and 'b' here). The join offsets the dates by 1 day. Then you can select the fields you want from the joined table.
select
a.present_date,
a.usi_value,
a.deal_count,
b.present_date as previous_present_date,
b.usi_value as previous_usi_value,
b.deal_count as previous_deal_count
from
tbl_usi as a
left join tbl_usi as b
on b.present_date = a.present_date - interval 1 day;
If you didn't already have one before, you will now want an index for the present_date column too BTW.
Alternative, which works when there are date gaps.
select
a.present_date,
a.usi_value,
a.deal_count,
b.present_date as previous_present_date,
b.usi_value as previous_usi_value,
b.deal_count as previous_deal_count
from
tbl_usi as a
join tbl_usi as b
where
b.present_date = (select max(present_date) from tbl_usi where present_date < a.present_date);
As with previous solution the same table is joined twice, but this time the previous row is found by way of a subquery.

Get N number of records from child table for each parent record in a MySQL View

I tried finding answer to this question in SO , but could not find any. Any links will be of great help.
I have a parent table and a child table with one to many relationship between the parent and child table. The child table contains around 1 million records and I want to create a view with 1st 10 records in child table for each parent record.
Example-
Parent_Table - Fields -- ID, Name
ID Name
---- -----
1 A
2 B
3 C
Child_Table - Fields -- ID, ParentID, Date, Data
ID ParentID Date Data
--------------------------
1 1 04/10 A1
2 1 04/11 A2
3 1 04/11 A3
4 1 04/12 A4
5 1 04/12 A5
6 2 04/10 B1
7 2 04/11 B2
8 2 04/12 B3
9 2 04/12 B4
10 2 04/13 B5
11 2 04/13 B6
Now, I want to create a view with 1st 4 records for each parent record sorted by date.
Output Expected
ID ParentID Date Data
--------------------------
1 1 04/10 A1
2 1 04/11 A2
3 1 04/11 A3
4 1 04/12 A4
6 2 04/10 B1
7 2 04/11 B2
8 2 04/12 B3
9 2 04/12 B4
Any links or guide to the solution will be appreciated. Thanks in advance!
In case you need any clarification, please post a comment.
If you need to create a VIEW, you could use something like this:
CREATE VIEW First_Four AS
SELECT c1.*
FROM
Child_Table c1 LEFT JOIN Child_Table c2
ON c1.ParentID = c2.ParentID
AND (STR_TO_DATE(c1.`date`, '%m/%Y')>STR_TO_DATE(c2.`date`, '%m/%Y')
OR (STR_TO_DATE(c1.`date`, '%m/%Y')=STR_TO_DATE(c2.`date`, '%m/%Y')
AND c1.ID>c2.ID)
)
GROUP BY
c1.ID, c1.ParentID, c1.`Date`, c1.Data
HAVING
COUNT(c2.ID)<4
I'm considering the field data as a VARCHAR column, so we need to use STR_TO_DATE, if it is not we can just compare c1.date with c2.date directly.
Please see fiddle here.
I tried this one my computer and it displayed based on your requirements using your own data. I changed some field name though like ID of Child to ChildID, Date to ChildDate, Data to ChildData. Here it is:
SELECT * FROM
(SELECT ParentID, ChildID, ChildDate, ChildData, #ChildRank:= CASE WHEN #Parent <> ParentID THEN 1 ELSE #ChildRank+1 END as ChildRanking, #Parent := ParentID as Parent FROM
(SELECT #ChildRank:=0) CR, (SELECT #Parent:=1) P, (SELECT * FROM Child_Table ORDER BY
ParentID, ChildID) MainTable) AllTable WHERE ChildRanking <=4;
I use only the Child Table only but anyway you could INNER JOIN this with Parent_Table if you like.
A little explanation:
1) ChildRank will starts with Rank 0 (i.e. SELECT #ChildRank:0) but because of #ChildRank+1 it will start with Rank 1
2) When new ParentID (i.e. #Parent<> ParentID) then starts with Rank 1 right away.
3) AllTable is the alias for everything so that you could now reference the ChildRanking field.
If you don't want to display the ChildRanking field then you have to specify the fields you want to dispaly.