SQL - For each ID, values in other columns should be repeated - sql

The table I am trying to create should look like this
**ID** **Timeframe** Value
1 60 15
1 60 30
1 90 45
2 60 15
2 60 30
2 90 45
3 60 15
3 60 30
3 90 45
So for each ID the values of 60,60,90 and 15,30,45 should be repeated.
Could anyone help me with a code? :)

You are looking for a cross join. The basic idea is something like this:
select i.id, tv.timeframe, tv.value
from (values (1), (2), (3)) i(id) cross join
(values (60, 15), (60, 30), (90, 45)) tv(timeframe, value)
order by i.id, tv.value;
Not all databases support the values() table constructor. In those databases, you would need to use the appropriate syntax.

So you have this table: ...
id
1
2
3
and you have this table: ...
timeframe value
60 15
60 30
90 45
Then try this:
WITH
-- the ID table...
id(id) AS (
SELECT 1
UNION ALL SELECT 2
UNION ALL SELECT 3
)
,
-- the values table:
vals(timeframe,value) AS (
SELECT 60,15
UNION ALL SELECT 60,30
UNION ALL SELECT 90,45
)
SELECT
id
, timeframe
, value
FROM id CROSS JOIN vals
ORDER BY id, timeframe;
-- out id | timeframe | value
-- out ----+-----------+-------
-- out 1 | 60 | 30
-- out 1 | 60 | 15
-- out 1 | 90 | 45
-- out 2 | 60 | 30
-- out 2 | 60 | 15
-- out 2 | 90 | 45
-- out 3 | 60 | 30
-- out 3 | 60 | 15
-- out 3 | 90 | 45
-- out (9 rows)

Related

SQL - How Cumulative Sum when group by values are missing

As a follow-up to my previous ticket - which I now believe the example was too simple (previous question), I prepared an example of a scenario where I'm looking to aggregate column cus_sum group on the date_col column and the cus column representing the unique customer number.
I wish to generate a series of dates for instance (function generate series) from the 1st of January 2018 till the 10th of January 2018 and then have a cumulative sum of column cus_sum for each customer. As in the case below, you can imagine that there are days without information at all and days where not all customers have any records - regardless i want to show their cumulative sum throughout that period.
CREATE TABLE test2 (date_col date, cus int, cus_sum int);
insert into test2 values ('2018-01-01', 1, 5);
insert into test2 values ('2018-01-02', 1, 12);
insert into test2 values ('2018-01-02', 2, 14);
insert into test2 values ('2018-01-03', 2, 8);
insert into test2 values ('2018-01-03', 2, 10);
insert into test2 values ('2018-01-04', 1, 22);
insert into test2 values ('2018-01-06', 2, 20);
insert into test2 values ('2018-01-06', 1, 5);
insert into test2 values ('2018-01-07', 1, 45);
insert into test2 values ('2018-01-08', 2, 32);
The output should look like:
date_col cus cum_sum
"2018-01-01" 1 5
"2018-01-01" 2 0
"2018-01-02" 1 17
"2018-01-02" 2 14
"2018-01-03" 1 17
"2018-01-03" 2 32
"2018-01-04" 1 39
"2018-01-04" 2 32
"2018-01-05" 1 39
"2018-01-05" 2 32
"2018-01-06" 1 89
"2018-01-06" 2 52
"2018-01-07" 1 134
"2018-01-07" 2 52
"2018-01-08" 1 134
"2018-01-08" 1 84
Perhaps I should add that - one table I assume will be a virtual table that generates a list of dates in a given timeframe. The second table is a list of customers[1,3,4,5..10], products purchases (product volume) - which is what I wish to cumulative sum for every customer and everyday of the series.
Assuming that you have separate table for customers, so you can use CTE to generate the data range and then join croos join customer table to have all combinations of customer and dates, then you get the sum from test2 table. the query will look like below -
WITH DateRange AS (
SELECT
[MyDate] = CONVERT(DATETIME,'01/01/2018')
UNION ALL
SELECT
[MyDate] = DATEADD(DAY, 1, [Date])
FROM
DateRange
WHERE
[MyDate] <= '01/10/2018'
) SELECT
d.[MyDate]
c.cus
(
select isnull(sum(cus_sume),0)
from test2 t
where t.date = d.mydate
and c.cust = t.cust
) as cus_sum
FROM
DateRange d
cross join customer c
order by d.MyDate
The cross join of generate_series() and unnest() creates a virtual table of all possible values:
select distinct
date_col::date,
cus,
coalesce(sum(cus_sum) over (partition by cus order by date_col), 0) as cum_sum
from generate_series('2018-01-01'::date, '2018-01-08', '1d') as date_col
cross join (select distinct cus from test2) c
left join test2 using (date_col, cus)
order by date_col, cus
date_col | cus | cum_sum
------------+-----+---------
2018-01-01 | 1 | 5
2018-01-01 | 2 | 0
2018-01-02 | 1 | 17
2018-01-02 | 2 | 14
2018-01-03 | 1 | 17
2018-01-03 | 2 | 32
2018-01-04 | 1 | 39
2018-01-04 | 2 | 32
2018-01-05 | 1 | 39
2018-01-05 | 2 | 32
2018-01-06 | 1 | 44
2018-01-06 | 2 | 52
2018-01-07 | 1 | 89
2018-01-07 | 2 | 52
2018-01-08 | 1 | 89
2018-01-08 | 2 | 84
(16 rows)
It looks like there are mistakes in the OP's expected results.
DbFiddle.

