CONCAT within GROUP_CONCAT - How to remove duplicate result - mysql

CONCAT within GROUP_CONCAT, what is wrong with following mysql code ? Please see SQL Fiddle, full code is there.
Let me explain, I have 5 table
cls - List of Classes
sec - List of Sections
fee - List of Fee
cls_sec - List of section assigned to each class
cls_fee - List of fee assigned to each section
Table cls - lists of Class
id | ttl
===========
1 | One
2 | Two
3 | Three
Table sec - lists of section
id | ttl
===========
1 | A
2 | B
Table cls_sec - lists of each section assigned to Class
id | c_id| s_id
=====================
1 | 1 | 1
2 | 1 | 2
3 | 2 | 1
Table fee - lists of fee category
id | ttl
===========
1 | Annual
2 | Monthly
3 | Library
Table cls_fee - lists of each fee and amount assigned to Class
id | c_id| s_id| f_id| fee
=====================================
1 | 1 | 1 | 1 | 2000
2 | 1 | 1 | 2 | 500
3 | 1 | 2 | 1 | 3000
4 | 1 | 2 | 2 | 400
5 | 2 | 1 | 1 | 4500
6 | 2 | 1 | 2 | 450
7 | 3 | 0 | 1 | 5000
8 | 3 | 0 | 2 | 600
9 | 3 | 0 | 3 | 300
Here I am trying to include all relation in one GROUP_CONCAT Result
My current output (Class name and section name is fetched repetitively according to fee )
//Class Name - Section Name (if exist) - fee, Class Name - Section Name (if exist) - fee ..
3.Three.Library->300, 3.Three.Monthly->600, 3.Three.Annual->5000,
2.Two-A.Monthly->450, 2.Two-A.Annual->4500, 1.One-A.Monthly->500,
1.One-A.Annual->2000, 1.One-B.Monthly->400, 1.One-B.Annual->3000
with following code
GROUP_CONCAT(DISTINCT CONCAT('\r\n',cls.id,'.',cls.ttl,
COALESCE(CONCAT('-',sec.ttl),''),COALESCE(CONCAT('.',fee.ttl,'->',cls_fee.fee)))
ORDER BY sec.id) AS cls
But what I want (remove duplication class and section)
//Class Name - Section Name (if exists) - fee, fee
3.Three.Library->300,Monthly->600,Annual->5000,
2.Two-A.Monthly->450,Annual->4500,
1.One-A.Monthly->500,Annual->2000,
1.One-B.Monthly->400,Annual->3000
So I add CONCAT within nested CONCAT
GROUP_CONCAT(DISTINCT CONCAT('\r\n',cls.id,'.',cls.ttl,
COALESCE(CONCAT('-',sec.ttl,COALESCE(CONCAT('.',fee.ttl,'->',cls_fee.fee))), ''))
ORDER BY sec.id) AS cls
and got output, but it doesn't fetch as expected, also missing some fee
3.Three,
2.Two-A.Monthly->450, 2.Two-A.Annual->4500,
1.One-A.Monthly->500, 1.One-A.Annual->2000,
1.One-B.Monthly->400, 1.One-B.Annual->3000
MySQL CODE
SELECT
GROUP_CONCAT(DISTINCT CONCAT('\r\n',cls.id,'.',cls.ttl,
COALESCE(CONCAT('-',sec.ttl),''),COALESCE(CONCAT('.',fee.ttl,'->',cls_fee.fee)))
ORDER BY sec.id) AS cls
FROM
cls
LEFT JOIN
cls_sec ON cls_sec.cls = cls.id
LEFT JOIN
sec ON sec.id = cls_sec.sec
LEFT JOIN
cls_fee ON cls_fee.c_id = cls.id
LEFT JOIN
fee ON fee.id = cls_fee.f_id
WHERE
CASE WHEN cls_fee.s_id != 0 THEN cls_fee.s_id = sec.id ELSE cls.id END
SQL Fiddle

