return MIN(DATE) and other existing date - mysql

I want to return all the minimum dates of every single clients in the table and display it as "FIRST" if it is the MIN date, and IF its not, it will return "OTHER"
this is my query
SELECT TRANS_DATE, IF(TRANS_DATE= MIN(TRANS_DATE), 'FIRST', 'OTHER') AS TR_CODE
FROM `posthis`
WHERE datepost IS NOT NULL
My query only returns the MIN()
this is the result that I need
CLIENTID TRANS_DATE TR_CODE
02-00002234 2002-02-01 FIRST
02-00002234 2002-02-02 OTHER
02-00002234 2002-02-03 OTHER
02-00002235 2003-01-03 FIRST
02-00002235 2003-01-05 OTHER
02-00002235 2003-01-06 OTHER
02-00002236 2003-01-03 FIRST
02-00002236 2003-01-04 OTHER
02-00002236 2003-01-13 OTHER

Using MIN() as an analytic function would really come in handy for your problem. But since MySQL does not support this, we can use a join instead. In the query below, I LEFT JOIN the posthis table to a subquery which identifies the earliest date for each client. Should a record in posthis match to this subquery, we label it with a code 'FIRST', otherwise we label 'OTHER'.
SELECT
t1.CLIENTID,
t1.TRANS_DATE,
CASE WHEN t2.CLIENTID IS NOT NULL THEN 'FIRST' ELSE 'OTHER' END AS TR_CODE
FROM posthis t1
LEFT JOIN
(
SELECT CLIENTID, MIN(TRANS_DATE) AS MIN_TRANS_DATE
FROM posthis
GROUP BY CLIENTID
) t2
ON t1.CLIENTID = t2.CLIENTID AND
t1.TRANS_DATE = t2.MIN_TRANS_DATE

Related

How to select max / distinct record in MySQL using a deleted_at column

I am trying to select distinct rows under the following two rules:
If its deleted_at date is null then it is the most recent record, select it
If it is the latest deleted_at date (and there's not a record with a NULL), it is also the most recent record, select it
Consider this table:
The result I am looking for would be:
I'm using MySQL mariaDB v10.1.33 which does not have all the functions I am use to.
NULL was being ignored so I use a
coalesce(fc.deleted_at, CURRENT_TIMESTAMP())
to trick it into being the latest date. That way I can use max() function to select it. However, when I use this it is mismatching the data in the rows! i.e. this:
SELECT max(coalesce(fc.deleted_at, CURRENT_TIMESTAMP())), folder_id, code
FROM folder_code fc
WHERE fc.folder_id = 5683
returns:
I did some reading and this is a common problem where it seems to be ordering and selecting the max of each column independent of the row it is associated with and there are suggestions to use group by and order by to overcome it. However when I do this I get the same result i.e. this also returns the same as above:
SELECT max(coalesce(fc.deleted_at, CURRENT_TIMESTAMP())) as maxdeleteddate, fc.folder_id, fc.code
FROM folder_code fc
WHERE fc.folder_id = 5683
GROUP BY fc.folder_id
ORDER BY maxdeleteddate desc
How to I achieve my desired result?
Thank you
This is how I would do it:
SELECT f1.*
FROM folder f1
INNER JOIN (
SELECT folder_id,
NULLIF(MAX(IF(deleted_at IS NULL,NOW(),deleted_at)),NOW()) AS deleted_at
FROM folder
GROUP BY folder_id
) f2 ON f2.folder_id = f1.folder_id AND f2.deleted_at <=> f1.deleted_at
And here's a fiddle: https://www.db-fiddle.com/f/wzCYktpavBNnJu2uejPpe9/1
The idea is to get the groupwise-max, then join your table against itself. If you simply group the rows, you are not guaranteed to get the correct values for non-aggregated columns.
There is also a trick with deleted_at column, using NOW() if it's null, then using NULLIF() to set it back to NULL for the join.
This approach also benefits from the fact that it potentially uses indexes if they exist.
If you are using MySQL 8+, then you may use ROW_NUMBER here:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY folder_id
ORDER BY -ISNULL(deleted_at), deleted_at DESC) rn
FROM folder_code
)
SELECT folder_id, code, deleted_at
FROM cte
WHERE rn = 1;
Demo
The ORDER BY clause used in the call to ROW_NUMBER places all records having a NULL deletion date after those records have a date, for each group of folder_id records. Then, the second level of sorting places more recent deletion date records first. This means that for those folders have a NULL record, it would appear first, otherwise the most recent record would appear first.
Here is an old school solution which might also work:
SELECT f1.folder_id, f1.code, f1.deleted_at
FROM folder_code f1
INNER JOIN
(
SELECT folder_id,
CASE WHEN COUNT(*) = COUNT(deleted_at)
THEN MAX(deleted_at) END AS max_deleted_at
FROM folder_code
GROUP BY folder_id
) f2
ON f1.folder_id = f2.folder_id AND
(f1.deleted_at = f2.max_deleted_at OR
(f1.deleted_at IS NULL AND f2.max_deleted_at IS NULL));
Demo
One way to get the latest date is to make sure there is no later date. Your approach to replace NULL with a high date is good and can be used for this.
select *
from folder_code fc
where not exists
(
select *
from folder_code fc2
where fc2.folder_id = fc.folder_id
and coalesce(fc2.deleted_at, date '9999-12-31') > coalesce(fc.deleted_at, date '9999-12-31')
);
You can try below - using correlated subquery
DEMO
select * from t1 a
where coalesce(deleted_at,CURRENT_TIMESTAMP()) =
(select max(coalesce(deleted_at,CURRENT_TIMESTAMP())) from t1 a1 where a.folder_id=a1.folder_id)
OUTPUT:
older_id code deleted_at
5333 12VA1 2019-09-27
5683 12SR1-X

