Combine Multiple child rows into one row MYSQL - mysql

I have two tables
Ordered_Item
ID | Item_Name
1 | Pizza
2 | Stromboli
Ordered_Options
Ordered_Item_ID | Option_Number | Value
1 43 Pepperoni
1 44 Extra Cheese
2 44 Extra Cheese
What I am looking to output is a mysql query is something to this effect
Output
ID | Item_Name | Option_1 | Option_2
1 Pizza Pepperoni Extra Cheese
2 Stromboli NULL Extra Cheese
I have tried numerous options most ending in syntax error, I have tried group_concat but thats not really what I am looking for. I have a crude example below of what I think might be a start. I need the options to be in the same order every time. And in the program where the info is collected there is no way to reliable ensure that will happen. Is it possible to have them concatenate according to option number. Also I know that I will never have over 5 options so a static solution would work
Select Ordered_Items.ID,
Ordered_Items.Item_Name,
FROM Ordered_Items
JOIN (SELECT Ordered_Options.Value FROM Ordered_Options Where Option_Number = 43) as Option_1
ON Ordered_Options.Ordered_Item_ID = Ordered_Item.ID
JOIN (SELECT Ordered_Options.Value FROM Ordered_Options Where Option_Number = 44) as Option_2
ON Ordered_Options.Ordered_Item_ID = Ordered_Item.ID;

The easiest way would be to make use of the GROUP_CONCAT group function here..
select
ordered_item.id as `Id`,
ordered_item.Item_Name as `ItemName`,
GROUP_CONCAT(Ordered_Options.Value) as `Options`
from
ordered_item,
ordered_options
where
ordered_item.id=ordered_options.ordered_item_id
group by
ordered_item.id
Which would output:
Id ItemName Options
1 Pizza Pepperoni,Extra Cheese
2 Stromboli Extra Cheese
That way you can have as many options as you want without having to modify your query.
Ah, if you see your results getting cropped, you can increase the size limit of GROUP_CONCAT like this:
SET SESSION group_concat_max_len = 8192;

I appreciate the help, I do think I have found a solution if someone would comment on the effectiveness I would appreciate it. Essentially what I did is. I realize it is somewhat static in its implementation but I does what I need it to do (forgive incorrect syntax)
SELECT
ordered_item.id as `Id`,
ordered_item.Item_Name as `ItemName`,
Options1.Value
Options2.Value
FROM ORDERED_ITEMS
LEFT JOIN (Ordered_Options as Options1)
ON (Options1.Ordered_Item.ID = Ordered_Options.Ordered_Item_ID
AND Options1.Option_Number = 43)
LEFT JOIN (Ordered_Options as Options2)
ON (Options2.Ordered_Item.ID = Ordered_Options.Ordered_Item_ID
AND Options2.Option_Number = 44);

If you really need multiple columns in your result, and the amount of options is limited, you can even do this:
select
ordered_item.id as `Id`,
ordered_item.Item_Name as `ItemName`,
if(ordered_options.id=1,Ordered_Options.Value,null) as `Option1`,
if(ordered_options.id=2,Ordered_Options.Value,null) as `Option2`,
if(ordered_options.id=43,Ordered_Options.Value,null) as `Option43`,
if(ordered_options.id=44,Ordered_Options.Value,null) as `Option44`,
GROUP_CONCAT(if(ordered_options.id not in (1,2,43,44),Ordered_Options.Value,null)) as `OtherOptions`
from
ordered_item,
ordered_options
where
ordered_item.id=ordered_options.ordered_item_id
group by
ordered_item.id

If you know you're going to have a limited number of max options then I would try this (example for max of 4 options per order):
Select OI.ID, OI.Item_Name, OO1.Value, OO2.Value, OO3.Value, OO4.Value
FROM Ordered_Items OI
LEFT JOIN Ordered_Options OO1 ON OO1.Ordered_Item_ID = OI.ID
LEFT JOIN Ordered_Options OO2 ON OO2.Ordered_Item_ID = OI.ID AND OO2.ID != OO1.ID
LEFT JOIN Ordered_Options OO3 ON OO3.Ordered_Item_ID = OI.ID AND OO3.ID != OO1.ID AND OO3.ID != OO2.ID
LEFT JOIN Ordered_Options OO4 ON OO4.Ordered_Item_ID = OI.ID AND OO4.ID != OO1.ID AND OO4.ID != OO2.ID AND OO4.ID != OO3.ID
GROUP BY OI.ID, OI.Item_Name
The group by condition gets rid of all of the duplicates that you would otherwise get. I've just implemented something similar on a site I'm working on where I knew I'd always have 1 or 2 matched in my child table, and I wanted to make sure I only had 1 row for each parent item.

