How to select as much records as indicated by the value from database - sql

I have one table in relational database Sybase ASE, with few columns. Three of them looks like this example:
_____________
| Product |
---------------
| ProductId |
| Name |
| Quantity |
_____________
So we have some records:
__________________________________
| ProductId | Name | Quantity |
----------------------------------
| 1 | pants | 2 |
| 2 | shirt | 1 |
| 3 | sweater | 3 |
----------------------------------
I need to get every name as many times as 'Quantity' of this product.
So the result should looks like:
pants
pants
shirt
sweater
sweater
sweater
If somebody have any idea how can I do this, please help me.
EDIT
2014-01-24 14:17 UTC+1
I'd like to thanks everybody. Gordon's solution is realy nice, but for my situation (bigger Quantity) I can't use that sql. I try do somethnig like 333kenshin's and simon's solutions but without cursor. I do somthnig like this:
IF OBJECT_ID('#TEMP') is not null
DROP TABLE #TEMP
create TABLE #TEMP (Name varchar(255))
DECLARE #Name varchar(255)
DECLARE #Quant INT
DECLARE #prodId INT
SET #prodId = 1
WHILE (EXISTS(SELECT 1 FROM product WHERE productID = #prodId))
BEGIN
SELECT
#Name = Name
#Quant = Quantity
FROM Product
DECLARE #i INT
SET #i = 1
WHILE #i <= #Quant
BEGIN
insert into #TEMP
values(#Name)
SELECT #i=#i+1
END
SELECT #prodId = #prodId + 1
END
select * from #TEMP
drop table #TEMP
For me, and my DB it was fastest solution. So thanks a lot for every answers.

To do this, you need a series of integers. You can generate one manually:
select p.name
from product p join
(select 1 as n union all select 2 union all select 3 union all select 4
) n
on n.n <= p.quantity;
This will work if quantity is not too big and you can put in the values in n.

The correct way to do this is temp table + cursors:
create a temp table
create cursor to iterate through Product table
within the cursor, create an inner WHILE loop
exit the loop and finally select the temp table
The following isn't 100% correct Sybase syntax, but it's pretty close.
-- 1: temp table
select productName into #TEMP
-- 2: cursor
declare
#productName char(10),
#quantity int
declare ProductRead CURSOR for
select
productName,
quantity
from
Product
OPEN ProductRead
FETCH ProductRead
INTO
#productName,
#quantity
WHILE (##sqlstatus=0)
BEGIN
-- 3: inner for loop
DECLARE #i INT
SET #i = 1
WHILE #i <= #quantity
BEGIN
insert #productName into #TEMP
END
END
-- 4: final result set
select productName from #TEMP

You could use a temporary table. Select all your product rows, then loop through each row returned. For each product, loop Quantity times and in the loop insert the product into the temp table. I'm not a Sybase user, so I can't give you the syntax but you would have a stored procedure which would do something like:
select all rows from product into a cursor
for each row
for i = 1 to row.Quantity
insert into temp (Name) values (row.Name)
next
end loop
select * from temp and return it
It's a pretty eccentric requirement, though.
EDIT: Gordon's solution is neat though! If n never gets too big I'd go with that!

Related

Fetch specific column and row data based on row values from another table in SQL server

Does anyone know if it is possible to fetch data from a column in a table based on row values from another table?
e.g.
Table 1:
Name| Date
----|-----
Bob | D1
Jon | D2
Stu | D3
Amy | D4
Table 2:
Date |Bob |Jon |Stu |Amy
-----|----|----|----|----
D1 | A | B | C | D
D2 | B | C | D | A
D3 | C | D | A | B
D4 | D | A | B | C
I need to match the date but bring through the correct letter for each name
So Table 3 would be:
Name| Date | Letter
----|------|-------
Bob | D1 | A
Jon | D2 | C
Stu | D3 | A
Amy | D4 | C
Any suggestions are welcome.
thanks
If you are looking for way without column hardcodes, you can try this.
Lets your tables has names #table1, #table2. Then:
select
[Name] = [t1].[Name]
,[Date] = [t1].[Date]
,[Letter] = [col2].[value]('.', 'varchar(10)')
from
#table1 as [t1]
cross apply
(
select [t2_xml] = cast((select * from #table2 for xml path('t2')) as xml)
) as [t2]
cross apply
[t2].[t2_xml].[nodes]('t2[Date/text()=sql:column("[t1].[Date]")]') as [tab]([col])
cross apply
[col].[nodes]('*[local-name(.)=sql:column("[t1].[Name]")]') as [tab2]([col2]);
There are many ways to achieve the desired output. My solution uses a combination of cursors and dynamic TSQL.
Here is the code, commented step by step:
--1. create test tables
create table table1 ([Name] nvarchar(50),[Date] nvarchar(50))
create table table2 ([Date] nvarchar(50),Bob nvarchar(50),Jon nvarchar(50),Stu nvarchar(50),Amy nvarchar(50))
create table table3 ([Name] nvarchar(50),[Date] nvarchar(50),[Letter] nvarchar(50))
--2. populate test tables
insert into table1
select 'Bob','D1'
union all select 'Jon','D2'
union all select 'Stu','D3'
union all select 'Amy','D4'
insert into table2
select 'D1','A','B','C','D'
union all select 'D2','B','C','D','A'
union all select 'D3','C','D','A','B'
union all select 'D4','D','A','B','C'
--3. declare variables
DECLARE #query NVARCHAR(max); --this variable will hold the dynamic TSQL query
DECLARE #name NVARCHAR(50);
DECLARE #date NVARCHAR(50);
DECLARE #result NVARCHAR(50) --this variable will hold "letter" value returned by dynamic TSQL query
DECLARE #testCursor CURSOR;
--4. define the cursor that will scan all rows in table1
SET #testCursor = CURSOR FOR SELECT [Name], [Date] FROM table1;
OPEN #testCursor;
FETCH NEXT FROM #testCursor INTO #name, #date;
WHILE ##FETCH_STATUS = 0
BEGIN
--5. for each row in table 1 create a dynamic query that retrieves the correct "Letter" value from table2
set #query = 'select #res=' + #name + ' from table2 where [Date] =''' + #date +''''
--6. executes dynamic TSQL query saving result in #result variable
EXECUTE sp_executesql #query, N'#res nvarchar(50) OUTPUT', #res=#result OUTPUT
--inserts data in table3 that holds final results
insert into table3 select #name, #date, #result
FETCH NEXT FROM #testCursor INTO #name, #date;
END
CLOSE #testCursor;
DEALLOCATE #testCursor;
select * from table1
select * from table2
select * from table3
Here are the results. The first two tables show the inputs, the third table contains the actual results:

Insert into table using a select statement and a while loop

I need to insert values into a table from another table. I also want to use a while loop to update a row in my table at the same time. Below you can see my query.
declare #id int
select #id = 1
while #id >=1 and #id <= 3
begin
INSERT INTO [dbo].[TEST]
([ID_PRODUCT],[PRODUCTID],[PRODUCTDESC],[COUNT]
select distinct
ID_PRODUCT,PRODUCTID,PRODUCTDESC,#id
from SAMPLES
select #id = #id + 1
end
This works but not as i was expecting. Instead of giving me three rows affected, it gives me three rows affected, three times. So i end up with nine new rows instead of the desired three i want.
ID_PRODUCT PRODUCTID PRODUCTDESC COUNT
35746 136559 Desc1 1
35747 276732 Desc2 1
35748 259910 Desc3 1
35746 136559 Desc1 2
35747 276732 Desc2 2
35748 259910 Desc3 2
35746 136559 Desc1 3
35747 276732 Desc2 3
35748 259910 Desc3 3
What i want to acheive is this :
ID_PRODUCT PRODUCTID PRODUCTDESC COUNT
35746 136559 Desc1 1
35747 276732 Desc2 2
35748 259910 Desc3 3
Can anyone see what im doing wrong?
The SELECT part of Insert statement always returns same records
#id does not change the selected rows, only inserts same data set with different #id values
select distinct
ID_PRODUCT,PRODUCTID,PRODUCTDESC,#id
from SAMPLES
You are inserting three times same list of rows with different #id.
I guess you actually mean this:
;with DistinctSampleValues as
(
select distinct
ID_PRODUCT,PRODUCTID,PRODUCTDESC
from SAMPLES
)
insert into [dbo].[TEST] ([ID_PRODUCT],[PRODUCTID],[PRODUCTDESC],[COUNT])
select
ID_PRODUCT,PRODUCTID,PRODUCTDESC,
ROW_NUMBER() OVER(ORDER BY ID_PRODUCT) RN
from DistinctSampleValues
one time insert all distinct values with additional "row number".
You are close. Add top 1 and where clause like ID_PRODUCT not in Test table
declare #id int
select #id = 1
while #id >=1 and #id <= 3
begin
INSERT INTO [dbo].[TEST]
([ID_PRODUCT],[PRODUCTID],[PRODUCTDESC],[COUNT]
SELECT DISTINCT TOP 1
ID_PRODUCT,
PRODUCTID,
PRODUCTDESC,
#id
from
SAMPLES S
WHERE
S.ID_PRODUCT NOT IN
(
SELECT T.ID_PRODUCT FROM [TEST] T
)
select #id = #id + 1
end
Eralper and Ivan Starostin both answered correctly and it is right solution for you.
To run your code correctly, you need to add WHERE clause.
declare #id int
select #id = 1
while #id >=1 and #id <= 3
begin
INSERT INTO [dbo].[TEST]
([ID_PRODUCT],[PRODUCTID],[PRODUCTDESC],[COUNT]
select distinct
ID_PRODUCT,PRODUCTID,PRODUCTDESC,#id
from
SAMPLES s
WHERE Count = #id
AND NOT EXISTS(SELECT 1 FROM Test t WHERE t.ID_PRODUCT = s.ID_PRODUCT
AND t.PRODUCTID = s.PRODUCTID)
select #id = #id + 1
end

How can I update strings within an SQL server table based on a query?

I have two tables A and B. A has an Id and a string with some embedded information for some text and ids from a table C that is not shown
Aid| AString
1 "<thing_5"><thing_6">"
2 "<thing_5"><thing_6">"
Bid|Cid|Aid
1 5 1
2 6 1
3 5 2
4 6 2
I realise this is an insane structure but that is life.
I need to update the strings within A so that instead of having the Cid they have the corresponding Bid (related by the Aid and Bid pairing)
Is this even something I should be thinking of doing in SQL... A has about 300 entries and B about 1200 so not something doing by hand
For clarity I wish for B to remain the same and A to finally look like this
Aid| AString
1 "<thing_1"><thing_2">"
2 "<thing_3"><thing_4">"
This script relies on generating dynamic SQL statements to update the table, then executes those statements.
Taking into account that the cid's are within thing_ and ":
First replaces the cid's using a placeholder ($$$$$$ in this case) to account for the fact that cid's and bid's may overlap (example, changing 3->2 and later 2->1)
Then changes the placeholders to the proper bid
CREATE TABLE #a(aid INT,astr VARCHAR(MAX));
INSERT INTO #a(aid,astr)VALUES(1,'<thing_5"><thing_6">'),(2,'<thing_5"><thing_6">');
CREATE TABLE #rep(aid INT,bid INT,cid INT);
INSERT INTO #rep(bid,cid,aid)VALUES(5,6,1),(6,5,1),(3,5,2),(4,6,2);
DECLARE #cmd NVARCHAR(MAX)=(
SELECT
'UPDATE #a '+
'SET astr=REPLACE(astr,''thing_'+CAST(r.cid AS VARCHAR(16))+'"'',''thing_$$$$$$'+CAST(r.cid AS VARCHAR(16))+'"'') '+
'WHERE aid='+CAST(a.aid AS VARCHAR(16))+';'
FROM
(SELECT DISTINCT aid FROM #a AS a) AS a
INNER JOIN #rep AS r ON
r.aid=a.aid
FOR
XML PATH('')
);
EXEC sp_executesql #cmd;
SET #cmd=(
SELECT
'UPDATE #a '+
'SET astr=REPLACE(astr,''thing_$$$$$$'+CAST(r.cid AS VARCHAR(16))+'"'',''thing_'+CAST(r.bid AS VARCHAR(16))+'"'') '+
'WHERE aid='+CAST(a.aid AS VARCHAR(16))+';'
FROM
(SELECT DISTINCT aid FROM #a AS a) AS a
INNER JOIN #rep AS r ON
r.aid=a.aid
FOR
XML PATH('')
);
EXEC sp_executesql #cmd;
SELECT * FROM #a;
DROP TABLE #rep;
DROP TABLE #a;
Result is:
+-----+----------------------+
| aid | astr |
+-----+----------------------+
| 1 | <thing_6"><thing_5"> |
| 2 | <thing_3"><thing_4"> |
+-----+----------------------+
You could do this with SQL with something like below. It wasn't clear to me how c was related, but you can adjust it as necessary...
create table a (
Aid int null,
AString varchar(25) null)
insert into a values(1,'"<thing_5"><thing_6">"')
insert into a values(2,'"<thing_5"><thing_6">"')
create table b (
Aid int null,
Bid int null,
Cid int null)
insert into b values(1,1,5)
insert into b values(1,2,6)
insert into b values(2,3,5)
insert into b values(2,4,6)
UPDATE Ax
SET Ax.ASTRING = REPLACE(Ax.ASTRING, 'thing_' + cast(cID as varchar(1)),'thing_' + cast(BID as varchar(1)))
FROM A Ax
INNER JOIN Bx
on ax.Aid=bx.Aid
and Ax.AString like '%thing_' + cast(Cid as varchar(1)) + '%'

Sorting results of SQL query just like IN parameter list

I'm doing a query which looks something like
SELECT id,name FROM table WHERE id IN (2,1,4,3)
I'd like to get
id name
2 B
1 A
4 D
3 C
but I'm getting
1 A
2 B
3 C
4 D
Is there any way to sort the query results in the same way as the list I'm including after IN?
Believe me, I have a practical reason that I would need it for ;)
SELECT id,name FROM table WHERE id IN (2,1,4,3)
ORDER BY CASE id
WHEN 2 THEN 1
WHEN 1 THEN 2
WHEN 4 THEN 3
WHEN 3 THEN 4
ELSE 5
END
This might solve your problem.
Solution 2, insert your list into a temp table and get them a running sequence
id, seq(+1 every new row added)
-----------------
2 1
1 2
4 3
3 4
then join 2 table together and order by this seq.
Okay, I did it myself. It's a bit mad but it works ;)
DECLARE #IDs varchar(max)
DECLARE #nr int
DECLARE #znak varchar(1)
DECLARE #index int
DECLARE #ID varchar(max)
SET #IDs='7002,7001,7004,7003'
SET #nr=1
SET #index=1
IF OBJECT_ID('tempdb..#temp') IS NOT NULL DROP TABLE #temp
CREATE TABLE #temp (nr int, id int)
--fill temp table with Ids
WHILE #index<=LEN(#Ids)
BEGIN
set #znak=''
set #ID=''
WHILE #znak<>',' AND #index<=LEN(#Ids)
BEGIN
SET #znak= SUBSTRING(#IDs,#index,1)
IF #znak<>',' SET #ID=#ID+#znak
SET #index=#index+1
END
INSERT INTO #temp(nr,id) VALUES (#nr,CAST(#ID as int))
SET #nr=#nr+1
END
-- select proper data in wanted order
SELECT MyTable.* FROM MyTable
INNER JOIN #temp ON MyTable.id=#temp.id
ORDER BY #temp.nr

SQL Server 2008 + Creating a cross-tab stored procedure

I have two tables that I want to join and create a crosstab table in SQL 2008:
TableA:
Auto_ID | Fiscal_Period | Amount
1 | 01012012 | NULL
1 | 01022012 | 80
1 | 01032012 | NULL
2 | 01012012 | NULL
2 | 01022012 | 10
TABLEB:
Auto_ID | Row_ID | StaticData
1 | 1 | sampledata
2 | 2 | data1
I would like to use cross table to dynamic create the following table structure:
Row_ID | StaticData | FiscalPeriod(01012012) | FiscalPeriod(01022012) | FiscalPeriod(01032012)
1 | sampledata | NULL | 80 | NULL
2 | data1 | NULL | 10 | NULL
My current query joins the tables correctly; however, I am having difficulty transposing the fiscal periods into my header row.
SELECT *
FROM (SELECT
B.Row_Id as RowID, B.StaticData as StaticData, A.Fiscal_Period AS FPPD
FROM TableA A
LEFT JOIN TableB B ON A.Auto_ID = B.Auto_ID)
This is what I would do:
First create some test data:
CREATE TABLE tblA (Auto_ID INT,Fiscal_Period VARCHAR(100),Amount FLOAT)
CREATE TABLE tblB (Auto_ID INT,Row_ID INT,StaticData VARCHAR(100))
INSERT INTO tblA
SELECT 1,'01012012',NULL UNION ALL
SELECT 1,'01022012',80 UNION ALL
SELECT 1,'01032012',NULL UNION ALL
SELECT 2,'01012012',NULL UNION ALL
SELECT 2,'01022012',10
INSERT INTO tblB
SELECT 1,1,'sampledata' UNION ALL
SELECT 2,2,'data1'
Then find the unique columns :
DECLARE #cols VARCHAR(MAX)
;WITH CTE
AS
(
SELECT
ROW_Number() OVER(PARTITION BY tblA.Fiscal_Period ORDER BY tblA.Fiscal_Period) AS RowNbr,
tblA.Fiscal_Period
FROM
tblA AS tblA
)
SELECT
#cols = COALESCE(#cols + ','+QUOTENAME('FiscalPeriod('+Fiscal_Period+')'),
QUOTENAME('FiscalPeriod('+Fiscal_Period+')'))
FROM
CTE
WHERE
CTE.RowNbr=1
Then execute a pivot with dynamic sql:
DECLARE #query NVARCHAR(4000)=
N'SELECT
*
FROM
(
SELECT
tblB.Row_ID,
tblb.StaticData,
''FiscalPeriod(''+tblA.Fiscal_Period+'')'' AS Name,
tblA.Amount
FROM
tblA AS tblA
JOIN tblB AS tblB
ON tblA.Auto_ID=tblB.Auto_ID
) AS p
PIVOT
(
SUM(Amount)
FOR Name IN ('+#cols+')
) AS Pvt'
EXECUTE(#query)
Then in my case I will drop the temp tables:
DROP TABLE tblA
DROP TABLE tblB
I hope this will help you
Since you didn't specify a flavour of database, please mind, that the following is valid only for MySQL!
A cross-tab query is possible only with a very dirty trick, it is only practically feasable in a stored proc.
You start by thinking out some way, to transform a list of fiscal periods into SQL, something like
SELECT
TABLEB.Row_ID,
TABLEB.staticdata
,fp01012012.Amount as fp01012012amount
,fp01022012.Amount as fp01022012amount
,fp01032012.Amount as fp01032012amount
FROM
TABLEB
LEFT JOIN TableA AS fp01012012 ON fp01012012.Auto_ID=TABLEB.Auto_ID AND fp01012012.Fiscal_Period='01012012'
LEFT JOIN TableA AS fp01022012 ON fp01022012.Auto_ID=TABLEB.Auto_ID AND fp01022012.Fiscal_Period='01022012'
LEFT JOIN TableA AS fp01032012 ON fp01032012.Auto_ID=TABLEB.Auto_ID AND fp01032012.Fiscal_Period='01032012'
Which you now have to build as dynamic SQL - this is feasable only in a stored proc.
DELIMITER $$
DROP PROCEDURE IF EXISTS `create_fiscal_data`$$
CREATE PROCEDURE `create_fiscal_data` ()
BEGIN
DECLARE dynfields VARCHAR(10000) DEFAULT 'SELECT TABLEB.Row_ID, TABLEB.staticdata';
DECLARE dynfrom VARCHAR(10000) DEFAULT ' FROM TABLEB';
DECLARE period VARCHAR(10) DEFAULT '';
DECLARE done INT DEFAULT 0;
DECLARE id INT DEFAULT 7;
DECLARE periods CURSOR FOR SELECT DISTINCT Fiscal_Period FROM TableA;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done=1;
OPEN periods;
cycleperiods: LOOP
FETCH periods INTO period;
IF done=1 THEN LEAVE cycleperiods; END IF;
SET dynfields=CONCAT(dynfields,',`fp',period,'`.Amount AS `fp',period,'amount`');
SET dynfrom=CONCAT(dynfrom,' LEFT JOIN TableA AS `fp',period,'` ON `fp',period,'`.Auto_ID=TABLEB.Auto_ID AND `fp',period,'`.Fiscal_Period="',period,'"');
END LOOP;
CLOSE periods;
SELECT #dynsql:=CONCAT(dynfields,dynfrom) INTO dynfields;
-- Here comes the trick!
PREPARE dynqry FROM #dynsql;
EXECUTE dynqry;
END$$
DELIMITER ;
The trick is, to build the SQL into the variable #dynsql (DECLAREd variables won't work), then prepare and execute it.
Now the query
CALL `create_fiscal_data;`
Will create the output you need.