Is there a way to reuse subqueries in the same query? - teradata

See Update at end of question for solution thanks to marked answer!
I'd like to treat a subquery as if it were an actual table that can be reused in the same query. Here's the setup SQL:
create table mydb.mytable
(
id integer not null,
fieldvalue varchar(100),
ts timestamp(6) not null
)
unique primary index (id, ts)
insert into mydb.mytable(0,'hello',current_timestamp - interval '1' minute);
insert into mydb.mytable(0,'hello',current_timestamp - interval '2' minute);
insert into mydb.mytable(0,'hello there',current_timestamp - interval '3' minute);
insert into mydb.mytable(0,'hello there, sir',current_timestamp - interval '4' minute);
insert into mydb.mytable(0,'hello there, sir',current_timestamp - interval '5' minute);
insert into mydb.mytable(0,'hello there, sir. how are you?',current_timestamp - interval '6' minute);
insert into mydb.mytable(1,'what up',current_timestamp - interval '1' minute);
insert into mydb.mytable(1,'what up',current_timestamp - interval '2' minute);
insert into mydb.mytable(1,'what up, mr man?',current_timestamp - interval '3' minute);
insert into mydb.mytable(1,'what up, duder?',current_timestamp - interval '4' minute);
insert into mydb.mytable(1,'what up, duder?',current_timestamp - interval '5' minute);
insert into mydb.mytable(1,'what up, duder?',current_timestamp - interval '6' minute);
What I want to do is return only rows where FieldValue differs from the previous row. This SQL does just that:
locking row for access
select id, fieldvalue, ts from
(
--locking row for access
select
id, fieldvalue,
min(fieldvalue) over
(
partition by id
order by ts, fieldvalue rows
between 1 preceding and 1 preceding
) fieldvalue2,
ts
from mydb.mytable
) x
where
hashrow(fieldvalue) <> hashrow(fieldvalue2)
order by id, ts desc
It returns:
+----+---------------------------------+----------------------------+
| id | fieldvalue | ts |
+----+---------------------------------+----------------------------+
| 0 | hello | 2015-05-06 10:13:34.160000 |
| 0 | hello there | 2015-05-06 10:12:34.350000 |
| 0 | hello there, sir | 2015-05-06 10:10:34.750000 |
| 0 | hello there, sir. how are you? | 2015-05-06 10:09:34.970000 |
| 1 | what up | 2015-05-06 10:13:35.470000 |
| 1 | what up, mr man? | 2015-05-06 10:12:35.690000 |
| 1 | what up, duder? | 2015-05-06 10:09:36.240000 |
+----+---------------------------------+----------------------------+
The next step is to return only the last row per ID. If I were to use this SQL to write the previous SELECT to a table...
create table mydb.reusetest as (above sql) with data;
...I could then do this do get the last row per ID:
locking row for access
select t1.* from mydb.reusetest t1,
(
select id, max(ts) ts from mydb.reusetest
group by id
) t2
where
t2.id = t1.id and
t2.ts = t1.ts
order by t1.id
It would return this:
+----+------------+----------------------------+
| id | fieldvalue | ts |
+----+------------+----------------------------+
| 0 | hello | 2015-05-06 10:13:34.160000 |
| 1 | what up | 2015-05-06 10:13:35.470000 |
+----+------------+----------------------------+
If I could reuse the subquery in my initial SELECT, I could achieve the same results. I could copy/paste the entire query SQL into another subquery to create a derived table, but this would just mean I'd need to change the SQL in two places if I ever needed to modify it.
Update
Thanks to Kristján, I was able to implement the WITH clause into my SQL like this for perfect results:
locking row for access
with items (id, fieldvalue, ts) as
(
select id, fieldvalue, ts from
(
select
id, fieldvalue,
min(fieldvalue) over
(
partition by id
order by ts, fieldvalue
rows between 1 preceding and 1 preceding
) fieldvalue2,
ts
from mydb.mytable
) x
where
hashrow(fieldvalue) <> hashrow(fieldvalue2)
)
select t1.* from items t1,
(
select id, max(ts) ts from items
group by id
) t2
where
t2.id = t1.id and
t2.ts = t1.ts
order by t1.id

