I have one table that looks like this called survey_1:
================================================
|id | token | 1X2X1 | 1X2X2 |
=====+========+===============+================|
| 1 | 1 | YES | Justin Beiber |
|----+--------+---------------+----------------|
| 2 | 1 | YES | Britney Spears |
|----+--------+---------------+----------------|
note: 1X2X1 represents- survey-id X group-id X question-id
I have another table called survey_questions:
===============================================================
|sid | gid | qid | question |
=====+========+===============+===============================|
| 1 | 2 | 1 | Do you listen to music? |
|----+--------+-----------------------------------------------|
| 1 | 2 | 2 | Who is your favorite music artists? |
|----+--------+-----------------------------------------------|
The sid (survey-id), gid (group-id) and qid(question-id) define that specific question in this table
I need a query that will give me a result like this:
======================================================
| Question | Answer |
=========+===========+===============================|
| Do you listen to music? | YES |
|----------------------------------------------------|
| Who is your favorite music artists? | Justin Beiber|
|----------------------------------------------------|
NOTE: My database contains thousands of these columns, so it would be very time consuming to edit every survey to match up perfectly in this format.
Can anyone help out with this? Thank you
Can you change the table schema? Cause that first table, survey_1 is better written with one row per answer and with the entire key of the other table per row. Like this (add your own indexes)
create table survey_1 (
id int,
token int,
sid int,
gid int,
qid int,
answer varchar(255)
)
Than the data would be
------------------------------------------
| 1 | 1 | 1 | 2 | 1 | "YES" |
| 1 | 1 | 1 | 2 | 2 | "Justin Beiber" |
| 2 | 1 | 1 | 2 | 1 | "YES" |
| 2 | 1 | 1 | 2 | 2 | "Britney Spears" |
------------------------------------------
It's going to be much easier to work with and generally a better design.
Here is how it would look http://sqlfiddle.com/#!2/4f1ca/2
Create a view for each survey. For the old surveys a simple script should be able to do it, for new surveys make it a part of the process when creating new surveys. This is how the view could look for survey_1
create or replace view v_survey_1 as
select id, question, 1X2X1 as answer
from question
join survey_1 s
where sid = 1
and gid = 2
and qid = 1
union
select id, question, 1X2X2
from question
join survey_1 s
where sid = 1
and gid = 2
and qid = 2
;
http://sqlfiddle.com/#!2/63aee/1
To build the views a script would roughly do like this.
Find all tables to build views on by running
select table_name
from information_schema.tables
where table_schema = 'test'
and table_name like 'survey\_%';
For each view find the union parts by running this for its table
select column_name
from information_schema.columns
where table_name = 'survey_1'
and column_name regexp '^[0-9]+X[0-9]+X[0-9]+$';
Extract the number parts and use them when comparing with sid, gid and qid.
This script could also be used to populate new proper tables.
You need to use 'UNPIVOT', which MySQL unfortunately does not support. You can do a similar thing by hardcoding the column names (but you need to know all the columns in advance) like this:
SELECT survey_questions.Question,
CASE survey_questions.qid
WHEN 1 THEN survey_1.`1X2X1`
WHEN 2 THEN survey_1.`1X2X2`
WHEN 3 THEN survey_1.`1X2X3`
WHEN 4 THEN survey_1.`1X2X4`
// ...
END as Answer
FROM survey_questions
JOIN survey_1
ON survey_questions.qid = survey_1.id
AND survey_questions.gid = survey_1.token_id
WHERE survey_questions.sid = 1
Of course, you can always use some scripting language to generate the column names for you... For example, here is a stored procedure you can make:
CREATE PROCEDURE 'get_qa_for_survey'
(
IN surveyId INT
)
BEGIN
DECLARE query1 TEXT;
SET #tableName = 'survey_' + surveyId;
SET query1 = 'SELECT survey_questions.Question,
CASE survey_questions.qid ';
DECLARE col_names CURSOR FOR
SELECT column_name
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name = #tableName
AND (column_name LIKE surveyId +'X%');
ORDER BY ordinal_position;
select FOUND_ROWS() into num_rows;
SET i = 1;
the_loop: LOOP
IF i > num_rows THEN
CLOSE col_names;
LEAVE the_loop;
END IF;
FETCH col_names
INTO col_name;
SET query1 = query1 + ' WHEN ' + i + ' THEN ' + #tableName + '.' + col_name
SET i = i + 1;
END LOOP the_loop;
SET query1 = query1 + ' END as Answer
FROM survey_questions
JOIN ' + #tableName + '
ON survey_questions.qid = ' + #tableName + '.id
AND survey_questions.gid = ' + #tableName + '.token_id
WHERE survey_questions.sid = ' + surveyId;
SET #Sql = query1;
PREPARE STMT FROM #Sql;
EXECUTE STMT;
DEALLOCATE PREPARE STMT;
END
Related
I have a MySQL table that looks something like this:
----------------------
| ID | Name | Parent |
----------------------
| 1 | a | null |
| 2 | b | null |
| 3 | c | 1 |
| 4 | d | 3 |
| 5 | e | 2 |
| 6 | f | 2 |
----------------------
with an unknown number of possible depth to the parent/child relationship.
I want a query that will give me the following result:
-----------------
| ID | Children |
-----------------
| 1 | 3,4 | -- because 4 is a child of 3, and 3 is a child of 1, it should show in both
| 2 | 5,6 |
| 3 | 4 |
| 4 | null |
| 5 | null |
| 6 | null |
-----------------
Is it possible to get this kind of result in a query? I've been trying everything I know to try, and searching everywhere and have not found something that will give me this result.
Considering out table as employees with fields ID, Name, Parent.
Approach 1: When we know the depth of our hierarchy
We can simply join our table n time equal to our hierarchy and can use GROUP BY to get the desired results. Here it is 3 so
SELECT t1.ID AS lev1, GROUP_CONCAT(CONCAT_WS(',', t2.ID, t3.ID)) AS childs
FROM employees AS t1
LEFT JOIN employees AS t2 ON t2.Parent = t1.ID
LEFT JOIN employees AS t3 ON t3.Parent = t2.ID
GROUP BY t1.ID
Corresponding fiddle you can look here.
Approach 2: When we don't know the depth of our hierarchy
We'll create two procedures for that.
store_emp_childs - It will store parent and its child in a temporary table.
get_emp_child - It will create temp table, call store_emp_childs to generate resultset. Select(return) the data from temporary table and remove the temporaray table.
CREATE DEFINER=`root`#`localhost` PROCEDURE `get_emp_child`()
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT ''
BEGIN
CREATE TEMPORARY TABLE `tmp_emp_child` (
`emp_id` INT(11) NOT NULL,
`child_id` INT(11) NOT NULL,
PRIMARY KEY (`emp_id`, `child_id`)
);
CALL store_emp_childs(NULL, '');
SELECT e.ID, GROUP_CONCAT(ec.child_id) AS childs
FROM employees e
LEFT JOIN tmp_emp_child ec ON e.ID = ec.emp_id
GROUP BY e.ID;
DROP TEMPORARY TABLE tmp_emp_child;
END
CREATE DEFINER=`root`#`localhost` PROCEDURE `store_emp_childs`(
IN `int_parent` INT,
IN `old_parents` VARCHAR(100)
)
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT ''
BEGIN
DECLARE finished, current_id INTEGER DEFAULT 0;
DEClARE cur CURSOR FOR SELECT id FROM employees WHERE IFNULL(Parent, 0) = IFNULL(int_parent, 0);
DECLARE CONTINUE HANDLER FOR NOT FOUND SET finished = 1;
OPEN cur;
curID: LOOP
FETCH cur INTO current_id;
IF finished = 1 THEN
LEAVE curID;
END IF;
INSERT INTO tmp_emp_child (emp_id, child_id)
SELECT id, current_id FROM employees WHERE FIND_IN_SET(id, old_parents) OR id = int_parent
ON DUPLICATE KEY UPDATE emp_id = emp_id;
CALL store_emp_childs(current_id, CONCAT(old_parents, ',', current_id));
END LOOP curID;
CLOSE cur;
END
Note:
We are recursively calling our store_emp_childs. It requires max_sp_recursion_depth parameter to be set more than 0. I recommend to make it 250. It will not work if records are more than this recursion depth. Will look further to improve this.
We are creating temporary table, so user should have rights to create that.
I have a table with matches information, and I need to return a row for each goal and each team. So for example:
+--------+-------+-------+-------+-------+
| Match | Team1 | goal1 | goal2 | Team2 |
+--------+-------+-------+-------+-------+
| 1 | Red | 1 | 0 | Blue |
+--------+-------+-------+-------+-------+
| 2 | Green | 2 | 1 | Black |
+--------+-------+-------+-------+-------+
I want to run a function for each row that returns a row for each goal for each team. So my function result would be:
+--------+-------+-------+
| Goal | Match | Team |
+--------+-------+-------+
| 1 | 1 | Red |
+--------+-------+-------+
| 2 | 2 | Green |
+--------+-------+-------+
| 3 | 2 | Green |
+--------+-------+-------+
| 4 | 2 | Black |
+--------+-------+-------+
My ultimate objective is that I need to have one row for each match/team/goal to fill in manually the Scorer and the minute. Since I hace over 40000 matches, copy pasting each row counting the amount of goals is a pain.
I would like to start with a goal table pre populated with as much information as I already have.
Create a table that contains numbers from 1 to the maximum number of possible goals, i.e.
CREATE TABLE numbers (
num INT PRIMARY KEY
);
INSERT INTO numbers VALUES (1), (2), (3), (4), (5), (6), ...
You can then join this table with your original table:
SELECT num AS Goal, `Match`, Team
FROM numbers
JOIN (
SELECT Team1 AS Team, goal1 AS goals, `Match`
FROM matches
UNION
SELECT Team2 AS Team, goal2 AS goals, `Match`
FROM matches
) ON num <= goals
While loop syntax example in MySQL:
delimiter //
CREATE procedure yourdatabase.while_example()
wholeblock:BEGIN
declare str VARCHAR(255) default '';
declare x INT default 0;
SET x = 1;
WHILE x <= 5 DO
SET str = CONCAT(str,x,',');
SET x = x + 1;
END WHILE;
select str;
END//
Which prints:
mysql> call while_example();
+------------+
| str |
+------------+
| 1,2,3,4,5, |
+------------+
FOR loop syntax example in MySQL:
delimiter //
CREATE procedure yourdatabase.for_loop_example()
wholeblock:BEGIN
DECLARE x INT;
DECLARE str VARCHAR(255);
SET x = -5;
SET str = '';
loop_label: LOOP
IF x > 0 THEN
LEAVE loop_label;
END IF;
SET str = CONCAT(str,x,',');
SET x = x + 1;
ITERATE loop_label;
END LOOP;
SELECT str;
END//
Which prints:
mysql> call for_loop_example();
+-------------------+
| str |
+-------------------+
| -5,-4,-3,-2,-1,0, |
+-------------------+
1 row in set (0.00 sec)
Tutorial: http://www.mysqltutorial.org/stored-procedures-loop.aspx
I have the following tables:
UserPrivileges:
+--------+------+------+------+
| UserID | Col1 | Col2 | Col3 |
+--------+------+------+------+
| 1 | 0 | 1 | 1 |
| 2 | 0 | 0 | 1 |
| 3 | 1 | 0 | 0 |
| 4 | 1 | 1 | 0 |
+--------+------+------+------+
Data:
+--------+------+------+------+
| DataID | Col1 | Col2 | Col3 |
+--------+------+------+------+
| 1 | A | B | C |
| 2 | D | E | F |
| 3 | G | H | I |
| 4 | J | K | L |
+--------+------+------+------+
My question at its simplest form doesn't has anything to do with the Data table but I just explain it anyways so that I might be doing it the wrong way.
How would I select the Column names from UserPrivileges based on the value ? So that I can use the result in another query to select only those columns.
Something along these lines:
SELECT (COLUMNS_NAME_QUERY_FROM_UserPrivileges(UserID='#')) WHERE DataID = '#' FROM Data
Or I don't mind a better way to manage user Privileges for specific columns.
The answer depends upon your requirements for the result. Do you require a result with a consistent set of columns, regardless of user privs? If so, you could set the disallowed values to null (or some other special value) using a IF clause, e.g.,
SELECT IF (p.col1 = 0 THEN NULL ELSE d.col1) AS col1,
IF (p.col2 = 0 THEN NULL ELSE d.col2) AS col2,
IF (p.col3 = 0 THEN NULL ELSE d.col3) AS col3
FROM Data d,
UserPrivileges p
WHERE p.userId = '#'
AND d.DataId = '#'
Of course, the "special value" could be a problem, since you need a value that would never appear in the data. If you needed to know that difference between a null because the real value is null vs. null because it is a prohibited column then you can't use null.
Another approach would have you simple include the privilege indicator for each column appear in the result, and let your business logic use that to determine which values are visible to the user.
A very different approach would have the result set to contain only the allowed columns. In this case you'll need to build your sql statement dynamically. I don't know if you are doing this in a stored procedure or in a host language, but the basic idea is something like this:
string sqlCmd = "SELECT "
+ (SELECT (FIELDS_NAME_QUERY(UserID='#')
FROM USER_PRIVILEGES
WHERE userid='#')
+ FROM data d
execute sqlCmd
"execute" meaning whatever you have available to execute a string as a sql command.
more after clarification by OP:
Ok, you need sql function that returns a string that looks like "colname1, colname2, ...". The following resembles what it would look like in sql server. syntax
create function
FIELDS_NAME_QUERY (#userid int)
begin
select col1, col2, col3... INTO #col1priv, #col2priv, #col3priv FROM userPrivileges WHERE UserId = #UserId
declare #result varhcar(60)
set #result = ''
if (#col1priv = 1) #result = 'col1'
if (#col2priv = 1) #result = #result + ' ,col2'
if (#col3priv = 1) #result = #result + ' ,col3'
return #result
end
have not tried it, but something like this should work
SELECT (SHOW COLUMNS FROM table WHERE expr) FROM data WHERE DataID = '#'
Check this post for details - How can I get column names from a table in Oracle?
Let us know how you solve this...
I have an unnormalized table with a column containing a comma separated list that is a foreign key to another table:
+----------+-------------+ +--------------+-------+
| part_id | material | | material_id | name |
+----------+-------------+ +--------------+-------+
| 339 | 1.2mm;1.6mm | | 1 | 1.2mm |
| 970 | 1.6mm | | 2 | 1.6mm |
+----------+-------------+ +--------------+-------+
I want to read this data into a search engine that offers no procedural language.
So is there a way to either make a join on this column or run a query on this data that inserts appropriate entries into a new table?
The resulting data should look like this:
+---------+-------------+
| part_id | material_id |
+---------+-------------+
| 339 | 1 |
| 339 | 2 |
| 970 | 2 |
+---------+-------------+
I could think of a solution if the DBMS supported functions returning a table but MySQL apparently doesn't.
In MySQL this can be achieved as below
SELECT id, length FROM vehicles WHERE id IN ( 117, 148, 126)
+---------------+
| id | length |
+---------------+
| 117 | 25 |
| 126 | 8 |
| 148 | 10 |
+---------------+
SELECT id,vehicle_ids FROM load_plan_configs WHERE load_plan_configs.id =42
+---------------------+
| id | vehicle_ids |
+---------------------+
| 42 | 117, 148, 126 |
+---------------------+
Now to get the length of comma separated vehicle_ids use below query
Output
SELECT length
FROM vehicles, load_plan_configs
WHERE load_plan_configs.id = 42 AND FIND_IN_SET(
vehicles.id, load_plan_configs.vehicle_ids
)
+---------+
| length |
+---------+
| 25 |
| 8 |
| 10 |
+---------+
For more info visit http://amitbrothers.blogspot.in/2014/03/mysql-split-comma-separated-list-into.html
I've answered two similar questions in as many days but not had any responses so I guess people are put off by the use of the cursor but as it should be a one off process I personally dont think that matters.
As you stated MySQL doesnt support table return types yet so you have little option other than to loop the table and parse the material csv string and generate the appropriate rows for part and material.
The following posts may prove of interest:
split keywords for post php mysql
MySQL procedure to load data from staging table to other tables. Need to split up multivalue field in the process
Rgds
MySQL does not have temporary table reuse and functions do not return rows.
I can't find anything in Stack Overflow to convert string of csv integers into rows so I wrote my own in MySQL.
DELIMITER $$
DROP PROCEDURE IF EXISTS str_split $$
CREATE PROCEDURE str_split(IN str VARCHAR(4000),IN delim varchar(1))
begin
DECLARE delimIdx int default 0;
DECLARE charIdx int default 1;
DECLARE rest_str varchar(4000) default '';
DECLARE store_str varchar(4000) default '';
create TEMPORARY table IF NOT EXISTS ids as (select parent_item_id from list_field where 1=0);
truncate table ids;
set #rest_str = str;
set #delimIdx = LOCATE(delim,#rest_str);
set #charIdx = 1;
set #store_str = SUBSTRING(#rest_str,#charIdx,#delimIdx-1);
set #rest_str = SUBSTRING(#rest_str from #delimIdx+1);
if length(trim(#store_str)) = 0 then
set #store_str = #rest_str;
end if;
INSERT INTO ids
SELECT (#store_str + 0);
WHILE #delimIdx <> 0 DO
set #delimIdx = LOCATE(delim,#rest_str);
set #charIdx = 1;
set #store_str = SUBSTRING(#rest_str,#charIdx,#delimIdx-1);
set #rest_str = SUBSTRING(#rest_str from #delimIdx+1);
select #store_str;
if length(trim(#store_str)) = 0 then
set #store_str = #rest_str;
end if;
INSERT INTO ids(parent_item_id)
SELECT (#store_str + 0);
END WHILE;
select parent_item_id from ids;
end$$
DELIMITER ;
call str_split('1,2,10,13,14',',')
You will also need to cast to different types if you are not using ints.
You can also use REGEXP
SET #materialids=SELECT material FROM parttable where part_id=1;
SELECT * FROM material_id WHERE REGEXP CONCAT('^',#materialids,'$');
This will help if you want to get just one part. Not the whole table of course
Let's say I have 3 tables (significant columns only)
Category (catId key, parentCatId)
Category_Hierarchy (catId key, parentTrail, catLevel)
Product (prodId key, catId, createdOn)
There's a reason for having a separate Category_Hierarchy table, because I'm using triggers on Category table that populate it, because MySql triggers work as they do and I can't populate columns on the same table inside triggers if I would like to use auto_increment values. For the sake of this problem this is irrelevant. These two tables are 1:1 anyway.
Category table could be:
+-------+-------------+
| catId | parentCatId |
+-------+-------------+
| 1 | NULL |
| 2 | 1 |
| 3 | 2 |
| 4 | 3 |
| 5 | 3 |
| 6 | 4 |
| ... | ... |
+-------+-------------+
Category_Hierarchy
+-------+-------------+----------+
| catId | parentTrail | catLevel |
+-------+-------------+----------+
| 1 | 1/ | 0 |
| 2 | 1/2/ | 1 |
| 3 | 1/2/3/ | 2 |
| 4 | 1/2/3/4/ | 3 |
| 5 | 1/2/3/5/ | 3 |
| 6 | 1/2/3/4/6/ | 4 |
| ... | ... | ... |
+-------+-------------+----------+
Product
+--------+-------+---------------------+
| prodId | catId | createdOn |
+--------+-------+---------------------+
| 1 | 4 | 2010-02-03 12:09:24 |
| 2 | 4 | 2010-02-03 12:09:29 |
| 3 | 3 | 2010-02-03 12:09:36 |
| 4 | 1 | 2010-02-03 12:09:39 |
| 5 | 3 | 2010-02-03 12:09:50 |
| ... | ... | ... |
+--------+-------+---------------------+
Category_Hierarchy makes it simple to get category subordinate trees like this:
select c.*
from Category c
join Category_Hierarchy h
on (h.catId = c.catId)
where h.parentTrail like '1/2/3/%'
Which would return complete subordinate tree of category 3 (that is below 2, that is below 1 which is root category) including subordinate tree root node. Excluding root node is just one more where condition.
The problem
I would like to write a stored procedure:
create procedure GetLatestProductsFromSubCategories(in catId int)
begin
/* return 10 latest products from each */
/* catId subcategory subordinate tree */
end;
This means if a certain category had 3 direct sub categories (with whatever number of nodes underneath) I would get 30 results (10 from each subordinate tree). If it had 5 sub categories I'd get 50 results.
What would be the best/fastest/most efficient way to do this? If possible I'd like to avoid cursors unless they'd work faster compared to any other solution as well as prepared statements, because this would be one of the most frequent calls to DB.
Edit
Since a picture tells 1000 words I'll try to better explain what I want using an image. Below image shows category tree. Each of these nodes can have an arbitrary number of products related to them. Products are not included in the picture.
So if I'd execute this call:
call GetLatestProductsFromSubCategories(1);
I'd like to effectively get 30 products:
10 latest products from the whole orange subtree
10 latest products from the whole blue subtree and
10 latest products from the whole green subtree
I don't want to get 10 latest products from each node under catId=1 node which would mean 320 products.
Final Solution
This solution has O(n) performance:
CREATE PROCEDURE foo(IN in_catId INT)
BEGIN
DECLARE done BOOLEAN DEFAULT FALSE;
DECLARE first_iteration BOOLEAN DEFAULT TRUE;
DECLARE current VARCHAR(255);
DECLARE categories CURSOR FOR
SELECT parentTrail
FROM category
JOIN category_hierarchy USING (catId)
WHERE parentCatId = in_catId;
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = TRUE;
SET #query := '';
OPEN categories;
category_loop: LOOP
FETCH categories INTO current;
IF `done` THEN LEAVE category_loop; END IF;
IF first_iteration = TRUE THEN
SET first_iteration = FALSE;
ELSE
SET #query = CONCAT(#query, " UNION ALL ");
END IF;
SET #query = CONCAT(#query, "(SELECT product.* FROM product JOIN category_hierarchy USING (catId) WHERE parentTrail LIKE CONCAT('",current,"','%') ORDER BY createdOn DESC LIMIT 10)");
END LOOP category_loop;
CLOSE categories;
IF #query <> '' THEN
PREPARE stmt FROM #query;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END IF;
END
Edit
Due to the latest clarification, this solution was simply edited to simplify the categories cursor query.
Note: Make the VARCHAR on line 5 the appropriate size based on your parentTrail column.