Custom Group tables - mysql

I have a contract table like this
And I want the result to look like this
What query should I use to retrieve the result like the example? I was trying to use group query but it still doesn't do what I want.

Something like this will return the specified result. It's not clear what columns you want to "match" on, I used the first four columns, because those are identical on the rows you show "grouped" together.
The "trick" is to use a GROUP BY to get the distinct list of rows you want to return. You can the use outer joins to pick out the individual rows, to match to the rows returned from the inline view (aliased as g).
This query assumes that Contract column will be unique within each "group". That is, there won't be two rows, for example, with Contract='I' for ('ENG','SWD','ABCDf','2012-11-06'). (In the sample data, it is unique, but we haven't been given any guarantee that it is unique.)
SELECT g.departemen
, g.section
, g.name
, g.start
, i.`end contract` AS `End Contract I`
, p.`end contract` AS `End Contract P`
, q.`end contract` AS `End Contract II`
FROM ( SELECT t.departemen
, t.section
, t.name
, t.start
FROM mytable t
GROUP
BY t.departemen
, t.section
, t.name
, t.start
) g
LEFT
JOIN mytable i
ON i.departemen = g.departemen
AND i.section = g.section
AND i.name = g.name
AND i.start = g.start
AND i.contract = 'I'
LEFT
JOIN mytable p
ON p.departemen = g.departemen
AND p.section = g.section
AND p.name = g.name
AND p.start = g.start
AND p.contract = 'P'
LEFT
JOIN mytable q
ON q.departemen = g.departemen
AND q.section = g.section
AND q.name = g.name
AND q.start = g.start
AND q.contract = 'II'

Related

mysql Multiple left joins using count

