SQL Server - List single row multiple times in query result - sql

I have a dataset in a SQL Server database, and I'd like to list run a query to list records from a table, multiple times according to the respective integer value in a column of that table.
I've seen many of the posts about listing a record multiple times in a query, with most of the more suitable responses describing a solution involving Cross join. The situation I'm describing is slightly different from what I've seen of yet, however.
The table structure of the records is akin to the following:
+-----+-------+------+------+
| id | name | type | num |
+-----+-------+------+------+
| 1 | bob | red | 1 |
+-----+-------+------+------+
| 2 | sam | blue | 3 |
+-----+-------+------+------+
| 3 | viv | green| 2 |
+-----+-------+------+------+
I'd like to display this in a query result as follows...
+-----+-------+------+
| id | name | type |
+-----+-------+------+
| 1 | bob | red |
+-----+-------+------+
| 2 | sam | blue |
+-----+-------+------+
| 2 | sam | blue |
+-----+-------+------+
| 2 | sam | blue |
+-----+-------+------+
| 3 | viv | green|
+-----+-------+------+
| 3 | viv | green|
+-----+-------+------+
... where each record appears multiple times according to the number it is listed in the original row. (bob * 1, sam * 3, viv * 2)
Is this possible via cross-join or any other method available?
Note: This does not need to be terribly efficient.

You can try to use recursive CTE
;with cte as (
SELECT id,name,type,1 startnum,num
FROM T
UNION ALL
SELECT id,name,type , startnum+1,num
FROM cte
WHERE startnum+1<=num
)
SELECT id,name,type
FROM cte
order by id
sqlfiddle

Another option is an ad-hoc tally/numbers table
Example
Select A.ID
,A.[Name]
,A.[Type]
From YourTable A
Cross Apply (Select Top (A.[num]) N=Row_Number() Over (Order By (Select NULL)) From master..spt_values n1) B
Or a simple JOIN
Select A.ID
,A.[Name]
,A.[Type]
From YourTable A
Join (Select Top (100) N=Row_Number() Over (Order By (Select NULL)) From master..spt_values n1) B
on B.N<=A.[Num]
Both would Return
ID Name Type
1 bob red
2 sam blue
2 sam blue
2 sam blue
3 viv green
3 viv green

Use a Stored procedure
create a temporary table variable
Write insert into temporary table in a while loop
while counter < max(num)
insert into table where counter <=num
Finally select from table .

