path enumeration using prime numbers(insert child) - mysql

I need a little help,
I have a table tree which has following data:
id | person | prime | product
----+--------+-------+---------
1 x 2 2
2 z 3 6
4 d 5 30
How this works is
Prime and products are used to calculate the parent child relationship of my family.
Prime = next available prime number
Product = (prime * product of parent). Each product of primes can only be divided by those primes.
for person d parent is z since 5(prime of d) * 6 (productof z) = 30(product of d)
Using the same principle I have to write a singe query to insert child of d (say a,b,c) to the table.
Provided there is another table prime_numbers with column primes which holds the list of prime numbers (2,3,5,7,11,13,17,....)
I had consulted This.
But was unable to derive the solution from it.

I don't have mySql installed so I can't check this, but maybe it can be a start.
You can use an Insert-select based on a join of some subqueries:
a subquery for getting a list of next prime numbers
from your_table, get the father (id=4)
join of 1 + 2 for calculating next pruducts and adding row number
a subquery for the new persons (I unioned constants) with row number
join of 3 + 4 according to row number
So it may look like:
SELECT prsns.idd, prsns.namee, t1.nxt, t1.product
FROM
(SELECT #rownum:=#rownum+1 'rn', p.nxt prime, p.nxt * tt.product product
(SELECT cur, max(nxt) as nxt
FROM
( select t.prime as cur, nxtt.prime as nxt
from prime_numbers t, prime_numbers nxtt
where t.prime > nxtt.prime )
GROUP BY cur
ORDER BY nxt) p, your_table tt, (SELECT #rownum:=0) r
WHERE tt.id = 4
AND p.nxt > tt.prime) t1,
(
SELECT #rownum:=#rownum+1 'rn', idd, namee
FROM (SELECT #rownum:=0) r, (
select 5 as idd, 'a' as namee union
select 6 , 'b' union
select 7 , 'c' union
)
) prsns
WHERE prsns.rn = t1.rn

Related

Random elements inside JOIN

I have this code here
INSERT INTO Directory.CatalogTaxonomy (`CatalogId`, `TaxonomyId`, `TaxonomyTypeId`, `IsApprovalRelevant`)
SELECT cat.CatalogId, dep.Id, #department_type, false
FROM Directory.Catalog cat
JOIN (SELECT * FROM (
SELECT * FROM Taxonomy.Department LIMIT 10
) as dep_tmp ORDER BY RAND() LIMIT 3) AS dep
WHERE cat.CatalogId NOT IN (SELECT CatalogId FROM Directory.CatalogTaxonomy WHERE TaxonomyTypeId = #department_type)
AND cat.UrlStatus = #url_status_green
AND (cat.StatusId = #status_published
OR cat.StatusId = #status_review_required);
And the problem is that, it should for each catalog take the first 10 elements from Department and randomly choose 3 of them, then add to CatalogDepartment 3 rows, each containing the catalog id and a taxonomy id. But instead it randomly chooses 3 Department elements and then adds those 3 elements to each catalog.
The current result looks like this:
1 000de9d7-af8b-4bac-bdbd-e6e361e5bc5e
1 001d4060-2924-4c75-b304-d780454f261b
1 001bc4b8-c1bc-498d-9aee-3825a40587d5
2 000de9d7-af8b-4bac-bdbd-e6e361e5bc5e
2 001d4060-2924-4c75-b304-d780454f261b
2 001bc4b8-c1bc-498d-9aee-3825a40587d5
3 000de9d7-af8b-4bac-bdbd-e6e361e5bc5e
3 001d4060-2924-4c75-b304-d780454f261b
3 001bc4b8-c1bc-498d-9aee-3825a40587d5
As you can see, there are only 3 departments chosen and repeated for every catalog
If you think that the query:
SELECT * FROM (
SELECT * FROM Taxonomy.Department LIMIT 10
) as dep_tmp
ORDER BY RAND() LIMIT 3
that you join to Directory.Catalog returns 3 different departments for each catalog then you are wrong.
This query is executed only once and returns 3 random departments which are joined (always the same 3) to Directory.Catalog.
What you can do is after you CROSS JOIN 10 departments to Directory.Catalog, choose randomly 3 of them for each catalog.
Try this:
INSERT INTO Directory.CatalogTaxonomy (`CatalogId`, `TaxonomyId`, `TaxonomyTypeId`, `IsApprovalRelevant`)
WITH cte AS (
SELECT cat.CatalogId, dep.Id AS TaxonomyId, #department_type AS TaxonomyTypeId, false AS IsApprovalRelevant
FROM Directory.Catalog AS cat
CROSS JOIN (SELECT * FROM Taxonomy.Department LIMIT 10) AS dep
WHERE cat.CatalogId NOT IN (SELECT CatalogId FROM Directory.CatalogTaxonomy WHERE TaxonomyTypeId = department_type)
AND cat.UrlStatus = #url_status_green
AND (cat.StatusId = #status_published OR cat.StatusId = #status_review_required);
)
SELECT t.CatalogId, t.TaxonomyId, t.TaxonomyTypeId, t.IsApprovalRelevant
FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY CatalogId ORDER BY RAND()) rn
FROM cte
) t
WHERE t.rn <= 3
Note that this:
SELECT * FROM Taxonomy.Department LIMIT 10
does not guarantee that you get the first 10 elements from Department because a table is not ordered.

SQL - Split a column into two columns in Mysql

I have this table. Considering the id starts from 0.
Table 1
ID Letter
1 A
2 B
3 C
4 D
6 E
I need following output
Col1 Col2
NULL A
B C
D NULL
E NULL
I tried using union with id, id - 1 and id + 1, but I couldn't figure out how to get letter based on ids, also tried even odd logic but nothing worked.
Any help is appreciated.
Thank you
You didn't post the database engine, so I'll assume PostgreSQL where the modulus operand is %.
The query should be:
select o.letter, e.letter
from (
select id, letter, id as base from my_table where id % 2 = 0
) o full outer join (
select id, letter, (id - 1) as base from my_table where id % 2 <> 0
) e on e.base = o.base
order by coalesce(o.base, e.base)
Please take the following option with a grain of salt since I don't have a way of testing it in MySQL 5.6. In the absence of a full outer join, you can perform two outer joins, and then you can union them, as in:
select * from (
select o.base, o.letter, e.letter
from (
select id, letter, id as base from my_table where id % 2 = 0
) o left join (
select id, letter, (id - 1) as base from my_table where id % 2 <> 0
) e on e.base = o.base
union
select e.base, o.letter, e.letter
from (
select id, letter, id as base from my_table where id % 2 = 0
) o right join (
select id, letter, (id - 1) as base from my_table where id % 2 <> 0
) e on e.base = o.base
) x
order by base
Just use conditional aggregation:
select max(case when id % 2 = 0 then letter end) as col1,
max(case when id % 2 = 1 then letter end) as col2
from t
group by floor(id / 2);
If you prefer, you can use mod() instead of %. MySQL supports both.

MySQL - Finding how much duplicates are inside the same table given

Considering I have the following two sets of rows (same type) in a WHERE clause:
A B
1 1
2 2
3 4
I need to find how many A is in B
For example, for the given table above, it would be 66% since 2 out of 3 numbers are in B
Another example:
A B
1 1
2 2
3 4
5
3
Would give 100% since all of the numbers in A are in B
Here is what I tried myself: (Doesn't work on all test cases..)
DROP PROCEDURE IF EXISTS getProductsByDate;
DELIMITER //
CREATE PROCEDURE getProductsByDate (IN d_given date)
BEGIN
SELECT
Product,
COUNT(*) AS 'total Number',
(SELECT
(SELECT COUNT(DISTINCT Part) FROM products WHERE Product=B.Product) - COUNT(*)
FROM
products AS b2
WHERE
b2.SOP < B.SOP AND b2.Part != B.Part) AS 'New Parts',
CONCAT(round((SELECT
(SELECT COUNT(DISTINCT Part) FROM products WHERE Product=B.Product) - COUNT(*)
FROM
products AS b2
WHERE
b2.SOP < B.SOP AND b2.Part != B.Part)/count(DISTINCT part)*100, 0), '%') as 'Share New'
FROM
products AS B
WHERE
b.SOP < d_given
GROUP BY Product;
END//
DELIMITER ;
CALL getProductsByDate (date("2018-01-01"));
Thanks.
Naming your tables TA and TB respectively you could try something like this (test made on MSSQL and Mysql at moment)
SELECT ROUND(SUM(PERC) ,4)AS PERC_TOT
FROM (
SELECT DISTINCT TA.ID , 1.00/ (SELECT COUNT(DISTINCT ID) FROM TA) AS PERC
FROM TA
WHERE EXISTS ( SELECT DISTINCT ID FROM TB WHERE TB.ID=TA.ID)
) C;
Output with your first sample data set:
PERC_TOT
0,6667
Output with your second sample data set:
PERC_TOT
1,0000
Update (I made the original for two tables, as I was thinking at solution). This is for one single table (is almost the same than the former query): (I used ID1 for column A and ID2 for column B)
SELECT ROUND(SUM(PERC) ,4)AS PERC_TOT
FROM (
SELECT DISTINCT TA.ID1 , 1.00/ (SELECT COUNT(DISTINCT ID1) FROM TA) AS PERC
FROM TA
WHERE EXISTS ( SELECT DISTINCT ID2 FROM TA AS TB WHERE TB.ID2=TA.ID1)
) C;

MySQL- Query with repetitive fixed id range

I have the following query:
SELECT child.id_catalog_category AS id_category, ancestor.id_catalog_category AS tree
FROM catalog_category AS child
JOIN catalog_category AS ancestor
ON (child.lft BETWEEN ancestor.lft AND ancestor.rgt)
WHERE ancestor.id_catalog_category != 1
ORDER BY id_category ASC, tree ASC
which is a reconstruction of a binary tree for a hierarchical product categories. For one id_category we could have a maximum of 4 "tree" values, as the example shows:
id_category / tree
3 2
3 3
4 2
4 3
4 4
5 2
5 3
5 5
6 2
6 3
6 6
7 2
7 3
7 7
Where the desired results should be :
id / id_category / tree
1 3 2
2 3 3
3 null null
4 null null
1 4 2
2 4 3
3 4 4
4 null null
.....
In words, I want to add a range id from 1 to 4 for each id_category, where if id_category has less than 4 value it should show null values.
Regards
To replicate the set of data you specified, I did this to get a working set:
CREATE TABLE cc (id_category INT, tree INT);
INSERT INTO cc VALUES (3,2),(3,3),(4,2),(4,3),(4,4),(5,2),(5,3),
(5,5),(6,2),(6,3),(6,6),(7,2),(7,3),(7,7);
SELECT cc.* FROM cc ORDER BY 1,2;
SQL Fiddle here: http://sqlfiddle.com/#!2/16249/3
Here's how I would approach the problem. First, I would get a distinct list of id_category values. That's straightforward. (I Could use a DISTINCT keyword rather than GROUP BY, whichever.)
SELECT id_category
FROM cc
GROUP BY id_category
ORDER BY id_category
Then, I would generate four rows from each of those rows. So, I'm going to wrap that previous query as an inline view (enclose it in a set of parenthesis, give it an alias, and reference the whole mess like it was just a tablename. Something like this:
SELECT c.id_category, j_.j
FROM (SELECT 1 AS j UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4) j_
JOIN (
SELECT cc.id_category
FROM cc cc
GROUP BY cc.id_category
ORDER BY cc.id_category
) c
ORDER BY c.id_category, j_.j
I'm using another inline view to return the integers 1 thru 4, and doing a CROSS JOIN to get four rows for each distinct id_category. That basically gets me the outline of the result set I want to return... but I don't have any values (other than NULL) for the tree column.
So now, I need to backup, and start working on another rowset, basically an ordered set from the cc table, but this time, including the value from the tree column. I'm not concerned here with getting exactly four rows, just the rows that have a value in the tree column. Again, very straightforward:
SELECT s.id_category_id, s.tree
FROM cc s
ORDER BY s.id_category, s.tree
But now, I want to assign each of those rows a relative row number, within each id_category. I can do by wrapping that query in a set of parenthesis, giving it an alias, and treating it like it were a table, like this:
SELECT #i := IF(r.id_category = #prev_idcat,#i + 1,1) AS i
, #prev_idcat := r.id_category AS id_category
, r.tree
FROM (SELECT #i := 0, #prev_idcat := NULL) i_
JOIN (
SELECT s.id_category, s.tree
FROM cc s
ORDER BY s.id_category, s.tree
) r
I'm using a MySQL trick with user variables, to assign ascending integer values, starting at 1, for each distinct id_category. The trick here is to have MySQL order the rows for me (in the inline view aliased as r, and "saving" the id_category from the previous row in a user variable, so I can compare it to the next row.
And now we're really getting to the point where having Common Table Expressions available in MySQL would be really, really nice. But since they aren't, we press forward, nesting our inline views.
So I'm going to give each of those "row numbering" queries an alias, and reference them like they were tables; The query is going to be of the form...
SELECT b.*, q.*
FROM ( ) b
LEFT
JOIN ( ) q
ON q.id_category = b.id_category AND q.i = b.j
(We omit the contents of those inline views just to get an overview of what the statement is really doing.)
This is going to start looking ugly, but this is where the magic happens.
I pull the four rows for each id_category from b, and I join that to q, matching on id_category and on "row number". It's a LEFT OUTER join, so I'm going to get all the rows from b, and pick up any "matching" row from q.
SELECT b.id_category, q.tree
FROM (SELECT c.id_category, j_.j
FROM (SELECT 1 AS j UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4
) j_
JOIN (
SELECT cc.id_category
FROM cc cc
GROUP BY cc.id_category
ORDER BY cc.id_category
) c
ORDER BY c.id_category, j_.j
) b
LEFT
JOIN (SELECT #i := IF(r.id_category = #prev_idcat,#i + 1,1) AS i
, #prev_idcat := r.id_category AS id_category
, r.tree
FROM (SELECT #i := 0, #prev_idcat := NULL) i_
JOIN (
SELECT s.id_category, s.tree
FROM cc s
ORDER BY s.id_category, s.tree
) r
) q
ON q.id_category = b.id_category AND q.i = b.j
ORDER BY b.id_category, b.j
The only thing remaining in the specification is the generation of a value for an id column. If I was inserting to a table, I could use an AUTO_INCREMENT column to do it for me. But absent that, the most convenient place for me to generate an id value is in the inline view aliased as b. Just a little tweak, and finally, we have this monstrosity of a query, which returns the specified result set:
SELECT b.k AS id, b.id_category, q.tree
FROM (SELECT #k := #k + 1 AS k
, c.id_category
, j_.j
FROM (SELECT #k := 0) k_
JOIN (SELECT 1 AS j UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4
) j_
JOIN (
SELECT cc.id_category
FROM cc cc
GROUP BY cc.id_category
ORDER BY cc.id_category
) c
ORDER BY c.id_category, j_.j
) b
LEFT
JOIN (SELECT #i := IF(r.id_category = #prev_idcat,#i + 1,1) AS i
, #prev_idcat := r.id_category AS id_category
, r.tree
FROM (SELECT #i := 0, #prev_idcat := NULL) i_
JOIN (
SELECT s.id_category, s.tree
FROM cc s
ORDER BY s.id_category, s.tree
) r
) q
ON q.id_category = b.id_category AND q.i = b.j
ORDER BY b.id_category, b.j
To get this to work with your rowset, you would replace each reference to my cc table with your query wrapped in parenthesis. Or, you could create a table named cc like I have done, and insert the results of your query into it.
Someone may have a simpler SQL statement that reliably produces the same result set. I'd be very interested to learn a simpler way.

Changing a Query with a numbered result set (with gaps,) to return result with no gaps, containing every number.

I have a select statement: select a, b, [...]; which returns the results:
a|b
---------
1|8688798
2|355744
4|457437
7|27834
I want it to return:
a|b
---------
1|8688798
2|355744
3|0
4|457437
5|0
6|0
7|27834
An example query that does not do what I would like, since it does not have the gap numbers:
select
sub.num_of_ratings,
count(sub.rater)
from
(
select
r.rater_id as rater,
count(r.id) as num_of_ratings
from ratings r
group by rater
) as sub
group by num_of_ratings;
Explanation of the query:
If a user rates another user, the rating is listed in the table ratings and the id of the rating user is kept in the field rater_id. Effectively I check for all users who are referred to in ratings and count how many ratings records I find for that user, which is rater / num_of_ratings, and then I use this result to find how many users have rated a given number of times.
At the end I know how many users rated once, how many users rated twice, etc. My problem is that the numbers for count(sub.rater) start fine from 1,2,3,4,5... However, for bigger numbers there are gaps. This is because there might be one user who rated 1028 times - but no user who rated 1027 times.
I don't want to apply stored procedures looping over the result or something like that. Is it possible to fill those gaps in the result without using stored procedures, looping, or creating temporary tables?
If you have a sequence of numbers, then you can do a JOIN with that table and fill in the gaps properly.
You can check out this questions on how to get the sequence:
generate an integer sequence in MySQL
Here is one of the answers posted that might be easily used with the limitation that generates numbers from 1 to 10,000:
SELECT #row := #row + 1 as row FROM
(select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t,
(select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t2,
(select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t3,
(select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t4,
(SELECT #row:=0) t5
Using a sequence of numbers, you can join your result set. For instance, assuming your number list is in a table called numbersList, with column number:
Select number, Count
from
numbersList left outer join
(select
sub.num_of_ratings,
count(sub.rater) as Count
from
(
select
r.rater_id as rater,
count(r.id) as num_of_ratings
from ratings r
group by rater
) as sub
group by num_of_ratings) as num
on num.num_of_ratings=numbersList.number
where numbersList.number<max(num.num_of_ratings)
Your numbers list must be larger than your largest value, obviously, and the restriction will allow it to not have all numbers up to the maximum. (If MySQL does not allow that type of where clause, you can either leave the where clause out to list all numbers up to the maximum, or modify the query in various ways to achieve the same result.)
#mazzucci: the query is too magical and you are not actually explaining the query.
#David: I cannot create a table for that purpose (as stated in the question)
Basically what I need is a select that returns a gap-less list of numbers. Then I can left join on that result set and treat NULL as 0.
What I need is an arbitrary table that keeps more records than the length of the final list. I use the table user for that in the following example:
select #row := #row + 1 as index
from (select #row := -1) r, users u
limit 101;
This query returns a set of the numbers von 0 to 100. Using it as a subquery in a left join finally fills the gap.
users is just a dummy to keep the relational engine going and hence producing the numbers incrementally.
select t1.index as a, ifnull(t2.b, 0) as b
from (
select #row := #row + 1 as index
from (select #row := 0) r, users u
limit 7
) as t1
left join (
select a, b [...]
) as t2
on t1.index = t2.a;
I didn't try this very query live, so have merci with me if there is a little flaw. but technically it works. you get my point.
EDIT:
just used this concept to gain a gapless list of dates to left join measures onto it:
select #date := date_add(#date, interval 1 day) as date
from (select #date := '2010-10-14') d, users u
limit 700
starts from 2010/10/15 and iterates 699 more days.