MYSQL group concat selecting multiple rows - mysql

Query:
SELECT `category`.*,
Group_concat(DISTINCT `sub_category`.`english_name` ORDER BY
`sub_category`.`order_number`
ASC) AS `sub_category`,
Group_concat(DISTINCT `sub_category`.`id` ORDER BY
`sub_category`.`order_number`
ASC) AS `sub_category_id`,
Group_concat(`sub_category`.`status` ORDER BY
`sub_category`.`order_number` ASC)
AS `sub_category_status`
FROM `category`
LEFT JOIN `item_category`
ON `item_category`.`category` = `category`.`id`
LEFT JOIN `sub_category`
ON `sub_category`.`category` = `category`.`id`
GROUP BY `category`.`id`
ORDER BY `category`.`order_number` ASC
Problematic line:
Group_concat(sub_category.status ORDER BY
sub_category.order_number ASC)
Result:
(
[ID] => 22
[Create_date] => 2017-11-20
[Created_by] => 0
[English_name] => Pens & refills
[Gujarati_name] => પેન અનેર રીફીલ
[Header] => False
[Sidebar] => False
[Order_number] => 2
[Status] => Close
[sub_category] => Botteled Ink,Fountain Pens,gel ink rollball pens
[sub_category_id] => 54,55,56
[sub_category_status] => Open,Open,Open,Open,Open,Open,Open,Open,Open,Open,Open,Open,Open,Open,Open,Open,Open,Open
)
As you can see I used DISTINCT where I can use. Status either open or close so DISTINCT is not helpful in this column. Can anyone please tell me How can I solve this issue?

You missed DISTINCT For sub_category_status Thats why you are getting duplicate value
Group_concat(`sub_category`.`status` ORDER BY
`sub_category`.`order_number` ASC)
AS `sub_category_status`
ADD DISTINCT for above column also
Group_concat( DISTINCT `sub_category`.`status` ORDER BY
`sub_category`.`order_number` ASC)
AS `sub_category_status`

Related

MYSQL error code: 1054 Unknown column in where clause. Error occurring in Nested SubQueries

I am trying to get through a problem where there are multiple accounts of same scheme on same customer id. On a given txn date I want to retrieve the total Sanctioned Limit and total utilized amount from these accounts. Below is the SQL query I have constructed.
SELECT
cust_id,
tran_date,
rollover_date,
next_rollover,
(
SELECT
acc_num as kcc_ac
FROM
dbzsubvention.acc_disb_amt a
WHERE
(a.tran_date <= AB.tran_date)
AND a.sch_code = 'xxx'
AND a.cust_id = AB.cust_id
ORDER BY
a.tran_date desc
LIMIT
1
) KCC_ACC,
(
SELECT
SUM(kcc_prod)
FROM
(
SELECT
prod_limit as kcc_prod,
acc_num,
s.acc_status
FROM
dbzsubvention.acc_disb_amt a
inner join dbzsubvention.acc_rollover_all_sub_status s using (acc_num)
left join dbzsubvention.acc_close_date c using (acc_num)
WHERE
a.cust_id = AB.cust_id
AND a.tran_date <= AB.tran_date
AND (
ac_close > AB.tran_date || ac_close is null
)
AND a.sch_code = 'xxx'
AND s.acc_status = 'R'
AND s.rollover_date <= AB.tran_date
AND (
AB.tran_date < s.next_rollover || s.next_rollover is null
)
GROUP BY
acc_num
order by
a.tran_date
) t
) kcc_prod,
(
SELECT
sum(disb_amt)
FROM
(
SELECT
disb_amt,
acc_num,
tran_date
FROM
(
SELECT
disb_amt,
a.acc_num,
a.tran_date
FROM
dbzsubvention.acc_disb_amt a
inner join dbzsubvention.acc_rollover_all_sub_status s using (acc_num)
left join dbzsubvention.acc_close_date c using (acc_num)
WHERE
a.tran_date <= AB.tran_date
AND (
c.ac_close > AB.tran_date || c.ac_close is null
)
AND a.sch_code = 'xxx'
AND a.cust_id = AB.cust_id
AND s.acc_status = 'R'
AND s.rollover_date <= AB.tran_date
AND (
AB.tran_date < s.next_rollover || s.next_rollover is null
)
GROUP BY
acc_num,
a.tran_date
order by
a.tran_date desc
) t
GROUP BY
acc_num
) tt
) kcc_disb
FROM
dbzsubvention.acc_disb_amt AB
WHERE
AB.cust_id = 'abcdef'
group by
cust_id,
tran_date
order by
tran_date asc;
This query isn't working. Upon research I have found that correlated subquery works only till 1 level down. However I couldn't get a workaround to this problem.
I have tried searching the solution around this problem but couldn't find the desired one. Using the SUM function at the inner query will not give desired results as
In the second subquery that will sum all the values in column before applying the group by clause.
In third subquery the sorting has to be done first then the grouping and finally the sum.
Therefore I am reaching out to the community for help to suggest a workaround to the issue.
You're correct - external column cannot be transferred through the nesting level immediately.
Try this workaround:
SELECT ... -- outer query
( -- correlated subquery nesting level 1
SELECT ...
( -- correlated subquery nesting level 2
SELECT ...
...
WHERE table0_level1.column0_1 ... -- moved value
)
FROM table1
-- move through nesting level making it a source of current level
CROSS JOIN ( SELECT table0.column0 AS column0_1 ) AS table0_level1
) AS ...,
...
FROM table0
...

