Why does COALESCE order results in this way? - php

I have a table with three columns, id, comment, and parent. If parent id is null, the comment is the root comment, and if it is not, this means the comment is a reply to another. I use the following query:
SELECT *
FROM `comment`
ORDER BY COALESCE( parent, id ) DESC
LIMIT 0 , 30
This query orders the last inserted comment with it's replies, but I don't understand the logic. Why is it ordered this way?

The COALESCE() function returns the first non-null argument that is received. So, if you go through each row and compare the parent/id column, you'll see that it is ordered like this:
7 (because parent is null)
2 (because parent is null)
2 (parent is not null, so it is used)
1 (because parent is null)
1 (parent is not null, so it is used)
1 (parent is not null, so it is used)
Which is in descending order, as you specified.
I suspect there may be some confusion here. So let me reiterate. COALESCE(parent, id) will return the first value out of those two that is not null. If parent is not null, it is returned. If it is null, it falls back on id and returns that. If you look at a list of those rows side by side and see the return values, it may be more clear:
| parent | id | return_value |
+--------+----+--------------+
| null | 7 | 7 |
| null | 2 | 2 |
| 2 | 4 | 2 |
| null | 1 | 1 |
| 1 | 3 | 1 |
| 1 | 5 | 1 |
| 1 | 6 | 1 |

Maybe your query should be
SELECT *
FROM `comment`
ORDER BY parent DESC, id DESC
LIMIT 0 , 30;
First comes the sort by parent (NULLs sort after ints)
4653
and then
sorting by id in reverse order
721
That will give
4
6
5
3
7
2
1
Is this what you were looking for?

Related

Updating the table with new value by finding specific row

