Selecting from multiple tables with multiple criteria - mysql

I have a MySQL database with three tables: sample, method, compound.
sample has the following columns: id(PK)(int), date(date), compound_id(int), location(varchar), method(int), value(float)
method has the following columns: id(PK)(int), label(varchar)
And compound has: id(PK)(int), name(varchar), unit(varchar)
I am trying to generate a SQL command that only pulls in the unique row for the following criteria:
Date (sample.date)
Compound Name (compound.name)
Location (sample.location)
Method (sample.method)
However, I want to substitute in the labels for some of the sample columns instead of the numbers:
sample.compound_id is matched to compound.id which has a corresponding compound.name and compound.unit
The first SQL command I tried to query was:
SELECT sample.id, sample.date, compound.name, sample.location, method.label, sample.value, compound.unit
FROM sample, compound, method
WHERE sample.date = "2011-11-03"
AND compound.name = "Zinc (Dissolved)"
AND sample.location = "13.0"
AND method.id = 1;
The output from the above command:
id date name location label value unit
1 2011-11-03 Zinc (Dissolved) 13.0 (1) Indivi... 378.261 μg/L
5 2011-11-03 Zinc (Dissolved) 13.0 (1) Indivi... 197.917 μg/L
9 2011-11-03 Zinc (Dissolved) 13.0 (1) Indivi... 92.4051 μg/L
But when I look at sample and compare sample.id to what was returned:
id date compound_id location method value
1 2011-11-03 13 13.0 1 378.261
5 2011-11-03 14 13.0 1 197.917
9 2011-11-03 47 13.0 1 92.4051
Where compound.id 47 corresponds to compound.id 47 and compound.name "Zinc (Dissolved)". Compound IDs #13 and #14 are "Copper (Dissolved)" and "Copper (Total)", respectively.
So it seems to be returning rows that meet the criteria for sample.date and sample.location without regard to compound.name. Given the above criteria, I know that my database should only return one row, but instead I get some sample.id rows that have a completely different sample.compound_id than the matching compound.name that I specified.
I would like to end up with the columns that are SELECTed in the first line to end up in the same order as I wrote them. This code is for a little database viewer/reporter program I'm writing in Python/Tkinter and relies on the columns being uniform. The code that I use to initialize the data for the program works as I expect:
SELECT sample.id, sample.date, compound.name, sample.location, method.label, sample.value, compound.unit
FROM sample, compound, method
WHERE sample.compound_id = compound.id
AND sample.method = method.id;
Which puts out each unique line in sample with the substitutions for sample.compound_id to compound.name and sample.method to method.label and adds in the compound.unit at the end.
Question #1: How do I need to restructure my query so that it only returns the row that meets that specific criteria?
Question #2: Eventually I'm going to need to specify multiple sample.locations at one time. Is that as simple as adding an OR statement for each individual location that I need?

SELECT sample.id, sample.date, compound.name, sample.location, method.label, sample.value, compound.unit
FROM sample
INNER JOIN compound ON compound.id = sample.compound_id
INNER JOIN method ON method.id = sample.method
WHERE sample.date = '2011-11-03'
AND compound.name = 'Zinc (Dissolved)'
AND sample.location = "13.0"
AND method.id = 1;

Now that I have the first question figured out, I figured out my second question:
SELECT sample.id, sample.date, compound.name, sample.location, method.label, sample.value, compound.unit
FROM sample
INNER JOIN compound ON compound.id = sample.compound_id
INNER JOIN method ON method.id = sample.method
WHERE sample.date = '2011-11-03'
AND compound.name = 'Zinc (Dissolved)'
AND sample.location IN ("13.0", "22.0")
AND method.id = 1;
And just keep adding ORs inside the brackets for each other location.

Related

MySQL Query with LEFT JOIN where second table has a 2-Part Primary Key

