Join new columns based on row values - mysql

I have a table called flux with :
INSERT INTO `flux` (`id`, `name`) VALUES
(20, 'test1'),
(21, 'test2');
And a table called flux_rules with :
INSERT INTO `flux_rules` (`id`, `id_flux`, `id_modele_scrape`, `disallow`) VALUES
(1, 20, 1, 1),
(2, 21, 1, 1);
And one more table called modele_scrape with :
INSERT INTO `modele_scrape` (`id`, `modele`) VALUES
(1, 'renault captur'),
(2, 'renault zoe');
I believe this is a pretty usual setup, but what I haven't found information on what I'm trying to achieve, which is a SELECT query that would list all rows from modele_scrape with a column for each row in flux where the value is defined by the flux_rules column (1 IF EXISTS, ELSE 0) :
modele_scrape.id modele_scrape.modele test1 test2
1 test 1 0
Right now I have working query, but it only works to join one row from flux :
SELECT id AS id,
(SELECT IF (EXISTS (SELECT id_flux, id_modele_scrape FROM flux_rules WHERE id_flux = 20 AND id_modele_scrape = modele_scrape.id), 1, 0) ) AS disallowed
FROM modele_scrape
The solution should work as to dynamically join X amount of columns where X is the amount of rows in flux (rows from this table can be added/deleted)

