ORDER BY with bool > date > null in SQLite - sqlite

I have a loooooooooooong SELECT ending with a ORDER BY that can include the following values :
checked (1 or 0)
date (YYYY-MM-DD)
time (HH:MM:SS)
I'd like to order the result of the query the following way :
|__checked = 0
| |__ date ASC
| |__ time ASC
| |__ date && time are null
|__checked = 1
|__ date ASC
|__ time ASC
|__ date && time are null
For now, I got something simple like "ORDER BY i1.checked, date, time" but the trouble is with this the items with empty date and times stay on the top.
Here is the whole query if you ever think you need all the data to find a proper solution. Don't scream.
SELECT i1._id AS _id, i1.title AS title, i1.checked AS checked,
// count the children (-1 is because the closure table include a relation between any item and itself)
(count( c1.item_id ) - 1) AS children_count,
// count the unchecked children
(SELECT (COUNT(DISTINCT i2._id) )
FROM item AS i2 JOIN closure AS c2 ON (i2._id = c2.item_id)
WHERE c2.ancestor_id = i1._id
AND i2.checked = 0
AND c2.item_id NOT IN (0, i1._id)) AS unchecked_children_count,
// get the closest deadlines among the children :
// if there is a date but no time, time is set to 00:00:00
// if there is a time but not date, date is set to today
// if there is no date nor time, both are set to an empty string
// date
ifnull(nullif((SELECT ifnull(nullif(i3.date, ""), date()) AS subdate
FROM item AS i3 JOIN closure AS c3 ON (i3._id = c3.item_id)
WHERE c3.ancestor_id = i1._id
AND (i3.date OR i3.time)
ORDER By subdate, ifnull(nullif(i3.time, ""), "00:00:00")
LIMIT 1), ""), "") AS date,
// time
ifnull(nullif((SELECT ifnull(nullif(i4.time, ""), "00:00:00") AS subtime
FROM item AS i4 JOIN closure AS c4 ON (i4._id = c4.item_id)
WHERE c4.ancestor_id = i1._id
AND (i4.date OR i4.time)
ORDER By ifnull(nullif(i4.date, ""), date()), subtime
LIMIT 1), ""), "") AS time
FROM item AS i1 JOIN closure AS c1 ON ( i1._id = c1.ancestor_id )
WHERE i1.parent_id = 0
AND c1.item_id != 0
GROUP BY c1.ancestor_id
ORDER BY i1.checked, date, time
The items are organised in a tree with a parent_id and a closure table.

ORDER BY il.checked,
CASE WHEN date IS NULL AND time IS NULL THEN 1 ELSE 0 END,
date, time

You could create an extra temp column in a wrapped select around all your sql to allow you to add an artificial order.
SELECT
EACH_COLUMN
, ...
, CASE WHEN DATECol IS NULL AND TimeCol is NULL THEN 0
WHEN TimeCol is NULL THEN 1
WHEN DATECol IS NULL THEN 2
ELSE 3 END As ORDERCOL...
FROM
(
INNER MESS OF ALL THE STUFF IN YOUR QUERY
)
ORDER BY
ORDERCol DESC --Preserves order of BothCols Populated, Null Dates Next, Null Times Next, Both Nulls Last
, Checked
, Date
, Time

Related

I can't run this query with MERGE in Teradata

This query worked perfectly until the moment I went in for vacations, now itdoes not run anymore and does not merge, dont know what it can be
MERGE INTO STG_FATO_MACRO_GESTAO AS FAT
USING(SELECT DISTINCT
COD_EMPRESA
,FUN.MATRICULA AS FUN_MAT
,APR.MATRICULA AS APR_MAT
,FUN.CPF AS FUN_CPF
,APR.CPF AS APR_CPF
,APR.DAT_DESLIGAMENTO
,YEAR(APR.DAT_DESLIGAMENTO)*100+MONTH(APR.DAT_DESLIGAMENTO) AS DESL
,FUN.DATA_ADMISSAO
,YEAR(FUN.DATA_ADMISSAO)*100+MONTH(FUN.DATA_ADMISSAO) AS ADM
, CASE WHEN YEAR(APR.DAT_DESLIGAMENTO)*100+MONTH(APR.DAT_DESLIGAMENTO) <= YEAR(FUN.DATA_ADMISSAO)*100+MONTH(FUN.DATA_ADMISSAO) THEN 1 ELSE 0 END AS ADMITIDO
,CASE WHEN FUN.DATA_ADMISSAO <= (APR.DAT_DESLIGAMENTO + INTERVAL '90' DAY) THEN 1 ELSE 0 END AS APR_90
FROM (SELECT CPF,DATA_ADMISSAO, MATRICULA, COD_EMPRESA FROM DIM_FUNCIONARIO
WHERE PROFISSAO NOT LIKE '%APRENDIZ%') AS FUN
INNER JOIN (SELECT DISTINCT
CPF,DAT_DESLIGAMENTO,MATRICULA
FROM HST_APRENDIZ
WHERE FLAG_FECHAMENTO = 2
AND DAT_DESLIGAMENTO IS NOT NULL) AS APR
ON FUN.CPF = APR.CPF) AS APR_90
ON FAT.COD_EMPRESA = APR_90.COD_EMPRESA
AND FAT.MATRICULA = APR_90.FUN_MAT
AND APR_90.APR_90 = 1
AND APR_90.ADMITIDO = 1
WHEN MATCHED THEN
UPDATE SET APRENDIZ_EFETIVADO_90 = 1
;
when running this query returns me this error:
"The search condition must fully specify the Target table primary index and partition column(s) and expression must match INSERT specification primary index and partition column(s). "

