MySQL permutation - mysql

I have two tables. One has products and the other has bundles that go with it. I need to figure out the SQL that allows me to find all the combinations in which I can sell the product with extras.
Products
Name ID
Bench 1
Extra
Name ID Parent ID QTY
undershelf 1 1 1
overshelf 2 1 1
wheels 3 1 1
I need and output table that shows all the combination in which I can sell the product:
Bench
Bench + undershelf
Bench + undershelf + overshelf
Bench + overshelf
Bench + wheels
bench + wheels + overshelf and so one.

Every extras can be in the bundle or not, making that a binary property.
A way to visualize the combination is to create a word with a bit for every extra, 1 mean that the extra is in the list, 0 mean the that it is not.
For example Bench + undershelf + overshelf is 110 (or 011 if the binary string is read in the opposite order)
Generating every combination of n bit will give every combination of n extras, it will also give every number from 0 to 2^n - 1.
We can work back from here:
1. generate the list of number from 0 to 2^n - 1;
2. convert the number to binary, to list the combination of extras
3. match every bit with an extra
4. concatenate the names of the extras in the bundle description.
SELECT CONCAT(b.Name
, COALESCE(CONCAT(' + '
, GROUP_CONCAT(x.Name SEPARATOR ' + '))
, '')) Combination
FROM (SELECT p.Name, p.id
, LPAD(BIN(u.N + t.N * 10), e.Dim, '0') bitmap
FROM Products p
CROSS JOIN (SELECT 0 N UNION ALL SELECT 1
UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4
UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7
UNION ALL SELECT 8 UNION ALL SELECT 9) u
CROSS JOIN (SELECT 0 N UNION ALL SELECT 1
UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4
UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7
UNION ALL SELECT 8 UNION ALL SELECT 9) t
INNER JOIN (SELECT COUNT(1) Dim
, `Parent ID` pID
FROM Extra) E ON e.pID = p.ID
WHERE u.N + t.N * 10 < Pow(2, e.Dim)
) B
LEFT JOIN (SELECT #rownum := #rownum + 1 ID
, `Parent ID` pID
, Name
FROM Extra
, (Select #rownum := 0) r) X
ON x.pID = b.ID
AND SUBSTRING(b.bitmap, x.ID, 1) = '1'
GROUP BY b.Name, b.bitmap
this query will work up to six extras, then it'll need another digit table (one digit every three extras).
How it Works
The subquery E count the number of the extras, this is used in C to limit the elements generated by the digit tables u and t (unit and tens) to 2^dim.
The number is converted to binary by BIN(u.N + t.N * 10), then left padded with '0' to the number of elements, generating a combination bitmap.
To use the generated bitmap each extras need a fake id that will match a position in it, that's what the subquery X is meant for.
The two subqueries are JOINed by the nth char of the bitmap: if the char is 1 the extra is in the bundle, LEFT joined to not loose the product without extras.

I cannot think of any ingenious way of doing this in mysql, but it is very easy in a scripting language. Here in PHP:
<?php
$extra = array('undershelf', 'overshelf', 'sheels');
$possible_combinations = pow(2, count($extra));
for ($i = 0; $i < $possible_combinations; $i++) {
$combo = array('Bench');
foreach ($extra as $j => $item) {
if ($i & pow(2, $j)) {
$combo[] = $item;
}
}
echo implode(' + ', $combo) . "\n";
}
prints
Bench
Bench + undershelf
Bench + overshelf
Bench + undershelf + overshelf
Bench + sheels
Bench + undershelf + sheels
Bench + overshelf + sheels
Bench + undershelf + overshelf + sheels

Possible entirely within MySQL, though not simple. This example can handle up to 5 "extras", and is easily extensible for more:
CREATE TABLE products (name varchar(100), id int primary key);
INSERT INTO products (name, id) VALUES ('Bench', 1);
CREATE TABLE extra (name varchar(100), id int primary key, parent_id int references products.id, qty int);
INSERT INTO extra (name, id, parent_id, qty) VALUES
('undershelf', 1, 1, 1), ('overshelf', 2, 1, 1), ('wheels', 3, 1, 1);
CREATE TABLE boolean_values (x boolean);
INSERT INTO boolean_values VALUES (TRUE), (FALSE);
CREATE VIEW product_extras_interim_vw AS
SELECT p.id product_id, p.name product_name, e.id extra_id, e.name extra_name, x
FROM products p
JOIN extra e ON (e.parent_id = p.id)
CROSS JOIN boolean_values;
SELECT DISTINCT a.product_name
, CASE WHEN a.x THEN CONCAT(' + ', a.extra_name) END extra1
, CASE WHEN b.x THEN CONCAT(' + ', b.extra_name) END extra2
, CASE WHEN c.x THEN CONCAT(' + ', c.extra_name) END extra3
, CASE WHEN d.x THEN CONCAT(' + ', d.extra_name) END extra4
, CASE WHEN e.x THEN CONCAT(' + ', e.extra_name) END extra5
FROM product_extras_interim_vw a
LEFT JOIN product_extras_interim_vw b
ON ( a.product_id = b.product_id
AND b.extra_id > a.extra_id
AND a.x )
LEFT JOIN product_extras_interim_vw c
ON ( a.product_id = c.product_id
AND c.extra_id > b.extra_id
AND b.x )
LEFT JOIN product_extras_interim_vw d
ON ( a.product_id = d.product_id
AND d.extra_id > c.extra_id
AND c.x)
LEFT JOIN product_extras_interim_vw e
ON ( a.product_id = e.product_id
AND e.extra_id > d.extra_id
AND d.x)
ORDER BY product_name, extra1, extra2, extra3, extra4, extra5;
Output:
Bench
Bench + overshelf
Bench + overshelf + wheels
Bench + undershelf
Bench + undershelf + overshelf
Bench + undershelf + overshelf + wheels
Bench + undershelf + wheels
Bench + wheels

Related

Combining the result of two queries to one result SQL

I'm trying t create a compare, now i'm only able to make it as multiple result (two different result).
both of the result above is from two queries.
My first result query
SELECT
customercode,
CONVERT(DATE, TransDate) transdate,
SUM(TotalReceivable) AS total
FROM
SalesOrderHeader
WHERE
CustomerCode = 'K-MDMM4'
AND TransDate BETWEEN '2016-07-25' AND '2016-07-30'
GROUP BY
CONVERT(DATE, TransDate), customercode
and my second query
SELECT
b.OutletCode AS outlet,
tanggal,
(cash + cc + dc + flash + piutang + reject + disc50 +
isnull(spesial_item,0)) total
FROM
transaksi a
LEFT JOIN
Outlet b ON a.Outlet = b.OutletCode
LEFT JOIN
area c ON b.areacode = c.areacode
WHERE
b.active = 1
AND b.OutletCode LIKE 'K-MDMM4'
AND flag = 1
AND tanggal BETWEEN '2016-07-25' AND '2016-07-30'
GROUP BY
b.OutletCode, tanggal, cash, cc, dc, flash,
piutang, reject, disc50, spesial_item, ba, mpm, tf,
ul,remarks
ORDER BY
tanggal DESC
I want this result.
customercode | transdate | total_tbl1 | total_tbl2
K-MDMM4 2016-07-25 6004050 6004050
K-MDMM4 2016-07-26 6880340 6880340
K-MDMM4 2016-07-27 5745040 5745040
K-MDMM4 2016-07-28 7424820 7424820
I can't use jsfiddle :(. I don't know why. I can't create table via queries.
From now, I have this query
SELECT
b.OutletCode AS outlet,
tanggal,
(cash + cc + dc + flash + piutang + reject + disc50 +
isnull(spesial_item, 0)) total,
SUM(d.TotalReceivable) AS total
FROM
transaksi a
LEFT JOIN
Outlet b ON a.Outlet = b.OutletCode
LEFT JOIN
area c ON b.areacode = c.areacode
LEFT JOIN
salesorderheader d ON CONVERT(DATE, a.tanggal) = CONVERT(DATE, d.transdate)
WHERE
b.active = 1
AND b.BrandCode LIKE '%%'
AND b.OutletCode LIKE '%%'
AND flag = 1
AND YEAR(tanggal) = '2016'
AND MONTH(tanggal) = '7'
AND outlet = 'K-MDMM4'
GROUP BY
OutletCode, tanggal, cash, cc, dc, flash,
piutang, reject, disc50, spesial_item, transdate, totalreceivable
ORDER BY
tanggal DESC
and the result so far from my desired result....
Combine both queries into a single join and select
SELECT tbl1.customercode,
CAST(tbl1.transdate AS DATE) AS transdate,
tbl1.total AS total_tbl1,
tbl2.total AS total_tbl2
FROM
(
-- Query 1
SELECT customercode,convert(date,TransDate) transdate,SUM(TotalReceivable) as total
FROM SalesOrderHeader
where CustomerCode = 'K-MDMM4'
and TransDate between '2016-07-25' and '2016-07-30'
group by convert(date,TransDate),customercode
) AS tbl1
INNER JOIN (
-- Query 2
select b.OutletCode as outlet,tanggal, (cash + cc + dc + flash + piutang + reject + disc50 +
isnull(spesial_item,0)) total From transaksi a
left join Outlet b on a.Outlet = b.OutletCode
left join area c on b.areacode = c.areacode
where b.active = 1 and b.OutletCode like 'K-MDMM4' and flag = 1 and tanggal
between '2016-07-25' and '2016-07-30'
group by b.OutletCode,tanggal,cash,cc,dc,flash,piutang,reject,disc50,spesial_item,ba,mpm,tf,ul,remarks
) AS tbl2 ON tbl2.outlet = tbl1.customercode AND CAST(tbl2.trnggal AS DATE) = CAST(tbl1.transdate AS DATE)
order by CAST(tbl1.transdate AS DATE) DESC;
I don't have a database installed on this PC but what you're looking for is:
SELECT val1, val2 FROM
(SELECT1_of_your_code AS table1) INNER JOIN
(SELECT2_of_your_code AS table2) ON
table1.x == table2.y

My SQL - Refer data from another table rows

I have no idea whether it is possible or not.
I have two tables:
Columns in hospitals Table
hospitalID
Name
AverageRating
Columns in ratings Table
ID
rating1
rating2
rating3
rating4
rating5
rating6
hospitalID
rating1 to rating6 is types of ratings. Now By calculation I am able to get average rating of particular hospital in ratings table from following SELECT Query
SELECT IFNULL((SUM(charges) + SUM(behaviour) + SUM(admission) + SUM(properInformation)
+ SUM(hygine) + SUM(treatment))/(count(hospitalID) * 6), 0
) AverageRating,COUNT(ID) RatingCount
FROM ratings
WHERE hospitalID = '111111'
Above query works for me perfectly but this averageRating I also want to calculate in my hospitals table because I want to ranks hospitals.
Is there any functions in mySQL through which I can calculate average rating to hospitals table by referring ratings table.
Rating Table
Query Output Table
Based on the given table structure, you can try this.
EDIT
SELECT avgratings.*, #curRow := #curRow + 1 AS hospitalRank
FROM (
SELECT (SUM(r.`rating1`)+SUM(r.`rating2`)+SUM(r.`rating3`)+SUM(r.`rating4`)+SUM(r.`rating5`)+SUM(r.`rating6`))/(COUNT(r.`hospitalID`)*6) AS AverageRating, h.hospitalID
FROM hospitals h INNER JOIN ratings r
ON h.`hospitalID`=r.`hospitalID`
WHERE 1 GROUP BY r.`hospitalID`
) avgratings JOIN (SELECT #curRow := 0) rank
ORDER BY avgratings.AverageRating DESC
2nd Query to get rank of a particular hospital.
SELECT tablea.*
FROM (
SELECT avgratings.*, #curRow := #curRow + 1 AS hospitalRank
FROM (
SELECT (SUM(r.`rating1`)+SUM(r.`rating2`)+SUM(r.`rating3`)+SUM(r.`rating4`)+SUM(r.`rating5`)+SUM(r.`rating6`))/(COUNT(r.`hospitalID`)*6) AS AverageRating, h.hospitalID
FROM hospitals h INNER JOIN ratings r
ON h.`hospitalID`=r.`hospitalID`
WHERE 1 GROUP BY r.`hospitalID`
) avgratings JOIN (SELECT #curRow := 0) rank
) tablea
WHERE tablea.hospitalID=1 ORDER BY tablea.AverageRating DESC
Replace 1 in WHERE block with hospitalID.
I am not sure I understand your question...
You get the avarage of a single record with:
charges + behaviour + admission + properInformation + hygine + treatment / 6
You get the total avarage hence with:
avg(charges + behaviour + admission + properInformation + hygine + treatment / 6)
I order to get it per hospital, you'd group by hospital_id:
select
hospitalid,
avg(charges + behaviour + admission + properinformation + hygine + treatment / 6) as avr,
count(*) as rating_count
from ratings
group by hospitalid
order by 2 desc;
You can select the hospital data (e.g. the hospital name) along by joining the hospital table. E.g.
select
h.hospitalid,
h.name,
avg(r.charges + r.behaviour + r.admission +
r.properinformation + r.hygine + r.treatment / 6) as average_rating,
count(*) as rating_count
from hospitals h
left join ratings r on r.hospitalid = h.hospitalid
group by h.hospitalid
order by average_rating desc;

Split values then resolve the values to a name

I need to be able to do something with my column (below) that can contain multiple values. The 'HearAboutEvent' column has multiple values separated by a comma. Each one of these values corresponds to an entry in another table. So the value of 11273 will equal facebook, 11274 will mean radio, and 11275 will mean commercial.
The data I am working with looks like this:
weather ID MemberID SubscriptionID DateEntered ParticipatedBefore ParticipatedBeforeCities WeatherDependent NonRefundable TShirtSize HearAboutEvent
Yes 24 18 1 2013-12-19 0 NULL 10950 10952 10957 11273, 11274, 11275
I am able to do the proper join to resolve the value of 'weather', note it is the first column and the 8th column.
This is the query I have created so far to resolve the values of WeatherDependent:
SELECT CFS1.Name as 'weather', *
FROM FSM_CustomForm_693 t
LEFT JOIN FSM_CustomFormSelectOptions CFS1 ON CFS1.ID = t.WeatherDependent
where t.ID = 24
Ultimately I need to have the data look like this:
weather ID MemberID SubscriptionID DateEntered ParticipatedBefore ParticipatedBeforeCities WeatherDependent NonRefundable TShirtSize HearAboutEvent
Yes 24 18 1 2013-12-19 0 NULL 10950 10952 10957 Facebook, radio, commercial
Things I think you could use to accomplish this are:
A Split TVF FUNCTION - http://msdn.microsoft.com/en-us/library/ms186755.aspx
CROSS APPLY - http://technet.microsoft.com/en-us/library/ms175156.aspx
STUFF & FOR XML PATH - http://msdn.microsoft.com/en-us/library/ms188043.aspx & http://msdn.microsoft.com/en-us/library/ms190922.aspx
Going one step further, you need something like this:
Excuse my profuse use of sub queries.
CREATE FUNCTION dbo.Split (#sep char(1), #s varchar(512))
RETURNS table
AS
RETURN (
WITH Pieces(pn, start, stop) AS (
SELECT 1, 1, CHARINDEX(#sep, #s)
UNION ALL
SELECT pn + 1, stop + 1, CHARINDEX(#sep, #s, stop + 1)
FROM Pieces
WHERE stop > 0
)
SELECT pn,
SUBSTRING(#s, start, CASE WHEN stop > 0 THEN stop-start ELSE 512 END) AS s
FROM Pieces
)
GO
SELECT
O.A,O.B,O.C,O.D,O.E,O.F,O.G,O.H,O.I,O.J,O.Stuffed
FROM (
SELECT
*
,STUFF((
SELECT ', ' + Name
FROM (
SELECT
V.*
,Y.Name
FROM (
SELECT
'Yes' AS A
,24 AS B
,18 AS C
,1 AS D
,'2013-12-19' AS E
,0 AS F
,NULL AS G
,10950 AS H
,10952 AS I
,10957 AS J
,'11273, 11274, 11275' AS K
)
AS V
CROSS APPLY dbo.Split(',',REPLACE(K,' ','')) AS P
JOIN (
SELECT 11273 AS Id , 'Facebook' AS Name UNION ALL
SELECT 11274 AS Id , 'radio' AS Name UNION ALL
SELECT 11275 AS Id , 'commercial' AS Name
)Y ON y.Id = p.s) ExampleTable
FOR XML PATH('')
), 1, 1, '' )
AS [Stuffed]
FROM (
SELECT
V.*
FROM (
SELECT
'Yes' AS A
,24 AS B
,18 AS C
,1 AS D
,'2013-12-19' AS E
,0 AS F
,NULL AS G
,10950 AS H
,10952 AS I
,10957 AS J
,'11273, 11274, 11275' AS K
)
AS V
CROSS APPLY dbo.Split(',',REPLACE(K,' ','')) AS P
JOIN (
SELECT 11273 AS Id , 'Facebook' AS Name UNION ALL
SELECT 11274 AS Id , 'radio' AS Name UNION ALL
SELECT 11275 AS Id , 'commercial' AS Name
)Y ON y.Id = p.s
)Z
) O
GROUP BY O.A,O.B,O.C,O.D,O.E,O.F,O.G,O.H,O.I,O.J,O.K,O.Stuffed

Can I do a mysql command to filter and delete duplicated entry

I have table as linkage with below values
++++++++++++++++++++++++++
+ company_id + industry +
++++++++++++++++++++++++++
+ 1 + a +
+ 1 + b +
+ 2 + a +
+ 2 + c +
+ 3 + a +
+ 4 + c +
+ 5 + a +
++++++++++++++++++++++++++
Is there a way that i can group my industry to get the top count sort by desc order example.
a = count 4
c = count 2
b = count 1
then delete duplicated industry leaving only the industry that has the higher count for each company_id.
Edit 1
This edit is based on OP comment I wish to only have the industry with the highest count, and deleting the rest of the entry for the same company_id. say for company_id 1, we will delete the second row, for company_id 2 we will delete the forth row.
Below is what I have.
++++++++++++++++++++++++++
+ company_id + industry +
++++++++++++++++++++++++++
+ 1 + a +
+ 1 + b +
+ 1 + c +
+ 2 + a +
+ 2 + c +
+ 3 + a +
+ 4 + c +
+ 5 + a +
++++++++++++++++++++++++++
as we see in column industry, a has max count, I would like to keep this entry per duplicated company_id and remove rest all enteries.
Consider company_id=1. I would need to remove second and third row.
Consider company_id=2. I would need to remove fifth row.
For id=3,4,5 nothing will happen as those are not duplicated.
So final data that should be there in my table is
++++++++++++++++++++++++++
+ company_id + industry +
++++++++++++++++++++++++++
+ 1 + a +
+ 2 + a +
+ 3 + a +
+ 4 + c +
+ 5 + a +
++++++++++++++++++++++++++
select t6.company_id,t6.industry from
(select t5.company_id,t5.industry,
row_number() over (partition by t5.company_id order by t5.company_id) rn
from
(select t3.company_id,t4.industry from
(select t2.company_id,max(t2.count) count from(
select m.company_id,m.industry,t1.count from linkage m
join
(select n.industry,count(n.industry) count from linkage n
group by n.industry
order by count desc)t1
on m.industry = t1.industry
order by m.company_id)t2
group by t2.company_id
order by t2.company_id)t3
join
(
select m.company_id,m.industry,t1.count from linkage m
join
(select n.industry,count(n.industry) count from linkage n
group by n.industry
order by count desc)t1
on m.industry = t1.industry
order by m.company_id)t4
on t3.company_id = t4.company_id
and t3.count = t4.count)t5
)t6
where t6.rn = '1'
How about this?
SELECT industry, count(industry) as "total"
FROM linkage
GROUP BY industry
ORDER BY total DESC
Demo at sqlfiddle
Edit 1
Can you take at look at below question.
how can I delete duplicate records from my database
I think that is what you are looking for.
select n.industry,count(n.industry) count from linkage n
group by n.industry
order by count desc
select t3.company_id,t4.industry from
(select t2.company_id,max(t2.count) count from(
select m.company_id,m.industry,t1.count from linkage m
join
(select n.industry,count(n.industry) count from linkage n
group by n.industry
order by count desc)t1
on m.industry = t1.industry
order by m.company_id)t2
group by t2.company_id
order by t2.company_id)t3
join
(
select m.company_id,m.industry,t1.count from linkage m
join
(select n.industry,count(n.industry) count from linkage n
group by n.industry
order by count desc)t1
on m.industry = t1.industry
order by m.company_id)t4
on t3.company_id = t4.company_id
and t3.count = t4.count
Demo at sqlfiddle

SQL statement to insert repetitive data

current situation is to add below value of A01, B03, Z11 and X21 in repetitive way in field code for 400 hundreds row of data in table BabyCode.
Above is current table - without value in 'Code" column
Above is to be updated table - repetitive value is added in 'Code' column
You can do this:
INSERT INTO BabyCode
SELECT Codes.Code
FROM
(
SELECT id
FROM
(
SELECT t3.digit * 100 + t2.digit * 10 + t1.digit + 1 AS id
FROM TEMP AS t1
CROSS JOIN TEMP AS t2
CROSS JOIN TEMP AS t3
) t
WHERE id <= 400
) t,
(
SELECT 1 AS ID, 'A01' AS Code
UNION ALL
SELECT 2, 'B03'
UNION ALL
SELECT 3, 'Z11'
UNION ALL
SELECT 4, 'X21'
) codes;
But you will need to define a temp table, to use as an anchor table:
CREATE TABLE TEMP (Digit int);
INSERT INTO Temp VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
SQL Fiddle Demo
This will insert 400 hundred rows of the values A01, B03, Z11, and X21, into the code column in the table BabyCode.
You could put the four values into a virtual table identical to that used in #Mahmoud Gamal's answer, and, if the ID values in your table start at 1 and are sequential (have neither gaps nor duplicates), you could use the following method to join to the virtual table and update the target's Code column:
UPDATE YourTable t
INNER JOIN (
SELECT 1 AS ID, 'A01' AS Code
UNION ALL SELECT 2, 'B03'
UNION ALL SELECT 3, 'Z11'
UNION ALL SELECT 4, 'X21'
) x
ON (t.ID - 1) MOD 4 + 1 = x.ID
SET t.Code = x.Code
;
Otherwise you could use variables to assign 1, 2, 3, 4 sequentially to every row of your table, then you would be able join to the virtual table using those values:
UPDATE YourTable t
INNER JOIN (
SELECT ID, #rnk := CASE WHEN #rnk = 4 THEN 0 ELSE #rnk END + 1 AS rnk
FROM YourTable
CROSS JOIN (SELECT #rnk := 0) x
ORDER BY ID
) r ON t.ID = r.ID
INNER JOIN (
SELECT 1 AS ID, 'A01' AS Code
UNION ALL SELECT 2, 'B03'
UNION ALL SELECT 3, 'Z11'
UNION ALL SELECT 4, 'X21'
) x
ON r.rnk = x.ID
SET t.Code = x.Code
;
Both queries can be played with at SQL Fiddle:
Method 1
Method 2