You can try to use a subquery to write GROUP_CONCAT from detail by cls.id, cls.ttl then do GROUP_CONCAT again in the main query.
Query 1:
SELECT GROUP_CONCAT(CONCAT(Id,'.',ttl,'.',flag,cls) ORDER BY Id desc,flag) result
FROM (
SELECT
cls.id,
cls.ttl,
COALESCE(CONCAT('-',sec.ttl),'') flag,
GROUP_CONCAT(DISTINCT CONCAT(
COALESCE(CONCAT('.',fee.ttl,'->',cls_fee.fee)))
ORDER BY sec.id) AS cls
FROM
cls
LEFT JOIN
cls_sec ON cls_sec.cls = cls.id
LEFT JOIN
sec ON sec.id = cls_sec.sec
LEFT JOIN
cls_fee ON cls_fee.c_id = cls.id
LEFT JOIN
fee ON fee.id = cls_fee.f_id
WHERE
CASE WHEN cls_fee.s_id != 0 THEN cls_fee.s_id = sec.id ELSE cls.id END
GROUP BY
cls.id,
cls.ttl,
COALESCE(CONCAT('-',sec.ttl),'')
)t1
Results:
| result |
|---------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 3.Three..Library->300,.Monthly->600,.Annual->5000,2.Two.-A.Monthly->450,.Annual->4500,1.One.-A.Monthly->500,.Annual->2000,1.One.-B.Monthly->400,.Annual->3000 |

Related

How to show data displayed even not matchiing WHERE condition?

I am trying to display some data from database using multiple parameters.
t_item
ID_ITEM ITEM PRICE
A Book 1000
B Pencil 2000
C Pen 3000
t_store_master
ID_STORE STORE
ST01 STORE_1
ST02 STORE_2
ST03 STORE_3
t_store_detail
ID_STORE_DETAIL ID_STORE ID_ITEM LAST_STOCK
1 ST01 A 5
2 ST01 B 7
3 ST02 A 9
4 ST02 C 4
5 ST03 C 8
I'd like to display the data even when I give non existing value as parameter. If I use not existing value as parameter, there is no data retrieved. Some example of the output:
STORE ITEM LAST_STOCK TOTAL
STORE_1 Book 5 5000 //Parameter: (ID_STORE = 'ST01' and ID_ITEM = 'A')
STORE_1 Pen 0 0 //Parameter: (ID_STORE = 'ST01' and ID_ITEM = 'C')
My current query:
SELECT
t_store_master.STORE,
t_item.ITEM,
t_store_detail.LAST_STOCK,
(t_store_detail.LAST_STOCK * t_item.PRICE) AS 'TOTAL'
FROM t_store_master
INNER JOIN t_store_detail ON t_store_master.ID_STORE = t_store_detail.ID_STORE
INNER JOIN t_item ON t_store_detail.ID_ITEM= t_item.ID_ITEM
WHERE t_store_detail.ID_STORE = '?' AND t_store_detail.ID_ITEM = '?'
Note: - Is it also possible to display data with these parameter?
STORE_6 Book 0 0 //Parameter: (ID_STORE = 'ST06' and ID_ITEM = 'A')
STORE_7 - 0 0 //Parameter: (ID_STORE = 'ST07' and ID_ITEM = 'E')
One way is to create a Derived Table, based on your Input Parameter values. If you have multiple combinations of input params, you can utilize UNION to include them all in a single subquery. Now, you can do a LEFT JOIN to all the table(s) to check if any matching row exists or not.
SELECT
prm.ID_STORE,
sm.STORE,
prm.ID_ITEM,
i.ITEM,
sd.LAST_STOCK,
(sd.LAST_STOCK * i.PRICE) AS TOTAL
FROM
-- Change the values in this query depending on parameter accordingly
(SELECT 'ST07' AS ID_STORE, 'E' AS ID_ITEM
-- If you have multiple parameter combinations, you can extend this using UNION
UNION
SELECT 'ST06', 'A') AS prm
LEFT JOIN t_store_master AS sm
ON sm.ID_STORE = prm.ID_STORE
LEFT JOIN t_item AS i
ON i.ID_ITEM = prm.ID_ITEM
LEFT JOIN t_store_detail AS sd
ON sd.ID_STORE = prm.ID_STORE
AND sd.ID_ITEM = prm.ID_ITEM
Result
| ID_STORE | ID_ITEM | STORE | ITEM | LAST_STOCK | TOTAL |
| -------- | ------- | ----- | ---- | ---------- | ----- |
| ST06 | A | | Book | | |
| ST07 | E | | | | |
View on DB Fiddle

Modify MySQL Query or Run in PHP

