Sqlite3 order by not working for union - sqlite

I have this simplified example:
CREATE TABLE test1 (
id INTEGER NOT NULL
PRIMARY KEY AUTOINCREMENT,
status TEXT NOT NULL
DEFAULT 'waiting');
CREATE TABLE test2 (
id INTEGER NOT NULL
PRIMARY KEY AUTOINCREMENT,
status TEXT NOT NULL
DEFAULT 'waiting');
And I run this query:
SELECT status
FROM test1
UNION
SELECT status
FROM test2
ORDER BY CASE status
WHEN 'accepted' THEN 1
WHEN 'invited' THEN 2
WHEN 'waiting' THEN 3
WHEN 'cancelled' THEN 4
ELSE 5
END
With hopes of getting combined ordering of 1-5 based on textual content of the field.
However, I get this error:
Error while executing query: 1st ORDER BY term does not match any column in the result set
When I remove either part of the UNION, the query works fine, so it's not related to the "case when" construct.
As far as I can see, both union parts have the same column named 'status', so I don't understand why I can't order the union.
It works in mysql, but I want to get it working in sqlite as well, if possible...

The documentation says:
If the SELECT is a compound SELECT, then ORDER BY expressions that are not aliases to output columns must be exactly the same as an expression used as an output column.
So you have to make the ordering expression an output column:
SELECT status,
CASE ... END AS order_me
FROM test1
UNION ALL
SELECT status,
CASE ... END
FROM test2
ORDER BY order_me
To avoid the duplication, you could use a subquery:
SELECT status
FROM (SELECT status
FROM test1
UNION ALL
SELECT status
FROM test2)
ORDER BY CASE status ...
END

Related

SQLite UNION right only if left is empty