How can I loop to get the previous value when the currernt value larger than the previous one?

Source:
Seq Amount
1 50
2 48
3 46
4 40
5 45
6 43
7 39
Here is what I want,
when the amount in currernt row is larger than the last one, It changes to the previous one.
For example in row 5, the amount 45>40 in row 4, then change it to 40
in row 6, the amount 43>40 in updated row5, then change it to 40
This is the expected result:
Seq Amount
1 50
2 48
3 46
4 40
5 40
6 40
7 39
I am currently using lag (amount) over (order by seq)
however, the result is not correct. I think I need a loop script but I am not sure how to do that, please help.
Thanks!
Here is a more generic version not requiring LAG:
SQL Fiddle
MS SQL Server 2008 Schema Setup:
CREATE TABLE Table1
([Seq] int, [Amount] int)
;
INSERT INTO Table1
([Seq], [Amount])
VALUES
(1, 50),
(2, 48),
(3, 46),
(4, 40),
(5, 45),
(6, 43),
(7, 39)
;
Query 1:
select t1.Seq, case when t1.Amount < t2.Amount or t2.Amount is null then t1.Amount else t2.Amount end as Value
from Table1 t1
left join Table1 t2 on t2.Seq = t1.Seq - 1
order by t1.Seq
Results:
| Seq | Value |
|-----|-------|
| 1 | 50 |
| 2 | 48 |
| 3 | 46 |
| 4 | 40 |
| 5 | 40 |
| 6 | 43 |
| 7 | 39 |
Update the column using the logic provided by RedFilter in a while loop until the value of ##rowcount = 0

SQL: Arrange numbers on a scale 1 to 10

