1

My core case a little bit more complicated, so I will illustrate it with an example. Let's pretend I have tables like these:

animals

name (PK) color
cat1 white
cat2 red
dog1 black

places

place (PK) name (FK) amount
cage1 cat1 2
room1 cat1 3
cage2 dog1 5

in_sale

name (FK) amount price
cat1 1 50.00
dog1 3 600.00
cat2 2 1.00

Here's code to create them:


    CREATE TABLE `animals` (
  `name` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
  `color` varchar(100) COLLATE utf8_unicode_ci NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

CREATE TABLE `in_sale` (
  `name` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
  `amount` int(11) NOT NULL,
  `price` varchar(100) COLLATE utf8_unicode_ci NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

CREATE TABLE `places` (
  `place` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
  `name` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
  `amount` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

INSERT INTO `animals` (`name`, `color`) VALUES
('cat1', 'white'),
('cat2', 'red'),
('dog1', 'black');

INSERT INTO `in_sale` (`name`, `amount`, `price`) VALUES
('cat1', 1, '25.00'),
('cat1', 1, '50.00'),
('cat2', 2, '1.00'),
('dog1', 3, '600.00');

INSERT INTO `places` (`place`, `name`, `amount`) VALUES
('cage1', 'cat1', 2),
('cage2', 'dog1', 5),
('room1', 'cat1', 3);

Now I wanted to run a query:

SELECT a.*, p.place, p.amount AS amount_in_place, s.sales 
FROM animals AS a 
LEFT JOIN places AS p ON a.name=p.name 
LEFT JOIN (SELECT GROUP_CONCAT("Amount: ",amount, " and price: ",price separator ", ") AS sales, name FROM in_sale GROUP BY name) AS s ON s.name=a.name 
ORDER BY a.name;

But unfortunately, I realized that result it's not something that I expected.

RESULT:

name color place amount_in_place sales
cat1 white cage1 2 Amount: 1 and price: 25.00, Amount: 1 and price: 5...
cat1 white room1 3 Amount: 1 and price: 25.00, Amount: 1 and price: 50.00
cat2 red NULL NULL Amount: 2 and price: 1.00
dog1 black cage2 5 Amount: 3 and price: 600.00

EXPECTED:

name color place amount_in_place sales
cat1 white cage1 2 Amount: 1 and price: 25.00, Amount: 1 and price: 50.00
cat1 white room1 3 NULL
cat2 red NULL NULL Amount: 2 and price: 1.00
dog1 black cage2 5 Amount: 3 and price: 600.00

What can I change in my query to join the last table with just first matching row? I tried to manipulate a little bit with LIMIT 1,OUTER JOIN and MIN as I found some suggestions in similar questions, but I couldn't achieve my goal anyway.

IMPORTANT! Pay attention that animals can be in sale, even if they're have no place assigned.

Kida
  • 734
  • 1
  • 9
  • 23
  • 1
    *I have tables like these* Provide them as CREATE TABLE + INSERT INTO scripts or a link to online fiddle. Provide desired output in textual table-formatted view (it must match the sample data precisely!). Specify precise MySQL version. – Akina Oct 28 '21 at 05:50
  • Why `in_sale` rows for `'cat1'` are joined to `'cage1'` but not joined to `'room1'`? What defines this relation? PS. [fiddle](https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=31bd0795e5fd78523dfc21ea72b44711) – Akina Oct 28 '21 at 06:42
  • I would like to join with first matching row, no matter if it will be `cage1` or `room1`. So my first idea was to join `animals` with `in_sale` on `name` if place is `MIN` or `NULL` for every `name` group. – Kida Oct 28 '21 at 07:00
  • 1
    *I would like to join with first matching row* (1) What is "first"? for this term to exist you must define some unique rows ordering. (2) Imagine that `amount` value in `places` is equal to 2 for both rows with `name = 'cat1'` - how to define what of these rows matches and what do not? (3) Imagine that there are 5 rows in `in_sale` for `name = 'cat1'` with 5 different `price` values - how to define what price to what place must belong to? – Akina Oct 28 '21 at 07:07
  • @Akina (3) as you see in my example above I'm concatenate all prices for `cat1` to one, so it doesn't matter how many different prices will be for `cat1` – Kida Oct 28 '21 at 07:17
  • But you will have one solid amount-price string with 5 values for a palce which have only 2 pets - is this correct? – Akina Oct 28 '21 at 07:18
  • @Akina (1) When I'm using `LIMIT 1`, then MySQL is matching to just one row and it is choosing by himself which one is "first", doesn't it? (2) The amount value can be the same, PK is on place, not on the amount, so I don't see a problem here – Kida Oct 28 '21 at 07:25
  • 1
    Look does the query in [this fiddle](https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=653ebc271744be5f3bb13afc0ecca6f1) is safe for your task? – Akina Oct 28 '21 at 07:26
  • Yes, that's it! Sorry, now I realized that I paste `NULL` on `amount_in_place`, where should be value `3` – Kida Oct 28 '21 at 07:28
  • Replace this output column with one more CASE like in the next output column. – Akina Oct 28 '21 at 08:20
  • I think the final version will be something like [that](https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=77a08439fa441f9c460f63756f2a12e6). Thanks for help, I didn't know about `LAG ... OVER ...` and `PARTITION BY` functions before. – Kida Oct 28 '21 at 08:33
  • Define "first matching row". Tables have no row order; query results are ordered per any outermost order by. This is not clear. Use enough words, sentences & references to parts of examples to clearly & fully say what you mean. Clarify via edits, not comments. Please [use text, not images/links, for text--including tables & ERDs](https://meta.stackoverflow.com/q/285551/3404097). Paraphrase or quote from other text. Give just what you need & relate it to your problem. Use images only for what cannot be expressed as text or to augment text. Include a legend/key & explanation with an image. – philipxy Oct 28 '21 at 08:35
  • Please in code questions give a [mre]--cut & paste & runnable code; example input with desired & actual output (including verbatim error messages); tags & versions; clear specification & explanation. For errors that includes the least code you can give that is code that you show is OK extended by code that you show is not OK. (Debugging fundamental.) For SQL include DDL & tabular initialization code. When you get a result you don't expect, pause your overall goal, chop to the 1st subexpression with unexpected result & say what you expected & why, justified by documentation. [ask] [Help] – philipxy Oct 28 '21 at 08:35

1 Answers1

1

Thanks to @Akina I can provide the final version of code for my example:

SELECT name,
   animals.color,
   places.place,
   places.amount amount_in_place,
   CASE WHEN name = LAG(name) OVER (PARTITION BY name ORDER BY place)
   THEN 
      null
   ELSE 
      (SELECT GROUP_CONCAT("Amount: ",amount, " and price: ",price 
      SEPARATOR ", ") AS sales 
      FROM in_sale 
      WHERE in_sale.name=animals.name GROUP BY name) 
   END sales
FROM animals
LEFT JOIN places USING (name)
LEFT JOIN in_sale USING (name)
GROUP BY 1,2,3,4;

Note that it works only for MySQL version 8 or higher.

For older versions we can use self-defined variable:

SELECT x.*,
   @rowname,
   CASE WHEN name = @rowname
   THEN 
      null
   ELSE 
      (SELECT GROUP_CONCAT('Amount: ',amount, ' and price: ',price 
      SEPARATOR ', ') AS sales 
      FROM in_sale 
      WHERE in_sale.name=x.name GROUP BY name) 
   END sales,
   @rowname := name
   from
(SELECT name,
   animals.color,
   places.place,
   places.amount amount_in_place
FROM animals
LEFT JOIN places USING (name)
LEFT JOIN in_sale USING (name)
GROUP BY 1,2,3,4) as x
join (SELECT @rowname := 0) as r;

WARNING! As @philipxy pointed out in a comment, it can give very different and unexpected results. For me, comparing results in columns @rowname and @rowname := name and checking the sales column, works fine every time. (locally 10.4.11-MariaDB and on an external server MySQL 5.7.34-37-log - Percona Server - I'm joining over a dozen tables. It's returning over 20000 rows)

Kida
  • 734
  • 1
  • 9
  • 23
  • Reading & assigning the same variable in the same select statement is undefined behaviour in MySQL, see the documentation re assignment & variables. [Why the order of evaluation for expressions involving user variables is undefined?](https://stackoverflow.com/a/44751302/3404097) See my comments (re an incorrect answer) at [MySQL - Define a variable within select and use it within the same select](https://stackoverflow.com/a/16715618/3404097). – philipxy Nov 04 '21 at 22:14
  • Archived dead link in a comment at linked answer: https://web.archive.org/web/20210303125758/https://mysqlserverteam.com/row-numbering-ranking-how-to-use-less-user-variables-in-mysql-queries/ – philipxy Nov 04 '21 at 22:31
  • @philipxy Wow, it's my first time when I'm using a self-defined variable in SQL and I didn't know that. I based my solution on some `LAG() OVER(PARTITION BY... ORDER BY...)` alternatives that I found on StackOverflow, but if you are able to share a safer solution, please feel free to do that :) – Kida Nov 05 '21 at 06:47
  • Use a stored procedure. Likely that is a faq. It is frequently an answer in questions re accomplishing the sort of thing that people wrongly try to do via variables in questions & answers. (Reasonable search involves many clear, concise & precise phrasings of your question/problem/goal, with & without your particular names/strings/numbers, 'site:stackoverflow.com' & tags, & reading many answers.) PS "works fine every time" (like "other people do it") is no reason to believe or use an (unclear) unjustified naive/folk theory. PS Also I doubt you checked all 20000 rows "every time". – philipxy Nov 05 '21 at 07:25
  • I don't have skills to use stored procedures and php multi_query function yet, but maybe in the future I will learn and update answer with better idea. PS. I understand, it's not math evidence. In my case I can handle it, even if something will go wrong somehow (what I think is low probability), the world won't end. Anyway I would like to see any example, when using this went wrong... In links I see just theory, but there is no example that didn't work as expected. PS2. By "every time" I meant every time during testing, I didn't have to check every row manually, of course. – Kida Nov 05 '21 at 08:58