I have been researching this for hours and the best code that I have come up with is this from an example i found on overstack. I have been through several derivations but the following is the only query that returns the correct data, the problem is it takes over 139s (more than 2 minutes) to return only 30 rows of data. Im stuck. (life_p is a 'likes'
SELECT
logos.id,
logos.in_gallery,
logos.active,
logos.pubpriv,
logos.logo_name,
logos.logo_image,
coalesce(cc.Count, 0) as CommentCount,
coalesce(lc.Count, 0) as LikeCount
FROM logos
left outer join(
select comments.logo_id, count( * ) as Count from comments group by comments.logo_id
) cc on cc.logo_id = logos.id
left outer join(
select life_p.logo_id, count( * ) as Count from life_p group by life_p.logo_id
) lc on lc.logo_id = logos.id
WHERE logos.active = '1'
AND logos.pubpriv = '0'
GROUP BY logos.id
ORDER BY logos.in_gallery desc
LIMIT 0, 30
I'm not sure whats wrong. If i do them singularly meaningremove the coalece and one of the joins:
SELECT
logos.id,
logos.in_gallery,
logos.active,
logos.pubpriv,
logos.logo_name,
logos.logo_image,
count( * ) as lc
FROM logos
left join life_p on life_p.logo_id = logos.id
WHERE logos.active = '1'
AND logos.pubpriv = '0'
GROUP BY logos.id
ORDER BY logos.in_gallery desc
LIMIT 0, 30
that runs in less than half a sec ( 2-300 ms )....
Here is a link to the explain: https://logopond.com/img/explain.png
MySQL has a peculiar quirk that allows a group by clause that does not list all non-aggregating columns. This is NOT a good thing and you should always specify ALL non-aggregating columns in the group by clause.
Note, when counting over joined tables it is useful to know that the COUNT() function ignores NULLs, so for a LEFT JOIN where NULLs can occur don't use COUNT(*), instead use a column from within the joined table and only rows from that table will be counted. From these points I would suggest the following query structure.
SELECT
logos.id
, logos.in_gallery
, logos.active
, logos.pubpriv
, logos.logo_name
, logos.logo_image
, COALESCE(COUNT(cc.logo_id), 0) AS CommentCount
, COALESCE(COUNT(lc.logo_id), 0) AS LikeCount
FROM logos
LEFT OUTER JOIN comments cc ON cc.logo_id = logos.id
LEFT OUTER JOIN life_p lc ON lc.logo_id = logos.id
WHERE logos.active = '1'
AND logos.pubpriv = '0'
GROUP BY
logos.id
, logos.in_gallery
, logos.active
, logos.pubpriv
, logos.logo_name
, logos.logo_image
ORDER BY logos.in_gallery DESC
LIMIT 0, 30
If you continue to have performance issues then use a execution plan and consider adding indexes to suit.
You can create some indexes on the joining fields:
ALTER TABLE table ADD INDEX idx__tableName__fieldName (field)
In your case will be something like:
ALTER TABLE cc ADD INDEX idx__cc__logo_id (logo_id);
I dont really like it because ive always read that sub queries are bad and that joins perform better under stress, but in this particular case subquery seems to be the only way to pull the correct data in under half a sec consistently. Thanks for the suggestions everyone.
SELECT
logos.id,
logos.in_gallery,
logos.active,
logos.pubpriv,
logos.logo_name,
logos.logo_image,
(Select COUNT(comments.logo_id) FROM comments
WHERE comments.logo_id = logos.id) AS coms,
(Select COUNT(life_p.logo_id) FROM life_p
WHERE life_p.logo_id = logos.id) AS floats
FROM logos
WHERE logos.active = '1' AND logos.pubpriv = '0'
ORDER BY logos.in_gallery desc
LIMIT ". $start .",". $pageSize ."
Also you can create a mapping tables to speed up your query try:
CREATE TABLE mapping_comments AS
SELECT
comments.logo_id,
count(*) AS Count
FROM
comments
GROUP BY
comments.logo_id
) cc ON cc.logo_id = logos.id
Then change your code
left outer join(
should become
inner join mapping_comments as mp on mp.logo_id =cc.id
Then each time a new comment are added to the cc table you need to update your mapping table OR you can create a stored procedure to do it automatically when your cc table changes

Grouping by multiple columns in SQL

I am trying to bring through the site.Site_Name, for each hive.hiveno and it's max(hiverdg.invdate). Running the code below doesn't work because site.Site_Name is not aggrigated. If I add site.Site_Name to the Group By, the code runs, but the ouput displays the results repeated, once for each site.Site_Name
select site.Site_Name ,hive.hiveno, max(hiverdg.invdate)
from hiverdg
inner join hive
on hiveRdg.hive_Link = hive.hive_Link
inner join Customer
on customer.Customer_Link = hive.Customer_Link
inner join site
on site.Customer_Link = customer.Customer_Link
where
(hiverdg.xtype = 'N'
and customer.CustomerName = 'Cust1')
or
(hiverdg.xtype = 'A'
and customer.CustomerName = 'Cust1')
group by hive.hiveno
The easiest way to do this, with your query, is the substring_index()/group_concat() trick:
select substring_index(group_concat(s.Site_Name order by rdg.invdate desc separator '|'
), '|', 1
) as SiteName,
h.hiveno, max(rdg.invdate)
from hiverdg rdg inner join
hive h
on rdg.hive_Link = h.hive_Link inner join
Customer c
on c.Customer_Link = h.Customer_Link inner join
site s
on s.Customer_Link = c.Customer_Link
where rdg.xtype in ('N', 'A') and c.CustomerName = 'Cust1')
group by h.hiveno;
I also made the following changes to your query:
Introduced table aliases, to make the query easier to write and to read.
Changed the where to use in, simplifying the logic.

Combining an INNER JOIN query with Count of different values