What you want is called a pivot, and it's not directly supported in MySQL, check this answer out for the options you've got:
How to pivot a MySQL entity-attribute-value schema

Here is how you would construct your query for this type of requirement.
select ID,Item_Name,max(Flavor) as Flavor,max(Extra_Cheese) as Extra_Cheese
from (select i.*,
case when o.Option_Number=43 then o.value else null end as Flavor,
case when o.Option_Number=44 then o.value else null end as Extra_Cheese
from Ordered_Item i,Ordered_Options o) a
group by ID,Item_Name;
You basically "case out" each column using case when, then select the max() for each of those columns using group by for each intended item.

Joe Edel's answer to himself is actually the right approach to resolve the pivot problem.
Basically the idea is to list out the columns in the base table firstly, and then any number of options.value from the joint option table. Just left join the same option table multiple times in order to get all the options.
What needs to be done by the programming language is to build this query dynamically according to a list of options needs to be queried.

Related

Joining 2 tables that use LIKE as a common identifier

I have two tables.
wp_rg_lead_detail:
id lead_id form_id field_number value
=====================================================
166649 2579 4 235 batman
167324 2602 4 235 batman
168439 2579 4 235 kelsey
169221 2836 4 235 batman
wp_rg_incomplete_submissions:
uuid form_id submission
=======================================================================
fds4389dsd2kjd 4 JSON entry that doesn't contain 'kelsey
ciwod2938slsck 4 JSON entry that contains 'kelsey
392copaa234jfl 4 JSON entry that doesn't contain 'kelsey
What I want to do is grab the record that:
has the word 'kelsey' in wp_rg_incomplete_submissions.submission
has a wp_rg_incomplete_submissions.form_id of 4
has the word 'kelsey' as a value in wp_rg_lead_detail
and the lead_id for that entry in wp_rg_lead_details should also have the word 'batman' for a value.
The only identifier between the two tables is the word 'kelsey'. But where it exists in wp_rg_lead_detail, that lead_id must also have an entry with the value of 'batman'.
I have tried subqueries and joins, and I'm getting nowhere. Can someone please point me in the right direction?
UPDATE
From the feedback below, it sounds like I should create an alias and then join them where that exists in both. Here's where I'm at:
SELECT *, 'kelsey' AS myvalue
FROM `wp_rg_lead_detail`
WHERE (`value` LIKE 'batman'
OR `value` LIKE 'kelsey')
AND `form_id` = 4
GROUP BY `lead_id`
HAVING count(*) > 1
I think somehow I need to join this where the LIKE uses myvalue:
SELECT *, uuid
FROM `wp_rg_incomplete_submissions`
WHERE `form_id` = 4
AND `submission` LIKE concat_ws(";", "%", myvalue, "%")
UPDATE #2
After continuing to struggle with this, I've come up with:
SELECT *
FROM wp_rg_lead_detail
INNER JOIN wp_rg_incomplete_submissions ON wp_rg_lead_detail.value
LIKE CONCAT('%', wp_rg_incomplete_submissions.submission, '%')
WHERE wp_rg_lead_detail.value = 'kelsey'
I know I'm doing something wrong because there are no results. But I feel it is much closer than where I started from.
So here is what I came up with, not vouching for it's efficiency as I don't write much SQL.
SELECT *
FROM submissions
JOIN (SELECT detail.*
FROM detail
JOIN detail detail2
ON detail2.lead_id = detail.lead_id
WHERE detail.value = 'kelsey'
AND detail2.value = 'batman'
) as detailjoin
ON detailjoin.form_id = submissions.form_id
WHERE submissions.submission LIKE '%kelsey%'
AND submissions.form_id = 4;
Which from you data set returns:
'ciwod2938slsck' 4 'JSON with kelsey' 168439 2579 4 235 'kelsey'
So to break it down, the inner join query gets all detail rows that have 'kelsey' as a value where that lead_id also exists in a row with a 'batman' value.
The outer query selects all rows with form_id of 4 and 'kelsey' in submission
Then it simply joins the two on form_id = form_id.
I believe this does what you needed although with the small data set not positive.