I'm trying to run a query to find which inventory I should promote and which campaign I should run so I can move that inventory.
I have three tables:
campaigns lists different campaigns that I can run, each campaign has a unique id. Some campaigns promote only one item and some promote multiple items.
inventory has all the items I have in stock and the quantity of those items.
campaign_to_inventory matches the unique campaign id to the inventory item.
campaigns:
name | id
-------------|---
blue-widgets | 1
gluten-free | 2
gadget | 3
inventory:
item | qty
-------|----
thing1 | 0
thing2 | 325
thing3 | 452
thing5 | 123
thing7 | 5
campaign_to_inventory:
id | item
---|-------
1 | thing1
1 | thing2
1 | thing5
2 | thing1
2 | thing3
3 | thing7
I'd like to run a query to find all the campaigns I could run where I have the needed inventory in stock. I'm currently running this query:
SELECT * FROM `campaigns` LEFT JOIN `campaign_to_inventory` ON `campaigns`.`id` = `campaign_to_inventory`.`id` LEFT JOIN `inventory` ON `campaign_to_inventory`.`item` = `inventory`.`item`
Which returns:
name | id | item | qty
-------------|----|--------|----
blue-widgets | 1 | thing1 | 0
blue-widgets | 1 | thing2 | 325
blue-widgets | 1 | thing5 | 123
gluten-free | 2 | thing1 | 0
gluten-free | 2 | thing3 | 452
gadget | 3 | thing7 | 5
Should I use PHP to process this data to find only campaigns where all item quantities are greater than a minimum threshold, or is there a way to modify the query to limit the rows there? Is there a rule of thumb of when I can/should do it in one and not the other?
There's no need to process the data in PHP.
One way to do this would be to select the campaign_to_inventory.id column where the number of items is less than your threshold, like this:
SET #min_qty = 1;
SELECT `c_to_i`.`id` FROM `campaign_to_inventory` AS `c_to_i`
INNER JOIN `inventory` ON `inventory`.`item` = `c_to_i`.`item`
WHERE `inventory`.`qty` <= #min_qty;
... And then do a left outer join from campaign_to_inventory to that like this:
SET #min_qty = 1;
SELECT `id`, `name` FROM `campaigns`
LEFT JOIN (
/* Table of campaigns which contain items with not enough qty*/
SELECT `c_to_i`.`id` FROM `campaign_to_inventory` AS `c_to_i`
INNER JOIN `inventory` ON `inventory`.`item` = `c_to_i`.`item`
WHERE `inventory`.`qty` <= #min_qty
) AS `campaigns_with_not_enough_items`
ON `campaigns`.`id` = `campaigns_with_not_enough_items`.`id`
WHERE `campaigns_with_not_enough_items`.`id` is NULL;
The result should be a table of campaigns which have the needed inventory in stock.
As an aside, you should rename your campaign_to_inventory.id column to campaign since the name id implies that the column is the primary key for the table.

MySQL Join two tables with condition

Based on these two tables:
products
| ID | Active | Name | No
--------------------------------------------------
| 1 | 1 | Shirt | 100
| 2 | 0 | Pullover | 200
variants
| MasterID | Active | Name | No
--------------------------------------------------
| 1 | 1 | Red | 101
| 1 | 0 | Yellow | 102
I want to get every product which is active and also their active variants in one sql.
Relation between those tables MasterID -> ID
Needed result:
ID (master) | Name | No
--------------------------------------------------
1 | Shirt | 100
1 | Red | 101
I tried it with using union, but then I am not able to get the belonging MasterIDs.
It looks like you just need a simple join:
select *
from products
left join variants
on products.ID = variants.MasterID
where products.Active = 1
and variants.Active = 1
Update after requirements were made clearer:
select ID, Name, No, 'products' as RowType
from products
where Active = 1
union
select variants.MasterID as ID, variants.Name, variants.No, 'variants' as RowType
from products
join variants
on products.ID = variants.MasterID
where products.Active = 1
and variants.Active = 1
order by ID, RowType, No
I've assumed you want the results ordered by ID, with products followed by variants. The No column may order it this way implicitly (it's impossible to know without real data), in which case the RowType column can be removed. The order by clause might need to be altered to match your specific RDBMS.
This should gives you the expected result:
select * from products left join variants on products.id = variants.masterId
where products.active=1 and variants.active=1
If not please add the expected result to your question.

Selecting row with minimal value in one column, in a join query