I have a table that I add information depending on the order number.
So when I enter information, I add some column names as numbers.
Then update the rows with new values.
My table takes 3 values everytime I insert value.
order number| total left | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9|
------------------------------------------------------------------------
11 | 100 | a | b | c | d | e | f | 0 | | |
------------------------------------------------------------------------
12 | 10 | x | y | z | 0 | | | s | d | f|
-------------------------------------------------------------------------
When I try to add new column, the value of the new rows in every other value becomes null or 0 depending on if it is int or varchar.
When I insert 3 values to order number 12, I want the last 0 in that row to be updated.
(In this case, 4-5-6 but I get 7-8-9 updated.)
So what is the best way for finding the last row which is 0
(in this case column 4 for order number 12 and insert the new values of s,d,f to the 4-5-6 instead of 7-8-9 ?
So maybe something like:
Loop through rows, find 0, insert 3 rows, break.
I take the last column:
$NewColumnNameKoliAdet=$LastColumnName+1;
$NewColumnNameMusteri=$LastColumnName+2;
$NewColumnNameTarih=$LastColumnName+3;
Then I add columns and update the table.
$Query="ALTER TABLE `koli_stok_hareketleri`
ADD `$NewColumnNameKoliAdet` INT(11) NOT NULL AFTER `$LastColumnName`,
ADD `$NewColumnNameMusteri` VARCHAR(100) NOT NULL AFTER `$NewColumnNameKoliAdet`,
ADD `$NewColumnNameTarih` VARCHAR(100) NOT NULL AFTER `$NewColumnNameMusteri`;";
$Query="UPDATE koli_stok_hareketleri SET kalan_koli=kalan_koli-
'$Uretilen_Koli',
`$NewColumnNameKoliAdet` = '$Uretilen_Koli',
`$NewColumnNameMusteri` = '$Musteri_ismifromrow',
`$NewColumnNameTarih` = '$Now'
WHERE koli_ismi ='$Koli_IsmifromRow' AND
koli_parti_no='$Parti_NofromRow'";
So the problem is it adds three value to each row automatically and but when I update, I need to update the order number 12 from 4th column not 7.

Single Query to fetch records from multiple tables with different columns

I have 2 different tables as want to get records in a single query. Currently, I am using 2 queries then merging the array result and then displaying the record. Following is my current code:
$db = JFactory::getDbo();
$query1 = "SELECT a.id as cId, a.title, a.parent_id,a.level FROM `categories` AS a WHERE ( a.title LIKE '%keyword%' )";
$result1 = $db->setQuery($query1)->loadObjectlist(); //gives selected records
$query2 = "SELECT b.id as indId, b.indicator , b.cat_id, b.subcat_id, b.section_id FROM `indicator` as b WHERE ( b.indicator LIKE '%keyword%' )";
$result2 = $db->setQuery($query2)->loadObjectlist(); //gives selected records
$_items = array_merge($result1,$result2); //then using $_items in php code to display the data
It is in Joomla however I just want to know how we can merge these 2 queries into one. I tried the following but it gives the result of first query from categories table.
(SELECT id as cId, title, parent_id,level, NULL FROM `categories` WHERE ( title LIKE '%birth%' ))
UNION ALL
(SELECT id as indId, indicator , cat_id, subcat_id, section_id FROM `indicator` WHERE ( indicator LIKE '%birth%' ))
Desired output:
+------+-------------+------------+--------+--------+----------------+--------+-----------+----------+
| cId | title | parent_id | level | indId | indicator | cat_id | subcat_id | section_id
+------+-------------+------------+--------+--------+----------------+--------+-----------+----------+
| 2874 | births | 2703 | 2 | null | null | null | null | null |
+------+-------------+------------+--------+--------+----------------+--------+-----------+----------+
| 13 | birth weight| 12 | 3 | null | null | null | null | null |
+------+-------------+------------+--------+--------+----------------+--------+-----------+----------+
| null | null | null | null | 135 | resident births| 23 | 25 | 1 |
+------+-------------+------------+--------+--------+----------------+--------+-----------+----------+
| null | null | null | null | 189 | births summary | 23 | 25 | 1 |
+------+-------------+------------+--------+--------+----------------+--------+-----------+----------+
This above output will help to get proper pagination records. I tried to use join but JOIN needs a common column in ON clause. Here, I want all the columns and their values. Basically I want to combine the 2 table records in one query. Any help would be appreciated
Here is an example,
There are a number of ways to do this, depending on what you really want. With no common columns, you need to decide whether you want to introduce a common column or get the product.
Let's say you have the two tables:
parts: custs:
+----+----------+ +-----+------+
| id | desc | | id | name |
+----+----------+ +-----+------+
| 1 | Sprocket | | 100 | Bob |
| 2 | Flange | | 101 | Paul |
+----+----------+ +-----+------+
Forget the actual columns since you'd most likely have a customer/order/part relationship in this case; I've just used those columns to illustrate the ways to do it.
A cartesian product will match every row in the first table with every row in the second:
> select * from parts, custs;
id desc id name
-- ---- --- ----
1 Sprocket 101 Bob
1 Sprocket 102 Paul
2 Flange 101 Bob
2 Flange 102 Paul
That's probably not what you want since 1000 parts and 100 customers would result in 100,000 rows with lots of duplicated information.
Alternatively, you can use a union to just output the data, though not side-by-side (you'll need to make sure column types are compatible between the two selects, either by making the table columns compatible or coercing them in the select):
> select id as pid, desc, '' as cid, '' as name from parts
union
select '' as pid, '' as desc, id as cid, name from custs;
pid desc cid name
--- ---- --- ----
101 Bob
102 Paul
1 Sprocket
2 Flange
In some databases, you can use a rowid/rownum column or pseudo-column to match records side-by-side, such as:
id desc id name
-- ---- --- ----
1 Sprocket 101 Bob
2 Flange 101 Bob
The code would be something like:
select a.id, a.desc, b.id, b.name
from parts a, custs b
where a.rownum = b.rownum;
It's still like a cartesian product but the where clause limits how the rows are combined to form the results (so not a cartesian product at all, really).
I haven't tested that SQL for this since it's one of the limitations of my DBMS of choice, and rightly so, I don't believe it's ever needed in a properly thought-out schema. Since SQL doesn't guarantee the order in which it produces data, the matching can change every time you do the query unless you have a specific relationship or order by clause.
I think the ideal thing to do would be to add a column to both tables specifying what the relationship is. If there's no real relationship, then you probably have no business in trying to put them side-by-side with SQL.
As #Sinto suggested the answer for union and dummy column names following is the whole correct query:
(SELECT id as cId, title, parent_id,level, NULL as indId, NULL as indicator , NULL as cat_id, NULL as subcat_id, NULL as section_id FROM `jm_categories` WHERE ( title LIKE '%births%' )) UNION ALL (SELECT NULL as cId, NULL as title, NULL as parent_id,NULL as level, id as indId, indicator , cat_id, subcat_id, section_id FROM `jm_indicator_setup` WHERE ( indicator LIKE '%births%' ))
We have to match the column names from both tables so that we get records as a combination.

Mysql max function with specific value

I have following table structure for tTable
id | version | parentId
1 | 1 | 1
2 | 1 | 2
3 | 2 | 2
4 | 1 | 3
5 | 2 | 3
6 | 3 | 3
If I execute
SELECT MAX(id) AS maxId, FROM tTable GROUP BY parentId
then it will return below result.
id | version | parentId
1 | 1 | 1
3 | 2 | 2
6 | 3 | 3
But here I would like to a little change in returned result for example I would like to have following result set with max() in tTable.
id | version | parentId
1 | 1 | 1
3 | 2 | 2
*5 | 2 | 3*
id = 5th record. I would like to get other two records with max(id) but want to get different id (I want to provide condition) for parentId = 3.
Is it possible in max() with any condition. I would like to preserve other max id but want only change in specific record?
Edit:
Here 5 (or may be 4) will be coming from dynamic variable. So in case of parentId = 3 there could be any value for id (may be 4 or 5 or 6).
You can use UNION ALL to get the result you want but that may not be a exact solution. thought of putting it once if it helps. See a demo here http://sqlfiddle.com/#!2/78cde/9
(SELECT MAX(id) AS maxId, version, parentId
FROM tTable
WHERE parentId != 3
GROUP BY parentId)
union all
(SELECT id as maxId, version, parentId
FROM tTable
WHERE parentId = 3
order by maxId desc
limit 1,1)

Make an SQL record inherit values from one or more records in the same table

Background
I have a MySQL table for which each record represents a region- and/or platform-specific version of an item. For any given item, there will be several versions; there's no primary key and mostly indexed columns.
I start with worldwide records, one for each platform-version of the item. Then I add records for any region-specific values, then add records for any country-specific values. The thing is that I only plan to add values that are unique to that region or country; in other words, all records are going to have null values because I don't want to enter repeated values, so I want records to inherit values from other records.
item | platform | region | country | date | price | [...]
1 | 1 | [WW] | null | 2013-04-01 | 100 |
1 | 2 | [WW] | null | 2013-04-01 | 100 |
1 | null | [EU] | null | 2013-04-20 | 80 |
1 | null | [UK] | null | null | 70 |
I plan to use PHP to display the relevant records for a given country. The thing is, I want to be able to combine/inherit values from that country's region record and the worldwide record. So the UK would have two total records: each one inheriting a platform value from the [WW] record, both inheriting the date value from [EU] record, and both having the price value from the [UK] record.
1 | 1 | [UK] | 2013-04-20 | 70
1 | 2 | [UK] | 2013-04-20 | 70
The question I want to know is there a solution/procedure/method of doing it in MySQL only? Or is the only way to do it is via PHP coding?
What you've requested
Please note this is NOT a real answer. It only outputs what you've asked in the question, but the logic here barely makes any sense so it is highly unlikely to be applicable for a real database.
SELECT a.item, b.platform, a.region, a.country, c.date, a.price FROM
(SELECT item, region, country, price FROM table WHERE platform IS NULL AND date IS NULL GROUP BY item) AS a
LEFT JOIN
(SELECT platform FROM table WHERE platform IS NOT NULL) AS b
ON a.item = b.item
LEFT JOIN
(SELECT date FROM table WHERE PLATFORM IS NULL AND date IS NOT NULL) AS c
ON a.item = c.item
Better Answer Here
A more organized and perhaps easier way (and still efficient if you don't go up more than 2 layers of parents) would be:
id | parent_id | item | platform | region | country | date | price | [...]
1 | null | 1 | 1 | [WW] | null | 2013-04-01 | 100 |
2 | null | 1 | 2 | [WW] | null | 2013-04-01 | 100 |
3 | 1 | 1 | null | [EU] | null | 2013-04-20 | 80 |
4 | 2 | 1 | null | [UK] | null | null | 70 |
SELECT items.*,
parent_items.platform AS pa_platform, parent_items.region AS pa_region, parent_items.country AS pa_country, parent_items.date AS pa_date, parent_items.price AS pa_price,
grandparent_items.platform AS gpa_platform, grandparent_items.region AS gpa_region, parent_items.country AS gpa_country, parent_items.date AS gpa_date, parent_items.price AS gpa_price
FROM items
LEFT JOIN
items AS parent_items
ON items.parent_id = parent_items.id
LEFT JOIN
items AS grandparent_items
ON parent_items.parent_id = grandparent_items.id
Then you have the choice of either using app level logic to display the closest non-empty value:
$region = $result['region'] ? $result['region'] : ($result['pa_region'] ? $result['pa_region'] : $result['gpa_region']);
or you can modify the above SQL to chose the first non-null value:
SELECT COALESCE(items.region, parent_items.region, grandparent.region) AS region, COALESCE(items.platform, parent_items.platform, grandparent.platform) AS platform, ...
Now... If you are actually going to add rows with dependencies
Why not simply make different tables?
Suppose you'll have a price for each region, each platform, each country, and you know the order of precedence (let's say as an example region > country > platform):
Why not make a base table (tbl_platform) with fields id/item/platform/date/price
then a country table (tbl_country) with fields id/platform_id/date/price
then a region table (tbl_region) with fields id/country_id/date/price
If you want the base info, just grab it directly from the base table, and if you want the region info, join the region to the country, then to the base.

Updating column so that it contains the row position

This is the content table:
ContentID | CategoryID | Position | Other1 | Other2
===================================================
1 | 1 | NULL | abcd | efgh
2 | 1 | NULL | abcd | efgh
3 | 1 | NULL | abcd | efgh
4 | 2 | NULL | abcd | efgh
5 | 2 | NULL | abcd | efgh
6 | 2 | NULL | abcd | efgh
These are the queries I'll be running:
SELECT ContentID FROM content WHERE CategoryID = 1 ORDER BY Position
SELECT ContentID FROM content WHERE CategoryID = 2 ORDER BY Position
Now I want to implement move up, move down, move to top and move to bottom function for content. All I need to do is to populate the Position column with numbers:
ContentID | CategoryID | Position
=================================
1 | 1 | 1
2 | 1 | 2
3 | 1 | 3
4 | 2 | 1
5 | 2 | 2
6 | 2 | 3
Is it possible to achieve this via single query in MySQL? Something like:
UPDATE content
SET Position = <ROW_NUMBER>
WHERE CategoryID = 1
ORDER BY Position
UPDATE content
SET Position = <ROW_NUMBER>
WHERE CategoryID = 2
ORDER BY Position
This should work
update
content,
(
select
#row_number:=ifnull(#row_number, 0)+1 as new_position,
ContentID
from content
where CategoryID=1
order by position
) as table_position
set position=table_position.new_position
where table_position.ContentID=content.ContentID;
But I would prefer to apply this first, to unset user defined variable
set #row_number:=0;
Added by Mchl:
You can do that in one statement like this
update
content,
(
select
#row_number:=ifnull(#row_number, 0)+1 as new_position,
ContentID
from content
where CategoryID=1
order by position
) as table_position,
(
select #row_number:=0
) as rowNumberInit
set position=table_position.new_position
where table_position.ContentID=content.ContentID;
Here is the solution that worked for me (hope it helps someone):
-- The following query re-populates the "Position" column with sequential numbers so that:
-- a) sequence is reset to 1 for each "group"
-- b) sequence is based on row number relative to each group depending on how ORDER BY is specified
-- c) sequence does not disturb the original order but
-- c.a) fixes NULLs so that they are moved to top
-- c.b) fixes duplicate position values depending on how ORDER BY is specified
-- ContentID is the primary key
-- CategoryID is a foreign key
-- Position column contains relative position of a record
SET #current_group = NULL;
SET #current_count = NULL;
UPDATE
content
SET Position = CASE
WHEN #current_group = CategoryID THEN #current_count := #current_count + 1
WHEN #current_group := CategoryID THEN #current_count := 1
END
ORDER BY CategoryID, Position -- <Column 3>, <Column 4>, ...
I think it would be very tedious to run additional queries all the time when you do some operations on the table. I would create a trigger that fires every time you want to insert/update something in the table.
In your case, a BEFORE UPDATE and BEFORE INSERT trigger would be advisable. If you also want to keep it clean after the deletion of an etntry, add an AFTER DELETE trigger.
Initial:
UPDATE content as uc
SET Position = (
SELECT count(*)
FROM content as sc
WHERE sc.CategoryId = uc.CategoryId AND sc.Position is not null)
WHERE uc.Position is null
ORDER BY uc.ContentId
Before insert:
UPDATE content
SET Position = Position+1
WHERE Position >= newPos AND CategoryId = newCat

Categories