Generically this sounds like a desire to "pivot" your information, and this may achieved by using "conditional aggregates" (basically this means using a case expression inside an aggregate function, typically this is the max() function.
select
m.id
, m.modele
, max(case when r.r.flux_id = 1 then flux.name end) as `name1`
, max(case when r.r.flux_id = 2 then flux.name end) as `name2`
, max(case when r.r.flux_id = 3 then flux.name end) as `name3`
from modele_scrape as m
left join flux_rules as r on m.id = r.id_modele_scrape
left join flux as f on r.flux_id = f.id
group by
m.id
, m.modele
Dynamic Pivot in MySQL
To produce such a pivot dynamically, the following query will work:
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
' max(case when r.id_flux = ''',
id,
''' then 1 else 0 end) ',
name
)
) INTO #sql
FROM
flux;
set #sql = CONCAT('SELECT m.id, m.modele,', #sql,
' from modele_scrape as m',
' left join flux_rules as r on m.id = r.id_modele_scrape',
' left join flux as f on r.id_flux = f.id',
' group by m.id, m.modele')
;
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
id | modele | test1 | test2
-: | :------------- | ----: | ----:
1 | renault captur | 1 | 1
2 | renault zoe | 0 | 0
see this as a working model here
NOTE: due to limited sample data I chose to produce the dynamic columns by directly querying the flux table, in the real tables this might produce more columns than you want/need so adjust the group_concat query, perhaps with joins to other tables and/or a where clause, to suit your data and requirements

Related

I want to use one dynamic row value as column name in mysql query

First Table Name: Operators
operator_id op_name
1 Demo 1
2 Demo 2
3 Demo 3
Second Table Name: Packs
pack_id pack_name 1 2 3
1 First 0.25 0.5 1.0
I want to Select * from Operators and if a first row of first table is selected and need to select value of which second tables column name equals to first table operator_id value in single my sql query
I have tried below but not working as excepted
$sql="SELECT *,
,(SELECT (SELECT operators.operator_id) FROM comission_packs WHERE comission_packs.pack_id=1 ) as percent
FROM `operators` ";
I want result like below
operator_id op_name percent
1 Demo 1 0.25
2 Demo 2 0.50
3 Demo 3 1.0
You can't use an expression for column names.
You can use a CASE expression to select the corresponding column from commission_packs
SELECT o.operator_id, o.op_name,
CASE o.operator_id
WHEN 1 THEN p.1
WHEN 2 THEN p.2
WHEN 3 THEN p.3
END AS percent
FROM operators AS o
CROSS JOIN comission_packs
WHERE p.pack_id = 1
a mpore dynamoc approach would be, but this would only wok for 1 comission_packs at a time
You should change your your design
pack_id pack_name operator_id percentage
Which would make it easier to get your result or privot the querys to get all packs in one table
CREATE TABLE operators (
`operator_id` INTEGER,
`op_name` VARCHAR(6)
);
INSERT INTO operators
(`operator_id`, `op_name`)
VALUES
('1', 'Demo 1'),
('2', 'Demo 2'),
('3', 'Demo 3');
CREATE TABLE comission_packs (
`pack_id` INTEGER,
`pack_name` VARCHAR(5),
`1` FLOAT,
`2` FLOAT,
`3` INTEGER
);
INSERT INTO comission_packs
(`pack_id`, `pack_name`, `1`, `2`, `3`)
VALUES
('1', 'First', '0.25', '0.5', '1.0');
SET #fields = (
SELECT
group_concat('WHEN ', `operator_id`, ' THEN p.`',`operator_id`,'`' SEPARATOR " ")
FROM
operators
ORDER BY `operator_id`
);
SET #stmt = CONCAT('SELECT o.operator_id, o.op_name,
CASE o.operator_id ',#fields,' END AS percent
FROM operators AS o
CROSS JOIN comission_packs p WHERE p.pack_id = 1');
PREPARE stmt FROM #stmt;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
operator_id | op_name | percent
----------: | :------ | ------:
1 | Demo 1 | 0.25
2 | Demo 2 | 0.5
3 | Demo 3 | 1
db<>fiddle here

Mysql select dynamic row values as column name from a table and value from another table

I have arranged a survey where a project has some questions. Users have to collect the given project answer from public. Survey tables like
user tables
user_id user_name
1 User 1
2 User 2
Project table
project_id project_name
1 project 1
2 project 2
Question table
ques_id project_id ques_name
1 1 Question 1
2 1 Question 2
3 1 Question 3
4 1 Question 4
Answer table
ans_id public_id user_id ques_id ques_ans
1 1 1 1 Answer 1
2 1 1 2 Answer 2
3 1 1 3 Answer 3
4 1 1 4 Answer 4
Now i want to generate a reports where question table values as column name matched by given project_id and question answers as value from answer table matched by ques_id
And, her is my expected output:
User_Name public_id Question 1 Question 2 Question 3 ...
User 1 1 Answer 1 Answer 2 Answer 3 ...
Someone suggested to use pivot but i found "MySQL doesn't have native support for pivoting operations" can anyone help me out?
You can use another output format of the query. For example:
SELECT user_name, answer.project_id, ques_name, ques_ans
FROM
`answer`
INNER JOIN `user` USING (user_id)
INNER JOIN `question` USING (ques_id);
To restrict rows by specific project add WHERE clause:
WHERE project_id = #ProjectID
Then transform the result to the desired view using PHP.
If it is critical to solve the question using MySQL then create new colums manually using aliaces. To aggregate rows by user and project use GROUP BY clause. To show the possible non-empty values use MAX() function. In your case:
SELECT
user_name, project_id,
MAX(IF(ques_name = 'Question 1', ques_ans, NULL)) AS `Question 1`,
MAX(IF(ques_name = 'Question 2', ques_ans, NULL)) AS `Question 2`,
MAX(IF(ques_name = 'Question 3', ques_ans, NULL)) AS `Question 3`,
MAX(IF(ques_name = 'Question 4', ques_ans, NULL)) AS `Question 4`
FROM
(SELECT
ans_id, user_id, user_name, answer.project_id, ques_name, ques_ans
FROM
answer
INNER JOIN `user` USING (user_id)
INNER JOIN question USING (ques_id)
) AS tmp
GROUP BY
user_id, project_id;
Finally the code is working
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'MAX(IF(a.ques_id = ',
ques_id,
', a.ques_ans, NULL)) AS `',
ques_name,'`'
)
) INTO #sql
FROM survey_answer inner join survey_question on survey_answer.ques_id=survey_question.id;
set #sql = CONCAT('select u.user_name ,q.category_id,a.p_code, ' ,#sql,' FROM `survey_answer` as a
LEFT JOIN `users` as u ON a.user_id = u.user_id
LEFT JOIN `survey_question` as q ON a.ques_id= q.id');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
your query should be :
SELECT an.user_name,an.public_id,qs.quest_name,an.quest_answer
FROM answer_table an,question_table qs,user_table usr
WHERE an.quest_id = qs.quest_id
AND an.user_id=usr.user_id
For the table rotation : here a similar question about rotating tables
mysql pivot table date (vertical to horizontal data)

How to pivot? How to convert multiple rows into one row with multiple columns?

I have two tables which I want to combine. The first table is with clients and the other with products. Currently I have 22 products, but I want to have a flexible DB design so instead of having 22 columns in the product DB, I have 1 row for each product for each client so if I add or remove 1 product overall, I don't have to change the DB structure.
I want to have a select statement where I select all products for each client and the output should be in a single row with a column for each product.
I have seen some other questions which are similar, but there the aim is to have all the rows concatenated in 1 column- which I don't want.
Assuming 2 clients and 3 products.
Table client:
ClientId | ClientName
---------------------
1 | Name1
2 | Name2
Table products
ProductId | ClientId | Product
-------------------------------------
1 | 1 | SomeproductA
2 | 1 | SomeproductB
3 | 1 | SomeproductA
4 | 2 | SomeproductC
5 | 2 | SomeproductD
6 | 2 | SomeproductA
The output should be something like:
Table output:
ClientId | ClientName | Product1 | Product 2 | Product 3
-------------------------------------------------------------------
1 | Name1 | SomeproductA | SomeproductB | SomeproductA
2 | Name2 | SomeproductC | SomeproductD | SomeproductA
The perfect solution would also be flexible in the sense that the select statement should count the number of distinct products for each client (they will always be the same for all clients), such that if I add or remove 1 product for all clients, I should not change the select statement.
MYSQL Edition
Here is the query. The joined query generates RowNumber (1,2,3,...) for each product inside each client group using User Defined Variables MySQL feature. The outer query forms a PIVOT table using GROUP BY and CASE with Row Numbers from the inner table. If you need to variable products column count then consider creating this query dynamic adding MAX(CASE WHEN p.RowNum=X THEN p.Product END) as ProductX to the select list.
select Clients.ClientName,
MAX(CASE WHEN p.RowNum=1 THEN p.Product END) as Product1,
MAX(CASE WHEN p.RowNum=2 THEN p.Product END) as Product2,
MAX(CASE WHEN p.RowNum=3 THEN p.Product END) as Product3,
MAX(CASE WHEN p.RowNum=4 THEN p.Product END) as Product4
FROM Clients
JOIN
(
SELECT Products.*,
if(#ClientId<>ClientId,#rn:=0,#rn),
#ClientId:=ClientId,
#rn:=#rn+1 as RowNum
FROM Products, (Select #rn:=0,#ClientId:=0) as t
ORDER BY ClientId,ProductID
) as P
ON Clients.ClientId=p.ClientId
GROUP BY Clients.ClientId
SQLFiddle demo
SQL Server Edition:
select Clients.ClientId,
MAX(Clients.ClientName),
MAX(CASE WHEN p.RowNum=1 THEN p.Product END) as Product1,
MAX(CASE WHEN p.RowNum=2 THEN p.Product END) as Product2,
MAX(CASE WHEN p.RowNum=3 THEN p.Product END) as Product3,
MAX(CASE WHEN p.RowNum=4 THEN p.Product END) as Product4
FROM Clients
JOIN
(
SELECT Products.*,
ROW_NUMBER() OVER (PARTITION BY ClientID ORDER BY ProductID)
as RowNum
FROM Products
) as P
ON Clients.ClientId=p.ClientId
GROUP BY Clients.ClientId
SQLFiddle demo
The answers seem to address both MySQL and SQL Server, so I am adding a further SQL Server Answer here and the logic might work in MySQL too.
Below is a dynamic SQL version in Transact SQL for MS SQL Server.
This enables you to get the same result without having to explicitly write out every column you need in the resultant table as the CASE WHEN solution. The CASE WHEN is nice and simple for a few columns, but I recently had a similar scenario that pivoted to around 200 columns.
For dynamic SQL you essentially compile the query that you want as a string using generated variables and then execute it.
-- variable tables to store data
DECLARE #Clients TABLE(ClientID int,
ClientName nvarchar(10))
DECLARE #Products TABLE(ProductID int,
ClientID int,
Product nvarchar(15))
-- populate the variable tables with sample data
INSERT INTO #Clients
VALUES (1, 'Name1'),
(2, 'Name2')
INSERT INTO #Products
VALUES (1, 1, 'SomeproductA'),
(2, 1, 'SomeproductB'),
(3, 1, 'SomeproductA'),
(4, 2, 'SomeproductC'),
(5, 2, 'SomeproductD'),
(6, 2, 'SomeproductA')
-- display the tables to check
SELECT * FROM #Clients
SELECT * FROM #Products
-- join the two tables and generate a column with rows which will become the new
-- column names (Product_col) which gives a number to each product per client
SELECT c.ClientID,
c.ClientName,
p.ProductID,
p.Product,
CONCAT('Product', ROW_NUMBER()
OVER(PARTITION BY c.ClientID ORDER BY p.Product ASC)) AS Product_col
INTO #Client_Products
FROM #Products p
LEFT JOIN #Clients c ON c.ClientID = p.ClientID
-- view the joined data and future column headings
SELECT * FROM #Client_Products
-- setup for the pivot, declare the variables to contain the column names for pivoted
-- rows and the query string
DECLARE #cols1 AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
-- column name list for products
SET #cols1 = STUFF((SELECT distinct ',' + QUOTENAME(Product_col)
FROM #Client_Products
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SELECT #cols1 -- view the future column names
-- generate query variable string
-- the top select is all the columns you want to actually see as the result
-- The inner query needs the columns you want to see in the result, and the columns
-- you are pivoting with. The pivot needs to select the value you want to go into the
-- new columns (MAX()) and the values that will become the column names (FOR x IN())
SET #query = 'SELECT ClientID,
ClientName,'
+ #cols1 +'
FROM
(
SELECT ClientID,
ClientName,
Product_col,
Product
FROM #Client_Products
) x
PIVOT
(
MAX(Product)
FOR Product_col IN (' + #cols1 + ')
) p'
EXECUTE(#query) -- execute the dynamic sql
DROP TABLE #Client_Products

Pivot Table Using MySQL

I have two tables Triples and Tags
Triples Table has the following Columns
id PostID TagID Value
1 1 1 Murder
2 1 2 New Brunswick
3 2 1 Theft
4 2 3 Gun
Tags Table has the following Columns
id TagName
1 Incident
2 Location
3 Weapon
I am trying to write sql to create a Pivot Table with Dynamic Headers
Output should be like this
PostID Incident Location Weapon
1 Murder New Brunswick
2 Theft Gun
Any help in writing the SQL would be appreciated. I have seen examples online but could not figure out this one
In order to pivot the data in MySQL, you will need to use both an aggregate function and a CASE expression.
If you have a known number of columns, then you can hard-code the query:
select p.postid,
max(case when t.tagname = 'Incident' then p.value end) Incident,
max(case when t.tagname = 'Location' then p.value end) Location,
max(case when t.tagname = 'Weapon' then p.value end) Weapon
from triples p
left join tags t
on p.tagid = t.id
group by p.postid;
See SQL Fiddle with Demo
But if you have an unknown number of columns, then you will need to use a prepared statement to generate dynamic SQL:
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'max(CASE WHEN TagName = ''',
TagName,
''' THEN p.value END) AS `',
TagName, '`'
)
) INTO #sql
FROM tags;
SET #sql
= CONCAT('SELECT p.postid, ', #sql, '
from triples p
left join tags t
on p.tagid = t.id
group by p.postid');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
See SQL Fiddle with Demo.
Both will give the result:
| POSTID | INCIDENT | LOCATION | WEAPON |
----------------------------------------------
| 1 | Murder | New Brunswick | (null) |
| 2 | Theft | (null) | Gun |

mysql pivot table or something

Ok, I'm still a beginner in sql and can not figure this one out yet.
I have four tables: companies, persons, details, person_details.
companies:
id, compname
(1, ACME),
(2, ACME Group), ...
persons:
id, name, lastname, company id
(1, donald, duck, 1),
(2, lucky, luke, 1),
(3, mickey, mouse, 2)
details:
id, description
(1, 'weight'),
(2, 'height'),
(3, 'haircolor'), ...
person_details:
id, persons id, details id, value
(1, 1, 1, 70),
(2, 1, 3, 'red'),
(3, 2, 1, 90),
(4, 3, 2, 180)
As you can see, not all persons have all the details and the list of available details is variable.
Now, for a given arary of person ids and detail ids, I would like to get rows containing: company name and id, person name and last name, detail name and value for each of the details in the supplied array.
Let's say persons(1,2), details(1,3) should result in:
companies.id, companies.name, name, lastname, details.description, person_details.value,...
1, ACME, donald, duck, 'weight', 70, 'haircolor', 'red'
2, ACEM, lucky, luke, 'weight', 90, 'haircolor', null
Help, please...
Based on your description it seems like you want to pivot the data but unfortunately MySQL does not have a pivot function so you will need to replicate it using an aggregate function with a CASE statement.
If you know the description values ahead of time you can hard-code your query to the following:
select c.id,
c.compname,
p.name,
p.lastname,
max(case when d.description = 'weight' then pd.value end) weight,
max(case when d.description = 'haircolor' then pd.value end) haircolor,
max(case when d.description = 'height' then pd.value end) height
from companies c
left join persons p
on c.id = p.`company id`
left join person_details pd
on p.id = pd.`persons id`
left join details d
on pd.`details id` = d.id
-- where p.id in (1, 2)
group by c.id, c.compname, p.name, p.lastname
See SQL Fiddle with Demo
If you have an unknown number of values, then you can use a prepared statement to generate this dynamically similar to this:
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'MAX(CASE WHEN d.description = ''',
description,
''' then pd.value end) AS ',
description
)
) INTO #sql
FROM details;
SET #sql = CONCAT('SELECT c.id,
c.compname,
p.name,
p.lastname, ', #sql, '
from companies c
left join persons p
on c.id = p.`company id`
left join person_details pd
on p.id = pd.`persons id`
left join details d
on pd.`details id` = d.id
-- where p.id in (1, 2)
group by c.id, c.compname, p.name, p.lastname');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
See SQL Fiddle with Demo
Both versions generate the result:
| ID | COMPNAME | NAME | LASTNAME | WEIGHT | HEIGHT | HAIRCOLOR |
---------------------------------------------------------------------
| 1 | ACME | donald | duck | 70 | (null) | red |
| 1 | ACME | lucky | luke | 90 | (null) | (null) |
| 2 | ACME Group | mickey | mouse | (null) | 180 | (null) |