Select distinct shou

I'm stuck with this problem for days and still can't think of a solution.Maybe i'm just making it too complex.
So my query looks like this.
SELECT distinct Cast(Table1.Date as Date) AS 'Date', Concat(Round(SUM((ISNULL(Price.Morning,0) + ISNULL(Price.Day,0) + ISNULL(Price.Evening,0))*Tickets.Count),2),' €') AS 'Total'
FROM Price,Tickets,Table1
WHERE Tickets.Price_ID = Price.Price_ID AND
Tickets.Table1_ID = Table1.Table1_ID
GROUP BY Date
;
The query should display distinct dates and total SUM should be displayed that was earned in the specific date.
As you can see there still are matching dates
EDIT. I don't know if i understood the criticism correctly but i applied these changes.
SELECT distinct Cast(Tabl1.Date as Date) AS 'Date', Concat(Round(SUM((ISNULL(Price.Morning,0) + ISNULL(Price.Day,0) + ISNULL(Price.Evening,0))*Tickets.Count),2),' €') AS 'Total'
FROM Table1
JOIN Tickets ON Tickets.Table1_ID = Table1.Table1_ID
JOIN Price ON Tickets.Price_ID = Price.Price_ID
GROUP BY Date
;
And i also understand that i need to provide more information for solution.
All the ..Concat(Round(SUM((ISNULL(Price.Morning,0) + ISNULL(Price.Day,0) + ISNULL(Price.Evening,0))*Tickets.Count),2),' €') AS 'Total' .. Is necessary because in the database there are Null values, thatš why there i use ISNULL function and 0 in case it is NULL because otherwise it brakes all the calculations.You can see that table here: I edited the column names so you could see(table itself is in different language)
(The column without name is not required for this)
Use explicit join not coma separated old join method and use subquery for format of total . No need distinct
select 'Date', concat(total,' €') from
(
SELECT Cast(Table1.Date as Date) AS 'Date',
SUM(Price.Morning+Price.Day+Price.Evening) as total
FROM Price join
Tickets on Tickets.Price_ID = Price.Price_ID
join Table1 on Tickets.Table1_ID = Table1.Table1_ID
GROUP BY Date
) as t

Return column value without aggregation for grouped query

