Mysql count from multiple columns? - mysql

I have the following simplified tables:
statistics
+-------------+-------------+---------------+
| type | itemnumber | borrowernumber |
+-------------+-------------+---------------+
| issue | 26191 | 11978 |
+-------------+-------------+---------------+
| issue | 26190 | 11979 |
+-------------+-------------+---------------+
items:
+-------------+-------------+
| itemnumber | bibliono |
+-------------+-------------+
| 26191 | 27 |
+-------------+-------------+
| 26190 | 28 |
+-------------+-------------+
biblio_metadata:
+-------------+----------------------------------------------------+
| bibliono | metadata |
+-------------+----------------------------------------------------+
| 27 | <?xml.. <datafield tag="082" ind1="0" ind2="4"> |
| <subfield code="a">005.133/M29</subfield> |
| </datafield> |
+-------------+----------------------------------------------------+
| 28 | <?xml.. <datafield tag="082" ind1="0" ind2="4"> |
| <subfield code="a">995.133/M29</subfield> |
| </datafield> |
+-------------+----------------------------------------------------+
borrowers
+-------------+-------------+
| borrowerno | sort1 |
+-------------+-------------+
| 11978 | CAS |
+-------------+-------------+
| 11979 | CBA |
+-------------+-------------+
I want to get the following through a mysql query:
+-------------+------------+
| DDC Range | CAS | CBA |
+-------------+------------
| 001-100 | 1 | |
+-------------+------------
| 900-999 | | 1 |
+-------------+-----------+
I'm trying to find the right combination of queries - if it's mysql select query multiple columns or any other keyword but can't seem to get the right term to search.
I have the following made up mysql queries but can't go pass the first column 'CAS' and further query the other sort1's (in this example CBA).
SELECT CASE
WHEN ExtractValue(metadata, '//datafield[#tag="082"]/subfield[#code="a"]') REGEXP '^[0]{1}[0-9]{2}[^0-9]+.*' THEN "000-099"
WHEN ExtractValue(metadata, '//datafield[#tag="082"]/subfield[#code="a"]') REGEXP '^[9]{1}[0-9]{2}[^0-9]+.*' THEN "900-999"
ELSE "Others"
END as "DDC Range", count(borrowers.sort1)
from statistics s
LEFT JOIN items on (s.itemnumber=items.itemnumber)
LEFT JOIN biblio_metadata ON (items.biblionumber=biblio_metadata.biblionumber)
LEFT JOIN borrowers on (s.borrowernumber=borrowers.borrowernumber)
WHERE s.type = "issue"
AND borrowers.sort1="CAS"
GROUP BY Subjects
I'm looking into this COUNT(*) from multiple tables in MySQL but I don't know where to put the next query or if what I'm trying to arrive at is related to the aforementioned link. Thanks in advance

Looks like in your query you're filtering out all entries where borrowers.sort1="CAS" but if I understand correctly you'll need these.
Can't you just do all the joins as specified in your question and then use two case statements? As it seems you're interested in the count(*) per subject you can then sum them.
Maybe try something like below:
SELECT
CASE
WHEN ExtractValue(metadata, '//datafield[#tag="082"]/subfield[#code="a"]') REGEXP '^[0]{1}[0-9]{2}[^0-9]+.*' THEN "000-099"
WHEN ExtractValue(metadata, '//datafield[#tag="082"]/subfield[#code="a"]') REGEXP '^[9]{1}[0-9]{2}[^0-9]+.*' THEN "900-999"
ELSE "Others"
END as "DDC Range", count(borrowers.sort1),
sum(case when borrowers.sort1="CAS" then 1 else '' end) as 'CAS',
sum(case when borrowers.sort1="CBA" then 1 else '' end) as 'CBA'
from statistics s
LEFT JOIN items on (s.itemnumber=items.itemnumber)
LEFT JOIN biblio_metadata ON (items.biblionumber=biblio_metadata.biblionumber)
LEFT JOIN borrowers on (s.borrowernumber=borrowers.borrowernumber)
WHERE s.type = "issue"
GROUP BY Subjects