I have 2 tables in a MySQL database (storeskus). The first is FBM_Orders and the second is IM_INV.
I am trying the query
SELECT `FBM_Orders`.`order-id`,`FBM_Orders`.`order-item-id`,`FBM_Orders`.`purchase-date`,
`FBM_Orders`.`promise-date`,`FBM_Orders`.`buyer-name`,`FBM_Orders`.`sku`,
`FBM_Orders`.`product-name`,`FBM_Orders`.`quantity-purchased`,
`FBM_Orders`.`recipient-name`,`IM_INV`.`LOC_ID`,`IM_INV`.`QTY_ON_HND`
FROM `FBM_Orders`
LEFT JOIN `IM_INV` ON `FBM_Orders`.`sku` = `IM_INV`.`ITEM_NO`
WHERE `FBM_Orders`.`quantity-to-ship` > 0
ORDER BY `FBM_Orders`.`purchase-date`, `IM_INV`.`LOC_ID` ASC;
Because the IM_INV table has a 2-part primary key: ITEM_NO & LOC_ID, I am getting 4 lines for each ITEM_NO with the QTY_ON_HND for each of the 4 locations (LOC_ID).
I am fairly new to SQL so I'm thrilled to have gotten this far, but how can I make it so that the result is a single line per ITEM_NO but with a column for each LOC_ID with its QTY_ON_HND?
Example:
My current result is
FBM_Order.sku FBM_Order.quantity-purchased IM_INV.LOC_ID QTY_ON_HND
'SCHO645256' 1 AF 2
'SCHO645256' 1 LO 2
'SCHO645256' 1 S 3
'SCHO645256' 1 SL 1
How can I change that to
FBM_Order.sku FBM_Order.quantity-purchased QTY_ON_HND_AF QTY_ON_HND_LO QTY_ON_HND_S QTY_ON_HND_SL
'SCHO645256' 1 2 2 3 1
?
Thanks!
You may load it as you already do and treat it inside your application, but if you really wanna make that inside your MySQL, try GROUP CONCAT and JSON as follows:
SELECT
GROUP_CONCAT(JSON_OBJECT(
'LOC_ID', IM_INV.LOC_ID,
'QTY_ON_HND', QTY_ON_HND
))
{another fields}
FROM `FBM_Orders`
LEFT JOIN `IM_INV` ON `FBM_Orders`.`sku` = `IM_INV`.`ITEM_NO`
WHERE `FBM_Orders`.`quantity-to-ship` > 0
GROUP BY `FBM_Orders`.`order-id`;
Note: JSON is just available for MySQL 5.7+ and may slow down your query a little bit. You're still gonna need convert your data to array inside your application. So it's half done inside your app and half inside your database.

How to Find First Valid Row in SQL Based on Difference of Column Values

I am trying to find a reliable query which returns the first instance of an acceptable insert range.
Research:
some of the below links adress similar questions, but I could get none of them to work for me.
Find first available date, given a date range in SQL
Find closest date in SQL Server
MySQL difference between two rows of a SELECT Statement
How to find a gap in range in SQL
and more...
Objective Query Function:
InsertRange(1) = (StartRange(i) - EndRange(i-1)) > NewValue
Where InsertRange(1) is the value the query should return. In other words, this would be the first instance where the above condition is satisfied.
Table Structure:
Primary Key: StartRange
StartRange(i-1) < StartRange(i)
StartRange(i-1) + EndRange(i-1) < StartRange(i)
Example Dataset
Below is an example User table (3 columns), with a set range distribution. StartRanges are always ordered in a strictly ascending way, UserID are arbitrary strings, only the sequences of StartRange and EndRange matters:
StartRange EndRange UserID
312 6896 user0
7134 16268 user1
16877 22451 user2
23137 25142 user3
25955 28272 user4
28313 35172 user5
35593 38007 user6
38319 38495 user7
38565 45200 user8
46136 48007 user9
My current Query
I am trying to use this query at the moment:
SELECT t2.StartRange, t2.EndRange
FROM user AS t1, user AS t2
WHERE (t1.StartRange - t2.StartRange+1) > NewValue
ORDER BY t1.EndRange
LIMIT 1
Example Case
Given the table, if NewValue = 800, then the returned answer should be 23137. This means, the first available slot would be between user3 and user4 (with an actual slot size = 813):
InsertRange(1) = (StartRange(i) - EndRange(i-1)) > NewValue
InsertRange = (StartRange(6) - EndRange(5)) > NewValue
23137 = 25955 - 25142 > 800
More Comments
My query above seemed to be working for the special case where StartRanges where tightly packed (i.e. StartRange(i) = StartRange(i-1) + EndRange(i-1) + 1). This no longer works with a less tightly packed set of StartRanges
Keep in mind that SQL tables have no implicit row order. It seems fair to order your table by StartRange value, though.
We can start to solve this by writing a query to obtain each row paired with the row preceding it. In MySQL, it's hard to do this beautifully because it lacks the row numbering function.
This works (http://sqlfiddle.com/#!9/4437c0/7/0). It may have nasty performance because it generates O(n^2) intermediate rows. There's no row for user0; it can't be paired with any preceding row because there is none.
select MAX(a.StartRange) SA, MAX(a.EndRange) EA,
b.StartRange SB, b.EndRange EB , b.UserID
from user a
join user b ON a.EndRange <= b.StartRange
group by b.StartRange, b.EndRange, b.UserID
Then, you can use that as a subquery, and apply your conditions, which are
gap >= 800
first matching row (lowest StartRange value) ORDER BY SB
just one LIMIT 1
Here's the query (http://sqlfiddle.com/#!9/4437c0/11/0)
SELECT SB-EA Gap,
EA+1 Beginning_of_gap, SB-1 Ending_of_gap,
UserId UserID_after_gap
FROM (
select MAX(a.StartRange) SA, MAX(a.EndRange) EA,
b.StartRange SB, b.EndRange EB , b.UserID
from user a
join user b ON a.EndRange <= b.StartRange
group by b.StartRange, b.EndRange, b.UserID
) pairs
WHERE SB-EA >= 800
ORDER BY SB
LIMIT 1
Notice that you may actually want the smallest matching gap instead of the first matching gap. That's called best fit, rather than first fit. To get that you use ORDER BY SB-EA instead.
Edit: There is another way to use MySQL to join adjacent rows, that doesn't have the O(n^2) performance issue. It involves employing user variables to simulate a row_number() function. The query involved is a hairball (that's a technical term). It's described in the third alternative of the answer to this question. How do I pair rows together in MYSQL?