SQL: Sum based on specified dates

Thanks again for the help everyone. I went with the script below...
SELECT beginning, end,
(SELECT SUM(sale) FROM sales_log WHERE date BETWEEN beginning AND `end` ) AS sales
FROM performance
and I added a salesperson column to both the performance table and sales_log but it winds up crashing DB Browser. What is the issue here? New code below:
SELECT beginning, end, salesperson
(SELECT SUM(sale) FROM sales_log WHERE (date BETWEEN beginning AND end) AND sales_log.salesperson = performance.salesperson ) AS sales
FROM performance
I believe that the following may do what you wish or be the basis for what you wish.
WITH sales_log_cte AS
(
SELECT substr(date,(length(date) -3),4)||'-'||
CASE WHEN length(replace(substr(date,instr(date,'/')+1,2),'/','')) < 2 THEN '0' ELSE '' END
||replace(substr(date,instr(date,'/')+1,2),'/','')||'-'||
CASE WHEN length(substr(date,1,instr(date,'/') -1)) < 2 THEN '0' ELSE '' END||substr(date,1,instr(date,'/') -1) AS date,
CAST(sale AS REAL) AS sale
FROM sales_log
),
performance_cte AS
(
SELECT substr(beginning,(length(beginning) -3),4)||'-'||
CASE WHEN length(replace(substr(beginning,instr(beginning,'/')+1,2),'/','')) < 2 THEN '0' ELSE '' END
||replace(substr(beginning,instr(beginning,'/')+1,2),'/','')||'-'||
CASE WHEN length(substr(beginning,1,instr(beginning,'/') -1)) < 2 THEN '0' ELSE '' END||substr(beginning,1,instr(beginning,'/') -1)
AS beginning,
substr(`end`,(length(`end`) -3),4)||'-'||
CASE WHEN length(replace(substr(`end`,instr(`end`,'/')+1,2),'/','')) < 2 THEN '0' ELSE '' END
||replace(substr(`end`,instr(`end`,'/')+1,2),'/','')||'-'||
CASE WHEN length(substr(`end`,1,instr(`end`,'/') -1)) < 2 THEN '0' ELSE '' END||substr(`end`,1,instr(`end`,'/') -1)
AS `end`
FROM performance
)
SELECT beginning, `end` , (SELECT SUM(sale) FROM sales_log_cte WHERE date BETWEEN beginning AND `end` ) AS sales
FROM performance_cte
;
From your data this results in :-
As can be seen the bulk of the code is converting the dates into a format (i.e. YYYY-MM-DD) that is usable/recognisable by SQLite for the BETWEEN clause.
Date And Time Functions
I don't believe that you want a join between performance (preformance_cte after reformatting the dates) and sales_log (sales_log_cte) as this will be a cartesian product and then sum will sum all the results within the range.
The use of end as a column name is also awkward as it is a KEYWORD requiring it to be enclosed (` grave accents used in the above).
The above works by using 2 CTE's (Common Table Expresssions), which are temporary tables who'd life time is for the query in which they are used.
The first sales_log_cte is simply the sales_log table but with the date reformatted. The second, likewise, is simply the performace table with the dates reformatted.
If the tables already has suitable date formatting then all of the above could simply be :-
SELECT beginning, `end` , (SELECT SUM(sale) FROM sales_log WHERE date BETWEEN beginning AND `end` ) AS sales FROM performance;

Oracle - Connect By Clause Required in query block

I would like to utilize the Month Column in the below syntax in a Case Statement. When I create a sub query I receive the Oracle error 01788 Connect By Clause Required in query block. How can one utilize the Month column in the case statment in the subquery?
TO_CHAR(ADD_MONTHS(TRUNC(StartDate, 'MM'), LEVEL - 1), 'YYYYMM') AS Month
Query below:
SELECT
CASE
WHEN first_assgn_dt_YYYYMM <= Month
THEN 0
WHEN EndDate < LAST_DAY(EndDate) AND EndDate != sysdate
AND LEVEL = 1 + MONTHS_BETWEEN(TRUNC(EndDate,'MM'),TRUNC(StartDate,'MM'))
THEN 0
ELSE 1
END AS active_at_month_end
FROM (
WITH
ActiveMemberData (ID,StartDate,EndDate,first_assgn_dt,first_assgn_dt_YYYYMM) AS (
SELECT DISTINCT
x.ID,
TRUNC(x.start_dt) AS StartDate,
CASE WHEN TRUNC(X.END_DT) = '1-JAN-3000' THEN SYSDATE ELSE TO_DATE(X.END_DT) END AS EndDate,
x.first_assgn_dt,
TO_CHAR(first_assgn_dt,'YYYYMM') AS first_assgn_dt_YYYYMM
FROM X
LEFT JOIN D ON X.MID = D.ID
WHERE 1=1
)
--------------------------------------------------
SELECT DISTINCT
ID,
first_assgn_dt,
first_assgn_dt_YYYYMM,
StartDate,
TO_CHAR(StartDate,'YYYYMM') AS StartDate_YYYYMM,
EndDate,
TO_CHAR(ADD_MONTHS(TRUNC(StartDate, 'MM'), LEVEL - 1), 'YYYYMM') AS Month,
LAST_DAY(EndDate) AS LastDayOfMonth
FROM ActiveMemberData
WHERE 1=1
------------------------------------------------------------------------------------
CONNECT BY LEVEL <= 1 + MONTHS_BETWEEN(TRUNC(EndDate,'MM'), TRUNC(StartDate,'MM'))
AND PRIOR ID = ID AND PRIOR STARTDATE = STARTDATE
AND PRIOR sys_guid() IS NOT NULL
) Z
WHERE 1=1
ORDER BY
ID,
Month
That has nothing to do with trying to refer to Month from the inline view; that is fine. It's the separate reference to level that is causing the error.
If you want to be able to see the level from your inline view in the outer query, as you are with this line:
AND LEVEL = 1 + MONTHS_BETWEEN(TRUNC(EndDate,'MM'),TRUNC(StartDate,'MM'))
then you have to include it in the select list - with an alias - and then refer to that alias:
SELECT
CASE
...
AND LEVEL_ALIAS = 1 + MONTHS_BETWEEN(TRUNC(EndDate,'MM'),TRUNC(StartDate,'MM'))
...
FROM (
...
SELECT DISTINCT
LEVEL as LEVEL_ALIAS,
ID,
...
You can call the alias whatever you want, of course; you just can't use the reserved word level.
Anything you want visible in the outer query always has to be in the inline view's select list - but usually you can keep the original column name; you have to use an alias for an expression or a pseucocolumn though, which is the case here.
You don't have to use an alias for the reserved word. Just add double quotes and capitilise it i.e. "LEVEL"

using doctrine and querybuilder, between two date

I want to retrieve some data using queryBuilder, the condition is to use a datetime field on parent table and pass it to where condition, the child table have two date start and end, the whole condition is to select parent row depend on date between the two date in child table
the code is:
$qb->select('parent,child')
->from($this->class, 'parent')
->innerJoin('parent.childTable', 'child')
//other join
// and where
//CONDITION -> startdate <= $date AND enddate >= $date
$betweenDates = $qb->expr()->andX(
$qb->expr()->gt("child.dateStart", "parent.datetime"),
$qb->expr()->lt("child.dateEnd", "parent.datetime")
);
//CONDITION 4 -> enddate == NULL
$endDateNull = $qb->expr()->isNull( 'child.dateEnd');
$dates = $qb->expr()->orX($betweenDates, $endDateNull);
// take care of dateEnd if it's null
$qb->expr()->andX($dates);
the problem is the where condition not work and the select still give me childs not respect date condition
any help would be greatly appreciated.

SQLITE Edge detection

I have a given SQLIt database (so no chance to do it in a better way):
CREATE TABLE `history` (
`TIMESTAMP` TIMESTAMP,
`DEVICE` `enter code here`varchar(32),
`TYPE` varchar(32),
`EVENT` varchar(512),
`READING` varchar(32),
`VALUE` varchar(32),
`UNIT` varchar(32)
);
In this table I have for example the following data:
DEVICE VALUE
d1 1
d5 500
d2 10
d1 2 <--
d5 501
d1 100 <---
I want to figure out for the device "d1" all timestamps where the difference between the last value and the current value is > 10
I have absolutly no idea how to do this with SQL
thank you
To get the last value for the current timestamp T, you would use a query like this:
SELECT Value
FROM History
WHERE Device = '...'
AND Timestamp < T
ORDER BY Timestamp DESC
LIMIT 1;
You can then use this as a correlated subquery in your query:
SELECT Timestamp
FROM History
WHERE Device = 'd1'
AND ABS((SELECT Value
FROM History AS last
WHERE Device = 'd1' -- or Device = History.Device
AND last.Timestamp < History.Timestamp
ORDER BY Timestamp DESC
) - Timestamp) > 10;

Resources