I have a table in a SQL Server 2008 database with a number column that I want to arrange on a scale 1 to 10.
Here is an example where the column (Scale) is what I want to accomplish with SQL
Name Count (Scale)
----------------------
A 19 2
B 1 1
C 25 3
D 100 10
E 29 3
F 60 7
In my example above the min and max count is 1 and 100 (this could be different from day to day).
I want to get a number to which each record belongs to.
1 = 0-9
2 = 10-19
3 = 20-29 and so on...
It has to be dynamic because this data changes everyday so I can not use a WHERE clause with static numbers like this: WHEN Count Between 0 and 10...
Try this, though note technically the value 100 doesn't fall in the range 90-99 and therefore should probably be classed as 11, hence why the value 60 comes out with a scale of 6 rather than your 7:
SQL Fiddle
MS SQL Server 2008 Schema Setup:
Query 1:
create table #scale
(
Name Varchar(10),
[Count] INT
)
INSERT INTO #scale
VALUES
('A', 19),
('B', 1),
('C', 25),
('D', 100),
('E', 29),
('F', 60)
SELECT name, [COUNT],
CEILING([COUNT] * 10.0 / (SELECT MAX([Count]) - MIN([Count]) + 1 FROM #Scale)) AS [Scale]
FROM #scale
Results:
| NAME | COUNT | SCALE |
|------|-------|-------|
| A | 19 | 2 |
| B | 1 | 1 |
| C | 25 | 3 |
| D | 100 | 10 |
| E | 29 | 3 |
| F | 60 | 6 |
This gets you your answer where 60 becomes 7, hence 100 is 11:
SELECT name, [COUNT],
CEILING([COUNT] * 10.0 / (SELECT MAX([Count]) - MIN([Count]) FROM #Scale)) AS [Scale]
FROM #scale
WITH MinMax(Min, Max) AS (SELECT MIN(Count), MAX(Count) FROM Table1)
SELECT Name, Count, 1 + 9 * (Count - Min) / (Max - Min) AS Scale
FROM Table1, MinMax
You can make Scale column a PERSISTED COMPUTED column as:
alter table test drop column Scale
ALTER TABLE test ADD
Scale AS (case when Count between 0 and 9 then 1
when Count between 10 and 19 then 2
when Count between 20 and 29 then 3
when Count between 30 and 39 then 4
when Count between 40 and 49 then 5
when Count between 50 and 59 then 6
when Count between 60 and 69 then 7
when Count between 70 and 79 then 8
when Count between 80 and 89 then 9
when Count between 90 and 100 then 10
end
)PERSISTED
GO
DEMO
select ntile(10) over (order by [count])

Group Date column based on hours

I have a table in sqlite database where I store data about call logs. As an example assume that my table looks like this
| Calls_count | Calls_duration | Time_slice | Time_stamp |
| 10 | 500 | 21 | 1399369269 |
| 2 | 88 | 22 | 1399383668 |
Here
Calls_count is calls made since last observations
Calls_duration is the duration of calls in ms since last observations
Time-slice represents a time portion of week. Every day is divided into 4 portions of 6 hours each such that
06:00-11:59 | 12:00-17:59 | 18:00- 23.59 | 24:00-05:59 |
Mon| 11 | 12 | 13 | 14 |
Tue| 21 | 22 | 23 | 24 |
Wed| 31 | 32 | 33 | 34 |
Thu| 41 | 42 | 43 | 44 |
Fri| 51 | 52 | 53 | 54 |
Sat| 61 | 62 | 63 | 64 |
Sun| 71 | 72 | 73 | 74 |
And the time_stamp is unix epoch when the observation was made/ record was inserted in the database
Now I want to create a query so that if I specify time_stamp for a start and the end of week, The result is 168 rows of data, giving me sum of calls grouped by hour such that I get 24 rows for each day of week. This is an hourly break down of calls in a week.
SUM_CALLS | Time_Slice | Hour_of_Week |
10 | 11 | 1 |
0 | 11 | 2 |
....
7 | 74 | 167 |
4 | 74 | 168 |
In the above example of intended result,
1st row is Monday 06:00-06:59
2nd row is Monday 07:00-07:59
Last row is Sunday 04:00-05:59
Since version 3.8.3 SQLite supports common table expressions
and this is a possible solution
WITH RECURSIVE
hours(x,y) AS (SELECT CAST(STRFTIME('%s',STRFTIME('%Y-%m-%d %H:00:00', '2014-05-05 00:00:00')) AS INTEGER),
CAST(STRFTIME('%s',STRFTIME('%Y-%m-%d %H:59:59', '2014-05-05 00:00:00')) AS INTEGER)
UNION ALL
SELECT x+3600,y+3600 FROM hours LIMIT 168)
SELECT
COALESCE(SUM(Calls_count),0) AS SUM_CALLS,
CASE CAST(STRFTIME('%w',x,'unixepoch') AS INTEGER)
WHEN 0 THEN 7 ELSE STRFTIME('%w',x,'unixepoch') END
||
CASE
WHEN STRFTIME('%H:%M:%S',x,'unixepoch') BETWEEN '06:00:00' AND '11:59:59' THEN 1
WHEN STRFTIME('%H:%M:%S',x,'unixepoch') BETWEEN '12:00:00' AND '17:59:59' THEN 2
WHEN STRFTIME('%H:%M:%S',x,'unixepoch') BETWEEN '18:00:00' AND '23:59:59' THEN 3
WHEN STRFTIME('%H:%M:%S',x,'unixepoch') BETWEEN '00:00:00' AND '05:59:59' THEN 4
END AS Time_Slice,
((x-(SELECT MIN(x) FROM hours))/3600)+1 AS Hour_of_Week
FROM hours LEFT JOIN call_logs
ON call_logs.time_stamp >= hours.x AND call_logs.time_stamp <= hours.y
GROUP BY Hour_of_Week
ORDER BY Hour_of_Week
;
This is tested with SQLite version 3.7.13 without cte:
DROP VIEW IF EXISTS digit;
CREATE TEMPORARY VIEW digit AS SELECT 0 AS d UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION
SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9
;
DROP VIEW IF EXISTS hours;
CREATE TEMPORARY VIEW hours AS SELECT STRFTIME('%s','2014-05-05 00:00:00') + s AS x,
STRFTIME('%s','2014-05-05 00:00:00') + s+3599 AS y
FROM (SELECT (a.d || b.d || c.d) * 3600 AS s FROM digit a, digit b, digit c LIMIT 168)
;
SELECT
COALESCE(SUM(Calls_count),0) AS SUM_CALLS,
CASE CAST(STRFTIME('%w',x,'unixepoch') AS INTEGER)
WHEN 0 THEN 7 ELSE STRFTIME('%w',x,'unixepoch') END
||
CASE
WHEN STRFTIME('%H:%M:%S',x,'unixepoch') BETWEEN '06:00:00' AND '11:59:59' THEN 1
WHEN STRFTIME('%H:%M:%S',x,'unixepoch') BETWEEN '12:00:00' AND '17:59:59' THEN 2
WHEN STRFTIME('%H:%M:%S',x,'unixepoch') BETWEEN '18:00:00' AND '23:59:59' THEN 3
WHEN STRFTIME('%H:%M:%S',x,'unixepoch') BETWEEN '00:00:00' AND '05:59:59' THEN 4
END AS Time_Slice,
((x-(SELECT MIN(x) FROM hours))/3600)+1 AS Hour_of_Week
FROM hours LEFT JOIN call_logs
ON call_logs.time_stamp >= hours.x AND call_logs.time_stamp <= hours.y
GROUP BY Hour_of_Week
ORDER BY Hour_of_Week
;

ORDER BY distance to another value

Lets say we have a table like that
id|value
--------
1 | 50
2 | 19
3 | 100
4 | 21
5 | -10
How can I use ORDER BY operator to order values by their distance to another value?
SELECT * FROM table ORDER BY nearest(value,30) DESC
To get this table:
id|value
--------
4 | 21
1 | 50
2 | 19
5 | -10
3 | 100
You may use:
SELECT * FROM table ORDER BY abs(value - 30) ASC
Not sure that all sql dialect accepts answer of Paul92.
Here is another solution:
SELECT *
FROM (
SELECT
t.*,
abs(value - 30) AS abs_value
FROM table t
) temp
ORDER BY abs_value