How to avoid full table scan in mysql join query

Consider the following query:
SELECT
`banner`.`id`,
`region`.*
FROM
`nms_section_region_banner` AS `section`
JOIN `aw_rbslider_slide_region` AS `region`
ON
FIND_IN_SET(
region.region_id,
section.region_id
) <> 0
JOIN `aw_rbslider_banner` AS `banner`
ON
`section`.`banner_id` = `banner`.`id`
JOIN `aw_rbslider_slide_banner` AS `slide_banner`
ON
`slide_banner`.`banner_id` = `banner`.`id`
JOIN `aw_rbslider_slide` AS `slide`
ON
`slide_banner`.`slide_id` = `slide`.`id` AND `slide`.`status` = 1
JOIN `aw_rbslider_slide_store` AS `store`
ON
`slide`.`id` = `store`.`slide_id`
WHERE
`section`.`section_id` = '414' AND(
`region`.`region_type` = '1' OR FIND_IN_SET('400020', region.region_code) <> 0 OR
FIND_IN_SET(
'PANINDIABEAUTY',
region.region_code
) <> 0 OR FIND_IN_SET(
'PANINDIADIGITAL',
region.region_code
) <> 0 OR FIND_IN_SET('6210', region.region_code) <> 0 OR FIND_IN_SET(
'PANINDIAJEWEL',
region.region_code
) <> 0 OR FIND_IN_SET('MH', region.region_code) <> 0 OR FIND_IN_SET('Mumbai',
region.region_code) <> 0
) AND(
`slide`.`display_from` <= '2021-07-23 02:05:16' OR `slide`.`display_from` IS NULL OR
`slide`.`display_from` = '0000-00-00 00:00:00'
) AND(
`slide`.`display_to` >= '2021-07-23 02:05:16' OR `slide`.`display_to` IS NULL OR
`slide`.`display_to` = '0000-00-00 00:00:00'
) AND(
`store`.`store_id` = '0' OR `store`.`store_id` = '2'
)
GROUP BY
`banner`.`id`
ORDER BY
FIELD(
region.region_type,
3,
2,
5,
4,
1
)
Need to avoid the full table scan.
My query is being like,
Picture1 and picture 2 describes type, keys and possible keys information for the table
Can someone guide me to avoid full table scan on those 6 tables.
First, a little cleanup so I can see and follow the hierarchy of your query and tables. Next, you are using a bunch of FIND_IN_SET() tests against the region code. From what this implies, your region code is a capacity of a long string of multiple values such that a region might be "MH, 400020, PANIDIAJEWEL, ETC", so you are looking for some "keyword" value within the region code. Is this accurate? -- OR -- does the region_code only have a single value. Please confirm.
With your join from section to region, they are both just "ID" keys, dont use Find_In_Set(), instead, direct equality. You can not optimize a join based on a function (hence my change) and MAY be a big issue on your query
For your group by, you originally had banner.id, but since that is already equal to section.banner_id via the join, and the section is the primary table, the index on section table can help optimize that grouping vs secondary table.
SELECT
section.banner_id id,
region.*
FROM
nms_section_region_banner section
JOIN aw_rbslider_slide_region region
ON section.region_id = region.region_id
JOIN aw_rbslider_banner banner
ON section.banner_id = banner.id
JOIN aw_rbslider_slide_banner slide_banner
ON section.banner_id = slide_banner.banner_id
JOIN aw_rbslider_slide slide
ON slide_banner.slide_id = slide.id
AND slide.status = 1
JOIN aw_rbslider_slide_store store
ON slide_banner.slide_id = store.slide_id
-- if IDs are integer, dont wrap in quotes
AND ( store.store_id in ( 0, 2 ) )
WHERE
-- dont use quotes if IDs are actually numbers
section.section_id = 414
AND ( -- unsure if region_type is integer vs string...
region.region_type = '1'
OR FIND_IN_SET( '400020', region.region_code ) <> 0
OR FIND_IN_SET( 'PANINDIABEAUTY', region.region_code ) <> 0
OR FIND_IN_SET( 'PANINDIADIGITAL', region.region_code ) <> 0
OR FIND_IN_SET( '6210', region.region_code) <> 0
OR FIND_IN_SET( 'PANINDIAJEWEL', region.region_code ) <> 0
OR FIND_IN_SET( 'MH', region.region_code) <> 0
OR FIND_IN_SET( 'Mumbai', region.region_code) <> 0 )
AND ( slide.display_from IS NULL
OR slide.display_from = '0000-00-00 00:00:00'
OR slide.display_from <= '2021-07-23 02:05:16' )
AND ( slide.display_to IS NULL
OR slide.display_to = '0000-00-00 00:00:00'
OR slide.display_to >= '2021-07-23 02:05:16' )
GROUP BY
section.banner_id
ORDER BY
FIELD( region.region_type,
3,
2,
5,
4,
1 )
To also help, I am sure indexes already exist on primary keys. But if you have compisite keys for the primary ID and the key to the next
table, that can help. In addition, a covering index to include other fields used within where/group possibilities can help.
I would try to have the following indexes.
Table Index
nms_section_region_banner ( banner_id, region_id) -- and in this specific order
aw_rbslider_slide_region ( region_id, region_type, region_code )
aw_rbslider_slide_banner ( banner_id, slide_id)
aw_rbslider_slide slide ( id, status, display_from, display_to )
aw_rbslider_slide_store ( slide_id, store_id )
Finally, your ORDER BY clause by doing the FIELD() function vs individually naming the field columns vs numbers.
Having explicit field names from the region table is more explicit and readable