Does WITH help? That lets you define a result set you can use multiple times in the SELECT.
From their example:
WITH orderable_items (product_id, quantity) AS
( SELECT stocked.product_id, stocked.quantity
FROM stocked, product
WHERE stocked.product_id = product.product_id
AND product.on_hand > 5
)
SELECT product_id, quantity
FROM orderable_items
WHERE quantity < 10;

Related

SQLite: Calculate how a counter has increased in current day and week

I have a SQLite database with a counter and timestamp in unixtime as showed below:
+---------+------------+
| counter | timestamp |
+---------+------------+
| | 1582933500 |
| 1 | |
+---------+------------+
| 2 | 1582933800 |
+---------+------------+
| ... | ... |
+---------+------------+
I would like to calculate how 'counter' has increased in current day and current week.
It is possible in a SQLite query?
Thanks!
Provided you have SQLite version >= 3.25.0 the SQLite window functions will help you achieve this.
Using the LAG function to retrieve the value from the previous record - if there is none (which will be the case for the first row) a default value is provided, that is same as current row.
For the purpose of demonstration this code:
SELECT counter, timestamp,
LAG (timestamp, 1, timestamp) OVER (ORDER BY counter) AS previous_timestamp,
(timestamp - LAG (timestamp, 1, timestamp) OVER (ORDER BY counter)) AS diff
FROM your_table
ORDER BY counter ASC
will give this result:
1 1582933500 1582933500 0
2 1582933800 1582933500 300
In a CTE get the min and max timestamp for each day and join it twice to the table:
with cte as (
select date(timestamp, 'unixepoch', 'localtime') day,
min(timestamp) mindate, max(timestamp) maxdate
from tablename
group by day
)
select c.day, t2.counter - t1.counter difference
from cte c
inner join tablename t1 on t1.timestamp = c.mindate
inner join tablename t2 on t2.timestamp = c.maxdate;
With similar code get the results for each week:
with cte as (
select strftime('%W', date(timestamp, 'unixepoch', 'localtime')) week,
min(timestamp) mindate, max(timestamp) maxdate
from tablename
group by week
)
select c.week, t2.counter - t1.counter difference
from cte c
inner join tablename t1 on t1.timestamp = c.mindate
inner join tablename t2 on t2.timestamp = c.maxdate;

SSMS Case Statement With Multiple Fields

I have a case statement to sum, round and label amounts that works fine, but the data ends up in horizontal format (multiple money amounts per record), causing me to do an unpivot in a subsequent statement to format the data vertically (one money amount per record). I would like to accomplish this in one statement if possible. My code is as follows:
SELECT
Field,
ROUND(SUM(CASE
WHEN TYPE = 'Paid Loss'
THEN AMOUNT
ELSE 0
END
), 2
) PAID,
ROUND(SUM(CASE
WHEN TYPE = 'OS'
THEN AMOUNT
ELSE 0
END
), 2
) OS,
ROUND(SUM(CASE
WHEN TYPE <> 'Paid Exp'
THEN AMOUNT
ELSE 0
END
), 2
) INCURRED
FROM dbo.mydatabase
GROUP BY Field;
The result is:
Field |PAID |OS |INCURRED
----------------------------
result1 | 1 | 20 | 10
result2 | 5 | 30 | 15
When what I really want is:
Field | DATA_TYPE | AMOUNT
---------------------------
result1 | PAID | 1
result2 | PAID | 5
result3 | OS | 20
result4 | OS | 30
result5 | INCURRED | 10
result6 | INCURRED | 15
Keys will be unique so that isn't an issue. Anyone know how to rearrange the CASE so this can be done in one statement? Thanks!
Wouldn't this work?
SELECT Field, 'PAID' DATA_TYPE, ROUND(SUM(AMOUNT), 2) AMOUNT
FROM dbo.mydatabase
WHERE TYPE = 'Paid Loss'
GROUP BY Field
UNION ALL
SELECT Field, 'OS' DATA_TYPE, ROUND(SUM(AMOUNT), 2) AMOUNT
FROM dbo.mydatabase
WHERE TYPE = 'OS'
GROUP BY Field
UNION ALL
SELECT Field, 'INCURRED' DATA_TYPE, ROUND(SUM(AMOUNT), 2) AMOUNT
FROM dbo.mydatabase
WHERE TYPE <> 'Paid Exp'
GROUP BY Field
or even like this:
SELECT Field
, (CASE
WHEN TYPE IN ('Paid Loss', 'OS') THEN TYPE
WHEN TYPE <> 'Paid Exp' THEN 'INCURRED'
END) DATA_TYPE
, ROUND(SUM(AMOUNT), 2) AMOUNT
FROM dbo.mydatabase
GROUP BY Field
, (CASE
WHEN TYPE IN ('Paid Loss', 'OS') THEN TYPE
WHEN TYPE <> 'Paid Exp' THEN 'INCURRED'
END) DATA_TYPE

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;