I am trying to link two tables with similar column. I need to find out how many values differ from table1.column1 and table 2.column1:
My current query:
SELECT i10_descr.i10_code, gems_pcsi9.i10_code
FROM i10_descr INNER JOIN gems_pcsi9 ON i10_descr.i10_code = gems_pcsi9.i10_code
ORDER BY i10_descr.i10_code;
I know this query shows the matching codes of each table: I cannot figure out how to COUNT the missing/different codes in the tables.
Also, I have to compute the ratio of codes.
Any help, tips, or direction is much appreciated.
Thanks
You could use an anti-join pattern to get a list of i10_code that exist in one table, but not the other. For example:
SELECT i.i10_code
FROM i10_descr i
LEFT
JOIN gems_pcsi9 g
ON g.i10_code = i.i10_code
WHERE g.i10_code IS NULL
ORDER BY i.i10_code
If you just want a count, you could use COUNT(i.i10_code) and/or COUNT(DISINCT i.i10_code) in the SELECT list and remove the ORDER BY clause.
To get the i10_code in the gems table that aren't in the i10 table, you'd do the same thing but invert the query so that gems is the "driving" table. e.g.
SELECT COUNT(DISTINCT g.i10_code) AS cnt_diff
FROM gems_pcsi9 g
LEFT
JOIN i10_descr i
ON i.i10_code = g.i10_code
WHERE i.i10_code IS NULL
If you want to combine the number of differences, you can combine the two queries by making them inline views:
SELECT d.cnt_diff + e.cnt_diff AS total_diff
FROM (
SELECT COUNT(DISTINCT g.i10_code) AS cnt_diff
FROM gems_pcsi9 g
LEFT
JOIN i10_descr i
ON i.i10_code = g.i10_code
WHERE i.i10_code IS NULL
) d
CROSS
JOIN (
SELECT COUNT(DISTINCT i.i10_code) AS cnt_diff
FROM i10_descr i
LEFT
JOIN gems_pcsi9 g
ON g.i10_code = i.i10_code
WHERE g.i10_code IS NULL
) e
NOTE: the COUNT aggregate will omit NULL values. The query would need to be tweaked if you also wanted to "count" rows that had NULL values for i10_code. You'd use COUNT(DISTINCT ) if you want just a number of distinct values that are different. A COUNT() would give a number of rows. These two results would be different if you had multiple rows with the same i10_code value.
To get a "ratio" of codes, assuming that at this point, the "differences" don't matter, you get a count of codes from each table. The queries to do that could be used inline views:
SELECT d.cnt / e.cnt AS ratio_cnt_g_over_cnt_i
, d.cnt AS cnt_g
, e.cnt AS cnt_i
FROM (
SELECT COUNT(DISTINCT g.i10_code) AS cnt
FROM gems_pcsi9 g
) d
CROSS
JOIN (
SELECT COUNT(DISTINCT i.i10_code) AS cnt
FROM i10_descr i
) e
An alternative method is to use union all with aggregation:
select in_i10descr, in_gems_pcsi9, count(*) as numcodes
from (select code, max(in_i10descr) as in_i10descr, max(in_gems_pcsi9) as in_gems_pcsi9
from ((select i10_descr.i10_code as code, 1 as in_i10descr, 0 as in_gems_pcsi9
from i10_descr
) union all
(select gems_pcsi9.i10_code, 0, 1
gems_pcsi9.i10_code
)
) t
group by code
) c
group by in_i10descr, in_gems_pcsi9;
This will calculate counts of things in each table separately and in both tables.

MySQL HAVING not behaving properly

I have a query:
SELECT I.Id
, CAST(SUBSTRING_INDEX(GROUP_CONCAT(I.StatusId ORDER BY I.TransactionId DESC), ',', 1) AS UNSIGNED) AS StatusId
, SUM(I.RefundAmount) AS RefundAmount
FROM (
SELECT I.Id
, IT.Id AS TransactionId
, IT.StatusId
, IF(IT.TypeId = 2, IT.RefundAmount, 0) AS RefundAmount
FROM Items I
INNER JOIN ItemTransactions IT ON IT.ItemId = I.Id
WHERE I.Id = someValue
) I
GROUP BY I.Id
HAVING StatusId = 1 AND RefundAmount = 0
Where Items table has transaction records stored in ItemTransactions table. I've been using this type of query and works for me until this time, got some issues with the having clause.
The query works in SQL Editors but not working properly when used on stored procedures. (Don't get me wrong, I've been using this query for most of my stored procedures). Debugging line per line, found that there is an issue with having clause.
As a temporary fix, I changed the query to:
SELECT I.Id
, I.StatusId
, I.RefundAmount
FROM (
SELECT I.Id
, CAST(SUBSTRING_INDEX(GROUP_CONCAT(I.StatusId ORDER BY I.TransactionId DESC), ',', 1) AS UNSIGNED) AS StatusId
, SUM(I.RefundAmount) AS RefundAmount
FROM (
SELECT I.Id
, IT.Id AS TransactionId
, IT.StatusId
, IF(IT.TypeId = 2, IT.RefundAmount, 0) AS RefundAmount
FROM Items I
INNER JOIN ItemTransactions IT ON IT.ItemId = I.Id
WHERE I.Id = someValue
) I
GROUP BY I.Id
--HAVING StatusId = 1 AND RefundAmount = 0
) I
WHERE I.StatusId = 1 AND I.RefundAmount = 0
The query works fine. But I'd like to know if somebody has already encountered this, and found a fix. I'm using MySQL 5.0.
Thanks
WHERE clause is used to filter data on the common attributes and expressions. HAVING clause is used to filter data after groupping had been performed and it's arguments should be either the ones of the GROUP BY clause, or expressions containing aggregate functions.
It is also illegal to use the alias of the column in WHERE, GROUP BY or HAVING clauses, while it does works for the ORDER BY clause.
One option, as you've found, is to use a sub-query and then column references will work.
Another one is to duplicate the whole expression in the HAVING clause:
SELECT I.Id,
CAST(SUBSTRING_INDEX(GROUP_CONCAT(I.StatusId ORDER BY I.TransactionId DESC),
',', 1) AS UNSIGNED) AS StatusId,
SUM(I.RefundAmount) AS RefundAmount
FROM (
SELECT I.Id
, IT.Id AS TransactionId
, IT.StatusId
, IF(IT.TypeId = 2, IT.RefundAmount, 0) AS RefundAmount
FROM Items I
INNER JOIN ItemTransactions IT ON IT.ItemId = I.Id
WHERE I.Id = someValue
) I
GROUP BY I.Id
HAVING
CAST(SUBSTRING_INDEX(GROUP_CONCAT(I.StatusId ORDER BY I.TransactionId DESC),
',', 1) AS UNSIGNED) = 1
AND SUM(I.RefundAmount) = 0;
EDIT: A closer look leaded to this question Using column alias in WHERE clause of MySQL query produces an error and to the MySQL documentation, that outlines that it is possible to use column aliases in GROUP BY, HAVING and ORDER BY clauses if such aliases are quoted with backticks, like:
...
HAVING `StatusId` = 1 AND `RefundAmount` = 0