List all 'feeds' and only 2 users with relationship

I need to make a list of all the items in the table feed and show only the first 2 users who subscribe to the content, but I can not put together a query that does the list only 2 users limit 2.
I've tried N querys and subquery, but could not get the expected result. The nearest was using group_concat, but if it concatenates all users and does not allow limited only to two initial, and would have to usesubstring_index for this purpose.
Query
select
feed.id
, feed.type
, user.name
from feed
inner join user on user.id = feed.user
group by feed.type
Result
Array(
[0] => Array(
[id] => 1
[type] => Comedy
[name] => Mike
)
[1] => Array(
[id] => 3
[type] => War
[name] => John
)
[2] => Array(
[id] => 6
[type] => Terror
[name] => Sophia
)
)
Expected
Array(
[0] => Array(
[id] => 1
[type] => Comedy
[name] => Mike, Papa
)
[1] => Array(
[id] => 3
[type] => War
[name] => John, Alex
)
[2] => Array(
[id] => 6
[type] => Terror
[name] => Sophia, Jessica
)
)
set #rn=0;
select id, type, name
from
(
select
#rn:=#rn+1 AS r_n
,feed.id
,feed.type
,user.name
from feed
inner join user on user.id = feed.user
group by feed.id
order by feed.id) t
where t.r_n <= 2
You can generate row numbers per group and then select the first 2 rows per feed id.
I don't know exactly the schema of your tables but try the same approach you already tried with group_concat but join to a subquery like:
...
inner join
(
select user.id, user.name from user limit 2
) as x on x.id = feed.user
...
You can use variables to simulate row_number() to give each user per feed a "rank" and only select rows with number <= 2 before doing the grouping in order to only get 2 users per group:
select id, type, group_concat(name) from (
select * from (
select *, #rn := if(#prevFeedId = id, #rn+1, 1) as rn,
#prevFeedId := id
from (
select
feed.id
, feed.type
, user.name
from feed
inner join user on user.id = feed.user
) t1 order by id
) t1 where rn <= 2
) t1 group by id, type