Related

Optimizing a conditional join in MySQL that depends on the character length of the source table

I'm using MySQL 5.7 and I'm trying to do a join with one of my source tables to a reference table in order to get the appropriate corresponding values. However, I'd like the join to be conditional so it can match according to the length of the value found in the source column.
Source Table
|---------------------|------------------|
| Company_Name | NAICS_Code |
|---------------------|------------------|
| Chem Inc | 325 |
|---------------------|------------------|
| Joe's Farming | 1112 |
|---------------------|------------------|
Reference Table
|---------------------|------------------|--------------------|------------------|
| NAICS_Code_3_Digit | NAICS_Code_ | NAICS_Code_4_Digit | NAICS_Cod_ |
| | 3D_Description | | 4D_Description |
|---------------------|------------------|--------------------|------------------|
| 325 | Chemicals | 3252 | Resin and Rubber|
|---------------------|------------------|--------------------|------------------|
| 111 | Crop Production | 1112 | Fruit and Nuts |
|---------------------|------------------|----------------------------------------
Final Table
|---------------------|------------------|------------------|--------------------|
| Company_Name | NAICS_Code | NAICS_Code_3D_ | NAICS_Code_4D |
| | | Description | Description |
|---------------------|------------------|---------------------------------------|
| Chem Inc | 325 | Chemicals | NULL |
|---------------------|------------------|------------------|--------------------|
| Joe's Farming | 1112 | Crop Production | Fruit and Nuts |
|---------------------|------------------|------------------|--------------------|
While I'm able to write a query that works, it takes an extremely long time and I' curious as to if there is a better way. Here's what I got so far:
SELECT src.Company_Name,
src.NAICS_Code,
CASE
WHEN LENGTH(src.NAICS_Code < 3 THEN NULL
ELSE ref.NAICS_Code_3D_Description
END AS NAICS_Code_3D_Description,
CASE
WHEN LENGTH(src.NAICS_Code < 4 THEN NULL
ELSE ref.NAICS_Code_4D Description
END AS NAICS_Code_4D_Description
FROM source_table AS src
LEFT JOIN reference_table AS ref ON CASE
WHEN LENGTH(src.NAICS_Code) = 4
AND src.NAICS_Code = ref.NAICS_Code_4_Digit THEN 1
WHEN LENGTH(src.NAICS_Code) = 3
AND src.NAICS_Code = ref.NAICS_Code_3_Digit THEN 1
ELSE 0
END = 1;
It might be more efficient to left join twice:
this avoids the need for the complicated logic in the on clause of the join
conditions are exclusive so it will not generate duplicates in the resultset
then you can use coalesce() in the select clause
So:
select
s.compay_name,
s.naics_code,
coalesce(r1.naics_code_3d_description, r2.naics_code_3d_description) naics_code_3d_description,
r2.naics_code_4d_description
from source_table s
left join reference_table r1 on r1.naics_code_3_digit = s.naics_code
left join reference_table r2 on r2.naics_code_4_digit = s.naics_code
If you want to evict source rows that did not match in the reference table, you can add a where clause, like:
where r1.naics_code_3_digit is not null or r2.naics_code_3d_description is not null

Retrieve all not logged in users from mysql tables (SQL query)

I have 2 tables listed below:
+-------------+-----------+--------------+-------------------------------+
| v3_customer |
+-------------+-----------+--------------+-------------------------------+
| customer_id | firstname | lastname | email |
+-------------+-----------+--------------+-------------------------------+
| 1 | Piet | Pizza | piet.pizza#example.com |
| 2 | Klaas | Hein | klaas.hein#example.com |
| 3 | Henk | Crowdcontrol | henk.crowdcontrol#example.com |
+-------------+-----------+--------------+-------------------------------+
+-------------+-------------+---------------+
| v3_customer_activity |
+-------------+-------------+---------------+
| activity_id | customer_id | key |
+-------------+-------------+---------------+
| 1 | 1 | login |
| 2 | 1 | order_account |
| 3 | 2 | login |
+-------------+-------------+---------------+
What i want is to select all customers which haven't logged in yet (note the login key in v3_customer_activity). So in this case that would be the customer with customer_id: 3
I'm using a mysql database.
I have tried using the following query:
SELECT DISTINCT v3_customer.customer_id, v3_customer.firstname, v3_customer.lastname, v3_customer.email FROM `v3_customer` JOIN v3_customer_activity ON v3_customer.customer_id = v3_customer_activity.customer_id WHERE v3_customer.customer_id != (SELECT v3_customer_activity.customer_id FROM v3_customer_activity)
In the hope it would iterate between the rows found in the subquery.
This resulted in an error telling me a subquery may not contain multiple rows.
TL;DR
What I want is to retrieve every customer from v3_customer who is not listed in the table v3_customer_activity
Try this:
select v3_customer.* from v3_customer
left join v3_customer_activity on v3_customer.customer_id=v3_customer_activity.customer_id
where v3_customer_activity.customer_id is null;
Left join v3_customer table with v3_customer_activity and filter records which are not matched.
select v3_customer.* from v3_customer
where v3_customer.customer_id not in (SELECT v3_customer_activity.customer_id FROM v3_customer_activity)

implementing Some kind of pivot in mysql

I have a table in mysql in this structure
table: member
| Id | Name | Lastname | Username
| --------------------------------
| 1 | Alexi| Lalas | alexi
| 2 | Jack | Louis | louis
And I have a table called member images with this structre:
table: image
| Id | MemberId | Image | Type |
|------------------------------------|
| 50 | 1 | face.jpg |Avetar |
| 51 | 1 | image.jpg |Gallery|
| 52 | 2 | main.jpg |Avetar |
| 53 | 2 | jungle.jpg |Gallery|
And I want to get this result
| Id | Name | Lastname | Username | Image1 | Image2 |
|-------------------------------------------------------|
| 1 | Alexi| Lalas | alexi |face.jpg |image.jpg |
| 2 | Jack | Louis | louis |main.jpg |jungle.jpg|
Becuase of some reasons I can't handle is on app side and I have to do it on sql side.
Imagin that I always have 2 type of images and we always have Image1 and Image2.
Any help would be appritiated.
Since they are only two types, you can use the CASE expression to do so. Something like this:
SELECT
m.Id,
m.Name,
m.LastName,
m.UserName,
MAX(CASE WHEN i.Type = 'Avetar' THEN i.Image END) AS 'Image1',
MAX(CASE WHEN i.Type ='Gallery' THEN i.Image END) AS 'Image2'
FROM member AS m
LEFT JOIN image AS i ON m.Id = i.MemberId
GROUP BY m.Id,
m.Name,
m.LastName,
m.UserName;
Note that: LEFT JOIN will include all the members from the member table even if they have no images in the image table, in this case NULL will be returned.
See it in action here:
SQL Fiddle Demo
The way to do this is via a subselect (subquery). You would subselect the first column as the image for the user withe Avetar type and the other column subselect would be for the image with the type Gallery. If you can't find a way to make User ID and Type unique in your sub table then you are going to have issues with this type of query. Keep in mind that a subselects can impact performance heavily.
http://dev.mysql.com/doc/refman/5.0/en/subqueries.html

MySQL: optimize query for scoring calculation

I have a data table that I use to do some calculations. The resulting data set after calculations looks like:
+------------+-----------+------+----------+
| id_process | id_region | type | result |
+------------+-----------+------+----------+
| 1 | 4 | 1 | 65.2174 |
| 1 | 5 | 1 | 78.7419 |
| 1 | 6 | 1 | 95.2308 |
| 1 | 4 | 1 | 25.0000 |
| 1 | 7 | 1 | 100.0000 |
+------------+-----------+------+----------+
By other hand I have other table that contains a set of ranges that are used to classify the calculations results. The range tables looks like:
+----------+--------------+---------+
| id_level | start | end | status |
+----------+--------------+---------+
| 1 | 0 | 75 | Danger |
| 2 | 76 | 90 | Alert |
| 3 | 91 | 100 | Good |
+----------+--------------+---------+
I need to do a query that add the corresponding 'status' column to each value when do calculations. Currently, I can do that adding the following field to calculation query:
select
...,
...,
[math formula] as result,
(select status
from ranges r
where result between r.start and r.end) status
from ...
where ...
It works ok. But when I have a lot of rows (more than 200K), calculation query become slow.
My question is: there is some way to find that 'status' value without do that subquery?
Some one have worked on something similar before?
Thanks
Yes, you are looking for a subquery and join:
select s.*, r.status
from (select s.*
from <your query here>
) s left outer join
ranges r
on s.result between r.start and r.end
Explicit joins often optimize better than nested select. In this case, though, the ranges table seems pretty small, so this may not be the performance issue.

In MYSQL, how do I get a LEFT JOIN to return every row in one table, and a flag if there were any matching rows in another table?

Basically, I have two tables, admin_privilege and admin_roll_privilege. I'm trying to write a query to get every row from admin_privilege, and if there is a row in admin_roll_privilege with a matching admin_privilege_id AND a matching admin_roll_id, to set a new column to 1. So far, I have this:
SELECT ap.*,
IF(arp.admin_privilege_id IS NULL,0,1) AS has_privilege
FROM admin_privilege ap LEFT JOIN admin_roll_privilege arp
ON ap.admin_privilege_id=arp.admin_privilege_id
WHERE arp.admin_roll_id=3
OR arp.admin_roll_id IS NULL;
This works in every case except where there are no matching rows admin_roll_privilege.
See Example:
+---------------+--------------------+
| admin_roll_id | admin_privilege_id |
+---------------+--------------------+
| 1 | 2 |
| 1 | 3 |
+---------------+--------------------+
+--------------------+------------------------+
| admin_privilege_id | admin_privilege_name |
+--------------------+------------------------+
| 1 | Access Developer Tools |
| 4 | Edit System Settings |
| 2 | Edit User Profiles |
| 3 | Resolve Challenges |
+--------------------+------------------------+
Querying for WHERE admin roll id=1 works as expected:
+--------------------+------------------------+---------------+
| admin_privilege_id | admin_privilege_name | has_privilege |
+--------------------+------------------------+---------------+
| 1 | Access Developer Tools | 0 |
| 4 | Edit System Settings | 0 |
| 2 | Edit User Profiles | 1 |
| 3 | Resolve Challenges | 1 |
+--------------------+------------------------+---------------+
But, if i query for admin_roll_id=3, i only get two rows returned:
+--------------------+------------------------+---------------+
| admin_privilege_id | admin_privilege_name | has_privilege |
+--------------------+------------------------+---------------+
| 1 | Access Developer Tools | 0 |
| 4 | Edit System Settings | 0 |
+--------------------+------------------------+---------------+
How can I get this query to return all 4?
Edit: This is what ended up working, moving the condition to the on clause:
SELECT ap.*,
IF(arp.admin_privilege_id IS NULL,0,1) AS has_privilege
FROM admin_privilege ap LEFT JOIN admin_roll_privilege arp
ON (ap.admin_privilege_id=arp.admin_privilege_id AND arp.admin_roll_id=1)
Move the appropriate conditions from the WHERE clause to the ON clause.
You are not returning all rows by using the WHERE clause on the entire statement.
Turn the LEFT JOIN into a subselect on wich you can add the WHERE clause you need.
SELECT ap.admin_privilege_id
, ap.admin_privilege_name
, IF(arp.admin_privilege_id IS NULL,0,1) AS has_privilege
FROM admin_privilege ap
LEFT OUTER JOIN (
SELECT admin_privilege_id
FROM admin_roll_privilege arp
WHERE arp.admin_roll_id = 3
) arp ON arp.admin_privilege_id = ap.admin_privilege_id