In SQLite there is no such function to UNION only if left expression results empty set.
Consider these as two base tables test and test2:
create table test(
id integer not null primary key,
val integer not null
);
insert into test values(1, 10);
insert into test values(2, 20);
insert into test values(3, 30);
create table test2(
id integer not null primary key,
val integer not null
);
insert into test2 values(1, 100);
insert into test2 values(2, 200);
insert into test2 values(3, 300);
I have this simple query:
select * from test
union
select * from test2;
I want this to always query the first select and the second if (and only if) the first gives empty result.
To illustrate:
select * from test
union
select * from test2;
This shall return all rows from test, and then quit: don't touch test2 at all.
Another sample:
select * from test where val > 50
union
select * from test2;
First query gives empty results, move on and do the second select, shall result all rows from test2.
I want this to be as fast as possible => therefore I don't want to add a subquery for the second select.
Here is the playground.
A SQL SELECT is declarative. It expresses what you want, not how to obtain it. It is up to the db engine to convert it to a query plan. Some SQL dialects allow to hint the engine to do things a certain way, but not SQLite.
The only solution is to query the two part from an imperative language (python, java, C#, ...) that allows you to implements the do something first and check result then do something else
I don't want to add a subquery for the second select.
Well, the two queries are functionally independent in the query, and there is no way for the second query to tell that the first one came up empty, unless you use a subquery.
So, you would typically use a not exists condition in the second query, with a subquery that matches the first query.
So:
select * from test
union all
select * from test2 where not exists (select 1 from test)
Or if there is a where clause in the first query:
select * from test where val > 50
union all
select * from test2 where not exists (select 1 from test where val > 50)

Filter table based on Table Valued Parameter and Insert TVP value into Select

I have a TVP with two fields.
sentence_id is the filter to select records from the table and this works perfectly. The TVP also includes a keyword. The TVP looks like this:
Create TYPE [dbo].[sentence_id_list2] AS TABLE(
[sentence_id] [nvarchar](50) NOT NULL,
[keyword] [nvarchar](50)
)
I want to pass that keyword for the same sentence_id in the result so it looks like this:
Sentence_Identifier,Keyword,Sentence
123, curious, hello donna, i have a curious problem
Where sentence_id passed in from the TVP is 123, keyword is curious.
This is the stored procedure I have, just can't figure out how to include the keyword in the result.
ALTER Procedure [dbo].[chat_Get_Sentences_Table_Value_Parameter]
#sentence_keys [dbo].[sentence_id_list2] READONLY
AS
SELECT TOP (100) PERCENT dbo.chat_All_Records_Sentence_Details.Sentence_Identifier,
dbo.chat_All_Records_Sentence_Details.Sentence,
-- how do I write this to insert the keyword from the TVP into the select?
(SELECT keyword FROM #sentence_keys) AS Keyword
FROM dbo.chat_All_Records_Sentence_Details
WHERE (dbo.chat_All_Records_Sentence_Details.Sentence_Identifier
IN (SELECT sentence_id FROM #sentence_keys))
Instead of using IN simpy use an INNER JOIN:
SELECT d.Sentence_Identifier,
d.Sentence,
sk.keyword
FROM chat_All_Records_Sentence_Details AS d
INNER JOIN #sentence_keys AS sk
ON sk.sentence_id = d.Sentence_Identifier;
I have removed TOP 100 PERCENT since this would be optimised away anyway, and also used aliases so that your identifiers are not so long.

Insert into Table with the first column being a Sequence

I am trying to use an Insert, Sequence and Select * to work together.
INSERT INTO BRK_INDV
Select * from (Select brk_seq.NEXTVAL as INDV_SEQ, a.*
FROM (select to_date(to_char(REQUEST_DATETIME,'DD-MM-YYYY'),'DD-MM-YYYY') BUSINESS_DAY, to_char(REQUEST_DATETIME,'hh24') src_hour,
CASE tran_type
WHEN 'V' THEN 'Visa'
WHEN 'M' THEN 'MasterCard'
ELSE tran_type
end text,
tran_type, count(*) as count
from DLY_STATS
where 1=1
AND to_date(to_char(REQUEST_DATETIME,'DD-MM-YYYY'),'DD-MM-YYYY') = '09-FEB-2015'
group by to_date(to_char(REQUEST_DATETIME,'DD-MM-YYYY'),'DD-MM-YYYY'),to_char(REQUEST_DATETIME,'hh24'),tran_type order by src_hour)a);
This gives me the following error:
ERROR at line 2:
ORA-02287: sequence number not allowed here
I tried to remove the order by and still the same error.
However, if I only run
Select brk_seq.NEXTVAL as INDV_SEQ, a.*
FROM (select to_date(to_char(REQUEST_DATETIME,'DD-MM-YYYY'),'DD-MM-YYYY') BUSINESS_DAY, to_char(REQUEST_DATETIME,'hh24') src_hour,
CASE tran_type
WHEN 'V' THEN 'Visa'
WHEN 'M' THEN 'MasterCard'
ELSE tran_type
end text,
tran_type, count(*) as count
from DLY_STATS
where 1=1
AND to_date(to_char(REQUEST_DATETIME,'DD-MM-YYYY'),'DD-MM-YYYY') = '09-FEB-2015'
group by to_date(to_char(REQUEST_DATETIME,'DD-MM-YYYY'),'DD-MM-YYYY'),to_char(REQUEST_DATETIME,'hh24'),tran_type order by src_hour)a;
It shows me proper entries. Then, why is select * not working for that?
Kindly help.
I see what you're trying to do. You want to insert rows into the BRK_INDV table in a particular order. The sequence number, which I assume will be the primary key of BRK_INDV, will be generated sequentially in the sorted order of the input rows.
You are working with a relational database. One of the first characteristics we all learn about a relational database is that the order of the rows in a table is insignificant. That's just a fancy word for fugitaboutit.
You cannot assume that a select * from table will return the rows in the same order they were written. It might. It might for quite a long time. Then something -- the number of rows, the grouping of some column values, the phase of the moon -- something will change and you will get them out in a seemingly totally random order.
If you want order, it must be imposed in the query, not the insert.
Here's the statement you should be executing:
INSERT INTO BRK_INDV
With
Grouped( Business_Day, Src_Hour, Text, Tran_Type, Count )As(
Select Trunc( Request_Datetime ) Business_Day,
To_Char( Request_Datetime, 'hh24') Src_Hour,
Case Tran_Type
When 'V' Then 'Visa'
When 'M' Then 'MasterCard'
Else Tran_Type
end Text,
Tran_Type, count(*) as count
from DLY_STATS
Where 1=1 --> Generated as dynamic SQL?
And Request_Datetime >= Date '2015-02-09'
And Request_Datetime < Date '2015-02-10'
Group By Trunc( Request_Datetime ), To_Char( Request_Datetime, 'hh24'), Tran_Type
)
Select brk_seq.Nextval Indv_Seq, G.*
from Grouped G;
Notice there is no order by. If you want to see the generated rows in a particular order:
select * from Brk_Indv order by src_hour;
Since there could be hundreds or thousands of transactions in any particular hour, you probably order by something other than hour anyway.
In Oracle, the trunc function is the best way to get a date with the time portion stripped away. However, you don't want to use it in the where clause (or, aamof, any other function such as to_date or to_char)as that would make the clause non-sargable and result in a complete table scan.
The problem is that you can't use a sequence in a subquery. For example, this gives the same ORA-02287 error you are getting:
create table T (x number);
create sequence s;
insert into T (select * from (select s.nextval from dual));
What you can do, though, is create a function that returns nextval from the sequence, and use that in a subquery:
create function f return number as
begin
return s.nextval;
end;
/
insert into T (select * from (select f() from dual));

tSQLt - Test that a column is output by a stored procedure

I'm very new to tSQLt and am having some difficulty with what should really be a very simple test.
I have added a column to the SELECT statement executed within a stored procedure.
How do I test in a tSQLt test that the column is included in the resultset from that stored procedure?
Generally, when adding a column to the output of a stored procedure, you will want to test that the column both exists and is populated with the correct data. Since we're going to make sure that the column is populated with the same data, we can design a test that does exactly that:
CREATE PROCEDURE MyTests.[test stored procedure values MyNewColumn correctly]
AS
BEGIN
-- Create Actual and Expected table to hold the actual results of MyProcedure
-- and the results that I expect
CREATE TABLE MyTests.Actual (FirstColumn INT, MyNewColumn INT);
CREATE TABLE MyTests.Expected (FirstColumn INT, MyNewColumn INT);
-- Capture the results of MyProcedure into the Actual table
INSERT INTO MyTests.Actual
EXEC MySchema.MyProcedure;
-- Create the expected output
INSERT INTO MyTests.Expected (FirstColumn, MyNewColumn)
VALUES (7, 12);
INSERT INTO MyTests.Expected (FirstColumn, MyNewColumn)
VALUES (25, 99);
-- Check that Expected and Actual tables contain the same results
EXEC tSQLt.AssertEqualsTable 'MyTests.Expected', 'MyTests.Actual';
END;
Generally, the stored procedure you are testing relies on other tables or other stored procedures. Therefore, you should become familiar with FakeTable and SpyProcedure as well: http://tsqlt.org/user-guide/isolating-dependencies/
Another option if you are just interested in the structure of the output and not the content (and you are running on SQL2012 or greater) would be to make use of sys.dm_exec_describe_first_result_set_for_object in your test.
This dmo (dynamic management object) returns a variety of information about the first result set returned for a given object.
In my example below, I have only used a few of the columns returned by this dmo but others are available if, for example, your output includes decimal data types.
In this test, I populate a temporary table (#expected) with information about how I expect each column to be returned - such as name, datatype and nullability.
I then select the equivalent columns from the dmo into another temporary table (#actual).
Finally I use tSQLt.AssertEqualsTable to compare the contents of the two tables.
Having said all that, whilst I frequently write tests to validate the structure of views or tables (using tSQLt.AssertResultSetsHaveSameMetaData), I have never found the need to just test the result set contract for procedures. Dennis is correct, you would typically be interested in asserting that the various columns in your result set are populated with the correct values and by the time you've covered that functionality you should have covered every column anyway.
if object_id('dbo.myTable') is not null drop table dbo.myTable;
go
if object_id('dbo.myTable') is null
begin
create table dbo.myTable
(
Id int not null primary key
, ColumnA varchar(32) not null
, ColumnB varchar(64) null
)
end
go
if object_id('dbo.myProcedure') is not null drop procedure dbo.myProcedure;
go
create procedure dbo.myProcedure
as
begin
select Id, ColumnA, ColumnB from dbo.myTable;
end
go
exec tSQLt.NewTestClass #ClassName = 'myTests';
if object_id('[myTests].[test result set on SQL2012+]') is not null drop procedure [myTests].[test result set on SQL2012+];
go
create procedure [myTests].[test result set on SQL2012+]
as
begin
; with expectedCte (name, column_ordinal, system_type_name, is_nullable)
as
(
-- The first row sets up the data types for the #expected but is excluded from the expected results
select cast('' as nvarchar(200)), cast(0 as int), cast('' as nvarchar(200)), cast(0 as bit)
-- This is the result we are expecting to see
union all select 'Id', 1, 'int', 0
union all select 'ColumnA', 2, 'varchar(32)', 0
union all select 'ColumnB', 3, 'varchar(64)', 1
)
select * into #expected from expectedCte where column_ordinal > 0;
--! Act
select
name
, column_ordinal
, system_type_name
, is_nullable
into
#actual
from
sys.dm_exec_describe_first_result_set_for_object(object_id('dbo.myProcedure'), 0);
--! Assert
exec tSQLt.AssertEqualsTable '#expected', '#actual';
end
go
exec tSQLt.Run '[myTests].[test result set on SQL2012+]'

"Insert if not exists" statement in SQLite

I have an SQLite database. I am trying to insert values (users_id, lessoninfo_id) in table bookmarks, only if both do not exist before in a row.
INSERT INTO bookmarks(users_id,lessoninfo_id)
VALUES(
(SELECT _id FROM Users WHERE User='"+$('#user_lesson').html()+"'),
(SELECT _id FROM lessoninfo
WHERE Lesson="+lesson_no+" AND cast(starttime AS int)="+Math.floor(result_set.rows.item(markerCount-1).starttime)+")
WHERE NOT EXISTS (
SELECT users_id,lessoninfo_id from bookmarks
WHERE users_id=(SELECT _id FROM Users
WHERE User='"+$('#user_lesson').html()+"') AND lessoninfo_id=(
SELECT _id FROM lessoninfo
WHERE Lesson="+lesson_no+")))
This gives an error saying:
db error near where syntax.
If you never want to have duplicates, you should declare this as a table constraint:
CREATE TABLE bookmarks(
users_id INTEGER,
lessoninfo_id INTEGER,
UNIQUE(users_id, lessoninfo_id)
);
(A primary key over both columns would have the same effect.)
It is then possible to tell the database that you want to silently ignore records that would violate such a constraint:
INSERT OR IGNORE INTO bookmarks(users_id, lessoninfo_id) VALUES(123, 456)
If you have a table called memos that has two columns id and text you should be able to do like this:
INSERT INTO memos(id,text)
SELECT 5, 'text to insert'
WHERE NOT EXISTS(SELECT 1 FROM memos WHERE id = 5 AND text = 'text to insert');
If a record already contains a row where text is equal to 'text to insert' and id is equal to 5, then the insert operation will be ignored.
I don't know if this will work for your particular query, but perhaps it give you a hint on how to proceed.
I would advice that you instead design your table so that no duplicates are allowed as explained in #CLs answer below.
For a unique column, use this:
INSERT OR REPLACE INTO tableName (...) values(...);
For more information, see: sqlite.org/lang_insert
insert into bookmarks (users_id, lessoninfo_id)
select 1, 167
EXCEPT
select user_id, lessoninfo_id
from bookmarks
where user_id=1
and lessoninfo_id=167;
This is the fastest way.
For some other SQL engines, you can use a Dummy table containing 1 record.
e.g:
select 1, 167 from ONE_RECORD_DUMMY_TABLE

Resources