EDITED:
(Thanks, #MatBailie!)
Using a recursive CTE on the num column to generate the required number of repetitions, and doing a left-join:
Given table so1 as follows:
id name type num
1 bob red 1
2 sam blue 3
3 viv green 2
4 jon grey 7
Try:
WITH RECURSIVE exp_num AS
(
SELECT id, num FROM so1
UNION
SELECT id, num - 1 FROM exp_num WHERE num > 1
)
SELECT s1.* FROM so1 s1
LEFT JOIN
(
SELECT id FROM exp_num
) s2
ON s1.id = s2.id
Output:
id name type
1 bob red
2 sam blue
2 sam blue
2 sam blue
3 viv green
3 viv green
4 jon grey
4 jon grey
4 jon grey
4 jon grey
4 jon grey
4 jon grey
4 jon grey

Related

Calculate how many rows are ahead of position in column when condition is met

How can I calculate how many people are ahead of Jane on Floor 2 (not including those on floor 1)?
+------+---------+----------+
|Index | Name | Floor |
+------+---------+----------+
| 1 | Sally | 1 |
| 2 | Sue | 1 |
| 3 | Fred | 1 |
| 4 | Wally | 2 |
| 5 | Tommy | 2 |
| 6 | Jane | 2 |
| 7 | Bart | 2 |
| 8 | Sam | 3 |
+------+---------+----------+
The expected result is 2 as there are 2 people (Wally & Tommy) ahead of Jane on floor 2.
I've tried using CHARINDEX to find the row number from a temp table that I've generated but that doesn't seem to work:
SELECT CHARINDEX('Jane', Name) as position
INTO #test
FROM tblExample
WHERE Floor = 2
select ROW_NUMBER() over (order by position) from #test
WHERE position = 1
I think a simple row_number() would do the trick
Select Value = RN-1
From (
Select *
,RN = row_number() over (partition by [floor] order by [index])
From YourTable
Where [Floor]=2
) A
Where [Name]='Jane'
You could do:
select count(*)
from t
where t.floor = 2 and
t.id < (select t2.id from t t2 where t2.name = 'Jane' and t2.floor = 2);
With an index on (floor, name, id), I would expect this to be faster than row_number().

how to convert a table into another based on each Columns in SQL

I have the following table(each name is unique):
TABLE1:
+----+----------+----------------+----------------+----------------+----------------+
| id | workflow | tire1_approver | tire2_approver | tire3_approver | tire4_approver |
+----+----------+----------------+----------------+----------------+----------------+
| 1 | 1 | John | Mike | Tom | Kevin |
+----+----------+----------------+----------------+----------------+----------------+
| 2 | 2 | Mike | Andrew | An | Bob |
+----+----------+----------------+----------------+----------------+----------------+
I need to translate it into the following table, a person can appear more than once:
TABLE2:
+----+--------+----------------+-----------+----------------------+
| ID | Name | Position | Workflow | upper_level_approver |
+----+--------+----------------+-----------+----------------------+
| 1 | John | tire1_approver | 1 | Mike |
+----+--------+----------------+-----------+----------------------+
| 2 | Mike | tire2_approver | 1 | Tom |
+----+--------+----------------+-----------+----------------------+
| 3 | Tom | tire3_approver | 1 | Kevin |
+----+--------+----------------+-----------+----------------------+
| 4 | Kevin | tire4_approver | 1 | N/A |
+----+--------+----------------+-----------+----------------------+
| 5 | Mike | tire1_approver | 2 | Andrew |
+----+--------+----------------+-----------+----------------------+
| 6 | Andrew | tire2_approver | 2 | An |
+----+--------+----------------+-----------+----------------------+
| 7 | An | tire3_approver | 2 | Bob |
+----+--------+----------------+-----------+----------------------+
| 8 | Bob | tire4_approver | 2 | N/A |
+----+--------+----------------+-----------+----------------------+
I'm using sql developer, i have tried loop and join but aren't able to get to what I want.
TABLE1 and TABLE2 are both in the database,
Ultimately I would like to store this in the a stored Procedure, when the Front end makes an update to TABLE1, it also calls this procedure and automatically updates TABLE2.
please help
Here is a solution using a cross join (instead of the unpivot operator). This will work in older versions of Oracle. Simulated data and query output are the same as in my other answer (with unpivot).
The OP did not mention whether null is possible in the input table (in the approver columns). If it is possible, this cross join solution will handle them differently from the unpivot solution. The unpivot solution can be modified to produce the same result as the cross join, by using the optional include nulls directive in the unpivot clause. Or, if the null values should not be included, that can be handled in the cross join solution with a where condition.
select id, name, position, workflow,
lead(name, 1, 'N/A') over (partition by id, workflow order by lvl)
as upper_level_approver
from ( select t.id,
case h.lvl when 1 then t.tire1_approver
when 2 then t.tire2_approver
when 3 then t.tire3_approver
when 4 then t.tire4_approver
end
as name,
case h.lvl when 1 then 'tire1_approver'
when 2 then 'tire2_approver'
when 3 then 'tire3_approver'
when 4 then 'tire4_approver'
end
as position,
t.workflow,
h.lvl
from table1 t
cross join
( select level as lvl from dual connect by level <= 4 ) h
)
;
Depending on your Oracle version, you may or may not be able to use the solution below. It uses the UNPIVOT operator, so it requires Oracle 11.2 or higher. In earlier versions, you can use a cross join.
with
table1 ( id, workflow, tire1_approver, tire2_approver,
tire3_approver, tire4_approver )
as (
select 1, 1, 'John', 'Mike' , 'Tom', 'Kevin' from dual union all
select 2, 2, 'Mike', 'Andrew', 'An' , 'Bob' from dual
)
-- End of simulated table (for testing; not part of the solution).
-- SQL query begins BELOW THIS LINE
select id, name, position, workflow,
lead(name, 1, 'N/A') over ( partition by id, workflow order by lvl )
as upper_level_approver
from table1
unpivot ( name for (position, lvl) in ( tire1_approver as ('tire1_approver', 1),
tire2_approver as ('tire2_approver', 2),
tire3_approver as ('tire3_approver', 3),
tire4_approver as ('tire4_approver', 4)
)
)
;
ID NAME POSITION WORKFLOW UPPER_LEVEL_APPROVER
-- ------ -------------- --------- --------------------
1 John tire1_approver 1 Mike
1 Mike tire2_approver 1 Tom
1 Tom tire3_approver 1 Kevin
1 Kevin tire4_approver 1 N/A
2 Mike tire1_approver 2 Andrew
2 Andrew tire2_approver 2 An
2 An tire3_approver 2 Bob
2 Bob tire4_approver 2 N/A

SQL count how many times a value appears across multiple columns?

Below is an example of a table in our CRM, its not the way I'd have chosen to store this data but thats by the by, What would be the 'nice' way to count how many times each option was selected by each team?
asking here before i go headlong into a convoluted case statement :)
+----------+--------+---------+---------+---------+
| PersonID | Team | Option1 | Option2 | Option3 |
+----------+--------+---------+---------+---------+
| 1 | Blue | A | B | C |
| 2 | Blue | B | C | D |
| 3 | Blue | D | A | E |
| 4 | Red | A | B | D |
| 5 | Red | B | A | C |
| 6 | Yellow | A | B | C |
| 7 | Yellow | A | C | D |
+----------+--------+---------+---------+---------+
Thanks in advance
You can unpivot your 3 option columns into a single column using CROSS APPLY and a table value constructor and then perform your count:
SELECT t.Team, upvt.[Option], COUNT(*) AS Occurances
FROM dbo.T
CROSS APPLY (VALUES (t.Option1), (t.Option2), (t.Option3)) AS upvt ([Option])
GROUP BY t.Team, upvt.[Option]
ORDER BY t.Team, upvt.[Option];
So this would give:
Team Option Occurances
-------------------------------
Blue A 2
Blue B 2
Blue C 2
Blue D 2
Blue E 1
Red A 2
Red B 2
Red C 1
Red D 1
Yellow A 2
Yellow B 1
Yellow C 2
Yellow D 1
You can use UNION ALL to move the values into single column and then, do aggregation:
select
team, option, count(*) cnt
from(
select team, option1 option from t union all
select team, option2 from t union all
select team, option3 from t
)t group by team, option;

