PIVOT in ORACLE 10G - oracle10g

I have following table in ORACLE 10G :-
AUTOID PLAN_DATE ITEM_CODE QTY
5 27-04-16 F10000010 10
7 27-04-16 F10000060 30
8 27-04-16 F10000020 10
Require OutPut is :-
Date
F10000010 F10000060 F10000020
27-04-16 10 30 10
As we know there is no pivot function available in oracle 10g. So how can we do this. Here Item_CODE is dynamic code which can change according to date.
Any help will be highly appericable.
Thanks
PREM

solved using following procedure:
CREATE OR REPLACE procedure dynamic_pivot(p_cursor in out sys_refcursor)
as
sql_query varchar2(1000) := 'select PLAN_DATE ';
begin
for x in (select distinct ITEM_CODE,qty from XXES_DAILY_PLAN_TRAN where PLAN_DATE='27-04-2016')
loop
sql_query := sql_query ||
' , sum(case when qty = '''||x.qty||''' then qty else 0 end) as '||x.ITEM_CODE;
dbms_output.put_line(sql_query);
end loop;
sql_query := sql_query || ' from XXES_DAILY_PLAN_TRAN where PLAN_DATE=''27-04-2016'' group by PLAN_DATE ';
dbms_output.put_line(sql_query);
open p_cursor for sql_query;
end;
//calling
variable x refcursor
exec dynamic_pivot(:x)
print x

Related

Trying to use temporary table in IBM DB2 and facing issues

I am getting the following error while creating a stored procedure for testing purpose:
SQL Error [42601]: An unexpected token "DECLARE GLOBAL TEMPORARY TABLE
SESSION" was found following "RSOR WITH RETURN FOR". Expected tokens may include: "".. SQLCODE=-104, SQLSTATE=42601, DRIVER=4.21.29
Code:
CREATE OR REPLACE PROCEDURE Test ( IN GE_OutPutType SMALLINT)
----------------------------------------------------------------------------------------------------
DYNAMIC RESULT SETS 1 LANGUAGE SQL
BEGIN
DECLARE C CURSOR WITH RETURN FOR DECLARE GLOBAL TEMPORARY TABLE
SESSION.TEMP (DATE CHAR(10) NOT NULL,
SALARY DECIMAL(9,
2) ,
COMM DECIMAL(9,
2));
INSERT
INTO
SESSION.TEMP (DATE,
SALARY,
COMM) SELECT
VARCHAR_FORMAT(CURRENT_DATE,
'MM/DD/YYYY'),
10.2,
11.5
FROM
sysibm.sysdummy1
IF GE_OutPutType = 1
BEGIN
SELECT
*
FROM
TEMP
ELSEIF GE_OutPutType = 2 SELECT
'HEADER' CONCAT SPACE(1979) CONCAT 'H'
FROM
sysibm.sysdummy1
END OPEN C;
END
Your syntax is not valid.
You must declare your temporary table independently of your cursor.
You cannot combine these in a single statement.
Use dynamic-SQL features to achieve what you need.
Use instead the format:
Declare c1 cursor with return to caller for Statement1
and
set v_cursor_text = 'select ... from session.temp ; `
then use
prepare Statement1 from v_cursor_text;
and before you exit the stored procedure you need to leave the cursor opened:
open c1;
Do study the Db2 online documentation to learn more about these features.
Here is a small fragment of your procedure showing what I mean:
CREATE OR REPLACE PROCEDURE mytest ( IN GE_OutPutType SMALLINT)
DYNAMIC RESULT SETS 1
LANGUAGE SQL
specific mytest
BEGIN
DECLARE v_cursor_text varchar(1024);
DECLARE C1 CURSOR WITH RETURN FOR Statement1;
DECLARE GLOBAL TEMPORARY TABLE SESSION.TEMP (
DATE CHAR(10) NOT NULL,
SALARY DECIMAL(9,
2) ,
COMM DECIMAL(9,
2))
with replace on commit preserve rows not logged;
INSERT INTO SESSION.TEMP (DATE, SALARY, COMM)
SELECT VARCHAR_FORMAT(CURRENT_DATE, 'MM/DD/YYYY'),
10.2,
11.5
FROM sysibm.sysdummy1 ;
if GE_OutPutType = 1
then
set v_cursor_text = 'select * from session.temp';
end if;
if GE_OutPutType = 2
then
set v_cursor_text = 'select ''header'' concat space(1979) concat ''H'' from sysibm.sysdummy1';
end if;
prepare Statement1 from v_cursor_text;
open c1;
END#

Db2 stored procedure error (not valid in the context where it is used)

My issue is when i call the stored procedure it is not working
drop procedure DELETE_WITH_COMMIT_COUNT1
DB20000I The SQL command completed successfully.
CREATE PROCEDURE DELETE_WITH_COMMIT_COUNT1(IN v_TABLE_NAME VARCHAR(24), IN v_COMMIT_COUNT INTEGER )
NOT DETERMINISTIC
LANGUAGE SQL
P1 : BEGIN
-- DECLARE Statements
DECLARE SQLCODE INTEGER;
DECLARE v_DELETE_QUERY VARCHAR(1024);
DECLARE v_DELETE_STATEMENT STATEMENT;
P2 : BEGIN
DECLARE V1 CHAR(24) FOR BIT DATA;
DECLARE V2 CHAR(24) FOR BIT DATA ;
DECLARE cur1 CURSOR WITH RETURN TO CLIENT FOR select min(MESSAGE_ID),max(MESSAGE_ID) from TESTING where TIMESTAMP between (select TIMESTAMP(date(min(timestamp))) from TESTING with ur) and (select TIMESTAMP(date(min(timestamp))) + 1 day from TESTING with ur) ;
OPEN cur1;
FETCH FROM cur1 INTO V1, V2;
SET v_DELETE_QUERY = 'DELETE FROM (SELECT 1 FROM ' || v_TABLE_NAME || ' WHERE MESSAGE_ID between V1 and V2 '
|| ' FETCH FIRST ' || RTRIM(CHAR(v_COMMIT_COUNT)) || ' ROWS ONLY) AS DELETE_TABLE';
PREPARE v_DELETE_STATEMENT FROM v_DELETE_QUERY;
DEL_LOOP:
LOOP
EXECUTE v_DELETE_STATEMENT;
IF SQLCODE = 100 THEN
LEAVE DEL_LOOP;
END IF;
COMMIT;
END LOOP;
COMMIT;
END P2;
END P1
DB20000I The SQL command completed successfully.
My procedure was created successfully but when I call it the following issue happens:
db2 "call DELETE_WITH_COMMIT_COUNT1 ('TESTING',50)" SQL0206N "V1" is
not valid in the context where it is used. SQLSTATE=42703
More information :
db2 "select min(MESSAGE_ID),max(MESSAGE_ID) from TESTING where
TIMESTAMP between (select TIMESTAMP(date(min(timestamp))) from TESTING
with ur) and (select TIMESTAMP(date(min(timestamp))) + 1 day from
TESTING with ur) "
1 2
--------------------------------------------------- ---------------------------------------------------
x'4B5753313032353039313133392020202020202020202020' x'4B5753313032353039313230302020202020202020202020'
1 record(s) selected.
I want to delete the records between these values and i currently have 99 records between minimum and maximum message id
message_id column is defined as CHAR(24) FOR BIT DATA on the table .
Your problem is with this statement:
SET v_DELETE_QUERY = 'DELETE FROM (SELECT 1 FROM ' || v_TABLE_NAME || ' WHERE MESSAGE_ID between V1 and V2 '
|| ' FETCH FIRST ' || RTRIM(CHAR(v_COMMIT_COUNT)) || ' ROWS ONLY) AS DELETE_TABLE';
In this statement, it looks like you want to use the values of your previously declared variables, V1 and V2:
' WHERE MESSAGE_ID between V1 and V2 '
DB2 sees this as a string literal. Instead, try changing this part of the statement like so:
SET v_DELETE_QUERY = 'DELETE FROM (SELECT 1 FROM ' || v_TABLE_NAME || ' WHERE MESSAGE_ID between ' || V1 || ' and ' || V2
|| ' FETCH FIRST ' || RTRIM(CHAR(v_COMMIT_COUNT)) || ' ROWS ONLY) AS DELETE_TABLE';

Postgres Function returning no results but when the same query returns results outside function

I am running a simple postgres function to return the count of rows. I am able to run the same query outside function with the output of raise option , but the function doesn't return any rows. I have tried different ways to produce results but unable to. Please find my function below,
CREATE OR REPLACE FUNCTION my_schema.usp_spellcheck3(SearchORItems_WithPipe varchar, site varchar, lan varchar, rows_display integer)
RETURNS TABLE (docmnt int) AS $BODY$
DECLARE
arrSearchTerms text[];
NewTerm varchar;
i integer;
AltSearch_withComma varchar;
AltSearch_withPipe varchar;
strDidYouMean varchar;
dpDidYouMean double precision;
txtDidYouMean Text;
SearchORItems_withComma varchar;
SearchORItems varchar;
SearchORItem varchar;
ws varchar;
arrSearchORItems_withComma varchar[];
BEGIN
strDidYouMean = 'DidYouMeanRow';
dpDidYouMean = 0.0;
txtDidYouMean = 'DidYouMeanRow';
ws = '''' || '%' || site || '%' || '''' ;
RAISE NOTICE '%', ws;
SearchORItems = REPLACE(SearchORItems_WithPipe, '|', ',');
SELECT regexp_split_to_array(SearchORItems, ',') INTO arrSearchORItems_withComma;
RAISE NOTICE '%', SearchORItems;
FOR i IN 1 .. coalesce(array_upper(arrSearchORItems_withComma, 1), 1) LOOP
IF (i = 1) THEN
SearchORItems_withComma = '''' || arrSearchORItems_withComma[i] || '''';
ELSE
SearchORItems_withComma = SearchORItems_withComma||','||'''' || arrSearchORItems_withComma[i] || '''';
END IF;
END LOOP;
RAISE NOTICE '%',SearchORItems_withComma;
SELECT COUNT(*) INTO res_count
FROM (
SELECT 1 FROM my_schema.features f , my_schema.documents d
WHERE term IN (SearchORItems_withComma)
AND d.docid = f.docid
AND d.url LIKE ws
GROUP BY f.docid, d.url) t;
RAISE NOTICE '%', res_count;
SearchORItem = 'SELECT COUNT(*) INTO res_count
FROM (SELECT 1 FROM my_schema.features f , my_schema.documents d
WHERE term IN ('||SearchORItems_withComma||')
AND d.docid = f.docid AND d.url LIKE ' || ws ||'
GROUP BY f.docid, d.url) t';
RAISE NOTICE '%',SearchORItem;
END;
$BODY$ LANGUAGE SQL VOLATILE;
this is my query output :
NOTICE: '%uni%'
NOTICE: daniel,data
NOTICE: 'daniel','data'
NOTICE: 0
NOTICE: select count(*) into res_count
from ( select 1 from my_schema.features f , my_schema.documents d
where term in ('daniel','data')
and d.docid=f.docid and d.url like '%uni%'
group by f.docid,d.url)t
Total query runtime: 16 ms.
0 rows retrieved.
I dont know where I'm going wrong, any help would be appreciated .. Thanks..
The simple reason that nothing is returned is that you have no RETURN statements in your code. When a function RETURNS TABLE you need to explicitly put one or more RETURN NEXT or RETURN QUERY statements in the body of your code, with a final RETURN statement to indicate the end of the function. See the documentation here: http://www.postgresql.org/docs/9.3/static/plpgsql-control-structures.html#PLPGSQL-STATEMENTS-RETURNING. What exactly you want to return is not clear but likely candidates are res_count and d.docid.
Other than that, your code could use a real clean-up reducing clutter like:
ws := '''%' || site || '%''' ;
instead of:
ws = '''' || '%' || site || '%' || '''' ;
and:
SELECT COUNT(*) INTO res_count
FROM my_schema.features f,
JOIN my_schema.documents d ON d.docid = f.docid
WHERE term IN (SearchORItems_withComma)
AND d.url LIKE ws
GROUP BY f.docid, d.url;
instead of:
SELECT COUNT(*) INTO res_count
FROM (
SELECT 1 FROM my_schema.features f , my_schema.documents d
WHERE term IN (SearchORItems_withComma)
AND d.docid = f.docid
AND d.url LIKE ws
GROUP BY f.docid, d.url) t;
And you should use the assignment operator (:=) instead of the equality operator in any plpgsql statement that is not a SQL statement.

Stored procedure which returns table and being returned table gathers 1 rows from 9000 table

I need a stored function which returns table and being returned table gathers 1 rows from all 9000 tables.
Prerequisites:
There are same structure 9000 tables.
Table name are subsquent, like X1000 X1001 .... X9999.
Issue query to all table and get 1 rows from all table.
Gather all rows and return as 1 table.
Envirnment are PostgreSQL 9.3 and PL/Pgsql
I hope you will give me some easy example.
I think I can not understand how to deal variable value.
I made myself, however it didn't work.
create or replace function ListOfInc
(ddate int) returns table(col1 int, col2 dec) as $$
declare
numofrow integer;
begin
for i in 1000..9999 loop
numofrow := execute 'select count(*) from x' || i || ';';
if numofrow > 0 then
return query execute 'select ' || i || ' Trunc(100 * (CAST(Lag(adjust) over (order by'
|| %1 || ') as dec) / CAST(adjust as dec) - 1) ,2) from x' || i || ' order by ' || %1 || ' desc limit 1;';
end if;
end loop;
end;
$$ language plpgsql;
This is an ideal job for PostgreSQL's inheritance features.
If you want to avoid inheritance, you'll need to build a union all of all the tables using dynamic SQL, or loop over each table using RETURN QUERY EXECUTE ....
Personally I recommend consolodating all the data into one table. Create views to emulate the old structure, so your apps don't notice the change.

Self-managing PostgreSQL partition tables

I am trying to make a self-managing partition table setup with Postgres. It all revolves around this function but I can't seem to get Postgres to accept my table names. Any ideas or examples of self-managing partition table trigger functions?
My current function:
DECLARE
day integer;
year integer;
tablename text;
startdate text;
enddate text;
BEGIN
day:=date_part('doy',to_timestamp(NEW.date));
year:=date_part('year',to_timestamp(NEW.date));
tablename:='pings_'||year||'_'||day||'_'||NEW.id;
-- RAISE EXCEPTION 'tablename=%',tablename;
PERFORM 'tablename' FROM pg_tables WHERE 'schemaname'=tablename;
-- RAISE EXCEPTION 'found=%',FOUND;
IF FOUND <> TRUE THEN
startdate:=date_part('year',to_timestamp(NEW.date))||'-'||date_part('month',to_timestamp(NEW.date))||'-'||date_part('day',to_timestamp(NEW.date));
enddate:=startdate::timestamp + INTERVAL '1 day';
EXECUTE 'CREATE TABLE $1 (
CHECK ( date >= DATE $2 AND date < DATE $3 )
) INHERITS (pings)' USING quote_ident(tablename),startdate,enddate;
END IF;
EXECUTE 'INSERT INTO $1 VALUES (NEW.*)' USING quote_ident(tablename);
RETURN NULL;
END;
I want it to auto-create a table called pings_YEAR_DOY_ID but it always fails with:
2011-10-24 13:39:04 CDT [15804]: [1-1] ERROR: invalid input syntax for type double precision: "-" at character 45
2011-10-24 13:39:04 CDT [15804]: [2-1] QUERY: SELECT date_part('year',to_timestamp( $1 ))+'-'+date_part('month',to_timestamp( $2 ))+'-'+date_part('day',to_timestamp( $3 ))
2011-10-24 13:39:04 CDT [15804]: [3-1] CONTEXT: PL/pgSQL function "ping_partition" line 15 at assignment
2011-10-24 13:39:04 CDT [15804]: [4-1] STATEMENT: INSERT INTO pings VALUES (0,0,5);
TRY 2
After applying the changes and modifying it some more (date is a unixtimestamp column, my thinking being that an integer column is faster than a timestamp column when selecting). I get the below error, not sure if I am using the proper syntax for USING NEW?
Updated function:
CREATE FUNCTION ping_partition() RETURNS trigger
LANGUAGE plpgsql
AS $_$DECLARE
day integer;
year integer;
tablename text;
startdate text;
enddate text;
BEGIN
day:=date_part('doy',to_timestamp(NEW.date));
year:=date_part('year',to_timestamp(NEW.date));
tablename:='pings_'||year||'_'||day||'_'||NEW.id;
-- RAISE EXCEPTION 'tablename=%',tablename;
PERFORM 'tablename' FROM pg_tables WHERE 'schemaname'=tablename;
-- RAISE EXCEPTION 'found=%',FOUND;
IF FOUND <> TRUE THEN
startdate := to_char(to_timestamp(NEW.date), 'YYYY-MM-DD');
enddate:=startdate::timestamp + INTERVAL '1 day';
EXECUTE 'CREATE TABLE ' || quote_ident(tablename) || ' (
CHECK ( date >= EXTRACT(EPOCH FROM DATE ' || quote_literal(startdate) || ')
AND date < EXTRACT(EPOCH FROM DATE ' || quote_literal(enddate) || ') )
) INHERITS (pings)';
END IF;
EXECUTE 'INSERT INTO ' || quote_ident(tablename) || ' SELECT $1' USING NEW;
RETURN NULL;
END;
$_$;
My statement:
INSERT INTO pings VALUES (0,0,5);
SQL error:
ERROR: column "date" is of type integer but expression is of type pings
LINE 1: INSERT INTO pings_1969_365_0 SELECT $1
^
HINT: You will need to rewrite or cast the expression.
QUERY: INSERT INTO pings_1969_365_0 SELECT $1
CONTEXT: PL/pgSQL function "ping_partition" line 22 at EXECUTE statement
Note: Since Postgres 10 declarative partitioning is typically superior to partitioning by inheritance as used in this case.
You are mixing double precision output of date_part() with text '-'. That doesn't make sense to PostgreSQL. You would need an explicit cast to text. But there is a much simpler way to do all of this:
startdate:=date_part('year',to_timestamp(NEW.date))
||'-'||date_part('month',to_timestamp(NEW.date))
||'-'||date_part('day',to_timestamp(NEW.date));
Use instead:
startdate := to_char(NEW.date, 'YYYY-MM-DD');
This makes no sense either:
EXECUTE 'CREATE TABLE $1 (
CHECK (date >= DATE $2 AND date < DATE $3 )
) INHERITS (pings)' USING quote_ident(tablename),startdate,enddate;
You can only supply values with the USING clause. Read the manual here. Try instead:
EXECUTE 'CREATE TABLE ' || quote_ident(tablename) || ' (
CHECK ("date" >= ''' || startdate || ''' AND
"date" < ''' || enddate || '''))
INHERITS (ping)';
Or better yet, use format(). See below.
Also, like #a_horse answered: You need to put your text values in single quotes.
Similar here:
EXECUTE 'INSERT INTO $1 VALUES (NEW.*)' USING quote_ident(tablename);
Instead:
EXECUTE 'INSERT INTO ' || quote_ident(tablename) || ' VALUES ($1.*)'
USING NEW;
Related answer:
How to dynamically use TG_TABLE_NAME in PostgreSQL 8.2?
Aside: While "date" is allowed for a column name in PostgreSQL it is a reserved word in every SQL standard. Don't name your column "date", it leads to confusing syntax errors.
Complete working demo
CREATE TABLE ping (ping_id integer, the_date date);
CREATE OR REPLACE FUNCTION trg_ping_partition()
RETURNS trigger
LANGUAGE plpgsql SET client_min_messages = 'WARNING' AS
$func$
DECLARE
_schema text := 'public'; -- double-quoted if necessary
_tbl text := to_char(NEW.the_date, '"ping_"YYYY_DDD_') || NEW.ping_id;
BEGIN
EXECUTE format('CREATE TABLE IF NOT EXISTS %1$s.%2$s
(CHECK (the_date >= %3$L
AND the_date < %4$L)) INHERITS (%1$s.ping)'
, _schema -- %1$s
, _tbl -- %2$s -- legal(!) name needs no quotes
, to_char(NEW.the_date, 'YYYY-MM-DD') -- %3$L
, to_char(NEW.the_date + 1, 'YYYY-MM-DD') -- %4$L
);
EXECUTE 'INSERT INTO ' || _tbl || ' VALUES ($1.*)'
USING NEW;
RETURN NULL;
END
$func$;
CREATE TRIGGER insbef
BEFORE INSERT ON ping
FOR EACH ROW EXECUTE FUNCTION trg_ping_partition();
Postgres 9.1 added the clause IF NOT EXISTS for CREATE TABLE. See:
PostgreSQL create table if not exists
Postgres 11 added the more appropriate syntax variant EXECUTE FUNCTION for triggers. Use EXECUTE PROCEDURE in older versions.
See:
Trigger uses a procedure or a function?
to_char() can take a date as $1. That's converted to timestamp automatically. See:
The manual on date / time functions.
I SET client_min_messages = 'WARNING' for the scope of the function to silence the flood of notices that would otherwise be raised on conflict by IF NOT EXISTS.
Multiple other simplifications and improvements. Compare the code.
Tests:
INSERT INTO ping VALUES (1, now()::date);
INSERT INTO ping VALUES (2, now()::date);
INSERT INTO ping VALUES (2, now()::date + 1);
INSERT INTO ping VALUES (2, now()::date + 1);
fiddle
OLD sqlfiddle
Dynamic partitioning in PostgreSQL is just a bad idea. Your code is not safe in a multi-user environment. For it to be safe you would have to use locks, which slows down execution. The optimal number of partitions is about one hundred. You can easily create that many well in advance to dramatically simplify the logic necessary for partitioning.
You need to put your date literals in single quotes. Currently you are executing something like this:
CHECK ( date >= DATE 2011-10-25 AND date < DATE 2011-11-25 )
which is invalid. In this case 2011-10-25 is interpreted as 2011 minus 10 minus 25
Your code needs to create the SQL using single quotes around the date literal:
CHECK ( date >= DATE '2011-10-25' AND date < DATE '2011-11-25' )
I figured out the entirety and it works great, even have an auto-delete after 30 days. I hope this helps out future people looking for an autopartition trigger function.
CREATE FUNCTION ping_partition() RETURNS trigger
LANGUAGE plpgsql
AS $_$
DECLARE
_keepdate text;
_tablename text;
_startdate text;
_enddate text;
_result record;
BEGIN
_keepdate:=to_char(to_timestamp(NEW.date) - interval '30 days', 'YYYY-MM-DD');
_startdate := to_char(to_timestamp(NEW.date), 'YYYY-MM-DD');
_tablename:='pings_'||NEW.id||'_'||_startdate;
PERFORM 1
FROM pg_catalog.pg_class c
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind = 'r'
AND c.relname = _tablename
AND n.nspname = 'pinglog';
IF NOT FOUND THEN
_enddate:=_startdate::timestamp + INTERVAL '1 day';
EXECUTE 'CREATE TABLE pinglog.' || quote_ident(_tablename) || ' (
CHECK ( date >= EXTRACT(EPOCH FROM DATE ' || quote_literal(_startdate) || ')
AND date < EXTRACT(EPOCH FROM DATE ' || quote_literal(_enddate) || ')
AND id = ' || quote_literal(NEW.id) || '
)
) INHERITS (pinglog.pings)';
EXECUTE 'CREATE INDEX ' || quote_ident(_tablename||'_indx1') || ' ON pinglog.' || quote_ident(_tablename) || ' USING btree (microseconds) WHERE microseconds IS NULL';
EXECUTE 'CREATE INDEX ' || quote_ident(_tablename||'_indx2') || ' ON pinglog.' || quote_ident(_tablename) || ' USING btree (date, id)';
EXECUTE 'CREATE INDEX ' || quote_ident(_tablename||'_indx3') || ' ON pinglog.' || quote_ident(_tablename) || ' USING btree (date, id, microseconds) WHERE microseconds IS NULL';
END IF;
EXECUTE 'INSERT INTO ' || quote_ident(_tablename) || ' VALUES ($1.*)' USING NEW;
FOR _result IN SELECT * FROM pg_tables WHERE schemaname='pinglog' LOOP
IF char_length(substring(_result.tablename from '[0-9-]*$')) <> 0 AND (to_timestamp(NEW.date) - interval '30 days') > to_timestamp(substring(_result.tablename from '[0-9-]*$'),'YYYY-MM-DD') THEN
-- RAISE EXCEPTION 'timestamp=%,table=%,found=%',to_timestamp(substring(_result.tablename from '[0-9-]*$'),'YYYY-MM-DD'),_result.tablename,char_length(substring(_result.tablename from '[0-9-]*$'));
-- could have it check for non-existant ids as well, or for archive bit and only delete if the archive bit is not set
EXECUTE 'DROP TABLE ' || quote_ident(_result.tablename);
END IF;
END LOOP;
RETURN NULL;
END;
$_$;