Related
I am trying to get data from one table in DB-Server-1, and pass it to a StoredProcedure being executed on DB-SERVER-2 (that joins to this data from DB-Server-1).
So,
I have 2 Tables on Different servers :
DB-Server-1 : tblItem
-------------------
item_id | item_qty
-------------------
1231 | 2
1232 | 4
-------------------
DB-Server-2 : tblItemDetails
----------------------------------------
item_detail_id | item_id | item_data
----------------------------------------
1 | 1231 | TEST_DATA_1
2 | 1232 | TEST_DATA_2
----------------------------------------
Now I want to get data from DB-Server-1 (Basic Select Query)
SELECT item_id, item_qty
FROM tblItem
WHERE item_id IN (1231, 1232);
AND Pass it to a Stored Procedure : spSetItemQuantityInItemDetails
CREATE PROCEDURE [dbo].[spUpdatePOCartons]
(
#valueList VARCHAR(MAX)
)
.
.
-- Now trying to split string and create a temp table
-- This temp table will later be used in the SP to join with tblItemDetails to set item_qty
DECLARE #temp TABLE (
item_id int,
item_qty int
);
DECLARE #pos1 INT
DECLARE #len1 INT
DECLARE #value1 varchar(8000)
DECLARE #pos2 INT
DECLARE #len2 INT
DECLARE #value2 varchar(8000)
SET #valueList = '1,4;2,5;3,14;';
set #pos1 = 0
set #len1 = 0
WHILE CHARINDEX(';', #valueList, #pos1+1)>0
BEGIN
set #len1 = CHARINDEX(';', #valueList, #pos1+1) - #pos1
set #value1 = SUBSTRING(#valueList, #pos1, #len1)
--SELECT #pos, #len, #value /*this is here for debugging*/
PRINT #value1;
-----------
set #pos2 = 0
set #len2 = 0
WHILE CHARINDEX(',', #value1, #pos2+1)>0
BEGIN
set #len2 = CHARINDEX(',', #value1, #pos2+1) - #pos2
set #value2 = SUBSTRING(#value1, #pos2, #len2)
--SELECT #pos, #len, #value /*this is here for debugging*/
PRINT #value2;
set #pos2 = CHARINDEX(',', #value1, #pos2+#len2) +1
END
------------
set #pos1 = CHARINDEX(';', #valueList, #pos1+#len1) +1
END
Issue
I am trying to understand if the above solution is closest to what would work (currently it is not splitting last value of string which does not end with ; or , )
OR is there some better approach
I think a better approach might be to pass a table variable directly to the stored procedure. In order to do so, you'd have to create a type that creates a structure for the input table. I recreated this scenario below. Let me know if you can work with this to achieve your goals.
--Create test tables
CREATE TABLE dbo.tblItem (item_id int, item_qty int);
CREATE TABLE dbo.tblItem2 (item_detail_id int, item_id int, item_details varchar(50));
GO
--Create the type that can be used as input
CREATE TYPE TableForInput as TABLE
(
Item_Id int,
Item_Qty int
);
GO
--Create/alter the stored procedure
CREATE PROCEDURE dbo.usp_GetCardData
#Input TableForInput READONLY
AS
BEGIN
SELECT * FROM dbo.tblItem2 as t2 INNER JOIN #Input as i ON t2.Item_id = i.Item_id
END
GO
--Insert dummy data into the test tables
INSERT INTO dbo.tblItem values (1231, 2), (1232, 4);
INSERT INTO dbo.tblItem2 VALUES (1, 1231, 'TEST_1'), (2, 1232, 'TEST2')
-- The below would mimic the client side.
-- Declare a local temporary table, select data into it
-- And then pass this as a parameter to the stored proc
DECLARE #Data TableForInput;
INSERT INTO #Data
SELECT * FROM dbo.tblItem;
exec dbo.usp_GetCardData #Data
Thanks a lot everyone. Your insights really helped get to the solution.
I was able to get the expected behaviour via :
IF OBJECT_ID('tempdb..#tval') IS NOT NULL DROP TABLE #tval;
CREATE TABLE #tval (val1 INT, val2 INT);
declare #s varchar(1000)
set #s = '1,11;2,22'
;WITH cte AS (
select value
from string_split(#s, ';')
)
INSERT INTO #tval
SELECT
MAX(CASE WHEN ord=1 THEN v END),
MAX(CASE WHEN ord=2 THEN v END)
FROM cte
CROSS APPLY (SELECT value AS v, ROW_NUMBER() OVER(ORDER BY 1/0) AS ord
FROM STRING_SPLIT([value], ',')) s
GROUP BY value;
SELECT * from #tval
I have a field COLORS (varchar(50)) in a my table SHIRTS that contains a comma delimited string such as 1,2,5,12,15,. Each number representing the available colors.
When running the query select * from shirts where colors like '%1%' to get all the red shirts (color=1), I also get the shirts whose color is grey (=12) and orange (=15).
How should I rewrite the query so that is selects ONLY the color 1 and not all colors containing the number 1?
The classic way would be to add commas to the left and right:
select * from shirts where CONCAT(',', colors, ',') like '%,1,%'
But find_in_set also works:
select * from shirts where find_in_set('1',colors) <> 0
FIND_IN_SET is your friend in this case
select * from shirts where FIND_IN_SET(1,colors)
Take a look at the FIND_IN_SET function for MySQL.
SELECT *
FROM shirts
WHERE FIND_IN_SET('1',colors) > 0
This will work for sure, and I actually tried it out:
lwdba#localhost (DB test) :: DROP TABLE IF EXISTS shirts;
Query OK, 0 rows affected (0.08 sec)
lwdba#localhost (DB test) :: CREATE TABLE shirts
-> (<BR>
-> id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
-> ticketnumber INT,
-> colors VARCHAR(30)
-> );<BR>
Query OK, 0 rows affected (0.19 sec)
lwdba#localhost (DB test) :: INSERT INTO shirts (ticketnumber,colors) VALUES
-> (32423,'1,2,5,12,15'),
-> (32424,'1,5,12,15,30'),
-> (32425,'2,5,11,15,28'),
-> (32426,'1,2,7,12,15'),
-> (32427,'2,4,8,12,15');
Query OK, 5 rows affected (0.06 sec)
Records: 5 Duplicates: 0 Warnings: 0
lwdba#localhost (DB test) :: SELECT * FROM shirts WHERE LOCATE(CONCAT(',', 1 ,','),CONCAT(',',colors,',')) > 0;
+----+--------------+--------------+
| id | ticketnumber | colors |
+----+--------------+--------------+
| 1 | 32423 | 1,2,5,12,15 |
| 2 | 32424 | 1,5,12,15,30 |
| 4 | 32426 | 1,2,7,12,15 |
+----+--------------+--------------+
3 rows in set (0.00 sec)
Give it a Try !!!
If the set of colors is more or less fixed, the most efficient and also most readable way would be to use string constants in your app and then use MySQL's SET type with FIND_IN_SET('red',colors) in your queries. When using the SET type with FIND_IN_SET, MySQL uses one integer to store all values and uses binary "and" operation to check for presence of values which is way more efficient than scanning a comma-separated string.
In SET('red','blue','green'), 'red' would be stored internally as 1, 'blue' would be stored internally as 2 and 'green' would be stored internally as 4. The value 'red,blue' would be stored as 3 (1|2) and 'red,green' as 5 (1|4).
select * from shirts where find_in_set('1',colors) <> 0
Works for me
If you're using MySQL, there is a method REGEXP that you can use...
http://dev.mysql.com/doc/refman/5.1/en/regexp.html#operator_regexp
So then you would use:
SELECT * FROM `shirts` WHERE `colors` REGEXP '\b1\b'
You should actually fix your database schema so that you have three tables:
shirt: shirt_id, shirt_name
color: color_id, color_name
shirtcolor: shirt_id, color_id
Then if you want to find all of the shirts that are red, you'd do a query like:
SELECT *
FROM shirt, color
WHERE color.color_name = 'red'
AND shirt.shirt_id = shirtcolor.shirt_id
AND color.color_id = shirtcolor.color_id
You can achieve this by following function.
Run following query to create function.
DELIMITER ||
CREATE FUNCTION `TOTAL_OCCURANCE`(`commastring` TEXT, `findme` VARCHAR(255)) RETURNS int(11)
NO SQL
-- SANI: First param is for comma separated string and 2nd for string to find.
return ROUND (
(
LENGTH(commastring)
- LENGTH( REPLACE ( commastring, findme, "") )
) / LENGTH(findme)
);
And call this function like this
msyql> select TOTAL_OCCURANCE('A,B,C,A,D,X,B,AB', 'A');
1. For MySQL:
SELECT FIND_IN_SET(5, columnname) AS result
FROM table
2.For Postgres SQL :
SELECT *
FROM TABLENAME f
WHERE 'searchvalue' = ANY (string_to_array(COLUMNNAME, ','))
Example
select *
from customer f
where '11' = ANY (string_to_array(customerids, ','))
All the answers are not really correct, try this:
select * from shirts where 1 IN (colors);
I have a field COLORS (varchar(50)) in a my table SHIRTS that contains a comma delimited string such as 1,2,5,12,15,. Each number representing the available colors.
When running the query select * from shirts where colors like '%1%' to get all the red shirts (color=1), I also get the shirts whose color is grey (=12) and orange (=15).
How should I rewrite the query so that is selects ONLY the color 1 and not all colors containing the number 1?
The classic way would be to add commas to the left and right:
select * from shirts where CONCAT(',', colors, ',') like '%,1,%'
But find_in_set also works:
select * from shirts where find_in_set('1',colors) <> 0
FIND_IN_SET is your friend in this case
select * from shirts where FIND_IN_SET(1,colors)
Take a look at the FIND_IN_SET function for MySQL.
SELECT *
FROM shirts
WHERE FIND_IN_SET('1',colors) > 0
This will work for sure, and I actually tried it out:
lwdba#localhost (DB test) :: DROP TABLE IF EXISTS shirts;
Query OK, 0 rows affected (0.08 sec)
lwdba#localhost (DB test) :: CREATE TABLE shirts
-> (<BR>
-> id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
-> ticketnumber INT,
-> colors VARCHAR(30)
-> );<BR>
Query OK, 0 rows affected (0.19 sec)
lwdba#localhost (DB test) :: INSERT INTO shirts (ticketnumber,colors) VALUES
-> (32423,'1,2,5,12,15'),
-> (32424,'1,5,12,15,30'),
-> (32425,'2,5,11,15,28'),
-> (32426,'1,2,7,12,15'),
-> (32427,'2,4,8,12,15');
Query OK, 5 rows affected (0.06 sec)
Records: 5 Duplicates: 0 Warnings: 0
lwdba#localhost (DB test) :: SELECT * FROM shirts WHERE LOCATE(CONCAT(',', 1 ,','),CONCAT(',',colors,',')) > 0;
+----+--------------+--------------+
| id | ticketnumber | colors |
+----+--------------+--------------+
| 1 | 32423 | 1,2,5,12,15 |
| 2 | 32424 | 1,5,12,15,30 |
| 4 | 32426 | 1,2,7,12,15 |
+----+--------------+--------------+
3 rows in set (0.00 sec)
Give it a Try !!!
If the set of colors is more or less fixed, the most efficient and also most readable way would be to use string constants in your app and then use MySQL's SET type with FIND_IN_SET('red',colors) in your queries. When using the SET type with FIND_IN_SET, MySQL uses one integer to store all values and uses binary "and" operation to check for presence of values which is way more efficient than scanning a comma-separated string.
In SET('red','blue','green'), 'red' would be stored internally as 1, 'blue' would be stored internally as 2 and 'green' would be stored internally as 4. The value 'red,blue' would be stored as 3 (1|2) and 'red,green' as 5 (1|4).
select * from shirts where find_in_set('1',colors) <> 0
Works for me
If you're using MySQL, there is a method REGEXP that you can use...
http://dev.mysql.com/doc/refman/5.1/en/regexp.html#operator_regexp
So then you would use:
SELECT * FROM `shirts` WHERE `colors` REGEXP '\b1\b'
You should actually fix your database schema so that you have three tables:
shirt: shirt_id, shirt_name
color: color_id, color_name
shirtcolor: shirt_id, color_id
Then if you want to find all of the shirts that are red, you'd do a query like:
SELECT *
FROM shirt, color
WHERE color.color_name = 'red'
AND shirt.shirt_id = shirtcolor.shirt_id
AND color.color_id = shirtcolor.color_id
You can achieve this by following function.
Run following query to create function.
DELIMITER ||
CREATE FUNCTION `TOTAL_OCCURANCE`(`commastring` TEXT, `findme` VARCHAR(255)) RETURNS int(11)
NO SQL
-- SANI: First param is for comma separated string and 2nd for string to find.
return ROUND (
(
LENGTH(commastring)
- LENGTH( REPLACE ( commastring, findme, "") )
) / LENGTH(findme)
);
And call this function like this
msyql> select TOTAL_OCCURANCE('A,B,C,A,D,X,B,AB', 'A');
1. For MySQL:
SELECT FIND_IN_SET(5, columnname) AS result
FROM table
2.For Postgres SQL :
SELECT *
FROM TABLENAME f
WHERE 'searchvalue' = ANY (string_to_array(COLUMNNAME, ','))
Example
select *
from customer f
where '11' = ANY (string_to_array(customerids, ','))
All the answers are not really correct, try this:
select * from shirts where 1 IN (colors);
I am trying to convert a mysql_query to pdo equivalent
My table structure is (removed unrelated columns - the one needed for my question is other_id) :
CREATE TABLE `temp_table` (
`id` int(12) NOT NULL DEFAULT '0',
`other_id` int(12) NOT NULL DEFAULT '0',
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
And the data it has:
-----------------
| id | other_id |
-----------------
| 1 | 123 |
-----------------
| 2 | 0 |
-----------------
| 3 | 456 |
-----------------
| 4 | 0 |
-----------------
The previous database query was :
$sql = "SELECT id FROM temp_table WHERE other_id = '{$other_id}'";
$result = mysql_query($sql, $db)
return mysql_fetch_assoc($result);
The query is called with $other_id as NULL ("null" in php and not string or anything).
Result mysql_query : This gives my values 2,4
PDO equivalent code :
$sql = "SELECT id FROM temp_table WHERE other_id = :other_id";
$sth = $dbConnection->prepare($sql);
$sth->bindValue(":other_id", $other_id);
$sth->execute();
return $sth->fetchAll(PDO::FETCH_COLUMN);
Result PDO : This gives no values at all.
This is weird issue which I have not encountered before (since I am more java developer and recently touched PHP after few years).
As a workaround I had to put below line to return 2,4 as result from pdo output, but want to understand more about the difference mentioned above.
$sth->bindValue(":other_id", empty($other_id) ? 0 : $other_id);
I also tried $sth->bindValue(":other_id", $other_id, PDO::PARAM_INT); which did not help
This is not an issue with the mysql_ vs PDO difference, but rather with type-juggling.
In the first non-PDO example, $other_id is being converted to a string in the query, so your query looks like this:
SELECT id FROM temp_table WHERE other_id = '';
MySQL treats an empty string the same as 0 for fields of numeric type during queries, so your query is actually correctly matching two records.
In the PDO example since you're passing $other_id (which is null), your query is bound as follows:
SELECT id FROM temp_table WHERE other_id = NULL;
MySQL does not treat 0 as NULL, so the queries that you're sending are actually not the same.
This is weird issue which I have not encountered before.
There is nothing weird here.
When $other_id is NULL the first query becomes:
SELECT id FROM temp_table WHERE other_id = ''
Because the type of the other_id column is numeric, the provided value (the empty string) is converted to the number 0 and there are two matching rows.
On the other hand, the query sent through PDO is equivalent to:
SELECT id FROM temp_table WHERE other_id = NULL
Not only that there are no NULLs in the table, but this query never returns any row, even if there are rows having NULL in the column other_id. (The correct way to select the rows having NULL in other_id is to use the IS NULL operator).
I have a complicated request. I have to make a report in PHP like the picture below.
Part summarized yield must be filled with across calculation.
Like I explain in formula column, value in part summarized yield got from calculation between location yield and part summarized yield.
(See this formula)
How to do that calculation in a PHP report?
I already tried using a cursor, but it still did not work.
Here is my cursor calculation:
--In PHP file, i make query to insert data first to table tyield_summary
--Cursor to input yield_summary
Declare #nourut varchar(2), #maxnourut varchar(2), #bpnum varchar(20), #pnum varchar(20), #curnourut varchar(2), #psum decimal(18,2), #ysum decimal(18,2)
DECLARE StockCursor CURSOR
FOR
select no_urut, part_number, Part_Summary
from tyield_summary
where no_urut<>'99'
order by part_number desc, no_urut asc
set #bpnum=''
OPEN StockCursor
FETCH NEXT FROM StockCursor INTO #nourut, #pnum, #psum
WHILE ##FETCH_STATUS=0
BEGIN
if #bpnum=#pnum
begin
select top 1 #curnourut=no_urut
from tyield_summary
where part_number=#pnum
and no_urut<#nourut
order by no_urut desc
set #bpnum=#pnum
select #maxnourut = max(no_urut) from tyield_summary
where part_number=#pnum
update tyield_summary
set yield_summary = case when Part_Summary=0 then #psum else (Part_Summary*#psum)/100 end
where part_number=#pnum
and no_urut=#curnourut
end
else
begin
set #bpnum=#pnum
end
FETCH NEXT FROM StockCursor INTO #nourut, #pnum, #psum
END
CLOSE StockCursor
DEALLOCATE StockCursor
Here table structure :
I need to fill part_summary field using formula that i show in excel.
In formula show, calculation using cross field.
Here is how you can do it (without nasty cursors).
Just use row number over your ordering/aggregation criteria to find the next row,
left join each row with its next (not forgetting the aggregation criteria) and it's done.
Let's use an example (and here is how you properlu post a table structure):
create table Lazydude
(
Partno varchar(20) not null
,Seq int not null
,[Value] float not null
)
GO
insert into Lazydude (Partno, Seq, [Value])
values
('AAA', 1, 77.7)
,('BBB', 0, 2)
,('BBB', 3, 3)
,('BBB', 9, 5)
,('CCC', 1, 33.3)
,('CCC', 2, 33.3)
GO
and to select using row number and use the result in a self-join
with Temp as(
select Partno, Seq, [Value]
,ROW_NUMBER() OVER(ORDER BY Partno, Seq) AS [Row]
from Lazydude
)
select t0.Partno, t0.Seq, t0.[Value], t0.[Row]
, t1.row as [Next Row], t1.[Value] as [Next Value]
, case when t1.row is null
then t0.[Value]
else t0.Value * t1.Value
end as [The Calculation]
from Temp t0
left join Temp t1 on t1.[Row] = t0.[Row] + 1 and t1.Partno = t0.Partno
You can see the results in the SQL Fiddle
Partno Seq Value Row Next Row Next Value The Calculation
AAA 1 77.7 1 (null) (null) 77.7
BBB 0 2 2 3 3 6
BBB 3 3 3 4 5 15
BBB 9 5 4 (null) (null) 5
CCC 1 33.3 5 6 33.3 1108.8899999999999
CCC 2 33.3 6 (null) (null) 33.3