Find duplicate combinations

I need a query to find duplicate combinations in these tables:
AttributeValue:
id | name
------------------
1 | green
2 | blue
3 | red
4 | 100x200
5 | 150x200
Product:
id | name
----------------
1 | Produkt A
ProductAttribute:
id | id_product | price
--------------------------
1 | 1 | 100
2 | 1 | 200
3 | 1 | 100
4 | 1 | 200
5 | 1 | 100
6 | 1 | 200
7 | 1 | 100 -- duplicate combination
8 | 1 | 100 -- duplicate combination
ProductAttributeCombinations:
id_product_attribute | id_attribute
-------------------------------------
1 | 1
1 | 4
2 | 1
2 | 5
3 | 2
3 | 4
4 | 2
4 | 5
5 | 3
5 | 4
6 | 3
6 | 5
7 | 1
7 | 4
8 | 1
8 | 5
I need SQL that creates result like:
id_product | duplicate_attributes
----------------------------------
1 | {7,8}
If I understand correct, 7 is a duplicate of 1 and 8 is a duplicate of 2. As phrased, your question is a bit confusing, because 7 and 8 are not related to each other and the only table of interest is ProductAttributeCombinations.
If this is the case, then one method is to use string aggregation
with combos as (
select id_product_attribute,
string_agg(id_attribute::text, ',' order by id_attribute) as combo
from ProductAttributeCombinations pac
group by id_product_attribute
)
select *
from combos c
where exists (select 1
from combos c2
where c2.id_product_attribute > c.id_product_attribute and
c2.combo = c.combo
);
Your question leaves some room for interpretation. Here is my educated guess:
For each product, return an array of all instances with the same set of attributes as any other instance of the same product with smaller ID.
WITH combo AS (
SELECT id_product, id, array_agg(id_attribute) AS attributes
FROM (
SELECT pa.id_product, pa.id, pac.id_attribute
FROM ProductAttribute pa
JOIN PoductAttributeCombinations pac ON pac.id_product_attribute = pa.id
ORDER BY pa.id_product, pa.id, pac.id_attribute
) sub
GROUP BY 1, 2
)
SELECT id_product, array_agg(id) AS duplicate_attributes
FROM combo c
WHERE EXISTS (
SELECT 1
FROM combo
WHERE id_product = c.id_product
AND attributes = c.attributes
AND id < c.id
)
GROUP BY 1;
Sorting can be inlined into the aggregate function so we don't need a subquery for the sort (like #Gordon already provided). This is shorter, but also typically slower:
WITH combo AS (
SELECT pa.id_product, pa.id
, array_agg(pac.id_attribute ORDER BY pac.id_attribute) AS attributes
FROM ProductAttribute pa
JOIN PoductAttributeCombinations pac ON pac.id_product_attribute = pa.id
GROUP BY 1, 2
)
SELECT ...
This only returns products with duplicate instances.
SQL Fiddle.
Your table names are rather misleading / contradict the rest of your question. Your sample data is not very clear either, only featuring a single product. I assume there are many in your table.
It's also unclear whether you are using double-quoted table names preserving CaMeL-case spelling. I assume: no.