MySQL query to substring length check up and sum respective substrings to one

How to write a SQL query to check if column each NAIC code value is 3 if yes then sum all output values having the same 3 digit NAIC code
SELECT ni.NAICS,
ncio.outputValue,
IF( NAICS =SUBSTR(ni.NAICS,0,3), SUM(ncio.outputValue), NULL )
FROM
nwk_company_industry_output ncio,
nwk_industry ni,
nwk_company nc
WHERE
ni.NAICS = ncio.company_id
ORDER BY
NAICS
This should work for you provided the join condition is correct (ni.NAICS=ncio.company_id)
SELECT ni.NAICS,
sum(ncio.outputValue) as sum_outputValue
FROM nwk_company_industry_output ncio,
nwk_industry ni
-- nwk_company nc (doesn't appear to be needed)
WHERE length(ni.NAICS) = 3
AND ni.NAICS = ncio.company_id
-- (join condition here if new_company is needed)
GROUP BY ni.NAICS;

Add Column in Table with conditional in block WHERE

I was doing a query with MySQL to save all objects returned, but I'd like identify these objects based in statements of the block WHERE, that is, if determined object to satisfy the specific characteristic I'd like create one column and in this column I assignment the value 0 or 1 in the row corresponding the object if it satisfy or not satisfy these characteristic.
This is my script:
SELECT
s.id, al.ID, al.j, al.k, al.r, gal.i
FROM
datas as al
WHERE
AND s.id = al.ID
AND al.j between 1 and 1
AND al.k BETWEEN 15 and 16
AND al.r BETWEEN 67 and 72
The script above is working perfectly and I can to save all objects which it return.
So, I'd like to know if is there a way add in the query above, on block WHERE, the following statement,
( Flags & (dbo.environment('cool') +
dbo.environment('ok') -
dbo.environment('source')) ) = 25
and ((al_pp x al_pp1)-0.5/3=11
and determined the objects that satisfy or not these condition with 0 or 1 in a new column created in Table saved.
I read some tutorials about this and saw some attempts with IF, CASE, ADD COLUMN or WHEN, but none of these solved.
Thanks in advance
MySQL has if function, see here
So you can simply use it in your query:
SELECT IF(( Flags & (dbo.fPhotoFlags('SATURATED') +
dbo.fPhotoFlags('BRIGHT') +
dbo.fPhotoFlags('EDGE')) ) = 0
and petroRad_r < 18
and ((colc_u - colc_g) - (psfMag_u - psfMag_g)) < -0.4
, 1 --// VALUE IF TRUE
, 0 --// VALUE IF FALSE
) as conditional_column, ... rest of your query

MySQL update with data from two tables

I have a MySQL database with the two tables that I need modified.
The first table holds notes
id type note
1 1 24 months warranty
2 1 12 months warranty
3 2 Garage in Denver
4 3 Pre sales maintenance done
....
And then a vehicle table that holds many vehicle tables and a field that hold notes with their text instead of a pointer
id licence_plate ... sales_notes ...
1 XH34DN ... <warranty>24 months warranty</warranty><garage>Garage in Denver</garage><maintenance>Pre sales maintenance done</maintenance> ...
2 K4B3C6 ... <warranty>12 months warranty</warranty><garage>Garage in Sacramento</garage><maintenance>Pre sales maintenance not done</maintenance> ...
As you can imagine this is higly inneficient and I want to modify to pointers that hold the id of the note.
id licence_plate ... warranty_note garage_note maintenace_note ...
1 XH34DN ... 1 3 4 ...
2 K4B3C6 ... 2 7 12 ...
I can do it manual updates but I would like to build one that makes it automatically by type.
So for notes.type=1 if the notes.note text is found in vehicle.sales_notes it updates the vehicle.warranty_note.
Any idea how to build something like that?
I have something like this in mind, but id doesn't work. No results are updated
UPDATE tx_vehicle v, tx_note n
SET v.garage_note = n.uid
WHERE v.sales_notes LIKE ('%'+n.note+'%')
MySQL has special XML parsing functions.
insert into your_new_notes_table (id, licence_plate, warranty_note, garage_note, maintenace_note)
select
sn.id,
sn.license_plate,
(select nt.id from notes as nt where nt.type = 1 and nt.note = ExtractValue(sn.sales_note, "/warranty")) as warranty_note,
(select nt.id from notes as nt where nt.type = 1 and nt.note = ExtractValue(sn.sales_note, "/garage")) as garage_note,
(select nt.id from notes as nt where nt.type = 1 and nt.note = ExtractValue(sn.sales_note, "/maintenance")) as maintenance_note
from note_types as nt
Although, it will fail if exact note from sales is not found in note_types table. You can adjust note text comparison, replacing it with like operator, regex checking or another functions; you can replace abbreviations with full words or invent your own MySQL function. Moreover, you can invent your own MySQL function that would encompass internal selects and return null if note is not found in note_types table.
If you want to update existing table, you need to perform update query with join to same select as I provided.