How to create virtual columns in Teradata? - teradata

Is it possible to create virtual columns in Teradata, that is, columns based on the values of another columns of the same table, for example:
CREATE SET TABLE TEST.ZZ_RECEIPTS,
NO FALLBACK ,
NO BEFORE JOURNAL,
NO AFTER JOURNAL,
CHECKSUM = DEFAULT,
DEFAULT MERGEBLOCKRATIO
(
ID_RECEIPT NUMBER,
ID_ITEM NUMBER,
NUM_ITEM NUMBER,
VALUE_ITEM DECIMAL (10, 2)
/*,
-- TOTAL_ITEM is the virtual column I want to add
TOTAL_ITEM DECIMAL (10, 2) AS (NUM_ITEM * VALUE_ITEM)
*/
) PRIMARY INDEX (ID_RECEIPT, ID_ITEM);
INSERT INTO TEST.ZZ_RECEIPTS (ID_RECEIPT, ID_ITEM, NUM_ITEM, VALUE_ITEM) VALUES (1, 1, 3, 4.50); --3*4,50 = 13,50
INSERT INTO TEST.ZZ_RECEIPTS (ID_RECEIPT, ID_ITEM, NUM_ITEM, VALUE_ITEM) VALUES (1, 2, 4, 5.50); --4*5,50 = 22
COMMIT;
SELECT * FROM TEST.ZZ_RECEIPTS;
ID_RECEIPT ID_ITEM NUM_ITEM VALUE_ITEM TOTAL_ITEM
---------- ---------- ---------- ------------ ----------
1 1 3 4,50 13,5
1 2 4 5,50 22
Edit: I want to create an index over this field, similar to a function-based index.
Thanks!

Related

Recursive join and group_concat with SQLite

I have the following table:
id value successor_id
-- ----- ------------
1 v1 2
2 v2 4
4 v3 null
7 v4 9
9 v5 null
12 v6 null
Note: Those are simple paths (no trees), so two ids can not have the same successor_id. Also, the ids are ordered and a successor must be the following id. E.g. in my example the only possible successor of id 2 is 4. It cannot be 1 or 7.
Now, I want to do some kind of recursive LEFT JOIN ON id = successor_id with the table itself and GROUP_CONCAT the values in order to get the following result:
min_id max_id values
------ ------ --------
1 4 v1,v2,v3
7 9 v4,v5
12 12 v6
How can I achieve this? I guess a combination of WITH RECURSIVE and GROUP BY, but I don't know how to start since WITH RECURSIVE requires a starting point, but I have multiple starting points (ids: 1, 7 and 12).
Here is the SQL code to create the example table:
.mode column
.nullvalue null
.width -1 -1 -1
CREATE TABLE test (
id INTEGER NOT NULL,
value STRING NOT NULL,
successor_id INTEGER
);
INSERT INTO test (id, value, successor_id) VALUES ( 1, 'v1', 2);
INSERT INTO test (id, value, successor_id) VALUES ( 2, 'v2', 4);
INSERT INTO test (id, value, successor_id) VALUES ( 4, 'v3', null);
INSERT INTO test (id, value, successor_id) VALUES ( 7, 'v4', 9);
INSERT INTO test (id, value, successor_id) VALUES ( 9, 'v5', null);
INSERT INTO test (id, value, successor_id) VALUES (12, 'v6', null);
SELECT id, value, successor_id
FROM test;
There is no need for a recursive query.
Use window functions:
WITH cte AS (
SELECT *, LAG(successor_id) OVER (ORDER BY id) IS NULL flag
FROM test
)
SELECT DISTINCT
MIN(id) OVER (PARTITION BY grp) min_id,
MAX(id) OVER (PARTITION BY grp) max_id,
GROUP_CONCAT(value) OVER (
PARTITION BY grp
ORDER BY id
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) "values"
FROM (
SELECT *, SUM(flag) OVER (ORDER BY id) grp
FROM cte
);
See the demo.

Selecting the n'th range/island of rows where columns have a common value?