SQL Get row number from the main table

Using SQL, how can I get the actual row number of the data from the Master table, when I combined two tables (Master-table and Table-2). Please see illustration below:
Table-1 (Master Table)
Name Age
---------- ------
1 Ruth | 45
2 Jennifer | 52
3 Isabel | 29
4 Jo | 59
5 Dan | 35
6 Lem | 26
Table-2
Name Color
---------- ------
1 Ruth | Blue
2 Jennifer | Blue
3 Isabel | Red
4 Jo | Blue
5 Dan | Red
6 Lem | Blue
The result I want to get: (1) get records where color is blue, (2) new column (Row_Number from Table-1):
Name Age Color Row_Number
---------- ------ -------- ------------
1 Ruth | 45 | Blue | 1
2 Jennifer | 52 | Blue | 2
3 Jo | 59 | Blue | 4
4 Lem | 26 | Blue | 6
This would solve your issue :
with all_users as
(
select *, row_number over(order by name/*order by whatever you want*/) as seq
from table1
) select *, all_users.seq
from table2
join all_users using(name);
You could also use a subquery, but I prefer with when using Oracle.
use row_nummber(), but you need to specify your sorting condition
select t.name,t.age,tbl.color,tbl.rn as rownumber
from table2 t
join (select name,color,row_nummber() over (order by name) as rn from table1) tbl
on t.name=tbl.name
EDIT: and if you you don't have any sort condition (as mentioned in comment) then you can simply use rownum without specifying sort condition:
select t.name,t.age,tbl.color,tbl.rn as rownumber
from table2 t
join (select name,color,rownum as rn from table1) tbl
on t.name=tbl.name