Ok, question sound very confusing, I just can't come up with better title.
Here is my query:
SELECT TS.LocationKey, TA.TrailerKey, MAX(TS.ArrivedOnLocal) MaxArrivedOnLocal
FROM dbo.DSPTripStop TS
INNER JOIN dbo.DSPTripAssignment TA ON TS.TripStopKey = TA.ToTripStopKey AND TA.TrailerKey IS NOT NULL
GROUP BY TS.LocationKey, TA.TrailerKey
Query returns list of trailers with locations and last time they were dropped at that location. This is what I need. MAX(time) for location is a goal.
But I'd like to also know which DSPTripStop.TripStopKey this MAX() time happened on.
I can't group by this value. I understand that it is not defined (can be multiple values for the same time). For my purpose ANY random will work. But I can't find any better way then joining second time by MaxArrivedOnLocal to get what I need.
SQL Server already "sees" this data when MAX() aggregated, any way to pull it in this query?
I think this is what you want. Rather than doing a group by, you partition instead, number the rows, then take the top 1
WITH cte AS
(
SELECT TS.LocationKey,
TA.TrailerKey,
TS.ArrivedOnLocal,
TS.TripStopKey,
ROW_NUMBER() OVER (PARTITION BY TS.LocationKey, TA.TrailerKey ORDER BY ArrivedOnLocal DESC) rn
FROM dbo.DSPTripStop TS
INNER JOIN dbo.DSPTripAssignment TA ON TS.TripStopKey = TA.ToTripStopKey AND TA.TrailerKey IS NOT NULL
)
SELECT LocationKey,
TrailerKey,
ArrivedOnLocal,
TripStopKey
FROM cte
WHERE rn = 1
If you need any random value for DSPTripStop.TripStopKey then you can use MAX itself as this will return the latest TripStopKey.
SELECT
TS.LocationKey,
TA.TrailerKey,
MAX(TS.ArrivedOnLocal) MaxArrivedOnLocal,
MAX(TS.TripStopKey)
FROM dbo.DSPTripStop TS
INNER JOIN dbo.DSPTripAssignment TA
ON TS.TripStopKey = TA.ToTripStopKey
AND TA.TrailerKey IS NOT NULL
GROUP BY TS.LocationKey, TA.TrailerKey

MySQL return 'empty result' even with coalesce

I have some trouble with MySQL.
Here is the query I use:
SELECT
COALESCE(SUM(`a`.`battles`), 0) AS `battles`
FROM
`account_stats` AS `a`
WHERE
`a`.`account_id` = 12345
GROUP BY
`a`.`account_id`
The Table account_stats is not empty, but has no row with account_id = 12345.
I want that MySQL returns 0 battles instead of Empty set. But even with COALSECE or IFNULL it returns Empty set.
When I remove the GROUP BY everything works fine, but I need it to calculate the SUM of battles.
Is there a way to workaround this problem?
If you only want information on one account, you can use conditional aggregation if you want the query to return a row with the value of 0:
SELECT SUM(CASE WHEN a.account_id = 12345 THEN a.battles ELSE 0 END) as battles
FROM account_stats a;
If the table is not empty, then you don't need coalesce().
If you have an index on account_id and the table is big, the following would probably be more efficient because the subquery would use the index and the rest of the query would be manipulating a single row:
SELECT x.account_id, COALESCE(SUM(a.battles), 0) as battles
FROM (SELECT 12345 as account_id
) x LEFT JOIN
(SELECT a.account_id, SUM(a.battles) as battles
FROM account_stats a
WHERE a.account_id = 12345
) a
ON x.account_id = a.account_id;

Query always returns one empty row?

This query always returns at least one row even if none is found
(
SELECT accounting.time, enclosure.enc_id_, enclosure.txt, accounting.amount AS sum, SUM(ROUND(vatcode.percent/(100+vatcode.percent)*accounting.amount)) AS sum_vat
FROM accounting
INNER JOIN enclosure ON enclosure.id=accounting.enc_id
LEFT JOIN vatcode ON vatcode.id=accounting.vatcode_id
WHERE accounting.account_id='10'
)
UNION (
SELECT accounting.time, enclosure.enc_id_, enclosure.txt, accounting.amount*-1 AS sum, NULL AS sum_vat
FROM accounting
INNER JOIN enclosure ON enclosure.id=accounting.enc_id
WHERE accounting.accountoff_id='10'
) ORDER BY time
I know that the error occurs in the second select here ... , NULL AS sum_vat.. If I remove it I get an error about not having the same statements in both select? How can this be solved?
return
Array
(
[time] => 0
[enc_id_] => 0
[txt] =>
[sum] => 0
[sum_vat] =>
)
If you use an aggregate without a group by, the aggregate will run over the entire table, always returning a single row. For example,
select max(price) from items where group = 'Servers'
returns a single row with the highest price. MySQL is the only database that allows other columns without a group by:
select name, max(price) from items where group = 'Servers'
But confusingly, it would just put a random value in name column; the name here won't be the name of the highest priced server.
In your case, the obvious solution is to add a group by to the first part of the union:
SELECT accounting.time, enclosure.enc_id_, enclosure.txt, accounting.amount sum,
SUM(ROUND(vatcode.percent/(100+vatcode.percent)*accounting.amount)) sum_vat
FROM accounting
INNER JOIN enclosure ON enclosure.id=accounting.enc_id
LEFT JOIN vatcode ON vatcode.id=accounting.vatcode_id
WHERE accounting.account_id='10'
GROUP BY accounting.time, enclosure.enc_id_, enclosure.txt, accounting.amount