I need to select all rows (for a range) which have a common value within a column.
For example (starting from the last row)
I try to select all of the rows where _user_id == 1 until _user_id != 1 ?
In this case resulting in selecting rows [4, 5, 6]
+------------------------+
| _id _user_id amount |
+------------------------+
| 1 1 777 |
| 2 2 1 |
| 3 2 11 |
| 4 1 10 |
| 5 1 100 |
| 6 1 101 |
+------------------------+
/*Create the table*/
CREATE TABLE IF NOT EXISTS t1 (
_id INTEGER PRIMARY KEY AUTOINCREMENT,
_user_id INTEGER,
amount INTEGER);
/*Add the datas*/
INSERT INTO t1 VALUES(1, 1, 777);
INSERT INTO t1 VALUES(2, 2, 1);
INSERT INTO t1 VALUES(3, 2, 11);
INSERT INTO t1 VALUES(4, 1, 10);
INSERT INTO t1 VALUES(5, 1, 100);
INSERT INTO t1 VALUES(6, 1, 101);
/*Check the datas*/
SELECT * FROM t1;
1|1|777
2|2|1
3|2|11
4|1|10
5|1|100
6|1|101
In my attempt I use Common Table Expressions to group the results of _user_id. This gives the index of the last row containing a unique value (eg. SELECT _id FROM t1 GROUP BY _user_id LIMIT 2; will produce: [6, 3])
I then use those two values to select a range where LIMIT 1 OFFSET 1 is the lower end (3) and LIMIT 1 is the upper end (6)
WITH test AS (
SELECT _id FROM t1 GROUP BY _user_id LIMIT 2
) SELECT * FROM t1 WHERE _id BETWEEN 1+ (
SELECT * FROM test LIMIT 1 OFFSET 1
) and (
SELECT * FROM test LIMIT 1
);
Output:
4|1|10
5|1|100
6|1|101
This appears to work ok at selecting the last "island" but what I really need is a way to select the n'th island.
Is there a way to generate a query capable of producing outputs like these when provided a parameter n?:
island (n=1):
4|1|10
5|1|100
6|1|101
island (n=2):
2|2|1
3|2|11
island (n=3):
1|1|777
Thanks!
SQL tables are unordered, so the only way to search for islands is to search for consecutive _id values:
WITH RECURSIVE t1_with_islands(_id, _user_id, amount, island_number) AS (
SELECT _id,
_user_id,
amount,
1
FROM t1
WHERE _id = (SELECT max(_id)
FROM t1)
UNION ALL
SELECT t1._id,
t1._user_id,
t1.amount,
CASE WHEN t1._user_id = t1_with_islands._user_id
THEN island_number
ELSE island_number + 1
END
FROM t1
JOIN t1_with_islands ON t1._id = (SELECT max(_id)
FROM t1
WHERE _id < t1_with_islands._id)
)
SELECT *
FROM t1_with_islands
ORDER BY _id;

SQLite group-by behaviour

If a column in the SELECT clause is omitted from the GROUP BY clause, does SQLite group by the remaining columns (by default), and then return the value of the omitted column in the first row it evaluates?
For example, finding the TransactionId associated with the highest value per ProductId:
CREATE TABLE IF NOT EXISTS ProductTransaction
(
Id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
ProductId INTEGER NOT NULL,
TransactionType INTEGER NOT NULL,
Value INTEGER NOT NULL
);
INSERT INTO ProductTransaction (ProductId, TransactionType, Value)
VALUES (1, 7, 23), (1, 3, 12), (2, 4, 43), (1, 7, 5), (1, 10, 23),
(3, 3, 23), (3, 2, 31), (1, 1, 23), (2, 5, 50), (2, 6, 14), (1, 4, 23);
SELECT ProductId
, TransactionType
, MAX(Value)
FROM ProductTransaction
GROUP BY ProductId;
DELETE FROM ProductTransaction;
Running the previous statements gives me the TransactionType of 7 for ProductId 1 (Highest value 23).
However, if I add an the index:
CREATE INDEX IF NOT EXISTS IDX_TransType ON ProductTransaction(ProductId ASC, TransactionType ASC);
It returns the TransactionType 1, presumably because it's now ordering the rows according to the index. Modifying the index supports this theory:
CREATE INDEX IF NOT EXISTS IDX_TransType ON ProductTransaction(ProductId ASC, TransactionType DESC);
It will now return TransactionType 10 for ProductId 1.
Is this behaviour by design, or is it just an unreliable side-effect?
EDIT: It seems that it's an unreliable side-effect. From the documentation:
Each expression in the result-set is then evaluated once for each
group of rows. If the expression is an aggregate expression, it is
evaluated across all rows in the group. Otherwise, it is evaluated
against a single arbitrarily chosen row from within the group. If
there is more than one non-aggregate expression in the result-set,
then all such expressions are evaluated for the same row.
https://www.sqlite.org/lang_select.html#resultset
Since SQLite 3.7.11, using MAX() or MIN() will force any non-aggregated columns to come from the same row that matches the MAX()/MIN().
However, when there are multiple rows with the same largest/smalles value, it is still unspecified from which of those rows the other columns' values come. (SQLite's behaviour is consistent in this regard, but can change in different versions or with different database schemas.)