I have two tables: tblBusinesses, tblBusinessImages that are matched on tblBusinesses.fldID = tblBusinessImages.fldBusinessID like this:
tblBusinesses
=============
fldID | fldName | fldTitle | fldBody
-------------------------------------
1 | b1 | title1 | body1
2 | b2 | title2 | body2
3 | b3 | title3 | body3
4 | b4 | title4 | body4
tblBusinessImages
=============
fldID | fldFileName | fldTitle | fldBusinessID | fldOrder
-----------------------------------------------------------
1 | img1.jpg | img1title | 1 | 3
2 | img2.jpg | img2title | 1 | 1
3 | img3.jpg | img3title | 1 | 2
I want to write a query that gets for every business in tblBusinesses, the image in tblBusinessImages with minimal fldOrder. In other words, in the example I wrote above I want to get business b1, title1, body1 along with img2.jpg from tblBusinessImages (because it has minimal fldOrder in tblBusinessImages ).
Any help would be appreciated!
If fldOrder alwyas starts at one for each fldBusinessID the query becomes
SELECT b.fldName, b.fldTitle, b.fldBody, i.fldFileName
FROM
tblBusinesses b
LEFT JOIN tblBusinessImages i
ON (b.fldID = i.fldBusinessID AND i.fldOrder = 1)
If not, you will have to use subqueries.
SELECT b.fldName, b.fldTitle, b.fldBody, firstImage.fldFileName
FROM
tblBusinesses b
LEFT JOIN (
SELECT i.fldBusinessID, i.fldFileName
FROM
tblBusinessImages i
WHERE
i.fldOrder = (SELECT MIN(x.fldOrder)
FROM tblBusinessImages x
WHERE x.fldBusinessID = i.fldBusinessID)
) firstImage
ON b.fldID = firstImage.fldBusinessID
Note that the left join makes the query return also businesses having no image at all.
If you need only entries having images where the smallest order id can have any value:
SELECT b.fldName, b.fldTitle, b.fldBody, i.fldFileName
FROM
tblBusinesses b
INNER JOIN tblBusinessImages i
ON b.fldID = i.fldBusinessID
WHERE
i.fldOrder = (SELECT MIN(x.fldOrder)
FROM tblBusinessImages x
WHERE x.fldBusinessID = i.fldBusinessID)
Do you only want to get the ONE entry with the smallest fldOrder?
Didn't test this query:
SELECT b.fldName,b.fldTitle,b.fldBody,bi.fldFileName,bi.fldOrder
FROM tblBuisnesses b
INNER JOIN tblBuisnessImages bi ON bi.fldBusinessID=b.fldID ORDER BY b.fldOrder DESC LIMIT 1;

Select all if no subset is present, otherwise select subset

Ok here's my problem. Assume a customer has access to a number of regions defined in a CustomerRegions table:
CustomerRegionID | CustomerID | RegionID
----------------------------------------
1 | 1 | 1
2 | 1 | 2
Assume that customer 1 has three users 1, 2, and 3. For each user we can specify to which of the CustomerRegions they have access via a table UserRegions:
UserRegionID | UserID | CustomerRegionID
----------------------------------------
1 | 1 | 1
2 | 1 | 2
3 | 2 | 2
So user 1 will have access to both Customerregions and user 2 will only have access to CustomerRegion 2.
If there are UserRegions specified for a given user then only those CustomerRegions are present in the result set, but if no UserRegions are specified for a given user then all CustomerRegions are present in the result. I want to get all accessible regions per user of a given customer. The result I am looking for is something like this:
CustomerID | UserID | RegionID
------------------------------
1 | 1 | 1
1 | 1 | 2
1 | 2 | 2
1 | 3 | 1
1 | 3 | 2
My question is can this be done in a single query and how?
Edit:
I seem to have it working now:
SELECT CustomerID,
UserID,
RegionID
FROM users
LEFT JOIN customerregions ON customerregions.CustomerID = users.CustomerID
LEFT JOIN userregions ON userregions.UserID = users.UserID AND userregions.CustomerRegionID = customerregions.CustomerRegionID
LEFT JOIN regions ON regions.RegionID = customerregions.RegionID
WHERE (userregions.UserID IS NOT NULL
OR (SELECT COUNT(1) FROM userregions WHERE userregions.UserID = users.UserID) = 0)
AND CustomerID = 1
The extra count query in the where seems to do the trick. Thanks #Pablo Martinez for your help. However if someone knows of a better way to do this please let me know.
I'm aggre with #diEcho, the table structure is very confusing
have you try to do a join?
Select CustomerID, UserID, RegionID
from UserRegions join CustomerRegion
on CustomerRegion.CustomerRegionID=UserRegions.CustomerRegionID
where customerID=1