Mysql Group BY with if statement

I'm not sure if this is something you can do in a single select statement without nesting selects.
I am grouping my results and I want to know IF a field inside the entire grouping contains a condition, display yes. With this it will just take the first row from the group and check the condition instead of all the rows in the group
if(field = 'condition','yes','no') as field_found
example table: id, score
SELECT t1.id, (
SELECT IF("10" IN (
SELECT score FROM table t2 WHERE t1.id = t2.id
),'yes','no'))
FROM table t1
GROUP BY t1.id
does that work?
Since you are already doing a group by, you should be able to add a MAX() as a column having the condition you are expecting and just add that to the group... such as
select
MAX( if( field LIKE '%condition%','1', '2' )) as ExtraOrderBy,
First_Name,
Last_Name,
... rest of query ...
group by
customers.Customer_ID
order by
1
In this case, the order by is the ordinal column in the SQL list instead of explicit retyping the MAX( IF() ) condition... So, if the condition is true, mark it with "1", otherwise "2" will float all those that qualify to the top of the list... Then, you could sub-order by other things like last name, first name, or other fields you have queried.
if(GROUP_CONCAT(field) LIKE '%condition%','yes','no')
SELECT first_name,last_name, CONCAT(physical_address," ",physical_address2," ",city, " ",zip) as address, MONTHNAME(install_date) as billing_month, IFNULL(status.quantity,0) as total_measures_instalclient2d,IFNULL(client1_measures,"") as client1_measures,IFNULL(client2_measures,"") as client2_measures,IFNULL(client1_quantity,0) as client1_quantity,IFNULL(client2_quantity,0) as client2_quantity,if(GROUP_CONCAT(measure_list.measure_type) LIKE '%Outreach/ Assessment%','yes','no') as outreach_invoiced,GROUP_CONCAT(IF(client='Client1',CONCAT(percent*100,"%-",measure_list.measure_type),NULL)) as client1_percent,GROUP_CONCAT(IF(client='Client2',CONCAT(percent*100,"%-",measure_list.measure_type),NULL)) as client2_percent,work_order.notes FROM customers
INNER JOIN measure on measure.customer_id = customers.customer_id
INNER JOIN measure_list on measure_list.measure_list_id = measure.measure_list_id
INNER JOIN work_order on work_order.work_order_id = measure.work_order_id
INNER JOIN billing on billing.workmanship = work_order.workmanship AND billing.measure_type = measure_list.measure_type
LEFT JOIN (
SELECT customers.customer_id,SUM(quantity) as quantity,GROUP_CONCAT(IF(client='Client1',measure_description,NULL)) as client1_measures,GROUP_CONCAT(IF(client='client2',measure_description,NULL)) as client2_measures,SUM(IF(client='client1',quantity,0)) as client1_quantity,SUM(IF(client='client2',quantity,0)) as client2_quantity FROM customers
INNER JOIN measure on measure.customer_id = customers.customer_id
INNER JOIN measure_list on measure_list.measure_list_id = measure.measure_list_id
INNER JOIN work_order on work_order.work_order_id = measure.work_order_id
INNER JOIN billing on billing.workmanship = work_order.workmanship AND billing.measure_type = measure_list.measure_type
WHERE measure_list.measure_type NOT IN ('measure1','measure2')
GROUP BY customers.customer_id
) as status on status.customer_id = customers.customer_id
WHERE measure_list.measure_type IN ('measure1','measure2')
GROUP BY customers.customer_id