SQLite floating point issue

This query returns 1.7763568394002505e-15 when it should return 0.00:
SELECT st.id
, Sum(
CASE sa.Type
WHEN 4 THEN sa.quantity * (st.price - st.commission)
WHEN 5 THEN -sa.quantity * (st.price - st.commission)
ELSE 0.0 END
) Sales
FROM sales sa
JOIN stock st
ON sa.stockid = st.id
WHERE st.id = 1
GROUP BY st.id
http://sqlfiddle.com/#!5/cccd8/3
It's looks like a classic floating point calculation issue, but how can I fix it?
I've tried casting the various columns to REAL but it doesn't make a difference.
You can simulate the result using this query:
SELECT 26.3 - 10.52 - 15.78 AS Result
SQLite's REAL isn't suitable for currency. SQlite doesn't support SQL decimal or SQL numeric data types, so your best option is to use integer, and store values as cents.
CREATE TABLE stock (
id INTEGER,
-- Store price and commission as integers, implying that price is in cents,
-- ($3.20 is stored as 320) and commission is a two-digit percentage (0.57%
-- is stored as 57). This is how scaled integers work in general.
price integer,
commission integer,
PRIMARY KEY(id)
);
CREATE TABLE sales (
id INTEGER,
stockid INTEGER,
type INTEGER,
quantity INTEGER,
PRIMARY KEY(id)
);
insert into stock values (1, 320, 57);
insert into sales values (1, 1, 4, 10);
insert into sales values (2, 1, 5, 4);
insert into sales values (3, 1, 5, 6);
This query, from your SQLfiddle, correctly returns 0.
SELECT st.id
, Sum(
CASE sa.Type
WHEN 4 THEN sa.Quantity * (st.price - st.commission)
WHEN 5 THEN -sa.Quantity * (st.price - st.commission)
ELSE 0.0 END
) Sales
FROM sales sa
JOIN stock st
ON sa.stockid = st.id
WHERE st.id = 1
GROUP BY st.id;
id Sales
---------- ----------
1 0
Casting to a more appropriate data type (not to REAL) will hide some problems--maybe even most problems or even all of them in a particular application. But casting won't solve them, because stored values are liable to be different than the values you really want.
Mike Sherrill is correct in that you probably should use integers. But for a quick-and-dirty fix, you can wrap the Sum call in a Round(__,2) to round to the nearest cent.

Counting the same column of different value sets in a single group by clause

I have a table (SQLite DB) like this,
CREATE TABLE parser (ip text, user text, code text);
Now I need to count how many code have a value of either 1, 2, or 3, and how many are not, group by ip field.
But as far as I can go, I can't do this altogether, but with two SQL phrases.
e.g
select count(*) as cnt, ip
from parser
where code in (1, 2, 3)
group by ip
order by cnt DESC
limit 10
And a not in query.
So, can I merge the two queries into a single one?
This will you give you two counts per ip, one for the rows where code has values 1, 2 or 3 and another count for all the rest (everything but 1, 2, 3, including NULL.)
SELECT ip,
COUNT(CASE WHEN code IN (1, 2, 3) THEN 1 ELSE NULL END) AS cnt_in,
COUNT(CASE WHEN code IN (1, 2, 3) THEN NULL ELSE 1 END) AS cnt_rest
FROM parser
GROUP BY ip
ORDER BY cnt_in DESC ;
This will you give you 3 counts, one for 1,2,3, another for the rest of integer values and a third for rows that have NULL in code:
SELECT ip,
COUNT(CASE WHEN code IN (1, 2, 3) THEN 1 END) AS cnt_in,
COUNT(CASE WHEN code NOT IN (1, 2, 3) THEN 1 END) AS cnt_not_in,
COUNT(CASE WHEN code IS NULL THEN 1 END) AS cnt_null
FROM parser
GROUP BY ip
ORDER BY cnt_in DESC ;
If you want to limit the first result (as your code) to the top 10 rows and the second result to the other top 10 rows, you can use two subqueries and a UNION:
( SELECT ip,
COUNT(*) AS cnt,
'in' AS type
FROM parser
WHERE code IN (1, 2, 3)
GROUP BY ip
ORDER BY cnt DESC
LIMIT 10
)
UNION ALL
( SELECT ip,
COUNT(*) AS cnt,
'not in' AS type
FROM parser
WHERE code NOT IN (1, 2, 3)
GROUP BY ip
ORDER BY cnt DESC
LIMIT 10
) ;
Tested at SQL-Fiddle

Resources