SQL unwanted results in NOT query

This looks like it should be really easy question, but I've been looking for an answer for the past two days and can't find it. Please help!
I have two tables along the lines of
texts.text_id, texts.other_stuff...
pairs.pair_id, pairs.textA, pairs.textB
The second table defines pairs of entries from the first table.
What I need is the reverse of an ordinary LEFT JOIN query like:
SELECT texts.text_id
FROM texts
LEFT JOIN text_pairs
ON texts.text_id = text_pairs.textA
WHERE text_pairs.textB = 123
ORDER BY texts.text_id
How do I get exclusively the texts that are not paired with A given textB? I've tried
WHERE text_pairs.textB != 123 OR WHERE text_pairs.textB IS NULL
However, this returns all the pairs where textB is not 123. So, in a situation like
textA TextB
1 3
1 4
2 4
if I ask for textB != 3, the query returns 1 and 2. I need something that will just give me 1.
The comparison on the second table goes in the ON clause. Then you add a condition to see if there is no match:
SELECT t.text_id
FROM texts t LEFT JOIN
text_pairs tp
ON t.text_id = tp.textA AND tp.textB = 123
WHERE tp.textB IS NULL
ORDER BY t.text_id ;
This logic is often expressed using NOT EXISTS or NOT IN:
select t.*
from texts t
where not exists (select 1
from text_pairs tp
where t.text_id = tp.textA AND tp.textB = 123
);

Using MAX of a field to select a record returns MAX of the table ignores the condition