Suggestion needed writing a complex query - sqlite

I have 4 columns in a table called musics - 'artist','genre', 'writer' , 'producer'.
I need to write a query such that, it returns a value 0 , if there are no repetition of values corresponding to the column name; if there is a repetition of values, it should return a value 1, corresponding to that column name.
Any help is much appreciated
SELECT (COUNT(artist) <> COUNT(DISTINCT artist)) artist,
(COUNT(genre) <> COUNT(DISTINCT genre)) genre,
(COUNT(writer) <> COUNT(DISTINCT writer)) writer,
(COUNT(producer) <> COUNT(DISTINCT producer)) producer
FROM musics
Another version
SELECT
( SELECT (COUNT(*) > 0)
FROM (SELECT 1 FROM musics GROUP BY artist HAVING COUNT(*) > 1) a
) artist,
( SELECT (COUNT(*) > 0)
FROM (SELECT 1 FROM musics GROUP BY genre HAVING COUNT(*) > 1) g
) genre,
( SELECT (COUNT(*) > 0)
FROM (SELECT 1 FROM musics GROUP BY writer HAVING COUNT(*) > 1) w
) writer,
( SELECT (COUNT(*) > 0)
FROM (SELECT 1 FROM musics GROUP BY producer HAVING COUNT(*) > 1) p
) producer
Sample data
| artist | genre | writer | producer |
------------------------------------------
| artist1 | genre1 | writer1 | producer1 |
| artist2 | genre2 | writer1 | producer2 |
| artist1 | genre3 | writer3 | producer3 |
Sample output:
| artist | genre | writer | producer |
--------------------------------------
| 1 | 0 | 1 | 0 |
SQLFiddle
For Artist
select convert(bit,(count(1)-1))
from table_name
group by artist -- <-- Replace artist with column name for which duplicate
write a select count statement using distinct with specified column and another select count without distinct and compare both of them based on your requirement
you can use 4 different query with union & each query must contain count(column name) + group by clause

Fetching multiple records while sampling epoch timestamp feild from MySQL table?

I have a table with feilds like
TimeStamp | Feild1 | Feild 2
--------------------------------------
1902909002 | xyddtz | 233447
1902909003 | xytzff | 233442
1902909005 | xytzdd | 233443
1902909007 | xytzdd | 233443
1902909009 | xytsqz | 233436
Now i want to query it and fetch records like between 1902909002 and 1902909007 which will be easily done with :
Select * from table where timestamp > 1902909001 AND timestamp < 1902909008
but two more things i want to do is :
maybe that particular time stamp is not there so i have to find nearest value
like if there are 200 records in that range but i want to fetch only 20 so i want to skip every 19 records in a row and fetch 20th, 40th etc records.
i will be having date in datetime format. i know i can convert it before querying but if there is some option in query itself then it willbe better.
Try this query
select * from(
select #rn:=if(#rn < rid, rid, #rn) as rn, rid, timestamp, feild1, feild2
from
(select #rn:=#rn+1 as rId, tbl.*
from tbl
join
(select #rn:=0) tmp
where timestamp between 1902909002 and 1902909024 order by rid desc)a
join
(select #rn:=0)tmp)tmp
where rid%(rn div 6)=0
SQL FIDDLE
Try this (it is crucial for both queries to be exec in the same mysql session):
SET #c:=0;
SELECT
*
FROM (
SELECT
* ,
#c:=#c+1 as counter
FROM
table
WHERE
timestamp > 1902909001
AND timestamp < 1902909008
) as tmp
WHERE
counter % 20 =1;

Resources