Join on max(T.<column>) including further information of T

I have two tables
create table item( id int )
insert into item ( id ) values ( 1 ), ( 2 ), ( 3 )
create table itemstatus
(
itemid int
, ts datetime
, "status" int
)
insert into itemstatus ( itemid, ts, status ) values
( 1, '2013-12-01T12:00:00.000', 1 ),
( 1, '2013-12-01T11:00:00.000', 2 ),
( 1, '2014-01-01T12:00:00.000', 1 ),
( 2, '2011-01-01T12:00:00.000', 1 )
I'd like to get all items with the last status set, in this case
1, '2014-01-01T12:00:00.000', 1
2, '2011-01-01T12:00:00.000', 1
3, NULL, NULL
What's the most efficient way to solve this?
I tried with a subselect and I get the latest timestamp, but I'm not able to add the status since this field is not included in aggregate-function or group-by. If I add it, the results got grouped by status - logically - but that leads to the fact, that I get too much result-lines and would have to add a further condition / subselect.
You may use the Fiddle-link for created tables and testdata. The second query includes the status-field.
Edit:
adding a further join does the trick, but I doubt that's the way to do it.
select
i.*
, d.*
, s.status
from
item i
left join ( select ts = max(ts), itemid from itemstatus group by itemid ) d
on 1 = 1
and i.id = d.itemid
left join itemstatus s
on 1 = 1
and s.itemid = d.itemid
and s.ts = d.ts
See SQL-fiddle for testing.
You can use row_number partitioned by itemid and ordered by ts desc to get the latest registration in itemstatus per itemid.
select I.id,
S.ts,
S.status
from item as I
left outer join (
select S.status,
S.ts,
S.itemid,
row_number() over(partition by S.itemid
order by S.ts desc) as rn
from itemstatus as S
) as S
on I.id = S.itemid and
S.rn = 1

Query two tables with mysql to count total topics by hour

my forum have two tables to store topics (posts and forums_archive_posts)
$this->DB->build( array(
'select' => "HOUR( FROM_UNIXTIME( post_date ) ) as hour, COUNT(*) AS postCount",
'from' => 'posts',
'where' => "new_topic=0 AND author_id=" . $member['member_id'],
'group' => 'HOUR( FROM_UNIXTIME( post_date ) )',
) );
This query works only with the first table (posts), I need a query that works also in "forum_archive_posts"
tables structure (forum_archive_posts=posts):
archive_author_id = author_id
archive_content_date = post_date
No idea of how you do it in that format but something like
Select postHour, sum(postcount) as postCount From
(
select Hour ... as PostHour
From
Posts...
Union
Select Hour ...
From
ForumArchivePosts ...
) dummyTableName
Group by PostHour
should do the job. the do count bny hoiurs from both tables, then add them together.