I'm doing a stored procedure for a report, and I'm trying to get only those records with the highest value of a determined field (accumulated amount), the thing is I can't seem to find the solution to this, the only solution that i've came up with is using an extra condition, the problem is the field changes every month (period) and not all the records are updated but I need to retrieve them all... (if an asset is depreciated there wont be anymore records relating that asset in that table)
I'm sorry if this is confusing, I'll try my best to explain
The report needs to have for each supplier registered a list of the assets that supplies, their description, their current location, it's price, and how much money still needs to be depreciated from the asset.
So, what I'm doing it's first getting the list of suppliers, then getting the list of assets associated with a location (Using cursors) then I try to calculate how much money needs to be depreciated, there's a table called 'DEPRECIACIONES' that stores the asset, the period, and how much money has been depreciated from that asset for each period and for each asset that hasn't been completely depreciated. The problem comes when I try to calculate the MAX amount of money depreciated for an asset and then selecting the row for that item that has that MAX amount, I'm sure i'm doing something wrong, my TSQL and general database knowledge is not good and I'm trying to learn by myself.
I've uploaded the schema, tables and the stored procedure that throws the wrong output here:
http://sqlfiddle.com/#!3/78c32
The Right output should be something like this:
Proveedor | Activo | Descripcion | Ubicacion Actual | Costo Adquisicion | Saldo sin depreciar | Periodo
Supplier | Asset | Description | Current Location | Cost | Money to be depreciated | Period
-------------------------------------------------------------------------------------------
Monse |ActivoT| texthere | 1114 |2034.50| RANDOM NUMBER HERE |RandomP
Monse |cesart | texthere | 4453 |4553.50| RANDOM NUMBER HERE |RandomP
nowlast | activ | texthere | 4453 |1234.65| RANDOM NUMBER HERE |RandomP
nowlast |augusto| texthere | 4450 |4553.50| RANDOM NUMBER HERE |RandomP
Sara |Activo | texthere | 1206 |746.65 | RANDOM NUMBER HERE |RandomP
I'd really appreciate telling me what i'm doing wrong (which is probably a lot) and how to fix it, thank you in advance.
Good skills in giving complete information via SqlFiddle.
I don't have a complete answer for you, but this may help.
Firstly, ditch the cursor - it's hard to debug and possibly slow. Refactor to a SELECT statement. This is my attempt, which should be logically equivalent to your code:
SELECT
p.Proveedor,
a.Activo,
a.Descripcion,
Ubi.Ubicacion,
saldo_sin_depreciar = a.Costo_adquisicion - d.Monto_acumulado,
d.Periodo
FROM
PROVEEDORES p
INNER JOIN ACTIVOS_FIJOS a ON a.Proveedor = p.Proveedor
INNER JOIN DEPRECIACIONES d ON a.Activo = d.Activo
INNER JOIN
(
SELECT
MAX(d1.Monto_acumulado) AS MaxMonto
FROM DEPRECIACIONES d1
INNER JOIN DEPRECIACIONES d2
ON d1.Monto_acumulado = d2.Monto_acumulado
) MaxAe
ON d.Monto_acumulado = MaxAe.MaxMonto
INNER JOIN ACTIVO_UBICACION Ubi ON a.activo = ubi.activo
INNER JOIN
(
SELECT
activo,
ubicacion,
Fecha_Ubicacion,
RowNum = row_number() OVER ( partition BY activo ORDER BY abs(datediff(dd, Fecha_Ubicacion, getdate())))
FROM
ACTIVO_UBICACION
) UbU
ON UbU.ubicacion = Ubi.Ubicacion
WHERE
-- a.Activo IS NOT NULL AND
UbU.RowNum = 1
ORDER BY
p.Proveedor
COMMENTS
I've moved the WHERE criteria that are defining the joins up into ON clauses in the table list, that makes it easier to see how you are joining the tables.
Note that all the joins are INNER, which may not be what you want - you may need some LEFT JOIN's, I don't understand the logic enough to say.
Also, in your cursor procedure the Ubi and UbU parts don't seem to explicitly join with the rest of the tables, so I've pencilled-in an INNER JOIN on the activo column, as this is the way the tables join in the FK relationship.
In your cursor code, you would effectively get a CROSS JOIN which is probably wrong and also expensive to run.
The WHERE clause a.Activo IS NOT NULL is not required, because the INNER JOIN ensures it.
Hope this helps you sort it out.
I ended up using another query for the cursor and fixed the problem. It's probably not optimal but it works. Whenever I learn more database related stuff I'll optimize it.
Here's the new query:
DECLARE P CURSOR STATIC
FOR SELECT a.Proveedor, actub.activo, actub.ubicacion FROM [SISACT].PROVEEDORES p,[SISACT].ACTIVOS_FIJOS a, (SELECT activo, ubicacion, Fecha_Ubicacion, row_number() OVER (
partition BY activo ORDER BY abs(datediff(dd, Fecha_Ubicacion, getdate()))
) AS RowNum FROM [SISACT].ACTIVO_UBICACION) actub WHERE RowNum = 1 AND a.Proveedor = p.Proveedor AND actub.activo = a.Activo
OPEN P
FETCH NEXT FROM P INTO #p, #a, #u
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #activo = a.Activo, #descripcion = a.Descripcion, #costo_adquisicion = a.Costo_adquisicion, #saldo_depreciado = MaxAe.MaxMonto, #periodo = d.Periodo
FROM [SISACT].ACTIVOS_FIJOS a, [SISACT].DEPRECIACIONES d, SISACT.PROVEEDORES pro, SISACT.ACTIVO_UBICACION actu, (SELECT MAX(d1.Monto_acumulado) AS MaxMonto FROM [SISACT].DEPRECIACIONES d1 INNER JOIN [SISACT].DEPRECIACIONES d2 ON d1.Monto_acumulado = d2.Monto_acumulado WHERE d1.Activo = #a AND d2.Activo = #a) MaxAe
WHERE a.Activo = d.Activo AND a.Activo = #a AND d.Activo = #a AND a.Proveedor = #p AND actu.Activo = #a AND actu.Ubicacion = #u
SET #saldo_sin_depreciar = #costo_adquisicion - #saldo_depreciado
FETCH NEXT FROM P INTO #p, #a, #u
END
CLOSE P
DEALLOCATE P

How to use result of an subquery multiple times into an query

A MySQL query needs the results of a subquery in different places, like this:
SELECT COUNT(*),(SELECT hash FROM sets WHERE ID=1)
FROM sets
WHERE hash=(SELECT hash FROM sets WHERE ID=1)
and XD=2;
Is there a way to avoid the double execution of the subquery (SELECT hash FROM sets WHERE ID=1)?
The result of the subquery always returns an valid hash value.
It is important that the result of the main query also includes the HASH.
First I tried a JOIN like this:
SELECT COUNT(*), m.hash FROM sets s INNER JOIN sets AS m
WHERE s.hash=m.hash AND id=1 AND xd=2;
If XD=2 doesn't match a row, the result is:
+----------+------+
| count(*) | HASH |
+----------+------+
| 0 | NULL |
+----------+------+
Instead of something like (what I need):
+----------+------+
| count(*) | HASH |
+----------+------+
| 0 | 8115e|
+----------+------+
Any ideas? Please let me know! Thank you in advance for any help.
//Edit:
finally that query only has to count all the entries in an table which has the same hash value like the entry with ID=1 and where XD=2. If no rows matches that (this case happend if XD is set to an other number), so return 0 and simply hash value.
SELECT SUM(xd = 2), hash
FROM sets
WHERE id = 1
If id is a PRIMARY KEY (which I assume it is since your are using a single-record query against it), then you can just drop the SUM:
SELECT xd = 2 AS cnt, hash
FROM sets
WHERE id = 1
Update:
Sorry, got your task wrong.
Try this:
SELECT si.hash, COUNT(so.hash)
FROM sets si
LEFT JOIN
sets so
ON so.hash = si.hash
AND so.xd = 2
WHERE si.id = 1
I normally nest the statements like the following
SELECT Count(ResultA.Hash2) AS Hash2Count,
ResultA.Hash1
FROM (SELECT S.Hash AS Hash2,
(SELECT s2.hash
FROM sets AS s2
WHERE s2.ID = 1) AS Hash1
FROM sets AS S
WHERE S.XD = 2) AS ResultA
WHERE ResultA.Hash2 = ResultA.Hash1
GROUP BY ResultA.Hash1
(this one is hand typed and not tested but you should get the point)
Hash1 is your subquery, once its nested, you can reference it by its alias in the outer query. It makes the query a little larger but I don't see that as a biggy.
If I understand correctly what you are trying to get, query should look like this:
select count(case xd when 2 then 1 else null end case), hash from sets where id = 1 group by hash
I agree with the other answers, that the GROUP BY may be better, but to answer the question as posed, here's how to eliminate the repetition:
SELECT COUNT(*), h.hash
FROM sets, (SELECT hash FROM sets WHERE ID=1) h
WHERE sets.hash=h.hash
and sets.ID=1 and sets.XD=2;

Mysql multiple tables select

I've got a table, called for example, "node", from which I need to return values for as shown:
SELECT nid FROM node WHERE type = "book"
After I get a list of values let's say:
|**nid**|
|123|
|12451|
|562|
|536|
Then I need to take these values, and check another table, for rows where column 'path' has values as "node/123", "node/12451" (numbers the previous request returned) in one joined request. It all would be easier if collumn 'path' had simple numbers, without the 'node/'.
And then also count the number of identical i.e. 'node/123' returned.
End result would look like:
nid | path | count(path) | count(distinct path)
123 |node/123| 412 | 123
562 |node/562| 123 | 56
Works fine if done in multiple separated queries, but that won't do.
select a.nid from node a join othertable b
on b.path = concat("node/", a.nid) where type='book'
You can probably do something like the following (nid may require additional conversion to some string type):
SELECT *
FROM OtherTable
JOIN node ON path = CONCAT('node/', nid)
WHERE type = 'book'
Thank you all for your help. Basically, the problem was that I didn't know how to get nid and node/ together, but concat helped.
End result looks something like:
SELECT node.nid, accesslog.path, count(accesslog.hostname), count(distinct accesslog.hostname)
FROM `node`, `accesslog`
WHERE node.uid=1
AND node.type='raamat'
AND accesslog.path = CONCAT('node/', node.nid)
GROUP BY node.nid